use a blend time for interpolation
This commit is contained in:
parent
85ff4d5e2e
commit
8f33957bf9
@ -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
|
||||
* ========================== */
|
||||
|
||||
@ -9,3 +9,6 @@
|
||||
|
||||
#define AUDIO_ENABLED 0
|
||||
#define VSYNC_ENABLED 0
|
||||
|
||||
#define GAME_FPS 30
|
||||
#define USER_FRAME_LIMIT 300
|
||||
|
||||
@ -9,8 +9,6 @@
|
||||
#include "math.h"
|
||||
#include "scratch.h"
|
||||
|
||||
#define GAME_FPS 30
|
||||
|
||||
GLOBAL struct {
|
||||
b32 shutdown;
|
||||
struct sys_thread game_thread;
|
||||
|
||||
@ -3,9 +3,6 @@
|
||||
|
||||
#include "intrinsics.h"
|
||||
|
||||
#define PI ((f32)3.14159265358979323846)
|
||||
#define TAU ((f32)6.28318530717958647693)
|
||||
|
||||
/* ========================== *
|
||||
* Rounding
|
||||
* ========================== */
|
||||
|
||||
165
src/user.c
165
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"));
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user