Initial commit
This commit is contained in:
596
networking/networking.jai
Normal file
596
networking/networking.jai
Normal file
@@ -0,0 +1,596 @@
|
||||
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";
|
||||
Reference in New Issue
Block a user