Net_Mode :: enum { DEDICATED_SERVER; LISTEN_SERVER; CLIENT; } LOBBY_PORT :: 27015; MAX_LOBBIES :: 8; lobbies : [MAX_LOBBIES] Lobby; num_lobbies: s32; Lobby :: struct { name: string; address: Net_Address; } Client_Id :: #type, isa s64; Client_Connection :: struct { id: Client_Id; address: Net_Address; } Server_Connection :: struct { address: Net_Address; } Server_Data :: struct { client_connections: [16] Client_Connection; num_client_connections: s64; } Client_Data :: struct { client_id: Client_Id; server_connection: Server_Connection; } Net_Data :: struct { net_mode: Net_Mode; socket: Socket.SOCKET; server: Server_Data; client: Client_Data; } net_data: Net_Data; Net_Address :: struct { address: Socket.sockaddr_in; valid: bool; } Entity_Network_State :: enum { LOCAL; LOCAL_REPLICATED; PROXY; } pending_net_messages : [..] Net_Message; messages_to_send : [..] Prepared_Net_Message; Join_Lobby_Data :: struct { } Net_Message_Type :: enum { INVALID; LOOKING_FOR_LOBBY; LOBBY_INFO; JOIN_LOBBY; CLIENT_ACCEPTED; MESSAGE_RESPONSE; START_GAME; CLIENT_INPUT; SPAWN_ENTITY; PICKUP_EVENT; TRANSFORM_UPDATE; } Net_Message_Header :: struct { type : Net_Message_Type; size: s64; timestamp: float64; } Prepared_Net_Message :: struct { to: Net_Address; message: Net_Message; } Net_Message :: struct { header: Net_Message_Header; body : [1024] u8; } // Create a lobby/game // Search for lobbies // Join a lobby // Start a game Net_Networking_State :: enum { IDLE; SHOULD_LOOK_FOR_LOBBIES; LOOKING_FOR_LOBBIES; CREATING_LOBBY; CREATED_LOBBY; JOINING_LOBBY; IN_LOBBY; READY_FOR_GAME_START; IN_GAME; } LOBBY_NAME_SIZE :: 32; Lobby_Info_Data :: struct { lobby_name: [LOBBY_NAME_SIZE] u8; name_len: u8; } Client_Joined_Data :: struct { accepted: bool; id: Client_Id; } Message_Response_Data :: struct { accepted: bool; } Client_Input :: enum_flags { UP; DOWN; LEFT; RIGHT; JUMP; SHOOT; } Client_Input_Data :: struct { client_id: Client_Id; flags: Client_Input; } Spawn_Entity_Data :: struct { type: Type; remote_id: Entity_Id; client_id: Client_Id; spawn_position: Vector3; } Transform_Update_Data :: struct { entity_id: Entity_Id; position: Vector3; rotation: Quaternion; } Player_Update_Data :: struct { entity_id: Entity_Id; position: Vector3; yaw: float; pitch: float; } message_mutex: Mutex; update_mutex: Mutex; net_init :: () { net_log("Init\n"); data : Socket.WSAData; if Socket.WSAStartup(0x0202, *data) != 0 { assert(false); net_log("Couldn't initialize Lyn\n"); } if thread_init(*thread, networking_thread_proc) { thread_start(*thread);; } if thread_init(*message_thread, net_check_for_messages) { thread_start(*message_thread);; } init(*update_mutex); init(*message_mutex); init(*internal_message_mutex); } net_get_state :: () -> Net_Networking_State { return state; } net_is_multiplayer :: () -> bool { return state != .IDLE; } net_is_client :: () -> bool { return net_data.net_mode == .CLIENT; } net_is_server :: () -> bool { return net_data.net_mode != .CLIENT; } net_can_start_game :: () -> bool { return state == .READY_FOR_GAME_START; } net_create_lobby :: () { state = .CREATING_LOBBY; } net_check_for_lobbies :: () { state = .SHOULD_LOOK_FOR_LOBBIES; } net_start_game :: () { message := net_new_message(.START_GAME, 0); for 0..net_data.server.num_client_connections - 1 { client := net_data.server.client_connections[it]; net_send_message(*message, net_data.socket, client.address); } //state = .IN_GAME; } net_cancel :: () { // @Incomplete: Tell all clients to disconnect Socket.closesocket(net_data.socket); state = .IDLE; } //net_pickup_item :: (player: *Player, item: *Item) { // message := net_new_message(.PICKUP_EVENT, 0); // net_send_message(*message, net_game_info.socket, net_game_info.other); // state = .IN_GAME; //} net_new_message :: (type: Net_Message_Type, size: s64) -> Net_Message { message : Net_Message; message.header.type = type; message.header.size = size; //message.body = alloc(size,temp); return message; } //net_send_message :: (message: *Net_Message) { // net_send_message(message, net_game_info.socket, net_game_info.other); //} #scope_file net_log :: inline (message: string, args: .. Any) { log(tprint("NET: %", message), args); } net_log_error :: inline (message: string, args: .. Any) { log_error(tprint("NET: %", message), args); } networking_thread_proc :: (thread: *Thread) -> s64 { while true { update_networking(); sleep_milliseconds(10); } return 0; } net_check_for_messages :: (thread: *Thread) -> s64 { while true { success, message, address := net_read_message(net_data.socket); if success { lock(*internal_message_mutex); defer unlock(*internal_message_mutex); pending: Internal_Pending_Message; pending.message = message; pending.address = address; array_add(*internal_pending_messages, pending); } } return 0; } net_broadcast_message :: (message: *Net_Message) { net_send_message(message, net_data.socket, broadcast=true); } net_send_message :: (msg: Net_Message, s: Socket.SOCKET, address: Net_Address = .{}, broadcast: bool = false) { message := msg; message.header.timestamp = to_float64_seconds(current_time_monotonic()); buffer : [1024] u8; memcpy(buffer.data, *message.header, size_of(Net_Message_Header)); memcpy(*buffer[size_of(Net_Message_Header)], message.body.data, message.header.size); wanted_size := size_of(Net_Message_Header) + message.header.size; bytes_sent : s32; if broadcast { broadcast_addr : Socket.sockaddr_in; broadcast_addr.sin_family = Socket.AF_INET; broadcast_addr.sin_port = Socket.htons(LOBBY_PORT); broadcast_addr.sin_addr.S_un.S_addr = Socket.INADDR_BROADCAST; bytes_sent = Socket.sendto(net_data.socket, buffer.data, xx wanted_size, 0, cast(*Socket.sockaddr)*broadcast_addr, size_of(Socket.sockaddr_in)); } else { if address.valid { bytes_sent = Socket.sendto(net_data.socket, buffer.data, xx wanted_size, 0, cast(*Socket.sockaddr)*address.address, size_of(Socket.sockaddr_in)); } else { bytes_sent = Socket.send(s, buffer.data, xx wanted_size, 0); } } if bytes_sent != wanted_size { net_log_error("Couldn't send message through socket. Wrong size.\n"); } if bytes_sent == Socket.SOCKET_ERROR { net_log_error("Couldn't send message through socket. Socket error %.\n", Socket.WSAGetLastError()); } } net_read_message :: (s: Socket.SOCKET) -> bool, Net_Message, Net_Address { BUFFER_SIZE :: 1024; buffer : [BUFFER_SIZE] u8; addr: Net_Address; addr.valid = true; addr_len : Socket.socklen_t = size_of(Socket.sockaddr_in); // First receive the header bytes_received := Socket.recvfrom(s, buffer.data, BUFFER_SIZE, 0, cast(*Socket.sockaddr)*addr.address, *addr_len); if bytes_received <= 0 return false, .{}, addr; message : Net_Message; memcpy(*message.header, buffer.data, size_of(Net_Message_Header)); //assert(bytes_received == message.header.size + size_of(Message_Header)); //if bytes_received != size_of(Message_Header) + message.header.size return false, .{}; //message.body = alloc(message.header.size,, temp); memcpy(message.body.data, *buffer[size_of(Net_Message_Header)], message.header.size); return true, message, addr; } net_send_message :: (type: Net_Message_Type, data: $T, s: Socket.SOCKET, address: Net_Address) { message: Net_Message; message.header.timestamp = to_float64_seconds(current_time_monotonic()); message.header.size = size_of(T); message.header.type = type; buffer : [1024] u8; memcpy(buffer.data, *message.header, size_of(Net_Message_Header)); memcpy(*buffer[size_of(Net_Message_Header)], *data, message.header.size); wanted_size := size_of(Net_Message_Header) + message.header.size; bytes_sent : s32; if address.valid { bytes_sent = Socket.sendto(net_data.socket, buffer.data, xx wanted_size, 0, cast(*Socket.sockaddr)*address.address, size_of(Socket.sockaddr_in)); } else { bytes_sent = Socket.send(s, buffer.data, xx wanted_size, 0); } if bytes_sent != wanted_size { net_log_error("Couldn't send message through socket. Wrong size.\n"); } if bytes_sent == Socket.SOCKET_ERROR { net_log_error("Couldn't send message through socket. Socket error.\n"); } } on_message_received :: (message: Net_Message, address: Net_Address) { if message.header.type == { case .LOOKING_FOR_LOBBY; { if net_is_server() { message := net_new_message(.LOBBY_INFO, size_of(Lobby_Info_Data)); data : Lobby_Info_Data; str := "My First Lobby"; data.name_len = xx str.count; memcpy(data.lobby_name.data, str.data, str.count); memcpy(message.body.data, *data, size_of(Lobby_Info_Data)); data.lobby_name[data.name_len] = #char "\0"; net_send_message(*message, net_data.socket, address); } } case .LOBBY_INFO; { data := cast(*Lobby_Info_Data)message.body.data; lobby : Lobby; lobby.address = address; lobby.name = alloc_string(data.name_len); memcpy(lobby.name.data, data.lobby_name.data, data.name_len); duplicate := false; for 0..num_lobbies-1 { if lobbies[it].name == lobby.name { duplicate = true; break; } } if !duplicate { lobbies[num_lobbies] = lobby; num_lobbies += 1; net_log("Got a lobby info message: %\n", lobby.name); net_send_message(.JOIN_LOBBY, Join_Lobby_Data.{}, net_data.socket, address); state = .JOINING_LOBBY; } else { free(lobby.name); } } case .CLIENT_ACCEPTED; { data := cast(*Client_Joined_Data)message.body.data; state = .IN_LOBBY; net_data.client.client_id = data.id; net_data.client.server_connection.address = address; net_log("Joined lobby as Client with id %!\n", net_data.client.client_id); } case .JOIN_LOBBY; { assert(net_is_server()); connection: Client_Connection; connection.address = address; connection.id = cast(Client_Id)(net_data.server.num_client_connections + 1); net_data.server.client_connections[net_data.server.num_client_connections] = connection; net_data.server.num_client_connections += 1; data: Client_Joined_Data; data.accepted = true; data.id = connection.id; net_send_message(.CLIENT_ACCEPTED, data, net_data.socket, address); state = .READY_FOR_GAME_START; } case .START_GAME; { { lock(*update_mutex); defer unlock(*update_mutex); array_add(*pending_net_messages, message); } } case .SPAWN_ENTITY; { { lock(*update_mutex); defer unlock(*update_mutex); array_add(*pending_net_messages, message); } } case .TRANSFORM_UPDATE; { lock(*update_mutex); defer unlock(*update_mutex); array_add(*pending_net_messages, message); } case; { lock(*update_mutex); defer unlock(*update_mutex); array_add(*pending_net_messages, message); } } } update_networking :: () { reset_temporary_storage(); { lock(*message_mutex); defer unlock(*message_mutex); for * messages_to_send { if net_is_client() { print("Sending message of type %\n", it.message.header.type); } net_send_message(it.message, net_data.socket, it.to); } messages_to_send.count = 0; } { lock(*internal_message_mutex); defer unlock(*internal_message_mutex); for internal_pending_messages { on_message_received(it.message, it.address); } internal_pending_messages.count = 0; } if state == { case .IDLE; { } case .CREATING_LOBBY; { net_data.socket = Socket.socket(Socket.AF_INET, .SOCK_DGRAM, xx 0); if net_data.socket < 0 { net_log_error("Could not create socket for lobby\n"); state = .IDLE; } else { server_address : Socket.sockaddr_in; server_address.sin_family = Socket.AF_INET; server_address.sin_port = Socket.htons(LOBBY_PORT); // or whatever port you'd like to listen to server_address.sin_addr.S_un.S_addr = Socket.INADDR_ANY; if Socket.bind(net_data.socket, cast(*Socket.sockaddr)*server_address, size_of(Socket.sockaddr_in)) < 0 { Socket.closesocket(net_data.socket); net_log_error("Could not bind socket for lobby to port %\n"); state = .IDLE; } else { state = .CREATED_LOBBY; net_log("Created lobby\n"); net_data.net_mode = .LISTEN_SERVER; connection: Client_Connection; connection.address = .{}; connection.id = cast(Client_Id)(net_data.server.num_client_connections + 1); net_data.server.client_connections[net_data.server.num_client_connections] = connection; net_data.server.num_client_connections += 1; } } } case .CREATED_LOBBY; { } case .SHOULD_LOOK_FOR_LOBBIES; { net_data.net_mode = .CLIENT; net_data.socket = Socket.socket(Socket.AF_INET, .SOCK_DGRAM, xx 0); if net_data.socket < 0 { net_log_error("Could not create socket to look for lobbies\n"); state = .IDLE; } else { true_flag : u8 = 1; if Socket.setsockopt(net_data.socket, Socket.SOL_SOCKET, Socket.SO_BROADCAST, *true_flag, size_of(int)) < 0 { Socket.closesocket(net_data.socket); state = .IDLE; net_log_error("Could not set broadcast setting on socket\n"); } else { state = .LOOKING_FOR_LOBBIES; net_log("Looking for lobbies\n"); } } } case .LOOKING_FOR_LOBBIES; { message := net_new_message(.LOOKING_FOR_LOBBY, 0); net_broadcast_message(*message); } } //net_check_for_messages(); } Internal_Pending_Message :: struct { message: Net_Message; address: Net_Address; } internal_pending_messages : [..] Internal_Pending_Message; internal_message_mutex: Mutex; thread : Thread; message_thread : Thread; state : Net_Networking_State; #import "Thread"; #import "Basic"; Socket :: #import "Socket";