From 261f602afe5d86f469108511b22301581459857f Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Wed, 12 Oct 2022 14:24:23 -0700 Subject: [PATCH] Basic multiplayer with enet --- .gitmodules | 3 + .vscode/launch.json | 2 +- .vscode/settings.json | 5 +- build_debug.bat | 3 +- main.c | 133 ++++++++++++++--- server.c | 156 +++++++++++++++++++- sokol_impl.c | 0 thirdparty/enet | 1 + thirdparty/sokol_time.h | 319 ++++++++++++++++++++++++++++++++++++++++ types.h | 12 +- 10 files changed, 601 insertions(+), 33 deletions(-) create mode 100644 .gitmodules create mode 100644 sokol_impl.c create mode 160000 thirdparty/enet create mode 100644 thirdparty/sokol_time.h diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..69b0692 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "thirdparty/enet"] + path = thirdparty/enet + url = https://github.com/lsalzman/enet.git diff --git a/.vscode/launch.json b/.vscode/launch.json index ebc3bef..47d88d7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "type": "cppvsdbg", "request": "launch", "program": "${workspaceFolder}/flight.exe", - "args": [], + "args": ["--host"], "stopAtEntry": false, "cwd": "${workspaceFolder}", "symbolSearchPath": "${workspaceFolder}", diff --git a/.vscode/settings.json b/.vscode/settings.json index e3757bd..bca0399 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,6 +25,9 @@ "xstring": "c", "algorithm": "c", "sokol_gp.h": "c", - "stdbool.h": "c" + "stdbool.h": "c", + "sokol_time.h": "c", + "enet.h": "c", + "types.h": "c" } } \ No newline at end of file diff --git a/build_debug.bat b/build_debug.bat index 188191b..496f2aa 100644 --- a/build_debug.bat +++ b/build_debug.bat @@ -7,4 +7,5 @@ IF %ERRORLEVEL% NEQ 0 ECHO ERROR download sokol-shdc from https://github.com/flo @REM example of how to compile shaders: sokol-shdc.exe --input triangle.glsl --output triangle.gen.h --slang glsl330:hlsl5:metal_macos -cl /MP /Zi /Fd"flight.pdb" /I"thirdparty" /Fe"flight" main.c gamestate.c server.c \ No newline at end of file +cl /MP /Zi /Fd"flight.pdb" /I"thirdparty" /Fe"flight" /I"thirdparty\enet\include" main.c gamestate.c server.c^ + thirdparty\enet\callbacks.c thirdparty\enet\compress.c thirdparty\enet\host.c thirdparty\enet\list.c thirdparty\enet\packet.c thirdparty\enet\peer.c thirdparty\enet\protocol.c thirdparty\enet\win32.c Ws2_32.lib winmm.lib \ No newline at end of file diff --git a/main.c b/main.c index 77ee43c..d388fff 100644 --- a/main.c +++ b/main.c @@ -8,16 +8,20 @@ #include "sokol_gp.h" #include "sokol_app.h" #include "sokol_glue.h" -#include +#include "sokol_time.h" +#include +#include // starting server thread #include "types.h" + static struct GameState gs = {0}; static int myplayer = -1; static bool mouse_down = false; static bool keydown[SAPP_KEYCODE_MENU] = {0}; static float funval = 0.0f; // easy to play with value controlled by left mouse button when held down @BeforeShip remove on release builds -static bool is_host = false; +static ENetHost *client; +static ENetPeer *peer; void init(void) { @@ -31,17 +35,7 @@ void init(void) exit(-1); } - gs.boxes[0] = (struct Box){ - .body = (struct Body){ - .position = (P2){.x = 0.75f, .y = 0.0}}, - }; - gs.boxes[0].body.old_position = gs.boxes[0].body.position; - gs.boxes[1] = (struct Box){ - .body = (struct Body){ - .position = (P2){.x = 0.75f, .y = 0.5f}}, - }; - gs.boxes[1].body.old_position = gs.boxes[1].body.position; - gs.num_boxes = 2; + sgp_desc sgpdesc = {0}; sgp_setup(&sgpdesc); @@ -50,26 +44,118 @@ void init(void) fprintf(stderr, "Failed to create Sokol GP context: %s\n", sgp_get_error_message(sgp_get_last_error())); exit(-1); } + + // socket initialization + { + if (enet_initialize() != 0) + { + fprintf(stderr, "An error occurred while initializing ENet.\n"); + exit(-1); + } + client = enet_host_create(NULL /* create a client host */, + 1 /* only allow 1 outgoing connection */, + 2 /* allow up 2 channels to be used, 0 and 1 */, + 0 /* assume any amount of incoming bandwidth */, + 0 /* assume any amount of outgoing bandwidth */); + if (client == NULL) + { + fprintf(stderr, + "An error occurred while trying to create an ENet client host.\n"); + exit(-1); + } + ENetAddress address; + ENetEvent event; + + enet_address_set_host(&address, "127.0.0.1"); + address.port = 8000; + peer = enet_host_connect(client, &address, 2, 0); + if (peer == NULL) + { + fprintf(stderr, + "No available peers for initiating an ENet connection.\n"); + exit(-1); + } + /* Wait up to 5 seconds for the connection attempt to succeed. */ + if (enet_host_service(client, &event, 1000) > 0 && + event.type == ENET_EVENT_TYPE_CONNECT) + { + Log("Connected\n"); + } + else + { + /* Either the 5 seconds are up or a disconnect event was */ + /* received. Reset the peer in the event the 5 seconds */ + /* had run out without any significant event. */ + enet_peer_reset(peer); + fprintf(stderr, "Connection to server failed."); + exit(-1); + } + } } -void frame(void) +static void frame(void) { int width = sapp_width(), height = sapp_height(); float ratio = width / (float)height; float time = sapp_frame_count() * sapp_frame_duration(); + // networking + { + ENetEvent event; + while(true) + { + int enet_status = enet_host_service(client, &event, 0); + if(enet_status > 0 ) + { + switch(event.type) + { + case ENET_EVENT_TYPE_CONNECT: + Log("New client from host %x\n", event.peer->address.host); + break; + case ENET_EVENT_TYPE_RECEIVE: + struct ServerToClient msg; + if(event.packet->dataLength != sizeof(msg)) + { + Log("Unknown packet size: %zd\n", event.packet->dataLength); + } else { + memcpy(&msg, event.packet->data, sizeof(msg)); + myplayer = msg.your_player; + gs = msg.cur_gs; + } + enet_packet_destroy(event.packet); + break; + case ENET_EVENT_TYPE_DISCONNECT: + fprintf(stderr, "Disconnected from server\n"); + exit(-1); + break; + } + } else if(enet_status == 0) { + break; + } else if(enet_status < 0) { + fprintf(stderr, "Error receiving enet events: %d\n", enet_status); + break; + } + } + } + // gameplay - struct ClientToServer curmsg = {0}; { + // @Robust accumulate total time and send input at rate like 20 hz, not every frame + struct ClientToServer curmsg = {0}; V2 input = (V2){ .x = (float)keydown[SAPP_KEYCODE_D] - (float)keydown[SAPP_KEYCODE_A], .y = (float)keydown[SAPP_KEYCODE_S] - (float)keydown[SAPP_KEYCODE_W], }; curmsg.input = input; + ENetPacket * packet = enet_packet_create((void*)&curmsg, sizeof(curmsg), ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT); + enet_peer_send(peer, 0, packet); + process(&gs, (float)sapp_frame_duration()); } + + // drawing { sgp_begin(width, height); @@ -84,7 +170,7 @@ void frame(void) // Drawing in world space now sgp_translate(width / 2, height / 2); sgp_scale_at(300.0f + funval, 300.0f + funval, 0.0f, 0.0f); - if(myplayer != -1) + if (myplayer != -1) { sgp_translate(-gs.players[myplayer].body.position.x, -gs.players[myplayer].body.position.y); } @@ -105,9 +191,10 @@ void frame(void) float halfbox = BOX_SIZE / 2.0f; // player - for(int i = 0; i < MAX_PLAYERS; i++){ - struct Player * p = &gs.players[i]; - if(!p->connected) + for (int i = 0; i < MAX_PLAYERS; i++) + { + struct Player *p = &gs.players[i]; + if (!p->connected) continue; sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f); sgp_draw_filled_rect(p->body.position.x - halfbox, p->body.position.y - halfbox, BOX_SIZE, BOX_SIZE); @@ -140,6 +227,7 @@ void cleanup(void) { sgp_shutdown(); sg_shutdown(); + enet_deinitialize(); } void event(const sapp_event *e) @@ -164,7 +252,7 @@ void event(const sapp_event *e) if (mouse_down) { funval += e->mouse_dx; - printf("Funval %f\n", funval); + Log("Funval %f\n", funval); } break; } @@ -172,7 +260,10 @@ void event(const sapp_event *e) sapp_desc sokol_main(int argc, char *argv[]) { - is_host = argc >= 1; + if (argc > 1) + { + _beginthread(server, 0, NULL); + } (void)argv; return (sapp_desc){ .init_cb = init, diff --git a/server.c b/server.c index 4a1a7be..3a343d6 100644 --- a/server.c +++ b/server.c @@ -1,6 +1,156 @@ -#include +#include "types.h" +#include "sokol_time.h" +#include +#include +#include // int64 printing + +#define TIMESTEP (1.0f / 60.0f) // started in a thread from host -void server() { - // SOCKET s = socket(); +void server(void *data) +{ + (void)data; + + stm_setup(); + + struct GameState gs = {0}; + + gs.boxes[0] = (struct Box){ + .body = (struct Body){ + .position = (P2){.x = 0.75f, .y = 0.0}}, + }; + gs.boxes[0].body.old_position = gs.boxes[0].body.position; + gs.boxes[1] = (struct Box){ + .body = (struct Body){ + .position = (P2){.x = 0.75f, .y = 0.5f}}, + }; + gs.boxes[1].body.old_position = gs.boxes[1].body.position; + gs.num_boxes = 2; + + if (enet_initialize() != 0) + { + fprintf(stderr, "An error occurred while initializing ENet.\n"); + exit(-1); + } + + ENetAddress address; + ENetHost *server; + address.host = ENET_HOST_ANY; + /* Bind the server to port 1234. */ + address.port = 8000; + server = enet_host_create(&address /* the address to bind the server host to */, + 32 /* allow up to 32 clients and/or outgoing connections */, + 2 /* allow up to 2 channels to be used, 0 and 1 */, + 0 /* assume any amount of incoming bandwidth */, + 0 /* assume any amount of outgoing bandwidth */); + if (server == NULL) + { + fprintf(stderr, + "An error occurred while trying to create an ENet server host.\n"); + exit(-1); + } + + Log("Serving on port 8000...\n"); + ENetEvent event; + uint64_t last_processed_time = stm_now(); + float total_time = 0.0f; + while (true) + { + // @Speed handle enet messages and simulate gamestate in parallel, then sync... must clone gamestate for this + while (true) + { + int ret = enet_host_service(server, &event, 16); + if (ret == 0) + break; + if (ret < 0) + { + fprintf(stderr, "Enet host service error %d\n", ret); + } + if (ret > 0) + { + switch (event.type) + { + case ENET_EVENT_TYPE_CONNECT: + Log("A new client connected from %x:%u.\n", + event.peer->address.host, + event.peer->address.port); + + int64_t player_slot = -1; + for (int i = 0; i < MAX_PLAYERS; i++) + { + if (!gs.players[i].connected) + { + player_slot = i; + break; + } + } + + if (player_slot == -1) + { + enet_peer_disconnect_now(event.peer, 69); + } + else + { + event.peer->data = (void *)player_slot; + gs.players[player_slot].connected = true; + } + + break; + case ENET_EVENT_TYPE_RECEIVE: + // Log("A packet of length %zu was received on channel %u.\n", + // event.packet->dataLength, + // event.channelID); + + size_t length = event.packet->dataLength; + if (length != sizeof(struct ClientToServer)) + { + Log("Length did not match up...\n"); + } + else + { + struct ClientToServer received = {0}; + memcpy(&received, event.packet->data, length); + int64_t player_slot = (int64_t)event.peer->data; + gs.players[player_slot].input = received.input; + } + + /* Clean up the packet now that we're done using it. */ + enet_packet_destroy(event.packet); + + break; + + case ENET_EVENT_TYPE_DISCONNECT: + Log("%" PRId64 " disconnected.\n", (int64_t)event.peer->data); + gs.players[(int64_t)event.peer->data].connected = false; + event.peer->data = NULL; + } + } + } + + total_time += (float)stm_sec(stm_diff(stm_now(), last_processed_time)); + last_processed_time = stm_now(); + // @Robost if can't process quick enough will be stuck being lagged behind, think of a solution for this... + while (total_time > TIMESTEP) + { + process(&gs, TIMESTEP); + total_time -= TIMESTEP; + } + + for (int i = 0; i < server->peerCount; i++) + { + // @Speed don't recreate the packet for every peer, gets expensive copying gamestate over and over again + if (server->peers[i].state != ENET_PEER_STATE_CONNECTED) + { + continue; + } + struct ServerToClient to_send; + to_send.cur_gs = gs; + to_send.your_player = (int)(int64_t)server->peers[i].data; + ENetPacket *gamestate_packet = enet_packet_create((void *)&to_send, sizeof(struct ServerToClient), ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT); + enet_peer_send(&server->peers[i], 0, gamestate_packet); + } + } + + enet_host_destroy(server); + enet_deinitialize(); } \ No newline at end of file diff --git a/sokol_impl.c b/sokol_impl.c new file mode 100644 index 0000000..e69de29 diff --git a/thirdparty/enet b/thirdparty/enet new file mode 160000 index 0000000..b06d154 --- /dev/null +++ b/thirdparty/enet @@ -0,0 +1 @@ +Subproject commit b06d15457909d54cd4fc0ba58c3b257d7e913f7f diff --git a/thirdparty/sokol_time.h b/thirdparty/sokol_time.h new file mode 100644 index 0000000..2d4d456 --- /dev/null +++ b/thirdparty/sokol_time.h @@ -0,0 +1,319 @@ +#if defined(SOKOL_IMPL) && !defined(SOKOL_TIME_IMPL) +#define SOKOL_TIME_IMPL +#endif +#ifndef SOKOL_TIME_INCLUDED +/* + sokol_time.h -- simple cross-platform time measurement + + Project URL: https://github.com/floooh/sokol + + Do this: + #define SOKOL_IMPL or + #define SOKOL_TIME_IMPL + before you include this file in *one* C or C++ file to create the + implementation. + + Optionally provide the following defines with your own implementations: + SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) + SOKOL_TIME_API_DECL - public function declaration prefix (default: extern) + SOKOL_API_DECL - same as SOKOL_TIME_API_DECL + SOKOL_API_IMPL - public function implementation prefix (default: -) + + If sokol_time.h is compiled as a DLL, define the following before + including the declaration or implementation: + + SOKOL_DLL + + On Windows, SOKOL_DLL will define SOKOL_TIME_API_DECL as __declspec(dllexport) + or __declspec(dllimport) as needed. + + void stm_setup(); + Call once before any other functions to initialize sokol_time + (this calls for instance QueryPerformanceFrequency on Windows) + + uint64_t stm_now(); + Get current point in time in unspecified 'ticks'. The value that + is returned has no relation to the 'wall-clock' time and is + not in a specific time unit, it is only useful to compute + time differences. + + uint64_t stm_diff(uint64_t new, uint64_t old); + Computes the time difference between new and old. This will always + return a positive, non-zero value. + + uint64_t stm_since(uint64_t start); + Takes the current time, and returns the elapsed time since start + (this is a shortcut for "stm_diff(stm_now(), start)") + + uint64_t stm_laptime(uint64_t* last_time); + This is useful for measuring frame time and other recurring + events. It takes the current time, returns the time difference + to the value in last_time, and stores the current time in + last_time for the next call. If the value in last_time is 0, + the return value will be zero (this usually happens on the + very first call). + + uint64_t stm_round_to_common_refresh_rate(uint64_t duration) + This oddly named function takes a measured frame time and + returns the closest "nearby" common display refresh rate frame duration + in ticks. If the input duration isn't close to any common display + refresh rate, the input duration will be returned unchanged as a fallback. + The main purpose of this function is to remove jitter/inaccuracies from + measured frame times, and instead use the display refresh rate as + frame duration. + NOTE: for more robust frame timing, consider using the + sokol_app.h function sapp_frame_duration() + + Use the following functions to convert a duration in ticks into + useful time units: + + double stm_sec(uint64_t ticks); + double stm_ms(uint64_t ticks); + double stm_us(uint64_t ticks); + double stm_ns(uint64_t ticks); + Converts a tick value into seconds, milliseconds, microseconds + or nanoseconds. Note that not all platforms will have nanosecond + or even microsecond precision. + + Uses the following time measurement functions under the hood: + + Windows: QueryPerformanceFrequency() / QueryPerformanceCounter() + MacOS/iOS: mach_absolute_time() + emscripten: emscripten_get_now() + Linux+others: clock_gettime(CLOCK_MONOTONIC) + + zlib/libpng license + + Copyright (c) 2018 Andre Weissflog + + 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. +*/ +#define SOKOL_TIME_INCLUDED (1) +#include + +#if defined(SOKOL_API_DECL) && !defined(SOKOL_TIME_API_DECL) +#define SOKOL_TIME_API_DECL SOKOL_API_DECL +#endif +#ifndef SOKOL_TIME_API_DECL +#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_TIME_IMPL) +#define SOKOL_TIME_API_DECL __declspec(dllexport) +#elif defined(_WIN32) && defined(SOKOL_DLL) +#define SOKOL_TIME_API_DECL __declspec(dllimport) +#else +#define SOKOL_TIME_API_DECL extern +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +SOKOL_TIME_API_DECL void stm_setup(void); +SOKOL_TIME_API_DECL uint64_t stm_now(void); +SOKOL_TIME_API_DECL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks); +SOKOL_TIME_API_DECL uint64_t stm_since(uint64_t start_ticks); +SOKOL_TIME_API_DECL uint64_t stm_laptime(uint64_t* last_time); +SOKOL_TIME_API_DECL uint64_t stm_round_to_common_refresh_rate(uint64_t frame_ticks); +SOKOL_TIME_API_DECL double stm_sec(uint64_t ticks); +SOKOL_TIME_API_DECL double stm_ms(uint64_t ticks); +SOKOL_TIME_API_DECL double stm_us(uint64_t ticks); +SOKOL_TIME_API_DECL double stm_ns(uint64_t ticks); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif // SOKOL_TIME_INCLUDED + +/*-- IMPLEMENTATION ----------------------------------------------------------*/ +#ifdef SOKOL_TIME_IMPL +#define SOKOL_TIME_IMPL_INCLUDED (1) +#include /* memset */ + +#ifndef SOKOL_API_IMPL + #define SOKOL_API_IMPL +#endif +#ifndef SOKOL_ASSERT + #include + #define SOKOL_ASSERT(c) assert(c) +#endif +#ifndef _SOKOL_PRIVATE + #if defined(__GNUC__) || defined(__clang__) + #define _SOKOL_PRIVATE __attribute__((unused)) static + #else + #define _SOKOL_PRIVATE static + #endif +#endif + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +typedef struct { + uint32_t initialized; + LARGE_INTEGER freq; + LARGE_INTEGER start; +} _stm_state_t; +#elif defined(__APPLE__) && defined(__MACH__) +#include +typedef struct { + uint32_t initialized; + mach_timebase_info_data_t timebase; + uint64_t start; +} _stm_state_t; +#elif defined(__EMSCRIPTEN__) +#include +typedef struct { + uint32_t initialized; + double start; +} _stm_state_t; +#else /* anything else, this will need more care for non-Linux platforms */ +#ifdef ESP8266 +// On the ESP8266, clock_gettime ignores the first argument and CLOCK_MONOTONIC isn't defined +#define CLOCK_MONOTONIC 0 +#endif +#include +typedef struct { + uint32_t initialized; + uint64_t start; +} _stm_state_t; +#endif +static _stm_state_t _stm; + +/* prevent 64-bit overflow when computing relative timestamp + see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 +*/ +#if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__)) +_SOKOL_PRIVATE int64_t _stm_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { + int64_t q = value / denom; + int64_t r = value % denom; + return q * numer + r * numer / denom; +} +#endif + +SOKOL_API_IMPL void stm_setup(void) { + memset(&_stm, 0, sizeof(_stm)); + _stm.initialized = 0xABCDABCD; + #if defined(_WIN32) + QueryPerformanceFrequency(&_stm.freq); + QueryPerformanceCounter(&_stm.start); + #elif defined(__APPLE__) && defined(__MACH__) + mach_timebase_info(&_stm.timebase); + _stm.start = mach_absolute_time(); + #elif defined(__EMSCRIPTEN__) + _stm.start = emscripten_get_now(); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + _stm.start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; + #endif +} + +SOKOL_API_IMPL uint64_t stm_now(void) { + SOKOL_ASSERT(_stm.initialized == 0xABCDABCD); + uint64_t now; + #if defined(_WIN32) + LARGE_INTEGER qpc_t; + QueryPerformanceCounter(&qpc_t); + now = (uint64_t) _stm_int64_muldiv(qpc_t.QuadPart - _stm.start.QuadPart, 1000000000, _stm.freq.QuadPart); + #elif defined(__APPLE__) && defined(__MACH__) + const uint64_t mach_now = mach_absolute_time() - _stm.start; + now = (uint64_t) _stm_int64_muldiv((int64_t)mach_now, (int64_t)_stm.timebase.numer, (int64_t)_stm.timebase.denom); + #elif defined(__EMSCRIPTEN__) + double js_now = emscripten_get_now() - _stm.start; + now = (uint64_t) (js_now * 1000000.0); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ((uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec) - _stm.start; + #endif + return now; +} + +SOKOL_API_IMPL uint64_t stm_diff(uint64_t new_ticks, uint64_t old_ticks) { + if (new_ticks > old_ticks) { + return new_ticks - old_ticks; + } + else { + return 1; + } +} + +SOKOL_API_IMPL uint64_t stm_since(uint64_t start_ticks) { + return stm_diff(stm_now(), start_ticks); +} + +SOKOL_API_IMPL uint64_t stm_laptime(uint64_t* last_time) { + SOKOL_ASSERT(last_time); + uint64_t dt = 0; + uint64_t now = stm_now(); + if (0 != *last_time) { + dt = stm_diff(now, *last_time); + } + *last_time = now; + return dt; +} + +// first number is frame duration in ns, second number is tolerance in ns, +// the resulting min/max values must not overlap! +static const uint64_t _stm_refresh_rates[][2] = { + { 16666667, 1000000 }, // 60 Hz: 16.6667 +- 1ms + { 13888889, 250000 }, // 72 Hz: 13.8889 +- 0.25ms + { 13333333, 250000 }, // 75 Hz: 13.3333 +- 0.25ms + { 11764706, 250000 }, // 85 Hz: 11.7647 +- 0.25 + { 11111111, 250000 }, // 90 Hz: 11.1111 +- 0.25ms + { 10000000, 500000 }, // 100 Hz: 10.0000 +- 0.5ms + { 8333333, 500000 }, // 120 Hz: 8.3333 +- 0.5ms + { 6944445, 500000 }, // 144 Hz: 6.9445 +- 0.5ms + { 4166667, 1000000 }, // 240 Hz: 4.1666 +- 1ms + { 0, 0 }, // keep the last element always at zero +}; + +SOKOL_API_IMPL uint64_t stm_round_to_common_refresh_rate(uint64_t ticks) { + uint64_t ns; + int i = 0; + while (0 != (ns = _stm_refresh_rates[i][0])) { + uint64_t tol = _stm_refresh_rates[i][1]; + if ((ticks > (ns - tol)) && (ticks < (ns + tol))) { + return ns; + } + i++; + } + // fallthough: didn't fit into any buckets + return ticks; +} + +SOKOL_API_IMPL double stm_sec(uint64_t ticks) { + return (double)ticks / 1000000000.0; +} + +SOKOL_API_IMPL double stm_ms(uint64_t ticks) { + return (double)ticks / 1000000.0; +} + +SOKOL_API_IMPL double stm_us(uint64_t ticks) { + return (double)ticks / 1000.0; +} + +SOKOL_API_IMPL double stm_ns(uint64_t ticks) { + return (double)ticks; +} +#endif /* SOKOL_TIME_IMPL */ + diff --git a/types.h b/types.h index c72661e..2dd2b0b 100644 --- a/types.h +++ b/types.h @@ -16,19 +16,19 @@ typedef sgp_vec2 sgp_point; #ifndef _STDBOOL -#define bool _Bool +#define bool _Bool #define false 0 -#define true 1 +#define true 1 #endif typedef sgp_vec2 V2; typedef sgp_point P2; +#define Log(...) fprintf(stdout, "%s:%d | ", __FILE__, __LINE__); fprintf(stdout, __VA_ARGS__) #define MAX_BOXES 32 -#define MAX_PLAYERS 2 +#define MAX_PLAYERS 4 #define BOX_SIZE 0.5f -#define TIMESTEP 1.0f / 60.0f struct Body { @@ -67,11 +67,11 @@ struct ClientToServer }; // server -void server(); +void server(void *data); // gamestate -void process(struct GameState * gs, float dt); // does in place +void process(struct GameState *gs, float dt); // does in place // all the math is static so that it can be defined in each compilation unit its included in