power_play/src/game.c
2024-03-13 15:10:16 -05:00

536 lines
18 KiB
C

#include "game.h"
#include "app.h"
#include "sys.h"
#include "util.h"
#include "world.h"
#include "sheet.h"
#include "sound.h"
#include "mixer.h"
#include "math.h"
#include "scratch.h"
GLOBAL struct {
b32 shutdown;
struct sys_thread game_thread;
struct world world;
/* Game thread input */
struct sys_mutex game_cmds_mutex;
struct arena game_cmds_arena;
/* Game thread output */
struct sys_mutex published_tick_mutex;
struct world published_tick;
} L = { 0 }, DEBUG_LVAR(L_game);
/* ========================== *
* Game cmd
* ========================== */
INTERNAL void push_cmds(struct game_cmd_array cmd_array)
{
sys_mutex_lock(&L.game_cmds_mutex);
{
struct game_cmd *cmds = arena_push_array(&L.game_cmds_arena, struct game_cmd, cmd_array.count);
MEMCPY(cmds, cmd_array.cmds, cmd_array.count * sizeof(*cmds));
}
sys_mutex_unlock(&L.game_cmds_mutex);
}
INTERNAL struct game_cmd_array pop_cmds(struct arena *arena)
{
struct game_cmd_array array = { 0 };
if (L.game_cmds_arena.pos > 0) {
sys_mutex_lock(&L.game_cmds_mutex);
{
struct buffer game_cmds_buff = arena_to_buffer(&L.game_cmds_arena);
arena_align(arena, ALIGNOF(struct game_cmd));
array.cmds = (struct game_cmd *)arena_push_array(arena, u8, game_cmds_buff.size);
array.count = game_cmds_buff.size / sizeof(struct game_cmd);
MEMCPY(array.cmds, game_cmds_buff.data, game_cmds_buff.size);
arena_reset(&L.game_cmds_arena);
}
sys_mutex_unlock(&L.game_cmds_mutex);
}
return array;
}
/* ========================== *
* Update
* ========================== */
INTERNAL void publish_game_tick(void)
{
__prof;
sys_mutex_lock(&L.published_tick_mutex);
world_copy_replace(&L.published_tick, &L.world);
sys_mutex_unlock(&L.published_tick_mutex);
}
INTERNAL void game_update(void)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
/* TODO: remove this (testing) */
/* Initialize entities */
static b32 run = 0;
if (!run) {
run = 1;
/* Player ent */
struct entity *player_ent;
{
struct v2 pos = V2(1, 1);
struct v2 size = V2(1, 1);
f32 r = 0;
struct entity *e = entity_alloc(&L.world.entity_store);
e->valid = true;
e->rel_xform = XFORM_TRS(.t = pos, .r = r, .s = size);
struct string sprite_name = STR("res/graphics/tim.ase");
struct string sprite_tag_name = STR("UNARMED");
struct sheet *sheet = sheet_load(sprite_name);
f32 meters_width = sheet->frame_size.x / PIXELS_PER_UNIT;
f32 meters_height = sheet->frame_size.y / PIXELS_PER_UNIT;
struct v2 sprite_pos = V2(0, 0);
f32 sprite_rot = 0;
struct v2 sprite_size = V2(meters_width, meters_height);
struct v2 sprite_pivot;
{
struct v2 sprite_pivot_norm = V2(0, 0.5); /* Pivot x & y are each normalized about sprite dimensions. <0, 0> is center, <1, 1> is bottom right corner, etc. */
struct v2 half_size = v2_mul(sprite_size, 0.5f);
sprite_pivot = v2_mul_v2(sprite_pivot_norm, half_size);
}
struct xform sprite_xf = XFORM_POS(sprite_pos);
sprite_xf = xform_rotate(sprite_xf, sprite_rot);
sprite_xf = xform_translate(sprite_xf, v2_neg(sprite_pivot));
sprite_xf = xform_scale(sprite_xf, sprite_size);
e->sprite_xform = sprite_xf;
e->sprite_name = sprite_name;
e->sprite_tag_name = sprite_tag_name;
e->sprite_tint = COLOR_WHITE;
entity_enable_prop(e, ENTITY_PROP_PLAYER_CONTROLLED);
e->player_max_speed = 5.f;
e->player_acceleration = 15.0f;
entity_enable_prop(e, ENTITY_PROP_ANIMATING);
player_ent = e;
//entity_enable_prop(e, ENTITY_PROP_TEST);
//entity_enable_prop(e, ENTITY_PROP_TEST_FOLLOW_MOUSE);
}
/* Child ent */
struct entity *parent = player_ent;
{
struct v2 pos = V2(0, 0.25);
struct v2 size = V2(1, 1);
f32 r = 0;
struct entity *e = entity_alloc(&L.world.entity_store);
e->valid = true;
e->rel_xform = XFORM_TRS(.t = pos, .r = r, .s = size);
struct string sprite_name = STR("res/graphics/tim.ase");
struct string sprite_tag_name = STR("UNARMED");
struct sheet *sheet = sheet_load(sprite_name);
f32 meters_width = sheet->frame_size.x / PIXELS_PER_UNIT;
f32 meters_height = sheet->frame_size.y / PIXELS_PER_UNIT;
struct v2 sprite_pos = V2(0, 0);
f32 sprite_rot = 0;
struct v2 sprite_size = V2(meters_width, meters_height);
struct v2 sprite_pivot;
{
struct v2 sprite_pivot_norm = V2(0, 0.5); /* Pivot x & y are each normalized about sprite dimensions. <0, 0> is center, <1, 1> is bottom right corner, etc. */
struct v2 half_size = v2_mul(sprite_size, 0.5f);
sprite_pivot = v2_mul_v2(sprite_pivot_norm, half_size);
}
struct xform sprite_xf = XFORM_POS(sprite_pos);
sprite_xf = xform_rotate(sprite_xf, sprite_rot);
sprite_xf = xform_translate(sprite_xf, v2_neg(sprite_pivot));
sprite_xf = xform_scale(sprite_xf, sprite_size);
e->sprite_xform = sprite_xf;
e->sprite_name = sprite_name;
e->sprite_tag_name = sprite_tag_name;
e->sprite_tint = RGBA_F(0.5, 0.5, 0, 1);
//entity_enable_prop(e, ENTITY_PROP_PLAYER_CONTROLLED);
//e->player_max_speed = 5.f;
//e->player_acceleration = 15.0f;
entity_enable_prop(e, ENTITY_PROP_ANIMATING);
//entity_enable_prop(e, ENTITY_PROP_TEST);
//entity_enable_prop(e, ENTITY_PROP_TEST_FOLLOW_MOUSE);
entity_link(&L.world.entity_store, parent, e);
if (sys_rand_u32() % 2 == 0) {
u64 parent_idx = sys_rand_u32() % world_get_entities(&L.world).count;
struct entity *rand_ent = entity_from_handle(&L.world.entity_store, (struct entity_handle) { .idx = parent_idx, .gen = 1 });
if (rand_ent->valid) {
parent = rand_ent;
} else {
parent = e;
}
}
}
/* Camera ent */
{
struct entity *e = entity_alloc(&L.world.entity_store);
e->valid = true;
e->rel_xform = XFORM_IDENT;
entity_enable_prop(e, ENTITY_PROP_CAMERA);
e->camera_active = true;
e->camera_follow = player_ent->handle;
e->camera_zoom = 1;
}
}
++L.world.tick_id;
L.world.tick_ts = sys_timestamp();
L.world.dt = max_f64(0.0, (1.0 / GAME_FPS) * L.world.timescale);
L.world.time += L.world.dt;
struct entity_array entities_array = world_get_entities(&L.world);
/* ========================== *
* Process game cmds
* ========================== */
L.world.player_move_dir = V2(0, 0);
struct game_cmd_array game_cmds = pop_cmds(scratch.arena);
for (u64 cmd_index = 0; cmd_index < game_cmds.count; ++cmd_index) {
struct game_cmd cmd = game_cmds.cmds[cmd_index];
switch (cmd.kind) {
/* Movement */
case GAME_CMD_KIND_PLAYER_MOVE: {
struct v2 dir = cmd.dir;
L.world.player_move_dir = v2_add(L.world.player_move_dir, dir);
} break;
/* Focus */
case GAME_CMD_KIND_PLAYER_FOCUS: {
L.world.player_focus = cmd.pos;
} break;
/* Clear level */
case GAME_CMD_KIND_CLEAR_ALL: {
for (u64 i = 0; i < entities_array.count; ++i) {
struct entity *ent = &entities_array.entities[i];
if (ent->valid) {
entity_release(&L.world.entity_store, ent);
}
}
} break;
default: break;
};
}
if (v2_len(L.world.player_move_dir) > 1.f) {
/* Clamp movement magnitude */
L.world.player_move_dir = v2_norm(L.world.player_move_dir);
}
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* ========================== *
* Update entities pre-physics
* ========================== */
for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) {
struct entity *ent = &entities_array.entities[entity_index];
if (!ent->valid) continue;
/* ========================== *
* Initialize
* ========================== */
/* ENTITY_PROP_TEST */
if (entity_has_prop(ent, ENTITY_PROP_TEST) && !ent->test_initialized) {
ent->test_initialized = true;
ent->test_start_rel_xform = ent->rel_xform;
ent->test_start_sprite_xform = ent->sprite_xform;
}
/* ========================== *
* Update animation
* ========================== */
if (entity_has_prop(ent, ENTITY_PROP_ANIMATING)) {
f64 time_in_frame = ent->animation_time_in_frame + L.world.dt;
u64 tag_frame_offset = ent->animation_frame;
struct sheet *sheet = sheet_load(ent->sprite_name);
if (sheet) {
struct sheet_tag tag = sheet_get_tag(sheet, ent->sprite_tag_name);
u64 frame_index = tag.start + tag_frame_offset;
struct sheet_frame frame = sheet_get_frame(sheet, frame_index);
while (time_in_frame > frame.duration) {
time_in_frame -= frame.duration;
++frame_index;
if (frame_index > tag.end) {
/* Loop animation */
frame_index = tag.start;
}
frame = sheet_get_frame(sheet, frame_index);
}
tag_frame_offset = frame_index - tag.start;
}
ent->animation_time_in_frame = time_in_frame;
ent->animation_frame = tag_frame_offset;
}
/* ========================== *
* Test
* ========================== */
/* ENTITY_PROP_TEST */
if (entity_has_prop(ent, ENTITY_PROP_TEST)) {
f32 t = ((f32)L.world.time);
struct v2 og = V2(math_cos(t), math_sin(t));
f32 r = t * 2.f;
f32 s = 1 + (math_fabs(math_sin(t * 5)) * 3);
(UNUSED)og;
(UNUSED)r;
(UNUSED)s;
ent->rel_xform.og = og;
ent->rel_xform = xform_with_rotation(ent->rel_xform, r);
ent->rel_xform = xform_with_scale(ent->rel_xform, V2(s, 1));
}
}
/* ========================== *
* Update entity physics
* ========================== */
for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) {
struct entity *ent = &entities_array.entities[entity_index];
if (!ent->valid) continue;
if (ent->parent.gen) continue; /* Only update parent entities */
/* ========================== *
* Player movement
* ========================== */
if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
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.world.player_move_dir, max_speed);
struct v2 target_acceleration = v2_sub(target_velocity, ent->velocity);
ent->acceleration = v2_mul(target_acceleration, acceleration_rate);
}
/* ========================== *
* Integrate acceleration & velocity
* ========================== */
{
f32 dt = (f32)L.world.dt;
/* Apply acceleration to velocity */
struct v2 a = v2_mul(ent->acceleration, dt);
ent->velocity = v2_add(ent->velocity, a);
/* Apply velocity to position */
ent->rel_xform.og = v2_add(ent->rel_xform.og, v2_mul(ent->velocity, dt));
}
/* ========================== *
* Player look direction
* ========================== */
if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
struct v2 ent_pos = ent->rel_xform.og;
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);
}
/* ========================== *
* Update position from mouse
* ========================== */
/* ENTITY_PROP_TEST_FOLLOW_MOUSE */
if (entity_has_prop(ent, ENTITY_PROP_TEST_FOLLOW_MOUSE)) {
ent->rel_xform.og = L.world.player_focus;
ent->test_start_rel_xform.og = L.world.player_focus;
}
/* ========================== *
* Calculate xforms
* ========================== */
{
struct temp_arena stack = arena_temp_begin(scratch.arena);
struct xform_stack_node {
struct entity *entity;
struct xform parent_xform;
};
/* Depth first iteration */
*arena_push(stack.arena, struct xform_stack_node) = (struct xform_stack_node) { .entity = ent, .parent_xform = XFORM_IDENT };
u64 stack_count = 1;
while (stack_count > 0) {
/* Pull from top of stack */
struct xform_stack_node node = *arena_pop(stack.arena, struct xform_stack_node);
--stack_count;
/* Calculate child world xform */
struct entity *child = node.entity;
struct xform world_xform = xform_mul(node.parent_xform, child->rel_xform);
child->world_xform = world_xform;
/* Append sub-children to stack */
struct entity *subchild = entity_from_handle(&L.world.entity_store, child->last);
while (subchild->valid) {
*arena_push(stack.arena, struct xform_stack_node) = (struct xform_stack_node) {
.entity = subchild,
.parent_xform = world_xform
};
++stack_count;
subchild = entity_from_handle(&L.world.entity_store, subchild->prev);
}
}
arena_temp_end(stack);
}
}
/* ========================== *
* Update entities post-physics
* ========================== */
for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) {
struct entity *ent = &entities_array.entities[entity_index];
if (!ent->valid) continue;
/* ========================== *
* Update camera position
* ========================== */
if (entity_has_prop(ent, ENTITY_PROP_CAMERA)) {
struct entity *follow = entity_from_handle(&L.world.entity_store, ent->camera_follow);
ent->world_xform = follow->world_xform;
ent->world_xform = xform_with_rotation(ent->world_xform, 0);
ent->world_xform = xform_with_scale(ent->world_xform, V2(1, 1));
}
/* ========================== *
* Update sound emitters
* ========================== */
if (entity_has_prop(ent, ENTITY_PROP_TEST_SOUND_EMITTER)) {
struct mixer_desc desc = ent->sound_desc;
desc.speed = L.world.timescale;
desc.pos = ent->world_xform.og;
struct sound *sound = sound_load_async(ent->sound_name, 0);
b32 played = ent->sound_handle.gen != 0;
if (sound) {
if (!played) {
ent->sound_handle = mixer_play_ex(sound, desc);
} else {
mixer_track_set(ent->sound_handle, desc);
}
}
}
}
/* ---------------------------------------------------------------------- */
/* ---------------------------------------------------------------------- */
/* Publish tick */
publish_game_tick();
__profframe("Game");
scratch_end(scratch);
}
/* ========================== *
* Startup
* ========================== */
INTERNAL void game_thread_entry_point(void *arg)
{
(UNUSED)arg;
sys_timestamp_t last_frame_ts = 0;
f64 target_dt = GAME_FPS > (0) ? (1.0 / GAME_FPS) : 0;
while (!L.shutdown) {
__profscope(game_update_w_sleep);
sleep_frame(last_frame_ts, target_dt);
last_frame_ts = sys_timestamp();
game_update();
}
}
void game_startup(void)
{
/* Initialize game cmd storage */
L.game_cmds_mutex = sys_mutex_alloc();
L.game_cmds_arena = arena_alloc(GIGABYTE(64));
/* Initialize world */
world_alloc(&L.world);
/* Initialize tick transmission */
world_alloc(&L.published_tick);
L.published_tick_mutex = sys_mutex_alloc();
L.world.timescale = 1.0;
L.game_thread = sys_thread_init(&game_thread_entry_point, NULL, STR("[P2] Game thread"));
}
void game_shutdown(void)
{
L.shutdown = true;
sys_thread_join(&L.game_thread);
}
/* ========================== *
* Interface
* ========================== */
void game_get_latest_tick(struct world *dest)
{
sys_mutex_lock(&L.published_tick_mutex);
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.tick_id;
}
void game_push_cmds(struct game_cmd_array cmd_array)
{
push_cmds(cmd_array);
}