rework tick -> world, move related logic from game into world api
This commit is contained in:
parent
8a2c40c817
commit
adce14bbf3
13
src/arena.c
13
src/arena.c
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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},
|
||||
|
||||
15
src/common.h
15
src/common.h
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
11
src/entity.h
11
src/entity.h
@ -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);
|
||||
|
||||
166
src/game.c
166
src/game.c
@ -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)
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
10
src/tick.c
10
src/tick.c
@ -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);
|
||||
}
|
||||
22
src/tick.h
22
src/tick.h
@ -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
|
||||
88
src/user.c
88
src/user.c
@ -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
98
src/world.c
Normal 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
39
src/world.h
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user