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 I32_MAX (0x7FFFFFFF)
|
||||||
#define I64_MAX (0x7FFFFFFFFFFFFFFFLL)
|
#define I64_MAX (0x7FFFFFFFFFFFFFFFLL)
|
||||||
|
|
||||||
#define I8_MIN ((i8)-0x80LL)
|
#define I8_MIN ((i8)-0x80)
|
||||||
#define I16_MIN ((i16)-0x8000LL)
|
#define I16_MIN ((i16)-0x8000)
|
||||||
#define I32_MIN ((i32)-0x80000000LL)
|
#define I32_MIN ((i32)-0x80000000)
|
||||||
#define I64_MIN ((i64)-0x8000000000000000LL)
|
#define I64_MIN ((i64)-0x8000000000000000LL)
|
||||||
|
|
||||||
|
#define PI ((f32)3.14159265358979323846)
|
||||||
|
#define TAU ((f32)6.28318530717958647693)
|
||||||
|
|
||||||
/* ========================== *
|
/* ========================== *
|
||||||
* Common structs
|
* Common structs
|
||||||
* ========================== */
|
* ========================== */
|
||||||
|
|||||||
@ -9,3 +9,6 @@
|
|||||||
|
|
||||||
#define AUDIO_ENABLED 0
|
#define AUDIO_ENABLED 0
|
||||||
#define VSYNC_ENABLED 0
|
#define VSYNC_ENABLED 0
|
||||||
|
|
||||||
|
#define GAME_FPS 30
|
||||||
|
#define USER_FRAME_LIMIT 300
|
||||||
|
|||||||
@ -9,8 +9,6 @@
|
|||||||
#include "math.h"
|
#include "math.h"
|
||||||
#include "scratch.h"
|
#include "scratch.h"
|
||||||
|
|
||||||
#define GAME_FPS 30
|
|
||||||
|
|
||||||
GLOBAL struct {
|
GLOBAL struct {
|
||||||
b32 shutdown;
|
b32 shutdown;
|
||||||
struct sys_thread game_thread;
|
struct sys_thread game_thread;
|
||||||
|
|||||||
@ -3,9 +3,6 @@
|
|||||||
|
|
||||||
#include "intrinsics.h"
|
#include "intrinsics.h"
|
||||||
|
|
||||||
#define PI ((f32)3.14159265358979323846)
|
|
||||||
#define TAU ((f32)6.28318530717958647693)
|
|
||||||
|
|
||||||
/* ========================== *
|
/* ========================== *
|
||||||
* Rounding
|
* Rounding
|
||||||
* ========================== */
|
* ========================== */
|
||||||
|
|||||||
165
src/user.c
165
src/user.c
@ -19,8 +19,9 @@
|
|||||||
#include "sound.h"
|
#include "sound.h"
|
||||||
#include "mixer.h"
|
#include "mixer.h"
|
||||||
|
|
||||||
/* NOTE: If vsync is enabled and lower then this will be ignored */
|
/* How far back in time should the user blend between game ticks (in addition
|
||||||
#define USER_FPS 300
|
* to the existing 1-tick delay) */
|
||||||
|
#define USER_INTERP_DELAY 0.005
|
||||||
|
|
||||||
struct view {
|
struct view {
|
||||||
f32 px_per_unit;
|
f32 px_per_unit;
|
||||||
@ -35,16 +36,25 @@ struct bind_state {
|
|||||||
u32 num_presses; /* How many times was this bind pressed since last frame */
|
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 {
|
GLOBAL struct {
|
||||||
b32 shutdown;
|
b32 shutdown;
|
||||||
struct sys_thread user_thread;
|
struct sys_thread user_thread;
|
||||||
|
|
||||||
|
struct arena arena;
|
||||||
|
|
||||||
struct sys_window *window;
|
struct sys_window *window;
|
||||||
struct renderer_canvas *world_canvas;
|
struct renderer_canvas *world_canvas;
|
||||||
struct renderer_canvas *screen_canvas;
|
struct renderer_canvas *screen_canvas;
|
||||||
struct view world_view;
|
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 world world;
|
||||||
|
|
||||||
struct bind_state bind_states[USER_BIND_KIND_COUNT];
|
struct bind_state bind_states[USER_BIND_KIND_COUNT];
|
||||||
@ -67,7 +77,6 @@ GLOBAL struct {
|
|||||||
struct v2 screen_mouse;
|
struct v2 screen_mouse;
|
||||||
} L = { 0 }, DEBUG_LVAR(L_user);
|
} L = { 0 }, DEBUG_LVAR(L_user);
|
||||||
|
|
||||||
|
|
||||||
/* ========================== *
|
/* ========================== *
|
||||||
* Bind state
|
* Bind state
|
||||||
* ========================== */
|
* ========================== */
|
||||||
@ -125,36 +134,115 @@ INTERNAL struct sys_event_array pull_sys_events(struct arena *arena)
|
|||||||
* Game -> user communication
|
* 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 interp_ticks {
|
||||||
struct world *from_tick;
|
struct world *from_tick;
|
||||||
struct world *to_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 */
|
__prof;
|
||||||
ASSERT(ARRAY_COUNT(L.blend_ticks) == 2);
|
|
||||||
|
|
||||||
/* Determine from & to tick from existing ticks */
|
/* Find newest stored tick */
|
||||||
struct world *from_tick = NULL;
|
struct world *newest_tick = NULL;
|
||||||
struct world *to_tick = NULL;
|
for (struct blend_tick *bt = L.head_blend_tick; bt; bt = bt->next) {
|
||||||
if (L.blend_ticks[1].tick_id > L.blend_ticks[0].tick_id) {
|
if (!newest_tick || bt->world.tick_id > newest_tick->tick_id) {
|
||||||
from_tick = &L.blend_ticks[0];
|
newest_tick = &bt->world;
|
||||||
to_tick = &L.blend_ticks[1];
|
}
|
||||||
} else {
|
|
||||||
from_tick = &L.blend_ticks[1];
|
|
||||||
to_tick = &L.blend_ticks[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check for new tick from game thread */
|
/* Pull new tick from game thread if necessary */
|
||||||
u64 latest_tick_id = game_get_latest_tick_id();
|
if (!newest_tick || game_get_latest_tick_id() > newest_tick->tick_id) {
|
||||||
if (latest_tick_id > to_tick->tick_id) {
|
struct blend_tick *latest_bt = blend_tick_alloc();
|
||||||
/* Swap pointers */
|
newest_tick = &latest_bt->world;
|
||||||
struct world *temp = from_tick;
|
game_get_latest_tick(newest_tick);
|
||||||
from_tick = to_tick;
|
}
|
||||||
to_tick = temp;
|
|
||||||
/* Pull game tick */
|
/* Find oldest tick */
|
||||||
game_get_latest_tick(to_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) {
|
return (struct interp_ticks) {
|
||||||
@ -498,7 +586,12 @@ INTERNAL void user_update(void)
|
|||||||
* ========================== */
|
* ========================== */
|
||||||
|
|
||||||
/* Pull ticks */
|
/* 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 *t0 = interp_ticks.from_tick;
|
||||||
struct world *t1 = interp_ticks.to_tick;
|
struct world *t1 = interp_ticks.to_tick;
|
||||||
|
|
||||||
@ -506,8 +599,14 @@ INTERNAL void user_update(void)
|
|||||||
{
|
{
|
||||||
__profscope(produce_interpolated_tick);
|
__profscope(produce_interpolated_tick);
|
||||||
|
|
||||||
sys_timestamp_t tick_publish_delta = t1->tick_published_ts - t0->tick_published_ts;
|
f64 t0_time = sys_timestamp_seconds(t0->tick_published_ts);
|
||||||
f32 tick_blend = (f32)((sys_timestamp() - t1->tick_published_ts) / (f64)tick_publish_delta);
|
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);
|
world_copy_replace(&L.world, t1);
|
||||||
|
|
||||||
@ -840,7 +939,7 @@ INTERNAL void user_thread_entry_point(void *arg)
|
|||||||
(UNUSED)arg;
|
(UNUSED)arg;
|
||||||
|
|
||||||
sys_timestamp_t last_frame_ts = 0;
|
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) {
|
while (!L.shutdown) {
|
||||||
__profscope(user_update_w_sleep);
|
__profscope(user_update_w_sleep);
|
||||||
@ -852,14 +951,11 @@ INTERNAL void user_thread_entry_point(void *arg)
|
|||||||
|
|
||||||
void user_startup(struct sys_window *window)
|
void user_startup(struct sys_window *window)
|
||||||
{
|
{
|
||||||
L.window = window;
|
L.arena = arena_alloc(GIGABYTE(64));
|
||||||
sys_window_register_event_callback(L.window, &window_event_callback);
|
|
||||||
|
|
||||||
L.sys_events_mutex = sys_mutex_alloc();
|
L.sys_events_mutex = sys_mutex_alloc();
|
||||||
L.sys_events_arena = arena_alloc(GIGABYTE(64));
|
L.sys_events_arena = arena_alloc(GIGABYTE(64));
|
||||||
|
|
||||||
world_alloc(&L.blend_ticks[0]);
|
|
||||||
world_alloc(&L.blend_ticks[1]);
|
|
||||||
world_alloc(&L.world);
|
world_alloc(&L.world);
|
||||||
|
|
||||||
L.world_canvas = renderer_canvas_alloc();
|
L.world_canvas = renderer_canvas_alloc();
|
||||||
@ -873,6 +969,9 @@ void user_startup(struct sys_window *window)
|
|||||||
|
|
||||||
L.screen_canvas = renderer_canvas_alloc();
|
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"));
|
L.user_thread = sys_thread_init(&user_thread_entry_point, NULL, STR("[P1] User thread"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
struct world {
|
struct world {
|
||||||
u64 tick_id; /* Starts at 1 */
|
u64 tick_id; /* Starts at 1 */
|
||||||
sys_timestamp_t tick_published_ts;
|
u64 tick_published_ts;
|
||||||
|
|
||||||
f64 dt;
|
f64 dt;
|
||||||
f64 time;
|
f64 time;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user