Files
coven/networking/networking.jai
2025-06-30 23:34:34 +02:00

597 lines
16 KiB
Plaintext

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: Thread.Mutex;
update_mutex: Thread.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.thread_init(*thread, networking_thread_proc) {
Thread.thread_start(*thread);;
}
if Thread.thread_init(*message_thread, net_check_for_messages) {
Thread.thread_start(*message_thread);;
}
Thread.init(*update_mutex);
Thread.init(*message_mutex);
Thread.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.Thread) -> s64 {
while true {
update_networking();
sleep_milliseconds(10);
}
return 0;
}
net_check_for_messages :: (thread: *Thread.Thread) -> s64 {
while true {
success, message, address := net_read_message(net_data.socket);
if success {
Thread.lock(*internal_message_mutex);
defer Thread.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; {
{
Thread.lock(*update_mutex);
defer Thread.unlock(*update_mutex);
array_add(*pending_net_messages, message);
}
}
case .SPAWN_ENTITY; {
{
Thread.lock(*update_mutex);
defer Thread.unlock(*update_mutex);
array_add(*pending_net_messages, message);
}
}
case .TRANSFORM_UPDATE; {
Thread.lock(*update_mutex);
defer Thread.unlock(*update_mutex);
array_add(*pending_net_messages, message);
}
case; {
Thread.lock(*update_mutex);
defer Thread.unlock(*update_mutex);
array_add(*pending_net_messages, message);
}
}
}
update_networking :: () {
reset_temporary_storage();
{
Thread.lock(*message_mutex);
defer Thread.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;
}
{
Thread.lock(*internal_message_mutex);
defer Thread.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: Thread.Mutex;
thread : Thread.Thread;
message_thread : Thread.Thread;
state : Net_Networking_State;
Thread :: #import "Thread";
#import "Basic";
Socket :: #import "Socket";