2018 lines
83 KiB
C
2018 lines
83 KiB
C
#include "user.h"
|
|
#include "app.h"
|
|
#include "sim.h"
|
|
#include "sim_ent.h"
|
|
#include "sim_snapshot.h"
|
|
#include "renderer.h"
|
|
#include "font.h"
|
|
#include "sprite.h"
|
|
#include "draw.h"
|
|
#include "intrinsics.h"
|
|
#include "app.h"
|
|
#include "asset_cache.h"
|
|
#include "string.h"
|
|
#include "scratch.h"
|
|
#include "math.h"
|
|
#include "sys.h"
|
|
#include "mixer.h"
|
|
#include "atomic.h"
|
|
#include "collider.h"
|
|
#include "rng.h"
|
|
#include "log.h"
|
|
#include "sock.h"
|
|
#include "host.h"
|
|
#include "bitbuff.h"
|
|
|
|
struct bind_state {
|
|
b32 is_held; /* Is this bind held down this frame */
|
|
u32 num_presses; /* How many times was this bind pressed since last frame */
|
|
u32 num_presses_and_repeats; /* Same as `num_presses` but includes key repeats as well */
|
|
u32 num_releases; /* How many times was this bind released since last frame */
|
|
};
|
|
|
|
struct second_stat {
|
|
u64 last_second_start;
|
|
u64 last_second_end;
|
|
u64 last_second;
|
|
};
|
|
|
|
GLOBAL struct {
|
|
struct atomic_i32 user_thread_shutdown;
|
|
struct sys_thread user_thread;
|
|
|
|
struct atomic_i32 local_sim_thread_shutdown;
|
|
struct sys_thread local_sim_thread;
|
|
struct sim_ctx *local_sim_ctx;
|
|
|
|
struct arena arena;
|
|
struct sys_window *window;
|
|
struct host *host;
|
|
struct string connect_address_str;
|
|
|
|
struct sim_snapshot_store *sim_snapshot_store; /* Contains buffered snapshots from sim */
|
|
struct sim_snapshot_store *world_snapshot_store; /* Contains single world snapshot from result of blending sim snapshots */
|
|
struct sim_snapshot *world;
|
|
|
|
/* Dynamic bitbuff used by encoders */
|
|
struct bitbuff encoder_bitbuff;
|
|
|
|
/* Usage stats */
|
|
i64 last_second_reset_ns;
|
|
struct second_stat client_bytes_read;
|
|
struct second_stat client_bytes_sent;
|
|
|
|
/* Render targets */
|
|
struct renderer_texture final_texture;
|
|
struct renderer_texture world_texture;
|
|
struct renderer_texture ui_texture;
|
|
struct renderer_texture backbuffer_texture;
|
|
|
|
struct renderer_cmd_buffer *world_cmd_buffer;
|
|
struct renderer_cmd_buffer *ui_cmd_buffer;
|
|
struct renderer_cmd_buffer *final_cmd_buffer;
|
|
struct renderer_cmd_buffer *backbuffer_cmd_buffer;
|
|
|
|
struct xform world_to_ui_xf;
|
|
|
|
struct bind_state bind_states[USER_BIND_KIND_COUNT];
|
|
|
|
b32 debug_camera;
|
|
b32 debug_camera_panning;
|
|
struct v2 debug_camera_pan_start;
|
|
|
|
b32 debug_draw;
|
|
|
|
/* User thread input */
|
|
struct sys_mutex sys_events_mutex;
|
|
struct arena sys_events_arena;
|
|
|
|
i64 real_dt_ns;
|
|
i64 real_time_ns;
|
|
|
|
u64 local_sim_last_known_tick;
|
|
i64 local_sim_last_known_time_ns;
|
|
|
|
/* Calculated from <last snapshot receive time + time since packet receive> */
|
|
i64 local_sim_predicted_time_ns;
|
|
i64 local_sim_predicted_time_smoothed_ns;
|
|
|
|
/* Per-frame */
|
|
struct v2 screen_size;
|
|
struct v2 screen_cursor;
|
|
struct v2 ui_screen_offset;
|
|
struct v2 ui_size;
|
|
struct v2 ui_center;
|
|
struct v2 ui_cursor;
|
|
struct v2 world_cursor;
|
|
} G = ZI, DEBUG_ALIAS(G, G_user);
|
|
|
|
/* ========================== *
|
|
* Bind state
|
|
* ========================== */
|
|
|
|
/* TODO: Remove this */
|
|
|
|
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,
|
|
[SYS_BTN_D] = USER_BIND_KIND_MOVE_RIGHT,
|
|
[SYS_BTN_ALT] = USER_BIND_KIND_WALK,
|
|
[SYS_BTN_M1] = USER_BIND_KIND_FIRE,
|
|
|
|
/* Testing */
|
|
|
|
[SYS_BTN_M2] = USER_BIND_KIND_DEBUG_DRAG,
|
|
[SYS_BTN_C] = USER_BIND_KIND_DEBUG_CLEAR,
|
|
[SYS_BTN_V] = USER_BIND_KIND_DEBUG_SPAWN,
|
|
[SYS_BTN_N] = USER_BIND_KIND_DEBUG_STEP,
|
|
[SYS_BTN_F1] = USER_BIND_KIND_DEBUG_PAUSE,
|
|
[SYS_BTN_F2] = USER_BIND_KIND_DEBUG_CAMERA,
|
|
[SYS_BTN_F3] = USER_BIND_KIND_DEBUG_DRAW,
|
|
[SYS_BTN_F11] = USER_BIND_KIND_FULLSCREEN,
|
|
[SYS_BTN_MWHEELUP] = USER_BIND_KIND_ZOOM_IN,
|
|
[SYS_BTN_MWHEELDOWN] = USER_BIND_KIND_ZOOM_OUT,
|
|
[SYS_BTN_M3] = USER_BIND_KIND_PAN,
|
|
[SYS_BTN_CTRL] = USER_BIND_KIND_CTRL_TEST,
|
|
|
|
#if RTC
|
|
/* Debug */
|
|
|
|
[SYS_BTN_FORWARD_SLASH] = USER_BIND_KIND_RESET_COLLIDER_GJK_STEPS,
|
|
[SYS_BTN_COMMA] = USER_BIND_KIND_DECR_COLLIDER_GJK_STEPS,
|
|
[SYS_BTN_PERIOD] = USER_BIND_KIND_INCR_COLLIDER_GJK_STEPS
|
|
#endif
|
|
};
|
|
|
|
/* ========================== *
|
|
* Startup
|
|
* ========================== */
|
|
|
|
INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(user_shutdown);
|
|
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_thread_entry_point, arg);
|
|
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg);
|
|
INTERNAL SYS_WINDOW_EVENT_CALLBACK_FUNC_DEF(window_event_callback, event);
|
|
|
|
struct user_startup_receipt user_startup(struct work_startup_receipt *work_sr,
|
|
struct renderer_startup_receipt *renderer_sr,
|
|
struct font_startup_receipt *font_sr,
|
|
struct sprite_startup_receipt *sprite_sr,
|
|
struct draw_startup_receipt *draw_sr,
|
|
struct asset_cache_startup_receipt *asset_cache_sr,
|
|
struct sound_startup_receipt *sound_sr,
|
|
struct mixer_startup_receipt *mixer_sr,
|
|
struct phys_startup_receipt *phys_sr,
|
|
struct host_startup_receipt *host_sr,
|
|
struct sim_snapshot_startup_receipt *sim_snapshot_sr,
|
|
struct string connect_address_str,
|
|
struct sys_window *window)
|
|
{
|
|
(UNUSED)work_sr;
|
|
(UNUSED)renderer_sr;
|
|
(UNUSED)font_sr;
|
|
(UNUSED)sprite_sr;
|
|
(UNUSED)draw_sr;
|
|
(UNUSED)asset_cache_sr;
|
|
(UNUSED)sound_sr;
|
|
(UNUSED)mixer_sr;
|
|
(UNUSED)phys_sr;
|
|
(UNUSED)host_sr;
|
|
(UNUSED)sim_snapshot_sr;
|
|
|
|
G.arena = arena_alloc(GIGABYTE(64));
|
|
G.sys_events_mutex = sys_mutex_alloc();
|
|
G.sys_events_arena = arena_alloc(GIGABYTE(64));
|
|
|
|
/* Snapshot store */
|
|
G.sim_snapshot_store = sim_snapshot_store_alloc();
|
|
G.world_snapshot_store = sim_snapshot_store_alloc();
|
|
G.world = sim_snapshot_nil();
|
|
|
|
//struct sock_address bind_addr = sock_address_from_any_local_interface_with_dynamic_port();
|
|
G.host = host_alloc(0);
|
|
|
|
G.encoder_bitbuff = bitbuff_alloc(GIGABYTE(64));
|
|
|
|
G.world_to_ui_xf = XFORM_IDENT;
|
|
G.world_cmd_buffer = renderer_cmd_buffer_alloc();
|
|
G.ui_cmd_buffer = renderer_cmd_buffer_alloc();
|
|
G.final_cmd_buffer = renderer_cmd_buffer_alloc();
|
|
G.backbuffer_cmd_buffer = renderer_cmd_buffer_alloc();
|
|
|
|
G.real_time_ns = sys_time_ns();
|
|
|
|
G.window = window;
|
|
sys_window_register_event_callback(G.window, &window_event_callback);
|
|
|
|
/* TODO: Remove this */
|
|
#if 0
|
|
connect_address_str = STRING(0, 0);
|
|
if (connect_address_str.len == 0) {
|
|
G.local_sim_ctx = sim_ctx_alloc(sprite_sr, phys_sr, host_sr, sim_snapshot_sr, 12345);
|
|
G.connect_address_str = LIT("127.0.0.1:12345");
|
|
G.local_sim_thread = sys_thread_alloc(&user_local_sim_thread_entry_point, G.local_sim_ctx, LIT("[P8] Local sim thread"));
|
|
} else {
|
|
G.connect_address_str = string_copy(&G.arena, connect_address_str);
|
|
}
|
|
#else
|
|
connect_address_str = STRING(0, 0);
|
|
if (connect_address_str.len == 0) {
|
|
G.connect_address_str = LIT("127.0.0.1:12345");
|
|
G.local_sim_thread = sys_thread_alloc(&user_local_sim_thread_entry_point, G.local_sim_ctx, LIT("[P8] Local sim thread"));
|
|
} else {
|
|
G.connect_address_str = string_copy(&G.arena, connect_address_str);
|
|
}
|
|
#endif
|
|
|
|
G.debug_draw = true;
|
|
|
|
G.user_thread = sys_thread_alloc(&user_thread_entry_point, NULL, LIT("[P9] User thread"));
|
|
app_register_exit_callback(&user_shutdown);
|
|
|
|
return (struct user_startup_receipt) { 0 };
|
|
}
|
|
|
|
INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(user_shutdown)
|
|
{
|
|
__prof;
|
|
atomic_i32_eval_exchange(&G.user_thread_shutdown, true);
|
|
sys_thread_wait_release(&G.user_thread);
|
|
|
|
#if 0
|
|
if (G.local_sim_ctx) {
|
|
atomic_i32_eval_exchange(&G.local_sim_thread_shutdown, true);
|
|
sys_thread_wait_release(&G.local_sim_thread);
|
|
sim_ctx_release(G.local_sim_ctx);
|
|
}
|
|
#else
|
|
if (G.local_sim_ctx) {
|
|
atomic_i32_eval_exchange(&G.local_sim_thread_shutdown, true);
|
|
sys_thread_wait_release(&G.local_sim_thread);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* ========================== *
|
|
* Window -> user communication
|
|
* ========================== */
|
|
|
|
INTERNAL struct sys_event_array pop_sys_events(struct arena *arena)
|
|
{
|
|
struct sys_event_array array = ZI;
|
|
struct sys_lock lock = sys_mutex_lock_e(&G.sys_events_mutex);
|
|
{
|
|
struct string events_buff = arena_to_string(&G.sys_events_arena);
|
|
arena_align(arena, alignof(struct sys_event));
|
|
array.events = (struct sys_event *)arena_push_array(arena, u8, events_buff.len);
|
|
array.count = events_buff.len / sizeof(struct sys_event);
|
|
MEMCPY(array.events, events_buff.text, events_buff.len);
|
|
arena_reset(&G.sys_events_arena);
|
|
}
|
|
sys_mutex_unlock(&lock);
|
|
return array;
|
|
}
|
|
|
|
INTERNAL SYS_WINDOW_EVENT_CALLBACK_FUNC_DEF(window_event_callback, event)
|
|
{
|
|
struct sys_lock lock = sys_mutex_lock_e(&G.sys_events_mutex);
|
|
{
|
|
*arena_push(&G.sys_events_arena, struct sys_event) = event;
|
|
}
|
|
sys_mutex_unlock(&lock);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Debug draw
|
|
* ========================== */
|
|
|
|
/* TODO: remove this (testing) */
|
|
INTERNAL void debug_draw_xform(struct xform xf, u32 color_x, u32 color_y)
|
|
{
|
|
f32 thickness = 2.f;
|
|
f32 arrowhead_len = 15.f;
|
|
|
|
struct v2 pos = xform_mul_v2(G.world_to_ui_xf, xf.og);
|
|
struct v2 x_ray = xform_basis_mul_v2(G.world_to_ui_xf, xform_get_right(xf));
|
|
struct v2 y_ray = xform_basis_mul_v2(G.world_to_ui_xf, xform_get_up(xf));
|
|
|
|
f32 ray_scale = 1;
|
|
x_ray = v2_mul(x_ray, ray_scale);
|
|
y_ray = v2_mul(y_ray, ray_scale);
|
|
|
|
draw_arrow_ray(G.ui_cmd_buffer, pos, x_ray, thickness, arrowhead_len, color_x);
|
|
draw_arrow_ray(G.ui_cmd_buffer, pos, y_ray, thickness, arrowhead_len, color_y);
|
|
|
|
//u32 color_quad = RGBA_32_F(0, 1, 1, 0.3);
|
|
//struct quad quad = quad_from_rect(RECT(0, 0, 1, -1));
|
|
//quad = xform_mul_quad(xf, quad_scale(quad, 0.075f));
|
|
//draw_quad(G.ui_cmd_buffer, quad, color);
|
|
}
|
|
|
|
/* TODO: remove this (testing) */
|
|
INTERNAL void debug_draw_movement(struct sim_ent *ent)
|
|
{
|
|
f32 thickness = 2.f;
|
|
f32 arrow_len = 15.f;
|
|
|
|
u32 color_vel = RGBA_32_F(1, 0.5, 0, 1);
|
|
|
|
struct xform xf = sim_ent_get_xform(ent);
|
|
struct v2 velocity = ent->linear_velocity;
|
|
|
|
struct v2 pos = xform_mul_v2(G.world_to_ui_xf, xf.og);
|
|
struct v2 vel_ray = xform_basis_mul_v2(G.world_to_ui_xf, velocity);
|
|
|
|
if (v2_len(vel_ray) > 0.00001) {
|
|
draw_arrow_ray(G.ui_cmd_buffer, pos, vel_ray, thickness, arrow_len, color_vel);
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Sort entities
|
|
* ========================== */
|
|
|
|
INTERNAL SORT_COMPARE_FUNC_DEF(ent_draw_order_cmp, arg_a, arg_b, udata)
|
|
{
|
|
(UNUSED)udata;
|
|
struct sim_ent *a = *(struct sim_ent **)arg_a;
|
|
struct sim_ent *b = *(struct sim_ent **)arg_b;
|
|
|
|
i32 res = 0;
|
|
|
|
if (res == 0) {
|
|
/* Sort by layer */
|
|
i32 a_cmp = a->layer;
|
|
i32 b_cmp = b->layer;
|
|
res = (a_cmp < b_cmp) - (a_cmp > b_cmp);
|
|
}
|
|
if (res == 0) {
|
|
/* Sort by sprite */
|
|
u128 a_cmp = a->sprite.hash;
|
|
u128 b_cmp = b->sprite.hash;
|
|
res = u128_lt(a_cmp, b_cmp) - u128_gt(a_cmp, b_cmp);
|
|
}
|
|
if (res == 0) {
|
|
/* Sort by activation */
|
|
u64 a_cmp = a->activation_tick;
|
|
u64 b_cmp = b->activation_tick;
|
|
res = (a_cmp < b_cmp) - (a_cmp > b_cmp);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update
|
|
* ========================== */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
INTERNAL void queue_sim_cmd(struct arena *arena, struct sim_cmd_frame *frame, struct sim_cmd src)
|
|
{
|
|
struct sim_cmd *cmd = arena_push(arena, struct sim_cmd);
|
|
*cmd = src;
|
|
if (frame->last) {
|
|
frame->last->next = cmd;
|
|
} else {
|
|
frame->first = cmd;
|
|
}
|
|
frame->last = cmd;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
INTERNAL void user_update(void)
|
|
{
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
|
|
/* ========================== *
|
|
* Begin frame
|
|
* ========================== */
|
|
|
|
G.real_dt_ns = sys_time_ns() - G.real_time_ns;
|
|
G.real_time_ns += G.real_dt_ns;
|
|
G.screen_size = sys_window_get_size(G.window);
|
|
|
|
struct sprite_scope *sprite_frame_scope = sprite_scope_begin();
|
|
struct sim_cmd_frame cmd_frame = ZI;
|
|
|
|
/* ========================== *
|
|
* Process host events into sim cmds
|
|
* ========================== */
|
|
|
|
struct sim_cmd_frame incoming_cmd_frame = ZI;
|
|
{
|
|
host_update(G.host);
|
|
struct host_event_array host_events = host_pop_events(scratch.arena, G.host);
|
|
|
|
struct sim_cmd_frame_list l = ZI;
|
|
sim_cmd_frames_decode(scratch.arena, host_events, &l);
|
|
for (struct sim_cmd_frame *frame = l.first; frame; frame = frame->next) {
|
|
if (frame->tick == 0 || frame->tick > incoming_cmd_frame.tick) {
|
|
incoming_cmd_frame = *frame;
|
|
incoming_cmd_frame.next = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Process sim cmd frame
|
|
* ========================== */
|
|
|
|
{
|
|
static f64 last_try_connect = 0;
|
|
f64 now = SECONDS_FROM_NS(sys_time_ns());
|
|
if (last_try_connect == 0 || (now - last_try_connect) > 0.1) {
|
|
struct sock_address connect_addr = sock_address_from_string(G.connect_address_str);
|
|
host_queue_connect_to_address(G.host, connect_addr);
|
|
last_try_connect = now;
|
|
}
|
|
|
|
for (struct sim_cmd *cmd = incoming_cmd_frame.first; cmd; cmd = cmd->next) {
|
|
enum sim_cmd_kind kind = cmd->kind;
|
|
|
|
switch (kind) {
|
|
case SIM_CMD_KIND_CONNECT:
|
|
{
|
|
last_try_connect = F64_INFINITY;
|
|
} break;
|
|
|
|
case SIM_CMD_KIND_DISCONNECT:
|
|
{
|
|
last_try_connect = 0;
|
|
} break;
|
|
|
|
case SIM_CMD_KIND_SNAPSHOT:
|
|
{
|
|
/* TODO: Only read newest tick cmd */
|
|
if (cmd->snapshot_tick_end > G.world->tick) {
|
|
u64 ss0_tick = cmd->snapshot_tick_start;
|
|
u64 ss1_tick = cmd->snapshot_tick_end;
|
|
struct sim_snapshot *ss0 = sim_snapshot_from_tick(G.sim_snapshot_store, ss0_tick);
|
|
struct sim_snapshot *ss1 = sim_snapshot_from_tick(G.sim_snapshot_store, ss1_tick);
|
|
if (ss0->tick == ss0_tick) {
|
|
if (!ss1->valid) {
|
|
ss1 = sim_snapshot_alloc(G.sim_snapshot_store, ss0, ss1_tick);
|
|
ss1->received_at_ns = G.real_time_ns;
|
|
|
|
struct bitbuff bb = bitbuff_from_string(cmd->snapshot_encoded);
|
|
struct bitbuff_reader br = br_from_bitbuff(&bb);
|
|
sim_snapshot_decode(&br, ss1);
|
|
}
|
|
} else {
|
|
/* User should always have src tick present */
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
} break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Create user world from blended snapshots
|
|
* ========================== */
|
|
|
|
{
|
|
struct sim_snapshot *newest_snapshot = sim_snapshot_from_tick(G.sim_snapshot_store, G.sim_snapshot_store->last_tick);
|
|
G.local_sim_last_known_time_ns = newest_snapshot->real_time_ns;
|
|
G.local_sim_last_known_tick = newest_snapshot->tick;
|
|
|
|
/* This is the the tick that we know the sim has received our ack of.
|
|
* Therefore we must keep it around or else risk the server sending us
|
|
* a snapshot delta for a snapshot we've released. */
|
|
u64 oldest_possible_delta_base_tick = sim_client_from_handle(newest_snapshot, newest_snapshot->local_client)->ack;
|
|
|
|
/* Predict local sim time based on last received snapshot time,
|
|
* then smooth it out to prevent sudden jumps in rendering due
|
|
* to variance in snapshot receive time. */
|
|
/* TODO: Use a value that indicates desired dt to next frame, rather than real dt from last frame? */
|
|
f64 sim_time_smoothed_correction_rate = SECONDS_FROM_NS(G.real_dt_ns) / 0.05;
|
|
i64 time_since_newest_tick_ns = G.real_time_ns - newest_snapshot->received_at_ns;
|
|
G.local_sim_predicted_time_ns = newest_snapshot->real_time_ns + time_since_newest_tick_ns;
|
|
G.local_sim_predicted_time_smoothed_ns += G.real_dt_ns;
|
|
/* FIXME: Signed overflow check */
|
|
G.local_sim_predicted_time_smoothed_ns += (G.local_sim_predicted_time_ns - G.local_sim_predicted_time_smoothed_ns) * sim_time_smoothed_correction_rate;
|
|
|
|
#if USER_INTERP_ENABLED
|
|
i64 render_time_ns = G.local_sim_predicted_time_smoothed_ns - (USER_INTERP_RATIO * newest_snapshot->real_dt_ns);
|
|
|
|
/* Get two snapshots nearest to render time */
|
|
struct sim_snapshot *left_snapshot = sim_snapshot_nil();
|
|
struct sim_snapshot *right_snapshot = newest_snapshot;
|
|
{
|
|
struct sim_snapshot *ss = sim_snapshot_from_tick(G.sim_snapshot_store, G.sim_snapshot_store->first_tick);
|
|
while (ss->valid) {
|
|
u64 next_tick = ss->next_tick;
|
|
i64 ss_time_ns = ss->real_time_ns;
|
|
if (ss_time_ns < render_time_ns && ss_time_ns > left_snapshot->real_time_ns) {
|
|
if (left_snapshot->valid && left_snapshot->tick < oldest_possible_delta_base_tick) {
|
|
/* Snapshot no longer needed since render time has passed & it's older than any delta's we may receive, release it. */
|
|
sim_snapshot_release(left_snapshot);
|
|
}
|
|
left_snapshot = ss;
|
|
}
|
|
if (ss_time_ns > render_time_ns && ss_time_ns < right_snapshot->real_time_ns) {
|
|
right_snapshot = ss;
|
|
}
|
|
ss = sim_snapshot_from_tick(G.sim_snapshot_store, next_tick);
|
|
}
|
|
}
|
|
|
|
/* Create world from blended snapshots */
|
|
if (left_snapshot->valid && right_snapshot->valid) {
|
|
f64 blend = (f64)(render_time_ns - left_snapshot->real_time_ns) / (f64)(right_snapshot->real_time_ns - left_snapshot->real_time_ns);
|
|
G.world = sim_snapshot_alloc_from_lerp(G.world_snapshot_store, left_snapshot, right_snapshot, blend);
|
|
} else if (left_snapshot->valid) {
|
|
G.world = sim_snapshot_alloc(G.world_snapshot_store, left_snapshot, left_snapshot->tick);
|
|
} else if (right_snapshot->valid) {
|
|
G.world = sim_snapshot_alloc(G.world_snapshot_store, right_snapshot, right_snapshot->tick);
|
|
}
|
|
|
|
/* Release all other render snapshots */
|
|
{
|
|
struct sim_snapshot *ss = sim_snapshot_from_tick(G.world_snapshot_store, G.world_snapshot_store->first_tick);
|
|
while (ss->valid) {
|
|
u64 next_tick = ss->next_tick;
|
|
if (ss != G.world) {
|
|
sim_snapshot_release(ss);
|
|
}
|
|
ss = sim_snapshot_from_tick(G.world_snapshot_store, next_tick);
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
/* Release sim snapshots all except for newest tick */
|
|
{
|
|
struct sim_snapshot *ss = sim_snapshot_from_tick(G.sim_snapshot_store, G.sim_snapshot_store->first_tick);
|
|
while (ss->valid) {
|
|
u64 next_tick = ss->next_tick;
|
|
if (ss->tick != newest_snapshot->tick && ss->tick < oldest_possible_delta_base_tick) {
|
|
sim_snapshot_release(ss);
|
|
}
|
|
ss = sim_snapshot_from_tick(G.sim_snapshot_store, next_tick);
|
|
}
|
|
}
|
|
|
|
if (G.world->tick != newest_snapshot->tick) {
|
|
if (G.world->valid) {
|
|
sim_snapshot_release(G.world);
|
|
}
|
|
G.world = sim_snapshot_alloc(G.world_snapshot_store, newest_snapshot, newest_snapshot->tick);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* ========================== *
|
|
* Process sys events into user bind state
|
|
* ========================== */
|
|
|
|
{
|
|
struct sys_event_array events = pop_sys_events(scratch.arena);
|
|
|
|
/* Reset bind pressed / released states */
|
|
for (u32 i = 0; i < ARRAY_COUNT(G.bind_states); ++i) {
|
|
G.bind_states[i] = (struct bind_state) {
|
|
.is_held = G.bind_states[i].is_held
|
|
};
|
|
}
|
|
|
|
for (u64 ent_index = 0; ent_index < events.count; ++ent_index) {
|
|
struct sys_event *event = &events.events[ent_index];
|
|
|
|
if (event->kind == SYS_EVENT_KIND_QUIT) {
|
|
app_exit();
|
|
}
|
|
|
|
if (event->kind == SYS_EVENT_KIND_BUTTON_UP) {
|
|
#if DEVELOPER
|
|
/* Escape quit */
|
|
if (event->button == SYS_BTN_ESC) {
|
|
app_exit();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Update mouse pos */
|
|
if (event->kind == SYS_EVENT_KIND_CURSOR_MOVE) {
|
|
G.screen_cursor = event->cursor_position;
|
|
}
|
|
|
|
/* Update bind states */
|
|
if ((event->kind == SYS_EVENT_KIND_BUTTON_DOWN || event->kind == SYS_EVENT_KIND_BUTTON_UP)) {
|
|
enum sys_btn button = event->button;
|
|
button = button >= SYS_BTN_COUNT ? SYS_BTN_NONE : button;
|
|
enum user_bind_kind bind = g_binds[button];
|
|
if (bind) {
|
|
b32 pressed = event->kind == SYS_EVENT_KIND_BUTTON_DOWN;
|
|
#if 0
|
|
b32 out_of_bounds = button >= SYS_BTN_M1 && button <= SYS_BTN_M5 &&
|
|
(G.ui_cursor.x < 0 ||
|
|
G.ui_cursor.y < 0 ||
|
|
G.ui_cursor.x > G.ui_size.x ||
|
|
G.ui_cursor.y > G.ui_size.y);
|
|
#else
|
|
b32 out_of_bounds = false;
|
|
#endif
|
|
G.bind_states[bind].is_held = pressed && !out_of_bounds;
|
|
if (pressed) {
|
|
if (!out_of_bounds) {
|
|
++G.bind_states[bind].num_presses_and_repeats;
|
|
if (!event->is_repeat) {
|
|
++G.bind_states[bind].num_presses;
|
|
}
|
|
}
|
|
} else {
|
|
++G.bind_states[bind].num_releases;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update user state from binds
|
|
* ========================== */
|
|
|
|
/* Test fullscreen */
|
|
{
|
|
struct bind_state state = G.bind_states[USER_BIND_KIND_FULLSCREEN];
|
|
if (state.num_presses) {
|
|
struct sys_window_settings settings = sys_window_get_settings(G.window);
|
|
settings.flags ^= SYS_WINDOW_SETTINGS_FLAG_FULLSCREEN;
|
|
sys_window_update_settings(G.window, &settings);
|
|
}
|
|
}
|
|
|
|
if (G.bind_states[USER_BIND_KIND_DEBUG_DRAW].num_presses > 0) {
|
|
G.debug_draw = !G.debug_draw;
|
|
}
|
|
|
|
if (G.bind_states[USER_BIND_KIND_DEBUG_CAMERA].num_presses > 0) {
|
|
G.debug_camera = !G.debug_camera;
|
|
}
|
|
|
|
/* ========================== *
|
|
* Find local entities
|
|
* ========================== */
|
|
|
|
struct sim_client *local_client = sim_client_from_handle(G.world, G.world->local_client);
|
|
struct sim_ent *local_player = sim_ent_from_handle(G.world, local_client->control_ent);
|
|
struct sim_ent *local_camera = sim_ent_from_handle(G.world, local_client->camera_ent);
|
|
|
|
/* ========================== *
|
|
* Apply shake
|
|
* ========================== */
|
|
|
|
for (u64 ent_index = 0; ent_index < G.world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &G.world->ents[ent_index];
|
|
if (!sim_ent_is_valid_and_active(ent)) continue;
|
|
|
|
/* How much time between camera shakes */
|
|
i64 frequency_ns = NS_FROM_SECONDS(0.01f);
|
|
f32 shake = ent->shake;
|
|
if (shake > 0) {
|
|
u64 basis = hash_fnv64(HASH_FNV64_BASIS, STRING_FROM_STRUCT(&ent->handle));
|
|
u64 angle_seed0 = basis + (u64)(G.world->world_time_ns / frequency_ns);
|
|
u64 angle_seed1 = angle_seed0 + 1;
|
|
f32 angle0 = rng_noise_f32(angle_seed0, 0, TAU);
|
|
f32 angle1 = rng_noise_f32(angle_seed1, 0, TAU);
|
|
|
|
struct v2 vec0 = v2_with_len(v2_from_angle(angle0), shake);
|
|
/* NOTE: vec1 not completely accurate since shake can change between frames, just a prediction */
|
|
struct v2 vec1 = v2_with_len(v2_from_angle(angle1), shake);
|
|
|
|
/* TODO: Cubic interp? */
|
|
f32 blend = (f32)(G.world->world_time_ns % frequency_ns) / (f32)frequency_ns;
|
|
struct v2 vec = v2_lerp(vec0, vec1, blend);
|
|
|
|
struct xform xf = sim_ent_get_xform(ent);
|
|
xf.og = v2_add(xf.og, v2_mul(vec, shake));
|
|
sim_ent_set_xform(ent, xf);
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Update ui from camera
|
|
* ========================== */
|
|
|
|
/* Calculate ui dimensions */
|
|
if (G.debug_camera) {
|
|
G.ui_size = G.screen_size;
|
|
G.ui_screen_offset = V2(0, 0);
|
|
} else {
|
|
|
|
/* Determine ui size by camera & window dimensions */
|
|
f32 aspect_ratio = 1.0;
|
|
{
|
|
struct xform quad_xf = xform_mul(sim_ent_get_xform(local_camera), local_camera->camera_quad_xform);
|
|
struct v2 camera_size = xform_get_scale(quad_xf);
|
|
if (!v2_is_zero(camera_size)) {
|
|
aspect_ratio = camera_size.x / camera_size.y;
|
|
}
|
|
}
|
|
f32 width = G.screen_size.x;
|
|
f32 height = G.screen_size.y;
|
|
if (width / height > aspect_ratio) {
|
|
width = height * aspect_ratio;
|
|
} else {
|
|
height = math_ceil(width / aspect_ratio);
|
|
}
|
|
G.ui_size = V2(width, height);
|
|
|
|
/* Center ui in window */
|
|
f32 x = 0;
|
|
f32 y = 0;
|
|
x = math_round(G.screen_size.x / 2 - width / 2);
|
|
y = math_round(G.screen_size.y / 2 - height / 2);
|
|
G.ui_screen_offset = V2(x, y);
|
|
}
|
|
|
|
G.ui_center = v2_mul(G.ui_size, 0.5);
|
|
G.ui_cursor = v2_sub(G.screen_cursor, G.ui_screen_offset);
|
|
|
|
/* ========================== *
|
|
* Update view from camera
|
|
* ========================== */
|
|
|
|
if (G.debug_camera) {
|
|
G.world_to_ui_xf = xform_basis_with_rotation_world(G.world_to_ui_xf, 0);
|
|
|
|
struct v2 world_cursor = xform_invert_mul_v2(G.world_to_ui_xf, G.ui_cursor);
|
|
|
|
/* Pan view */
|
|
if (G.bind_states[USER_BIND_KIND_PAN].is_held) {
|
|
if (!G.debug_camera_panning) {
|
|
G.debug_camera_pan_start = world_cursor;
|
|
G.debug_camera_panning = true;
|
|
}
|
|
struct v2 offset = v2_neg(v2_sub(G.debug_camera_pan_start, world_cursor));
|
|
G.world_to_ui_xf = xform_translated(G.world_to_ui_xf, offset);
|
|
world_cursor = xform_invert_mul_v2(G.world_to_ui_xf, G.ui_cursor);
|
|
G.debug_camera_pan_start = world_cursor;
|
|
} else {
|
|
G.debug_camera_panning = false;
|
|
}
|
|
|
|
/* 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;
|
|
if (input_zooms != 0) {
|
|
/* Zoom to cursor */
|
|
f32 zoom_rate = 2;
|
|
f32 zoom = math_pow(zoom_rate, input_zooms);
|
|
G.world_to_ui_xf = xform_translated(G.world_to_ui_xf, world_cursor);
|
|
G.world_to_ui_xf = xform_scaled(G.world_to_ui_xf, V2(zoom, zoom));
|
|
G.world_to_ui_xf = xform_translated(G.world_to_ui_xf, v2_neg(world_cursor));
|
|
}
|
|
} else {
|
|
struct xform xf = sim_ent_get_xform(local_camera);
|
|
|
|
struct v2 center = xf.og;
|
|
f32 rot = xform_get_rotation(xf);
|
|
|
|
/* Scale view into viewport based on camera size */
|
|
struct v2 size = G.ui_size;
|
|
{
|
|
struct xform quad_xf = xform_mul(xf, local_camera->camera_quad_xform);
|
|
struct v2 camera_size = xform_get_scale(quad_xf);
|
|
if (!v2_is_zero(camera_size)) {
|
|
size = v2_div_v2(size, camera_size);
|
|
}
|
|
}
|
|
f32 scale_ui = min_f32(size.x, size.y);
|
|
|
|
struct trs trs = TRS(.t = v2_sub(G.ui_center, center), .r = rot, .s = V2(scale_ui, scale_ui));
|
|
struct v2 pivot = center;
|
|
G.world_to_ui_xf = XFORM_IDENT;
|
|
G.world_to_ui_xf = xform_translated(G.world_to_ui_xf, pivot);
|
|
G.world_to_ui_xf = xform_translated(G.world_to_ui_xf, trs.t);
|
|
G.world_to_ui_xf = xform_rotated(G.world_to_ui_xf, trs.r);
|
|
G.world_to_ui_xf = xform_scaled(G.world_to_ui_xf, trs.s);
|
|
G.world_to_ui_xf = xform_translated(G.world_to_ui_xf, v2_neg(pivot));
|
|
}
|
|
G.world_cursor = xform_invert_mul_v2(G.world_to_ui_xf, G.ui_cursor);
|
|
|
|
/* ========================== *
|
|
* Update listener from view
|
|
* ========================== */
|
|
|
|
{
|
|
struct v2 up = V2(0, -1);
|
|
struct v2 listener_pos = xform_invert_mul_v2(G.world_to_ui_xf, G.ui_center);
|
|
struct v2 listener_dir = v2_norm(xform_basis_invert_mul_v2(G.world_to_ui_xf, up));
|
|
mixer_set_listener(listener_pos, listener_dir);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Draw test grid
|
|
* ========================== */
|
|
|
|
{
|
|
f32 thickness = 2;
|
|
u32 color = RGBA_32(0x3f, 0x3f, 0x3f, 0xFF);
|
|
|
|
struct v2 offset = v2_neg(xform_mul_v2(G.world_to_ui_xf, V2(0, 0)));
|
|
f32 spacing = xform_get_scale(G.world_to_ui_xf).x;
|
|
|
|
struct v2 pos = xform_invert_mul_v2(G.world_to_ui_xf, V2(0, 0));
|
|
struct v2 size = xform_basis_invert_mul_v2(G.world_to_ui_xf, G.ui_size);
|
|
draw_grid(G.world_cmd_buffer, RECT_FROM_V2(pos, size), color, thickness, spacing, offset);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
/* ========================== *
|
|
* Sort entities
|
|
* ========================== */
|
|
|
|
struct sim_ent **sorted = arena_dry_push(scratch.arena, struct sim_ent *);
|
|
u64 sorted_count = 0;
|
|
{
|
|
/* Copy valid entities */
|
|
{
|
|
__profscope(copy_sprites_for_sorting);
|
|
for (u64 ent_index = 0; ent_index < G.world->num_ents_reserved; ++ent_index) {
|
|
struct sim_ent *ent = &G.world->ents[ent_index];
|
|
if (sim_ent_is_valid_and_active(ent)) {
|
|
*arena_push(scratch.arena, struct sim_ent *) = ent;
|
|
++sorted_count;
|
|
}
|
|
}
|
|
}
|
|
/* Sort */
|
|
{
|
|
__profscope(sort_sprites);
|
|
merge_sort(sorted, sorted_count, sizeof(*sorted), ent_draw_order_cmp, NULL);
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Draw entities
|
|
* ========================== */
|
|
|
|
{
|
|
__profscope(draw_entities);
|
|
for (u64 sorted_index = 0; sorted_index < sorted_count; ++sorted_index) {
|
|
struct sim_ent *ent = sorted[sorted_index];
|
|
if (!sim_ent_is_valid_and_active(ent)) continue;
|
|
//if (sprite_tag_is_nil(ent->sprite)) continue;
|
|
|
|
struct sprite_tag sprite = ent->sprite;
|
|
|
|
struct sim_ent *parent = sim_ent_from_handle(G.world, ent->parent);
|
|
|
|
struct xform xf = sim_ent_get_xform(ent);
|
|
struct xform parent_xf = sim_ent_get_xform(parent);
|
|
|
|
b32 skip_debug_draw = !G.debug_camera && ent == local_camera;
|
|
b32 skip_debug_draw_transform = sim_ent_has_prop(ent, SIM_ENT_PROP_CAMERA);
|
|
skip_debug_draw_transform = true;
|
|
|
|
struct xform sprite_xform = xf;
|
|
|
|
/* Draw tracer */
|
|
if (sim_ent_has_prop(ent, SIM_ENT_PROP_TRACER)) {
|
|
struct v2 velocity = ent->tracer_start_velocity;
|
|
|
|
struct v2 a = ent->tracer_start;
|
|
struct v2 b = xf.og;
|
|
struct v2 c = ent->tracer_gradient_start;
|
|
struct v2 d = ent->tracer_gradient_end;
|
|
|
|
struct v2 vcd = v2_sub(d, c);
|
|
struct v2 vca = v2_sub(a, c);
|
|
struct v2 vdb = v2_sub(b, d);
|
|
struct v2 vdc = v2_neg(vcd);
|
|
|
|
f32 opacity_a = 1;
|
|
if (v2_dot(velocity, vca) < 0) {
|
|
a = c;
|
|
opacity_a = 0;
|
|
} else {
|
|
opacity_a = v2_dot(vcd, vca) / v2_len_sq(vcd);
|
|
}
|
|
|
|
f32 opacity_b = clamp_f32(1.f - (v2_dot(vdc, vdb) / v2_len_sq(vdc)), 0, 1);
|
|
|
|
f32 thickness = 0.01f;
|
|
u32 color_start = RGBA_32_F(1, 0.5, 0, opacity_a);
|
|
u32 color_end = RGBA_32_F(1, 0.8, 0.4, opacity_b);
|
|
|
|
if (opacity_b > 0.99f) {
|
|
draw_circle(G.world_cmd_buffer, b, thickness / 2, color_end, 20);
|
|
}
|
|
draw_gradient_line(G.world_cmd_buffer, a, b, thickness, color_start, color_end);
|
|
|
|
}
|
|
|
|
/* Draw sprite */
|
|
//if ((false)) {
|
|
if (!sprite_tag_is_nil(sprite)) {
|
|
/* Calculate sprite xform */
|
|
sprite_xform = xform_mul(xf, ent->sprite_local_xform);
|
|
|
|
/* Async load */
|
|
struct sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, sprite);
|
|
struct sprite_texture *texture = sprite_texture_from_tag_async(sprite_frame_scope, sprite);
|
|
(UNUSED)texture;
|
|
|
|
/* TODO: Fade in placeholder if texture isn't loaded */
|
|
if (sheet->loaded) {
|
|
struct sprite_sheet_frame frame = sprite_sheet_get_frame(sheet, ent->animation_frame);
|
|
struct quad quad = xform_mul_quad(sprite_xform, QUAD_UNIT_SQUARE_CENTERED);
|
|
struct draw_texture_params params = DRAW_TEXTURE_PARAMS(.sprite = sprite, .tint = ent->sprite_tint, .clip = frame.clip);
|
|
draw_quad_texture(G.world_cmd_buffer, params, quad);
|
|
}
|
|
}
|
|
|
|
/* Debug draw entity info */
|
|
if (G.debug_draw && !skip_debug_draw) {
|
|
struct temp_arena temp = arena_temp_begin(scratch.arena);
|
|
|
|
if (sim_ent_has_prop(ent, SIM_ENT_PROP_PHYSICAL_DYNAMIC) || sim_ent_has_prop(ent, SIM_ENT_PROP_PHYSICAL_KINEMATIC)) {
|
|
debug_draw_movement(ent);
|
|
}
|
|
|
|
/* Draw xform */
|
|
if (!skip_debug_draw_transform) {
|
|
u32 color_x = RGBA_32_F(1, 0, 0, 0.5);
|
|
u32 color_y = RGBA_32_F(0, 1, 0, 0.5);
|
|
debug_draw_xform(xf, color_x, color_y);
|
|
}
|
|
|
|
/* Draw AABB */
|
|
if (ent->local_collider.count > 0) {
|
|
struct aabb aabb = collider_aabb_from_collider(&ent->local_collider, xf);
|
|
f32 thickness = 1;
|
|
u32 color = RGBA_32_F(1, 0, 1, 0.5);
|
|
struct quad quad = quad_from_aabb(aabb);
|
|
quad = xform_mul_quad(G.world_to_ui_xf, quad);
|
|
draw_quad_line(G.ui_cmd_buffer, quad, thickness, color);
|
|
}
|
|
|
|
/* Draw focus arrow */
|
|
if (ent == local_player) {
|
|
struct sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, ent->sprite);
|
|
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, LIT("attach.wep"), ent->animation_frame);
|
|
struct v2 start = xform_mul_v2(sprite_xform, slice.center);
|
|
start = xform_mul_v2(G.world_to_ui_xf, start);
|
|
struct v2 end = v2_add(xf.og, ent->control.focus);
|
|
end = xform_mul_v2(G.world_to_ui_xf, end);
|
|
draw_arrow_line(G.ui_cmd_buffer, start, end, 3, 10, RGBA_32_F(1, 1, 1, 0.5));
|
|
}
|
|
|
|
#if 0
|
|
/* Draw slices */
|
|
if (!sprite_tag_is_nil(ent->sprite)) {
|
|
struct sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, sprite);
|
|
|
|
u32 quad_color = RGBA_32_F(1, 0, 0.5, 1);
|
|
u32 point_color = RGBA_32_F(1, 0, 0, 1);
|
|
u32 ray_color = RGBA_32_F(1, 0, 0.5, 1);
|
|
|
|
if (colliding) {
|
|
quad_color = RGBA_32_F(1, 1, 1, 1);
|
|
}
|
|
|
|
for (u64 i = 0; i < sheet->slice_groups_count; ++i) {
|
|
struct sprite_sheet_slice_group *group = &sheet->slice_groups[i];
|
|
if (string_ends_with(group->name, LIT(".ray"))) continue;
|
|
|
|
for (u32 j = 0; j < group->per_frame_count; ++j) {
|
|
struct sprite_sheet_slice slice = group->frame_slices[(ent->animation_frame * group->per_frame_count) + j];
|
|
|
|
struct v2 center = xform_mul_v2(sprite_xform, slice.center);
|
|
center = xform_mul_v2(G.world_to_ui_xf, center);
|
|
|
|
if (!slice.has_ray) {
|
|
struct quad quad = quad_from_rect(slice.rect);
|
|
quad = xform_mul_quad(sprite_xform, quad);
|
|
quad = xform_mul_quad(G.world_to_ui_xf, quad);
|
|
draw_quad_line(G.ui_cmd_buffer, quad, 2, quad_color);
|
|
}
|
|
|
|
draw_circle(G.ui_cmd_buffer, center, 3, point_color, 20);
|
|
|
|
if (slice.has_ray) {
|
|
struct v2 ray = xform_basis_mul_v2(sprite_xform, slice.dir);
|
|
ray = xform_basis_mul_v2(G.world_to_ui_xf, ray);
|
|
ray = v2_with_len(ray, 25);
|
|
draw_arrow_ray(G.ui_cmd_buffer, center, ray, 2, 10, ray_color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Draw collider */
|
|
if (sim_ent_has_prop(ent, SIM_ENT_PROP_PHYSICAL_DYNAMIC)) {
|
|
struct collider_shape collider = ent->local_collider;
|
|
u32 color = RGBA_32_F(1, 1, 0, 0.5);
|
|
f32 thickness = 2;
|
|
{
|
|
/* Draw collider using support points */
|
|
u32 detail = 32;
|
|
//u32 detail = 64;
|
|
//u32 detail = 512;
|
|
draw_collider_line(G.ui_cmd_buffer, G.world_to_ui_xf, collider, xf, thickness, color, detail);
|
|
}
|
|
{
|
|
/* Draw collider shape points */
|
|
for (u32 i = 0; i < collider.count; ++i) {
|
|
struct v2 p = xform_mul_v2(xform_mul(G.world_to_ui_xf, xf), collider.points[i]);
|
|
draw_circle(G.ui_cmd_buffer, p, 3, COLOR_BLUE, 10);
|
|
}
|
|
}
|
|
if (collider.count == 1 && collider.radius > 0) {
|
|
/* Draw upwards line for circle */
|
|
struct v2 start = xf.og;
|
|
struct v2 end = collider_get_support_point(&collider, xf, v2_neg(xf.by)).p;
|
|
start = xform_mul_v2(G.world_to_ui_xf, start);
|
|
end = xform_mul_v2(G.world_to_ui_xf, end);
|
|
draw_line(G.ui_cmd_buffer, start, end, thickness, color);
|
|
}
|
|
#if 0
|
|
/* Draw support point at focus dir */
|
|
{
|
|
struct v2 p = collider_support_point(&collider, xf, ent->control.focus);
|
|
p = xform_mul_v2(G.world_to_ui_xf, p);
|
|
draw_circle(G.ui_cmd_buffer, p, 3, COLOR_RED, 10);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Draw contact constraint */
|
|
if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTACT_CONSTRAINT)) {
|
|
struct phys_contact_constraint *data = &ent->contact_constraint_data;
|
|
struct sim_ent *e0 = sim_ent_from_handle(G.world, data->e0);
|
|
struct sim_ent *e1 = sim_ent_from_handle(G.world, data->e1);
|
|
(UNUSED)e0;
|
|
(UNUSED)e1;
|
|
|
|
#if 1
|
|
#if DEVELOPER
|
|
/* Draw contact points */
|
|
{
|
|
f32 radius = 5;
|
|
for (u32 i = 0; i < data->num_points; ++i) {
|
|
struct phys_contact_point point = data->points[i];
|
|
struct v2 dbg_pt = point.dbg_pt;
|
|
|
|
/* Draw point */
|
|
{
|
|
//u32 color = contact.persisted ? RGBA_32_F(1, 1, 0, 0.50) : RGBA_32_F(1, 0, 0, 0.50);
|
|
u32 color = RGBA_32_F(1, 1, 0, 0.50);
|
|
//struct v2 point = xform_mul_v2(e0_xf, contact.p0_local);
|
|
//struct v2 point = contact.p0_initial_world;
|
|
draw_circle(G.ui_cmd_buffer, xform_mul_v2(G.world_to_ui_xf, dbg_pt), radius, color, 10);
|
|
}
|
|
/* Draw normal */
|
|
{
|
|
u32 color = COLOR_WHITE;
|
|
f32 len = 0.1f;
|
|
f32 arrow_thickness = 2;
|
|
f32 arrow_height = 5;
|
|
struct v2 start = xform_mul_v2(G.world_to_ui_xf, dbg_pt);
|
|
struct v2 end = xform_mul_v2(G.world_to_ui_xf, v2_add(dbg_pt, v2_mul(v2_norm(data->normal), len)));
|
|
draw_arrow_line(G.ui_cmd_buffer, start, end, arrow_thickness, arrow_height, color);
|
|
}
|
|
#if 0
|
|
/* Draw contact info */
|
|
{
|
|
struct font *disp_font = font_load_async(LIT("res/fonts/fixedsys.ttf"), 12.0f);
|
|
if (disp_font) {
|
|
f32 offset_px = 10;
|
|
|
|
struct string fmt = LIT(
|
|
"e0 index: %F\n"
|
|
"e1 index: %F\n"
|
|
"id: 0x%F\n"
|
|
"impulse (n): %F\n"
|
|
"impulse (t): %F\n"
|
|
"separation: %F\n"
|
|
"normal: (%F, %F)\n"
|
|
"num contacts: %F"
|
|
);
|
|
struct string text = string_format(temp.arena, fmt,
|
|
FMT_UINT(e0->handle.idx),
|
|
FMT_UINT(e1->handle.idx),
|
|
FMT_HEX(point.id),
|
|
FMT_FLOAT_P(point.normal_impulse, 3),
|
|
FMT_FLOAT_P(point.tangent_impulse, 3),
|
|
FMT_FLOAT_P(point.starting_separation, 6),
|
|
FMT_FLOAT_P(data->normal.x, 6), FMT_FLOAT_P(data->normal.y, 6),
|
|
FMT_UINT(data->num_points));
|
|
|
|
|
|
draw_text(G.ui_cmd_buffer, disp_font, v2_add(v2_round(xform_mul_v2(G.world_to_ui_xf, dbg_pt)), V2(0, offset_px)), text);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Draw collision debug */
|
|
#if COLLIDER_DEBUG
|
|
if (sim_ent_has_prop(ent, SIM_ENT_PROP_COLLISION_DEBUG)) {
|
|
struct phys_collision_debug *data = &ent->collision_debug_data;
|
|
struct collider_collision_points_result collider_res = data->res;
|
|
struct sim_ent *e0 = sim_ent_from_handle(G.world, data->e0);
|
|
struct sim_ent *e1 = sim_ent_from_handle(G.world, data->e1);
|
|
struct collider_shape e0_collider = e0->local_collider;
|
|
struct collider_shape e1_collider = e1->local_collider;
|
|
(UNUSED)e0_collider;
|
|
(UNUSED)e1_collider;
|
|
|
|
/* Draw closest points */
|
|
#if 0
|
|
{
|
|
f32 radius = 4;
|
|
u32 color = RGBA_32_F(1, 1, 0, 0.5);
|
|
struct v2 a = xform_mul_v2(G.world_to_ui_xf, data->closest0);
|
|
struct v2 b = xform_mul_v2(G.world_to_ui_xf, data->closest1);
|
|
draw_circle(G.ui_cmd_buffer, a, radius, color, 10);
|
|
draw_circle(G.ui_cmd_buffer, b, radius, color, 10);
|
|
}
|
|
#endif
|
|
|
|
/* Draw clipping */
|
|
{
|
|
f32 thickness = 2;
|
|
f32 radius = 4;
|
|
u32 color_line = RGBA_32_F(1, 0, 1, 0.25);
|
|
u32 color_a = RGBA_32_F(1, 0, 0, 0.25);
|
|
u32 color_b = RGBA_32_F(0, 1, 0, 0.25);
|
|
u32 color_line_clipped = RGBA_32_F(1, 0, 1, 1);
|
|
u32 color_a_clipped = RGBA_32_F(1, 0, 0, 1);
|
|
u32 color_b_clipped = RGBA_32_F(0, 1, 0, 1);
|
|
{
|
|
struct v2 a = xform_mul_v2(G.world_to_ui_xf, collider_res.a0);
|
|
struct v2 b = xform_mul_v2(G.world_to_ui_xf, collider_res.b0);
|
|
draw_line(G.ui_cmd_buffer, a, b, thickness, color_line);
|
|
draw_circle(G.ui_cmd_buffer, a, radius, color_a, 10);
|
|
draw_circle(G.ui_cmd_buffer, b, radius, color_b, 10);
|
|
|
|
struct v2 a_clipped = xform_mul_v2(G.world_to_ui_xf, collider_res.a0_clipped);
|
|
struct v2 b_clipped = xform_mul_v2(G.world_to_ui_xf, collider_res.b0_clipped);
|
|
draw_line(G.ui_cmd_buffer, a_clipped, b_clipped, thickness, color_line_clipped);
|
|
draw_circle(G.ui_cmd_buffer, a_clipped, radius, color_a_clipped, 10);
|
|
draw_circle(G.ui_cmd_buffer, b_clipped, radius, color_b_clipped, 10);
|
|
}
|
|
{
|
|
struct v2 a = xform_mul_v2(G.world_to_ui_xf, collider_res.a1);
|
|
struct v2 b = xform_mul_v2(G.world_to_ui_xf, collider_res.b1);
|
|
draw_line(G.ui_cmd_buffer, a, b, thickness, color_line);
|
|
draw_circle(G.ui_cmd_buffer, a, radius, color_a, 10);
|
|
draw_circle(G.ui_cmd_buffer, b, radius, color_b, 10);
|
|
|
|
struct v2 a_clipped = xform_mul_v2(G.world_to_ui_xf, collider_res.a1_clipped);
|
|
struct v2 b_clipped = xform_mul_v2(G.world_to_ui_xf, collider_res.b1_clipped);
|
|
draw_line(G.ui_cmd_buffer, a_clipped, b_clipped, thickness, color_line_clipped);
|
|
draw_circle(G.ui_cmd_buffer, a_clipped, radius, color_a_clipped, 10);
|
|
draw_circle(G.ui_cmd_buffer, b_clipped, radius, color_b_clipped, 10);
|
|
}
|
|
}
|
|
|
|
#if COLLIDER_DEBUG_DETAILED_DRAW_MENKOWSKI
|
|
struct xform e0_xf = data->xf0;
|
|
struct xform e1_xf = data->xf1;
|
|
|
|
#if 0
|
|
/* Only draw points with large separation */
|
|
b32 should_draw = false;
|
|
for (u32 i = 0; i < data->num_points; ++i) {
|
|
if (data->points[i].starting_separation < -0.1) {
|
|
should_draw = true;
|
|
break;
|
|
}
|
|
}
|
|
#else
|
|
b32 should_draw = true;
|
|
#endif
|
|
|
|
if (should_draw) {
|
|
#if 0
|
|
/* Test info */
|
|
{
|
|
struct font *disp_font = font_load_async(LIT("res/fonts/fixedsys.ttf"), 12.0f);
|
|
if (disp_font) {
|
|
f32 offset_px = 10;
|
|
struct string fmt = LIT(
|
|
"e0 pos: (%F, %F)\n"
|
|
"e0 rot: %F\n"
|
|
"e1 pos: (%F, %F)\n"
|
|
"e1 rot: %F\n"
|
|
);
|
|
struct string text = string_format(temp.arena, fmt,
|
|
FMT_FLOAT_P(e0_xf.og.x, 24), FMT_FLOAT_P(e0_xf.og.y, 24),
|
|
FMT_FLOAT_P(xform_get_rotation(e0_xf), 24),
|
|
FMT_FLOAT_P(e1_xf.og.x, 24), FMT_FLOAT_P(e1_xf.og.y, 24),
|
|
FMT_FLOAT_P(xform_get_rotation(e1_xf), 24));
|
|
|
|
|
|
draw_text(G.ui_cmd_buffer, disp_font, v2_add(v2_round(xform_mul_v2(G.world_to_ui_xf, V2(0, 0))), V2(0, offset_px)), text);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Draw menkowski */
|
|
{
|
|
|
|
u32 color = collider_res.solved ? RGBA_32_F(0, 0, 0.25, 1) : RGBA_32_F(0, 0.25, 0.25, 1);
|
|
f32 thickness = 2;
|
|
u32 detail = 512;
|
|
(UNUSED)thickness;
|
|
|
|
struct v2_array m = menkowski(temp.arena, &e0_collider, &e1_collider, e0_xf, e1_xf, detail);
|
|
|
|
for (u64 i = 0; i < m.count; ++i) m.points[i] = xform_mul_v2(G.world_to_ui_xf, m.points[i]);
|
|
draw_poly_line(G.ui_cmd_buffer, m, true, thickness, color);
|
|
//draw_poly(G.ui_cmd_buffer, m, color);
|
|
}
|
|
|
|
/* Draw cloud */
|
|
{
|
|
u32 color = RGBA_32_F(1, 1, 1, 1);
|
|
f32 radius = 2;
|
|
|
|
struct v2_array m = cloud(temp.arena, &e0_collider, &e1_collider, e0_xf, e1_xf);
|
|
|
|
for (u64 i = 0; i < m.count; ++i) {
|
|
struct v2 p = xform_mul_v2(G.world_to_ui_xf, m.points[i]);
|
|
draw_circle(G.ui_cmd_buffer, p, radius, color, 10);
|
|
}
|
|
}
|
|
|
|
/* Draw normal */
|
|
{
|
|
u32 color = COLOR_WHITE;
|
|
f32 len = 0.1f;
|
|
f32 arrow_thickness = 2;
|
|
f32 arrow_height = 5;
|
|
struct v2 start = xform_mul_v2(G.world_to_ui_xf, V2(0, 0));
|
|
struct v2 end = xform_mul_v2(G.world_to_ui_xf, v2_mul(v2_norm(collider_res.normal), len));
|
|
draw_arrow_line(G.ui_cmd_buffer, start, end, arrow_thickness, arrow_height, color);
|
|
}
|
|
|
|
/* Draw prototype */
|
|
{
|
|
f32 thickness = 2;
|
|
u32 color = RGBA_32_F(1, 1, 1, 0.25);
|
|
|
|
struct v2_array m = {
|
|
.points = collider_res.prototype.points,
|
|
.count = collider_res.prototype.len
|
|
};
|
|
for (u64 i = 0; i < m.count; ++i) m.points[i] = xform_mul_v2(G.world_to_ui_xf, m.points[i]);
|
|
draw_poly_line(G.ui_cmd_buffer, m, true, thickness, color);
|
|
for (u64 i = 0; i < m.count; ++i) draw_circle(G.ui_cmd_buffer, m.points[i], 10, color, 10);
|
|
}
|
|
|
|
/* Draw simplex */
|
|
{
|
|
f32 thickness = 2;
|
|
u32 line_color = COLOR_YELLOW;
|
|
u32 color_first = RGBA_32_F(1, 0, 0, 0.75);
|
|
u32 color_second = RGBA_32_F(0, 1, 0, 0.75);
|
|
u32 color_third = RGBA_32_F(0, 0, 1, 0.75);
|
|
|
|
struct collider_menkowski_simplex simplex = collider_res.simplex;
|
|
struct v2 simplex_points[] = { simplex.a.p, simplex.b.p, simplex.c.p };
|
|
for (u64 i = 0; i < ARRAY_COUNT(simplex_points); ++i) simplex_points[i] = xform_mul_v2(G.world_to_ui_xf, simplex_points[i]);
|
|
struct v2_array simplex_array = { .count = simplex.len, .points = simplex_points };
|
|
|
|
if (simplex.len >= 1) {
|
|
u32 color = simplex.len == 1 ? color_first : (simplex.len == 2 ? color_second : color_third);
|
|
draw_circle(G.ui_cmd_buffer, simplex_array.points[0], thickness * 3, color, 10);
|
|
}
|
|
if (simplex.len >= 2) {
|
|
u32 color = simplex.len == 2 ? color_first : color_second;
|
|
draw_circle(G.ui_cmd_buffer, simplex_array.points[1], thickness * 3, color, 10);
|
|
}
|
|
if (simplex.len >= 3) {
|
|
u32 color = color_first;
|
|
draw_circle(G.ui_cmd_buffer, simplex_array.points[2], thickness * 3, color, 10);
|
|
}
|
|
if (simplex.len >= 2) {
|
|
draw_poly_line(G.ui_cmd_buffer, simplex_array, simplex.len > 2, thickness, line_color);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
/* Draw hierarchy */
|
|
if (sim_ent_has_prop(parent, SIM_ENT_PROP_ACTIVE) && !parent->is_root) {
|
|
u32 color = RGBA_32_F(0.6, 0.6, 1, 0.75);
|
|
f32 thickness = 2;
|
|
f32 arrow_height = 15;
|
|
|
|
struct v2 start = xform_mul_v2(G.world_to_ui_xf, xf.og);
|
|
struct v2 end = xform_mul_v2(G.world_to_ui_xf, parent_xf.og);
|
|
draw_arrow_line(G.ui_cmd_buffer, start, end, thickness, arrow_height, color);
|
|
}
|
|
|
|
/* Draw camera rect */
|
|
if (sim_ent_has_prop(ent, SIM_ENT_PROP_CAMERA)) {
|
|
u32 color = ent == local_camera ? RGBA_32_F(1, 1, 1, 0.5) : RGBA_32_F(0, 0.75, 0, 0.5);
|
|
f32 thickness = 3;
|
|
|
|
struct xform quad_xf = xform_mul(xf, ent->camera_quad_xform);
|
|
struct quad quad = xform_mul_quad(quad_xf, QUAD_UNIT_SQUARE_CENTERED);
|
|
quad = xform_mul_quad(G.world_to_ui_xf, quad);
|
|
|
|
draw_quad_line(G.ui_cmd_buffer, quad, thickness, color);
|
|
}
|
|
|
|
arena_temp_end(temp);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Draw crosshair or show cursor */
|
|
if (!G.debug_camera) {
|
|
__profscope(draw_crosshair);
|
|
struct v2 crosshair_pos = G.ui_cursor;
|
|
u32 tint = RGBA_32_F(1, 1, 1, 1);
|
|
|
|
struct sprite_tag crosshair_tag = sprite_tag_from_path(LIT("res/graphics/crosshair.ase"));
|
|
struct sprite_texture *t = sprite_texture_from_tag_async(sprite_frame_scope, crosshair_tag);
|
|
|
|
struct v2 size = V2(t->width, t->height);
|
|
struct xform xf = XFORM_TRS(.t = crosshair_pos, .s = size);
|
|
struct quad quad = xform_mul_quad(xf, QUAD_UNIT_SQUARE_CENTERED);
|
|
draw_quad_texture(G.ui_cmd_buffer, DRAW_TEXTURE_PARAMS(.sprite = crosshair_tag, .tint = tint), quad);
|
|
|
|
struct rect cursor_clip = RECT_FROM_V2(G.ui_screen_offset, G.ui_size);
|
|
cursor_clip.pos = v2_add(cursor_clip.pos, v2_mul(size, 0.5f));
|
|
cursor_clip.pos = v2_add(cursor_clip.pos, V2(1, 1));
|
|
cursor_clip.size = v2_sub(cursor_clip.size, size);
|
|
sys_window_cursor_hide(G.window);
|
|
sys_window_cursor_enable_clip(G.window, cursor_clip);
|
|
} else {
|
|
__profscope(update_window_cursor);
|
|
sys_window_cursor_disable_clip(G.window);
|
|
sys_window_cursor_show(G.window);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Queue player control cmd
|
|
* ========================== */
|
|
|
|
{
|
|
/* Queue player move cmd */
|
|
f32 move_speed = 1.0f;
|
|
if (G.bind_states[USER_BIND_KIND_WALK].is_held) {
|
|
//const f32 walk_ratio = 0.25f;
|
|
const f32 walk_ratio = 0.05f;
|
|
move_speed *= walk_ratio;
|
|
}
|
|
|
|
struct v2 input_move_dir = ZI;
|
|
{
|
|
for (enum user_bind_kind bind = 0; bind < (i32)ARRAY_COUNT(G.bind_states); ++bind) {
|
|
struct bind_state state = G.bind_states[bind];
|
|
|
|
if (!state.is_held && state.num_presses <= 0) {
|
|
continue;
|
|
}
|
|
|
|
switch (bind) {
|
|
/* Movement */
|
|
case USER_BIND_KIND_MOVE_UP: {
|
|
input_move_dir.y -= 1;
|
|
} break;
|
|
|
|
case USER_BIND_KIND_MOVE_DOWN: {
|
|
input_move_dir.y += 1;
|
|
} break;
|
|
|
|
case USER_BIND_KIND_MOVE_LEFT: {
|
|
input_move_dir.x -= 1;
|
|
} break;
|
|
|
|
case USER_BIND_KIND_MOVE_RIGHT: {
|
|
input_move_dir.x += 1;
|
|
} break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
input_move_dir = xform_basis_invert_mul_v2(G.world_to_ui_xf, input_move_dir); /* Make move dir relative to world view */
|
|
input_move_dir = v2_mul(v2_norm(input_move_dir), move_speed);
|
|
}
|
|
struct v2 input_aim_dir = v2_sub(G.world_cursor, sim_ent_get_xform(local_player).og);
|
|
|
|
/* Queue player control cmd */
|
|
{
|
|
struct sim_control control = ZI;
|
|
|
|
if (!G.debug_camera) {
|
|
control.move = input_move_dir;
|
|
control.focus = input_aim_dir;
|
|
}
|
|
|
|
struct bind_state fire_state = G.bind_states[USER_BIND_KIND_FIRE];
|
|
struct bind_state drag_state = G.bind_states[USER_BIND_KIND_DEBUG_DRAG];
|
|
struct bind_state clear_state = G.bind_states[USER_BIND_KIND_DEBUG_CLEAR];
|
|
struct bind_state pause_state = G.bind_states[USER_BIND_KIND_DEBUG_PAUSE];
|
|
struct bind_state step_state = G.bind_states[USER_BIND_KIND_DEBUG_STEP];
|
|
struct bind_state spawn_state = G.bind_states[USER_BIND_KIND_DEBUG_SPAWN];
|
|
|
|
if ((fire_state.num_presses - fire_state.num_releases > 0) || fire_state.is_held) {
|
|
control.flags |= SIM_CONTROL_FLAG_FIRING;
|
|
}
|
|
if ((drag_state.num_presses - drag_state.num_releases > 0) || drag_state.is_held) {
|
|
control.flags |= SIM_CONTROL_FLAG_DRAGGING;
|
|
}
|
|
if (clear_state.num_presses) {
|
|
control.flags |= SIM_CONTROL_FLAG_CLEAR_ALL;
|
|
}
|
|
if (pause_state.num_presses) {
|
|
control.flags |= SIM_CONTROL_FLAG_PAUSE;
|
|
}
|
|
if (step_state.num_presses) {
|
|
control.flags |= SIM_CONTROL_FLAG_STEP;
|
|
}
|
|
if (spawn_state.num_presses) {
|
|
control.flags |= SIM_CONTROL_FLAG_SPAWN_TEST;
|
|
}
|
|
|
|
queue_sim_cmd(scratch.arena, &cmd_frame, (struct sim_cmd) {
|
|
.kind = SIM_CMD_KIND_CLIENT_CONTROL,
|
|
.control = control,
|
|
.cursor_pos = G.world_cursor,
|
|
});
|
|
}
|
|
|
|
#if COLLIDER_DEBUG
|
|
/* Gjk steps */
|
|
{
|
|
i64 new_steps = collider_debug_steps;
|
|
new_steps += G.bind_states[USER_BIND_KIND_INCR_COLLIDER_GJK_STEPS].num_presses_and_repeats;
|
|
new_steps -= G.bind_states[USER_BIND_KIND_DECR_COLLIDER_GJK_STEPS].num_presses_and_repeats;
|
|
if (G.bind_states[USER_BIND_KIND_RESET_COLLIDER_GJK_STEPS].num_presses_and_repeats > 0) new_steps = 0;
|
|
collider_debug_steps = (u32)clamp_i64(new_steps, 0, U32_MAX);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
/* Debug draw info */
|
|
if (G.debug_draw) {
|
|
|
|
f32 spacing = 20;
|
|
struct v2 pos = V2(10, 8);
|
|
struct font *font = font_load_async(LIT("res/fonts/fixedsys.ttf"), 12.0f);
|
|
if (font) {
|
|
struct temp_arena temp = arena_temp_begin(scratch.arena);
|
|
|
|
#if BITBUFF_DEBUG
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("(bitbuff debug enabled)")));
|
|
pos.y += spacing;
|
|
#endif
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("Read from local sim: %F mbit/s"), FMT_FLOAT_P((f64)G.client_bytes_read.last_second * 8 / 1000 / 1000, 3)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("Send to local sim: %F mbit/s"), FMT_FLOAT_P((f64)G.client_bytes_sent.last_second * 8 / 1000 / 1000, 3)));
|
|
pos.y += spacing;
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("user entities: %F/%F"), FMT_UINT(G.world->num_ents_allocated), FMT_UINT(G.world->num_ents_reserved)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("user tick: %F"), FMT_UINT(G.world->tick)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("user time: %F"), FMT_FLOAT_P(SECONDS_FROM_NS(G.world->real_time_ns), 3)));
|
|
pos.y += spacing;
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("local sim last known tick: %F"), FMT_UINT(G.local_sim_last_known_tick)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("local sim last known time: %F"), FMT_FLOAT_P(SECONDS_FROM_NS(G.local_sim_last_known_time_ns), 3)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("local sim predicted time: %F"), FMT_FLOAT_P(SECONDS_FROM_NS(G.local_sim_predicted_time_ns), 3)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("local sim predicted time (smoothed): %F"), FMT_FLOAT_P(SECONDS_FROM_NS(G.local_sim_predicted_time_smoothed_ns), 3)));
|
|
pos.y += spacing;
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("Memory usage: %F MiB"), FMT_FLOAT_P((f64)atomic_u64_eval(&app_statistics()->memory_committed) / 1024 / 1024, 3)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("Virtual memory usage: %F TiB"), FMT_FLOAT_P((f64)atomic_u64_eval(&app_statistics()->memory_reserved) / 1024 / 1024 / 1024 / 1024, 3)));
|
|
pos.y += spacing;
|
|
pos.y += spacing;
|
|
|
|
#if 0
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("screen_size: (%F, %F)"), FMT_FLOAT((f64)G.screen_size.x), FMT_FLOAT((f64)G.screen_size.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("screen_cursor: (%F, %F)"), FMT_FLOAT((f64)G.screen_cursor.x), FMT_FLOAT((f64)G.screen_cursor.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("ui_screen_offset: (%F, %F)"), FMT_FLOAT((f64)G.ui_screen_offset.x), FMT_FLOAT((f64)G.ui_screen_offset.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("ui_size: (%F, %F)"), FMT_FLOAT((f64)G.ui_size.x), FMT_FLOAT((f64)G.ui_size.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("ui_center: (%F, %F)"), FMT_FLOAT((f64)G.ui_center.x), FMT_FLOAT((f64)G.ui_center.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("ui_cursor: (%F, %F)"), FMT_FLOAT((f64)G.ui_cursor.x), FMT_FLOAT((f64)G.ui_cursor.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("world_to_ui_xf.og: (%F, %F)"), FMT_FLOAT((f64)G.world_to_ui_xf.og.x), FMT_FLOAT((f64)G.world_to_ui_xf.og.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("world_to_ui_xf rotation: %F"), FMT_FLOAT((f64)xform_get_rotation(G.world_to_ui_xf))));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("world_to_ui_xf scale: (%F, %F)"), FMT_FLOAT((f64)xform_get_scale(G.world_to_ui_xf).x), FMT_FLOAT((f64)xform_get_scale(G.world_to_ui_xf).x)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("world_cursor: (%F, %F)"), FMT_FLOAT((f64)G.world_cursor.x), FMT_FLOAT((f64)G.world_cursor.y)));
|
|
pos.y += spacing;
|
|
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("debug_camera: %F"), FMT_STR(G.debug_camera ? LIT("true") : LIT("false"))));
|
|
pos.y += spacing;
|
|
|
|
struct v2 player_linear_vel = sim_ent_find_first_match_one(store, SIM_ENT_PROP_PLAYER_CONTROLLED)->linear_velocity;
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("player linear velocity: (%F, %F)"), FMT_FLOAT_P((f64)player_linear_vel.x, 12), FMT_FLOAT_P((f64)player_linear_vel.y, 12)));
|
|
pos.y += spacing;
|
|
|
|
f32 player_angular_vel = sim_ent_find_first_match_one(store, SIM_ENT_PROP_PLAYER_CONTROLLED)->angular_velocity;
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("player angular velocity: %F"), FMT_FLOAT_P((f64)player_angular_vel, 12)));
|
|
pos.y += spacing;
|
|
|
|
struct v2 player_pos = sim_ent_get_xform(sim_ent_find_first_match_one(store, SIM_ENT_PROP_PLAYER_CONTROLLED)).og;
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("player pos: (%F, %F)"), FMT_FLOAT_P((f64)player_pos.x, 12), FMT_FLOAT_P((f64)player_pos.y, 12)));
|
|
pos.y += spacing;
|
|
#endif
|
|
|
|
#if COLLIDER_DEBUG
|
|
draw_text(G.ui_cmd_buffer, font, pos, string_format(temp.arena, LIT("collider gjk steps: %F"), FMT_UINT(collider_debug_steps)));
|
|
pos.y += spacing;
|
|
#endif
|
|
|
|
arena_temp_end(temp);
|
|
}
|
|
|
|
}
|
|
|
|
/* Publish sim cmds */
|
|
{
|
|
struct sim_cmd_frame_list l = ZI;
|
|
l.first = &cmd_frame;
|
|
l.last = &cmd_frame;
|
|
|
|
struct bitbuff_writer bw = bw_from_bitbuff(&G.encoder_bitbuff);
|
|
sim_cmd_frames_encode(&bw, l, G.local_sim_last_known_tick);
|
|
struct string cmds_str = bw_get_written(scratch.arena, &bw);
|
|
host_queue_write(G.host, HOST_CHANNEL_ID_ALL, cmds_str, 0);
|
|
}
|
|
|
|
host_update(G.host);
|
|
|
|
/* Update network usage stats */
|
|
G.client_bytes_read.last_second_end = G.host->bytes_received;
|
|
G.client_bytes_sent.last_second_end = G.host->bytes_sent;
|
|
if (G.real_time_ns - G.last_second_reset_ns > NS_FROM_SECONDS(1)) {
|
|
G.last_second_reset_ns = G.real_time_ns;
|
|
G.client_bytes_read.last_second = G.client_bytes_read.last_second_end - G.client_bytes_read.last_second_start;
|
|
G.client_bytes_sent.last_second = G.client_bytes_sent.last_second_end - G.client_bytes_sent.last_second_start;
|
|
G.client_bytes_read.last_second_start = G.client_bytes_read.last_second_end;
|
|
G.client_bytes_sent.last_second_start = G.client_bytes_sent.last_second_end;
|
|
}
|
|
|
|
/* ========================== *
|
|
* Render
|
|
* ========================== */
|
|
|
|
{
|
|
__profscope(render);
|
|
struct rect ui_viewport = RECT_FROM_V2(V2(0, 0), G.ui_size);
|
|
struct rect backbuffer_viewport = RECT_FROM_V2(V2(0, 0), G.screen_size);
|
|
|
|
/* Allocate render textures */
|
|
struct v2i32 ui_resolution = v2_round_to_int(ui_viewport.size);
|
|
struct v2i32 backbuffer_resolution = v2_round_to_int(backbuffer_viewport.size);
|
|
struct v2i32 world_resolution = ui_resolution;
|
|
|
|
{
|
|
/* World texture */
|
|
if (!G.world_texture.handle || !v2i32_eq(renderer_texture_get_size(G.world_texture), world_resolution)) {
|
|
if (G.world_texture.handle) {
|
|
renderer_texture_release(G.world_texture);
|
|
}
|
|
G.world_texture = renderer_texture_alloc(RENDERER_TEXTURE_FORMAT_R8G8B8A8_UNORM, RENDERER_TEXTURE_FLAG_TARGET, world_resolution, NULL);
|
|
}
|
|
/* Ui texture */
|
|
if (!G.ui_texture.handle || !v2i32_eq(renderer_texture_get_size(G.ui_texture), ui_resolution)) {
|
|
if (G.ui_texture.handle) {
|
|
renderer_texture_release(G.ui_texture);
|
|
}
|
|
G.ui_texture = renderer_texture_alloc(RENDERER_TEXTURE_FORMAT_R8G8B8A8_UNORM, RENDERER_TEXTURE_FLAG_TARGET, ui_resolution, NULL);
|
|
}
|
|
/* Final texture */
|
|
if (!G.final_texture.handle || !v2i32_eq(renderer_texture_get_size(G.final_texture), ui_resolution)) {
|
|
if (G.final_texture.handle) {
|
|
renderer_texture_release(G.final_texture);
|
|
}
|
|
G.final_texture = renderer_texture_alloc(RENDERER_TEXTURE_FORMAT_R8G8B8A8_UNORM, RENDERER_TEXTURE_FLAG_TARGET, ui_resolution, NULL);
|
|
}
|
|
/* Backbuffer texture */
|
|
if (!G.backbuffer_texture.handle || !v2i32_eq(renderer_texture_get_size(G.backbuffer_texture), backbuffer_resolution)) {
|
|
G.backbuffer_texture = renderer_backbuffer_recreate(backbuffer_resolution);;
|
|
}
|
|
}
|
|
|
|
/* Combine render textures w/ quad draw cmds */
|
|
{
|
|
/* Draw world texture to final */
|
|
{
|
|
struct draw_texture_params params = DRAW_TEXTURE_PARAMS(.texture = G.world_texture);
|
|
struct quad quad = quad_from_rect(RECT_FROM_V2(V2(0, 0), V2_FROM_V2I32(ui_resolution)));
|
|
draw_quad_texture(G.final_cmd_buffer, params, quad);
|
|
}
|
|
|
|
/* Draw ui texture to final */
|
|
{
|
|
struct draw_texture_params params = DRAW_TEXTURE_PARAMS(.texture = G.ui_texture);
|
|
struct quad quad = quad_from_rect(RECT_FROM_V2(V2(0, 0), V2_FROM_V2I32(ui_resolution)));
|
|
draw_quad_texture(G.final_cmd_buffer, params, quad);
|
|
}
|
|
|
|
/* Draw final texture to backbuffer */
|
|
{
|
|
struct draw_texture_params params = DRAW_TEXTURE_PARAMS(.texture = G.final_texture);
|
|
struct quad quad = quad_from_rect(RECT_FROM_V2(G.ui_screen_offset, V2_FROM_V2I32(G.ui_size)));
|
|
draw_quad_texture(G.backbuffer_cmd_buffer, params, quad);
|
|
}
|
|
}
|
|
|
|
/* Send cmd buffers to GPU */
|
|
renderer_cmd_buffer_send_to_gpu(G.world_cmd_buffer);
|
|
renderer_cmd_buffer_send_to_gpu(G.ui_cmd_buffer);
|
|
renderer_cmd_buffer_send_to_gpu(G.final_cmd_buffer);
|
|
renderer_cmd_buffer_send_to_gpu(G.backbuffer_cmd_buffer);
|
|
|
|
/* Execute render cmds */
|
|
{
|
|
/* Clear textures */
|
|
renderer_texture_clear(G.world_texture, RGBA_32_F(0.2f, 0.2f, 0.2f, 1.f));
|
|
renderer_texture_clear(G.ui_texture, RGBA_32_F(0, 0, 0, 0));
|
|
renderer_texture_clear(G.final_texture, RGBA_32_F(0, 0, 0, 0));
|
|
renderer_texture_clear(G.backbuffer_texture, RGBA_32_F(0, 0, 0, 1));
|
|
|
|
/* Render to world texture */
|
|
renderer_texture_render(G.world_texture, G.world_cmd_buffer, G.world_to_ui_xf, ui_viewport, sprite_frame_scope);
|
|
|
|
/* Render to UI texture */
|
|
renderer_texture_render(G.ui_texture, G.ui_cmd_buffer, XFORM_IDENT, ui_viewport, sprite_frame_scope);
|
|
|
|
/* Render to final texture */
|
|
renderer_texture_render(G.final_texture, G.final_cmd_buffer, XFORM_IDENT, ui_viewport, sprite_frame_scope);
|
|
|
|
/* Render to backbuffer */
|
|
renderer_texture_render(G.backbuffer_texture, G.backbuffer_cmd_buffer, XFORM_IDENT, backbuffer_viewport, sprite_frame_scope);
|
|
}
|
|
|
|
/* Present */
|
|
renderer_backbuffer_present(VSYNC_ENABLED);
|
|
}
|
|
|
|
/* ========================== *
|
|
* End frame cache scopes
|
|
* ========================== */
|
|
|
|
sprite_scope_end(sprite_frame_scope);
|
|
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
/* ========================== *
|
|
* User thread
|
|
* ========================== */
|
|
|
|
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_thread_entry_point, arg)
|
|
{
|
|
(UNUSED)arg;
|
|
|
|
i64 last_frame_ns = 0;
|
|
i64 target_dt_ns = NS_FROM_SECONDS(USER_FRAME_LIMIT > (0) ? (1.0 / USER_FRAME_LIMIT) : 0);
|
|
|
|
while (!atomic_i32_eval(&G.user_thread_shutdown)) {
|
|
__profscope(user_update_w_sleep);
|
|
sleep_frame(last_frame_ns, target_dt_ns);
|
|
last_frame_ns = sys_time_ns();
|
|
user_update();
|
|
}
|
|
}
|
|
|
|
/* ========================== *
|
|
* Local sim thread
|
|
* ========================== */
|
|
|
|
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
|
|
{
|
|
#if 0
|
|
struct host_listen_address local_listen_addr = host_listen_address_from_local_name(LIT("LOCAL_SIM"));
|
|
struct host_listen_address net_listen_addr = host_listen_address_from_net_port(12345);
|
|
//struct host *host = host_alloc();
|
|
/* TODO: Host system should allocate & copy string stored in local_listen_addr */
|
|
//host_listen(host, local_listen_addr);
|
|
//host_listen(host, net_listen_addr);
|
|
#else
|
|
struct host *host = host_alloc(12345);
|
|
#endif
|
|
|
|
struct bitbuff encoder_bitbuff = bitbuff_alloc(GIGABYTE(64));
|
|
struct sim_snapshot_store *snapshot_store = sim_snapshot_store_alloc();
|
|
|
|
|
|
|
|
(UNUSED)arg;
|
|
|
|
|
|
|
|
#if 1
|
|
|
|
struct sim_snapshot *prev_ss = sim_snapshot_nil();
|
|
|
|
i64 last_tick_ns = 0;
|
|
i64 target_dt_ns = NS_FROM_SECONDS(1) / SIM_TICKS_PER_SECOND;;
|
|
while (!atomic_i32_eval(&G.local_sim_thread_shutdown)) {
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
{
|
|
__profscope(local_sim_sleep);
|
|
sleep_frame(last_tick_ns, target_dt_ns);
|
|
last_tick_ns = sys_time_ns();
|
|
}
|
|
|
|
/* Release old snapshots */
|
|
/* TODO: Remove this */
|
|
{
|
|
u64 keep_count = 50;
|
|
u64 keep_tick = prev_ss->tick > keep_count ? prev_ss->tick - keep_count : 0;
|
|
|
|
struct sim_snapshot *remss = sim_snapshot_from_tick(snapshot_store, snapshot_store->first_tick);
|
|
while (remss->valid) {
|
|
u64 next_tick = remss->next_tick;
|
|
if (remss->tick < keep_tick) {
|
|
sim_snapshot_release(remss);
|
|
} else {
|
|
break;
|
|
}
|
|
remss = sim_snapshot_from_tick(snapshot_store, next_tick);
|
|
}
|
|
}
|
|
|
|
struct sim_cmd_frame_list raw_input_cmd_frames = ZI;
|
|
{
|
|
host_update(host);
|
|
struct host_event_array host_events = host_pop_events(scratch.arena, host);
|
|
sim_cmd_frames_decode(scratch.arena, host_events, &raw_input_cmd_frames);
|
|
}
|
|
|
|
|
|
|
|
|
|
b32 should_break = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Merge cmd frames from user thread */
|
|
struct sim_cmd *user_control_cmd = NULL;
|
|
struct sim_cmd_frame user_cmd_frame = ZI;
|
|
struct sim_cmd_frame_list input_cmd_frames = ZI;
|
|
{
|
|
struct sim_cmd_frame *frame = raw_input_cmd_frames.first;
|
|
while (frame) {
|
|
struct sim_cmd_frame *next_frame = frame->next;
|
|
/* FIXME: Only do this for user cmds */
|
|
if (frame->tick == 0) {
|
|
/* All cmd frames from the user thread need to be merged into a single cmd frame representing inputs for one sim tick */
|
|
if (user_cmd_frame.tick == 0) {
|
|
user_cmd_frame = *frame;
|
|
user_cmd_frame.tick = prev_ss->tick + 1;
|
|
user_cmd_frame.next = NULL;
|
|
}
|
|
if (frame->ack > user_cmd_frame.ack) {
|
|
user_cmd_frame.ack = frame->ack;
|
|
}
|
|
struct sim_cmd *cmd = frame->first;
|
|
while (cmd) {
|
|
struct sim_cmd *next_cmd = cmd->next;
|
|
b32 should_insert = true;
|
|
if (cmd->kind == SIM_CMD_KIND_CLIENT_CONTROL) {
|
|
if (user_control_cmd) {
|
|
/* Merge with existing control cmd */
|
|
|
|
if (cmd->control.flags & SIM_CONTROL_FLAG_CLEAR_ALL) {
|
|
should_break = true;
|
|
DEBUGBREAKABLE;
|
|
}
|
|
|
|
should_insert = false;
|
|
u32 flags = user_control_cmd->control.flags;
|
|
*user_control_cmd = *cmd;
|
|
user_control_cmd->control.flags |= flags;
|
|
} else {
|
|
user_control_cmd = cmd;
|
|
}
|
|
}
|
|
if (should_insert) {
|
|
if (user_cmd_frame.last) {
|
|
user_cmd_frame.last->next = cmd;
|
|
} else {
|
|
user_cmd_frame.first = cmd;
|
|
}
|
|
user_cmd_frame.last = cmd;
|
|
}
|
|
cmd->next = NULL;
|
|
cmd = next_cmd;
|
|
}
|
|
} else {
|
|
if (input_cmd_frames.last) {
|
|
input_cmd_frames.last = frame;
|
|
} else {
|
|
input_cmd_frames.first = frame;
|
|
}
|
|
input_cmd_frames.last = frame;
|
|
}
|
|
frame->next = NULL;
|
|
frame = next_frame;
|
|
}
|
|
}
|
|
if (user_cmd_frame.tick != 0) {
|
|
if (input_cmd_frames.last) {
|
|
input_cmd_frames.last->next = &user_cmd_frame;
|
|
} else {
|
|
input_cmd_frames.first = &user_cmd_frame;
|
|
}
|
|
input_cmd_frames.last = &user_cmd_frame;
|
|
}
|
|
|
|
if (input_cmd_frames.first && input_cmd_frames.first->first && input_cmd_frames.first->first->control.flags & SIM_CONTROL_FLAG_CLEAR_ALL) {
|
|
DEBUGBREAKABLE;
|
|
} else if (should_break) {
|
|
DEBUGBREAKABLE;
|
|
}
|
|
|
|
/* Step */
|
|
struct sim_snapshot *ss = sim_step(snapshot_store, prev_ss, input_cmd_frames, target_dt_ns);
|
|
|
|
/* Publish snapshot cmds */
|
|
u64 oldest_ack_tick = 0;
|
|
for (u64 i = 0; i < ss->num_clients_reserved; ++i) {
|
|
struct sim_client *client = &ss->clients[i];
|
|
if (client->valid) {
|
|
struct temp_arena temp = arena_temp_begin(scratch.arena);
|
|
|
|
if (oldest_ack_tick == 0 || client->ack < oldest_ack_tick) {
|
|
oldest_ack_tick = client->ack;
|
|
}
|
|
|
|
struct sim_snapshot *ss0 = sim_snapshot_from_tick(snapshot_store, client->ack);
|
|
struct sim_snapshot *ss1 = ss;
|
|
|
|
/* Create & encode snapshot cmd */
|
|
struct sim_cmd snapshot_cmd = ZI;
|
|
{
|
|
snapshot_cmd.kind = SIM_CMD_KIND_SNAPSHOT;
|
|
snapshot_cmd.snapshot_tick_start = ss0->tick;
|
|
snapshot_cmd.snapshot_tick_end = ss1->tick;
|
|
{
|
|
struct bitbuff_writer bw = bw_from_bitbuff(&encoder_bitbuff);
|
|
sim_snapshot_encode(&bw, ss0, ss1, client);
|
|
snapshot_cmd.snapshot_encoded = bw_get_written(temp.arena, &bw);
|
|
}
|
|
}
|
|
|
|
struct sim_cmd_frame snapshot_cmd_frame = ZI;
|
|
snapshot_cmd_frame.first = &snapshot_cmd;
|
|
snapshot_cmd_frame.last = &snapshot_cmd;
|
|
|
|
struct sim_cmd_frame_list cmd_frames = ZI;
|
|
cmd_frames.first = &snapshot_cmd_frame;
|
|
cmd_frames.last = &snapshot_cmd_frame;
|
|
|
|
/* Encode cmds */
|
|
struct string cmds_msg = ZI;
|
|
{
|
|
struct bitbuff_writer bw = bw_from_bitbuff(&encoder_bitbuff);
|
|
/* FIXME: Ack tick */
|
|
sim_cmd_frames_encode(&bw, cmd_frames, 0);
|
|
cmds_msg = bw_get_written(temp.arena, &bw);
|
|
}
|
|
|
|
host_queue_write(host, client->channel_id, cmds_msg, 0);
|
|
|
|
arena_temp_end(temp);
|
|
}
|
|
}
|
|
|
|
/* Send host messages */
|
|
host_update(host);
|
|
__profframe("Local sim");
|
|
|
|
scratch_end(scratch);
|
|
|
|
prev_ss = ss;
|
|
}
|
|
|
|
#else
|
|
|
|
i64 last_tick_ns = 0;
|
|
i64 target_dt_ns = NS_FROM_SECONDS(1) / SIM_TICKS_PER_SECOND;;
|
|
while (!atomic_i32_eval(&G.local_sim_thread_shutdown)) {
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
{
|
|
__profscope(local_sim_sleep);
|
|
sleep_frame(last_tick_ns, target_dt_ns);
|
|
last_tick_ns = sys_time_ns();
|
|
}
|
|
|
|
struct sim_cmd_frame user_frame = ZI;
|
|
user_frame.tick = prev_ss->tick + 1;
|
|
user_frame.ack = prev_ss->tick;
|
|
|
|
/* Read cmds from host */
|
|
host_update(host);
|
|
struct host_event_array host_events = host_pop_events(scratch.arena, host);
|
|
sim_cmds_decode(scratch.arena, host_events, &sim_cmds);
|
|
|
|
/* Step */
|
|
struct sim_snapshot *ss = sim_step(ss_store, prev_ss, user_cmds, target_dt_ns);
|
|
|
|
/* Encode & enqueue sim_cmd frames */
|
|
for (struct sim_cmd_frame *f = output_cmds.first; f; f = f->next) {
|
|
struct host_channel_id channel_id = f->dst_channel;
|
|
struct string cmd_frame_msg = ZI;
|
|
struct bitbuff_writer bw = bw_from_bitbuff(&encoder_bitbuff);
|
|
sim_cmd_frame_encode(&bw, q);
|
|
cmd_frame_msg = bw_get_written(temp.arena, &bw);
|
|
host_queue_write(host, channel_id, cmd_frame_msg, 0);
|
|
}
|
|
|
|
/* Send host messages */
|
|
//host_update(host);
|
|
__profframe("Local sim");
|
|
|
|
scratch_end(scratch);
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
sim_snapshot_store_release(snapshot_store);
|
|
bitbuff_release(&encoder_bitbuff);
|
|
host_release(host);
|
|
}
|