master snapshot blending
This commit is contained in:
parent
7e7e6a8f87
commit
d0bfbfeb2d
19
src/config.h
19
src/config.h
@ -28,12 +28,22 @@
|
|||||||
|
|
||||||
#define IMAGE_PIXELS_PER_UNIT 256.0
|
#define IMAGE_PIXELS_PER_UNIT 256.0
|
||||||
|
|
||||||
/* 64^2 = 4096 bins */
|
/* How many ticks back in time should the user blend between?
|
||||||
|
* <Delay> = <USER_INTERP_RATIO> * <Tick interval>
|
||||||
|
* E.g: At 1.5, the user thread will render 75ms back in time if the sim runs at 50tps
|
||||||
|
*/
|
||||||
|
#define USER_INTERP_RATIO 1.5
|
||||||
|
#define USER_INTERP_ENABLED 1
|
||||||
|
|
||||||
|
/* 64^2 = 4096 bins */
|
||||||
#define SPACE_CELL_BINS_SQRT (64)
|
#define SPACE_CELL_BINS_SQRT (64)
|
||||||
#define SPACE_CELL_SIZE 1.0f
|
#define SPACE_CELL_SIZE 1.0f
|
||||||
|
|
||||||
#define SIM_TICKS_PER_SECOND 50
|
#define SIM_TICKS_PER_SECOND 50
|
||||||
#define SIM_TIMESCALE 1
|
#define SIM_TIMESCALE 1
|
||||||
|
/* Like USER_INTERP_RATIO, but applies to snapshots received by the local sim from the
|
||||||
|
* master sim (how far back in time should the client render the server's state) */
|
||||||
|
#define SIM_CLIENT_INTERP_RATIO 2.0
|
||||||
|
|
||||||
#define SIM_PHYSICS_SUBSTEPS 4
|
#define SIM_PHYSICS_SUBSTEPS 4
|
||||||
#define SIM_PHYSICS_ENABLE_WARM_STARTING 1
|
#define SIM_PHYSICS_ENABLE_WARM_STARTING 1
|
||||||
@ -47,13 +57,6 @@
|
|||||||
#define SIM_MAX_LINEAR_VELOCITY 500
|
#define SIM_MAX_LINEAR_VELOCITY 500
|
||||||
#define SIM_MAX_ANGULAR_VELOCITY (TAU * 20)
|
#define SIM_MAX_ANGULAR_VELOCITY (TAU * 20)
|
||||||
|
|
||||||
/* How many ticks back in time should the user blend between?
|
|
||||||
* <Delay ms> = <USER_INTERP_RATIO> * <Tick interval>
|
|
||||||
* E.g: At 1.5, the user thread will render 75ms back in time if the sim runs at 50tps
|
|
||||||
*/
|
|
||||||
#define USER_INTERP_RATIO 1.5
|
|
||||||
#define USER_INTERP_ENABLED 1
|
|
||||||
|
|
||||||
#define COLLIDER_DEBUG 0
|
#define COLLIDER_DEBUG 0
|
||||||
#define COLLIDER_DEBUG_DETAILED 0
|
#define COLLIDER_DEBUG_DETAILED 0
|
||||||
#define COLLIDER_DEBUG_DETAILED_DRAW_MENKOWSKI 0
|
#define COLLIDER_DEBUG_DETAILED_DRAW_MENKOWSKI 0
|
||||||
|
|||||||
10
src/host.c
10
src/host.c
@ -631,7 +631,7 @@ i64 host_get_channel_last_rtt_ns(struct host *host, struct host_channel_id chann
|
|||||||
* Update
|
* Update
|
||||||
* ========================== */
|
* ========================== */
|
||||||
|
|
||||||
INTERNAL struct host_event *alloc_event(struct arena *arena, struct host_event_list *list)
|
INTERNAL struct host_event *push_event(struct arena *arena, struct host_event_list *list)
|
||||||
{
|
{
|
||||||
struct host_event *event = arena_push_zero(arena, struct host_event);
|
struct host_event *event = arena_push_zero(arena, struct host_event);
|
||||||
if (list->last) {
|
if (list->last) {
|
||||||
@ -721,7 +721,7 @@ struct host_event_list host_update_begin(struct arena *arena, struct host *host)
|
|||||||
/* We successfully connected to a foreign host and they are ready to receive messages */
|
/* We successfully connected to a foreign host and they are ready to receive messages */
|
||||||
if (channel->valid && !channel->connected) {
|
if (channel->valid && !channel->connected) {
|
||||||
logf_info("Host received connection from %F", FMT_STR(sock_string_from_address(scratch.arena, address)));
|
logf_info("Host received connection from %F", FMT_STR(sock_string_from_address(scratch.arena, address)));
|
||||||
struct host_event *event = alloc_event(arena, &events);
|
struct host_event *event = push_event(arena, &events);
|
||||||
event->kind = HOST_EVENT_KIND_CHANNEL_OPENED;
|
event->kind = HOST_EVENT_KIND_CHANNEL_OPENED;
|
||||||
event->channel_id = channel->id;
|
event->channel_id = channel->id;
|
||||||
channel->connected = true;
|
channel->connected = true;
|
||||||
@ -733,7 +733,7 @@ struct host_event_list host_update_begin(struct arena *arena, struct host *host)
|
|||||||
/* A foreign host disconnected from us */
|
/* A foreign host disconnected from us */
|
||||||
if (channel->valid) {
|
if (channel->valid) {
|
||||||
logf_info("Host received disconnection from %F", FMT_STR(sock_string_from_address(scratch.arena, address)));
|
logf_info("Host received disconnection from %F", FMT_STR(sock_string_from_address(scratch.arena, address)));
|
||||||
struct host_event *event = alloc_event(arena, &events);
|
struct host_event *event = push_event(arena, &events);
|
||||||
event->kind = HOST_EVENT_KIND_CHANNEL_CLOSED;
|
event->kind = HOST_EVENT_KIND_CHANNEL_CLOSED;
|
||||||
event->channel_id = channel->id;
|
event->channel_id = channel->id;
|
||||||
host_channel_release(channel);
|
host_channel_release(channel);
|
||||||
@ -789,7 +789,7 @@ struct host_event_list host_update_begin(struct arena *arena, struct host *host)
|
|||||||
if (ma->num_chunks_received == chunk_count) {
|
if (ma->num_chunks_received == chunk_count) {
|
||||||
/* All chunks filled, message has finished assembling */
|
/* All chunks filled, message has finished assembling */
|
||||||
/* TODO: Message ordering */
|
/* TODO: Message ordering */
|
||||||
struct host_event *event = alloc_event(arena, &events);
|
struct host_event *event = push_event(arena, &events);
|
||||||
struct string data = ZI;
|
struct string data = ZI;
|
||||||
data.len = ((chunk_count - 1) * PACKET_MSG_CHUNK_MAX_LEN) + ma->last_chunk_len;
|
data.len = ((chunk_count - 1) * PACKET_MSG_CHUNK_MAX_LEN) + ma->last_chunk_len;
|
||||||
data.text = arena_push_array(arena, u8, data.len);
|
data.text = arena_push_array(arena, u8, data.len);
|
||||||
@ -1014,7 +1014,7 @@ void host_update_end(struct host *host)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Process packets */
|
/* Send packets */
|
||||||
/* TODO: Aggregate small packets */
|
/* TODO: Aggregate small packets */
|
||||||
{
|
{
|
||||||
__profscope(host_update_send_packets);
|
__profscope(host_update_send_packets);
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
#define RAND_H
|
#define RAND_H
|
||||||
|
|
||||||
struct rand_state {
|
struct rand_state {
|
||||||
/* If a state's seed = 0 upon a to a related function, it will be initialized using platform's true rng source */
|
/* If a state's seed == 0 upon a call to a related function, it will be initialized using platform's true rng source */
|
||||||
u64 seed;
|
u64 seed;
|
||||||
u64 counter;
|
u64 counter;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -599,7 +599,7 @@ struct sim_snapshot *sim_snapshot_alloc_from_lerp(struct sim_client *client, str
|
|||||||
* ========================== */
|
* ========================== */
|
||||||
|
|
||||||
/* Syncs entity data between snapshots */
|
/* Syncs entity data between snapshots */
|
||||||
void sim_snapshot_sync_ents(struct sim_snapshot *local_ss, struct sim_snapshot *remote_ss, struct sim_ent_id remote_client_ent)
|
void sim_snapshot_sync_ents(struct sim_snapshot *local_ss, struct sim_snapshot *remote_ss, struct sim_ent_id remote_client_ent, u32 sync_flags)
|
||||||
{
|
{
|
||||||
__prof;
|
__prof;
|
||||||
|
|
||||||
@ -621,6 +621,9 @@ void sim_snapshot_sync_ents(struct sim_snapshot *local_ss, struct sim_snapshot *
|
|||||||
struct sim_ent *local_ent = &local_ss->ents[i];
|
struct sim_ent *local_ent = &local_ss->ents[i];
|
||||||
if (local_ent->valid && sim_ent_has_prop(local_ent, SIM_ENT_PROP_SYNC_DST)) {
|
if (local_ent->valid && sim_ent_has_prop(local_ent, SIM_ENT_PROP_SYNC_DST)) {
|
||||||
b32 should_sync = sim_ent_id_eq(local_ent->owner, remote_client_ent) || sim_ent_id_is_nil(remote_client_ent);
|
b32 should_sync = sim_ent_id_eq(local_ent->owner, remote_client_ent) || sim_ent_id_is_nil(remote_client_ent);
|
||||||
|
if ((sync_flags & SIM_SYNC_FLAG_NOSYNC_PREDICTABLES) && sim_ent_id_eq(local_ent->predictor, local_ss->local_client_ent)) {
|
||||||
|
should_sync = false;
|
||||||
|
}
|
||||||
if (should_sync) {
|
if (should_sync) {
|
||||||
struct sim_ent *remote_ent = sim_ent_from_id(remote_ss, local_ent->id);
|
struct sim_ent *remote_ent = sim_ent_from_id(remote_ss, local_ent->id);
|
||||||
if (remote_ent->valid) {
|
if (remote_ent->valid) {
|
||||||
|
|||||||
24
src/sim.h
24
src/sim.h
@ -73,7 +73,7 @@ struct sim_client {
|
|||||||
struct arena snapshots_arena;
|
struct arena snapshots_arena;
|
||||||
|
|
||||||
/* Round trip time of the client (if networked) */
|
/* Round trip time of the client (if networked) */
|
||||||
i64 rtt_ns;
|
i64 last_rtt_ns;
|
||||||
|
|
||||||
struct host_channel_id channel_id;
|
struct host_channel_id channel_id;
|
||||||
u64 channel_hash;
|
u64 channel_hash;
|
||||||
@ -129,15 +129,19 @@ struct sim_client *sim_client_from_handle(struct sim_client_store *store, struct
|
|||||||
* Snapshot
|
* Snapshot
|
||||||
* ========================== */
|
* ========================== */
|
||||||
|
|
||||||
|
enum sim_sync_flag {
|
||||||
|
SIM_SYNC_FLAG_NOSYNC_PREDICTABLES = 1 << 0
|
||||||
|
};
|
||||||
|
|
||||||
enum sim_control_flag {
|
enum sim_control_flag {
|
||||||
SIM_CONTROL_FLAG_FIRE = 1 << 0,
|
SIM_CONTROL_FLAG_FIRE = 1 << 0,
|
||||||
|
|
||||||
/* Testing */
|
/* Testing */
|
||||||
SIM_CONTROL_FLAG_DRAG = 1 << 1,
|
SIM_CONTROL_FLAG_DRAG = 1 << 1,
|
||||||
SIM_CONTROL_FLAG_CLEAR_ALL = 1 << 2,
|
SIM_CONTROL_FLAG_CLEAR_ALL = 1 << 2,
|
||||||
SIM_CONTROL_FLAG_SPAWN_TEST = 1 << 3,
|
SIM_CONTROL_FLAG_SPAWN_TEST = 1 << 3,
|
||||||
SIM_CONTROL_FLAG_PAUSE = 1 << 4,
|
SIM_CONTROL_FLAG_PAUSE = 1 << 4,
|
||||||
SIM_CONTROL_FLAG_STEP = 1 << 5
|
SIM_CONTROL_FLAG_STEP = 1 << 5
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sim_control {
|
struct sim_control {
|
||||||
@ -160,10 +164,6 @@ struct sim_snapshot {
|
|||||||
|
|
||||||
struct arena arena;
|
struct arena arena;
|
||||||
|
|
||||||
/* Program time of local snapshot publish to user thread (if relevant) */
|
|
||||||
i64 publish_dt_ns;
|
|
||||||
i64 publish_time_ns;
|
|
||||||
|
|
||||||
/* Sim time (guaranteed to increase by sim_dt_ns each step) */
|
/* Sim time (guaranteed to increase by sim_dt_ns each step) */
|
||||||
i64 sim_dt_ns;
|
i64 sim_dt_ns;
|
||||||
i64 sim_time_ns;
|
i64 sim_time_ns;
|
||||||
@ -212,7 +212,7 @@ struct sim_snapshot *sim_snapshot_from_closest_tick_gte(struct sim_client *clien
|
|||||||
struct sim_snapshot *sim_snapshot_alloc_from_lerp(struct sim_client *client, struct sim_snapshot *ss0, struct sim_snapshot *ss1, f64 blend);
|
struct sim_snapshot *sim_snapshot_alloc_from_lerp(struct sim_client *client, struct sim_snapshot *ss0, struct sim_snapshot *ss1, f64 blend);
|
||||||
|
|
||||||
/* Sync */
|
/* Sync */
|
||||||
void sim_snapshot_sync_ents(struct sim_snapshot *local_ss, struct sim_snapshot *remote_ss, struct sim_ent_id remote_client_ent);
|
void sim_snapshot_sync_ents(struct sim_snapshot *local_ss, struct sim_snapshot *remote_ss, struct sim_ent_id remote_client_ent, u32 sync_flags);
|
||||||
|
|
||||||
/* Encode / decode */
|
/* Encode / decode */
|
||||||
void sim_snapshot_encode(struct bitbuff_writer *bw, struct sim_client *receiver, struct sim_snapshot *ss0, struct sim_snapshot *ss1);
|
void sim_snapshot_encode(struct bitbuff_writer *bw, struct sim_client *receiver, struct sim_snapshot *ss0, struct sim_snapshot *ss1);
|
||||||
|
|||||||
@ -392,18 +392,16 @@ void sim_step(struct sim_step_ctx *ctx)
|
|||||||
|
|
||||||
/* Update rtt */
|
/* Update rtt */
|
||||||
if (is_master && client_ent->valid) {
|
if (is_master && client_ent->valid) {
|
||||||
client_ent->client_last_rtt_ns = client->rtt_ns;
|
client_ent->client_last_rtt_ns = client->last_rtt_ns;
|
||||||
f64 avg = client_ent->client_average_rtt_seconds;
|
client_ent->client_average_rtt_seconds -= client_ent->client_average_rtt_seconds / 200;
|
||||||
avg -= avg / 200;
|
client_ent->client_average_rtt_seconds += SECONDS_FROM_NS(client->last_rtt_ns) / 200;
|
||||||
avg += SECONDS_FROM_NS(client->rtt_ns) / 200;
|
|
||||||
client_ent->client_average_rtt_seconds = avg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sync ents from client */
|
/* Sync ents from client */
|
||||||
if (client_ent->valid) {
|
if (client_ent->valid) {
|
||||||
struct sim_snapshot *src_ss = sim_snapshot_from_tick(client, world->tick);
|
struct sim_snapshot *src_ss = sim_snapshot_from_tick(client, world->tick);
|
||||||
if (src_ss->valid) {
|
if (src_ss->valid) {
|
||||||
sim_snapshot_sync_ents(world, src_ss, client_ent->id);
|
sim_snapshot_sync_ents(world, src_ss, client_ent->id, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -993,6 +991,7 @@ void sim_step(struct sim_step_ctx *ctx)
|
|||||||
* Create motor joints from ground friction (gravity)
|
* Create motor joints from ground friction (gravity)
|
||||||
* ========================== */
|
* ========================== */
|
||||||
|
|
||||||
|
#if 0
|
||||||
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) {
|
||||||
struct sim_ent *ent = &world->ents[ent_index];
|
struct sim_ent *ent = &world->ents[ent_index];
|
||||||
if (!sim_ent_should_simulate(ent)) continue;
|
if (!sim_ent_should_simulate(ent)) continue;
|
||||||
@ -1017,6 +1016,7 @@ void sim_step(struct sim_step_ctx *ctx)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* ========================== *
|
/* ========================== *
|
||||||
* Create mouse joints from client debug drag
|
* Create mouse joints from client debug drag
|
||||||
@ -1275,7 +1275,7 @@ void sim_step(struct sim_step_ctx *ctx)
|
|||||||
struct sim_snapshot *pub_world = sim_snapshot_alloc(publish_client, prev_pub_world, world->tick);
|
struct sim_snapshot *pub_world = sim_snapshot_alloc(publish_client, prev_pub_world, world->tick);
|
||||||
|
|
||||||
/* Sync */
|
/* Sync */
|
||||||
sim_snapshot_sync_ents(pub_world, world, world_client->ent_id);
|
sim_snapshot_sync_ents(pub_world, world, world_client->ent_id, 0);
|
||||||
|
|
||||||
/* Mark all synced ents as both sync dsts & sync srcs */
|
/* Mark all synced ents as both sync dsts & sync srcs */
|
||||||
for (u64 ent_index = 2; ent_index < pub_world->num_ents_reserved; ++ent_index) {
|
for (u64 ent_index = 2; ent_index < pub_world->num_ents_reserved; ++ent_index) {
|
||||||
|
|||||||
499
src/user.c
499
src/user.c
@ -95,11 +95,11 @@ GLOBAL struct {
|
|||||||
struct sys_mutex local_to_user_client_mutex;
|
struct sys_mutex local_to_user_client_mutex;
|
||||||
struct sim_client_store *local_to_user_client_store;
|
struct sim_client_store *local_to_user_client_store;
|
||||||
struct sim_client *local_to_user_client;
|
struct sim_client *local_to_user_client;
|
||||||
|
i64 local_to_user_client_publish_dt_ns;
|
||||||
|
i64 local_to_user_client_publish_time_ns;
|
||||||
|
|
||||||
/* Rolling window of local sim -> user publish time deltas */
|
/* Rolling window of local sim -> user publish time deltas */
|
||||||
i64 last_local_to_user_snapshot_published_at_ns;
|
i64 last_local_to_user_snapshot_published_at_ns;
|
||||||
i64 local_to_user_snapshot_publish_dts_ns[10];
|
|
||||||
i64 local_to_user_snapshot_publish_dts_index;
|
|
||||||
i64 average_local_to_user_snapshot_publish_dt_ns;
|
i64 average_local_to_user_snapshot_publish_dt_ns;
|
||||||
|
|
||||||
i64 local_sim_predicted_time_ns; /* Calculated from <last local sim to user pubilsh time> + <time since last local sim to user publish> */
|
i64 local_sim_predicted_time_ns; /* Calculated from <last local sim to user pubilsh time> + <time since last local sim to user publish> */
|
||||||
@ -202,10 +202,8 @@ struct user_startup_receipt user_startup(struct work_startup_receipt *work_sr,
|
|||||||
/* TODO: Remove this */
|
/* TODO: Remove this */
|
||||||
G.connect_address_str = string_copy(&G.arena, connect_address_str);
|
G.connect_address_str = string_copy(&G.arena, connect_address_str);
|
||||||
|
|
||||||
/* Initialize average dts to a reasonable value */
|
/* Initialize average dt to a reasonable value */
|
||||||
for (u64 i = 0; i < ARRAY_COUNT(G.local_to_user_snapshot_publish_dts_ns); ++i) {
|
G.average_local_to_user_snapshot_publish_dt_ns = NS_FROM_SECONDS(1) / SIM_TICKS_PER_SECOND;
|
||||||
G.local_to_user_snapshot_publish_dts_ns[i] = NS_FROM_SECONDS(1) / SIM_TICKS_PER_SECOND;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sys events */
|
/* Sys events */
|
||||||
G.sys_events_mutex = sys_mutex_alloc();
|
G.sys_events_mutex = sys_mutex_alloc();
|
||||||
@ -488,39 +486,13 @@ INTERNAL void user_update(void)
|
|||||||
if (last_tick > old_last_tick) {
|
if (last_tick > old_last_tick) {
|
||||||
struct sim_snapshot *src = sim_snapshot_from_tick(G.local_to_user_client, last_tick);
|
struct sim_snapshot *src = sim_snapshot_from_tick(G.local_to_user_client, last_tick);
|
||||||
sim_snapshot_alloc(G.user_unblended_client, src, src->tick);
|
sim_snapshot_alloc(G.user_unblended_client, src, src->tick);
|
||||||
G.last_local_to_user_snapshot_published_at_ns = src->publish_time_ns;
|
G.last_local_to_user_snapshot_published_at_ns = G.local_to_user_client_publish_time_ns;
|
||||||
G.local_to_user_snapshot_publish_dts_ns[G.local_to_user_snapshot_publish_dts_index++] = src->publish_dt_ns;
|
G.average_local_to_user_snapshot_publish_dt_ns -= G.average_local_to_user_snapshot_publish_dt_ns / 50;
|
||||||
if (G.local_to_user_snapshot_publish_dts_index >= (i64)ARRAY_COUNT(G.local_to_user_snapshot_publish_dts_ns)) {
|
G.average_local_to_user_snapshot_publish_dt_ns += G.local_to_user_client_publish_dt_ns / 50;
|
||||||
G.local_to_user_snapshot_publish_dts_index = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sys_mutex_unlock(&lock);
|
sys_mutex_unlock(&lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========================== *
|
|
||||||
* Determine average local sim publish dt
|
|
||||||
* ========================== */
|
|
||||||
|
|
||||||
{
|
|
||||||
i64 average_publish_dt_ns = 0;
|
|
||||||
{
|
|
||||||
i64 num_dts = 0;
|
|
||||||
for (u64 i = 0; i < ARRAY_COUNT(G.local_to_user_snapshot_publish_dts_ns); ++i) {
|
|
||||||
i64 dt_ns = G.local_to_user_snapshot_publish_dts_ns[i];
|
|
||||||
if (dt_ns != 0) {
|
|
||||||
average_publish_dt_ns += dt_ns;
|
|
||||||
++num_dts;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (num_dts > 0) {
|
|
||||||
average_publish_dt_ns /= num_dts;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
G.average_local_to_user_snapshot_publish_dt_ns = average_publish_dt_ns;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========================== *
|
/* ========================== *
|
||||||
* Create user world from blended snapshots
|
* Create user world from blended snapshots
|
||||||
* ========================== */
|
* ========================== */
|
||||||
@ -537,7 +509,6 @@ INTERNAL void user_update(void)
|
|||||||
struct sim_snapshot *newest_snapshot = sim_snapshot_from_tick(G.user_unblended_client, G.user_unblended_client->last_tick);
|
struct sim_snapshot *newest_snapshot = sim_snapshot_from_tick(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_time_ns = newest_snapshot->sim_time_ns;
|
||||||
G.local_sim_last_known_tick = newest_snapshot->tick;
|
G.local_sim_last_known_tick = newest_snapshot->tick;
|
||||||
u64 keep_unblended_tick = newest_snapshot->tick;
|
|
||||||
G.local_sim_predicted_time_ns = newest_snapshot->sim_time_ns + (newest_snapshot->sim_dt_ns * tick_progress);
|
G.local_sim_predicted_time_ns = newest_snapshot->sim_time_ns + (newest_snapshot->sim_dt_ns * tick_progress);
|
||||||
|
|
||||||
#if USER_INTERP_ENABLED
|
#if USER_INTERP_ENABLED
|
||||||
@ -572,10 +543,6 @@ INTERNAL void user_update(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (left_snapshot->tick < keep_unblended_tick) {
|
|
||||||
keep_unblended_tick = left_snapshot->tick;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create world from blended snapshots */
|
/* Create world from blended snapshots */
|
||||||
if (left_snapshot->valid && right_snapshot->valid) {
|
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);
|
f64 blend = (f64)(G.render_time_ns - left_snapshot->sim_time_ns) / (f64)(right_snapshot->sim_time_ns - left_snapshot->sim_time_ns);
|
||||||
@ -598,20 +565,14 @@ INTERNAL void user_update(void)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Release unneeded unblended sim snapshots */
|
/* Release unneeded unblended sim snapshots */
|
||||||
if (keep_unblended_tick > 0) {
|
if (left_snapshot->tick > 0) {
|
||||||
sim_snapshot_release_ticks_in_range(G.user_unblended_client, 0, keep_unblended_tick - 1);
|
sim_snapshot_release_ticks_in_range(G.user_unblended_client, 0, left_snapshot->tick - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Release unused blended snapshots */
|
/* Release unneeded blended snapshots */
|
||||||
{
|
if (G.ss_blended->tick > 0) {
|
||||||
struct sim_snapshot *ss = sim_snapshot_from_tick(G.user_blended_client, G.user_blended_client->first_tick);
|
sim_snapshot_release_ticks_in_range(G.user_blended_client, 0, G.ss_blended->tick - 1);
|
||||||
while (ss->valid) {
|
sim_snapshot_release_ticks_in_range(G.user_blended_client, G.ss_blended->tick + 1, U64_MAX);
|
||||||
u64 next_tick = ss->next_tick;
|
|
||||||
if (ss != G.ss_blended) {
|
|
||||||
sim_snapshot_release(ss);
|
|
||||||
}
|
|
||||||
ss = sim_snapshot_from_tick(G.user_blended_client, next_tick);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1061,7 +1022,7 @@ INTERNAL void user_update(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Draw focus arrow */
|
/* Draw focus arrow */
|
||||||
if (ent == local_player) {
|
if (ent == local_player || sim_ent_id_eq(ent->id, G.debug_following)) {
|
||||||
struct sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, ent->sprite);
|
struct sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, ent->sprite);
|
||||||
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, LIT("attach.wep"), ent->animation_frame);
|
struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, LIT("attach.wep"), ent->animation_frame);
|
||||||
struct v2 start = xform_mul_v2(sprite_xform, slice.center);
|
struct v2 start = xform_mul_v2(sprite_xform, slice.center);
|
||||||
@ -1968,7 +1929,8 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
|
|||||||
struct sim_client *local_client = sim_client_alloc(store); /* Stores snapshots produced locally */
|
struct sim_client *local_client = sim_client_alloc(store); /* Stores snapshots produced locally */
|
||||||
struct sim_client *publish_client = sim_client_alloc(store); /* Stores versions of local snapshots that will be published to remote sims */
|
struct sim_client *publish_client = sim_client_alloc(store); /* Stores versions of local snapshots that will be published to remote sims */
|
||||||
|
|
||||||
struct sim_client *master_client = sim_client_nil(); /* Stores snapshots received from master (if relevant) */
|
struct sim_client *master_client = sim_client_nil(); /* Stores snapshots received from master */
|
||||||
|
struct sim_client *master_blended_client = sim_client_nil(); /* Stores interpolated master snapshots */
|
||||||
b32 initialized_from_master = false;
|
b32 initialized_from_master = false;
|
||||||
|
|
||||||
|
|
||||||
@ -1983,11 +1945,14 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
i64 prev_last_tick_from_master = 0;
|
|
||||||
i64 last_tick_from_master = 0;
|
|
||||||
i64 last_ticks_ahead_from_master = 0;
|
|
||||||
#endif
|
|
||||||
|
i64 master_blend_time_ns = 0;
|
||||||
|
i64 average_master_receive_dt_ns = 0;
|
||||||
|
i64 last_tick_from_master_received_at_ns = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -2002,8 +1967,12 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
i64 last_publish_ns = 0;
|
|
||||||
i64 last_tick_ns = 0;
|
|
||||||
|
|
||||||
|
i64 last_publish_to_user_ns = 0;
|
||||||
|
i64 real_time_ns = 0;
|
||||||
|
i64 real_dt_ns = 0;
|
||||||
i64 step_dt_ns = NS_FROM_SECONDS(1) / SIM_TICKS_PER_SECOND;
|
i64 step_dt_ns = NS_FROM_SECONDS(1) / SIM_TICKS_PER_SECOND;
|
||||||
f64 compute_timescale = 1.0;
|
f64 compute_timescale = 1.0;
|
||||||
while (!atomic_i32_eval(&G.local_sim_thread_shutdown)) {
|
while (!atomic_i32_eval(&G.local_sim_thread_shutdown)) {
|
||||||
@ -2011,9 +1980,10 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
|
|||||||
struct temp_arena scratch = scratch_begin_no_conflict();
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
||||||
{
|
{
|
||||||
__profscope(local_sim_sleep);
|
__profscope(local_sim_sleep);
|
||||||
sleep_frame(last_tick_ns, step_dt_ns / compute_timescale);
|
sleep_frame(real_time_ns, step_dt_ns * compute_timescale);
|
||||||
last_tick_ns = sys_time_ns();
|
|
||||||
}
|
}
|
||||||
|
real_dt_ns = sys_time_ns() - real_time_ns;
|
||||||
|
real_time_ns += real_dt_ns;
|
||||||
|
|
||||||
struct host_event_list host_events = host_update_begin(scratch.arena, host);
|
struct host_event_list host_events = host_update_begin(scratch.arena, host);
|
||||||
|
|
||||||
@ -2026,13 +1996,22 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
|
|||||||
switch (event->kind) {
|
switch (event->kind) {
|
||||||
case HOST_EVENT_KIND_CHANNEL_OPENED:
|
case HOST_EVENT_KIND_CHANNEL_OPENED:
|
||||||
{
|
{
|
||||||
/* Create remote client */
|
|
||||||
if (!client->valid) {
|
if (!client->valid) {
|
||||||
/* TODO: Master challenge */
|
if (is_master) {
|
||||||
client = sim_client_alloc(store);
|
/* Create remote client */
|
||||||
sim_client_set_channel_id(client, channel_id);
|
client = sim_client_alloc(store);
|
||||||
if (!is_master) {
|
sim_client_set_channel_id(client, channel_id);
|
||||||
master_client = client;
|
} else {
|
||||||
|
/* Create master client */
|
||||||
|
if (!master_client->valid) {
|
||||||
|
client = sim_client_alloc(store);
|
||||||
|
sim_client_set_channel_id(client, channel_id);
|
||||||
|
master_client = client;
|
||||||
|
master_blended_client = sim_client_alloc(store);
|
||||||
|
} else {
|
||||||
|
/* We already have a master client */
|
||||||
|
ASSERT(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
@ -2052,7 +2031,7 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
|
|||||||
client->double_ack = double_ack;
|
client->double_ack = double_ack;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read snapshots */
|
/* Read & queue incoming snapshots for decoding */
|
||||||
u64 tmp_encoded_len = br_read_uv(&msg_br);
|
u64 tmp_encoded_len = br_read_uv(&msg_br);
|
||||||
while (tmp_encoded_len > 0) {
|
while (tmp_encoded_len > 0) {
|
||||||
u8 *tmp_encoded_bytes = br_read_bytes_raw(&msg_br, tmp_encoded_len);
|
u8 *tmp_encoded_bytes = br_read_bytes_raw(&msg_br, tmp_encoded_len);
|
||||||
@ -2090,9 +2069,9 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
|
|||||||
client->highest_received_tick = tick;
|
client->highest_received_tick = tick;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (client == master_client) {
|
} else {
|
||||||
/* Decode incoming master client snapshots for decoding (only the newest one) */
|
/* Decode incoming master client snapshots for decoding (only the newest one) */
|
||||||
b32 should_decode = tick > client->highest_received_tick;
|
b32 should_decode = client == master_client && tick > client->highest_received_tick;
|
||||||
if (should_decode) {
|
if (should_decode) {
|
||||||
struct sim_ss_decode_node *node = queue.first ? queue.first : arena_push_zero(scratch.arena, struct sim_ss_decode_node);
|
struct sim_ss_decode_node *node = queue.first ? queue.first : arena_push_zero(scratch.arena, struct sim_ss_decode_node);
|
||||||
node->client = client;
|
node->client = client;
|
||||||
@ -2103,6 +2082,13 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
|
|||||||
queue.last = node;
|
queue.last = node;
|
||||||
if (tick > client->highest_received_tick) {
|
if (tick > client->highest_received_tick) {
|
||||||
client->highest_received_tick = tick;
|
client->highest_received_tick = tick;
|
||||||
|
if (average_master_receive_dt_ns == 0) {
|
||||||
|
average_master_receive_dt_ns = NS_FROM_SECONDS(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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2162,13 +2148,15 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
|
|||||||
u64 oldest_client_ack = 0;
|
u64 oldest_client_ack = 0;
|
||||||
for (u64 i = 0; i < store->num_clients_reserved; ++i) {
|
for (u64 i = 0; i < store->num_clients_reserved; ++i) {
|
||||||
struct sim_client *client = &store->clients[i];
|
struct sim_client *client = &store->clients[i];
|
||||||
if (client->valid && client != local_client && client != publish_client && client != user_input_client) {
|
if (client->valid && client != local_client && client != publish_client && client != user_input_client && client != master_client) {
|
||||||
client->rtt_ns = host_get_channel_last_rtt_ns(host, client->channel_id);
|
client->last_rtt_ns = host_get_channel_last_rtt_ns(host, client->channel_id);
|
||||||
/* Release unneeded received snapshots */
|
/* Release unneeded received snapshots */
|
||||||
|
/* TDOO: Cap how many client snapshots we're willing to retain */
|
||||||
if (client->double_ack > 0) {
|
if (client->double_ack > 0) {
|
||||||
//u64 keep_tick = max_u64(min_u64(client->double_ack, step_tick), 1);
|
u64 keep_tick = min_u64(client->double_ack, local_client->last_tick);
|
||||||
u64 keep_tick = 1;
|
if (keep_tick > 0) {
|
||||||
sim_snapshot_release_ticks_in_range(client, 0, keep_tick - 1);
|
sim_snapshot_release_ticks_in_range(client, 0, keep_tick - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (client->ack < oldest_client_ack || oldest_client_ack == 0) {
|
if (client->ack < oldest_client_ack || oldest_client_ack == 0) {
|
||||||
oldest_client_ack = client->ack;
|
oldest_client_ack = client->ack;
|
||||||
@ -2190,7 +2178,7 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
|
|||||||
|
|
||||||
/* Release old local snapshots */
|
/* Release old local snapshots */
|
||||||
{
|
{
|
||||||
u64 keep_range = 250;
|
u64 keep_range = 50;
|
||||||
if (local_client->last_tick > keep_range) {
|
if (local_client->last_tick > keep_range) {
|
||||||
u64 keep_tick = local_client->last_tick - keep_range;
|
u64 keep_tick = local_client->last_tick - keep_range;
|
||||||
sim_snapshot_release_ticks_in_range(local_client, 0, keep_tick);
|
sim_snapshot_release_ticks_in_range(local_client, 0, keep_tick);
|
||||||
@ -2203,74 +2191,6 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
u64 sim_base_tick = local_client->last_tick;
|
|
||||||
u64 sim_to_tick = sim_base_tick + 1;
|
|
||||||
if (master_client->valid) {
|
|
||||||
prev_last_tick_from_master = master_client->last_tick;
|
|
||||||
last_tick_from_master = master_client->last_tick;
|
|
||||||
last_ticks_ahead_from_master = master_client->ack - last_tick_from_master;
|
|
||||||
if (math_abs_i64(last_ticks_ahead_from_master) > 50) {
|
|
||||||
/* We're too far from master time, snap to last sim time */
|
|
||||||
i64 rtt_ns = master_client->rtt_ns;
|
|
||||||
f64 rtt_tick_ratio = (f64)(rtt_ns + (step_dt_ns - 1)) / (f64)step_dt_ns;
|
|
||||||
i64 num_predict_ticks = math_round_to_int64(rtt_tick_ratio) + 2;
|
|
||||||
sim_base_tick = last_tick_from_master;
|
|
||||||
sim_to_tick = last_tick_from_master + num_predict_ticks;
|
|
||||||
sim_snapshot_release_ticks_in_range(local_client, sim_base_tick + 1, U64_MAX);
|
|
||||||
sim_snapshot_release_ticks_in_range(user_input_client, sim_base_tick + 1, U64_MAX);
|
|
||||||
compute_timescale = 1.1;
|
|
||||||
} else if (last_ticks_ahead_from_master > 4) {
|
|
||||||
/* Slow down time to bring sim time closer to master time */
|
|
||||||
compute_timescale = 0.9;
|
|
||||||
} else if (last_ticks_ahead_from_master < 2) {
|
|
||||||
/* Speed up time to give master more inputs to work with */
|
|
||||||
compute_timescale = 1.1;
|
|
||||||
} else {
|
|
||||||
compute_timescale = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Step */
|
|
||||||
{
|
|
||||||
struct sim_step_ctx 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;
|
|
||||||
|
|
||||||
if (is_master) {
|
|
||||||
struct sim_snapshot *prev_world = sim_snapshot_from_tick(local_client, sim_base_tick);
|
|
||||||
ctx.world = sim_snapshot_alloc(local_client, prev_world, sim_base_tick + 1);
|
|
||||||
sim_step(&ctx);
|
|
||||||
} else {
|
|
||||||
struct sim_snapshot *master_ss = sim_snapshot_from_tick(master_client, master_client->last_tick);
|
|
||||||
if (master_ss->valid) {
|
|
||||||
local_client->ent_id = master_ss->local_client_ent;
|
|
||||||
user_input_client->ent_id = master_ss->local_client_ent;
|
|
||||||
|
|
||||||
struct sim_snapshot *prev_world = sim_snapshot_alloc(local_client, master_ss, master_ss->tick);
|
|
||||||
|
|
||||||
i64 num_steps = (local_client->last_tick + num_sim_ticks) - master_ss->tick;
|
|
||||||
for (i64 i = 0; i < num_steps; ++i) {
|
|
||||||
ctx.world = sim_snapshot_alloc(local_client, prev_world, prev_world->tick + 1);
|
|
||||||
sim_step(&ctx);
|
|
||||||
prev_world = ctx.world;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -2295,102 +2215,197 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
|
|||||||
/* TODO: Eventually determine master tick based on a delay to allow for jitter and also interpolation so we can lower snapshot publish frequency */
|
/* TODO: Eventually determine master tick based on a delay to allow for jitter and also interpolation so we can lower snapshot publish frequency */
|
||||||
|
|
||||||
|
|
||||||
u64 master_tick = master_client->last_tick;
|
b32 master_ss_is_blended = false;
|
||||||
struct sim_snapshot *master_ss = sim_snapshot_from_tick(master_client, master_tick);
|
struct sim_snapshot *master_ss = sim_snapshot_nil();
|
||||||
struct sim_ent *master_client_ent = sim_ent_find_first_match_one(master_ss, SIM_ENT_PROP_CLIENT_IS_MASTER);
|
|
||||||
user_input_client->ent_id = master_ss->local_client_ent;
|
|
||||||
local_client->ent_id = master_ss->local_client_ent;
|
|
||||||
|
|
||||||
/* Check for misprediction */
|
|
||||||
/* TODO: Actually check for misprediction rather than triggering mispredict any time a new master snapshot is received */
|
|
||||||
u64 mispredicted_tick = master_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 last sent ack - server 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:
|
|
||||||
* - Dilate local compute time (not sim time) to increase the rate at which we predict & 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 time to decrease the rate at which we predict & 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;
|
/* How along are we between master sim ticks (0 = start of tick, 1 = end of tick) */
|
||||||
if (math_abs_i64(cmds_ahead_on_master) > 50) {
|
f64 tick_progress = 0;
|
||||||
/* Cmds are too far from master time, snap step end tick */
|
i64 next_tick_expected_ns = last_tick_from_master_received_at_ns + average_master_receive_dt_ns;
|
||||||
i64 rtt_ns = master_client->rtt_ns;
|
if (next_tick_expected_ns > last_tick_from_master_received_at_ns) {
|
||||||
f64 rtt_tick_ratio = (f64)(rtt_ns + (step_dt_ns - 1)) / (f64)step_dt_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);
|
||||||
i64 num_predict_ticks = math_round_to_int64(rtt_tick_ratio) + 5;
|
}
|
||||||
step_end_tick = step_base_tick + num_predict_ticks;
|
|
||||||
compute_timescale = 1.1;
|
/* Predict master sim time based on average snapshot publish dt. */
|
||||||
} else if (cmds_ahead_on_master > 2) {
|
struct sim_snapshot *newest_snapshot = sim_snapshot_from_tick(master_client, master_client->last_tick);
|
||||||
/* Slow down simulation rate to bring sim time closer to master time */
|
i64 master_sim_predicted_time_ns = newest_snapshot->sim_time_ns + (newest_snapshot->sim_dt_ns * tick_progress);
|
||||||
compute_timescale = 0.95;
|
|
||||||
} else if (cmds_ahead_on_master < 1) {
|
/* Determine blend time */
|
||||||
/* Speed up simulation rate to give master more inputs to work with */
|
i64 master_blend_time_target_ns = master_sim_predicted_time_ns - (SIM_CLIENT_INTERP_RATIO * average_master_receive_dt_ns);
|
||||||
compute_timescale = 1.05;
|
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 > NS_FROM_SECONDS(0.100) || blend_time_target_diff_ns < NS_FROM_SECONDS(-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 */
|
||||||
|
struct sim_snapshot *left_snapshot = sim_snapshot_nil();
|
||||||
|
struct sim_snapshot *right_snapshot = newest_snapshot;
|
||||||
|
{
|
||||||
|
struct sim_snapshot *ss = sim_snapshot_from_tick(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 = sim_snapshot_from_tick(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 = false;
|
||||||
|
master_ss = left_snapshot;
|
||||||
|
} else if (blend > 1 - epsilon) {
|
||||||
|
master_ss_is_blended = false;
|
||||||
|
master_ss = right_snapshot;
|
||||||
|
} else {
|
||||||
|
master_ss_is_blended = true;
|
||||||
|
master_ss = sim_snapshot_alloc_from_lerp(master_blended_client, left_snapshot, right_snapshot, blend);
|
||||||
|
|
||||||
|
/* Release unneeded blended master snapshots */
|
||||||
|
if (master_ss->tick > 0) {
|
||||||
|
sim_snapshot_release_ticks_in_range(master_blended_client, 0, master_ss->tick - 1);
|
||||||
|
sim_snapshot_release_ticks_in_range(master_blended_client, master_ss->tick + 1, U64_MAX);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Server's cmd buffer is in a healthy range */
|
master_ss_is_blended = false;
|
||||||
compute_timescale = 1;
|
master_ss = left_snapshot->valid ? left_snapshot : right_snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Release unneeded master snapshots */
|
||||||
|
u64 keep_master_tick = min_u64(left_snapshot->tick, master_client->double_ack);
|
||||||
|
if (keep_master_tick > 0) {
|
||||||
|
sim_snapshot_release_ticks_in_range(master_client, 0, keep_master_tick - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
DEBUGBREAKABLE;
|
||||||
|
logf_debug("*************************************************");
|
||||||
|
logf_debug("local_client->last_tick: %F", FMT_UINT(local_client->last_tick));
|
||||||
|
logf_debug("master_sim_predicted_time_ns: %F", FMT_SINT(master_sim_predicted_time_ns));
|
||||||
|
logf_debug("tick_progress: %F", FMT_FLOAT(tick_progress));
|
||||||
|
logf_debug("sim_publish_timescale: %F", FMT_FLOAT(sim_publish_timescale));
|
||||||
|
logf_debug("last_tick_from_master_received_at_ns: %F", FMT_SINT(last_tick_from_master_received_at_ns));
|
||||||
|
logf_debug("average_master_receive_dt_ns: %F", FMT_SINT(average_master_receive_dt_ns));
|
||||||
|
logf_debug("next_tick_expected_ns: %F", FMT_SINT(next_tick_expected_ns));
|
||||||
|
logf_debug("master_blend_time_target_ns: %F", FMT_SINT(master_blend_time_target_ns));
|
||||||
|
logf_debug("blend_time_target_diff_ns: %F", FMT_SINT(blend_time_target_diff_ns));
|
||||||
|
logf_debug("master_blend_time_ns: %F", FMT_SINT(master_blend_time_ns));
|
||||||
|
logf_debug("left_snapshot->tick: %F", FMT_UINT(left_snapshot->tick));
|
||||||
|
logf_debug("right_snapshot->tick: %F", FMT_UINT(right_snapshot->tick));
|
||||||
|
logf_debug("master_ss->tick: %F", FMT_UINT(master_ss->tick));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (master_ss->valid) {
|
||||||
|
struct sim_ent *master_client_ent = sim_ent_find_first_match_one(master_ss, SIM_ENT_PROP_CLIENT_IS_MASTER);
|
||||||
|
|
||||||
|
/* Update ent id from master */
|
||||||
|
{
|
||||||
|
user_input_client->ent_id = master_ss->local_client_ent;
|
||||||
|
local_client->ent_id = master_ss->local_client_ent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 - 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 = math_round_to_int64(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 */
|
||||||
|
struct sim_snapshot *base_ss = sim_snapshot_from_tick(local_client, step_base_tick);
|
||||||
|
if (mispredicted_tick) {
|
||||||
|
if (base_ss->valid) {
|
||||||
|
sim_snapshot_sync_ents(base_ss, master_ss, master_client_ent->id, 0);
|
||||||
|
} else {
|
||||||
|
base_ss = sim_snapshot_alloc(local_client, master_ss, step_base_tick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Release any existing ticks that are about to be simulated */
|
||||||
|
sim_snapshot_release_ticks_in_range(local_client, step_base_tick + 1, U64_MAX);
|
||||||
|
|
||||||
|
/* Step */
|
||||||
|
generate_user_input_cmds(user_input_client, step_end_tick);
|
||||||
|
{
|
||||||
|
struct sim_step_ctx 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;
|
||||||
|
struct sim_snapshot *prev_ss = base_ss;
|
||||||
|
while (step_tick <= step_end_tick) {
|
||||||
|
ctx.world = sim_snapshot_alloc(local_client, prev_ss, step_tick);
|
||||||
|
if (!mispredicted_tick && step_tick == step_end_tick) {
|
||||||
|
sim_snapshot_sync_ents(ctx.world, master_ss, master_client_ent->id, SIM_SYNC_FLAG_NOSYNC_PREDICTABLES);
|
||||||
|
}
|
||||||
|
sim_step(&ctx);
|
||||||
|
prev_ss = ctx.world;
|
||||||
|
++step_tick;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Sync master with local base tick */
|
|
||||||
struct sim_snapshot *base_ss = sim_snapshot_from_tick(local_client, step_base_tick);
|
|
||||||
if (base_ss->valid) {
|
|
||||||
/* FIXME: Set master client ent id somewhere */
|
|
||||||
sim_snapshot_sync_ents(base_ss, master_ss, master_client_ent->id);
|
|
||||||
} else {
|
|
||||||
base_ss = sim_snapshot_alloc(local_client, master_ss, step_base_tick);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Release any existing ticks that are about to be simulated */
|
|
||||||
sim_snapshot_release_ticks_in_range(local_client, step_base_tick + 1, U64_MAX);
|
|
||||||
|
|
||||||
/* Step */
|
|
||||||
generate_user_input_cmds(user_input_client, step_end_tick);
|
|
||||||
{
|
|
||||||
struct sim_step_ctx 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;
|
|
||||||
struct sim_snapshot *prev_ss = base_ss;
|
|
||||||
while (step_tick <= step_end_tick) {
|
|
||||||
ctx.world = sim_snapshot_alloc(local_client, prev_ss, step_tick);
|
|
||||||
sim_step(&ctx);
|
|
||||||
prev_ss = ctx.world;
|
|
||||||
++step_tick;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -2454,11 +2469,11 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_local_sim_thread_entry_point, arg)
|
|||||||
if (local_ss->valid) {
|
if (local_ss->valid) {
|
||||||
/* TODO: Double buffer */
|
/* TODO: Double buffer */
|
||||||
struct sys_lock lock = sys_mutex_lock_e(&G.local_to_user_client_mutex);
|
struct sys_lock lock = sys_mutex_lock_e(&G.local_to_user_client_mutex);
|
||||||
struct sim_snapshot *copy_ss = sim_snapshot_alloc(G.local_to_user_client, local_ss, local_ss->tick);
|
sim_snapshot_alloc(G.local_to_user_client, local_ss, local_ss->tick);
|
||||||
i64 publish_ns = sys_time_ns();
|
i64 publish_ns = sys_time_ns();
|
||||||
copy_ss->publish_dt_ns = publish_ns - last_publish_ns;
|
G.local_to_user_client_publish_dt_ns = publish_ns - last_publish_to_user_ns;
|
||||||
copy_ss->publish_time_ns = publish_ns;
|
G.local_to_user_client_publish_time_ns = publish_ns;
|
||||||
last_publish_ns = publish_ns;
|
last_publish_to_user_ns = publish_ns;
|
||||||
sim_snapshot_release_ticks_in_range(G.local_to_user_client, 0, local_ss->tick - 1);
|
sim_snapshot_release_ticks_in_range(G.local_to_user_client, 0, local_ss->tick - 1);
|
||||||
sys_mutex_unlock(&lock);
|
sys_mutex_unlock(&lock);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user