Files
Bryan Tabor 77e14db612 fix up initialization (#70)
* fix up initialization

* typo
2024-09-12 10:42:32 +02:00

476 lines
13 KiB
C

/*
Copyright (C) 2024 BIAGINI Nathan
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/*
--- NBNET WEBRTC DRIVER ---
WebRTC driver using a single unreliable data channel for the nbnet library.
How to use:
1. Include this header *once* after the nbnet header in the same file where you defined the NBNET_IMPL macro
2. Call NBN_WebRTC_Register in both your client and server code before calling NBN_GameClient_Start or NBN_GameServer_Start
*/
typedef struct NBN_WebRTC_Config
{
bool enable_tls;
const char *cert_path;
const char *key_path;
} NBN_WebRTC_Config;
void NBN_WebRTC_Register(NBN_WebRTC_Config config);
#ifdef NBNET_IMPL
#if !defined(EXTERN_C)
#if defined(__cplusplus)
#define NBN_EXTERN extern "C"
#else
#define NBN_EXTERN extern
#endif
#endif
#include <emscripten/emscripten.h>
#define NBN_WEBRTC_DRIVER_ID 1
#define NBN_WEBRTC_DRIVER_NAME "WebRTC"
typedef struct
{
uint32_t id;
NBN_Connection *conn;
} NBN_WebRTC_Peer;
#pragma region Hashtable
#define HTABLE_DEFAULT_INITIAL_CAPACITY 32
#define HTABLE_LOAD_FACTOR_THRESHOLD 0.75
typedef struct
{
uint32_t peer_id;
NBN_WebRTC_Peer *peer;
unsigned int slot;
} NBN_WebRTC_HTableEntry;
typedef struct
{
NBN_WebRTC_HTableEntry **internal_array;
unsigned int capacity;
unsigned int count;
float load_factor;
} NBN_WebRTC_HTable;
static NBN_WebRTC_HTable *NBN_WebRTC_HTable_Create(void);
static NBN_WebRTC_HTable *NBN_WebRTC_HTable_CreateWithCapacity(unsigned int);
static void NBN_WebRTC_HTable_Destroy(NBN_WebRTC_HTable *);
static void NBN_WebRTC_HTable_Add(NBN_WebRTC_HTable *, uint32_t, NBN_WebRTC_Peer *);
static NBN_WebRTC_Peer *NBN_WebRTC_HTable_Get(NBN_WebRTC_HTable *, uint32_t);
static NBN_WebRTC_Peer *NBN_WebRTC_HTable_Remove(NBN_WebRTC_HTable *, uint32_t);
static void NBN_WebRTC_HTable_InsertEntry(NBN_WebRTC_HTable *, NBN_WebRTC_HTableEntry *);
static void NBN_WebRTC_HTable_RemoveEntry(NBN_WebRTC_HTable *, NBN_WebRTC_HTableEntry *);
static unsigned int NBN_WebRTC_HTable_FindFreeSlot(NBN_WebRTC_HTable *, NBN_WebRTC_HTableEntry *, bool *);
static NBN_WebRTC_HTableEntry *NBN_WebRTC_HTable_FindEntry(NBN_WebRTC_HTable *, uint32_t);
static void NBN_WebRTC_HTable_Grow(NBN_WebRTC_HTable *);
static NBN_WebRTC_HTable *NBN_WebRTC_HTable_Create(void)
{
return NBN_WebRTC_HTable_CreateWithCapacity(HTABLE_DEFAULT_INITIAL_CAPACITY);
}
static NBN_WebRTC_HTable *NBN_WebRTC_HTable_CreateWithCapacity(unsigned int capacity)
{
NBN_WebRTC_HTable *htable = (NBN_WebRTC_HTable*)NBN_Allocator(sizeof(NBN_WebRTC_HTable));
htable->internal_array = (NBN_WebRTC_HTableEntry**)NBN_Allocator(sizeof(NBN_WebRTC_HTableEntry *) * capacity);
htable->capacity = capacity;
htable->count = 0;
htable->load_factor = 0;
for (unsigned int i = 0; i < htable->capacity; i++)
htable->internal_array[i] = NULL;
return htable;
}
static void NBN_WebRTC_HTable_Destroy(NBN_WebRTC_HTable *htable)
{
for (unsigned int i = 0; i < htable->capacity; i++)
{
NBN_WebRTC_HTableEntry *entry = htable->internal_array[i];
if (entry)
NBN_Deallocator(entry);
}
NBN_Deallocator(htable->internal_array);
NBN_Deallocator(htable);
}
static void NBN_WebRTC_HTable_Add(NBN_WebRTC_HTable *htable, uint32_t peer_id, NBN_WebRTC_Peer *peer)
{
NBN_WebRTC_HTableEntry *entry = (NBN_WebRTC_HTableEntry*)NBN_Allocator(sizeof(NBN_WebRTC_HTableEntry));
entry->peer_id = peer_id;
entry->peer = peer;
NBN_WebRTC_HTable_InsertEntry(htable, entry);
if (htable->load_factor >= HTABLE_LOAD_FACTOR_THRESHOLD)
NBN_WebRTC_HTable_Grow(htable);
}
static NBN_WebRTC_Peer *NBN_WebRTC_HTable_Get(NBN_WebRTC_HTable *htable, uint32_t peer_id)
{
NBN_WebRTC_HTableEntry *entry = NBN_WebRTC_HTable_FindEntry(htable, peer_id);
return entry ? entry->peer : NULL;
}
static NBN_WebRTC_Peer *NBN_WebRTC_HTable_Remove(NBN_WebRTC_HTable *htable, uint32_t peer_id)
{
NBN_WebRTC_HTableEntry *entry = NBN_WebRTC_HTable_FindEntry(htable, peer_id);
if (entry)
{
NBN_WebRTC_Peer *peer = entry->peer;
NBN_WebRTC_HTable_RemoveEntry(htable, entry);
return peer;
}
return NULL;
}
static void NBN_WebRTC_HTable_InsertEntry(NBN_WebRTC_HTable *htable, NBN_WebRTC_HTableEntry *entry)
{
bool use_existing_slot = false;
unsigned int slot = NBN_WebRTC_HTable_FindFreeSlot(htable, entry, &use_existing_slot);
entry->slot = slot;
htable->internal_array[slot] = entry;
if (!use_existing_slot)
{
htable->count++;
htable->load_factor = (float)htable->count / htable->capacity;
}
}
static void NBN_WebRTC_HTable_RemoveEntry(NBN_WebRTC_HTable *htable, NBN_WebRTC_HTableEntry *entry)
{
htable->internal_array[entry->slot] = NULL;
NBN_Deallocator(entry);
htable->count--;
htable->load_factor = htable->count / htable->capacity;
}
static unsigned int NBN_WebRTC_HTable_FindFreeSlot(NBN_WebRTC_HTable *htable, NBN_WebRTC_HTableEntry *entry, bool *use_existing_slot)
{
unsigned long hash = entry->peer_id;
unsigned int slot;
// quadratic probing
NBN_WebRTC_HTableEntry *current_entry;
unsigned int i = 0;
do
{
slot = (hash + (int)pow(i, 2)) % htable->capacity;
current_entry = htable->internal_array[slot];
i++;
} while (current_entry != NULL && current_entry->peer_id != entry->peer_id);
if (current_entry != NULL) // it means the current entry as the same key as the inserted entry
{
*use_existing_slot = true;
NBN_Deallocator(current_entry);
}
return slot;
}
static NBN_WebRTC_HTableEntry *NBN_WebRTC_HTable_FindEntry(NBN_WebRTC_HTable *htable, uint32_t peer_id)
{
unsigned long hash = peer_id;
unsigned int slot;
//quadratic probing
NBN_WebRTC_HTableEntry *current_entry;
unsigned int i = 0;
do
{
slot = (hash + (int)pow(i, 2)) % htable->capacity;
current_entry = htable->internal_array[slot];
if (current_entry != NULL && current_entry->peer_id == peer_id)
{
return current_entry;
}
i++;
} while (i < htable->capacity);
return NULL;
}
static void NBN_WebRTC_HTable_Grow(NBN_WebRTC_HTable *htable)
{
unsigned int old_capacity = htable->capacity;
unsigned int new_capacity = old_capacity * 2;
NBN_WebRTC_HTableEntry** old_internal_array = htable->internal_array;
NBN_WebRTC_HTableEntry** new_internal_array = (NBN_WebRTC_HTableEntry**)NBN_Allocator(sizeof(NBN_WebRTC_HTableEntry*) * new_capacity);
for (unsigned int i = 0; i < new_capacity; i++)
{
new_internal_array[i] = NULL;
}
htable->internal_array = new_internal_array;
htable->capacity = new_capacity;
htable->count = 0;
htable->load_factor = 0;
// rehash
for (unsigned int i = 0; i < old_capacity; i++)
{
if (old_internal_array[i])
NBN_WebRTC_HTable_InsertEntry(htable, old_internal_array[i]);
}
NBN_Deallocator(old_internal_array);
}
#pragma endregion // Hashtable
#pragma region Game server
/* --- JS API --- */
NBN_EXTERN void __js_game_server_init(uint32_t, bool, const char *, const char *);
NBN_EXTERN int __js_game_server_start(uint16_t);
NBN_EXTERN int __js_game_server_dequeue_packet(uint32_t *, uint8_t *);
NBN_EXTERN int __js_game_server_send_packet_to(uint8_t *, unsigned int, uint32_t);
NBN_EXTERN void __js_game_server_close_client_peer(unsigned int);
NBN_EXTERN void __js_game_server_stop(void);
/* --- Driver implementation --- */
typedef struct NBN_WebRTC_Server
{
NBN_WebRTC_HTable *peers;
uint8_t packet_buffer[NBN_PACKET_MAX_SIZE];
uint32_t protocol_id;
} NBN_WebRTC_Server;
static NBN_WebRTC_Server nbn_wrtc_serv = {NULL, {0}, 0};
static NBN_WebRTC_Config nbn_wrtc_cfg;
static int NBN_WebRTC_ServStart(uint32_t protocol_id, uint16_t port)
{
__js_game_server_init(protocol_id, nbn_wrtc_cfg.enable_tls, nbn_wrtc_cfg.key_path, nbn_wrtc_cfg.cert_path);
if (__js_game_server_start(port) < 0)
return -1;
nbn_wrtc_serv.peers = NBN_WebRTC_HTable_Create();
nbn_wrtc_serv.protocol_id = protocol_id;
return 0;
}
static void NBN_WebRTC_ServStop(void)
{
__js_game_server_stop();
NBN_WebRTC_HTable_Destroy(nbn_wrtc_serv.peers);
}
static int NBN_WebRTC_ServRecvPackets(void)
{
uint32_t peer_id;
unsigned int len;
while ((len = __js_game_server_dequeue_packet(&peer_id, (uint8_t *)nbn_wrtc_serv.packet_buffer)) > 0)
{
NBN_Packet packet;
NBN_WebRTC_Peer *peer = NBN_WebRTC_HTable_Get(nbn_wrtc_serv.peers, peer_id);
if (peer == NULL)
{
if (GameServer_GetClientCount() >= NBN_MAX_CLIENTS)
continue;
NBN_LogTrace("Peer %d has connected", peer_id);
peer = (NBN_WebRTC_Peer *)NBN_Allocator(sizeof(NBN_WebRTC_Peer));
peer->id = peer_id;
peer->conn = NBN_GameServer_CreateClientConnection(
NBN_WEBRTC_DRIVER_ID,
peer,
nbn_wrtc_serv.protocol_id,
peer_id);
NBN_WebRTC_HTable_Add(nbn_wrtc_serv.peers, peer_id, peer);
NBN_Driver_RaiseEvent(NBN_DRIVER_SERV_CLIENT_CONNECTED, peer->conn);
}
if (NBN_Packet_InitRead(&packet, peer->conn, nbn_wrtc_serv.packet_buffer, len) < 0)
continue;
packet.sender = peer->conn;
NBN_Driver_RaiseEvent(NBN_DRIVER_SERV_CLIENT_PACKET_RECEIVED, &packet);
}
return 0;
}
static void NBN_WebRTC_ServRemoveClientConnection(NBN_Connection *conn)
{
assert(conn != NULL);
__js_game_server_close_client_peer(conn->id);
NBN_WebRTC_Peer *peer = NBN_WebRTC_HTable_Remove(nbn_wrtc_serv.peers, ((NBN_WebRTC_Peer *)conn->driver_data)->id);
if (peer)
{
NBN_LogDebug("Destroyed peer %d", peer->id);
NBN_Deallocator(peer);
}
}
static int NBN_WebRTC_ServSendPacketTo(NBN_Packet *packet, NBN_Connection *conn)
{
return __js_game_server_send_packet_to(packet->buffer, packet->size, conn->id);
}
#pragma endregion /* Game server */
#pragma region Game client
/* --- JS API --- */
NBN_EXTERN void __js_game_client_init(uint32_t, bool);
NBN_EXTERN int __js_game_client_start(const char *, uint16_t);
NBN_EXTERN int __js_game_client_dequeue_packet(uint8_t *);
NBN_EXTERN int __js_game_client_send_packet(uint8_t *, unsigned int);
NBN_EXTERN void __js_game_client_close(void);
/* --- Driver implementation --- */
typedef struct NBN_WebRTC_Client
{
NBN_Connection *server_conn;
} NBN_WebRTC_Client;
static NBN_WebRTC_Client nbn_wrtc_cli = {NULL};
static int NBN_WebRTC_CliStart(uint32_t protocol_id, const char *host, uint16_t port)
{
__js_game_client_init(protocol_id, nbn_wrtc_cfg.enable_tls);
nbn_wrtc_cli.server_conn = NBN_GameClient_CreateServerConnection(NBN_WEBRTC_DRIVER_ID, NULL, protocol_id);
int res;
if ((res = __js_game_client_start(host, port)) < 0)
return -1;
return 0;
}
static void NBN_WebRTC_CliStop(void)
{
__js_game_client_close();
}
static int NBN_WebRTC_CliRecvPackets(void)
{
unsigned int len;
while ((len = __js_game_client_dequeue_packet((uint8_t *)nbn_wrtc_serv.packet_buffer)) > 0)
{
NBN_Packet packet;
if (NBN_Packet_InitRead(&packet, nbn_wrtc_cli.server_conn, nbn_wrtc_serv.packet_buffer, len) < 0)
continue;
NBN_Driver_RaiseEvent(NBN_DRIVER_CLI_PACKET_RECEIVED, &packet);
}
return 0;
}
static int NBN_WebRTC_CliSendPacket(NBN_Packet *packet)
{
return __js_game_client_send_packet(packet->buffer, packet->size);
}
#pragma endregion /* Game client */
#pragma region Driver registering
void NBN_WebRTC_Register(NBN_WebRTC_Config config)
{
NBN_DriverImplementation driver_impl = {
// Client implementation
NBN_WebRTC_CliStart,
NBN_WebRTC_CliStop,
NBN_WebRTC_CliRecvPackets,
NBN_WebRTC_CliSendPacket,
// Server implementation
NBN_WebRTC_ServStart,
NBN_WebRTC_ServStop,
NBN_WebRTC_ServRecvPackets,
NBN_WebRTC_ServSendPacketTo,
NBN_WebRTC_ServRemoveClientConnection
};
nbn_wrtc_cfg = config;
NBN_Driver_Register(
NBN_WEBRTC_DRIVER_ID,
NBN_WEBRTC_DRIVER_NAME,
driver_impl
);
}
#pragma endregion /* Driver registering */
#endif /* NBNET_IMPL */