From 8f33957bf984e55d1162f6cc1076ddf223f1557f Mon Sep 17 00:00:00 2001 From: jacob Date: Wed, 13 Mar 2024 13:14:51 -0500 Subject: [PATCH] use a blend time for interpolation --- src/common.h | 9 ++- src/config.h | 3 + src/game.c | 2 - src/math.h | 3 - src/user.c | 165 ++++++++++++++++++++++++++++++++++++++++----------- src/world.h | 2 +- 6 files changed, 142 insertions(+), 42 deletions(-) diff --git a/src/common.h b/src/common.h index 7a1011a4..0305862d 100644 --- a/src/common.h +++ b/src/common.h @@ -273,11 +273,14 @@ typedef u64 umm; #define I32_MAX (0x7FFFFFFF) #define I64_MAX (0x7FFFFFFFFFFFFFFFLL) -#define I8_MIN ((i8)-0x80LL) -#define I16_MIN ((i16)-0x8000LL) -#define I32_MIN ((i32)-0x80000000LL) +#define I8_MIN ((i8)-0x80) +#define I16_MIN ((i16)-0x8000) +#define I32_MIN ((i32)-0x80000000) #define I64_MIN ((i64)-0x8000000000000000LL) +#define PI ((f32)3.14159265358979323846) +#define TAU ((f32)6.28318530717958647693) + /* ========================== * * Common structs * ========================== */ diff --git a/src/config.h b/src/config.h index 82ba958b..85360799 100644 --- a/src/config.h +++ b/src/config.h @@ -9,3 +9,6 @@ #define AUDIO_ENABLED 0 #define VSYNC_ENABLED 0 + +#define GAME_FPS 30 +#define USER_FRAME_LIMIT 300 diff --git a/src/game.c b/src/game.c index 0aaadc14..b0bdc403 100644 --- a/src/game.c +++ b/src/game.c @@ -9,8 +9,6 @@ #include "math.h" #include "scratch.h" -#define GAME_FPS 30 - GLOBAL struct { b32 shutdown; struct sys_thread game_thread; diff --git a/src/math.h b/src/math.h index 7e577050..87a12eb5 100644 --- a/src/math.h +++ b/src/math.h @@ -3,9 +3,6 @@ #include "intrinsics.h" -#define PI ((f32)3.14159265358979323846) -#define TAU ((f32)6.28318530717958647693) - /* ========================== * * Rounding * ========================== */ diff --git a/src/user.c b/src/user.c index 6daae5be..eb211406 100644 --- a/src/user.c +++ b/src/user.c @@ -19,8 +19,9 @@ #include "sound.h" #include "mixer.h" -/* NOTE: If vsync is enabled and lower then this will be ignored */ -#define USER_FPS 300 +/* How far back in time should the user blend between game ticks (in addition + * to the existing 1-tick delay) */ +#define USER_INTERP_DELAY 0.005 struct view { f32 px_per_unit; @@ -35,16 +36,25 @@ struct bind_state { u32 num_presses; /* How many times was this bind pressed since last frame */ }; +struct blend_tick { + struct blend_tick *next; + struct blend_tick *prev; + struct world world; +}; + GLOBAL struct { b32 shutdown; struct sys_thread user_thread; + struct arena arena; + struct sys_window *window; struct renderer_canvas *world_canvas; struct renderer_canvas *screen_canvas; struct view world_view; - struct world blend_ticks[2]; + struct blend_tick *head_free_blend_tick; + struct blend_tick *head_blend_tick; struct world world; struct bind_state bind_states[USER_BIND_KIND_COUNT]; @@ -67,7 +77,6 @@ GLOBAL struct { struct v2 screen_mouse; } L = { 0 }, DEBUG_LVAR(L_user); - /* ========================== * * Bind state * ========================== */ @@ -125,36 +134,115 @@ INTERNAL struct sys_event_array pull_sys_events(struct arena *arena) * Game -> user communication * ========================== */ +INTERNAL struct blend_tick *blend_tick_alloc(void) +{ + struct blend_tick *bt = NULL; + if (L.head_free_blend_tick) { + bt = L.head_free_blend_tick; + L.head_free_blend_tick = bt->next; + *bt = (struct blend_tick) { + .world = bt->world + }; + } else { + bt = arena_push_zero(&L.arena, struct blend_tick); + world_alloc(&bt->world); + } + if (L.head_blend_tick) { + bt->next = L.head_blend_tick; + L.head_blend_tick->prev = bt; + } + L.head_blend_tick = bt; + return bt; +} + +INTERNAL void blend_tick_release(struct blend_tick *bt) +{ + struct blend_tick *next = bt->next; + struct blend_tick *prev = bt->prev; + + /* Remove from list */ + if (next) { + next->prev = prev; + } + if (prev) { + prev->next = next; + } + if (bt == L.head_blend_tick) { + L.head_blend_tick = next; + } + + /* Add to free list */ + bt->next = L.head_free_blend_tick; + bt->prev = NULL; + L.head_free_blend_tick = bt; +} + struct interp_ticks { struct world *from_tick; struct world *to_tick; }; -INTERNAL struct interp_ticks pull_interp_ticks(void) +INTERNAL struct interp_ticks pull_ticks(f64 blend_time) { - /* Function is currently hard-coded for 2 blend ticks */ - ASSERT(ARRAY_COUNT(L.blend_ticks) == 2); + __prof; - /* Determine from & to tick from existing ticks */ - struct world *from_tick = NULL; - struct world *to_tick = NULL; - if (L.blend_ticks[1].tick_id > L.blend_ticks[0].tick_id) { - from_tick = &L.blend_ticks[0]; - to_tick = &L.blend_ticks[1]; - } else { - from_tick = &L.blend_ticks[1]; - to_tick = &L.blend_ticks[0]; + /* Find newest stored tick */ + struct world *newest_tick = NULL; + for (struct blend_tick *bt = L.head_blend_tick; bt; bt = bt->next) { + if (!newest_tick || bt->world.tick_id > newest_tick->tick_id) { + newest_tick = &bt->world; + } } - /* Check for new tick from game thread */ - u64 latest_tick_id = game_get_latest_tick_id(); - if (latest_tick_id > to_tick->tick_id) { - /* Swap pointers */ - struct world *temp = from_tick; - from_tick = to_tick; - to_tick = temp; - /* Pull game tick */ - game_get_latest_tick(to_tick); + /* Pull new tick from game thread if necessary */ + if (!newest_tick || game_get_latest_tick_id() > newest_tick->tick_id) { + struct blend_tick *latest_bt = blend_tick_alloc(); + newest_tick = &latest_bt->world; + game_get_latest_tick(newest_tick); + } + + /* Find oldest tick */ + struct world *oldest_tick = NULL; + for (struct blend_tick *bt = L.head_blend_tick; bt; bt = bt->next) { + if (!oldest_tick || bt->world.tick_id < oldest_tick->tick_id) { + oldest_tick = &bt->world; + } + } + + /* Find closest ticks to blend time */ + struct world *from_tick = oldest_tick; + struct world *to_tick = newest_tick; + for (struct blend_tick *bt = L.head_blend_tick; bt; bt = bt->next) { + f64 bt_time = sys_timestamp_seconds(bt->world.tick_published_ts); + + if (bt_time < blend_time && bt_time > sys_timestamp_seconds(from_tick->tick_published_ts)) { + from_tick = &bt->world; + } + + if (bt_time > blend_time && bt_time < sys_timestamp_seconds(to_tick->tick_published_ts)) { + to_tick = &bt->world; + } + } + + /* Free any unused old ticks */ + { + struct temp_arena scratch = scratch_begin_no_conflict(); + struct blend_tick **bts_to_free = arena_dry_push(scratch.arena, struct blend_tick *); + u64 bts_to_free_count = 0; + + for (struct blend_tick *bt = L.head_blend_tick; bt; bt = bt->next) { + f64 bt_time = sys_timestamp_seconds(bt->world.tick_published_ts); + if (bt_time < sys_timestamp_seconds(from_tick->tick_published_ts)) { + *arena_push(scratch.arena, struct blend_tick *) = bt; + ++bts_to_free_count; + } + } + + for (u64 i = 0; i < bts_to_free_count; ++i) { + blend_tick_release(bts_to_free[i]); + } + + scratch_end(scratch); } return (struct interp_ticks) { @@ -498,7 +586,12 @@ INTERNAL void user_update(void) * ========================== */ /* Pull ticks */ - struct interp_ticks interp_ticks = pull_interp_ticks(); + + /* FIXME: use real game DT */ + f64 blend_time_offset = USER_INTERP_DELAY + (1.0 / GAME_FPS); + f64 blend_time = L.time > blend_time_offset ? L.time - blend_time_offset : 0; + + struct interp_ticks interp_ticks = pull_ticks(blend_time); struct world *t0 = interp_ticks.from_tick; struct world *t1 = interp_ticks.to_tick; @@ -506,8 +599,14 @@ INTERNAL void user_update(void) { __profscope(produce_interpolated_tick); - sys_timestamp_t tick_publish_delta = t1->tick_published_ts - t0->tick_published_ts; - f32 tick_blend = (f32)((sys_timestamp() - t1->tick_published_ts) / (f64)tick_publish_delta); + f64 t0_time = sys_timestamp_seconds(t0->tick_published_ts); + f64 t1_time = sys_timestamp_seconds(t1->tick_published_ts); + + f32 tick_blend = 0; + if (t1_time > t0_time) { + tick_blend = (f32)((blend_time - t0_time) / (t1_time - t0_time)); + } + tick_blend = clamp_f32(tick_blend, 0.0f, 1.0f); world_copy_replace(&L.world, t1); @@ -840,7 +939,7 @@ INTERNAL void user_thread_entry_point(void *arg) (UNUSED)arg; sys_timestamp_t last_frame_ts = 0; - f64 target_dt = USER_FPS > 0 ? (1.0 / USER_FPS) : 0; + f64 target_dt = USER_FRAME_LIMIT > 0 ? (1.0 / USER_FRAME_LIMIT) : 0; while (!L.shutdown) { __profscope(user_update_w_sleep); @@ -852,14 +951,11 @@ INTERNAL void user_thread_entry_point(void *arg) void user_startup(struct sys_window *window) { - L.window = window; - sys_window_register_event_callback(L.window, &window_event_callback); + L.arena = arena_alloc(GIGABYTE(64)); L.sys_events_mutex = sys_mutex_alloc(); L.sys_events_arena = arena_alloc(GIGABYTE(64)); - world_alloc(&L.blend_ticks[0]); - world_alloc(&L.blend_ticks[1]); world_alloc(&L.world); L.world_canvas = renderer_canvas_alloc(); @@ -873,6 +969,9 @@ void user_startup(struct sys_window *window) L.screen_canvas = renderer_canvas_alloc(); + L.window = window; + sys_window_register_event_callback(L.window, &window_event_callback); + L.user_thread = sys_thread_init(&user_thread_entry_point, NULL, STR("[P1] User thread")); } diff --git a/src/world.h b/src/world.h index 256ded9b..0753a0f8 100644 --- a/src/world.h +++ b/src/world.h @@ -5,7 +5,7 @@ struct world { u64 tick_id; /* Starts at 1 */ - sys_timestamp_t tick_published_ts; + u64 tick_published_ts; f64 dt; f64 time;