597 lines
16 KiB
Plaintext
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";
|