3211 lines
131 KiB
C
3211 lines
131 KiB
C
SharedUserState shared_user_state = ZI;
|
|
|
|
////////////////////////////////
|
|
//~ Startup
|
|
|
|
void StartupUser(void)
|
|
{
|
|
__prof;
|
|
SharedUserState *g = &shared_user_state;
|
|
|
|
SetGstat(GSTAT_DEBUG_STEPS, U64Max);
|
|
|
|
g->arena = AcquireArena(Gibi(64));
|
|
g->real_time_ns = TimeNs();
|
|
|
|
/* TODO: Remove this */
|
|
String connect_address_str = Lit("");
|
|
g->connect_address_str = PushString(g->arena, connect_address_str);
|
|
|
|
/* Initialize average dt to a reasonable value */
|
|
g->average_local_to_user_snapshot_publish_dt_ns = NsFromSeconds(1) / SIM_TICKS_PER_SECOND;
|
|
|
|
/* User blend clients */
|
|
g->user_client_store = AcquireClientStore();
|
|
g->user_unblended_client = AcquireClient(g->user_client_store);
|
|
g->user_blended_client = AcquireClient(g->user_client_store);
|
|
g->ss_blended = NilSnapshot();
|
|
|
|
/* Renderer data arenas */
|
|
g->material_instances_arena = AcquireArena(Gibi(64));
|
|
g->ui_rect_instances_arena = AcquireArena(Gibi(64));
|
|
g->ui_shape_verts_arena = AcquireArena(Gibi(64));
|
|
g->ui_shape_indices_arena = AcquireArena(Gibi(64));
|
|
g->grids_arena = AcquireArena(Gibi(64));
|
|
|
|
/* Local to user client */
|
|
g->local_to_user_client_store = AcquireClientStore();
|
|
g->local_to_user_client = AcquireClient(g->local_to_user_client_store);
|
|
|
|
g->world_to_ui_xf = XformIdentity;
|
|
g->world_to_render_xf = XformIdentity;
|
|
|
|
g->console_logs_arena = AcquireArena(Gibi(64));
|
|
//P_RegisterLogCallback(ConsoleLogCallback, P_LogLevel_Success);
|
|
P_RegisterLogCallback(ConsoleLogCallback, P_LogLevel_Debug);
|
|
g->window = P_AcquireWindow();
|
|
g->swapchain = GPU_AcquireSwapchain(g->window, VEC2I32(100, 100));
|
|
P_ShowWindow(g->window);
|
|
|
|
/* Start jobs */
|
|
RunJob(UpdateUserOrSleep, .pool = JobPool_User, .counter = &g->shutdown_job_counter);
|
|
RunJob(UpdateSim, .pool = JobPool_Sim, .counter = &g->shutdown_job_counter);
|
|
OnExit(&ShutdownUser);
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ Shutdown
|
|
|
|
ExitFuncDef(ShutdownUser)
|
|
{
|
|
__prof;
|
|
SharedUserState *g = &shared_user_state;
|
|
Atomic32FetchSet(&g->shutdown, 1);
|
|
YieldOnJobs(&g->shutdown_job_counter);
|
|
P_ReleaseWindow(g->window);
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ Debug draw
|
|
|
|
//- Draw xform
|
|
void DrawDebugXform(Xform xf, u32 color_x, u32 color_y)
|
|
{
|
|
SharedUserState *g = &shared_user_state;
|
|
f32 thickness = 2.f;
|
|
f32 arrowhead_len = 15.f;
|
|
|
|
Vec2 pos = MulXformV2(g->world_to_ui_xf, xf.og);
|
|
Vec2 x_ray = MulXformBasisV2(g->world_to_ui_xf, RightFromXform(xf));
|
|
Vec2 y_ray = MulXformBasisV2(g->world_to_ui_xf, UpFromXform(xf));
|
|
|
|
f32 ray_scale = 1;
|
|
x_ray = MulVec2(x_ray, ray_scale);
|
|
y_ray = MulVec2(y_ray, ray_scale);
|
|
|
|
/* FIXME: Enable this */
|
|
#if 0
|
|
D_DrawArrowRay(g->render_sig, pos, x_ray, thickness, arrowhead_len, color_x);
|
|
D_DrawArrowRay(g->render_sig, pos, y_ray, thickness, arrowhead_len, color_y);
|
|
#else
|
|
LAX x_ray;
|
|
LAX y_ray;
|
|
LAX thickness;
|
|
LAX arrowhead_len;
|
|
LAX pos;
|
|
LAX color_x;
|
|
LAX color_y;
|
|
#endif
|
|
|
|
//u32 color_quad = Rgba32F(0, 1, 1, 0.3);
|
|
//Quad quad = QuadFromRect(RectFromScalar(0, 0, 1, -1));
|
|
//quad = MulXformQuad(xf, ScaleQuad(quad, 0.075f));
|
|
//D_DrawQuad(g->render_sig, quad, color);
|
|
}
|
|
|
|
//- Draw movement
|
|
void DrawDebugMovement(Entity *ent)
|
|
{
|
|
SharedUserState *g = &shared_user_state;
|
|
f32 thickness = 2.f;
|
|
f32 arrow_len = 15.f;
|
|
|
|
u32 color_vel = ColorOrange;
|
|
|
|
Xform xf = XformFromEntity(ent);
|
|
Vec2 velocity = ent->linear_velocity;
|
|
|
|
Vec2 pos = MulXformV2(g->world_to_ui_xf, xf.og);
|
|
Vec2 vel_ray = MulXformBasisV2(g->world_to_ui_xf, velocity);
|
|
|
|
if (Vec2Len(vel_ray) > 0.00001)
|
|
{
|
|
/* FIXME: Enable this */
|
|
#if 0
|
|
D_DrawArrowRay(g->render_sig, pos, vel_ray, thickness, arrow_len, color_vel);
|
|
#else
|
|
LAX thickness;
|
|
LAX arrow_len;
|
|
LAX color_vel;
|
|
LAX xf;
|
|
LAX velocity;
|
|
LAX pos;
|
|
LAX vel_ray;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
//- Entity debug string
|
|
String DebugStringFromEntity(Arena *arena, Entity *ent)
|
|
{
|
|
TempArena scratch = BeginScratch(arena);
|
|
Snapshot *ss = ent->ss;
|
|
|
|
const u8 hex[] = "0123456789abcdef";
|
|
|
|
String result = ZI;
|
|
result.text = PushDry(arena, u8);
|
|
|
|
//result.len += StringF(arena, "[%F]", FmtUid(ent->id.uid)).len;
|
|
{
|
|
b32 transmitting = HasProp(ent, Prop_SyncSrc);
|
|
b32 receiving = HasProp(ent, Prop_SyncDst);
|
|
if (transmitting & receiving)
|
|
{
|
|
result.len += StringF(arena, " networked (sending & receiving)").len;
|
|
}
|
|
else if (transmitting)
|
|
{
|
|
result.len += StringF(arena, " networked (sending)").len;
|
|
}
|
|
else if (receiving)
|
|
{
|
|
result.len += StringF(arena, " networked (receiving)").len;
|
|
}
|
|
else
|
|
{
|
|
result.len += StringF(arena, " local").len;
|
|
}
|
|
}
|
|
result.len += StringF(arena, "\n").len;
|
|
|
|
result.len += StringF(arena, "owner: [%F]\n", FmtUid(ent->owner.uid)).len;
|
|
|
|
result.len += StringF(arena, "\n").len;
|
|
|
|
{
|
|
result.len += StringF(arena, "props: 0x").len;
|
|
for (u64 chunk_index = countof(ent->props); chunk_index-- > 0;)
|
|
{
|
|
u64 chunk = ent->props[chunk_index];
|
|
for (u64 part_index = 8; part_index-- > 0;)
|
|
{
|
|
if ((chunk_index != (countof(ent->props) - 1)) || ((chunk_index * 64) + (part_index * 8)) <= Prop_Count)
|
|
{
|
|
u8 part = (chunk >> (part_index * 8)) & 0xFF;
|
|
StringFromChar(arena, hex[(part >> 4) & 0x0F]);
|
|
StringFromChar(arena, hex[(part >> 0) & 0x0F]);
|
|
result.len += 2;
|
|
}
|
|
}
|
|
}
|
|
result.len += StringF(arena, "\n").len;
|
|
}
|
|
|
|
if (!EqId(ent->parent, RootEntityId))
|
|
{
|
|
result.len += StringF(arena, "parent: [%F]\n", FmtUid(ent->parent.uid)).len;
|
|
}
|
|
|
|
if (!IsNilId(ent->next) || !IsNilId(ent->prev))
|
|
{
|
|
result.len += StringF(arena, "prev: [%F]\n", FmtUid(ent->prev.uid)).len;
|
|
result.len += StringF(arena, "next: [%F]\n", FmtUid(ent->next.uid)).len;
|
|
}
|
|
|
|
result.len += StringF(arena, "\n").len;
|
|
|
|
/* Pos */
|
|
Xform xf = XformFromEntity(ent);
|
|
Vec2 linear_velocity = ent->linear_velocity;
|
|
f32 angular_velocity = ent->angular_velocity;
|
|
result.len += StringF(arena, "pos: (%F, %F)\n", FmtFloat(xf.og.x), FmtFloat(xf.og.y)).len;
|
|
result.len += StringF(arena, "linear velocity: (%F, %F)\n", FmtFloat(linear_velocity.x), FmtFloat(linear_velocity.y)).len;
|
|
result.len += StringF(arena, "angular velocity: %F\n", FmtFloat(angular_velocity)).len;
|
|
|
|
/* Test */
|
|
result.len += StringF(arena, "collision dir: (%F, %F)\n", FmtFloat(ent->collision_dir.x), FmtFloat(ent->collision_dir.y)).len;
|
|
|
|
/* Children */
|
|
if (!IsNilId(ent->first) || !IsNilId(ent->last))
|
|
{
|
|
Entity *child = EntityFromId(ss, ent->first);
|
|
if (!EqId(ent->first, ent->last) || !child->valid)
|
|
{
|
|
result.len += StringF(arena, "first child: [%F]\n", FmtUid(ent->first.uid)).len;
|
|
result.len += StringF(arena, "last child: [%F]\n", FmtUid(ent->last.uid)).len;
|
|
}
|
|
while (child->valid)
|
|
{
|
|
result.len += StringF(arena, "\n---------------------------------\n").len;
|
|
result.len += StringF(arena, "CHILD\n").len;
|
|
String child_text = DebugStringFromEntity(scratch.arena, child);
|
|
result.len += IndentString(arena, child_text, 4).len;
|
|
child = EntityFromId(ss, child->next);
|
|
}
|
|
}
|
|
|
|
EndScratch(scratch);
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ Console
|
|
|
|
//- Console log callback
|
|
P_LogEventCallbackFuncDef(ConsoleLogCallback, log)
|
|
{
|
|
__prof;
|
|
SharedUserState *g = &shared_user_state;
|
|
Lock lock = LockE(&g->console_logs_mutex);
|
|
{
|
|
ConsoleLog *clog = PushStruct(g->console_logs_arena, ConsoleLog);
|
|
clog->level = log.level;
|
|
clog->msg = PushString(g->console_logs_arena, log.msg);
|
|
clog->datetime = log.datetime;
|
|
clog->time_ns = log.time_ns;
|
|
|
|
if (g->last_console_log)
|
|
{
|
|
g->last_console_log->next = clog;
|
|
clog->prev = g->last_console_log;
|
|
/* Alternating color index between logs of same level */
|
|
i32 *color_index = &g->console_log_color_indices[log.level];
|
|
clog->color_index = *color_index;
|
|
*color_index = 1 - *color_index;
|
|
}
|
|
else
|
|
{
|
|
g->first_console_log = clog;
|
|
}
|
|
g->last_console_log = clog;
|
|
}
|
|
Unlock(&lock);
|
|
}
|
|
|
|
//- Draw console
|
|
void DrawDebugConsole(i32 level, b32 minimized)
|
|
{
|
|
/* FIXME: Enable this */
|
|
#if 0
|
|
__prof;
|
|
SharedUserState *g = &shared_user_state;
|
|
TempArena scratch = BeginScratchNoConflict();
|
|
|
|
Vec2 desired_start_pos = VEC2(10, minimized ? 100 : 600);
|
|
i64 fade_time_ns = NsFromSeconds(10);
|
|
f32 fade_curve = 0.5;
|
|
f32 spacing = 0;
|
|
f32 bg_margin = 5;
|
|
|
|
u32 colors[P_LogLevel_Count][2] = ZI;
|
|
SetBytes(colors, 0xFF, sizeof(colors));
|
|
#if 1
|
|
colors[P_LogLevel_Debug][0] = Rgb32F(0.4, 0.1, 0.4); colors[P_LogLevel_Debug][1] = Rgb32F(0.5, 0.2, 0.5);
|
|
colors[P_LogLevel_Info][0] = Rgb32F(0.4, 0.4, 0.4); colors[P_LogLevel_Info][1] = Rgb32F(0.5, 0.5, 0.5);
|
|
colors[P_LogLevel_Success][0] = Rgb32F(0.1, 0.3, 0.1); colors[P_LogLevel_Success][1] = Rgb32F(0.2, 0.4, 0.2);
|
|
colors[P_LogLevel_Warning][0] = Rgb32F(0.4, 0.4, 0.1); colors[P_LogLevel_Warning][1] = Rgb32F(0.5, 0.5, 0.2);
|
|
colors[P_LogLevel_Error][0] = Rgb32F(0.4, 0.1, 0.1); colors[P_LogLevel_Error][1] = Rgb32F(0.5, 0.2, 0.2);
|
|
#else
|
|
u32 info_colors[2] = { Rgb32F(0.4, 0.4, 0.4), Rgb32F(0.5, 0.5, 0.5) };
|
|
u32 success_colors[2] = { Rgb32F(0.1, 0.3, 0.1), Rgb32F(0.2, 0.4, 0.2) };
|
|
u32 warning_colors[2] = { Rgb32F(0.4, 0.4, 0.1), Rgb32F(0.5, 0.5, 0.2) };
|
|
u32 error_colors[2] = { Rgb32F(0.4, 0.1, 0.1), Rgb32F(0.5, 0.2, 0.2) };
|
|
#endif
|
|
|
|
Vec2 draw_pos = desired_start_pos;
|
|
f32 bounds_top = F32Infinity;
|
|
f32 bounds_bottom = -F32Infinity;
|
|
|
|
if (g->console_logs_height < desired_start_pos.y)
|
|
{
|
|
draw_pos.y = g->console_logs_height;
|
|
}
|
|
g->console_logs_height = 0;
|
|
|
|
i64 now_ns = TimeNs();
|
|
F_Font *font = F_LoadFontAsync(Lit("font/fixedsys.ttf"), 12.0f);
|
|
if (font)
|
|
{
|
|
Lock lock = LockE(&g->console_logs_mutex);
|
|
{
|
|
for (ConsoleLog *log = g->last_console_log; log; log = log->prev)
|
|
{
|
|
f32 opacity = 0.75;
|
|
if (minimized)
|
|
{
|
|
f32 lin = 1.0 - ClampF64((f64)(now_ns - log->time_ns) / (f64)fade_time_ns, 0, 1);
|
|
opacity *= PowF32(lin, fade_curve);
|
|
}
|
|
if (draw_pos.y > -desired_start_pos.y && opacity > 0)
|
|
{
|
|
if (log->level <= level)
|
|
{
|
|
/* Draw background */
|
|
u32 color = colors[log->level][log->color_index];
|
|
D_DrawQuad(g->render_sig, QuadFromRect(log->bounds), Alpha32F(color, opacity));
|
|
|
|
/* Draw text */
|
|
String text = log->msg;
|
|
if (!minimized)
|
|
{
|
|
P_DateTime datetime = log->datetime;
|
|
text = StringF(
|
|
scratch.arena,
|
|
"[%F:%F:%F.%F] %F",
|
|
FmtUintZ(datetime.hour, 2),
|
|
FmtUintZ(datetime.minute, 2),
|
|
FmtUintZ(datetime.second, 2),
|
|
FmtUintZ(datetime.milliseconds, 3),
|
|
FmtString(text));
|
|
}
|
|
|
|
D_TextParams params = D_TEXTPARAMS(.font = font, .pos = draw_pos, .offset_y = DRAW_TEXT_OFFSET_Y_BOTTOM, .color = Alpha32F(ColorWhite, opacity), .str = text);
|
|
Rect bounds = draw_text(g->render_sig, params);
|
|
|
|
Rect draw_bounds = bounds;
|
|
draw_bounds.x -= bg_margin;
|
|
draw_bounds.y -= bg_margin;
|
|
draw_bounds.width += bg_margin * 2.f;
|
|
draw_bounds.height += bg_margin * 2.f;
|
|
draw_pos.y -= draw_bounds.height + spacing;
|
|
log->bounds = draw_bounds;
|
|
|
|
bounds_top = MinF32(bounds_top, draw_bounds.y);
|
|
bounds_bottom = MaxF32(bounds_bottom, draw_bounds.y + draw_bounds.height);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Unlock(&lock);
|
|
}
|
|
if (bounds_top < F32Infinity && bounds_bottom > -F32Infinity)
|
|
{
|
|
g->console_logs_height = bounds_bottom - bounds_top;
|
|
}
|
|
EndScratch(scratch);
|
|
#else
|
|
LAX level;
|
|
LAX minimized;
|
|
#endif
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ Render buffers
|
|
|
|
//- Gbuffer
|
|
|
|
GPU_Resource *AcquireGbuffer(GPU_Format format, Vec2I32 size)
|
|
{
|
|
__prof;
|
|
GPU_ResourceDesc desc = ZI;
|
|
desc.kind = GPU_ResourceKind_Texture2D;
|
|
desc.flags = GPU_ResourceFlag_AllowSrv | GPU_ResourceFlag_AllowUav | GPU_ResourceFlag_AllowRtv;
|
|
desc.texture.format = format;
|
|
desc.texture.size = VEC3I32(size.x, size.y, 1);
|
|
desc.texture.mip_levels = 1;
|
|
return GPU_AcquireResource(desc);
|
|
}
|
|
|
|
//- Transfer buffer
|
|
|
|
GPU_Resource *AcquireTransferBuffer(u32 element_count, u32 element_size, void *src)
|
|
{
|
|
__prof;
|
|
u64 size = element_size * element_count;
|
|
GPU_ResourceDesc desc = ZI;
|
|
desc.kind = GPU_ResourceKind_Buffer;
|
|
desc.flags = GPU_ResourceFlag_None;
|
|
desc.buffer.heap_kind = GPU_HeapKind_Upload;
|
|
desc.buffer.size = size;
|
|
desc.buffer.element_count = element_count;
|
|
desc.buffer.element_size = element_size;
|
|
GPU_Resource *r = GPU_AcquireResource(desc);
|
|
{
|
|
__profn("Copy to transfer buffer");
|
|
GPU_CopyString(0, r, STRING(size, src));
|
|
}
|
|
return r;
|
|
}
|
|
|
|
GPU_Resource *AcquireTransferBufferFromArena(u32 element_count, Arena *arena)
|
|
{
|
|
__prof;
|
|
u64 element_size = element_count > 0 ? arena->pos / element_count : 0;
|
|
GPU_Resource *r = AcquireTransferBuffer(element_count, element_size, (void *)ArenaBase(arena));
|
|
return r;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ Sort entities
|
|
|
|
MergesortCompareFuncDef(EntitySortCmp, arg_a, arg_b, _)
|
|
{
|
|
Entity *a = *(Entity **)arg_a;
|
|
Entity *b = *(Entity **)arg_b;
|
|
|
|
i32 result = 0;
|
|
|
|
if (result == 0)
|
|
{
|
|
/* Sort by light */
|
|
b32 a_cmp = HasProp(a, Prop_LightTest);
|
|
b32 b_cmp = HasProp(b, Prop_LightTest);
|
|
result = (a_cmp > b_cmp) - (a_cmp < b_cmp);
|
|
}
|
|
if (result == 0)
|
|
{
|
|
/* Sort by layer */
|
|
i32 a_cmp = a->layer;
|
|
i32 b_cmp = b->layer;
|
|
result = (a_cmp < b_cmp) - (a_cmp > b_cmp);
|
|
}
|
|
if (result == 0)
|
|
{
|
|
/* Sort by sprite */
|
|
u64 a_cmp = a->sprite.hash;
|
|
u64 b_cmp = b->sprite.hash;
|
|
result = (a_cmp < b_cmp) - (a_cmp > b_cmp);
|
|
}
|
|
if (result == 0)
|
|
{
|
|
/* Sort by activation */
|
|
u64 a_cmp = a->activation_tick;
|
|
u64 b_cmp = b->activation_tick;
|
|
result = (a_cmp < b_cmp) - (a_cmp > b_cmp);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ User update
|
|
|
|
void UpdateUser(P_Window *window)
|
|
{
|
|
__prof;
|
|
SharedUserState *g = &shared_user_state;
|
|
TempArena scratch = BeginScratchNoConflict();
|
|
|
|
//- Begin frame
|
|
g->real_dt_ns = TimeNs() - g->real_time_ns;
|
|
g->real_time_ns += g->real_dt_ns;
|
|
g->screen_size = P_GetWindowSize(window);
|
|
|
|
//- Pull latest local sim snapshot
|
|
|
|
{
|
|
__profn("Pull snapshot");
|
|
Lock lock = LockE(&g->local_to_user_client_mutex);
|
|
u64 old_last_tick = g->user_unblended_client->last_tick;
|
|
u64 last_tick = g->local_to_user_client->last_tick;
|
|
if (last_tick > old_last_tick)
|
|
{
|
|
Snapshot *src = SnapshotFromTick(g->local_to_user_client, last_tick);
|
|
AcquireSnapshot(g->user_unblended_client, src, src->tick);
|
|
g->last_local_to_user_snapshot_published_at_ns = g->local_to_user_client_publish_time_ns;
|
|
g->average_local_to_user_snapshot_publish_dt_ns -= g->average_local_to_user_snapshot_publish_dt_ns / 50;
|
|
g->average_local_to_user_snapshot_publish_dt_ns += g->local_to_user_client_publish_dt_ns / 50;
|
|
}
|
|
Unlock(&lock);
|
|
}
|
|
|
|
//- Create user world from blended snapshots
|
|
|
|
{
|
|
__profn("Blend snapshots");
|
|
/* How along are we between sim ticks (0 = start of tick, 1 = end of tick) */
|
|
f64 tick_progress = 0;
|
|
i64 next_tick_expected_ns = g->last_local_to_user_snapshot_published_at_ns + g->average_local_to_user_snapshot_publish_dt_ns;
|
|
if (next_tick_expected_ns > g->last_local_to_user_snapshot_published_at_ns)
|
|
{
|
|
tick_progress = (f64)(g->real_time_ns - g->last_local_to_user_snapshot_published_at_ns) / (f64)(next_tick_expected_ns - g->last_local_to_user_snapshot_published_at_ns);
|
|
}
|
|
|
|
/* Predict local sim time based on average snapshot publish dt. */
|
|
Snapshot *newest_snapshot = SnapshotFromTick(g->user_unblended_client, g->user_unblended_client->last_tick);
|
|
g->local_sim_last_known_time_ns = newest_snapshot->sim_time_ns;
|
|
g->local_sim_last_known_tick = newest_snapshot->tick;
|
|
if (Atomic32Fetch(&g->user_paused))
|
|
{
|
|
g->local_sim_predicted_time_ns = g->local_sim_last_known_tick;
|
|
}
|
|
else
|
|
{
|
|
g->local_sim_predicted_time_ns = newest_snapshot->sim_time_ns + (newest_snapshot->sim_dt_ns * tick_progress);
|
|
}
|
|
|
|
if (USER_INTERP_ENABLED && !Atomic32Fetch(&g->user_paused))
|
|
{
|
|
/* Determine render time */
|
|
g->render_time_target_ns = g->local_sim_predicted_time_ns - (USER_INTERP_RATIO * g->average_local_to_user_snapshot_publish_dt_ns);
|
|
if (g->average_local_to_user_snapshot_publish_dt_ns > 0)
|
|
{
|
|
/* Increment render time based on average publish dt */
|
|
f64 sim_publish_timescale = (f64)newest_snapshot->sim_dt_ns / (f64)g->average_local_to_user_snapshot_publish_dt_ns;
|
|
g->render_time_ns += g->real_dt_ns * sim_publish_timescale;
|
|
}
|
|
i64 render_time_target_diff_ns = g->render_time_target_ns - g->render_time_ns;
|
|
if (render_time_target_diff_ns > NsFromSeconds(0.010) || render_time_target_diff_ns < NsFromSeconds(-0.005))
|
|
{
|
|
/* Snap render time if it gets too out of sync with target render time */
|
|
g->render_time_ns = g->render_time_target_ns;
|
|
}
|
|
|
|
/* Get two snapshots nearest to render time */
|
|
Snapshot *left_snapshot = NilSnapshot();
|
|
Snapshot *right_snapshot = newest_snapshot;
|
|
{
|
|
Snapshot *ss = SnapshotFromTick(g->user_unblended_client, g->user_unblended_client->first_tick);
|
|
while (ss->valid)
|
|
{
|
|
u64 next_tick = ss->next_tick;
|
|
i64 ss_time_ns = ss->sim_time_ns;
|
|
if (ss_time_ns < g->render_time_ns && ss_time_ns > left_snapshot->sim_time_ns)
|
|
{
|
|
left_snapshot = ss;
|
|
}
|
|
if (ss_time_ns > g->render_time_ns && ss_time_ns < right_snapshot->sim_time_ns)
|
|
{
|
|
right_snapshot = ss;
|
|
}
|
|
ss = SnapshotFromTick(g->user_unblended_client, next_tick);
|
|
}
|
|
}
|
|
|
|
/* Create world from blended snapshots */
|
|
if (left_snapshot->valid && right_snapshot->valid)
|
|
{
|
|
f64 blend = (f64)(g->render_time_ns - left_snapshot->sim_time_ns) / (f64)(right_snapshot->sim_time_ns - left_snapshot->sim_time_ns);
|
|
g->ss_blended = AcquireSnapshotFromLerp(g->user_blended_client, left_snapshot, right_snapshot, blend);
|
|
}
|
|
else if (left_snapshot->valid)
|
|
{
|
|
g->ss_blended = AcquireSnapshot(g->user_blended_client, left_snapshot, left_snapshot->tick);
|
|
}
|
|
else if (right_snapshot->valid)
|
|
{
|
|
g->ss_blended = AcquireSnapshot(g->user_blended_client, right_snapshot, right_snapshot->tick);
|
|
}
|
|
|
|
/* Release unneeded unblended snapshots */
|
|
if (left_snapshot->tick > 0)
|
|
{
|
|
ReleaseSnapshotsInRange(g->user_unblended_client, 0, left_snapshot->tick - 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Interp disabled, just copy latest snapshot */
|
|
g->render_time_target_ns = newest_snapshot->sim_time_ns;
|
|
g->render_time_ns = newest_snapshot->sim_time_ns;
|
|
g->ss_blended = AcquireSnapshot(g->user_blended_client, newest_snapshot, newest_snapshot->tick);
|
|
|
|
/* Release unneeded unblended snapshots */
|
|
if (newest_snapshot->tick > 0)
|
|
{
|
|
ReleaseSnapshotsInRange(g->user_unblended_client, 0, newest_snapshot->tick - 1);
|
|
}
|
|
}
|
|
|
|
/* Release unneeded blended snapshots */
|
|
if (g->ss_blended->tick > 0)
|
|
{
|
|
ReleaseSnapshotsInRange(g->user_blended_client, 0, g->ss_blended->tick - 1);
|
|
ReleaseSnapshotsInRange(g->user_blended_client, g->ss_blended->tick + 1, U64Max);
|
|
}
|
|
}
|
|
|
|
//- Process sys events into user bind state
|
|
|
|
{
|
|
__profn("Process sys events");
|
|
|
|
P_WindowEventArray events = P_PopWindowEvents(scratch.arena, window);
|
|
|
|
/* Reset bind pressed / released states */
|
|
for (u32 i = 0; i < countof(g->bind_states); ++i)
|
|
{
|
|
g->bind_states[i] = (BindState) {
|
|
.is_held = g->bind_states[i].is_held
|
|
};
|
|
}
|
|
|
|
for (u64 ent_index = 0; ent_index < events.count; ++ent_index)
|
|
{
|
|
P_WindowEvent *event = &events.events[ent_index];
|
|
if (event->kind == P_WindowEventKind_Quit)
|
|
{
|
|
SignalExit(0);
|
|
}
|
|
|
|
if (event->kind == P_WindowEventKind_ButtonUp)
|
|
{
|
|
/* Escape quit */
|
|
if (event->button == P_Btn_ESC)
|
|
{
|
|
SignalExit(0);
|
|
}
|
|
}
|
|
|
|
/* Update mouse pos */
|
|
if (event->kind == P_WindowEventKind_CursorMove)
|
|
{
|
|
g->screen_cursor = event->cursor_position;
|
|
}
|
|
|
|
/* Update bind states */
|
|
if ((event->kind == P_WindowEventKind_ButtonDown || event->kind == P_WindowEventKind_ButtonUp))
|
|
{
|
|
P_Btn button = event->button;
|
|
button = button >= P_Btn_Count ? P_Btn_None : button;
|
|
BindKind bind = g_binds[button];
|
|
if (bind)
|
|
{
|
|
b32 pressed = event->kind == P_WindowEventKind_ButtonDown;
|
|
#if 0
|
|
b32 out_of_bounds = button >= P_Btn_M1 && button <= P_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 = 0;
|
|
#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_repeats;
|
|
}
|
|
else
|
|
{
|
|
++g->bind_states[bind].num_presses;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++g->bind_states[bind].num_releases;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//- Find local entities
|
|
|
|
Entity *local_player = EntityFromId(g->ss_blended, g->ss_blended->local_player);
|
|
Entity *local_control = EntityFromId(g->ss_blended, local_player->player_control_ent);
|
|
Entity *local_camera = EntityFromId(g->ss_blended, local_player->player_camera_ent);
|
|
|
|
//- Find hovered entity
|
|
|
|
Entity *hovered_ent = NilEntity();
|
|
{
|
|
Xform mouse_xf = XformFromPos(g->world_cursor);
|
|
CLD_Shape mouse_shape = ZI;
|
|
mouse_shape.points[0] = VEC2(0, 0);
|
|
mouse_shape.count = 1;
|
|
mouse_shape.radius = 0.01f;
|
|
for (u64 ent_index = 0; ent_index < g->ss_blended->num_ents_reserved; ++ent_index)
|
|
{
|
|
Entity *ent = &g->ss_blended->ents[ent_index];
|
|
if (!IsValidAndActive(ent)) continue;
|
|
|
|
CLD_Shape ent_collider = ent->local_collider;
|
|
if (ent_collider.count > 0)
|
|
{
|
|
/* TODO: Can just use boolean GJK */
|
|
Xform ent_xf = XformFromEntity(ent);
|
|
CLD_CollisionData collision_result = CLD_CollisionDataFromShapes(&ent_collider, &mouse_shape, ent_xf, mouse_xf);
|
|
if (collision_result.num_points > 0)
|
|
{
|
|
hovered_ent = EntityFromId(g->ss_blended, ent->top);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//- Update user state from binds
|
|
|
|
/* Test fullscreen */
|
|
{
|
|
if (g->bind_states[BindKind_Fullscreen].num_presses && g->bind_states[BindKind_FullscreenMod].is_held)
|
|
{
|
|
P_WindowSettings settings = P_GetWindowSettings(window);
|
|
settings.flags ^= P_WindowSettingsFlag_Fullscreen;
|
|
P_UpdateWindowSettings(window, &settings);
|
|
}
|
|
}
|
|
|
|
if (g->bind_states[BindKind_DebugDraw].num_presses > 0)
|
|
{
|
|
g->debug_draw = !g->debug_draw;
|
|
}
|
|
|
|
if (g->bind_states[BindKind_DebugToggleTopmost].num_presses > 0)
|
|
{
|
|
P_ToggleWindowTopmost(window);
|
|
P_LogSuccessF("Toggle topmost");
|
|
}
|
|
|
|
if (g->bind_states[BindKind_DebugConsole].num_presses > 0)
|
|
{
|
|
g->debug_console = !g->debug_console;
|
|
}
|
|
|
|
if (g->bind_states[BindKind_DebugCamera].num_presses > 0)
|
|
{
|
|
g->debug_camera = !g->debug_camera;
|
|
}
|
|
|
|
{
|
|
if (g->bind_states[BindKind_DebugFollow].num_presses > 0)
|
|
{
|
|
if (IsNilId(g->debug_following))
|
|
{
|
|
g->debug_following = hovered_ent->id;
|
|
}
|
|
else
|
|
{
|
|
g->debug_following = NilEntityId;
|
|
}
|
|
}
|
|
if (!IsNilId(g->debug_following))
|
|
{
|
|
Entity *follow_ent = EntityFromId(g->ss_blended, g->debug_following);
|
|
Entity *follow_camera = NilEntity();
|
|
for (u64 i = 0; i < g->ss_blended->num_ents_reserved; ++i)
|
|
{
|
|
Entity *ent = &g->ss_blended->ents[i];
|
|
Entity *ent_camera_follow = EntityFromId(g->ss_blended, ent->camera_follow);
|
|
if (ent_camera_follow->valid && ent_camera_follow == follow_ent)
|
|
{
|
|
follow_camera = ent;
|
|
break;
|
|
}
|
|
}
|
|
if (follow_camera->valid)
|
|
{
|
|
local_camera = follow_camera;
|
|
}
|
|
else
|
|
{
|
|
g->debug_following = NilEntityId;
|
|
}
|
|
}
|
|
}
|
|
|
|
//- Apply shake
|
|
|
|
for (u64 ent_index = 0; ent_index < g->ss_blended->num_ents_reserved; ++ent_index)
|
|
{
|
|
Entity *ent = &g->ss_blended->ents[ent_index];
|
|
if (!IsValidAndActive(ent)) continue;
|
|
|
|
/* How much time between camera shakes */
|
|
i64 frequency_ns = NsFromSeconds(0.01f);
|
|
f32 shake = ent->shake;
|
|
if (shake > 0)
|
|
{
|
|
u64 angle_seed0 = ent->id.uid.lo + (u64)(g->ss_blended->sim_time_ns / frequency_ns);
|
|
u64 angle_seed1 = angle_seed0 + 1;
|
|
f32 angle0 = RandF64FromSeed(angle_seed0, 0, Tau);
|
|
f32 angle1 = RandF64FromSeed(angle_seed1, 0, Tau);
|
|
|
|
Vec2 vec0 = Vec2WithLen(Vec2FromAngle(angle0), shake);
|
|
/* NOTE: vec1 not completely accurate since shake can change between frames, it's just a prediction */
|
|
Vec2 vec1 = Vec2WithLen(Vec2FromAngle(angle1), shake);
|
|
|
|
/* TODO: Cubic interp? */
|
|
f32 blend = (f32)(g->ss_blended->sim_time_ns % frequency_ns) / (f32)frequency_ns;
|
|
Vec2 vec = LerpVec2(vec0, vec1, blend);
|
|
|
|
Xform xf = XformFromEntity(ent);
|
|
xf.og = AddVec2(xf.og, MulVec2(vec, shake));
|
|
SetXform(ent, xf);
|
|
}
|
|
}
|
|
|
|
//- Update ui to screen xform from screen size
|
|
|
|
if (g->debug_camera)
|
|
{
|
|
g->ui_size = g->screen_size;
|
|
g->ui_to_screen_xf = XformIdentity;
|
|
g->ui_to_screen_xf.og = RoundVec2(g->ui_to_screen_xf.og);
|
|
}
|
|
else
|
|
{
|
|
/* Determine ui size by camera & window dimensions */
|
|
f32 aspect_ratio = (f32)(DEFAULT_CAMERA_WIDTH / DEFAULT_CAMERA_HEIGHT);
|
|
if (local_camera->valid)
|
|
{
|
|
Xform quad_xf = MulXform(XformFromEntity(local_camera), local_camera->camera_quad_xform);
|
|
Vec2 camera_size = ScaleFromXform(quad_xf);
|
|
if (!IsVec2Zero(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 = CeilF32(width / aspect_ratio);
|
|
}
|
|
g->ui_size = VEC2I32(width, height);
|
|
|
|
/* Center ui in window */
|
|
f32 x = RoundF32(g->screen_size.x / 2 - width / 2);
|
|
f32 y = RoundF32(g->screen_size.y / 2 - height / 2);
|
|
g->ui_to_screen_xf = XformFromTrs(TRS(.t = VEC2(x, y)));
|
|
g->ui_to_screen_xf.og = RoundVec2(g->ui_to_screen_xf.og);
|
|
}
|
|
|
|
g->ui_cursor = MulXformV2(InvertXform(g->ui_to_screen_xf), g->screen_cursor);
|
|
|
|
//- Update world to ui xform from camera
|
|
|
|
if (g->debug_camera)
|
|
{
|
|
g->world_to_ui_xf = XformWIthWorldRotation(g->world_to_ui_xf, 0);
|
|
|
|
Vec2 world_cursor = InvertXformMulV2(g->world_to_ui_xf, g->ui_cursor);
|
|
|
|
/* Pan view */
|
|
if (g->bind_states[BindKind_Pan].is_held)
|
|
{
|
|
if (!g->debug_camera_panning)
|
|
{
|
|
g->debug_camera_pan_start = world_cursor;
|
|
g->debug_camera_panning = 1;
|
|
}
|
|
Vec2 offset = NegVec2(SubVec2(g->debug_camera_pan_start, world_cursor));
|
|
g->world_to_ui_xf = TranslateXform(g->world_to_ui_xf, offset);
|
|
world_cursor = InvertXformMulV2(g->world_to_ui_xf, g->ui_cursor);
|
|
g->debug_camera_pan_start = world_cursor;
|
|
}
|
|
else
|
|
{
|
|
g->debug_camera_panning = 0;
|
|
}
|
|
|
|
/* Zoom view */
|
|
i32 input_zooms = g->bind_states[BindKind_ZoomIn].num_presses - g->bind_states[BindKind_ZoomOut].num_presses;
|
|
if (input_zooms != 0)
|
|
{
|
|
/* Zoom to cursor */
|
|
f32 zoom_rate = 2;
|
|
f32 zoom = PowF32(zoom_rate, input_zooms);
|
|
g->world_to_ui_xf = TranslateXform(g->world_to_ui_xf, world_cursor);
|
|
g->world_to_ui_xf = ScaleXform(g->world_to_ui_xf, VEC2(zoom, zoom));
|
|
g->world_to_ui_xf = TranslateXform(g->world_to_ui_xf, NegVec2(world_cursor));
|
|
}
|
|
g->world_to_ui_xf.og = RoundVec2(g->world_to_ui_xf.og);
|
|
}
|
|
else
|
|
{
|
|
Xform xf = XformFromEntity(local_camera);
|
|
|
|
Vec2 world_center = xf.og;
|
|
f32 rot = RotationFromXform(xf);
|
|
|
|
/* Scale view into viewport based on camera size */
|
|
Vec2 scale = VEC2(g->ui_size.x, g->ui_size.y);
|
|
{
|
|
Xform quad_xf = MulXform(xf, local_camera->camera_quad_xform);
|
|
Vec2 camera_size = ScaleFromXform(quad_xf);
|
|
if (!IsVec2Zero(camera_size))
|
|
{
|
|
scale = DivVec2Vec2(scale, camera_size);
|
|
}
|
|
}
|
|
scale.x = MinF32(scale.x, scale.y);
|
|
scale.y = scale.x;
|
|
|
|
Vec2 ui_center = MulVec2(VEC2(g->ui_size.x, g->ui_size.y), 0.5);
|
|
Trs trs = TRS(.t = SubVec2(ui_center, world_center), .r = rot, .s = scale);
|
|
Vec2 pivot = world_center;
|
|
g->world_to_ui_xf = XformIdentity;
|
|
g->world_to_ui_xf = TranslateXform(g->world_to_ui_xf, pivot);
|
|
g->world_to_ui_xf = TranslateXform(g->world_to_ui_xf, trs.t);
|
|
g->world_to_ui_xf = RotateXform(g->world_to_ui_xf, trs.r);
|
|
g->world_to_ui_xf = ScaleXform(g->world_to_ui_xf, trs.s);
|
|
g->world_to_ui_xf = TranslateXform(g->world_to_ui_xf, NegVec2(pivot));
|
|
g->world_to_ui_xf.og = RoundVec2(g->world_to_ui_xf.og);
|
|
}
|
|
|
|
g->world_cursor = InvertXformMulV2(g->world_to_ui_xf, g->ui_cursor);
|
|
|
|
//- Update world to render xform from world to ui xform
|
|
|
|
b32 effects_disabled = 0;
|
|
g->render_size = RoundVec2ToVec2I32(VEC2(RENDER_WIDTH, RENDER_HEIGHT));
|
|
|
|
if (g->debug_camera)
|
|
{
|
|
g->render_size = g->ui_size;
|
|
effects_disabled = 1;
|
|
g->world_to_render_xf = g->world_to_ui_xf;
|
|
}
|
|
else
|
|
{
|
|
Xform ui_to_world_xf = InvertXform(g->world_to_ui_xf);
|
|
Vec2 world_center = MulXformV2(ui_to_world_xf, MulVec2(VEC2(g->ui_size.x, g->ui_size.y), 0.5));
|
|
|
|
Vec2 scale = VEC2(PIXELS_PER_UNIT, PIXELS_PER_UNIT);
|
|
|
|
Xform xf = XformIdentity;
|
|
|
|
xf = TranslateXform(xf, MulVec2(VEC2(g->render_size.x, g->render_size.y), 0.5));
|
|
|
|
xf = ScaleXform(xf, scale);
|
|
xf = TranslateXform(xf, MulVec2(world_center, -1));
|
|
xf.og = RoundVec2(xf.og);
|
|
|
|
g->world_to_render_xf = xf;
|
|
}
|
|
|
|
//- Update render to ui xform
|
|
|
|
{
|
|
Xform world_to_ui_xf = g->world_to_ui_xf;
|
|
Xform world_to_render_xf = g->world_to_render_xf;
|
|
Xform render_to_world_xf = InvertXform(world_to_render_xf);
|
|
Xform render_to_ui_xf = MulXform(world_to_ui_xf, render_to_world_xf);
|
|
g->render_to_ui_xf = render_to_ui_xf;
|
|
}
|
|
|
|
//- Update listener from view
|
|
|
|
{
|
|
Vec2 up = VEC2(0, -1);
|
|
Vec2 ui_center = MulVec2(VEC2(g->ui_size.x, g->ui_size.y), 0.5f);
|
|
Vec2 listener_pos = InvertXformMulV2(g->world_to_ui_xf, ui_center);
|
|
Vec2 listener_dir = NormVec2(InvertXformBasisMulV2(g->world_to_ui_xf, up));
|
|
MIX_UpdateListener(listener_pos, listener_dir);
|
|
}
|
|
|
|
//- Draw grid
|
|
|
|
/* FIXME: Enable this */
|
|
#if 0
|
|
{
|
|
f32 thickness = 2;
|
|
|
|
Vec2 offset = NegVec2(MulXformV2(g->world_to_render_xf, VEC2(0, 0)));
|
|
f32 spacing = ScaleFromXform(g->world_to_render_xf).x;
|
|
|
|
Vec2 pos = InvertXformMulV2(g->world_to_render_xf, VEC2(0, 0));
|
|
Vec2 size = InvertXformBasisMulV2(g->world_to_render_xf, VEC2(g->render_size.x, g->render_size.y));
|
|
u32 color0 = Rgba32F(0.17f, 0.17f, 0.17f, 1.f);
|
|
u32 color1 = Rgba32F(0.15f, 0.15f, 0.15f, 1.f);
|
|
D_DrawGrid(g->render_sig, XformFromRect(RectFromVec2(pos, size)), color0, color1, Rgba32(0x3f, 0x3f, 0x3f, 0xFF), ColorRed, ColorGreen, thickness, spacing, offset);
|
|
}
|
|
#else
|
|
#endif
|
|
|
|
#if 0
|
|
//- Acquire / release tile cache entries
|
|
|
|
/* Acquire entries from new sim chunks */
|
|
|
|
for (u64 ent_index = 0; ent_index < g->ss_blended->num_ents_reserved; ++ent_index)
|
|
{
|
|
Entity *chunk_ent = &g->ss_blended->ents[ent_index];
|
|
if (IsValidAndActive(chunk_ent) && HasProp(chunk_ent, Prop_TileChunk))
|
|
{
|
|
struct user_tile_cache_entry *entry = user_tile_cache_entry_from_chunk_pos(chunk_ent->tile_chunk_pos);
|
|
if (!entry->valid)
|
|
{
|
|
entry = user_tile_cache_entry_acquire(chunk_ent->tile_chunk_pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Release entries with invalid sim chunks */
|
|
|
|
for (u64 entry_index = 0; entry_index < g->tile_cache.num_reserved_entries; ++entry_index)
|
|
{
|
|
struct tile_cache_entry *entry = &g->tile_cache.entries[entry_index];
|
|
if (entry->valid)
|
|
{
|
|
Entity *chunk_ent = sim_ent_from_chunk_pos(entry->pos);
|
|
if (!chunk_ent->valid)
|
|
{
|
|
user_tile_cache_entry_release(entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
//- Draw dirty tile cache entries
|
|
|
|
for (u64 entry_index = 0; entry_index < g->tile_cache.num_reserved_entries; ++entry_index)
|
|
{
|
|
struct tile_cache_entry *entry = &g->tile_cache.entries[entry_index];
|
|
if (entry->valid)
|
|
{
|
|
Vec2I32 chunk_pos = entry->pos;
|
|
Entity *chunk_ent = sim_ent_from_chunk_pos(chunk_pos);
|
|
if (entry->applied_dirty_gen != chunk_ent->dirty_gen)
|
|
{
|
|
entry->applied_dirty_gen = chunk_ent->dirty_gen;
|
|
/* TODO: Autotiling */
|
|
|
|
String data = sim_ent_get_chunk_tile_data(chunk_ent);
|
|
u64 tile_count = data.len;
|
|
if (tile_count == SIM_TILES_PER_CHUNK_SQRT * SIM_TILES_PER_CHUNK_SQRT)
|
|
{
|
|
for (u64 y_in_chunk = 0; y_in_chunk < SIM_TILES_PER_CHUNK_SQRT; ++y_in_chunk)
|
|
{
|
|
for (u64 x_in_chunk = 0; x_in_chunk < SIM_TILES_PER_CHUNK_SQRT; ++x_in_chunk)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* TODO: Clear gpu buffer if it exists */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
for (u64 entry_index = 0; entry_index < g->tile_cache.num_reserved_entries; ++entry_index)
|
|
{
|
|
struct tile_cache_entry *entry = &g->tile_cache.entries[entry_index];
|
|
if (entry->valid)
|
|
{
|
|
Vec2I32 chunk_pos = entry->pos;
|
|
Entity *chunk_ent = sim_ent_from_chunk_pos(chunk_pos);
|
|
if (entry->applied_dirty_gen != chunk_ent->dirty_gen)
|
|
{
|
|
entry->applied_dirty_gen = chunk_ent->dirty_gen;
|
|
|
|
/* Retreive surrounding chunk info since we're auto-tiling
|
|
* [TL] [T] [TR]
|
|
* [L ] X [R ]
|
|
* [BL] [B] [BR]
|
|
*/
|
|
Vec2I32 chunk_pos_tl = VEC2I32(chunk_pos.x - 1, chunk_pos.y - 1);
|
|
Vec2I32 chunk_pos_t = VEC2I32(chunk_pos.x, chunk_pos.y - 1);
|
|
Vec2I32 chunk_pos_tr = VEC2I32(chunk_pos.x + 1, chunk_pos.y - 1);
|
|
Vec2I32 chunk_pos_l = VEC2I32(chunk_pos.x - 1, chunk_pos.y);
|
|
Vec2I32 chunk_pos_r = VEC2I32(chunk_pos.x + 1, chunk_pos.y);
|
|
Vec2I32 chunk_pos_bl = VEC2I32(chunk_pos.x - 1, chunk_pos.y + 1);
|
|
Vec2I32 chunk_pos_b = VEC2I32(chunk_pos.x, chunk_pos.y + 1);
|
|
Vec2I32 chunk_pos_br = VEC2I32(chunk_pos.x + 1, chunk_pos.y + 1);
|
|
Entity *chunk_ent_tl = sim_ent_from_chunk_pos(chunk_pos_tl);
|
|
Entity *chunk_ent_t = sim_ent_from_chunk_pos(chunk_pos_t);
|
|
Entity *chunk_ent_tr = sim_ent_from_chunk_pos(chunk_pos_tr);
|
|
Entity *chunk_ent_l = sim_ent_from_chunk_pos(chunk_pos_l);
|
|
Entity *chunk_ent_r = sim_ent_from_chunk_pos(chunk_pos_r);
|
|
Entity *chunk_ent_bl = sim_ent_from_chunk_pos(chunk_pos_bl);
|
|
Entity *chunk_ent_b = sim_ent_from_chunk_pos(chunk_pos_b);
|
|
Entity *chunk_ent_br = sim_ent_from_chunk_pos(chunk_pos_br);
|
|
|
|
String data = sim_ent_get_chunk_tile_data(chunk_ent);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
//- Sort drawable entities
|
|
|
|
Entity **sorted = PushDry(scratch.arena, Entity *);
|
|
u64 sorted_count = 0;
|
|
{
|
|
/* Copy valid entities */
|
|
{
|
|
__profn("Build ents list for sorting");
|
|
for (u64 ent_index = 0; ent_index < g->ss_blended->num_ents_reserved; ++ent_index)
|
|
{
|
|
Entity *ent = &g->ss_blended->ents[ent_index];
|
|
if (IsValidAndActive(ent))
|
|
{
|
|
*PushStructNoZero(scratch.arena, Entity *) = ent;
|
|
++sorted_count;
|
|
}
|
|
}
|
|
}
|
|
/* Sort */
|
|
{
|
|
__profn("Sort ents");
|
|
Mergesort(sorted, sorted_count, sizeof(*sorted), EntitySortCmp, 0);
|
|
}
|
|
}
|
|
|
|
//- Draw entities
|
|
|
|
{
|
|
__profn("Draw entities");
|
|
for (u64 sorted_index = 0; sorted_index < sorted_count; ++sorted_index)
|
|
{
|
|
Entity *ent = sorted[sorted_index];
|
|
if (!IsValidAndActive(ent)) continue;
|
|
//if (S_IsNil(ent->sprite)) continue;
|
|
|
|
Resource sprite = ent->sprite;
|
|
|
|
Entity *parent = EntityFromId(g->ss_blended, ent->parent);
|
|
|
|
Xform xf = XformFromEntity(ent);
|
|
UNUSED Xform parent_xf = XformFromEntity(parent);
|
|
|
|
b32 skip_debug_draw = !g->debug_camera && ent == local_camera;
|
|
skip_debug_draw = skip_debug_draw || HasProp(ent, Prop_MotorJoint);
|
|
|
|
b32 skip_debug_draw_transform = HasProp(ent, Prop_Camera);
|
|
skip_debug_draw_transform = 1;
|
|
|
|
UNUSED Xform sprite_xform = MulXform(xf, ent->sprite_local_xform);
|
|
|
|
/* Draw tracer */
|
|
/* TODO: Enable this */
|
|
#if 0
|
|
if (HasProp(ent, Prop_Tracer))
|
|
{
|
|
Vec2 velocity = ent->tracer_start_velocity;
|
|
|
|
Vec2 a = ent->tracer_start;
|
|
Vec2 b = xf.og;
|
|
Vec2 c = ent->tracer_gradient_start;
|
|
Vec2 d = ent->tracer_gradient_end;
|
|
|
|
Vec2 vcd = SubVec2(d, c);
|
|
Vec2 vca = SubVec2(a, c);
|
|
Vec2 vdb = SubVec2(b, d);
|
|
Vec2 vdc = NegVec2(vcd);
|
|
|
|
f32 opacity_a = 1;
|
|
f32 opacity_b = 1;
|
|
if (Vec2LenSq(vcd) != 0)
|
|
{
|
|
if (DotVec2(velocity, vca) <= 0)
|
|
{
|
|
a = c;
|
|
opacity_a = 0;
|
|
}
|
|
else
|
|
{
|
|
opacity_a = DotVec2(vcd, vca) / Vec2LenSq(vcd);
|
|
}
|
|
opacity_a = ClampF32(opacity_a, 0, 1);
|
|
opacity_b = ClampF32(1.f - (DotVec2(vdc, vdb) / Vec2LenSq(vdc)), 0, 1);
|
|
}
|
|
|
|
f32 thickness = 0.01f;
|
|
u32 color_start = Rgba32F(1, 0.5, 0, opacity_a);
|
|
u32 color_end = Rgba32F(1, 0.8, 0.4, opacity_b);
|
|
|
|
if (opacity_b > 0.99f)
|
|
{
|
|
D_DrawCircle(g->render_sig, b, thickness / 2, color_end, 20);
|
|
}
|
|
D_DrawLineGradient(g->render_sig, a, b, thickness, color_start, color_end);
|
|
}
|
|
#endif
|
|
|
|
/* Draw sprite */
|
|
if (!IsResourceNil(sprite))
|
|
{
|
|
S_Sheet *sheet = S_SheetFromResourceAsync(sprite);
|
|
S_Texture *texture = S_TextureFromResourceAsync(sprite);
|
|
|
|
/* TODO: Fade in placeholder if texture isn't loaded */
|
|
if (sheet->loaded && texture->loaded)
|
|
{
|
|
b32 is_light = HasProp(ent, Prop_LightTest);
|
|
Vec3 emittance = ent->sprite_emittance;
|
|
u32 tint = ent->sprite_tint;
|
|
S_Frame frame = S_FrameFromIndex(sheet, ent->animation_frame);
|
|
/* FIXME: Enable this */
|
|
#if 0
|
|
D_MaterialParams params = D_MATERIALPARAMS(.xf = sprite_xform, .texture = texture->gpu_resource, .tint = tint, .clip = frame.clip, .is_light = is_light, .light_emittance = emittance);
|
|
D_DrawMaterial(g->render_sig, params);
|
|
#else
|
|
LAX is_light;
|
|
LAX emittance;
|
|
LAX tint;
|
|
LAX frame;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* Draw tiles */
|
|
/* TODO: Something better */
|
|
if (HasProp(ent, Prop_TileChunk))
|
|
{
|
|
Vec2I32 chunk_index = ent->tile_chunk_index;
|
|
Resource tile_sprite = ResourceFromStore(&GameResources, Lit("sprite/tile.ase"));
|
|
S_Texture *tile_texture = S_TextureFromResourceAsync(tile_sprite);
|
|
if (tile_texture->loaded)
|
|
{
|
|
f32 tile_size = 1.f / SIM_TILES_PER_UNIT_SQRT;
|
|
for (i32 tile_y = 0; tile_y < SIM_TILES_PER_CHUNK_SQRT; ++tile_y)
|
|
{
|
|
for (i32 tile_x = 0; tile_x < SIM_TILES_PER_CHUNK_SQRT; ++tile_x)
|
|
{
|
|
Vec2I32 local_tile_index = VEC2I32(tile_x, tile_y);
|
|
TileKind tile = ent->tile_chunk_tiles[local_tile_index.x + (local_tile_index.y * SIM_TILES_PER_CHUNK_SQRT)];
|
|
/* FIXME: Enable this */
|
|
#if 0
|
|
//if (tile > -1)
|
|
if (tile == TileKind_Wall)
|
|
{
|
|
Vec2I32 world_tile_index = WorldTileIndexFromLocalTileIndex(chunk_index, local_tile_index);
|
|
Vec2 pos = PosFromWorldTileIndex(world_tile_index);
|
|
Xform tile_xf = XformFromRect(RectFromVec2(pos, VEC2(tile_size, tile_size)));
|
|
D_MaterialParams params = D_MATERIALPARAMS(.xf = tile_xf, .texture = tile_texture->gpu_resource, .is_light = 1, .light_emittance = VEC3(0, 0, 0));
|
|
D_DrawMaterial(g->render_sig, params);
|
|
}
|
|
#else
|
|
LAX local_tile_index;
|
|
LAX tile;
|
|
LAX tile_size;
|
|
LAX chunk_index;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Debug draw entity info */
|
|
if (g->debug_draw && !skip_debug_draw)
|
|
{
|
|
TempArena temp = BeginTempArena(scratch.arena);
|
|
|
|
if (HasProp(ent, Prop_Kinematic) || HasProp(ent, Prop_Dynamic))
|
|
{
|
|
DrawDebugMovement(ent);
|
|
}
|
|
|
|
/* Draw xform */
|
|
if (!skip_debug_draw_transform)
|
|
{
|
|
u32 color_x = Rgba32F(1, 0, 0, 0.5);
|
|
u32 color_y = Rgba32F(0, 1, 0, 0.5);
|
|
DrawDebugXform(xf, color_x, color_y);
|
|
}
|
|
|
|
/* Draw AABB */
|
|
/* FIXME: Enable this */
|
|
#if 0
|
|
if (ent->local_collider.count > 0)
|
|
{
|
|
Aabb aabb = CLD_AabbFromShape(&ent->local_collider, xf);
|
|
f32 thickness = 1;
|
|
u32 color = Rgba32F(1, 0, 1, 0.5);
|
|
Quad quad = QuadFromAabb(aabb);
|
|
quad = MulXformQuad(g->world_to_ui_xf, quad);
|
|
D_DrawQuadLine(g->render_sig, quad, thickness, color);
|
|
}
|
|
|
|
/* Draw focus arrow */
|
|
if (ent == local_control || EqId(ent->id, g->debug_following))
|
|
{
|
|
S_Sheet *sheet = S_SheetFromResourceAsync(ent->sprite);
|
|
S_Slice slice = S_SliceFromNameIndex(sheet, Lit("attach.wep"), ent->animation_frame);
|
|
Vec2 start = MulXformV2(sprite_xform, slice.center);
|
|
start = MulXformV2(g->world_to_ui_xf, start);
|
|
Vec2 end = AddVec2(xf.og, ent->control.focus);
|
|
end = MulXformV2(g->world_to_ui_xf, end);
|
|
D_DrawArrowLine(g->render_sig, start, end, 3, 10, Rgba32F(1, 1, 1, 0.5));
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
/* Draw slices */
|
|
if (!S_IsNil(ent->sprite))
|
|
{
|
|
S_Sheet *sheet = S_SheetFromResourceAsync(sprite);
|
|
|
|
u32 quad_color = Rgba32F(1, 0, 0.5, 1);
|
|
u32 point_color = Rgba32F(1, 0, 0, 1);
|
|
u32 ray_color = Rgba32F(1, 0, 0.5, 1);
|
|
|
|
for (u64 i = 0; i < sheet->slice_groups_count; ++i)
|
|
{
|
|
S_SheetSliceGroup *group = &sheet->slice_groups[i];
|
|
if (StringEndsWith(group->name, Lit(".ray"))) continue;
|
|
|
|
for (u32 j = 0; j < group->per_frame_count; ++j)
|
|
{
|
|
S_Slice slice = group->frame_slices[(ent->animation_frame * group->per_frame_count) + j];
|
|
|
|
Vec2 center = MulXformV2(sprite_xform, slice.center);
|
|
center = MulXformV2(g->world_to_ui_xf, center);
|
|
|
|
if (!slice.has_ray)
|
|
{
|
|
Quad quad = QuadFromRect(slice.rect);
|
|
quad = MulXformQuad(sprite_xform, quad);
|
|
quad = MulXformQuad(g->world_to_ui_xf, quad);
|
|
D_DrawQuadLine(g->render_sig, quad, 2, quad_color);
|
|
}
|
|
|
|
D_DrawCircle(g->render_sig, center, 3, point_color, 20);
|
|
|
|
if (slice.has_ray)
|
|
{
|
|
Vec2 ray = MulXformBasisV2(sprite_xform, slice.dir);
|
|
ray = MulXformBasisV2(g->world_to_ui_xf, ray);
|
|
ray = Vec2WithLen(ray, 25);
|
|
D_DrawArrowRay(g->render_sig, center, ray, 2, 10, ray_color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Draw weld joint */
|
|
#if 0
|
|
if (HasProp(ent, Prop_WeldJoint))
|
|
{
|
|
Entity *e1 = EntityFromId(g->ss_blended, ent->weld_joint_data.e1);
|
|
Xform e1_xf = XformFromEntity(e1);
|
|
|
|
u32 color = ColorYellow;
|
|
f32 radius = 3;
|
|
Vec2 point = MulXformV2(e1_xf, ent->weld_joint_data.point_local_e1);
|
|
point = MulXformV2(g->world_to_ui_xf, point);
|
|
D_DrawCircle(g->render_sig, point, radius, color, 10);
|
|
|
|
DEBUGBREAKABLE;
|
|
}
|
|
#endif
|
|
|
|
/* Draw mouse joint */
|
|
/* FIXME: Enable this */
|
|
#if 0
|
|
if (HasProp(ent, Prop_MouseJoint))
|
|
{
|
|
Entity *target = EntityFromId(g->ss_blended, ent->mouse_joint_data.target);
|
|
Xform target_xf = XformFromEntity(target);
|
|
u32 color = ColorWhite;
|
|
Vec2 point_start = MulXformV2(target_xf, ent->mouse_joint_data.point_local_start);
|
|
Vec2 point_end = g->world_cursor;
|
|
point_start = MulXformV2(g->world_to_ui_xf, point_start);
|
|
point_end = MulXformV2(g->world_to_ui_xf, point_end);
|
|
D_DrawArrowLine(g->render_sig, point_start, point_end, 3, 10, color);
|
|
D_DrawCircle(g->render_sig, point_start, 4, color, 10);
|
|
}
|
|
#endif
|
|
|
|
/* Draw collider */
|
|
/* FIXME: Enable this */
|
|
#if 0
|
|
if (ent->local_collider.count > 0)
|
|
{
|
|
CLD_Shape collider = ent->local_collider;
|
|
u32 color = Rgba32F(1, 1, 0, 0.5);
|
|
f32 thickness = 2;
|
|
{
|
|
/* Draw collider using support points */
|
|
u32 detail = 32;
|
|
Xform collider_draw_xf = MulXform(g->world_to_ui_xf, xf);
|
|
D_DrawColliderLine(g->render_sig, collider, collider_draw_xf, thickness, color, detail);
|
|
}
|
|
{
|
|
/* Draw collider shape points */
|
|
for (u32 i = 0; i < collider.count; ++i)
|
|
{
|
|
Vec2 p = MulXformV2(MulXform(g->world_to_ui_xf, xf), collider.points[i]);
|
|
D_DrawCircle(g->render_sig, p, 3, ColorBlue, 10);
|
|
}
|
|
}
|
|
if (collider.count == 1 && collider.radius > 0)
|
|
{
|
|
/* Draw upwards line for circle */
|
|
Vec2 start = xf.og;
|
|
Vec2 end = CLD_SupportPointFromDir(&collider, xf, NegVec2(xf.by)).p;
|
|
start = MulXformV2(g->world_to_ui_xf, start);
|
|
end = MulXformV2(g->world_to_ui_xf, end);
|
|
D_DrawLine(g->render_sig, start, end, thickness, color);
|
|
}
|
|
#if 0
|
|
/* Draw support point at focus dir */
|
|
{
|
|
Vec2 p = collider_support_point(&collider, xf, ent->control.focus);
|
|
p = MulXformV2(g->world_to_ui_xf, p);
|
|
D_DrawCircle(g->render_sig, p, 3, ColorRed, 10);
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
/* Draw contact constraint */
|
|
/* FIXME: Enable this */
|
|
#if 0
|
|
if (HasProp(ent, Prop_ContactConstraint))
|
|
{
|
|
ContactConstraint *data = &ent->contact_constraint_data;
|
|
Entity *e0 = EntityFromId(g->ss_blended, data->e0);
|
|
Entity *e1 = EntityFromId(g->ss_blended, data->e1);
|
|
LAX e0;
|
|
LAX e1;
|
|
|
|
#if DeveloperIsEnabled
|
|
/* Draw contact points */
|
|
{
|
|
f32 radius = 5;
|
|
for (u32 i = 0; i < data->num_points; ++i)
|
|
{
|
|
u32 color = (data->skip_solve || data->wrong_dir) ? Alpha32F(ColorYellow, 0.3) : Rgba32F(0.8, 0.2, 0.2, 1);
|
|
ContactPoint point = data->points[i];
|
|
Vec2 dbg_pt = point.dbg_pt;
|
|
|
|
/* Draw point */
|
|
{
|
|
D_DrawCircle(g->render_sig, MulXformV2(g->world_to_ui_xf, dbg_pt), radius, color, 10);
|
|
}
|
|
|
|
/* Draw normal */
|
|
{
|
|
f32 len = 0.1f;
|
|
f32 arrow_thickness = 2;
|
|
f32 arrow_height = 5;
|
|
Vec2 start = MulXformV2(g->world_to_ui_xf, dbg_pt);
|
|
Vec2 end = MulXformV2(g->world_to_ui_xf, AddVec2(dbg_pt, MulVec2(NormVec2(data->normal), len)));
|
|
D_DrawArrowLine(g->render_sig, start, end, arrow_thickness, arrow_height, color);
|
|
}
|
|
#if 0
|
|
/* Draw contact info */
|
|
{
|
|
F_Font *disp_font = F_LoadFontAsync(Lit("font/fixedsys.ttf"), 12.0f);
|
|
if (disp_font)
|
|
{
|
|
f32 offset_px = 10;
|
|
|
|
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"
|
|
);
|
|
String text = StringF(temp.arena, fmt,
|
|
FmtUint(e0->handle.idx),
|
|
FmtUint(e1->handle.idx),
|
|
FmtHex(point.id),
|
|
FmtFloat(point.normal_impulse),
|
|
FmtFloat(point.tangent_impulse),
|
|
FmtFloatP(point.starting_separation, 6),
|
|
FmtFloatP(data->normal.x, 6), FmtFloatP(data->normal.y, 6),
|
|
FmtUint(data->num_points));
|
|
|
|
draw_text(g->render_sig, disp_font, AddVec2(RoundVec2(MulXformV2(g->world_to_ui_xf, dbg_pt)), VEC2(0, offset_px)), text);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
/* Draw collision debug */
|
|
#if COLLIDER_DEBUG
|
|
if (HasProp(ent, Prop_CollisionDebug))
|
|
{
|
|
ContactDebugData *data = &ent->collision_debug_data;
|
|
CLD_CollisionData collision_reuslt = data->collision_result;
|
|
Entity *e0 = EntityFromId(g->ss_blended, data->e0);
|
|
Entity *e1 = EntityFromId(g->ss_blended, data->e1);
|
|
CLD_Shape e0_collider = e0->local_collider;
|
|
CLD_Shape e1_collider = e1->local_collider;
|
|
LAX e0_collider;
|
|
LAX e1_collider;
|
|
|
|
/* Draw closest points */
|
|
#if 0
|
|
{
|
|
f32 radius = 4;
|
|
u32 color = Rgba32F(1, 1, 0, 0.5);
|
|
Vec2 a = MulXformV2(g->world_to_ui_xf, data->closest0);
|
|
Vec2 b = MulXformV2(g->world_to_ui_xf, data->closest1);
|
|
D_DrawCircle(g->render_sig, a, radius, color, 10);
|
|
D_DrawCircle(g->render_sig, b, radius, color, 10);
|
|
}
|
|
#endif
|
|
|
|
/* Draw clipping */
|
|
{
|
|
f32 thickness = 4;
|
|
f32 radius = 4;
|
|
u32 color_line = Rgba32F(1, 0, 1, 0.75);
|
|
u32 color_a = Rgba32F(1, 0, 0, 0.25);
|
|
u32 color_b = Rgba32F(0, 1, 0, 0.25);
|
|
u32 color_line_clipped = Rgba32F(1, 0, 1, 1);
|
|
u32 color_a_clipped = Rgba32F(1, 0, 0, 1);
|
|
u32 color_b_clipped = Rgba32F(0, 1, 0, 1);
|
|
{
|
|
Vec2 a = MulXformV2(g->world_to_ui_xf, collision_reuslt.a0);
|
|
Vec2 b = MulXformV2(g->world_to_ui_xf, collision_reuslt.b0);
|
|
D_DrawLine(g->render_sig, a, b, thickness, color_line);
|
|
D_DrawCircle(g->render_sig, a, radius, color_a, 10);
|
|
D_DrawCircle(g->render_sig, b, radius, color_b, 10);
|
|
|
|
Vec2 a_clipped = MulXformV2(g->world_to_ui_xf, collision_reuslt.a0_clipped);
|
|
Vec2 b_clipped = MulXformV2(g->world_to_ui_xf, collision_reuslt.b0_clipped);
|
|
D_DrawLine(g->render_sig, a_clipped, b_clipped, thickness, color_line_clipped);
|
|
D_DrawCircle(g->render_sig, a_clipped, radius, color_a_clipped, 10);
|
|
D_DrawCircle(g->render_sig, b_clipped, radius, color_b_clipped, 10);
|
|
}
|
|
{
|
|
Vec2 a = MulXformV2(g->world_to_ui_xf, collision_reuslt.a1);
|
|
Vec2 b = MulXformV2(g->world_to_ui_xf, collision_reuslt.b1);
|
|
D_DrawLine(g->render_sig, a, b, thickness, color_line);
|
|
D_DrawCircle(g->render_sig, a, radius, color_a, 10);
|
|
D_DrawCircle(g->render_sig, b, radius, color_b, 10);
|
|
|
|
Vec2 a_clipped = MulXformV2(g->world_to_ui_xf, collision_reuslt.a1_clipped);
|
|
Vec2 b_clipped = MulXformV2(g->world_to_ui_xf, collision_reuslt.b1_clipped);
|
|
D_DrawLine(g->render_sig, a_clipped, b_clipped, thickness, color_line_clipped);
|
|
D_DrawCircle(g->render_sig, a_clipped, radius, color_a_clipped, 10);
|
|
D_DrawCircle(g->render_sig, b_clipped, radius, color_b_clipped, 10);
|
|
}
|
|
}
|
|
|
|
#if COLLIDER_DEBUG_DETAILED_DRAW_MENKOWSKI
|
|
Xform e0_xf = data->xf0;
|
|
Xform e1_xf = data->xf1;
|
|
|
|
#if 0
|
|
/* Only draw points with large separation */
|
|
b32 should_draw = 0;
|
|
for (u32 i = 0; i < data->num_points; ++i)
|
|
{
|
|
if (data->points[i].starting_separation < -0.1)
|
|
{
|
|
should_draw = 1;
|
|
break;
|
|
}
|
|
}
|
|
#else
|
|
b32 should_draw = 1;
|
|
#endif
|
|
|
|
if (should_draw)
|
|
{
|
|
#if 0
|
|
/* Test info */
|
|
{
|
|
F_Font *disp_font = F_LoadFontAsync(Lit("font/fixedsys.ttf"), 12.0f);
|
|
if (disp_font)
|
|
{
|
|
f32 offset_px = 10;
|
|
String fmt = Lit(
|
|
"e0 pos: (%F, %F)\n"
|
|
"e0 rot: %F\n"
|
|
"e1 pos: (%F, %F)\n"
|
|
"e1 rot: %F\n"
|
|
);
|
|
String text = StringF(temp.arena, fmt,
|
|
FmtFloatP(e0_xf.og.x, 24), FmtFloatP(e0_xf.og.y, 24),
|
|
FmtFloatP(RotationFromXform(e0_xf), 24),
|
|
FmtFloatP(e1_xf.og.x, 24), FmtFloatP(e1_xf.og.y, 24),
|
|
FmtFloatP(RotationFromXform(e1_xf), 24));
|
|
|
|
draw_text(g->render_sig, disp_font, AddVec2(RoundVec2(MulXformV2(g->world_to_ui_xf, VEC2(0, 0))), VEC2(0, offset_px)), text);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Draw CLD_Menkowski */
|
|
{
|
|
|
|
u32 color = collision_reuslt.solved ? Rgba32F(0, 0, 0.25, 1) : Rgba32F(0, 0.25, 0.25, 1);
|
|
f32 thickness = 2;
|
|
u32 detail = 512;
|
|
LAX thickness;
|
|
|
|
Vec2Array m = CLD_Menkowski(temp.arena, &e0_collider, &e1_collider, e0_xf, e1_xf, detail);
|
|
|
|
for (u64 i = 0; i < m.count; ++i) m.points[i] = MulXformV2(g->world_to_ui_xf, m.points[i]);
|
|
D_DrawPolyLine(g->render_sig, m, 1, thickness, color);
|
|
//D_DrawPoly(g->render_sig, m, color);
|
|
}
|
|
|
|
/* Draw CLD_PointCloud */
|
|
{
|
|
u32 color = Rgba32F(1, 1, 1, 1);
|
|
f32 radius = 2;
|
|
|
|
Vec2Array m = CLD_PointCloud(temp.arena, &e0_collider, &e1_collider, e0_xf, e1_xf);
|
|
|
|
for (u64 i = 0; i < m.count; ++i)
|
|
{
|
|
Vec2 p = MulXformV2(g->world_to_ui_xf, m.points[i]);
|
|
D_DrawCircle(g->render_sig, p, radius, color, 10);
|
|
}
|
|
}
|
|
|
|
/* Draw prototype */
|
|
{
|
|
f32 thickness = 2;
|
|
u32 color = Rgba32F(1, 1, 1, 0.25);
|
|
|
|
Vec2Array m = {
|
|
.points = collision_reuslt.prototype.points,
|
|
.count = collision_reuslt.prototype.len
|
|
};
|
|
for (u64 i = 0; i < m.count; ++i) m.points[i] = MulXformV2(g->world_to_ui_xf, m.points[i]);
|
|
D_DrawPolyLine(g->render_sig, m, 1, thickness, color);
|
|
for (u64 i = 0; i < m.count; ++i) D_DrawCircle(g->render_sig, m.points[i], 10, color, 10);
|
|
}
|
|
|
|
/* Draw simplex */
|
|
{
|
|
f32 thickness = 2;
|
|
u32 line_color = ColorYellow;
|
|
u32 color_first = Rgba32F(1, 0, 0, 0.75);
|
|
u32 color_second = Rgba32F(0, 1, 0, 0.75);
|
|
u32 color_third = Rgba32F(0, 0, 1, 0.75);
|
|
|
|
CLD_MenkowskiSimplex simplex = collision_reuslt.simplex;
|
|
Vec2 simplex_points[] = { simplex.a.p, simplex.b.p, simplex.c.p };
|
|
for (u64 i = 0; i < countof(simplex_points); ++i) simplex_points[i] = MulXformV2(g->world_to_ui_xf, simplex_points[i]);
|
|
Vec2Array 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);
|
|
D_DrawCircle(g->render_sig, simplex_array.points[0], thickness * 3, color, 10);
|
|
}
|
|
if (simplex.len >= 2)
|
|
{
|
|
u32 color = simplex.len == 2 ? color_first : color_second;
|
|
D_DrawCircle(g->render_sig, simplex_array.points[1], thickness * 3, color, 10);
|
|
}
|
|
if (simplex.len >= 3)
|
|
{
|
|
u32 color = color_first;
|
|
D_DrawCircle(g->render_sig, simplex_array.points[2], thickness * 3, color, 10);
|
|
}
|
|
if (simplex.len >= 2)
|
|
{
|
|
D_DrawPolyLine(g->render_sig, simplex_array, simplex.len > 2, thickness, line_color);
|
|
}
|
|
}
|
|
|
|
/* Draw normal */
|
|
{
|
|
u32 color = ColorWhite;
|
|
f32 len = 0.1f;
|
|
f32 arrow_thickness = 4;
|
|
f32 arrowhead_height = 10;
|
|
Vec2 start = MulXformV2(g->world_to_ui_xf, VEC2(0, 0));
|
|
Vec2 end = MulXformV2(g->world_to_ui_xf, MulVec2(NormVec2(collision_reuslt.normal), len));
|
|
D_DrawArrowLine(g->render_sig, start, end, arrow_thickness, arrowhead_height, color);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
/* Draw hierarchy */
|
|
/* FIXME: Enable this */
|
|
#if 0
|
|
if (HasProp(parent, Prop_Active) && !parent->is_root)
|
|
{
|
|
u32 color = Rgba32F(0.6, 0.6, 1, 0.75);
|
|
f32 thickness = 2;
|
|
f32 arrow_height = 15;
|
|
|
|
Vec2 start = MulXformV2(g->world_to_ui_xf, xf.og);
|
|
Vec2 end = MulXformV2(g->world_to_ui_xf, parent_xf.og);
|
|
D_DrawArrowLine(g->render_sig, start, end, thickness, arrow_height, color);
|
|
}
|
|
|
|
/* Draw camera rect */
|
|
if (HasProp(ent, Prop_Camera))
|
|
{
|
|
u32 color = ent == local_camera ? Rgba32F(1, 1, 1, 0.5) : Rgba32F(0, 0.75, 0, 0.5);
|
|
f32 thickness = 3;
|
|
|
|
Xform quad_xf = MulXform(xf, ent->camera_quad_xform);
|
|
Quad quad = MulXformQuad(quad_xf, CenteredUnitSquareQuad);
|
|
quad = MulXformQuad(g->world_to_ui_xf, quad);
|
|
|
|
D_DrawQuadLine(g->render_sig, quad, thickness, color);
|
|
}
|
|
#endif
|
|
|
|
EndTempArena(temp);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Draw crosshair or show cursor */
|
|
/* FIXME: Enable this */
|
|
#if 0
|
|
if (!g->debug_camera)
|
|
{
|
|
__profn("Draw crosshair");
|
|
Vec2 crosshair_pos = g->ui_cursor;
|
|
Resource crosshair = ResourceFromStore(GameResources, Lit("sprite/crosshair.ase"));
|
|
S_Texture *t = S_TextureFromResourceAsync(crosshair);
|
|
Vec2 size = VEC2(t->width, t->height);
|
|
Xform xf = XformFromTrs(TRS(.t = crosshair_pos, .s = size));
|
|
D_DrawUiRect(g->render_sig, D_UIRECTPARAMS(.xf = xf, .texture = t->gpu_resource));
|
|
}
|
|
#endif
|
|
|
|
/* FIXME: Enable this */
|
|
#if 0
|
|
{
|
|
__profn("Update window cursor");
|
|
if (g->debug_camera)
|
|
{
|
|
P_DisableWindoweCursorClip(g->window);
|
|
P_ShowWindowCursor(g->window);
|
|
}
|
|
else
|
|
{
|
|
S_Texture *t = S_TextureFromResourceAsync(ResourceFromStore(GameResources, Lit("sprite/crosshair.ase")));
|
|
Vec2 size = VEC2(t->width, t->height);
|
|
Rect cursor_clip = RectFromVec2(g->ui_screen_offset, g->ui_size);
|
|
cursor_clip.pos = AddVec2(cursor_clip.pos, MulVec2(size, 0.5f));
|
|
cursor_clip.pos = AddVec2(cursor_clip.pos, VEC2(1, 1));
|
|
cursor_clip.size = SubVec2(cursor_clip.size, size);
|
|
P_HideWindowCursor(g->window);
|
|
P_EnableWindoweCursorClip(g->window, cursor_clip);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//- Create user sim cmd
|
|
|
|
{
|
|
/* Queue player move cmd */
|
|
f32 move_speed = 1.0f;
|
|
//if (g->bind_states[BindKind_Walk].is_held)
|
|
if (g->bind_states[BindKind_FullscreenMod].is_held)
|
|
{
|
|
//const f32 walk_ratio = 0.25f;
|
|
const f32 walk_ratio = 0.05f;
|
|
move_speed *= walk_ratio;
|
|
}
|
|
|
|
Vec2 input_move_dir = ZI;
|
|
{
|
|
for (BindKind bind = 0; bind < (i32)countof(g->bind_states); ++bind)
|
|
{
|
|
BindState state = g->bind_states[bind];
|
|
|
|
if (!state.is_held && state.num_presses <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
switch (bind)
|
|
{
|
|
default: break;
|
|
|
|
/* Movement */
|
|
case BindKind_MoveUp:
|
|
{
|
|
input_move_dir.y -= 1;
|
|
} break;
|
|
|
|
case BindKind_MoveDown:
|
|
{
|
|
input_move_dir.y += 1;
|
|
} break;
|
|
|
|
case BindKind_MoveLeft:
|
|
{
|
|
input_move_dir.x -= 1;
|
|
} break;
|
|
|
|
case BindKind_MoveRight:
|
|
{
|
|
input_move_dir.x += 1;
|
|
} break;
|
|
}
|
|
}
|
|
|
|
input_move_dir = InvertXformBasisMulV2(g->world_to_ui_xf, input_move_dir); /* Make move dir relative to world view */
|
|
input_move_dir = MulVec2(NormVec2(input_move_dir), move_speed);
|
|
}
|
|
|
|
if (!g->debug_camera)
|
|
{
|
|
g->focus_send = SubVec2(g->world_cursor, XformFromEntity(local_control).og);
|
|
}
|
|
Vec2 input_aim_dir = g->focus_send;
|
|
|
|
/* Queue player control cmd */
|
|
{
|
|
ControlData control = ZI;
|
|
control.move = input_move_dir;
|
|
control.focus = input_aim_dir;
|
|
control.dbg_cursor = g->world_cursor;
|
|
|
|
BindState fire_state = g->bind_states[BindKind_Fire];
|
|
BindState fire_alt_state = g->bind_states[BindKind_AltFire];
|
|
BindState drag_state = g->bind_states[BindKind_DebugDrag];
|
|
BindState delete_state = g->bind_states[BindKind_DebugDelete];
|
|
BindState clear_state = g->bind_states[BindKind_DebugClear];
|
|
BindState spawn1_state = g->bind_states[BindKind_DebugSpawn1];
|
|
BindState spawn2_state = g->bind_states[BindKind_DebugSpawn2];
|
|
BindState spawn3_state = g->bind_states[BindKind_DebugSpawn3];
|
|
BindState spawn4_state = g->bind_states[BindKind_DebugSpawn4];
|
|
BindState walls_state = g->bind_states[BindKind_DebugWalls];
|
|
BindState pause_state = g->bind_states[BindKind_DebugPause];
|
|
BindState step_state = g->bind_states[BindKind_DebugStep];
|
|
BindState tile_state = g->bind_states[BindKind_TestTile];
|
|
BindState explode_state = g->bind_states[BindKind_DebugExplode];
|
|
BindState teleport_state = g->bind_states[BindKind_DebugTeleport];
|
|
|
|
if (fire_state.num_presses || fire_state.is_held)
|
|
{
|
|
control.flags |= ControlFlag_Fire;
|
|
}
|
|
if (fire_alt_state.num_presses || fire_alt_state.is_held)
|
|
{
|
|
control.flags |= ControlFlag_AltFire;
|
|
}
|
|
if (drag_state.num_presses || drag_state.is_held)
|
|
{
|
|
control.flags |= ControlFlag_Drag;
|
|
}
|
|
if (delete_state.num_presses || delete_state.is_held)
|
|
{
|
|
control.flags |= ControlFlag_Delete;
|
|
}
|
|
if (clear_state.num_presses_and_repeats)
|
|
{
|
|
control.flags |= ControlFlag_ClearAll;
|
|
}
|
|
if (spawn1_state.num_presses_and_repeats)
|
|
{
|
|
control.flags |= ControlFlag_SpawnTest1;
|
|
}
|
|
if (spawn2_state.num_presses_and_repeats)
|
|
{
|
|
control.flags |= ControlFlag_SpawnTest2;
|
|
}
|
|
if (spawn3_state.num_presses_and_repeats)
|
|
{
|
|
control.flags |= ControlFlag_SpawnTest3;
|
|
}
|
|
if (spawn4_state.num_presses_and_repeats)
|
|
{
|
|
control.flags |= ControlFlag_SpawnTest4;
|
|
}
|
|
if (walls_state.num_presses_and_repeats)
|
|
{
|
|
control.flags |= ControlFlag_TestWalls;
|
|
}
|
|
if (tile_state.num_presses || tile_state.is_held)
|
|
{
|
|
control.flags |= ControlFlag_TestTiles;
|
|
}
|
|
if (explode_state.num_presses_and_repeats)
|
|
{
|
|
control.flags |= ControlFlag_TestExplode;
|
|
}
|
|
if (teleport_state.num_presses_and_repeats || (g->debug_camera && teleport_state.is_held))
|
|
{
|
|
control.flags |= ControlFlag_TestTeleport;
|
|
}
|
|
|
|
if (pause_state.num_presses)
|
|
{
|
|
Atomic32FetchXor(&g->user_paused, 1);
|
|
}
|
|
Atomic32FetchAdd(&g->user_paused_steps, step_state.num_presses_and_repeats);
|
|
|
|
/* Set user sim control */
|
|
{
|
|
Lock lock = LockE(&g->user_sim_cmd_mutex);
|
|
|
|
/* Reset flags */
|
|
if (g->user_sim_cmd_gen != g->last_user_sim_cmd_gen)
|
|
{
|
|
g->user_sim_cmd_control.flags = 0;
|
|
g->last_user_sim_cmd_gen = g->user_sim_cmd_gen;
|
|
}
|
|
|
|
u32 old_flags = g->user_sim_cmd_control.flags;
|
|
g->user_sim_cmd_control = control;
|
|
g->user_sim_cmd_control.flags |= old_flags;
|
|
g->user_hovered_ent = hovered_ent->id;
|
|
Unlock(&lock);
|
|
}
|
|
}
|
|
|
|
#if RtcIsEnabled
|
|
/* Gjk steps */
|
|
{
|
|
if (g->bind_states[BindKind_ResetDebugSteps].num_presses_and_repeats > 0)
|
|
{
|
|
SetGstat(GSTAT_DEBUG_STEPS, 0);
|
|
}
|
|
i32 add_steps = 0;
|
|
add_steps += g->bind_states[BindKind_IncrementDebugSteps].num_presses_and_repeats;
|
|
add_steps -= g->bind_states[BindKind_DecrementDebugSteps].num_presses_and_repeats;
|
|
if (add_steps != 0)
|
|
{
|
|
AddGstat(GSTAT_DEBUG_STEPS, add_steps);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
{
|
|
/* Update network usage stats */
|
|
i64 stat_now_ns = TimeNs();
|
|
g->net_bytes_read.last_second_end = GetGstat(GSTAT_SOCK_BYTES_RECEIVED);
|
|
g->net_bytes_sent.last_second_end = GetGstat(GSTAT_SOCK_BYTES_SENT);
|
|
if (stat_now_ns - g->last_second_reset_ns > NsFromSeconds(1))
|
|
{
|
|
g->last_second_reset_ns = stat_now_ns;
|
|
g->net_bytes_read.last_second = g->net_bytes_read.last_second_end - g->net_bytes_read.last_second_start;
|
|
g->net_bytes_sent.last_second = g->net_bytes_sent.last_second_end - g->net_bytes_sent.last_second_start;
|
|
g->net_bytes_read.last_second_start = g->net_bytes_read.last_second_end;
|
|
g->net_bytes_sent.last_second_start = g->net_bytes_sent.last_second_end;
|
|
}
|
|
}
|
|
|
|
//- Draw ent debug info
|
|
/* FIXME: Enable this */
|
|
#if 0
|
|
if (g->debug_draw && hovered_ent->valid)
|
|
{
|
|
Entity *ent = hovered_ent;
|
|
|
|
Vec2 pos = AddVec2(g->ui_cursor, VEC2(15, 15));
|
|
F_Font *font = F_LoadFontAsync(Lit("font/fixedsys.ttf"), 12.0f);
|
|
if (font)
|
|
{
|
|
TempArena temp = BeginTempArena(scratch.arena);
|
|
|
|
String dbg_text = ZI;
|
|
dbg_text.text = PushDry(temp.arena, u8);
|
|
dbg_text.len += DebugStringFromEntity(temp.arena, ent).len;
|
|
draw_text(g->render_sig, D_TEXTPARAMS(.font = font, .pos = pos, .str = dbg_text));
|
|
|
|
EndTempArena(temp);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//- Query vram
|
|
|
|
GPU_MemoryInfo vram = GPU_QueryMemoryInfo();
|
|
|
|
//- Draw global debug info
|
|
|
|
/* FIXME: Enable this */
|
|
#if 0
|
|
if (g->debug_draw)
|
|
{
|
|
__profn("Draw debug info");
|
|
F_Font *font = F_LoadFontAsync(Lit("font/fixedsys.ttf"), 12.0f);
|
|
if (font)
|
|
{
|
|
TempArena temp = BeginTempArena(scratch.arena);
|
|
String text = ZI;
|
|
text.text = PushDry(temp.arena, u8);
|
|
|
|
#if BITBUFF_DEBUG
|
|
text.len += StringF(temp.arena, "(bitbuff debug enabled)").len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
#endif
|
|
|
|
text.len += StringF(temp.arena, "blended world entities: %F/%F", FmtUint(g->ss_blended->num_ents_allocated), FmtUint(g->ss_blended->num_ents_reserved)).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "blended world tick: %F", FmtUint(g->ss_blended->tick)).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "blended world time: %F", FmtFloat(SecondsFromNs(g->ss_blended->sim_time_ns))).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "average local sim publish dt: %F", FmtFloat(SecondsFromNs(g->average_local_to_user_snapshot_publish_dt_ns))).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "local sim last known tick: %F", FmtUint(g->local_sim_last_known_tick)).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "local sim last known time: %F", FmtFloat(SecondsFromNs(g->local_sim_last_known_time_ns))).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "local sim predicted time: %F", FmtFloat(SecondsFromNs(g->local_sim_predicted_time_ns))).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "render time target: %F", FmtFloat(SecondsFromNs(g->render_time_target_ns))).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "render time: %F", FmtFloat(SecondsFromNs(g->render_time_ns))).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "local player: [%F]", FmtUid(local_player->id.uid)).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
Vec2 world_cursor = g->world_cursor;
|
|
text.len += StringF(temp.arena, "cursor world: %F, %F", FmtFloat(world_cursor.x), FmtFloat(world_cursor.y)).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
Vec2I32 world_tile_cursor = WorldTileIndexFromPos(world_cursor);
|
|
text.len += StringF(temp.arena, "cursor world tile: %F, %F", FmtSint(world_tile_cursor.x), FmtSint(world_tile_cursor.y)).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
Vec2I32 local_tile_cursor = LocalTileIndexFromWorldTileIndex(world_tile_cursor);
|
|
text.len += StringF(temp.arena, "cursor local tile: %F, %F", FmtSint(local_tile_cursor.x), FmtSint(local_tile_cursor.y)).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
Vec2I32 tile_chunk_cursor = TileChunkIndexFromWorldTileIndex(world_tile_cursor);
|
|
text.len += StringF(temp.arena, "cursor tile chunk: %F, %F", FmtSint(tile_chunk_cursor.x), FmtSint(tile_chunk_cursor.y)).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "Network read: %F mbit/s", FmtFloat((f64)g->net_bytes_read.last_second * 8 / 1000 / 1000)).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "Network write: %F mbit/s", FmtFloat((f64)g->net_bytes_sent.last_second * 8 / 1000 / 1000)).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "Ping (real): %F ms", FmtFloat(SecondsFromNs(local_player->player_last_rtt_ns) * 1000)).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "Ping (average): %F ms", FmtFloat(local_player->player_average_rtt_seconds * 1000)).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "Memory committed: %F MiB", FmtFloat((f64)GetGstat(GSTAT_MEMORY_COMMITTED) / 1024 / 1024)).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "Virtual memory reserved: %F TiB", FmtFloat((f64)GetGstat(GSTAT_MEMORY_RESERVED) / 1024 / 1024 / 1024 / 1024)).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "Arenas allocated: %F", FmtUint(GetGstat(GSTAT_NUM_ARENAS))).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
|
|
text.len += StringF(temp.arena, "Video memory (GPU): %F MiB", FmtFloat((f64)vram.local_used / 1024 / 1024)).len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
text.len += StringF(temp.arena, "Video memory (shared): %F MiB", FmtFloat((f64)vram.non_local_used / 1024 / 1024)).len;
|
|
//text.len += StringF(temp.arena, \n")).len;
|
|
//text.len += StringF(temp.arena, \n")).len;
|
|
|
|
#if RtcIsEnabled
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
text.len += StringF(temp.arena, "\n").len;
|
|
text.len += StringF(temp.arena, "Debug steps: %F", FmtUint(GetGstat(GSTAT_DEBUG_STEPS))).len;
|
|
//text.len += StringF(temp.arena, \n")).len;
|
|
|
|
#endif
|
|
//draw_text(g->render_sig, font, pos, StringF(temp.arena, "blended world entities: %F/%F", FmtUint(g->ss_blended->num_ents_allocated), FmtUint(g->ss_blended->num_ents_reserved)));
|
|
//draw_text(g->render_sig, font, pos, text);
|
|
|
|
Vec2 pos = VEC2(10, g->ui_size.y);
|
|
D_TextOffsetY offset_y = DRAW_TEXT_OFFSET_Y_BOTTOM;
|
|
draw_text(g->render_sig, D_TEXTPARAMS(.font = font, .pos = pos, .str = text, .offset_y = offset_y, .color = ColorWhite));
|
|
EndTempArena(temp);
|
|
}
|
|
}
|
|
#else
|
|
LAX vram;
|
|
#endif
|
|
|
|
{
|
|
#if DeveloperIsEnabled
|
|
b32 console_minimized = !g->debug_console;
|
|
i32 console_level = console_minimized ? P_LogLevel_Success : P_LogLevel_Debug;
|
|
DrawDebugConsole(console_level, console_minimized);
|
|
#else
|
|
if (g->debug_draw)
|
|
{
|
|
DrawDebugConsole(P_LogLevel_Info, 0);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//- Render
|
|
|
|
{
|
|
__profn("Render");
|
|
Rect ui_viewport = RectFromVec2(VEC2(0, 0), VEC2(g->ui_size.x, g->ui_size.y));
|
|
Rect render_viewport = RectFromVec2(VEC2(0, 0), VEC2(g->render_size.x, g->render_size.y));
|
|
|
|
/* Acquire gbuffers */
|
|
if (g->shade_target && !EqVec2I32(g->render_size, GPU_GetTextureSize(g->shade_target)))
|
|
{
|
|
__profn("Release render resources");
|
|
GPU_ReleaseResource(g->albedo, g->render_fence, GPU_ReleaseFlag_None);
|
|
GPU_ReleaseResource(g->emittance, g->render_fence, GPU_ReleaseFlag_None);
|
|
GPU_ReleaseResource(g->emittance_flood_read, g->render_fence, GPU_ReleaseFlag_None);
|
|
GPU_ReleaseResource(g->emittance_flood_target, g->render_fence, GPU_ReleaseFlag_None);
|
|
GPU_ReleaseResource(g->shade_read, g->render_fence, GPU_ReleaseFlag_None);
|
|
GPU_ReleaseResource(g->shade_target, g->render_fence, GPU_ReleaseFlag_None);
|
|
g->shade_target = 0;
|
|
}
|
|
if (!g->shade_target)
|
|
{
|
|
__profn("Acquire sig resources");
|
|
g->albedo = AcquireGbuffer(GPU_Format_R8G8B8A8_Unorm, g->render_size);
|
|
g->emittance = AcquireGbuffer(GPU_Format_R16G16B16A16_Float, g->render_size);
|
|
g->emittance_flood_read = AcquireGbuffer(GPU_Format_R16G16_Uint, g->render_size);
|
|
g->emittance_flood_target = AcquireGbuffer(GPU_Format_R16G16_Uint, g->render_size);
|
|
g->shade_read = AcquireGbuffer(GPU_Format_R16G16B16A16_Float, g->render_size);
|
|
g->shade_target = AcquireGbuffer(GPU_Format_R16G16B16A16_Float, g->render_size);
|
|
}
|
|
|
|
/* Acquire ui buffers */
|
|
if (g->ui_target && !EqVec2I32(g->ui_size, GPU_GetTextureSize(g->ui_target)))
|
|
{
|
|
GPU_ReleaseResource(g->ui_target, g->render_fence, GPU_ReleaseFlag_None);
|
|
g->ui_target = 0;
|
|
}
|
|
if (!g->ui_target)
|
|
{
|
|
g->ui_target = AcquireGbuffer(GPU_Format_R8G8B8A8_Unorm, g->ui_size);
|
|
}
|
|
|
|
/* Acquire transfer buffers */
|
|
/* TODO: Make these static */
|
|
u16 quad_indices[6] = { 0, 1, 2, 0, 2, 3 };
|
|
GPU_Resource *quad_index_buffer = AcquireTransferBuffer(countof(quad_indices), sizeof(*quad_indices), quad_indices);
|
|
GPU_Resource *material_instance_buffer = AcquireTransferBufferFromArena(g->material_instances_count, g->material_instances_arena);
|
|
GPU_Resource *ui_rect_instance_buffer = AcquireTransferBufferFromArena(g->ui_rect_instances_count, g->ui_rect_instances_arena);
|
|
GPU_Resource *ui_shape_verts_buffer = AcquireTransferBufferFromArena(g->ui_shape_verts_count, g->ui_shape_verts_arena);
|
|
GPU_Resource *ui_shape_indices_buffer = AcquireTransferBufferFromArena(g->ui_shape_indices_count, g->ui_shape_indices_arena);
|
|
GPU_Resource *grids_buffer = AcquireTransferBufferFromArena(g->grids_count, g->grids_arena);
|
|
|
|
GPU_CommandList *cl = GPU_BeginCommandList();
|
|
{
|
|
__profn("Run render");
|
|
GPU_ProfN(cl, Lit("Run render"));
|
|
Mat4x4 world_to_render_vp_matrix = ProjectMat4x4View(g->world_to_render_xf, render_viewport.width, render_viewport.height);
|
|
Mat4x4 ui_vp_matrix = ProjectMat4x4View(XformIdentity, ui_viewport.width, ui_viewport.height);
|
|
Mat4x4 blit_vp_matrix = ZI;
|
|
{
|
|
Xform xf = g->render_to_ui_xf;
|
|
xf = ScaleXform(xf, VEC2(g->render_size.x, g->render_size.y));
|
|
xf = TranslateXform(xf, VEC2(0.5, 0.5));
|
|
blit_vp_matrix = ProjectMat4x4View(xf, ui_viewport.width, ui_viewport.height);
|
|
}
|
|
|
|
//- Prep material pass
|
|
{
|
|
__profn("Clear gbuffers");
|
|
GPU_ProfN(cl, Lit("Clear gbuffers"));
|
|
GPU_TransitionToRtv(cl, g->albedo);
|
|
GPU_TransitionToRtv(cl, g->emittance);
|
|
GPU_ClearResource(cl, g->albedo, VEC4(0, 0, 0, 0));
|
|
GPU_ClearResource(cl, g->emittance, VEC4(0, 0, 0, 0));
|
|
}
|
|
|
|
//- Material pass
|
|
{
|
|
__profn("Material pass");
|
|
GPU_ProfN(cl, Lit("Material pass"));
|
|
|
|
GPU_Resource *rts[] = {
|
|
g->albedo,
|
|
g->emittance
|
|
};
|
|
GPU_Viewport viewport = GPU_ViewportFromRect(render_viewport);
|
|
GPU_Scissor scissor = GPU_ScissorFromRect(render_viewport);
|
|
|
|
MaterialSig sig = ZI;
|
|
/* FIXME: set sampler urid id here */
|
|
sig.projection = world_to_render_vp_matrix;
|
|
sig.instances_urid = GPU_GetResourceId(material_instance_buffer);
|
|
sig.grids_urid = GPU_GetResourceId(grids_buffer);
|
|
GPU_Rasterize(cl,
|
|
&sig,
|
|
MaterialVS, MaterialPS,
|
|
countof(rts), rts,
|
|
viewport,
|
|
scissor,
|
|
g->material_instances_count,
|
|
quad_index_buffer,
|
|
GPU_RasterizeMode_TriangleList);
|
|
}
|
|
|
|
//- Prep flood pass
|
|
{
|
|
GPU_TransitionToSrv(cl, g->emittance);
|
|
GPU_TransitionToUav(cl, g->emittance_flood_read);
|
|
GPU_TransitionToUav(cl, g->emittance_flood_target);
|
|
}
|
|
|
|
//- Flood pass
|
|
if (!effects_disabled)
|
|
{
|
|
__profn("Flood pass");
|
|
GPU_ProfN(cl, Lit("Flood pass"));
|
|
|
|
i32 step_length = -1;
|
|
|
|
/* TODO: Remove this */
|
|
u64 max_steps = GetGstat(GSTAT_DEBUG_STEPS);
|
|
u64 step = 0;
|
|
while (step_length != 0 && step < max_steps)
|
|
{
|
|
__profn("Flood step");
|
|
GPU_ProfN(cl, Lit("Flood step"));
|
|
|
|
GPU_FlushUav(cl, g->emittance_flood_read);
|
|
|
|
FloodSig sig = ZI;
|
|
sig.step_len = step_length;
|
|
sig.emittance_tex_urid = GPU_GetResourceId(g->emittance);
|
|
sig.read_flood_tex_urid = GPU_GetResourceId(g->emittance_flood_read);
|
|
sig.target_flood_tex_urid = GPU_GetResourceId(g->emittance_flood_target);
|
|
sig.tex_width = g->render_size.x;
|
|
sig.tex_height = g->render_size.y;
|
|
GPU_Compute(cl, &sig, FloodCS, (g->render_size.x + 7) / 8, (g->render_size.y + 7) / 8, 1);
|
|
|
|
/* Swap buffers */
|
|
GPU_Resource *swp = g->emittance_flood_read;
|
|
g->emittance_flood_read = g->emittance_flood_target;
|
|
g->emittance_flood_target = swp;
|
|
|
|
/* Update step */
|
|
if (step_length == -1)
|
|
{
|
|
step_length = MaxI32(g->render_size.x, g->render_size.y) / 2;
|
|
}
|
|
else
|
|
{
|
|
step_length /= 2;
|
|
}
|
|
++step;
|
|
}
|
|
}
|
|
|
|
//- Prep shade pass
|
|
{
|
|
__profn("Clear shade target");
|
|
GPU_ProfN(cl, Lit("Clear shade target"));
|
|
GPU_TransitionToSrv(cl, g->albedo);
|
|
GPU_TransitionToSrv(cl, g->emittance);
|
|
GPU_TransitionToUav(cl, g->shade_target);
|
|
GPU_FlushUav(cl, g->emittance_flood_read);
|
|
GPU_FlushUav(cl, g->shade_read);
|
|
GPU_ClearResource(cl, g->shade_target, VEC4(0, 0, 0, 0));
|
|
}
|
|
|
|
//- Shade pass
|
|
{
|
|
__profn("Shade pass");
|
|
GPU_ProfN(cl, Lit("Shade pass"));
|
|
|
|
u32 shade_flags = ShadeFlag_None;
|
|
if (effects_disabled)
|
|
{
|
|
shade_flags |= ShadeFlag_DisableEffects;
|
|
}
|
|
ShadeSig sig = ZI;
|
|
sig.flags = shade_flags;
|
|
sig.tex_width = g->render_size.x;
|
|
sig.tex_height = g->render_size.y;
|
|
sig.frame_seed = VEC4I32((u32)(RandU64FromState(&g->frame_rand) & 0xFFFFFFFF),
|
|
(u32)(RandU64FromState(&g->frame_rand) & 0xFFFFFFFF),
|
|
(u32)(RandU64FromState(&g->frame_rand) & 0xFFFFFFFF),
|
|
(u32)(RandU64FromState(&g->frame_rand) & 0xFFFFFFFF));
|
|
sig.frame_index = g->frame_index;
|
|
sig.camera_offset = g->world_to_render_xf.og;
|
|
sig.albedo_tex_urid = GPU_GetResourceId(g->albedo);
|
|
sig.emittance_tex_urid = GPU_GetResourceId(g->emittance);
|
|
sig.emittance_flood_tex_urid = GPU_GetResourceId(g->emittance_flood_read);
|
|
sig.read_tex_urid = GPU_GetResourceId(g->shade_read);
|
|
sig.target_tex_urid = GPU_GetResourceId(g->shade_target);
|
|
GPU_Compute(cl, &sig, ShadeCS, (g->render_size.x + 7) / 8, (g->render_size.y + 7) / 8, 1);
|
|
|
|
/* Swap */
|
|
GPU_Resource *swp = g->shade_read;
|
|
g->shade_read = g->shade_target;
|
|
g->shade_target = swp;
|
|
}
|
|
|
|
//- Prep ui pass
|
|
{
|
|
__profn("Clear ui target");
|
|
GPU_ProfN(cl, Lit("Clear ui target"));
|
|
GPU_TransitionToRtv(cl, g->ui_target);
|
|
GPU_FlushUav(cl, g->shade_read);
|
|
GPU_ClearResource(cl, g->ui_target, VEC4(0, 0, 0, 0));
|
|
}
|
|
|
|
//- Ui blit pass
|
|
{
|
|
__profn("UI blit pass");
|
|
GPU_ProfN(cl, Lit("UI blit pass"));
|
|
|
|
GPU_Viewport viewport = GPU_ViewportFromRect(ui_viewport);
|
|
GPU_Scissor scissor = GPU_ScissorFromRect(ui_viewport);
|
|
|
|
UiBlitSig sig = ZI;
|
|
sig.projection = blit_vp_matrix;
|
|
sig.flags = UiBlitFlag_ToneMap | UiBlitFlag_GammaCorrect;
|
|
sig.exposure = 2.0;
|
|
sig.gamma = (f32)2.2;
|
|
sig.tex_urid = GPU_GetResourceId(g->shade_read);
|
|
GPU_Rasterize(cl,
|
|
&sig,
|
|
UiBlitVS, UiBlitPS,
|
|
1, &g->ui_target,
|
|
viewport,
|
|
scissor,
|
|
1,
|
|
quad_index_buffer,
|
|
GPU_RasterizeMode_TriangleList);
|
|
}
|
|
|
|
//- Ui rect pass
|
|
{
|
|
__profn("UI rect pass");
|
|
GPU_ProfN(cl, Lit("UI rect pass"));
|
|
|
|
GPU_Viewport viewport = GPU_ViewportFromRect(ui_viewport);
|
|
GPU_Scissor scissor = GPU_ScissorFromRect(ui_viewport);
|
|
|
|
UiRectSig sig = ZI;
|
|
sig.projection = ui_vp_matrix;
|
|
sig.instances_urid = GPU_GetResourceId(ui_rect_instance_buffer);
|
|
GPU_Rasterize(cl,
|
|
&sig,
|
|
UiRectVS, UiRectPS,
|
|
1, &g->ui_target,
|
|
viewport,
|
|
scissor,
|
|
g->ui_rect_instances_count,
|
|
quad_index_buffer,
|
|
GPU_RasterizeMode_TriangleList);
|
|
}
|
|
|
|
//- Ui shape pass
|
|
{
|
|
__profn("UI shape pass");
|
|
GPU_ProfN(cl, Lit("UI shape pass"));
|
|
|
|
GPU_Viewport viewport = GPU_ViewportFromRect(ui_viewport);
|
|
GPU_Scissor scissor = GPU_ScissorFromRect(ui_viewport);
|
|
|
|
UiShapeSig sig = ZI;
|
|
sig.projection = ui_vp_matrix;
|
|
sig.verts_urid = GPU_GetResourceId(ui_shape_verts_buffer);
|
|
GPU_Rasterize(cl,
|
|
&sig,
|
|
UiShapeVS, UiShapePS,
|
|
1, &g->ui_target,
|
|
viewport,
|
|
scissor,
|
|
1,
|
|
ui_shape_indices_buffer,
|
|
GPU_RasterizeMode_TriangleList);
|
|
}
|
|
}
|
|
g->render_fence = GPU_EndCommandList(cl);
|
|
|
|
/* Release transfer buffers */
|
|
{
|
|
GPU_ReleaseResource(quad_index_buffer, g->render_fence, GPU_ReleaseFlag_Reuse);
|
|
GPU_ReleaseResource(material_instance_buffer, g->render_fence, GPU_ReleaseFlag_Reuse);
|
|
GPU_ReleaseResource(ui_rect_instance_buffer, g->render_fence, GPU_ReleaseFlag_Reuse);
|
|
GPU_ReleaseResource(ui_shape_verts_buffer, g->render_fence, GPU_ReleaseFlag_Reuse);
|
|
GPU_ReleaseResource(ui_shape_indices_buffer, g->render_fence, GPU_ReleaseFlag_Reuse);
|
|
GPU_ReleaseResource(grids_buffer, g->render_fence, GPU_ReleaseFlag_Reuse);
|
|
ResetArena(g->material_instances_arena);
|
|
ResetArena(g->ui_rect_instances_arena);
|
|
ResetArena(g->ui_shape_verts_arena);
|
|
ResetArena(g->ui_shape_indices_arena);
|
|
ResetArena(g->grids_arena);
|
|
g->material_instances_count = 0;
|
|
g->ui_rect_instances_count = 0;
|
|
g->ui_shape_verts_count = 0;
|
|
g->ui_shape_indices_count = 0;
|
|
g->grids_count = 0;
|
|
}
|
|
}
|
|
|
|
EndScratch(scratch);
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ User update job
|
|
|
|
JobDef(UpdateUserOrSleep, UNUSED sig, UNUSED id)
|
|
{
|
|
SharedUserState *g = &shared_user_state;
|
|
i64 time_ns = TimeNs();
|
|
while (!Atomic32Fetch(&g->shutdown))
|
|
{
|
|
P_Window *window = g->window;
|
|
{
|
|
__profn("User sleep");
|
|
{
|
|
__profn("Swapchain wait");
|
|
GPU_WaitOnSwapchain(g->swapchain);
|
|
}
|
|
{
|
|
__profn("Frame limiter wait");;
|
|
P_SleepFrame(time_ns, 1000000000 / FPS_LIMIT);
|
|
time_ns = TimeNs();
|
|
}
|
|
}
|
|
UpdateUser(window);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ Generate user input cmds
|
|
|
|
void GenerateuserInputCmds(Client *user_input_client, u64 tick)
|
|
{
|
|
SharedUserState *g = &shared_user_state;
|
|
Snapshot *prev_user_input_ss = SnapshotFromTick(user_input_client, user_input_client->last_tick);
|
|
Snapshot *user_input_ss = AcquireSnapshot(user_input_client, prev_user_input_ss, tick);
|
|
Entity *user_input_root = EntityFromId(user_input_ss, RootEntityId);
|
|
/* Find / create local control cmd ent */
|
|
Entity *control_cmd = FirstWithProp(user_input_ss, Prop_Cmd);
|
|
if (!control_cmd->valid)
|
|
{
|
|
control_cmd = AcquireSyncSrc(user_input_root);
|
|
control_cmd->cmd_kind = CmdKind_Control;
|
|
control_cmd->predictor = user_input_client->player_id;
|
|
EnableProp(control_cmd, Prop_Cmd);
|
|
Activate(control_cmd, user_input_ss->tick);
|
|
}
|
|
{
|
|
Lock lock = LockE(&g->user_sim_cmd_mutex);
|
|
/* Update control cmd */
|
|
{
|
|
control_cmd->cmd_control = g->user_sim_cmd_control;
|
|
control_cmd->cmd_control_hovered_ent = g->user_hovered_ent;
|
|
}
|
|
#if 0
|
|
/* Create chat cmd */
|
|
if (g->user_sim_cmd_chat.len > 0)
|
|
{
|
|
Entity *chat_cmd = AcquireSyncSrc(user_input_root);
|
|
chat_cmd->cmd_kind = CmdKind_Chat;
|
|
//chat_cmd->chat_msg = ZI
|
|
}
|
|
#endif
|
|
++g->user_sim_cmd_gen;
|
|
Unlock(&lock);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ Sim update
|
|
|
|
JobDef(UpdateSim, UNUSED sig, UNUSED id)
|
|
{
|
|
SharedUserState *g = &shared_user_state;
|
|
#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);
|
|
//N_Host *host = N_AcquireHost();
|
|
/* TODO: Host system should allocate & copy string stored in local_listen_addr */
|
|
//host_listen(host, local_listen_addr);
|
|
//host_listen(host, net_listen_addr);
|
|
#endif
|
|
|
|
b32 is_master = 0;
|
|
N_Host *host;
|
|
if (g->connect_address_str.len > 0)
|
|
{
|
|
host = N_AcquireHost(0);
|
|
P_Address addr = P_AddressFromString(g->connect_address_str);
|
|
N_Connect(host, addr);
|
|
}
|
|
else
|
|
{
|
|
host = N_AcquireHost(12345);
|
|
is_master = 1;
|
|
}
|
|
|
|
BB_Buff msg_writer_bb = BB_AcquireBuff(Gibi(64));
|
|
BB_Buff snapshot_writer_bb = BB_AcquireBuff(Gibi(64));
|
|
SimAccel accel = AcquireSimAccel();
|
|
|
|
ClientStore *store = AcquireClientStore();
|
|
Client *user_input_client = AcquireClient(store); /* Stores snapshots containing commands to be published to local client */
|
|
Client *local_client = AcquireClient(store); /* Stores snapshots produced locally */
|
|
Client *publish_client = AcquireClient(store); /* Stores versions of local snapshots that will be published to remote sims */
|
|
|
|
Client *master_client = NilClient(); /* Stores snapshots received from master */
|
|
Client *master_blended_client = NilClient(); /* Stores interpolated master snapshots */
|
|
b32 initialized_from_master = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
i64 master_blend_time_ns = 0;
|
|
i64 average_master_receive_dt_ns = 0;
|
|
i64 last_tick_from_master_received_at_ns = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
i64 last_publish_to_user_ns = 0;
|
|
i64 real_time_ns = 0;
|
|
i64 real_dt_ns = 0;
|
|
i64 step_dt_ns = NsFromSeconds(1) / SIM_TICKS_PER_SECOND;
|
|
f64 compute_timescale = 1.0;
|
|
while (!Atomic32Fetch(&g->shutdown))
|
|
{
|
|
TempArena scratch = BeginScratchNoConflict();
|
|
{
|
|
__profn("Sim sleep");
|
|
P_SleepFrame(real_time_ns, step_dt_ns * compute_timescale);
|
|
}
|
|
{
|
|
__profn("Sim update");
|
|
|
|
real_dt_ns = TimeNs() - real_time_ns;
|
|
real_time_ns += real_dt_ns;
|
|
|
|
N_EventList host_events = N_BeginUpdate(scratch.arena, host);
|
|
|
|
/* Read net messages */
|
|
DecodeQueue queue = ZI;
|
|
{
|
|
for (N_Event *event = host_events.first; event; event = event->next)
|
|
{
|
|
N_ChannelId channel_id = event->channel_id;
|
|
Client *client = ClientFromChannelId(store, channel_id);
|
|
switch (event->kind)
|
|
{
|
|
case N_EventKind_ChannelOpened:
|
|
{
|
|
if (!client->valid)
|
|
{
|
|
if (is_master)
|
|
{
|
|
/* Create remote client */
|
|
client = AcquireClient(store);
|
|
SetClientChannelId(client, channel_id);
|
|
}
|
|
else
|
|
{
|
|
/* Create master client */
|
|
if (!master_client->valid)
|
|
{
|
|
client = AcquireClient(store);
|
|
SetClientChannelId(client, channel_id);
|
|
master_client = client;
|
|
master_blended_client = AcquireClient(store);
|
|
}
|
|
else
|
|
{
|
|
/* We already have a master client */
|
|
Assert(0);
|
|
}
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case N_EventKind_Msg:
|
|
{
|
|
if (client->valid)
|
|
{
|
|
BB_Buff msg_bb = BB_BuffFromString(event->msg);
|
|
BB_Reader msg_br = BB_ReaderFromBuff(&msg_bb);
|
|
|
|
u64 ack = BB_ReadUV(&msg_br);
|
|
u64 double_ack = BB_ReadUV(&msg_br);
|
|
if (ack > client->ack)
|
|
{
|
|
client->ack = ack;
|
|
}
|
|
if (double_ack > client->double_ack)
|
|
{
|
|
client->double_ack = double_ack;
|
|
}
|
|
|
|
/* Read & queue incoming snapshots for decoding */
|
|
u64 tmp_encoded_len = BB_ReadUV(&msg_br);
|
|
while (tmp_encoded_len > 0)
|
|
{
|
|
u8 *tmp_encoded_bytes = BB_ReadBytesRaw(&msg_br, tmp_encoded_len);
|
|
if (!tmp_encoded_bytes) break;
|
|
|
|
BB_Buff decoder_bb = BB_BuffFromString(STRING(tmp_encoded_len, tmp_encoded_bytes));
|
|
BB_Reader decoder_br = BB_ReaderFromBuff(&decoder_bb);
|
|
u64 base_tick = BB_ReadUV(&decoder_br);
|
|
u64 tick = BB_ReadUV(&decoder_br);
|
|
|
|
String tmp_encoded = ZI;
|
|
tmp_encoded.len = BB_NumBytesRemaining(&decoder_br);
|
|
tmp_encoded.text = BB_ReadBytesRaw(&decoder_br, tmp_encoded.len);
|
|
if (!tmp_encoded.text) tmp_encoded.len = 0;
|
|
|
|
Snapshot *base_ss = SnapshotFromTick(client, base_tick);
|
|
if (base_ss->tick == base_tick)
|
|
{
|
|
if (is_master)
|
|
{
|
|
/* Queue incoming slave client snapshot for decoding */
|
|
//b32 should_decode = tick == client->highest_received_tick + 1 || client->highest_received_tick == 0;
|
|
b32 should_decode = tick > client->highest_received_tick;
|
|
if (should_decode)
|
|
{
|
|
DecodeQueueNode *node = PushStruct(scratch.arena, DecodeQueueNode);
|
|
node->client = client;
|
|
node->tick = tick;
|
|
node->base_tick = base_tick;
|
|
node->tmp_encoded = tmp_encoded;
|
|
if (queue.last)
|
|
{
|
|
queue.last->next = node;
|
|
}
|
|
else
|
|
{
|
|
queue.first = node;
|
|
}
|
|
queue.last = node;
|
|
if (tick > client->highest_received_tick)
|
|
{
|
|
client->highest_received_tick = tick;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Decode incoming master client snapshots for decoding (only the newest one) */
|
|
b32 should_decode = client == master_client && tick > client->highest_received_tick;
|
|
if (should_decode)
|
|
{
|
|
DecodeQueueNode *node = queue.first ? queue.first : PushStruct(scratch.arena, DecodeQueueNode);
|
|
node->client = client;
|
|
node->tick = tick;
|
|
node->base_tick = base_tick;
|
|
node->tmp_encoded = tmp_encoded;
|
|
queue.first = node;
|
|
queue.last = node;
|
|
if (tick > client->highest_received_tick)
|
|
{
|
|
client->highest_received_tick = tick;
|
|
if (average_master_receive_dt_ns == 0)
|
|
{
|
|
average_master_receive_dt_ns = NsFromSeconds(1) / SIM_TICKS_PER_SECOND;
|
|
}
|
|
else
|
|
{
|
|
average_master_receive_dt_ns -= average_master_receive_dt_ns / 50;
|
|
average_master_receive_dt_ns += (real_time_ns - last_tick_from_master_received_at_ns) / 50;
|
|
}
|
|
last_tick_from_master_received_at_ns = real_time_ns;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* We do not have the tick that the incoming delta is based from */
|
|
Assert(0);
|
|
}
|
|
|
|
tmp_encoded_len = BB_ReadUV(&msg_br);
|
|
}
|
|
}
|
|
} break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Decode incoming snapshots */
|
|
for (DecodeQueueNode *n = queue.first; n; n = n->next)
|
|
{
|
|
Client *client = n->client;
|
|
u64 base_tick = n->base_tick;
|
|
u64 tick = n->tick;
|
|
Snapshot *base_ss = SnapshotFromTick(client, base_tick);
|
|
if (base_ss->tick == base_tick)
|
|
{
|
|
BB_Buff bb = BB_BuffFromString(n->tmp_encoded);
|
|
BB_Reader br = BB_ReaderFromBuff(&bb);
|
|
|
|
/* Acquire & decode snapshot */
|
|
Snapshot *ss = AcquireSnapshot(client, base_ss, tick);
|
|
DecodeSnapshot(&br, ss);
|
|
|
|
/* Assume all incoming ents want to be sync srcs */
|
|
for (u64 i = 0; i < ss->num_ents_reserved; ++i)
|
|
{
|
|
Entity *ent = &ss->ents[i];
|
|
if (ent->valid && HasProp(ent, Prop_SyncDst))
|
|
{
|
|
DisableProp(ent, Prop_SyncDst);
|
|
EnableProp(ent, Prop_SyncSrc);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* We do not have the tick that the incoming delta is based from.
|
|
* This decode should never have been queued in the first place. */
|
|
Assert(0);
|
|
}
|
|
}
|
|
|
|
if (!is_master && !initialized_from_master)
|
|
{
|
|
if (master_client->valid && master_client->last_tick > 0)
|
|
{
|
|
initialized_from_master = 1;
|
|
}
|
|
else
|
|
{
|
|
goto skip_step;
|
|
}
|
|
}
|
|
|
|
b32 should_step = !Atomic32Fetch(&g->user_paused);
|
|
if (Atomic32Fetch(&g->user_paused_steps) > 0)
|
|
{
|
|
should_step = 1;
|
|
Atomic32FetchAdd(&g->user_paused_steps, -1);
|
|
}
|
|
|
|
if (!should_step)
|
|
{
|
|
goto skip_step;
|
|
}
|
|
|
|
/* Update networked clients */
|
|
u64 oldest_client_ack = 0;
|
|
for (u64 i = 0; i < store->num_clients_reserved; ++i)
|
|
{
|
|
Client *client = &store->clients[i];
|
|
if (client->valid && client != local_client && client != publish_client && client != user_input_client && client != master_client)
|
|
{
|
|
client->last_rtt_ns = N_GetChannelLastRttNs(host, client->channel_id);
|
|
/* Release unneeded received snapshots */
|
|
/* TDOO: Cap how many client snapshots we're willing to retain */
|
|
if (client->double_ack > 0)
|
|
{
|
|
u64 keep_tick = MinU64(client->double_ack, local_client->last_tick);
|
|
if (keep_tick > 0)
|
|
{
|
|
ReleaseSnapshotsInRange(client, 0, keep_tick - 1);
|
|
}
|
|
}
|
|
if (client->ack < oldest_client_ack || oldest_client_ack == 0)
|
|
{
|
|
oldest_client_ack = client->ack;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Release unneeded published snapshots */
|
|
{
|
|
u64 keep_tick = oldest_client_ack;
|
|
if (keep_tick == 0 && publish_client->last_tick > 0)
|
|
{
|
|
keep_tick = publish_client->last_tick - 1;
|
|
}
|
|
if (keep_tick > 0)
|
|
{
|
|
--keep_tick;
|
|
}
|
|
ReleaseSnapshotsInRange(publish_client, 0, keep_tick);
|
|
}
|
|
|
|
/* Release old local snapshots */
|
|
{
|
|
u64 keep_range = 50;
|
|
if (local_client->last_tick > keep_range)
|
|
{
|
|
u64 keep_tick = local_client->last_tick - keep_range;
|
|
ReleaseSnapshotsInRange(local_client, 0, keep_tick);
|
|
}
|
|
}
|
|
|
|
/* Release unneeded user input snapshots */
|
|
ReleaseSnapshotsInRange(user_input_client, 0, local_client->first_tick - 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (is_master)
|
|
{
|
|
/* Step master */
|
|
u64 prev_tick = local_client->last_tick;
|
|
u64 next_tick = prev_tick + 1;
|
|
SimStepCtx ctx = ZI;
|
|
ctx.is_master = is_master;
|
|
ctx.sim_dt_ns = step_dt_ns;
|
|
ctx.accel = &accel;
|
|
ctx.user_input_client = user_input_client;
|
|
ctx.master_client = master_client;
|
|
ctx.publish_client = publish_client;
|
|
Snapshot *prev_world = SnapshotFromTick(local_client, prev_tick);
|
|
ctx.world = AcquireSnapshot(local_client, prev_world, next_tick);
|
|
GenerateuserInputCmds(user_input_client, next_tick);
|
|
StepSim(&ctx);
|
|
}
|
|
else if (master_client->valid)
|
|
{
|
|
/* Step client */
|
|
|
|
/* TODO: Eventually determine master tick based on a delay to allow for jitter and also interpolation so we can lower snapshot publish frequency */
|
|
|
|
|
|
b32 master_ss_is_blended = 0;
|
|
Snapshot *master_ss = NilSnapshot();
|
|
{
|
|
/* How along are we between master sim ticks (0 = start of tick, 1 = end of tick) */
|
|
f64 tick_progress = 0;
|
|
i64 next_tick_expected_ns = last_tick_from_master_received_at_ns + average_master_receive_dt_ns;
|
|
if (next_tick_expected_ns > last_tick_from_master_received_at_ns)
|
|
{
|
|
tick_progress = (f64)(real_time_ns - last_tick_from_master_received_at_ns) / (f64)(next_tick_expected_ns - last_tick_from_master_received_at_ns);
|
|
}
|
|
|
|
/* Predict master sim time based on average snapshot publish dt. */
|
|
Snapshot *newest_snapshot = SnapshotFromTick(master_client, master_client->last_tick);
|
|
i64 master_sim_predicted_time_ns = newest_snapshot->sim_time_ns + (newest_snapshot->sim_dt_ns * tick_progress);
|
|
|
|
/* Determine blend time */
|
|
i64 master_blend_time_target_ns = master_sim_predicted_time_ns - (SIM_CLIENT_INTERP_RATIO * average_master_receive_dt_ns);
|
|
if (average_master_receive_dt_ns > 0)
|
|
{
|
|
master_blend_time_ns += real_dt_ns;
|
|
}
|
|
|
|
i64 blend_time_target_diff_ns = master_blend_time_target_ns - master_blend_time_ns;
|
|
if (blend_time_target_diff_ns > NsFromSeconds(0.100) || blend_time_target_diff_ns < NsFromSeconds(-0.100))
|
|
{
|
|
/* Snap blend time if it gets too far from target blend time */
|
|
master_blend_time_ns = master_blend_time_target_ns;
|
|
}
|
|
u64 master_blend_tick = master_blend_time_ns / newest_snapshot->sim_dt_ns;
|
|
|
|
/* Get snapshot nearest to master blend time */
|
|
/* TODO: Blend */
|
|
Snapshot *left_snapshot = NilSnapshot();
|
|
Snapshot *right_snapshot = newest_snapshot;
|
|
{
|
|
Snapshot *ss = SnapshotFromTick(master_client, master_client->first_tick);
|
|
while (ss->valid)
|
|
{
|
|
u64 next_tick = ss->next_tick;
|
|
i64 ss_time_ns = ss->sim_time_ns;
|
|
if (ss_time_ns < master_blend_time_ns && ss_time_ns > left_snapshot->sim_time_ns)
|
|
{
|
|
left_snapshot = ss;
|
|
}
|
|
if (ss_time_ns > master_blend_time_ns && ss_time_ns < right_snapshot->sim_time_ns)
|
|
{
|
|
right_snapshot = ss;
|
|
}
|
|
ss = SnapshotFromTick(master_client, next_tick);
|
|
}
|
|
}
|
|
|
|
/* Create world from blended master snapshots */
|
|
f64 blend = 0;
|
|
if (left_snapshot->valid && right_snapshot->valid && right_snapshot->tick > left_snapshot->tick)
|
|
{
|
|
blend = (f64)(master_blend_tick - left_snapshot->tick) / (f64)(right_snapshot->tick - left_snapshot->tick);
|
|
f64 epsilon = 0.001;
|
|
if (blend < epsilon)
|
|
{
|
|
master_ss_is_blended = 0;
|
|
master_ss = left_snapshot;
|
|
}
|
|
else if (blend > 1 - epsilon)
|
|
{
|
|
master_ss_is_blended = 0;
|
|
master_ss = right_snapshot;
|
|
}
|
|
else
|
|
{
|
|
master_ss_is_blended = 1;
|
|
master_ss = AcquireSnapshotFromLerp(master_blended_client, left_snapshot, right_snapshot, blend);
|
|
|
|
/* Release unneeded blended master snapshots */
|
|
if (master_ss->tick > 0)
|
|
{
|
|
ReleaseSnapshotsInRange(master_blended_client, 0, master_ss->tick - 1);
|
|
ReleaseSnapshotsInRange(master_blended_client, master_ss->tick + 1, U64Max);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
master_ss_is_blended = 0;
|
|
master_ss = left_snapshot->valid ? left_snapshot : right_snapshot;
|
|
}
|
|
|
|
/* Release unneeded master snapshots */
|
|
u64 keep_master_tick = MinU64(left_snapshot->tick, master_client->double_ack);
|
|
if (keep_master_tick > 0)
|
|
{
|
|
ReleaseSnapshotsInRange(master_client, 0, keep_master_tick - 1);
|
|
}
|
|
|
|
#if 0
|
|
DEBUGBREAKABLE;
|
|
P_LogDebugF("*************************************************");
|
|
P_LogDebugF("local_client->last_tick: %F", FmtUint(local_client->last_tick));
|
|
P_LogDebugF("master_sim_predicted_time_ns: %F", FmtSint(master_sim_predicted_time_ns));
|
|
P_LogDebugF("tick_progress: %F", FmtFloat(tick_progress));
|
|
P_LogDebugF("sim_publish_timescale: %F", FmtFloat(sim_publish_timescale));
|
|
P_LogDebugF("last_tick_from_master_received_at_ns: %F", FmtSint(last_tick_from_master_received_at_ns));
|
|
P_LogDebugF("average_master_receive_dt_ns: %F", FmtSint(average_master_receive_dt_ns));
|
|
P_LogDebugF("next_tick_expected_ns: %F", FmtSint(next_tick_expected_ns));
|
|
P_LogDebugF("master_blend_time_target_ns: %F", FmtSint(master_blend_time_target_ns));
|
|
P_LogDebugF("blend_time_target_diff_ns: %F", FmtSint(blend_time_target_diff_ns));
|
|
P_LogDebugF("master_blend_time_ns: %F", FmtSint(master_blend_time_ns));
|
|
P_LogDebugF("left_snapshot->tick: %F", FmtUint(left_snapshot->tick));
|
|
P_LogDebugF("right_snapshot->tick: %F", FmtUint(right_snapshot->tick));
|
|
P_LogDebugF("master_ss->tick: %F", FmtUint(master_ss->tick));
|
|
#endif
|
|
}
|
|
|
|
if (master_ss->valid)
|
|
{
|
|
Entity *master_player = FirstWithProp(master_ss, Prop_IsMaster);
|
|
|
|
/* Update ent id from master */
|
|
{
|
|
user_input_client->player_id = master_ss->local_player;
|
|
local_client->player_id = master_ss->local_player;
|
|
}
|
|
|
|
/* Check for misprediction */
|
|
u64 mispredicted_tick = 0;
|
|
if (!master_ss_is_blended)
|
|
{
|
|
/* TODO: Actually check for misprediction rather than triggering mispredict any time a new master snapshot is received */
|
|
mispredicted_tick = master_ss->tick;
|
|
}
|
|
|
|
|
|
u64 step_base_tick = local_client->last_tick;
|
|
u64 step_end_tick = step_base_tick + 1;
|
|
if (mispredicted_tick > 0)
|
|
{
|
|
step_base_tick = mispredicted_tick;
|
|
if (step_end_tick <= step_base_tick)
|
|
{
|
|
step_end_tick = step_base_tick + 1;
|
|
}
|
|
}
|
|
|
|
/* We want to simulate the ahead of the server to predict client input.
|
|
* How many ticks ahead we want to simulate is a balance between added latency and the server not receiving our inputs on time.
|
|
* We can take the server's ack minus the server's tick to determine how many cmds of ours the server has buffered.
|
|
*
|
|
* If this buffer gets too low (because we are lagging behind or the connection is unstable), meaning the server is not getting our input on time:
|
|
* - Shorten local compute rate to increase the rate at which we predict ahead & produce cmds, until the server's ack indicates a buffer size within desired range.
|
|
*
|
|
* If this buffer gets too large (because the client predicts too far ahead), meaning unneeded latency is being introduced:
|
|
* - Dilate local compute rate to decrease the rate at which we predict ahead & produce cmds until the server's ack indicates a buffer size within desired range.
|
|
*/
|
|
{
|
|
i64 cmds_ahead_on_master = (i64)master_client->ack - (i64)master_client->last_tick;
|
|
if (cmds_ahead_on_master < -3 || cmds_ahead_on_master > 10)
|
|
{
|
|
/* Cmds are too far from master time, snap step end tick */
|
|
i64 rtt_ns = master_client->last_rtt_ns;
|
|
f64 rtt_tick_ratio = (f64)(rtt_ns + (step_dt_ns - 1)) / (f64)step_dt_ns;
|
|
i64 num_predict_ticks = RoundF64ToI64(rtt_tick_ratio) + 5;
|
|
step_end_tick = master_client->last_tick + num_predict_ticks;
|
|
compute_timescale = 1.1;
|
|
}
|
|
else if (cmds_ahead_on_master > 2)
|
|
{
|
|
/* Slow down simulation to dial back how far ahead we are predicting and bring local sim time closer to master sim time */
|
|
compute_timescale = 1.1;
|
|
}
|
|
else if (cmds_ahead_on_master < 1)
|
|
{
|
|
/* Speed up simulation rate predict more ticks and give master more inputs to work with */
|
|
compute_timescale = 0.9;
|
|
}
|
|
else
|
|
{
|
|
/* Server's cmd buffer is in a healthy range */
|
|
compute_timescale = 1;
|
|
}
|
|
}
|
|
|
|
/* Sync master with local base tick */
|
|
Snapshot *base_ss = SnapshotFromTick(local_client, step_base_tick);
|
|
if (mispredicted_tick)
|
|
{
|
|
if (base_ss->valid)
|
|
{
|
|
SyncSnapshotEntities(base_ss, master_ss, master_player->id, 0);
|
|
}
|
|
else
|
|
{
|
|
base_ss = AcquireSnapshot(local_client, master_ss, step_base_tick);
|
|
}
|
|
}
|
|
|
|
/* Release any existing ticks that are about to be simulated */
|
|
ReleaseSnapshotsInRange(local_client, step_base_tick + 1, U64Max);
|
|
|
|
/* Step */
|
|
GenerateuserInputCmds(user_input_client, step_end_tick);
|
|
{
|
|
SimStepCtx ctx = ZI;
|
|
ctx.is_master = is_master;
|
|
ctx.sim_dt_ns = step_dt_ns;
|
|
ctx.accel = &accel;
|
|
ctx.user_input_client = user_input_client;
|
|
ctx.master_client = master_client;
|
|
ctx.publish_client = publish_client;
|
|
|
|
u64 step_tick = step_base_tick + 1;
|
|
Snapshot *prev_ss = base_ss;
|
|
while (step_tick <= step_end_tick)
|
|
{
|
|
ctx.world = AcquireSnapshot(local_client, prev_ss, step_tick);
|
|
if (!mispredicted_tick && step_tick == step_end_tick)
|
|
{
|
|
SyncSnapshotEntities(ctx.world, master_ss, master_player->id, SyncFlag_NoSyncPredictables);
|
|
}
|
|
StepSim(&ctx);
|
|
prev_ss = ctx.world;
|
|
++step_tick;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Publish snapshot to remote clients */
|
|
for (u64 i = 0; i < store->num_clients_reserved; ++i)
|
|
{
|
|
Client *client = &store->clients[i];
|
|
if (client->valid && client != user_input_client && client != local_client && client != publish_client)
|
|
{
|
|
BB_Writer msg_bw = BB_WriterFromBuff(&msg_writer_bb);
|
|
|
|
BB_WriteUV(&msg_bw, client->highest_received_tick); /* ack */
|
|
BB_WriteUV(&msg_bw, client->ack); /* double ack */
|
|
|
|
Snapshot *base_ss = SnapshotFromTick(publish_client, client->ack);
|
|
Snapshot *publish_ss;
|
|
if (client == master_client)
|
|
{
|
|
/* If sending to master, start sending all snapshots since last ack */
|
|
publish_ss = SnapshotFromClosestTickGte(publish_client, base_ss->tick + 1);
|
|
}
|
|
else
|
|
{
|
|
/* If sending to slave, only send latest snapshot */
|
|
publish_ss = SnapshotFromTick(publish_client, publish_client->last_tick);
|
|
}
|
|
|
|
while (publish_ss->valid)
|
|
{
|
|
BB_Writer snapshot_bw = BB_WriterFromBuff(&snapshot_writer_bb);
|
|
String tmp_snapshot_encoded = ZI;
|
|
{
|
|
BB_WriteUV(&snapshot_bw, base_ss->tick);
|
|
BB_WriteUV(&snapshot_bw, publish_ss->tick);
|
|
EncodeSnapshot(&snapshot_bw, client, base_ss, publish_ss);
|
|
tmp_snapshot_encoded.len = BB_GetNumBytesWritten(&snapshot_bw);
|
|
tmp_snapshot_encoded.text = BB_GetWrittenRaw(&snapshot_bw);
|
|
}
|
|
BB_WriteUV(&msg_bw, tmp_snapshot_encoded.len);
|
|
BB_WriteBytes(&msg_bw, tmp_snapshot_encoded);
|
|
publish_ss = SnapshotFromTick(publish_client, publish_ss->tick + 1);
|
|
}
|
|
BB_WriteUV(&msg_bw, 0);
|
|
|
|
String encoded = ZI;
|
|
encoded.len = BB_GetNumBytesWritten(&msg_bw);
|
|
encoded.text = BB_GetWrittenRaw(&msg_bw);
|
|
N_Write(host, client->channel_id, encoded, 0);
|
|
}
|
|
}
|
|
|
|
/* Copy local snapshot to user client */
|
|
{
|
|
Snapshot *local_ss = SnapshotFromTick(local_client, local_client->last_tick);
|
|
if (local_ss->valid)
|
|
{
|
|
/* TODO: Double buffer */
|
|
Lock lock = LockE(&g->local_to_user_client_mutex);
|
|
AcquireSnapshot(g->local_to_user_client, local_ss, local_ss->tick);
|
|
i64 publish_ns = TimeNs();
|
|
if (last_publish_to_user_ns == 0)
|
|
{
|
|
last_publish_to_user_ns = publish_ns - g->average_local_to_user_snapshot_publish_dt_ns;
|
|
}
|
|
g->local_to_user_client_publish_dt_ns = publish_ns - last_publish_to_user_ns;
|
|
g->local_to_user_client_publish_time_ns = publish_ns;
|
|
last_publish_to_user_ns = publish_ns;
|
|
ReleaseSnapshotsInRange(g->local_to_user_client, 0, local_ss->tick - 1);
|
|
Unlock(&lock);
|
|
}
|
|
}
|
|
|
|
skip_step:
|
|
|
|
/* Send host messages */
|
|
N_EndUpdate(host);
|
|
__profframe("Local sim");
|
|
|
|
EndScratch(scratch);
|
|
}
|
|
}
|
|
|
|
ReleaseClientStore(store);
|
|
ReleaseSimAccel(&accel);
|
|
BB_ReleaseBuff(&snapshot_writer_bb);
|
|
BB_ReleaseBuff(&msg_writer_bb);
|
|
N_ReleaseHost(host);
|
|
}
|