differentiate client entities

This commit is contained in:
jacob 2025-02-08 17:11:04 -06:00
parent 16e8bb0dca
commit eee5945954
14 changed files with 519 additions and 327 deletions

View File

@ -5,8 +5,8 @@
typedef APP_EXIT_CALLBACK_FUNC_DEF(app_exit_callback_func); typedef APP_EXIT_CALLBACK_FUNC_DEF(app_exit_callback_func);
struct app_statistics { struct app_statistics {
struct atomic_u64 host_bytes_sent; struct atomic_u64 sock_bytes_sent;
struct atomic_u64 host_bytes_received; struct atomic_u64 sock_bytes_received;
struct atomic_u64 memory_committed; struct atomic_u64 memory_committed;
struct atomic_u64 memory_reserved; struct atomic_u64 memory_reserved;
}; };

View File

@ -6,7 +6,6 @@
#include "util.h" #include "util.h"
#include "log.h" #include "log.h"
#include "buddy.h" #include "buddy.h"
#include "app.h"
#include "atomic.h" #include "atomic.h"
//#define HOST_NETWORK_ADDRESS_STRING(str) //#define HOST_NETWORK_ADDRESS_STRING(str)
@ -196,7 +195,8 @@ struct host *host_alloc(u16 listen_port)
void host_release(struct host *host) void host_release(struct host *host)
{ {
/* FIXME: Signal thread shutdown */ atomic_i32_eval_exchange(&host->receiver_thread_shutdown_flag, 1);
sock_wake(host->sock);
sys_thread_wait_release(&host->receiver_thread); sys_thread_wait_release(&host->receiver_thread);
sys_mutex_release(&host->rcv_buffer_write_mutex); sys_mutex_release(&host->rcv_buffer_write_mutex);
@ -682,10 +682,12 @@ void host_update(struct host *host)
/* A foreign host is trying to connect to us */ /* A foreign host is trying to connect to us */
if (!channel->valid) { if (!channel->valid) {
logf_info("Received conection attempt from %F", FMT_STR(sock_string_from_address(scratch.arena, address))); logf_info("Received conection attempt from %F", FMT_STR(sock_string_from_address(scratch.arena, address)));
/* TODO: Verify that some per-host uuid isn't /* TODO: Verify that some per-host uuid isn't present in a rolling window to prevent reconnects right after a disconnect? */
* present in a rolling window to prevent reconnects right after a disconnect? */
channel = host_channel_alloc(host, address); channel = host_channel_alloc(host, address);
} }
struct host_cmd *cmd = host_cmd_alloc_and_append(host);
cmd->kind = HOST_CMD_KIND_CONNECT_SUCCESS;
cmd->channel_id = channel->id;
} break; } break;
case HOST_PACKET_KIND_CONNECT_SUCCESS: case HOST_PACKET_KIND_CONNECT_SUCCESS:
@ -715,7 +717,7 @@ void host_update(struct host *host)
case HOST_PACKET_KIND_MSG_CHUNK: case HOST_PACKET_KIND_MSG_CHUNK:
{ {
if (channel->valid) { if (channel->valid && channel->connected) {
/* Packet is chunk <chunk_id> out of <chunk_count> belonging to message <msg_id> */ /* Packet is chunk <chunk_id> out of <chunk_count> belonging to message <msg_id> */
u64 msg_id = br_read_var_uint(&br); u64 msg_id = br_read_var_uint(&br);
u64 chunk_id = br_read_var_uint(&br); u64 chunk_id = br_read_var_uint(&br);
@ -771,7 +773,6 @@ void host_update(struct host *host)
} }
} }
host->bytes_received += packet->data.len; host->bytes_received += packet->data.len;
atomic_u64_eval_add_u64(&app_statistics()->host_bytes_received, packet->data.len);
} }
} }
/* Reset read buffer */ /* Reset read buffer */
@ -816,11 +817,11 @@ void host_update(struct host *host)
/* Release timed out unreliable msg buffers */ /* Release timed out unreliable msg buffers */
{ {
/* TODO: Configurable timeout */ /* TODO: Configurable timeout */
i64 timeout_ns = NS_FROM_SECONDS(0.1); i64 unreliable_msg_timeout_ns = NS_FROM_SECONDS(0.1);
struct host_msg_assembler *ma = channel->least_recent_msg_assembler; struct host_msg_assembler *ma = channel->least_recent_msg_assembler;
while (ma) { while (ma) {
struct host_msg_assembler *next = ma->more_recent; struct host_msg_assembler *next = ma->more_recent;
if ((now_ns - ma->touched_ns) > timeout_ns) { if ((now_ns - ma->touched_ns) > unreliable_msg_timeout_ns) {
if (!ma->is_reliable) { if (!ma->is_reliable) {
host_msg_assembler_release(ma); host_msg_assembler_release(ma);
} }
@ -957,7 +958,6 @@ void host_update(struct host *host)
channel->num_unreliable_packets = 0; channel->num_unreliable_packets = 0;
} }
host->bytes_sent += total_sent; host->bytes_sent += total_sent;
atomic_u64_eval_add_u64(&app_statistics()->host_bytes_sent, total_sent);
} }
} }
} }
@ -1021,26 +1021,29 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(host_receiver_thread_entry_point, arg)
socks.socks = &host->sock; socks.socks = &host->sock;
socks.count = 1; socks.count = 1;
/* FIXME: Shutdown signal */ struct atomic_i32 *shutdown = &host->receiver_thread_shutdown_flag;
volatile b32 run = true; while (!atomic_i32_eval(shutdown)) {
while (run) {
struct sock *sock = sock_wait_for_available_read(socks, NULL, F32_INFINITY); struct sock *sock = sock_wait_for_available_read(socks, NULL, F32_INFINITY);
struct sock_read_result res; struct sock_read_result res;
while (sock && (res = sock_read(sock, read_buff)).valid) { while (!atomic_i32_eval(shutdown) && sock && (res = sock_read(sock, read_buff)).valid) {
struct sys_lock lock = sys_mutex_lock_e(&host->rcv_buffer_write_mutex); struct sock_address address = res.address;
{ struct string data = res.data;
struct host_rcv_buffer *rcv_buffer = host->rcv_buffer_write; if (data.len > 0) {
struct host_rcv_packet *packet = arena_push_zero(&rcv_buffer->arena, struct host_rcv_packet); struct sys_lock lock = sys_mutex_lock_e(&host->rcv_buffer_write_mutex);
packet->address = res.address; {
packet->data = string_copy(&rcv_buffer->arena, res.data); struct host_rcv_buffer *rcv_buffer = host->rcv_buffer_write;
if (rcv_buffer->last_packet) { struct host_rcv_packet *packet = arena_push_zero(&rcv_buffer->arena, struct host_rcv_packet);
rcv_buffer->last_packet->next = packet; packet->address = address;
} else { packet->data = string_copy(&rcv_buffer->arena, data);
rcv_buffer->first_packet = packet; if (rcv_buffer->last_packet) {
rcv_buffer->last_packet->next = packet;
} else {
rcv_buffer->first_packet = packet;
}
rcv_buffer->last_packet = packet;
} }
rcv_buffer->last_packet = packet; sys_mutex_unlock(&lock);
} }
sys_mutex_unlock(&lock);
} }
} }

View File

@ -58,6 +58,8 @@ struct host_event_array {
struct host { struct host {
struct arena arena; struct arena arena;
struct sock_signal *sock_signal;
struct sock *sock; struct sock *sock;
struct buddy_ctx *buddy; /* For storing msg assembler data */ struct buddy_ctx *buddy; /* For storing msg assembler data */
@ -94,6 +96,7 @@ struct host {
u64 bytes_received; u64 bytes_received;
u64 bytes_sent; u64 bytes_sent;
struct atomic_i32 receiver_thread_shutdown_flag;
struct sys_thread receiver_thread; struct sys_thread receiver_thread;
}; };

428
src/sim.c
View File

@ -29,9 +29,6 @@ struct sim_ctx {
struct host *host; struct host *host;
/* For debugging */
struct v2 user_cursor;
/* TODO: Remove this (testing) */ /* TODO: Remove this (testing) */
b32 extra_spawn; b32 extra_spawn;
b32 should_reset_level; b32 should_reset_level;
@ -44,7 +41,6 @@ struct sim_ctx {
struct sim_ent_lookup collision_debug_lookup; struct sim_ent_lookup collision_debug_lookup;
#endif #endif
struct space *space; struct space *space;
struct sim_client_store *client_store;
/* Tick */ /* Tick */
struct world tick; struct world tick;
@ -55,7 +51,8 @@ struct sim_ctx {
* ========================== */ * ========================== */
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sim_thread_entry_point, arg); INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sim_thread_entry_point, arg);
INTERNAL void reset_world(struct sim_ctx *ctx); INTERNAL void test_sim_world_alloc(struct sim_ctx *ctx);
INTERNAL void test_sim_world_release(struct sim_ctx *ctx);
struct sim_ctx *sim_ctx_alloc(struct sprite_startup_receipt *sprite_sr, struct sim_ctx *sim_ctx_alloc(struct sprite_startup_receipt *sprite_sr,
struct phys_startup_receipt *phys_sr, struct phys_startup_receipt *phys_sr,
@ -70,13 +67,11 @@ struct sim_ctx *sim_ctx_alloc(struct sprite_startup_receipt *sprite_sr,
(UNUSED)phys_sr; (UNUSED)phys_sr;
(UNUSED)host_sr; (UNUSED)host_sr;
ctx->client_store = sim_client_store_alloc();
/* Intialize host */ /* Intialize host */
ctx->host = host_alloc(host_port); ctx->host = host_alloc(host_port);
/* Initialize empty world */ /* Initialize empty world */
reset_world(ctx); test_sim_world_alloc(ctx);
ctx->sim_thread = sys_thread_alloc(&sim_thread_entry_point, ctx, LIT("[P2] Sim thread")); ctx->sim_thread = sys_thread_alloc(&sim_thread_entry_point, ctx, LIT("[P2] Sim thread"));
@ -88,25 +83,20 @@ void sim_ctx_release(struct sim_ctx *ctx)
__prof; __prof;
atomic_i32_eval_exchange(&ctx->sim_thread_shutdown, true); atomic_i32_eval_exchange(&ctx->sim_thread_shutdown, true);
sys_thread_wait_release(&ctx->sim_thread); sys_thread_wait_release(&ctx->sim_thread);
test_sim_world_release(ctx);
host_release(ctx->host);
arena_release(&ctx->arena);
} }
/* ========================== * /* ========================== *
* Reset * Reset
* ========================== */ * ========================== */
INTERNAL void reset_world(struct sim_ctx *ctx) /* TODO: Remove this */
{
if (ctx->tick.ent_store) {
/* Release world */
world_release(&ctx->tick);
/* Release bookkeeping */
space_release(ctx->space);
#if COLLIDER_DEBUG
sim_ent_lookup_release(&ctx->collision_debug_lookup);
#endif
sim_ent_lookup_release(&ctx->contact_lookup);
}
INTERNAL void test_sim_world_alloc(struct sim_ctx *ctx)
{
/* Create bookkeeping */ /* Create bookkeeping */
ctx->contact_lookup = sim_ent_lookup_alloc(4096); ctx->contact_lookup = sim_ent_lookup_alloc(4096);
#if COLLIDER_DEBUG #if COLLIDER_DEBUG
@ -119,6 +109,18 @@ INTERNAL void reset_world(struct sim_ctx *ctx)
ctx->tick.timescale = SIM_TIMESCALE; ctx->tick.timescale = SIM_TIMESCALE;
} }
INTERNAL void test_sim_world_release(struct sim_ctx *ctx)
{
/* Release world */
world_release(&ctx->tick);
/* Release bookkeeping */
space_release(ctx->space);
#if COLLIDER_DEBUG
sim_ent_lookup_release(&ctx->collision_debug_lookup);
#endif
sim_ent_lookup_release(&ctx->contact_lookup);
}
/* ========================== * /* ========================== *
* Test * Test
* ========================== */ * ========================== */
@ -131,70 +133,6 @@ INTERNAL void spawn_test_entities(struct sim_ctx *ctx)
root->mass_unscaled = F32_INFINITY; root->mass_unscaled = F32_INFINITY;
root->inertia_unscaled = F32_INFINITY; root->inertia_unscaled = F32_INFINITY;
/* Player */
struct sim_ent *player_ent = sim_ent_nil();
//if (!ctx->extra_spawn) {
{
struct sim_ent *e = sim_ent_alloc(root);
struct v2 pos = V2(1, -1);
//struct v2 size = V2(0.5, 0.5);
//struct v2 size = V2(0.5, 0.25);
struct v2 size = V2(1.0, 1.0);
//f32 r = PI / 4;
f32 r = 0;
if (!ctx->extra_spawn) {
sim_ent_enable_prop(e, SIM_ENT_PROP_PLAYER_CONTROLLED);
sim_ent_enable_prop(e, SIM_ENT_PROP_TEST);
e->sprite = sprite_tag_from_path(LIT("res/graphics/tim.ase"));
e->mass_unscaled = 10;
e->inertia_unscaled = 5;
} else {
sim_ent_enable_prop(e, SIM_ENT_PROP_TEST);
e->sprite = sprite_tag_from_path(LIT("res/graphics/tim.ase"));
e->mass_unscaled = 10;
e->inertia_unscaled = 5;
#if 0
e->sprite = sprite_tag_from_path(LIT("res/graphics/box.ase"));
e->mass_unscaled = 100;
e->inertia_unscaled = 100;
#endif
}
e->local_collider.points[0] = v2_with_len(V2(0.08f, 0.17f), 0.15f);
e->local_collider.points[1] = v2_with_len(V2(-0.07f, -0.2f), 0.15f);
e->local_collider.count = 2;
e->local_collider.radius = 0.075f;
//e->sprite = sprite_tag_from_path(LIT("res/graphics/box_rounded.ase"));
//e->sprite_span_name = LIT("idle.unarmed");
//e->sprite_span_name = LIT("idle.one_handed");
e->sprite_span_name = LIT("idle.two_handed");
e->layer = SIM_LAYER_SHOULDERS;
struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size);
//xf.bx.y = -1.f;
sim_ent_set_xform(e, xf);
e->linear_ground_friction = 250;
e->angular_ground_friction = 200;
//e->control_force = 500;
e->control_force = 500;
//e->control_force_max_speed = 4;
e->control_torque = 5000;
e->control_force_max_speed = 4;
sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC);
player_ent = e;
}
/* Enemy */ /* Enemy */
{ {
struct sim_ent *e = sim_ent_alloc(root); struct sim_ent *e = sim_ent_alloc(root);
@ -262,6 +200,77 @@ INTERNAL void spawn_test_entities(struct sim_ctx *ctx)
} }
#endif #endif
ctx->extra_spawn = true;
}
INTERNAL struct sim_ent *spawn_test_player(struct sim_ctx *ctx)
{
struct sim_ent_store *store = ctx->tick.ent_store;
struct sim_ent *root = sim_ent_from_handle(store, store->root);
/* Player */
struct sim_ent *player_ent = sim_ent_nil();
//if (!ctx->extra_spawn) {
{
struct sim_ent *e = sim_ent_alloc(root);
struct v2 pos = V2(1, -1);
//struct v2 size = V2(0.5, 0.5);
//struct v2 size = V2(0.5, 0.25);
struct v2 size = V2(1.0, 1.0);
//f32 r = PI / 4;
f32 r = 0;
if (!ctx->extra_spawn) {
sim_ent_enable_prop(e, SIM_ENT_PROP_TEST);
e->sprite = sprite_tag_from_path(LIT("res/graphics/tim.ase"));
e->mass_unscaled = 10;
e->inertia_unscaled = 5;
} else {
sim_ent_enable_prop(e, SIM_ENT_PROP_TEST);
e->sprite = sprite_tag_from_path(LIT("res/graphics/tim.ase"));
e->mass_unscaled = 10;
e->inertia_unscaled = 5;
#if 0
e->sprite = sprite_tag_from_path(LIT("res/graphics/box.ase"));
e->mass_unscaled = 100;
e->inertia_unscaled = 100;
#endif
}
e->local_collider.points[0] = v2_with_len(V2(0.08f, 0.17f), 0.15f);
e->local_collider.points[1] = v2_with_len(V2(-0.07f, -0.2f), 0.15f);
e->local_collider.count = 2;
e->local_collider.radius = 0.075f;
//e->sprite = sprite_tag_from_path(LIT("res/graphics/box_rounded.ase"));
//e->sprite_span_name = LIT("idle.unarmed");
//e->sprite_span_name = LIT("idle.one_handed");
e->sprite_span_name = LIT("idle.two_handed");
e->layer = SIM_LAYER_SHOULDERS;
struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size);
//xf.bx.y = -1.f;
sim_ent_set_xform(e, xf);
e->linear_ground_friction = 250;
e->angular_ground_friction = 200;
//e->control_force = 500;
e->control_force = 500;
//e->control_force_max_speed = 4;
e->control_torque = 5000;
e->control_force_max_speed = 4;
sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC);
player_ent = e;
}
/* Player weapon */ /* Player weapon */
if (player_ent->valid) { if (player_ent->valid) {
struct sim_ent *e = sim_ent_alloc(player_ent); struct sim_ent *e = sim_ent_alloc(player_ent);
@ -278,21 +287,29 @@ INTERNAL void spawn_test_entities(struct sim_ctx *ctx)
player_ent->equipped = e->handle; player_ent->equipped = e->handle;
} }
/* Camera */ return player_ent;
if (!ctx->extra_spawn && player_ent->valid) { }
struct sim_ent *e = sim_ent_alloc(root);
sim_ent_set_xform(e, XFORM_IDENT);
sim_ent_enable_prop(e, SIM_ENT_PROP_CAMERA); INTERNAL struct sim_ent *spawn_test_player_camera(struct sim_ctx *ctx, struct sim_ent *player_ent)
sim_ent_enable_prop(e, SIM_ENT_PROP_CAMERA_ACTIVE); {
e->camera_follow = player_ent->handle; struct sim_ent_store *store = ctx->tick.ent_store;
struct sim_ent *root = sim_ent_from_handle(store, store->root);
struct sim_ent *camera_ent = sim_ent_nil();
if (player_ent->valid) {
camera_ent = sim_ent_alloc(root);
sim_ent_set_xform(camera_ent, XFORM_IDENT);
sim_ent_enable_prop(camera_ent, SIM_ENT_PROP_CAMERA);
sim_ent_enable_prop(camera_ent, SIM_ENT_PROP_CAMERA_ACTIVE);
camera_ent->camera_follow = player_ent->handle;
f32 width = (f32)DEFAULT_CAMERA_WIDTH; f32 width = (f32)DEFAULT_CAMERA_WIDTH;
f32 height = (f32)DEFAULT_CAMERA_HEIGHT; f32 height = (f32)DEFAULT_CAMERA_HEIGHT;
e->camera_quad_xform = XFORM_TRS(.s = V2(width, height)); camera_ent->camera_quad_xform = XFORM_TRS(.s = V2(width, height));
} }
ctx->extra_spawn = true; return camera_ent;
} }
/* ========================== * /* ========================== *
@ -434,7 +451,7 @@ INTERNAL PHYS_COLLISION_CALLBACK_FUNC_DEF(on_collision, collision_data_array, ud
* Update * Update
* ========================== */ * ========================== */
INTERNAL void sim_update(struct sim_ctx *ctx) INTERNAL void sim_update(struct sim_ctx *ctx, f64 dt)
{ {
__prof; __prof;
@ -448,7 +465,8 @@ INTERNAL void sim_update(struct sim_ctx *ctx)
logf_info("Clearing level"); logf_info("Clearing level");
ctx->should_reset_level = false; ctx->should_reset_level = false;
ctx->extra_spawn = false; ctx->extra_spawn = false;
reset_world(ctx); test_sim_world_release(ctx);
test_sim_world_alloc(ctx);
} }
/* ========================== * /* ========================== *
@ -457,10 +475,9 @@ INTERNAL void sim_update(struct sim_ctx *ctx)
++ctx->tick.tick_id; ++ctx->tick.tick_id;
ctx->tick.dt_ns = NS_FROM_SECONDS(max_f64(0.0, (1.0 / SIM_FPS) * ctx->tick.timescale)); ctx->tick.dt_ns = NS_FROM_SECONDS(max_f64(0.0, dt * ctx->tick.timescale));
ctx->tick.time_ns += ctx->tick.dt_ns; ctx->tick.time_ns += ctx->tick.dt_ns;
f64 dt = SECONDS_FROM_NS(ctx->tick.dt_ns);
f64 time = SECONDS_FROM_NS(ctx->tick.time_ns); f64 time = SECONDS_FROM_NS(ctx->tick.time_ns);
ctx->sprite_frame_scope = sprite_scope_begin(); ctx->sprite_frame_scope = sprite_scope_begin();
ctx->root = sim_ent_from_handle(ctx->tick.ent_store, ctx->tick.ent_store->root); ctx->root = sim_ent_from_handle(ctx->tick.ent_store, ctx->tick.ent_store->root);
@ -544,13 +561,50 @@ INTERNAL void sim_update(struct sim_ctx *ctx)
enum sim_cmd_kind kind = cmd->kind; enum sim_cmd_kind kind = cmd->kind;
struct host_channel_id channel_id = cmd->channel_id; struct host_channel_id channel_id = cmd->channel_id;
struct sim_client *client = sim_client_from_channel_id(ctx->client_store, channel_id); struct sim_client *client = sim_client_from_channel_id(ctx->tick.client_store, channel_id);
if (client->valid || host_channel_id_is_nil(channel_id)) { if (client->valid) {
struct sim_ent *player_ent = sim_ent_from_handle(ctx->tick.ent_store, client->control_ent);
b32 start = cmd->state == SIM_CMD_STATE_START;
b32 stop = cmd->state == SIM_CMD_STATE_STOP;
switch (kind) { switch (kind) {
/* TODO: Combine movement from multiple inputs? E.ctx-> a sudden
* start and immediate stop cmd should still move the player a
* tad. */
case SIM_CMD_KIND_PLAYER_MOVE:
{
if (player_ent->valid) {
struct v2 move = cmd->move_dir;
struct v2 focus = cmd->aim_dir;
if (v2_len_sq(move) > 1) {
/* Cap movement vector magnitude at 1 */
move = v2_norm(move);
}
player_ent->control.move = move;
player_ent->control.focus = focus;
}
} break;
case SIM_CMD_KIND_PLAYER_FIRE:
{
if (player_ent->valid) {
b32 firing = sim_ent_has_prop(player_ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED);
if (start) {
firing = true;
} else if (stop) {
firing = false;
}
if (firing) {
sim_ent_enable_prop(player_ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED);
} else {
sim_ent_disable_prop(player_ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED);
}
}
} break;
/* Cursor */ /* Cursor */
case SIM_CMD_KIND_CURSOR_MOVE: case SIM_CMD_KIND_CURSOR_MOVE:
{ {
ctx->user_cursor = cmd->cursor_pos; client->cursor_pos = cmd->cursor_pos;
} break; } break;
/* Clear level */ /* Clear level */
@ -569,28 +623,84 @@ INTERNAL void sim_update(struct sim_ctx *ctx)
/* Disconnect client */ /* Disconnect client */
case SIM_CMD_KIND_SIM_CLIENT_DISCONNECT: case SIM_CMD_KIND_SIM_CLIENT_DISCONNECT:
{ {
if (client->valid) { if (player_ent->valid) {
struct sim_ent *client_ent = sim_ent_from_handle(ent_store, client->ent); sim_ent_enable_prop(player_ent, SIM_ENT_PROP_RELEASE_NEXT_TICK);
if (client_ent->valid) { host_queue_disconnect(ctx->host, channel_id);
sim_ent_disable_prop(client_ent, SIM_ENT_PROP_PLAYER_CONTROLLED);
sim_ent_enable_prop(client_ent, SIM_ENT_PROP_RELEASE_NEXT_TICK);
host_queue_disconnect(ctx->host, channel_id);
}
sim_client_release(client);
} }
sim_client_release(client);
} break; } break;
default: break; default: break;
}; };
} else if (kind == SIM_CMD_KIND_SIM_CLIENT_CONNECT && !host_channel_id_is_nil(channel_id) && !client->valid) { } else if (kind == SIM_CMD_KIND_SIM_CLIENT_CONNECT && !host_channel_id_is_nil(channel_id) && !client->valid) {
/* Connect client */ /* Connect client */
client = sim_client_alloc(ctx->client_store, channel_id); client = sim_client_alloc(ctx->tick.client_store, channel_id);
struct sim_ent *client_ent = sim_ent_alloc(root); struct sim_ent *player_ent = spawn_test_player(ctx);
sim_ent_enable_prop(client_ent, SIM_ENT_PROP_PLAYER_CONTROLLED); sim_ent_enable_prop(player_ent, SIM_ENT_PROP_CONTROLLED);
client_ent->controlling_client = client->handle; struct sim_ent *camera_ent = spawn_test_player_camera(ctx, player_ent);
client->control_ent = player_ent->handle;
client->camera_ent = camera_ent->handle;;
player_ent->controlling_client = client->handle;
} }
} }
#if 0
/* ========================== *
* Update entity movement from control
* ========================== */
for (u64 ent_index = 0; ent_index < ent_store->num_reserved; ++ent_index) {
struct sim_ent *ent = &ent_store->entities[ent_index];
if (!sim_ent_is_valid_and_active(ent)) continue;
if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) {
/* Process cmds */
struct v2 move = ent->control.move;
struct v2 focus = ent->control.focus;
b32 firing = sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED);
for (struct sim_cmd *cmd = sim_cmds.first; cmd; cmd = cmd->next) {
/* TODO: Combine movement from multiple inputs? E.ctx-> a sudden
* start and immediate stop cmd should still move the player a
* tad. */
switch (cmd->kind) {
case SIM_CMD_KIND_PLAYER_MOVE:
{
move = cmd->move_dir;
focus = cmd->aim_dir;
} break;
case SIM_CMD_KIND_PLAYER_FIRE:
{
if (start) {
firing = true;
} else if (stop) {
firing = false;
}
} break;
default: break;
}
}
/* Movement */
if (v2_len_sq(move) > 1) {
/* Cap movement vector magnitude at 1 */
move = v2_norm(move);
}
ent->control.move = move;
ent->control.focus = focus;
/* Firing */
if (firing) {
sim_ent_enable_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED);
} else {
sim_ent_disable_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED);
}
}
}
#endif
/* ========================== * /* ========================== *
* Update entities from sprite * Update entities from sprite
* ========================== */ * ========================== */
@ -711,64 +821,6 @@ INTERNAL void sim_update(struct sim_ctx *ctx)
sim_ent_set_local_xform(ent, xf); sim_ent_set_local_xform(ent, xf);
} }
/* ========================== *
* Update control from player cmds
* ========================== */
for (u64 ent_index = 0; ent_index < ent_store->num_reserved; ++ent_index) {
struct sim_ent *ent = &ent_store->entities[ent_index];
if (!sim_ent_is_valid_and_active(ent)) continue;
if (sim_ent_has_prop(ent, SIM_ENT_PROP_PLAYER_CONTROLLED)) {
/* Process cmds */
struct v2 move = ent->control.move;
struct v2 focus = ent->control.focus;
b32 firing = sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED);
for (struct sim_cmd *cmd = sim_cmds.first; cmd; cmd = cmd->next) {
b32 start = cmd->state == SIM_CMD_STATE_START;
b32 stop = cmd->state == SIM_CMD_STATE_STOP;
/* TODO: Combine movement from multiple inputs? E.ctx-> a sudden
* start and immediate stop cmd should still move the player a
* tad. */
switch (cmd->kind) {
case SIM_CMD_KIND_PLAYER_MOVE:
{
move = cmd->move_dir;
focus = cmd->aim_dir;
} break;
case SIM_CMD_KIND_PLAYER_FIRE:
{
if (start) {
firing = true;
} else if (stop) {
firing = false;
}
} break;
default: break;
}
}
/* Movement */
if (v2_len_sq(move) > 1) {
/* Cap movement vector magnitude at 1 */
move = v2_norm(move);
}
ent->control.move = move;
ent->control.focus = focus;
/* Firing */
if (firing) {
sim_ent_enable_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED);
} else {
sim_ent_disable_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED);
}
}
}
/* ========================== * /* ========================== *
* Test * Test
* ========================== */ * ========================== */
@ -919,7 +971,7 @@ INTERNAL void sim_update(struct sim_ctx *ctx)
struct sim_ent *ent = &ent_store->entities[ent_index]; struct sim_ent *ent = &ent_store->entities[ent_index];
if (!sim_ent_is_valid_and_active(ent)) continue; if (!sim_ent_is_valid_and_active(ent)) continue;
if (sim_ent_has_prop(ent, SIM_ENT_PROP_PLAYER_CONTROLLED)) { if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) {
struct sim_ent *joint_ent = sim_ent_from_handle(ent_store, ent->move_joint); struct sim_ent *joint_ent = sim_ent_from_handle(ent_store, ent->move_joint);
if (!sim_ent_is_valid_and_active(joint_ent)) { if (!sim_ent_is_valid_and_active(joint_ent)) {
joint_ent = sim_ent_alloc(root); joint_ent = sim_ent_alloc(root);
@ -953,7 +1005,7 @@ INTERNAL void sim_update(struct sim_ctx *ctx)
struct sim_ent *ent = &ent_store->entities[ent_index]; struct sim_ent *ent = &ent_store->entities[ent_index];
if (!sim_ent_is_valid_and_active(ent)) continue; if (!sim_ent_is_valid_and_active(ent)) continue;
if (sim_ent_has_prop(ent, SIM_ENT_PROP_PLAYER_CONTROLLED)) { if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) {
struct xform xf = sim_ent_get_xform(ent); struct xform xf = sim_ent_get_xform(ent);
struct xform sprite_xf = xform_mul(xf, ent->sprite_local_xform); struct xform sprite_xf = xform_mul(xf, ent->sprite_local_xform);
@ -1082,6 +1134,7 @@ INTERNAL void sim_update(struct sim_ctx *ctx)
phys.debug_lookup = &phys->collision_debug_lookup; phys.debug_lookup = &phys->collision_debug_lookup;
#endif #endif
#if 0
/* Mouse drag */ /* Mouse drag */
phys.dbg_cursor_pos = ctx->user_cursor; phys.dbg_cursor_pos = ctx->user_cursor;
for (struct sim_cmd *cmd = sim_cmds.first; cmd; cmd = cmd->next) { for (struct sim_cmd *cmd = sim_cmds.first; cmd; cmd = cmd->next) {
@ -1093,6 +1146,7 @@ INTERNAL void sim_update(struct sim_ctx *ctx)
} }
} }
} }
#endif
/* Step */ /* Step */
ctx->last_phys_iteration = phys_step(&phys, dt, ctx->last_phys_iteration); ctx->last_phys_iteration = phys_step(&phys, dt, ctx->last_phys_iteration);
@ -1318,22 +1372,26 @@ INTERNAL void sim_update(struct sim_ctx *ctx)
* Publish tick * Publish tick
* ========================== */ * ========================== */
{ for (u64 i = 0; i < ctx->tick.client_store->num_reserved; ++i) {
struct temp_arena temp = arena_temp_begin(scratch.arena); struct sim_client *client = &ctx->tick.client_store->clients[i];
if (client->valid) {
struct temp_arena temp = arena_temp_begin(scratch.arena);
/* TODO: Not like this */ /* TODO: Not like this */
struct sim_event snapshot_event = ZI; struct sim_event snapshot_event = ZI;
snapshot_event.kind = SIM_EVENT_KIND_SNAPSHOT_FULL; snapshot_event.kind = SIM_EVENT_KIND_SNAPSHOT;
snapshot_event.snapshot_data = sim_string_from_tick(temp.arena, &ctx->tick); snapshot_event.snapshot_data = sim_string_from_tick(temp.arena, client, &ctx->tick);
struct sim_event_list l = ZI; struct sim_event_list l = ZI;
l.first = &snapshot_event; l.first = &snapshot_event;
l.last = &snapshot_event; l.last = &snapshot_event;
struct string msg = sim_string_from_events(temp.arena, l); struct string msg = sim_string_from_events(temp.arena, l);
host_queue_write(ctx->host, HOST_CHANNEL_ID_ALL, msg, 0); host_queue_write(ctx->host, client->channel_id, msg, 0);
//host_queue_write(ctx->host, HOST_CHANNEL_ID_ALL, msg, 0);
arena_temp_end(temp); arena_temp_end(temp);
}
} }
host_update(ctx->host); host_update(ctx->host);
@ -1361,6 +1419,6 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sim_thread_entry_point, arg)
__profscope(sim_update_w_sleep); __profscope(sim_update_w_sleep);
sleep_frame(last_frame_ns, target_dt_ns); sleep_frame(last_frame_ns, target_dt_ns);
last_frame_ns = sys_time_ns(); last_frame_ns = sys_time_ns();
sim_update(ctx); sim_update(ctx, (1.0 / SIM_FPS));
} }
} }

View File

@ -8,11 +8,15 @@
/* Offset in bytes from start of store struct to start of clients array (assume adjacently allocated) */ /* Offset in bytes from start of store struct to start of clients array (assume adjacently allocated) */
#define STORE_CLIENTS_OFFSET (sizeof(struct sim_client_store) + (sizeof(struct sim_client_store) % alignof(struct sim_client))) #define STORE_CLIENTS_OFFSET (sizeof(struct sim_client_store) + (sizeof(struct sim_client_store) % alignof(struct sim_client)))
/* Accessed via client_nil() */ /* Accessed via client_nil() */
READONLY struct sim_client _g_sim_client_nil = { .valid = false }; READONLY struct sim_client _g_sim_client_nil = { .valid = false };
READONLY struct sim_client_store _g_sim_client_store_nil = { .valid = false }; READONLY struct sim_client_store _g_sim_client_store_nil = { .valid = false };
struct sim_client_channel_lookup_bucket {
struct sim_client *first;
struct sim_client *last;
};
/* ========================== * /* ========================== *
* Store * Store
* ========================== */ * ========================== */
@ -21,7 +25,7 @@ struct sim_client_store *sim_client_store_alloc(void)
{ {
struct arena arena = arena_alloc(GIGABYTE(64)); struct arena arena = arena_alloc(GIGABYTE(64));
u64 num_channel_lookup_buckets = CHANNEL_LOOKUP_BUCKETS; u64 num_channel_lookup_buckets = CHANNEL_LOOKUP_BUCKETS;
u64 channel_lookup_buckets = arena_push_array_zero(&arena, struct channel_lookup_bucket, num_channel_lookup_buckets); u64 channel_lookup_buckets = arena_push_array_zero(&arena, struct sim_client_channel_lookup_bucket, num_channel_lookup_buckets);
struct sim_client_store *store = arena_push_zero(&arena, struct sim_client_store); struct sim_client_store *store = arena_push_zero(&arena, struct sim_client_store);
store->clients = arena_dry_push(&arena, struct sim_client); store->clients = arena_dry_push(&arena, struct sim_client);
@ -60,9 +64,9 @@ INTERNAL u64 hash_from_channel_id(struct host_channel_id channel_id)
return hash_fnv64(HASH_FNV64_BASIS, STRING_FROM_STRUCT(&channel_id)); return hash_fnv64(HASH_FNV64_BASIS, STRING_FROM_STRUCT(&channel_id));
} }
struct sim_client *sim_client_from_handle(struct sim_client_store *store, struct client_handle handle) struct sim_client *sim_client_from_handle(struct sim_client_store *store, struct sim_client_handle handle)
{ {
if (handle.gen != 0 && handle.idx < store->clients_reserved) { if (handle.gen != 0 && handle.idx < store->num_reserved) {
struct sim_client *client = &store->clients[handle.idx]; struct sim_client *client = &store->clients[handle.idx];
if (client->handle.gen == handle.gen) { if (client->handle.gen == handle.gen) {
return client; return client;
@ -76,7 +80,7 @@ struct sim_client *sim_client_from_channel_id(struct sim_client_store *store, st
struct sim_client *res = client_nil(); struct sim_client *res = client_nil();
u64 channel_hash = hash_from_channel_id(channel_id); u64 channel_hash = hash_from_channel_id(channel_id);
u64 bucket_index = channel_hash % store->num_channel_lookup_buckets; u64 bucket_index = channel_hash % store->num_channel_lookup_buckets;
struct channel_lookup_bucket *bucket = &store->channel_lookup_buckets[bucket_index]; struct sim_client_channel_lookup_bucket *bucket = &store->channel_lookup_buckets[bucket_index];
for (struct sim_client *client = bucket->first; client; client = client->next_hash) { for (struct sim_client *client = bucket->first; client; client = client->next_hash) {
if (client->channel_hash == channel_hash) { if (client->channel_hash == channel_hash) {
res = client; res = client;
@ -89,7 +93,7 @@ struct sim_client *sim_client_from_channel_id(struct sim_client_store *store, st
struct sim_client *sim_client_alloc(struct sim_client_store *store, struct host_channel_id channel_id) struct sim_client *sim_client_alloc(struct sim_client_store *store, struct host_channel_id channel_id)
{ {
struct sim_client *client = NULL; struct sim_client *client = NULL;
struct client_handle handle = ZI; struct sim_client_handle handle = ZI;
if (store->first_free_client) { if (store->first_free_client) {
client = store->first_free_client; client = store->first_free_client;
store->first_free_client = client->next_free; store->first_free_client = client->next_free;
@ -98,9 +102,10 @@ struct sim_client *sim_client_alloc(struct sim_client_store *store, struct host_
} else { } else {
client = arena_push(&store->arena, struct sim_client); client = arena_push(&store->arena, struct sim_client);
handle.gen = 1; handle.gen = 1;
handle.idx = store->clients_reserved; handle.idx = store->num_reserved;
++store->clients_reserved; ++store->num_reserved;
} }
++store->num_allocated;
*client = _g_sim_client_nil; *client = _g_sim_client_nil;
client->valid = true; client->valid = true;
client->handle = handle; client->handle = handle;
@ -111,7 +116,7 @@ struct sim_client *sim_client_alloc(struct sim_client_store *store, struct host_
/* Insert into channel lookup */ /* Insert into channel lookup */
u64 bucket_index = channel_hash % store->num_channel_lookup_buckets; u64 bucket_index = channel_hash % store->num_channel_lookup_buckets;
struct channel_lookup_bucket *bucket = &store->channel_lookup_buckets[bucket_index]; struct sim_client_channel_lookup_bucket *bucket = &store->channel_lookup_buckets[bucket_index];
if (bucket->last) { if (bucket->last) {
bucket->last->next_hash = client; bucket->last->next_hash = client;
client->prev_hash = bucket->last; client->prev_hash = bucket->last;
@ -129,10 +134,11 @@ void sim_client_release(struct sim_client *client)
++client->handle.gen; ++client->handle.gen;
client->next_free = store->first_free_client; client->next_free = store->first_free_client;
store->first_free_client = client; store->first_free_client = client;
--store->num_allocated;
/* Remove from channel lookup */ /* Remove from channel lookup */
u64 bucket_index = client->channel_hash % store->num_channel_lookup_buckets; u64 bucket_index = client->channel_hash % store->num_channel_lookup_buckets;
struct channel_lookup_bucket *bucket = &store->channel_lookup_buckets[bucket_index]; struct sim_client_channel_lookup_bucket *bucket = &store->channel_lookup_buckets[bucket_index];
struct sim_client *prev = client->prev_hash; struct sim_client *prev = client->prev_hash;
struct sim_client *next = client->next_hash; struct sim_client *next = client->next_hash;
if (prev) { if (prev) {

View File

@ -1,15 +1,16 @@
#ifndef SIM_CLIENT_H #ifndef SIM_CLIENT_H
#define SIM_CLIENT_H #define SIM_CLIENT_H
struct client_handle { struct sim_client_channel_lookup_bucket;
b32 valid;
struct sim_client_handle {
u32 idx; u32 idx;
u32 gen; u32 gen;
}; };
struct sim_client { struct sim_client {
b32 valid; b32 valid;
struct client_handle handle; struct sim_client_handle handle;
struct host_channel_id channel_id; struct host_channel_id channel_id;
u64 channel_hash; u64 channel_hash;
@ -18,24 +19,22 @@ struct sim_client {
struct sim_client *next_hash; struct sim_client *next_hash;
struct sim_client *prev_hash; struct sim_client *prev_hash;
struct sim_ent_handle ent; struct v2 cursor_pos;
}; struct sim_ent_handle camera_ent;
struct sim_ent_handle control_ent;
struct channel_lookup_bucket {
struct sim_client *first;
struct sim_client *last;
}; };
struct sim_client_store { struct sim_client_store {
b32 valid; b32 valid;
struct arena arena; struct arena arena;
struct channel_lookup_bucket *channel_lookup_buckets; struct sim_client_channel_lookup_bucket *channel_lookup_buckets;
u64 num_channel_lookup_buckets; u64 num_channel_lookup_buckets;
struct sim_client *clients; struct sim_client *clients;
struct sim_client *first_free_client; struct sim_client *first_free_client;
u64 clients_reserved; u64 num_allocated;
u64 num_reserved;
}; };
INLINE struct sim_client *client_nil(void) INLINE struct sim_client *client_nil(void)
@ -53,7 +52,7 @@ INLINE struct sim_client_store *client_store_nil(void)
struct sim_client_store *sim_client_store_alloc(void); struct sim_client_store *sim_client_store_alloc(void);
void sim_client_store_release(struct sim_client_store *store); void sim_client_store_release(struct sim_client_store *store);
struct sim_client_store *client_store_from_client(struct sim_client *client); struct sim_client_store *client_store_from_client(struct sim_client *client);
struct sim_client *sim_client_from_handle(struct sim_client_store *store, struct client_handle handle); struct sim_client *sim_client_from_handle(struct sim_client_store *store, struct sim_client_handle handle);
struct sim_client *sim_client_from_channel_id(struct sim_client_store *store, struct host_channel_id channel_id); struct sim_client *sim_client_from_channel_id(struct sim_client_store *store, struct host_channel_id channel_id);
struct sim_client *sim_client_alloc(struct sim_client_store *store, struct host_channel_id channel_id); struct sim_client *sim_client_alloc(struct sim_client_store *store, struct host_channel_id channel_id);
void sim_client_release(struct sim_client *client); void sim_client_release(struct sim_client *client);

View File

@ -17,13 +17,14 @@ enum sim_ent_prop {
SIM_ENT_PROP_PHYSICAL_DYNAMIC, SIM_ENT_PROP_PHYSICAL_DYNAMIC,
SIM_ENT_PROP_PHYSICAL_KINEMATIC, SIM_ENT_PROP_PHYSICAL_KINEMATIC,
SIM_ENT_PROP_CONTROLLED,
SIM_ENT_PROP_COLLISION_DEBUG, SIM_ENT_PROP_COLLISION_DEBUG,
SIM_ENT_PROP_CONTACT_CONSTRAINT, SIM_ENT_PROP_CONTACT_CONSTRAINT,
SIM_ENT_PROP_MOTOR_JOINT, SIM_ENT_PROP_MOTOR_JOINT,
SIM_ENT_PROP_MOUSE_JOINT, SIM_ENT_PROP_MOUSE_JOINT,
SIM_ENT_PROP_SENSOR, SIM_ENT_PROP_SENSOR,
SIM_ENT_PROP_PLAYER_CONTROLLED,
SIM_ENT_PROP_CAMERA, SIM_ENT_PROP_CAMERA,
SIM_ENT_PROP_CAMERA_ACTIVE, SIM_ENT_PROP_CAMERA_ACTIVE,
@ -155,15 +156,13 @@ struct sim_ent {
/* SIM_ENT_PROP_MOUSE_JOINT */ /* SIM_ENT_PROP_MOUSE_JOINT */
struct phys_mouse_joint mouse_joint_data; struct phys_mouse_joint mouse_joint_data;
/* ====================================================================== */
/* Player */
/* SIM_ENT_PROP_PLAYER_CONTROLLED */
struct client_handle controlling_client;
/* ====================================================================== */ /* ====================================================================== */
/* Control */ /* Control */
/* SIM_ENT_PROP_CONTROLLED */
struct sim_client_handle controlling_client;
f32 control_force; /* How much force is applied to achieve desired control movement */ f32 control_force; /* How much force is applied to achieve desired control movement */
f32 control_force_max_speed; /* Maximum linear velocity achieved by force (m/s) */ f32 control_force_max_speed; /* Maximum linear velocity achieved by force (m/s) */

View File

@ -86,6 +86,7 @@ void sim_cmds_from_host_events(struct arena *arena, struct host_event_array host
u64 cmd_size = br_read_u64(&br); u64 cmd_size = br_read_u64(&br);
u64 cmd_pos_end = br_pos(&br) + cmd_size; u64 cmd_pos_end = br_pos(&br) + cmd_size;
cmd->kind = br_read_i8(&br); cmd->kind = br_read_i8(&br);
cmd->channel_id = host_event.channel_id;
cmd->state = br_read_i8(&br); cmd->state = br_read_i8(&br);
#if COLLIDER_DEBUG #if COLLIDER_DEBUG
cmd->collider_gjk_steps = br_read_u32(&br); cmd->collider_gjk_steps = br_read_u32(&br);
@ -137,7 +138,7 @@ struct string sim_string_from_events(struct arena *arena, struct sim_event_list
bw_write_i8(&bw, event->kind); bw_write_i8(&bw, event->kind);
switch (event->kind) { switch (event->kind) {
case SIM_EVENT_KIND_SNAPSHOT_FULL: case SIM_EVENT_KIND_SNAPSHOT:
{ {
bw_write_string(&bw, event->snapshot_data); bw_write_string(&bw, event->snapshot_data);
} break; } break;
@ -180,7 +181,7 @@ void sim_events_from_host_events(struct arena *arena, struct host_event_array ho
sim_event->kind = br_read_i8(&br); sim_event->kind = br_read_i8(&br);
switch (sim_event->kind) { switch (sim_event->kind) {
case SIM_EVENT_KIND_SNAPSHOT_FULL: case SIM_EVENT_KIND_SNAPSHOT:
{ {
sim_event->snapshot_data = br_read_string(arena, &br); sim_event->snapshot_data = br_read_string(arena, &br);
} break; } break;
@ -209,7 +210,7 @@ void sim_events_from_host_events(struct arena *arena, struct host_event_array ho
* Snapshot * Snapshot
* ========================== */ * ========================== */
struct string sim_string_from_tick(struct arena *arena, struct world *tick) struct string sim_string_from_tick(struct arena *arena, struct sim_client *client, struct world *tick)
{ {
__prof; __prof;
struct byte_writer bw = bw_from_arena(arena); struct byte_writer bw = bw_from_arena(arena);
@ -222,6 +223,19 @@ struct string sim_string_from_tick(struct arena *arena, struct world *tick)
bw_write_var_sint(&bw, tick->dt_ns); bw_write_var_sint(&bw, tick->dt_ns);
bw_write_var_sint(&bw, tick->time_ns); bw_write_var_sint(&bw, tick->time_ns);
bw_write_var_uint(&bw, client->handle.gen);
bw_write_var_uint(&bw, client->handle.idx);
/* Client store */
u64 num_clients = tick->client_store->num_reserved;
bw_write_var_uint(&bw, num_clients);
struct string clients_src = ZI;
clients_src.text = (u8 *)tick->client_store->clients;
clients_src.len = sizeof(struct sim_client) * num_clients;
br_write_bytes(&bw, clients_src);
/* Entity store */
u64 num_entities = tick->ent_store->num_reserved; u64 num_entities = tick->ent_store->num_reserved;
bw_write_var_uint(&bw, num_entities); bw_write_var_uint(&bw, num_entities);
@ -246,6 +260,28 @@ void sim_tick_from_string(struct string str, struct world *tick_out)
tick_out->dt_ns = br_read_var_sint(&br); tick_out->dt_ns = br_read_var_sint(&br);
tick_out->time_ns = br_read_var_sint(&br); tick_out->time_ns = br_read_var_sint(&br);
tick_out->local_client.gen = br_read_var_uint(&br);
tick_out->local_client.idx = br_read_var_uint(&br);
/* Client store */
u64 num_clients = br_read_var_uint(&br);
arena_push_array(&tick_out->client_store->arena, struct sim_client, num_clients - tick_out->client_store->num_reserved);
tick_out->client_store->num_reserved = num_clients;
tick_out->client_store->num_allocated = 0;
struct sim_client *clients_src = br_seek(&br, num_clients * sizeof(struct sim_client));
if (clients_src) {
for (u64 i = 0; i < num_clients; ++i) {
struct sim_client *src = &clients_src[i];
struct sim_client *dst = &tick_out->client_store->clients[i];
if (dst->valid) {
++tick_out->client_store->num_allocated;
}
*dst = *src;
}
}
/* Entity store */
u64 num_entities = br_read_var_uint(&br); u64 num_entities = br_read_var_uint(&br);
arena_push_array(&tick_out->ent_store->arena, struct sim_ent, num_entities - tick_out->ent_store->num_reserved); arena_push_array(&tick_out->ent_store->arena, struct sim_ent, num_entities - tick_out->ent_store->num_reserved);
tick_out->ent_store->num_reserved = num_entities; tick_out->ent_store->num_reserved = num_entities;

View File

@ -4,6 +4,7 @@
#include "host.h" #include "host.h"
struct world; struct world;
struct sim_client;
/* ========================== * /* ========================== *
* Sim cmd * Sim cmd
@ -74,7 +75,7 @@ enum sim_event_kind {
SIM_EVENT_KIND_CONNECT, SIM_EVENT_KIND_CONNECT,
SIM_EVENT_KIND_DISCONNECT, SIM_EVENT_KIND_DISCONNECT,
SIM_EVENT_KIND_SNAPSHOT_FULL, SIM_EVENT_KIND_SNAPSHOT,
//SIM_EVENT_KIND_ENTITY_UPDATE, //SIM_EVENT_KIND_ENTITY_UPDATE,
//SIM_EVENT_KIND_ENTITY_CREATE, //SIM_EVENT_KIND_ENTITY_CREATE,
@ -85,11 +86,10 @@ struct sim_event {
enum sim_event_kind kind; enum sim_event_kind kind;
struct host_channel_id channel_id; struct host_channel_id channel_id;
struct string snapshot_data;
struct string disconnect_reason; struct string disconnect_reason;
//struct sim_ent_handle entity; /* SIM_EVENT_KIND_SNAPSHOT */
//struct string update_data; struct string snapshot_data;
struct sim_event *next; struct sim_event *next;
}; };
@ -106,7 +106,7 @@ void sim_events_from_host_events(struct arena *arena, struct host_event_array ho
* Snapshot * Snapshot
* ========================== */ * ========================== */
struct string sim_string_from_tick(struct arena *arena, struct world *tick); struct string sim_string_from_tick(struct arena *arena, struct sim_client *client, struct world *tick);
void sim_tick_from_string(struct string str, struct world *tick_out); void sim_tick_from_string(struct string str, struct world *tick_out);
#endif #endif

View File

@ -25,7 +25,7 @@ struct sock_address {
}; };
struct sock_read_result { struct sock_read_result {
b32 valid; b32 valid; /* Since data.len = 0 can be valid */
struct sock_address address; struct sock_address address;
struct string data; struct string data;
}; };
@ -44,6 +44,9 @@ INLINE b32 sock_address_eq(struct sock_address a, struct sock_address b)
struct sock *sock_alloc(u16 listen_port, u64 sndbuf_size, u64 rcvbuf_size); struct sock *sock_alloc(u16 listen_port, u64 sndbuf_size, u64 rcvbuf_size);
void sock_release(struct sock *sock); void sock_release(struct sock *sock);
/* Wake anyone blocking on sock read */
void sock_wake(struct sock *sock);
struct sock *sock_wait_for_available_read(struct sock_array socks, struct sock_signal *signal, f32 timeout); struct sock *sock_wait_for_available_read(struct sock_array socks, struct sock_signal *signal, f32 timeout);
struct sock_read_result sock_read(struct sock *sock, struct string read_buff); struct sock_read_result sock_read(struct sock *sock, struct string read_buff);
void sock_write(struct sock *sock, struct sock_address address, struct string data); void sock_write(struct sock *sock, struct sock_address address, struct string data);

View File

@ -5,6 +5,8 @@
#include "scratch.h" #include "scratch.h"
#include "string.h" #include "string.h"
#include "log.h" #include "log.h"
#include "app.h"
#include "atomic.h"
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#define UNICODE #define UNICODE
@ -18,7 +20,7 @@
#define MAX_POLL_FDS 64 #define MAX_POLL_FDS 64
struct winsock_address { struct win32_address {
i32 size; i32 size;
i32 family; i32 family;
union { union {
@ -29,15 +31,32 @@ struct winsock_address {
}; };
}; };
struct win32_sock {
SOCKET sock;
struct win32_sock *next_free;
};
/* ========================== *
* Global state
* ========================== */
GLOBAL struct {
WSADATA wsa_data;
struct arena win32_socks_arena;
struct sys_mutex win32_socks_mutex;
struct win32_sock *first_free_win32_sock;
} G = ZI, DEBUG_ALIAS(G, G_sock_win32);
/* ========================== * /* ========================== *
* Startup * Startup
* ========================== */ * ========================== */
struct sock_startup_receipt sock_startup(void) struct sock_startup_receipt sock_startup(void)
{ {
WSADATA wsa_data;
/* Startup winsock */ /* Startup winsock */
WSAStartup(MAKEWORD(2, 2), &wsa_data); WSAStartup(MAKEWORD(2, 2), &G.wsa_data);
G.win32_socks_arena = arena_alloc(GIGABYTE(64));
G.win32_socks_mutex = sys_mutex_alloc();
return (struct sock_startup_receipt) { 0 }; return (struct sock_startup_receipt) { 0 };
} }
@ -203,9 +222,9 @@ struct string sock_string_from_address(struct arena *arena, struct sock_address
return res; return res;
} }
INTERNAL struct winsock_address winsock_address_from_sock_address(struct sock_address addr) INTERNAL struct win32_address win32_address_from_sock_address(struct sock_address addr)
{ {
struct winsock_address res = ZI; struct win32_address res = ZI;
if (addr.family == SOCK_ADDRESS_FAMILY_IPV4) { if (addr.family == SOCK_ADDRESS_FAMILY_IPV4) {
res.family = AF_INET; res.family = AF_INET;
res.size = sizeof(struct sockaddr_in); res.size = sizeof(struct sockaddr_in);
@ -222,7 +241,39 @@ INTERNAL struct winsock_address winsock_address_from_sock_address(struct sock_ad
return res; return res;
} }
INTERNAL struct sock_address sock_address_from_winsock_address(struct winsock_address ws_addr) /* If supplied address has ip INADDR_ANY (0), convert ip to localhost */
INTERNAL struct win32_address win32_address_convert_any_to_localhost(struct win32_address addr)
{
if (addr.family == AF_INET) {
u8 *bytes = (u8 *)&addr.sin.sin_addr;
b32 is_any = true;
for (u64 i = 0; i < 4; ++i) {
if (bytes[i] != 0) {
is_any = false;
break;
}
}
if (is_any) {
bytes[0] = 127;
bytes[3] = 1;
}
} else if (addr.family == AF_INET6) {
u8 *bytes = (u8 *)&addr.sin.sin_addr;
b32 is_any = true;
for (u64 i = 0; i < 16; ++i) {
if (bytes[i] != 0) {
is_any = false;
break;
}
}
if (is_any) {
bytes[15] = 1;
}
}
return addr;
}
INTERNAL struct sock_address sock_address_from_win32_address(struct win32_address ws_addr)
{ {
struct sock_address res = ZI; struct sock_address res = ZI;
if (ws_addr.family == AF_INET) { if (ws_addr.family == AF_INET) {
@ -240,38 +291,80 @@ INTERNAL struct sock_address sock_address_from_winsock_address(struct winsock_ad
} }
/* ========================== * /* ========================== *
* Alloc * Sock
* ========================== */ * ========================== */
INTERNAL struct win32_sock *win32_sock_alloc(void)
{
struct win32_sock *ws = NULL;
{
struct sys_lock lock = sys_mutex_lock_e(&G.win32_socks_mutex);
if (G.first_free_win32_sock) {
ws = G.first_free_win32_sock;
G.first_free_win32_sock = ws->next_free;
} else {
ws = arena_push(&G.win32_socks_arena, struct win32_sock);
}
sys_mutex_unlock(&lock);
}
MEMZERO_STRUCT(ws);
return ws;
}
INTERNAL void win32_sock_release(struct win32_sock *ws)
{
struct sys_lock lock = sys_mutex_lock_e(&G.win32_socks_mutex);
ws->next_free = G.first_free_win32_sock;
G.first_free_win32_sock = ws;
sys_mutex_unlock(&lock);
}
struct sock *sock_alloc(u16 listen_port, u64 sndbuf_size, u64 rcvbuf_size) struct sock *sock_alloc(u16 listen_port, u64 sndbuf_size, u64 rcvbuf_size)
{ {
struct win32_sock *ws = win32_sock_alloc();
struct sock_address addr = sock_address_from_port(listen_port); struct sock_address addr = sock_address_from_port(listen_port);
struct winsock_address ws_addr = winsock_address_from_sock_address(addr); struct win32_address bind_address = win32_address_from_sock_address(addr);
ws->sock = socket(bind_address.family, SOCK_DGRAM, IPPROTO_UDP);
SOCKET ws = socket(ws_addr.family, SOCK_DGRAM, IPPROTO_UDP);
{ {
i32 sb = sndbuf_size; i32 sb = sndbuf_size;
i32 rb = rcvbuf_size; i32 rb = rcvbuf_size;
setsockopt(ws, SOL_SOCKET, SO_SNDBUF, (char *)&sb, sizeof(sb)); setsockopt(ws->sock, SOL_SOCKET, SO_SNDBUF, (char *)&sb, sizeof(sb));
setsockopt(ws, SOL_SOCKET, SO_RCVBUF, (char *)&rb, sizeof(rb)); setsockopt(ws->sock, SOL_SOCKET, SO_RCVBUF, (char *)&rb, sizeof(rb));
} }
#if 0 bind(ws->sock, &bind_address.sa, bind_address.size);
if (listen_port != 0) {
bind(ws, &ws_addr.sa, ws_addr.size);
}
#else
bind(ws, &ws_addr.sa, ws_addr.size);
#endif
return (struct sock *)ws; return (struct sock *)ws;
} }
void sock_release(struct sock *sock) void sock_release(struct sock *sock)
{ {
SOCKET ws = (SOCKET)sock; struct win32_sock *ws = (struct win32_sock *)sock;
closesocket(ws); closesocket(ws->sock);
win32_sock_release(ws);
}
/* Send an empty dummy packet to wake anyone blocking on read (dumb hack since
* winsock doesn't have eventfd).
*
* TODO: Use WSAEvent and WSAWaitForMultipleEvents instead */
void sock_wake(struct sock *sock)
{
struct win32_sock *ws = (struct win32_sock *)sock;
/* Get bound address as localhost so we can write to it (if bound to INADDR_ANY) */
struct win32_address bind_address = ZI;
{
i32 len = sizeof(bind_address.sas);
getsockname(ws->sock, &bind_address.sa, &len);
bind_address.family = bind_address.sin.sin_family;
bind_address.size = len;
bind_address = win32_address_convert_any_to_localhost(bind_address);
}
/* Have sock send an empty dummy packet to itself to signal read available */
sendto(ws->sock, "", 0, 0, &bind_address.sa, bind_address.size);
} }
/* ========================== * /* ========================== *
@ -280,27 +373,14 @@ void sock_release(struct sock *sock)
struct sock *sock_wait_for_available_read(struct sock_array socks, struct sock_signal *signal, f32 timeout) struct sock *sock_wait_for_available_read(struct sock_array socks, struct sock_signal *signal, f32 timeout)
{ {
struct temp_arena scratch = scratch_begin_no_conflict();
struct sock *res = NULL; struct sock *res = NULL;
u64 num_fds = socks.count + (signal != NULL); u64 num_fds = socks.count + (signal != NULL);
WSAPOLLFD fds[MAX_POLL_FDS] = ZI; WSAPOLLFD fds[MAX_POLL_FDS] = ZI;
{ for (u32 i = 0; i < socks.count; ++i) {
u32 fd_i = 0; struct win32_sock *ws = (struct win32_sock *)socks.socks[i];
if (signal != NULL) { fds[i].fd = ws->sock;
fds[fd_i].fd = (SOCKET)signal; fds[i].events = POLLRDNORM;
fds[fd_i].events = POLLRDNORM;
++fd_i;
}
for (u32 i = 0; i < socks.count; ++i) {
if (fd_i >= ARRAY_COUNT(fds)) {
ASSERT(false);
break;
}
fds[fd_i].fd = (SOCKET)socks.socks[i];
fds[fd_i].events = POLLRDNORM;
++fd_i;
}
} }
i32 timeout_ms; i32 timeout_ms;
@ -311,43 +391,42 @@ struct sock *sock_wait_for_available_read(struct sock_array socks, struct sock_s
} }
WSAPoll(fds, num_fds, timeout_ms); WSAPoll(fds, num_fds, timeout_ms);
b32 none = true;
for (u64 i = 0; i < num_fds; ++i) { for (u64 i = 0; i < num_fds; ++i) {
if (fds[i].revents & POLLRDNORM) { if (fds[i].revents & POLLRDNORM) {
if (i < socks.count) { res = socks.socks[i];
res = socks.socks[i]; none = false;
break; break;
} else {
/* FIXME: Return signal */
ASSERT(false);
break;
}
} }
} }
scratch_end(scratch); if (none) {
DEBUGBREAKABLE;
}
return res; return res;
} }
struct sock_read_result sock_read(struct sock *sock, struct string read_buff) struct sock_read_result sock_read(struct sock *sock, struct string read_buff)
{ {
SOCKET ws = (SOCKET)sock; struct win32_sock *ws = (struct win32_sock *)sock;
struct sock_read_result res = ZI; struct sock_read_result res = ZI;
struct winsock_address ws_addr = ZI; struct win32_address ws_addr = ZI;
ws_addr.size = sizeof(ws_addr.sas); ws_addr.size = sizeof(ws_addr.sas);
i32 size = recvfrom(ws, (char *)read_buff.text, read_buff.len, 0, &ws_addr.sa, &ws_addr.size); i32 size = recvfrom(ws->sock, (char *)read_buff.text, read_buff.len, 0, &ws_addr.sa, &ws_addr.size);
ws_addr.family = ws_addr.sin.sin_family; ws_addr.family = ws_addr.sin.sin_family;
res.address = sock_address_from_winsock_address(ws_addr); res.address = sock_address_from_win32_address(ws_addr);
if (size > 0) { if (size >= 0) {
atomic_u64_eval_add_u64(&app_statistics()->sock_bytes_sent, size);
res.data.text = read_buff.text; res.data.text = read_buff.text;
res.data.len = size; res.data.len = size;
res.valid = true; res.valid = true;
} else { } else {
#if RTC #if RTC
i32 err = WSAGetLastError(); i32 err = WSAGetLastError();
if (err != WSAEWOULDBLOCK && err != WSAETIMEDOUT) { if (err != WSAEWOULDBLOCK && err != WSAETIMEDOUT && err != WSAECONNRESET) {
ASSERT(false); ASSERT(false);
} }
#endif #endif
@ -362,10 +441,12 @@ struct sock_read_result sock_read(struct sock *sock, struct string read_buff)
void sock_write(struct sock *sock, struct sock_address address, struct string data) void sock_write(struct sock *sock, struct sock_address address, struct string data)
{ {
SOCKET ws = (SOCKET)sock; struct win32_sock *ws = (struct win32_sock *)sock;
struct winsock_address ws_addr = winsock_address_from_sock_address(address); struct win32_address ws_addr = win32_address_from_sock_address(address);
i32 size = sendto(ws, (char *)data.text, data.len, 0, &ws_addr.sa, ws_addr.size); i32 size = sendto(ws->sock, (char *)data.text, data.len, 0, &ws_addr.sa, ws_addr.size);
(UNUSED)size; if (size > 0) {
atomic_u64_eval_add_u64(&app_statistics()->sock_bytes_sent, size);
}
#if RTC #if RTC
if (size != (i32)data.len) { if (size != (i32)data.len) {
i32 err = WSAGetLastError(); i32 err = WSAGetLastError();

View File

@ -660,7 +660,7 @@ INTERNAL void user_update(void)
last_try_connect = 0; last_try_connect = 0;
} break; } break;
case SIM_EVENT_KIND_SNAPSHOT_FULL: case SIM_EVENT_KIND_SNAPSHOT:
{ {
struct string snapshot_data = event->snapshot_data; struct string snapshot_data = event->snapshot_data;
sim_tick_from_string(snapshot_data, &G.world); sim_tick_from_string(snapshot_data, &G.world);
@ -740,20 +740,12 @@ INTERNAL void user_update(void)
} }
/* ========================== * /* ========================== *
* Find important entities * Find local entities
* ========================== */ * ========================== */
struct sim_ent *active_camera; struct sim_client *local_client = sim_client_from_handle(G.world.client_store, G.world.local_client);
{ struct sim_ent *local_player = sim_ent_from_handle(G.world.ent_store, local_client->control_ent);
enum sim_ent_prop props[] = { SIM_ENT_PROP_CAMERA, SIM_ENT_PROP_CAMERA_ACTIVE }; struct sim_ent *local_camera = sim_ent_from_handle(G.world.ent_store, local_client->camera_ent);
active_camera = sim_ent_find_first_match_all(store, (struct sim_ent_prop_array) { .count = ARRAY_COUNT(props), .props = props });
}
struct sim_ent *active_player;
{
enum sim_ent_prop props[] = { SIM_ENT_PROP_PLAYER_CONTROLLED, SIM_ENT_PROP_ACTIVE };
active_player = sim_ent_find_first_match_all(store, (struct sim_ent_prop_array) { .count = ARRAY_COUNT(props), .props = props });
}
/* ========================== * /* ========================== *
* Debug commands * Debug commands
@ -862,7 +854,7 @@ INTERNAL void user_update(void)
/* Determine ui size by camera & window dimensions */ /* Determine ui size by camera & window dimensions */
f32 aspect_ratio = 1.0; f32 aspect_ratio = 1.0;
{ {
struct xform quad_xf = xform_mul(sim_ent_get_xform(active_camera), active_camera->camera_quad_xform); 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); struct v2 camera_size = xform_get_scale(quad_xf);
if (!v2_is_zero(camera_size)) { if (!v2_is_zero(camera_size)) {
aspect_ratio = camera_size.x / camera_size.y; aspect_ratio = camera_size.x / camera_size.y;
@ -922,7 +914,7 @@ INTERNAL void user_update(void)
G.world_to_ui_xf = xform_translated(G.world_to_ui_xf, v2_neg(world_cursor)); G.world_to_ui_xf = xform_translated(G.world_to_ui_xf, v2_neg(world_cursor));
} }
} else { } else {
struct xform xf = sim_ent_get_xform(active_camera); struct xform xf = sim_ent_get_xform(local_camera);
struct v2 center = xf.og; struct v2 center = xf.og;
f32 rot = xform_get_rotation(xf); f32 rot = xform_get_rotation(xf);
@ -930,7 +922,7 @@ INTERNAL void user_update(void)
/* Scale view into viewport based on camera size */ /* Scale view into viewport based on camera size */
struct v2 size = G.ui_size; struct v2 size = G.ui_size;
{ {
struct xform quad_xf = xform_mul(xf, active_camera->camera_quad_xform); struct xform quad_xf = xform_mul(xf, local_camera->camera_quad_xform);
struct v2 camera_size = xform_get_scale(quad_xf); struct v2 camera_size = xform_get_scale(quad_xf);
if (!v2_is_zero(camera_size)) { if (!v2_is_zero(camera_size)) {
size = v2_div_v2(size, camera_size); size = v2_div_v2(size, camera_size);
@ -1022,7 +1014,7 @@ INTERNAL void user_update(void)
struct xform xf = sim_ent_get_xform(ent); struct xform xf = sim_ent_get_xform(ent);
struct xform parent_xf = sim_ent_get_xform(parent); struct xform parent_xf = sim_ent_get_xform(parent);
b32 skip_debug_draw = !G.debug_camera && ent == active_camera; b32 skip_debug_draw = !G.debug_camera && ent == local_camera;
b32 skip_debug_draw_transform = sim_ent_has_prop(ent, SIM_ENT_PROP_CAMERA); b32 skip_debug_draw_transform = sim_ent_has_prop(ent, SIM_ENT_PROP_CAMERA);
skip_debug_draw_transform = true; skip_debug_draw_transform = true;
@ -1109,7 +1101,7 @@ INTERNAL void user_update(void)
} }
/* Draw focus arrow */ /* Draw focus arrow */
if (sim_ent_has_prop(ent, SIM_ENT_PROP_PLAYER_CONTROLLED)) { if (ent == local_player) {
struct sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, ent->sprite); 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 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); struct v2 start = xform_mul_v2(sprite_xform, slice.center);
@ -1476,7 +1468,7 @@ INTERNAL void user_update(void)
/* Draw camera rect */ /* Draw camera rect */
if (sim_ent_has_prop(ent, SIM_ENT_PROP_CAMERA)) { if (sim_ent_has_prop(ent, SIM_ENT_PROP_CAMERA)) {
u32 color = ent == active_camera ? RGBA_32_F(1, 1, 1, 0.5) : RGBA_32_F(0, 0.75, 0, 0.5); 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; f32 thickness = 3;
struct xform quad_xf = xform_mul(xf, ent->camera_quad_xform); struct xform quad_xf = xform_mul(xf, ent->camera_quad_xform);
@ -1562,7 +1554,7 @@ INTERNAL void user_update(void)
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 = 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); 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(active_player).og); struct v2 input_aim_dir = v2_sub(G.world_cursor, sim_ent_get_xform(local_player).og);
/* Queue cursor move cmd */ /* Queue cursor move cmd */
queue_sim_cmd(scratch.arena, &cmd_list, (struct sim_cmd) { queue_sim_cmd(scratch.arena, &cmd_list, (struct sim_cmd) {

View File

@ -1,19 +1,23 @@
#include "world.h" #include "world.h"
#include "sim_ent.h" #include "sim_ent.h"
#include "sim_client.h"
#include "arena.h" #include "arena.h"
#include "scratch.h" #include "scratch.h"
void world_alloc(struct world *world) void world_alloc(struct world *world)
{ {
MEMZERO_STRUCT(world); MEMZERO_STRUCT(world);
world->client_store = sim_client_store_alloc();
world->ent_store = sim_ent_store_alloc(); world->ent_store = sim_ent_store_alloc();
} }
void world_release(struct world *world) void world_release(struct world *world)
{ {
sim_ent_store_release(world->ent_store); sim_ent_store_release(world->ent_store);
sim_client_store_release(world->client_store);
} }
#if 0
void world_copy_replace(struct world *dest, struct world *src) void world_copy_replace(struct world *dest, struct world *src)
{ {
__prof; __prof;
@ -27,3 +31,4 @@ void world_copy_replace(struct world *dest, struct world *src)
scratch_end(scratch); scratch_end(scratch);
} }
#endif

View File

@ -2,6 +2,7 @@
#define WORLD_H #define WORLD_H
#include "sim_ent.h" #include "sim_ent.h"
#include "sim_client.h"
struct world { struct world {
u64 continuity_gen; /* Starts at 1 */ u64 continuity_gen; /* Starts at 1 */
@ -13,11 +14,17 @@ struct world {
i64 dt_ns; i64 dt_ns;
i64 time_ns; i64 time_ns;
struct sim_client_handle local_client;
struct sim_client_store *client_store;
struct sim_ent_store *ent_store; struct sim_ent_store *ent_store;
}; };
void world_alloc(struct world *world); void world_alloc(struct world *world);
void world_release(struct world *world); void world_release(struct world *world);
#if 0
void world_copy_replace(struct world *dest, struct world *src); void world_copy_replace(struct world *dest, struct world *src);
#endif
#endif #endif