rework tick -> world, move related logic from game into world api

This commit is contained in:
jacob 2024-03-08 19:56:34 -06:00
parent 8a2c40c817
commit adce14bbf3
16 changed files with 262 additions and 224 deletions

View File

@ -90,6 +90,19 @@ void *_arena_push_bytes(struct arena *arena, u64 size, u64 align)
return start;
}
/* Copies the memory from the source arena into the destination arena,
* replacing old contents. Destination arena will be expanded if necessary contents. */
void arena_copy_replace(struct arena *dest, struct arena *src)
{
arena_reset(dest);
if (dest->committed < src->committed) {
u64 diff = src->committed - dest->committed;
_arena_push_bytes(dest, diff, 1);
}
MEMCPY(dest->base, src->base, src->committed);
}
void arena_decommit_unused_blocks(struct arena *arena)
{
__prof;

View File

@ -29,6 +29,7 @@ struct temp_arena {
struct arena arena_alloc(u64 reserve);
void arena_release(struct arena *arena);
void *_arena_push_bytes(struct arena *arena, u64 size, u64 align);
void arena_copy_replace(struct arena *dest, struct arena *src);
void arena_decommit_unused_blocks(struct arena *arena);
INLINE void *_arena_push_bytes_zero(struct arena *arena, u64 size, u64 align)

View File

@ -71,11 +71,11 @@ struct huffman {
struct huffman_entry *entries;
};
GLOBAL const u32 g_hclen_order[] = {
GLOBAL READONLY const u32 g_hclen_order[] = {
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
};
GLOBAL const struct huffman_entry g_length_table[] = {
GLOBAL READONLY const struct huffman_entry g_length_table[] = {
{3, 0}, /* 257 */
{4, 0}, /* 258 */
{5, 0}, /* 259 */
@ -107,7 +107,7 @@ GLOBAL const struct huffman_entry g_length_table[] = {
{258, 0}, /* 285 */
};
GLOBAL const struct huffman_entry g_dist_table[] = {
GLOBAL READONLY const struct huffman_entry g_dist_table[] = {
{1, 0}, /* 0 */
{2, 0}, /* 1 */
{3, 0}, /* 2 */
@ -140,7 +140,7 @@ GLOBAL const struct huffman_entry g_dist_table[] = {
{24577, 13}, /* 29 */
};
GLOBAL const u32 g_fixed_bl_counts[][2] = {
GLOBAL READONLY const u32 g_fixed_bl_counts[][2] = {
{143, 8},
{255, 9},
{279, 7},

View File

@ -163,7 +163,7 @@ extern "C" {
/* Storage specifiers */
#pragma section(".roglob", read)
#define READ_ONLY __declspec(allocate(".roglob"))
#define READONLY __declspec(allocate(".roglob"))
/* Markup */
#define UNUSED void
@ -231,19 +231,6 @@ extern "C" {
#define CAT1(a, b) a ## b
#define CAT(a, b) CAT1(a, b)
/* ========================== *
* Nil structs
* ========================== */
/* Declare */
#define DECLARE_NIL_STRUCT(type, function_name) \
extern READ_ONLY type CAT(__nil_struct__, function_name); \
INLINE READ_ONLY type *function_name(void) { return &CAT(__nil_struct__, function_name); }
/* Expects struct body after invocation. E.g. DEFINE_NIL_STRUCT(struct a, a_nil) { 0 } */
#define DEFINE_NIL_STRUCT(type, function_name) \
type READ_ONLY CAT(__nil_struct__, function_name) =
#if 0
/* ========================== *
* Bit utils

View File

@ -6,7 +6,6 @@
#define RESOURCES_EMBEDDED !(DEVELOPER)
#define PIXELS_PER_UNIT 256
#define MAX_ENTITIES 4096
#define AUDIO_ENABLED 0
#define VSYNC_ENABLED 0

View File

@ -1,10 +1,6 @@
#include "entity.h"
DEFINE_NIL_STRUCT(struct entity, entity_nil) { 0 };
/* ========================== *
* Prop
* ========================== */
READONLY struct entity _g_entity_nil = { 0 };
void entity_enable_prop(struct entity *entity, enum entity_prop prop)
{

View File

@ -4,10 +4,6 @@
#include "sheet.h"
#include "mixer.h"
/* ========================== *
* Entity
* ========================== */
struct entity_handle {
u64 idx;
u64 gen;
@ -35,7 +31,7 @@ struct entity {
u64 continuity_gen;
u64 props[(ENTITY_PROP_COUNT + 63) / 64];
struct entity *next_free;
struct entity_handle next_free;
/* Tree */
struct entity_handle parent;
@ -96,9 +92,8 @@ struct entity {
f32 camera_zoom;
};
DECLARE_NIL_STRUCT(struct entity, entity_nil);
/* Prop */
extern READONLY struct entity _g_entity_nil;
INLINE READONLY struct entity *entity_nil(void) { return &_g_entity_nil; }
void entity_enable_prop(struct entity *entity, enum entity_prop prop);
void entity_disable_prop(struct entity *entity, enum entity_prop prop);

View File

@ -2,9 +2,8 @@
#include "app.h"
#include "sys.h"
#include "util.h"
#include "entity.h"
#include "world.h"
#include "sheet.h"
#include "tick.h"
#include "sound.h"
#include "mixer.h"
#include "math.h"
@ -17,8 +16,7 @@ GLOBAL struct {
struct sys_thread game_thread;
f64 timescale;
struct entity *free_entity_head;
struct tick tick;
struct world world;
/* Game thread input */
struct sys_mutex game_cmds_mutex;
@ -26,78 +24,9 @@ GLOBAL struct {
/* Game thread output */
struct sys_mutex published_tick_mutex;
struct tick published_tick;
struct world published_tick;
} L = { 0 }, DEBUG_LVAR(L_game);
/* ========================== *
* Entity allocation
* ========================== */
INTERNAL struct entity *entity_alloc(void)
{
struct entity *entity = NULL;
if (L.free_entity_head) {
/* Reuse from free list */
entity = L.free_entity_head;
L.free_entity_head = entity->next_free;
*entity = (struct entity) {
.handle = entity->handle
};
} else {
/* Make new */
if (L.tick.entities_count >= MAX_ENTITIES) {
sys_panic(STR("MAX_ENTITIES reached"));
}
u64 idx = L.tick.entities_count++;
entity = &L.tick.entities[idx];
*entity = (struct entity) {
.handle = { .gen = 1, .idx = idx }
};
}
return entity;
}
#if 0
INTERNAL void entity_release(struct entity *entity)
{
entity->next_free = L.free_entity_head;
L.free_entity_head = entity;
*entity = (struct entity) {
.gen = entity->gen + 1
};
}
#endif
/* Returns a valid entity or nil entity. Always safe to read result, need to check to write. */
INTERNAL struct entity *entity_from_handle(struct entity_handle eh)
{
if (eh.idx < L.tick.entities_count) {
struct entity *entity = &L.tick.entities[eh.idx];
if (entity->handle.gen == eh.gen) {
return entity;
}
}
return entity_nil();
}
INTERNAL void entity_tree_attach(struct entity *parent, struct entity *child)
{
struct entity *first_child = entity_from_handle(parent->first);
struct entity *last_child = entity_from_handle(parent->last);
child->prev = last_child->handle;
child->parent = parent->handle;
if (last_child->valid) {
last_child->next = child->handle;
}
parent->last = child->handle;
if (!first_child->valid) {
parent->first = child->handle;
}
}
/* ========================== *
* Game cmd
* ========================== */
@ -140,8 +69,8 @@ INTERNAL void publish_game_tick(void)
{
__prof;
sys_mutex_lock(&L.published_tick_mutex);
L.tick.published_ts = sys_timestamp();
tick_copy(&L.published_tick, &L.tick);
L.world.tick_published_ts = sys_timestamp();
world_copy_replace(&L.published_tick, &L.world);
sys_mutex_unlock(&L.published_tick_mutex);
}
@ -151,16 +80,16 @@ INTERNAL void game_update(void)
struct temp_arena scratch = scratch_begin_no_conflict();
++L.tick.id;
L.tick.dt = max_f64(0.0, (1.0 / GAME_FPS) * L.timescale);
L.tick.time += L.tick.dt;
++L.world.tick_id;
L.world.dt = max_f64(0.0, (1.0 / GAME_FPS) * L.timescale);
L.world.time += L.world.dt;
/* TODO: remove this (testing) */
/* Initialize entities */
static b32 run = 0;
if (!run) {
run = 1;
(UNUSED)entity_tree_attach;
(UNUSED)world_link_entities;
/* Player ent */
struct entity *player_ent;
@ -169,7 +98,7 @@ INTERNAL void game_update(void)
struct v2 size = V2(1, 1);
f32 r = 0;
struct entity *e = entity_alloc();
struct entity *e = world_alloc_entity(&L.world);
e->valid = true;
e->rel_xform = XFORM_TRS(.t = pos, .r = r, .s = size);
@ -219,7 +148,7 @@ INTERNAL void game_update(void)
struct v2 size = V2(1, 1);
f32 r = 0;
struct entity *e = entity_alloc();
struct entity *e = world_alloc_entity(&L.world);
e->valid = true;
e->rel_xform = XFORM_TRS(.t = pos, .r = r, .s = size);
@ -260,12 +189,12 @@ INTERNAL void game_update(void)
//entity_enable_prop(e, ENTITY_PROP_TEST);
//entity_enable_prop(e, ENTITY_PROP_TEST_FOLLOW_MOUSE);
entity_tree_attach(player_ent, e);
world_link_entities(&L.world, player_ent, e);
}
/* Camera ent */
{
struct entity *e = entity_alloc();
struct entity *e = world_alloc_entity(&L.world);
e->valid = true;
e->rel_xform = XFORM_IDENT;
@ -281,7 +210,7 @@ INTERNAL void game_update(void)
* Process game cmds
* ========================== */
L.tick.player_move_dir = V2(0, 0);
L.world.player_move_dir = V2(0, 0);
struct game_cmd_array game_cmds = pop_cmds(scratch.arena);
for (u64 i = 0; i < game_cmds.count; ++i) {
@ -292,21 +221,21 @@ INTERNAL void game_update(void)
case GAME_CMD_KIND_PLAYER_MOVE: {
struct v2 dir = cmd.dir;
L.tick.player_move_dir = v2_add(L.tick.player_move_dir, dir);
L.world.player_move_dir = v2_add(L.world.player_move_dir, dir);
} break;
/* Focus */
case GAME_CMD_KIND_PLAYER_FOCUS: {
L.tick.player_focus = cmd.pos;
L.world.player_focus = cmd.pos;
} break;
default: break;
};
}
if (v2_len(L.tick.player_move_dir) > 1.f) {
if (v2_len(L.world.player_move_dir) > 1.f) {
/* Clamp movement magnitude */
L.tick.player_move_dir = v2_norm(L.tick.player_move_dir);
L.world.player_move_dir = v2_norm(L.world.player_move_dir);
}
/* ---------------------------------------------------------------------- */
@ -316,8 +245,8 @@ INTERNAL void game_update(void)
* Update entities pre-physics
* ========================== */
for (u64 entity_index = 0; entity_index < ARRAY_COUNT(L.tick.entities); ++entity_index) {
struct entity *ent = &L.tick.entities[entity_index];
for (u64 entity_index = 0; entity_index < L.world.entities_count; ++entity_index) {
struct entity *ent = &L.world.entities[entity_index];
if (!ent->valid) continue;
/* ========================== *
@ -333,7 +262,7 @@ INTERNAL void game_update(void)
/* ENTITY_PROP_ANIMATING */
if (entity_has_prop(ent, ENTITY_PROP_ANIMATING) && ent->animation_start_time == 0) {
ent->animation_start_time = L.tick.time;
ent->animation_start_time = L.world.time;
}
/* ========================== *
@ -343,7 +272,7 @@ INTERNAL void game_update(void)
if (ent->animation_start_time > 0) {
/* Stop animation if past duration and not looping */
if (!ent->animation_looping) {
f64 time_in_anim = L.tick.time - ent->animation_start_time;
f64 time_in_anim = L.world.time - ent->animation_start_time;
struct sheet *sheet = sheet_load(ent->sprite_name);
if (sheet) {
@ -374,7 +303,7 @@ INTERNAL void game_update(void)
/* ENTITY_PROP_TEST */
if (entity_has_prop(ent, ENTITY_PROP_TEST)) {
f32 t = ((f32)L.tick.time);
f32 t = ((f32)L.world.time);
f32 r = t * 2.f;
f32 s = 1 + (math_fabs(math_sin(t * 5)) * 3);
(UNUSED)r;
@ -390,8 +319,8 @@ INTERNAL void game_update(void)
* Update entity physics
* ========================== */
for (u64 entity_index = 0; entity_index < ARRAY_COUNT(L.tick.entities); ++entity_index) {
struct entity *ent = &L.tick.entities[entity_index];
for (u64 entity_index = 0; entity_index < L.world.entities_count; ++entity_index) {
struct entity *ent = &L.world.entities[entity_index];
if (!ent->valid) continue;
if (ent->parent.gen) continue; /* Only update parent entities */
@ -403,7 +332,7 @@ INTERNAL void game_update(void)
f32 max_speed = ent->player_max_speed;
f32 acceleration_rate = ent->player_acceleration;
acceleration_rate = clamp_f32(acceleration_rate, 0, GAME_FPS); /* Can't integrate acceleration rate higher than FPS */
struct v2 target_velocity = v2_mul(L.tick.player_move_dir, max_speed);
struct v2 target_velocity = v2_mul(L.world.player_move_dir, max_speed);
struct v2 target_acceleration = v2_sub(target_velocity, ent->velocity);
ent->acceleration = v2_mul(target_acceleration, acceleration_rate);
}
@ -413,7 +342,7 @@ INTERNAL void game_update(void)
* ========================== */
{
f32 dt = (f32)L.tick.dt;
f32 dt = (f32)L.world.dt;
/* Apply acceleration to velocity */
struct v2 a = v2_mul(ent->acceleration, dt);
@ -429,7 +358,7 @@ INTERNAL void game_update(void)
if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
struct v2 ent_pos = ent->rel_xform.og;
struct v2 look_pos = L.tick.player_focus;
struct v2 look_pos = L.world.player_focus;
f32 r = v2_angle_to_point(ent_pos, look_pos) + PI / 2;
ent->rel_xform = xform_with_rotation(ent->rel_xform, r);
}
@ -440,8 +369,8 @@ INTERNAL void game_update(void)
/* ENTITY_PROP_TEST_FOLLOW_MOUSE */
if (entity_has_prop(ent, ENTITY_PROP_TEST_FOLLOW_MOUSE)) {
ent->rel_xform.og = L.tick.player_focus;
ent->test_start_rel_xform.og = L.tick.player_focus;
ent->rel_xform.og = L.world.player_focus;
ent->test_start_rel_xform.og = L.world.player_focus;
}
/* ========================== *
@ -450,25 +379,27 @@ INTERNAL void game_update(void)
ent->world_xform = ent->rel_xform;
struct entity *child = entity_from_handle(ent->first);
struct entity *child = world_entity_from_handle(&L.world, ent->first);
struct xform parent_xform = ent->world_xform;
while (child->valid) {
/* Calculate child world xform */
child->world_xform = xform_mul(parent_xform, child->rel_xform);
/* Depth first iteration */
if (child->first.gen) {
/* Next child */
parent_xform = child->world_xform;
child = entity_from_handle(child->first);
child = world_entity_from_handle(&L.world, child->first);
} else if (child->next.gen) {
/* Next sibling */
child = entity_from_handle(child->next);
} else if (child->parent.gen && entity_from_handle(child->parent)->next.gen) {
child = world_entity_from_handle(&L.world, child->next);
} else if (child->parent.gen && world_entity_from_handle(&L.world, child->parent)->next.gen) {
/* Next parent sibling */
struct entity *parent = entity_from_handle(child->parent);
struct entity *grandparent = entity_from_handle(parent->parent);
struct entity *parent = world_entity_from_handle(&L.world, child->parent);
struct entity *grandparent = world_entity_from_handle(&L.world, parent->parent);
parent_xform = grandparent->world_xform;
child = entity_from_handle(parent->next);
child = world_entity_from_handle(&L.world, parent->next);
} else {
child = entity_nil();
}
@ -479,8 +410,8 @@ INTERNAL void game_update(void)
* Update entities post-physics
* ========================== */
for (u64 entity_index = 0; entity_index < ARRAY_COUNT(L.tick.entities); ++entity_index) {
struct entity *ent = &L.tick.entities[entity_index];
for (u64 entity_index = 0; entity_index < L.world.entities_count; ++entity_index) {
struct entity *ent = &L.world.entities[entity_index];
if (!ent->valid) continue;
/* ========================== *
@ -488,7 +419,7 @@ INTERNAL void game_update(void)
* ========================== */
if (entity_has_prop(ent, ENTITY_PROP_CAMERA)) {
struct entity *follow = entity_from_handle(ent->camera_follow);
struct entity *follow = world_entity_from_handle(&L.world, ent->camera_follow);
ent->world_xform = follow->world_xform;
ent->world_xform = xform_with_rotation(ent->world_xform, 0);
@ -548,9 +479,14 @@ void game_startup(void)
L.game_cmds_mutex = sys_mutex_alloc();
L.game_cmds_arena = arena_alloc(GIGABYTE(64));
/* Initialize tick storage */
/* Initialize world */
world_alloc(&L.world);
/* Initialize tick transmission */
world_alloc(&L.published_tick);
L.published_tick_mutex = sys_mutex_alloc();
L.timescale = 1.0;
L.game_thread = sys_thread_init(&game_thread_entry_point, NULL, STR("[P2] Game thread"));
}
@ -565,16 +501,16 @@ void game_shutdown(void)
* Interface
* ========================== */
void game_get_latest_tick(struct tick *dest)
void game_get_latest_tick(struct world *dest)
{
sys_mutex_lock(&L.published_tick_mutex);
tick_copy(dest, &L.published_tick);
world_copy_replace(dest, &L.published_tick);
sys_mutex_unlock(&L.published_tick_mutex);
}
u64 game_get_latest_tick_id(void)
{
return L.published_tick.id;
return L.published_tick.tick_id;
}
void game_push_cmds(struct game_cmd_array cmd_array)

View File

@ -1,7 +1,7 @@
#ifndef GAME_H
#define GAME_H
struct tick;
struct world;
enum game_cmd_kind {
GAME_CMD_KIND_NONE,
@ -31,7 +31,7 @@ struct game_cmd_array {
void game_startup(void);
void game_shutdown(void);
void game_get_latest_tick(struct tick *dest);
void game_get_latest_tick(struct world *dest);
u64 game_get_latest_tick_id(void);
void game_push_cmds(struct game_cmd_array cmd_array);

View File

@ -21,7 +21,7 @@ GLOBAL struct {
b32 file_valid;
} L = { 0 }, DEBUG_LVAR(L_log);
GLOBAL const struct log_level_settings g_log_level_settings[LOG_LEVEL_COUNT] = {
GLOBAL READONLY const struct log_level_settings g_log_level_settings[LOG_LEVEL_COUNT] = {
[LOG_LEVEL_CRITICAL] = {
STR_NOCAST("CRITICAL"),
0xFFFF00FF

View File

@ -267,7 +267,7 @@ struct sheet *sheet_load(struct string path)
* Sheet data
* ========================== */
GLOBAL struct sheet_tag g_default_tag = { 0 };
GLOBAL READONLY struct sheet_tag g_default_tag = { 0 };
struct sheet_tag sheet_get_tag(struct sheet *sheet, struct string name)
{

View File

@ -1,10 +0,0 @@
#include "tick.h"
void tick_copy(struct tick *dst, struct tick *src)
{
__prof;
/* Copy non-entity fields */
MEMCPY(dst, src, FIELD_OFFSETOF(struct tick, entities));
/* Copy entities */
MEMCPY(&dst->entities, &src->entities, sizeof(struct entity) * src->entities_count);
}

View File

@ -1,22 +0,0 @@
#ifndef TICK_H
#define TICK_H
#include "entity.h"
struct tick {
u64 id; /* Starts at 1 */
sys_timestamp_t published_ts;
f64 dt;
f64 time;
struct v2 player_move_dir; /* Player movement direction */
struct v2 player_focus; /* Mouse cursor pos in world coordinates */
u64 entities_count; /* Includes 'released' & non-active entities */
struct entity entities[MAX_ENTITIES];
};
void tick_copy(struct tick *dst, struct tick *src);
#endif

View File

@ -12,7 +12,8 @@
#include "math.h"
#include "console.h"
#include "sys.h"
#include "tick.h"
#include "world.h"
#include "entity.h"
/* FIXME: remove this (testing) */
#include "sound.h"
@ -29,6 +30,11 @@ struct view {
f32 rot;
};
struct bind_state {
b32 pressed; /* Is this bind held down this frame */
u32 num_presses; /* How many times was this bind pressed since last frame */
};
GLOBAL struct {
b32 shutdown;
struct sys_thread user_thread;
@ -37,7 +43,11 @@ GLOBAL struct {
struct renderer_canvas *world_canvas;
struct renderer_canvas *ui_canvas;
struct view world_view;
struct tick blend_ticks[2];
struct world blend_ticks[2];
struct world world;
struct bind_state bind_states[USER_BIND_KIND_COUNT];
b32 debug_camera;
b32 debug_camera_panning;
@ -62,16 +72,9 @@ GLOBAL struct {
* Bind state
* ========================== */
struct bind_state {
b32 pressed; /* Is this bind held down this frame */
u32 num_presses; /* How many times was this bind pressed since last frame */
};
GLOBAL struct bind_state g_bind_states[USER_BIND_KIND_COUNT] = { 0 };
/* TODO: Remove this */
GLOBAL enum user_bind_kind g_binds[SYS_BTN_COUNT] = {
GLOBAL READONLY enum user_bind_kind g_binds[SYS_BTN_COUNT] = {
[SYS_BTN_W] = USER_BIND_KIND_MOVE_UP,
[SYS_BTN_S] = USER_BIND_KIND_MOVE_DOWN,
[SYS_BTN_A] = USER_BIND_KIND_MOVE_LEFT,
@ -122,8 +125,8 @@ INTERNAL struct sys_event_array pull_sys_events(struct arena *arena)
* ========================== */
struct interp_ticks {
struct tick *from_tick;
struct tick *to_tick;
struct world *from_tick;
struct world *to_tick;
};
INTERNAL struct interp_ticks pull_interp_ticks(void)
@ -132,9 +135,9 @@ INTERNAL struct interp_ticks pull_interp_ticks(void)
ASSERT(ARRAY_COUNT(L.blend_ticks) == 2);
/* Determine from & to tick from existing ticks */
struct tick *from_tick = NULL;
struct tick *to_tick = NULL;
if (L.blend_ticks[1].id > L.blend_ticks[0].id) {
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 {
@ -144,9 +147,9 @@ INTERNAL struct interp_ticks pull_interp_ticks(void)
/* Check for new tick from game thread */
u64 latest_tick_id = game_get_latest_tick_id();
if (latest_tick_id > to_tick->id) {
if (latest_tick_id > to_tick->tick_id) {
/* Swap pointers */
struct tick *temp = from_tick;
struct world *temp = from_tick;
from_tick = to_tick;
to_tick = temp;
/* Pull game tick */
@ -314,9 +317,9 @@ INTERNAL void user_update(void)
* ========================== */
/* Reset bind states "was_pressed" */
for (u32 i = 0; i < ARRAY_COUNT(g_bind_states); ++i) {
g_bind_states[i] = (struct bind_state) {
.pressed = g_bind_states[i].pressed
for (u32 i = 0; i < ARRAY_COUNT(L.bind_states); ++i) {
L.bind_states[i] = (struct bind_state) {
.pressed = L.bind_states[i].pressed
};
}
@ -371,9 +374,9 @@ INTERNAL void user_update(void)
enum user_bind_kind bind = g_binds[button];
if (bind) {
b32 pressed = event->kind == SYS_EVENT_KIND_BUTTON_DOWN;
g_bind_states[bind].pressed = pressed;
L.bind_states[bind].pressed = pressed;
if (pressed) {
++g_bind_states[bind].num_presses;
++L.bind_states[bind].num_presses;
}
}
}
@ -386,8 +389,8 @@ INTERNAL void user_update(void)
/* Movement */
{
struct v2 input_move_dir = { 0 };
for (enum user_bind_kind bind = 0; bind < (i32)ARRAY_COUNT(g_bind_states); ++bind) {
struct bind_state state = g_bind_states[bind];
for (enum user_bind_kind bind = 0; bind < (i32)ARRAY_COUNT(L.bind_states); ++bind) {
struct bind_state state = L.bind_states[bind];
if (!state.pressed && state.num_presses <= 0) {
continue;
@ -424,11 +427,11 @@ INTERNAL void user_update(void)
* Update view from debug camera
* ========================== */
if (g_bind_states[USER_BIND_KIND_DEBUG_DRAW].num_presses > 0) {
if (L.bind_states[USER_BIND_KIND_DEBUG_DRAW].num_presses > 0) {
L.debug_draw = !L.debug_draw;
}
if (g_bind_states[USER_BIND_KIND_DEBUG_CAMERA].num_presses > 0) {
if (L.bind_states[USER_BIND_KIND_DEBUG_CAMERA].num_presses > 0) {
L.debug_camera = !L.debug_camera;
}
@ -444,7 +447,7 @@ INTERNAL void user_update(void)
}
/* Zoom view */
i32 input_zooms = g_bind_states[USER_BIND_KIND_ZOOM_IN].num_presses - g_bind_states[USER_BIND_KIND_ZOOM_OUT].num_presses;
i32 input_zooms = L.bind_states[USER_BIND_KIND_ZOOM_IN].num_presses - L.bind_states[USER_BIND_KIND_ZOOM_OUT].num_presses;
if (input_zooms != 0) {
i32 dir = input_zooms >= 0 ? 1 : -1;
u32 zooms_abs = input_zooms >= 0 ? input_zooms : -input_zooms;
@ -479,29 +482,28 @@ INTERNAL void user_update(void)
/* Pull ticks */
struct interp_ticks interp_ticks = pull_interp_ticks();
struct tick *t0 = interp_ticks.from_tick;
struct tick *t1 = interp_ticks.to_tick;
struct world *t0 = interp_ticks.from_tick;
struct world *t1 = interp_ticks.to_tick;
/* Produce interpolated tick */
struct tick *tick = arena_push_zero(scratch.arena, struct tick);
{
__profscope(produce_interpolated_tick);
sys_timestamp_t tick_publish_delta = t1->published_ts - t0->published_ts;
f32 tick_blend = (f32)((sys_timestamp() - t1->published_ts) / (f64)tick_publish_delta);
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);
tick_copy(tick, t1);
world_copy_replace(&L.world, t1);
#if 1
/* Blend time */
tick->time = math_lerp_f64(t0->time, t1->time, (f64)tick_blend);
L.world.time = math_lerp_f64(t0->time, t1->time, (f64)tick_blend);
/* Blend entities */
u64 num_entities = min_u64(t0->entities_count, t1->entities_count);
for (u64 i = 0; i < num_entities; ++i) {
struct entity *e = &tick->entities[i];
struct entity *e0 = &t0->entities[i];
struct entity *e1 = &t1->entities[i];
struct entity *e = &L.world.entities[i];
if (e0->handle.gen == e1->handle.gen && e0->continuity_gen == e1->continuity_gen) {
e->rel_xform = xform_lerp(e0->rel_xform, e1->rel_xform, tick_blend);
e->world_xform = xform_lerp(e0->world_xform, e1->world_xform, tick_blend);
@ -525,8 +527,8 @@ INTERNAL void user_update(void)
* ========================== */
/* Find camera */
for (u64 entity_index = 0; entity_index < tick->entities_count; ++entity_index) {
struct entity *ent = &tick->entities[entity_index];
for (u64 entity_index = 0; entity_index < L.world.entities_count; ++entity_index) {
struct entity *ent = &L.world.entities[entity_index];
if (!ent->valid) continue;
if (entity_has_prop(ent, ENTITY_PROP_CAMERA) && ent->camera_active && !L.debug_camera) {
@ -586,8 +588,8 @@ INTERNAL void user_update(void)
* ========================== */
/* Iterate entities */
for (u64 entity_index = 0; entity_index < tick->entities_count; ++entity_index) {
struct entity *ent = &tick->entities[entity_index];
for (u64 entity_index = 0; entity_index < L.world.entities_count; ++entity_index) {
struct entity *ent = &L.world.entities[entity_index];
if (!ent->valid) continue;
b32 is_camera = entity_has_prop(ent, ENTITY_PROP_CAMERA);
@ -613,7 +615,7 @@ INTERNAL void user_update(void)
struct sheet_frame frame = sheet_get_frame(sheet, tag.start);
if (entity_has_prop(ent, ENTITY_PROP_ANIMATING)) {
b32 looping = ent->animation_looping;
f64 time_in_anim = tick->time - ent->animation_start_time;
f64 time_in_anim = L.world.time - ent->animation_start_time;
u64 frame_index = tag.start;
while (time_in_anim > 0) {
@ -769,7 +771,7 @@ INTERNAL void user_update(void)
u64 canvases_count = 0;
{
/* Only render world if not on first frame */
if (t0->id > 0 && t1->id > 0) {
if (t0->tick_id > 0 && t1->tick_id > 0) {
*arena_push(scratch.arena, struct renderer_canvas *) = L.world_canvas;
++canvases_count;
}
@ -810,6 +812,10 @@ void user_startup(struct sys_window *window)
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();
L.world_view = (struct view) {
.px_per_unit = PIXELS_PER_UNIT,

98
src/world.c Normal file
View File

@ -0,0 +1,98 @@
#include "world.h"
#include "entity.h"
/* ========================== *
* World allocation
* ========================== */
void world_alloc(struct world *world)
{
MEMZERO_STRUCT(world);
world->entities_arena = arena_alloc(GIGABYTE(64));
world->entities = (struct entity *)world->entities_arena.base;
}
void world_copy_replace(struct world *dest, struct world *src)
{
__prof;
/* Copy non-arena fields */
MEMCPY(dest, src, FIELD_OFFSETOF(struct world, _copy_barrier));
/* Copy arena contents */
arena_copy_replace(&dest->entities_arena, &src->entities_arena);
}
/* ========================== *
* Entity allocation
* ========================== */
struct entity *world_alloc_entity(struct world *world)
{
struct entity *entity = NULL;
if (world->first_free_entity.gen) {
/* Reuse from free list */
entity = world_entity_from_handle(world, world->first_free_entity);
world->first_free_entity = entity->next_free;
*entity = (struct entity) {
.handle = entity->handle
};
} else {
/* Make new */
u64 idx = world->entities_count++;
entity = &world->entities[idx];
*entity = (struct entity) {
.handle = { .gen = 1, .idx = idx }
};
}
return entity;
}
void world_release_entity(struct world *world, struct entity *entity)
{
struct entity_handle next_free = world->first_free_entity;
world->first_free_entity = entity->handle;
*entity = (struct entity) {
.handle.gen = entity->handle.gen + 1,
.next_free = next_free
};
}
/* ========================== *
* Queries
* ========================== */
/* Returns a valid entity or nil entity. Always safe to read result, need to check to write. */
struct entity *world_entity_from_handle(struct world *world, struct entity_handle handle)
{
if (handle.idx < world->entities_count) {
struct entity *entities = world->entities;
struct entity *entity = &entities[handle.idx];
if (entity->handle.gen == handle.gen) {
return entity;
}
}
return entity_nil();
}
/* ========================== *
* Tree
* ========================== */
void world_link_entities(struct world *world, struct entity *parent, struct entity *child)
{
struct entity *first_child = world_entity_from_handle(world, parent->first);
struct entity *last_child = world_entity_from_handle(world, parent->last);
child->prev = last_child->handle;
child->parent = parent->handle;
if (last_child->valid) {
last_child->next = child->handle;
}
parent->last = child->handle;
if (!first_child->valid) {
parent->first = child->handle;
}
}

39
src/world.h Normal file
View File

@ -0,0 +1,39 @@
#ifndef WORLD_H
#define WORLD_H
#include "entity.h"
struct world {
u64 tick_id; /* Starts at 1 */
sys_timestamp_t tick_published_ts;
f64 dt;
f64 time;
struct v2 player_move_dir; /* Player movement direction */
struct v2 player_focus; /* Mouse cursor pos in world coordinates */
u64 entities_count; /* Includes 'released' & inactive entities */
struct entity_handle first_free_entity;
/* ====================================================================== */
/* Everything after this field is not automatically coppied
* in world_copy_replace */
u8 _copy_barrier;
/* Arenas */
struct arena entities_arena;
struct entity *entities;
};
void world_alloc(struct world *world);
void world_copy_replace(struct world *dest, struct world *src);
struct entity *world_alloc_entity(struct world *world);
void world_release_entity(struct world *world, struct entity *entity);
struct entity *world_entity_from_handle(struct world *world, struct entity_handle handle);
void world_link_entities(struct world *world, struct entity *parent, struct entity *child);
#endif