code: demo record/playback feature
parent
f7ee8d0f11
commit
f56c45e195
|
@ -0,0 +1,94 @@
|
||||||
|
#include "debug_replay.h"
|
||||||
|
#include "camera.h"
|
||||||
|
#include "entity.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
pkt_send_keystate pkt;
|
||||||
|
uint64_t delay;
|
||||||
|
} replay_record;
|
||||||
|
|
||||||
|
static uint8_t is_recording = false;
|
||||||
|
static replay_record *records = NULL;
|
||||||
|
static uint64_t last_record_time = 0.0f;
|
||||||
|
|
||||||
|
static uint8_t is_playing = false;
|
||||||
|
static int record_pos = 0;
|
||||||
|
static uint64_t playback_time = 0;
|
||||||
|
static ecs_entity_t mime = 0;
|
||||||
|
static ecs_entity_t plr = 0;
|
||||||
|
|
||||||
|
void debug_replay_start(void) {
|
||||||
|
is_recording = true;
|
||||||
|
|
||||||
|
if (records) zpl_array_free(records);
|
||||||
|
zpl_array_init(records, zpl_heap());
|
||||||
|
|
||||||
|
last_record_time = zpl_time_rel_ms();
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug_replay_clear(void) {
|
||||||
|
if (!records || is_playing || is_recording) return;
|
||||||
|
zpl_array_free(records);
|
||||||
|
records = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug_replay_stop(void) {
|
||||||
|
is_recording = false;
|
||||||
|
// TODO(zaklaus):
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug_replay_run(void) {
|
||||||
|
if (mime) return;
|
||||||
|
is_playing = true;
|
||||||
|
record_pos = 0;
|
||||||
|
playback_time = zpl_time_rel_ms();
|
||||||
|
|
||||||
|
plr = camera_get().ent_id;
|
||||||
|
Position const *p1 = ecs_get(world_ecs(), plr, Position);
|
||||||
|
|
||||||
|
mime = entity_spawn(EKIND_DEMO_NPC);
|
||||||
|
Position *pos = ecs_get_mut(world_ecs(), mime, Position, NULL);
|
||||||
|
*pos = *p1;
|
||||||
|
|
||||||
|
ecs_set(world_ecs(), mime, Input, {0});
|
||||||
|
|
||||||
|
camera_set_follow(mime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug_replay_update(void) {
|
||||||
|
if (!is_playing) return;
|
||||||
|
if (playback_time >= zpl_time_rel_ms()) return;
|
||||||
|
|
||||||
|
replay_record *r = &records[record_pos];
|
||||||
|
playback_time = zpl_time_rel() + r->delay;
|
||||||
|
|
||||||
|
Input *i = ecs_get_mut(world_ecs(), mime, Input, NULL);
|
||||||
|
i->x = r->pkt.x;
|
||||||
|
i->y = r->pkt.y;
|
||||||
|
i->use = r->pkt.use;
|
||||||
|
i->sprint = r->pkt.sprint;
|
||||||
|
|
||||||
|
record_pos += 1;
|
||||||
|
|
||||||
|
// NOTE(zaklaus): remove our dummy art exhibist
|
||||||
|
if (mime && record_pos == zpl_array_count(records)) {
|
||||||
|
entity_despawn(mime);
|
||||||
|
mime = 0;
|
||||||
|
|
||||||
|
is_playing = false;
|
||||||
|
camera_set_follow(plr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug_replay_record_keystate(pkt_send_keystate state) {
|
||||||
|
if (!is_recording) return;
|
||||||
|
float record_time = zpl_time_rel_ms();
|
||||||
|
|
||||||
|
replay_record rec = {
|
||||||
|
.pkt = state,
|
||||||
|
.delay = (record_time - last_record_time),
|
||||||
|
};
|
||||||
|
|
||||||
|
zpl_array_append(records, rec);
|
||||||
|
last_record_time = zpl_time_rel_ms();
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
#pragma once
|
||||||
|
#include "system.h"
|
||||||
|
#include "packets/pkt_send_keystate.h"
|
||||||
|
|
||||||
|
void debug_replay_record_keystate(pkt_send_keystate state);
|
||||||
|
void debug_replay_update(void);
|
|
@ -7,6 +7,7 @@ typedef enum {
|
||||||
DITEM_BUTTON,
|
DITEM_BUTTON,
|
||||||
DITEM_SLIDER,
|
DITEM_SLIDER,
|
||||||
DITEM_LIST,
|
DITEM_LIST,
|
||||||
|
DITEM_COND,
|
||||||
DITEM_END,
|
DITEM_END,
|
||||||
|
|
||||||
DITEM_FORCE_UINT8 = UINT8_MAX
|
DITEM_FORCE_UINT8 = UINT8_MAX
|
||||||
|
@ -52,6 +53,8 @@ typedef struct debug_item {
|
||||||
} slider;
|
} slider;
|
||||||
|
|
||||||
void (*on_click)(void);
|
void (*on_click)(void);
|
||||||
|
|
||||||
|
uint8_t (*on_success)(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
debug_draw_result (*proc)(struct debug_item*, float, float);
|
debug_draw_result (*proc)(struct debug_item*, float, float);
|
||||||
|
@ -99,6 +102,29 @@ static debug_item items[] = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.kind = DITEM_LIST,
|
||||||
|
.name = "replay system",
|
||||||
|
.list = {
|
||||||
|
.items = (debug_item[]) {
|
||||||
|
{ .kind = DITEM_TEXT, .name = "macro", .text = "<unnamed>", .proc = DrawLiteral },
|
||||||
|
{ .kind = DITEM_BUTTON, .name = "load", .on_click = NULL },
|
||||||
|
{ .kind = DITEM_BUTTON, .name = "save", .on_click = NULL },
|
||||||
|
|
||||||
|
{ .kind = DITEM_COND, .on_success = CondReplayStatusOff },
|
||||||
|
{ .kind = DITEM_BUTTON, .name = "record", .on_click = ActReplayBegin },
|
||||||
|
|
||||||
|
{ .kind = DITEM_COND, .on_success = CondReplayStatusOn },
|
||||||
|
{ .kind = DITEM_BUTTON, .name = "stop", .on_click = ActReplayEnd },
|
||||||
|
|
||||||
|
{ .kind = DITEM_COND, .on_success = CondReplayDataPresent },
|
||||||
|
{ .kind = DITEM_BUTTON, .name = "replay", .on_click = ActReplayRun },
|
||||||
|
|
||||||
|
{ .kind = DITEM_BUTTON, .name = "clear", .on_click = ActReplayClear },
|
||||||
|
{ .kind = DITEM_END },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.kind = DITEM_LIST,
|
.kind = DITEM_LIST,
|
||||||
.name = "profilers",
|
.name = "profilers",
|
||||||
|
@ -129,6 +155,13 @@ debug_draw_result debug_draw_list(debug_item *list, float xpos, float ypos, bool
|
||||||
is_shadow_rendered = is_shadow;
|
is_shadow_rendered = is_shadow;
|
||||||
for (debug_item *it = list; it->kind != DITEM_END; it += 1) {
|
for (debug_item *it = list; it->kind != DITEM_END; it += 1) {
|
||||||
switch (it->kind) {
|
switch (it->kind) {
|
||||||
|
case DITEM_COND: {
|
||||||
|
assert(it->on_success);
|
||||||
|
|
||||||
|
if (!it->on_success()) {
|
||||||
|
it += 1;
|
||||||
|
}
|
||||||
|
}break;
|
||||||
case DITEM_LIST: {
|
case DITEM_LIST: {
|
||||||
// NOTE(zaklaus): calculate and cache name width for future use
|
// NOTE(zaklaus): calculate and cache name width for future use
|
||||||
if (it->name_width == 0) {
|
if (it->name_width == 0) {
|
||||||
|
@ -166,16 +199,19 @@ debug_draw_result debug_draw_list(debug_item *list, float xpos, float ypos, bool
|
||||||
}break;
|
}break;
|
||||||
|
|
||||||
case DITEM_BUTTON: {
|
case DITEM_BUTTON: {
|
||||||
assert(it->on_click);
|
|
||||||
char const *text = TextFormat("> %s", it->name);
|
char const *text = TextFormat("> %s", it->name);
|
||||||
if (it->name_width == 0) {
|
if (it->name_width == 0) {
|
||||||
it->name_width = UIMeasureText(text, DBG_FONT_SIZE);
|
it->name_width = UIMeasureText(text, DBG_FONT_SIZE);
|
||||||
}
|
}
|
||||||
Color color = RAYWHITE;
|
Color color = RAYWHITE;
|
||||||
if (is_btn_pressed(xpos, ypos, it->name_width, DBG_FONT_SIZE, &color)) {
|
if (is_btn_pressed(xpos, ypos, it->name_width, DBG_FONT_SIZE, &color) && it->on_click) {
|
||||||
it->on_click();
|
it->on_click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!it->on_click) {
|
||||||
|
color = GRAY;
|
||||||
|
}
|
||||||
|
|
||||||
debug_draw_result res = DrawColoredText(xpos, ypos, text, color);
|
debug_draw_result res = DrawColoredText(xpos, ypos, text, color);
|
||||||
ypos = res.y;
|
ypos = res.y;
|
||||||
}break;
|
}break;
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
#include "modules/components.h"
|
#include "modules/components.h"
|
||||||
|
|
||||||
|
#include "debug_replay.c"
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
ActExitGame(void) {
|
ActExitGame(void) {
|
||||||
game_request_close();
|
game_request_close();
|
||||||
|
@ -21,3 +23,41 @@ ActSpawnCar(void) {
|
||||||
Position * dest = ecs_get_mut(world_ecs(), e, Position, NULL);
|
Position * dest = ecs_get_mut(world_ecs(), e, Position, NULL);
|
||||||
*dest = *origin;
|
*dest = *origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE(zaklaus): Replay system
|
||||||
|
|
||||||
|
static inline uint8_t
|
||||||
|
CondReplayStatusOn(void) {
|
||||||
|
return is_recording;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint8_t
|
||||||
|
CondReplayStatusOff(void) {
|
||||||
|
return !is_recording;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint8_t
|
||||||
|
CondReplayDataPresent(void) {
|
||||||
|
return records != NULL && !is_recording;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
ActReplayBegin(void) {
|
||||||
|
debug_replay_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
ActReplayEnd(void) {
|
||||||
|
debug_replay_stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
ActReplayRun(void) {
|
||||||
|
debug_replay_run();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
ActReplayClear(void) {
|
||||||
|
debug_replay_clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ uint64_t entity_spawn(uint16_t class_id) {
|
||||||
if (class_id != EKIND_SERVER) {
|
if (class_id != EKIND_SERVER) {
|
||||||
librg_entity_track(world_tracker(), e);
|
librg_entity_track(world_tracker(), e);
|
||||||
librg_entity_chunk_set(world_tracker(), e, librg_chunk_from_realpos(world_tracker(), pos->x, pos->y, 0));
|
librg_entity_chunk_set(world_tracker(), e, librg_chunk_from_realpos(world_tracker(), pos->x, pos->y, 0));
|
||||||
|
librg_entity_owner_set(world_tracker(), e, (int64_t)e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (uint64_t)e;
|
return (uint64_t)e;
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
#include "profiler.h"
|
#include "profiler.h"
|
||||||
|
|
||||||
#include "flecs/flecs.h"
|
#include "flecs/flecs.h"
|
||||||
#include "flecs/flecs_dash.h"
|
//#include "flecs/flecs_dash.h"
|
||||||
#include "flecs/flecs_systems_civetweb.h"
|
//#include "flecs/flecs_systems_civetweb.h"
|
||||||
#include "flecs/flecs_os_api_stdcpp.h"
|
#include "flecs/flecs_os_api_stdcpp.h"
|
||||||
|
|
||||||
#include "modules/components.h"
|
#include "modules/components.h"
|
||||||
|
@ -98,10 +98,12 @@ void game_world_view_set_active(world_view *view) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void flecs_dash_init() {
|
void flecs_dash_init() {
|
||||||
|
#if 0
|
||||||
ECS_IMPORT(world_ecs(), FlecsDash);
|
ECS_IMPORT(world_ecs(), FlecsDash);
|
||||||
ECS_IMPORT(world_ecs(), FlecsSystemsCivetweb);
|
ECS_IMPORT(world_ecs(), FlecsSystemsCivetweb);
|
||||||
|
|
||||||
ecs_set(world_ecs(), 0, EcsDashServer, {.port = 27001});
|
ecs_set(world_ecs(), 0, EcsDashServer, {.port = 27001});
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
float game_time() {
|
float game_time() {
|
||||||
|
|
|
@ -49,7 +49,7 @@ int main(int argc, char** argv) {
|
||||||
uint16_t num_viewers = zpl_opts_integer(&opts, "viewer-count", 1);
|
uint16_t num_viewers = zpl_opts_integer(&opts, "viewer-count", 1);
|
||||||
uint16_t chunk_size = DEFAULT_CHUNK_SIZE; //zpl_opts_integer(&opts, "chunk-size", DEFAULT_CHUNK_SIZE);
|
uint16_t chunk_size = DEFAULT_CHUNK_SIZE; //zpl_opts_integer(&opts, "chunk-size", DEFAULT_CHUNK_SIZE);
|
||||||
uint16_t world_size = zpl_opts_integer(&opts, "world-size", DEFAULT_WORLD_SIZE);
|
uint16_t world_size = zpl_opts_integer(&opts, "world-size", DEFAULT_WORLD_SIZE);
|
||||||
uint32_t npc_count = zpl_opts_integer(&opts, "npc-count", 10000);
|
uint32_t npc_count = zpl_opts_integer(&opts, "npc-count", 1000);
|
||||||
|
|
||||||
if (zpl_opts_has_arg(&opts, "random-seed")) {
|
if (zpl_opts_has_arg(&opts, "random-seed")) {
|
||||||
zpl_random rnd={0};
|
zpl_random rnd={0};
|
||||||
|
@ -67,8 +67,8 @@ int main(int argc, char** argv) {
|
||||||
game_init(is_viewer_only, num_viewers, seed, chunk_size, world_size, is_dash_enabled);
|
game_init(is_viewer_only, num_viewers, seed, chunk_size, world_size, is_dash_enabled);
|
||||||
|
|
||||||
// TODO(zaklaus): VERY TEMPORARY -- SPAWN SOME NPCS THAT RANDOMLY MOVE
|
// TODO(zaklaus): VERY TEMPORARY -- SPAWN SOME NPCS THAT RANDOMLY MOVE
|
||||||
|
#if 1
|
||||||
{
|
{
|
||||||
ECS_IMPORT(world_ecs(), Components);
|
|
||||||
for (uint32_t i = 0; i < npc_count; i++) {
|
for (uint32_t i = 0; i < npc_count; i++) {
|
||||||
uint64_t e = entity_spawn(EKIND_DEMO_NPC);
|
uint64_t e = entity_spawn(EKIND_DEMO_NPC);
|
||||||
ecs_add(world_ecs(), e, EcsDemoNPC);
|
ecs_add(world_ecs(), e, EcsDemoNPC);
|
||||||
|
@ -81,6 +81,7 @@ int main(int argc, char** argv) {
|
||||||
v->y = (rand()%3-1) * 100;
|
v->y = (rand()%3-1) * 100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
while (game_is_running()) {
|
while (game_is_running()) {
|
||||||
profile (PROF_MAIN_LOOP) {
|
profile (PROF_MAIN_LOOP) {
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
#include "modules/systems.h"
|
#include "modules/systems.h"
|
||||||
#include "world/world.h"
|
#include "world/world.h"
|
||||||
|
|
||||||
|
#include "debug_replay.h"
|
||||||
|
|
||||||
pkt_desc pkt_send_keystate_desc[] = {
|
pkt_desc pkt_send_keystate_desc[] = {
|
||||||
{ PKT_REAL(pkt_send_keystate, x) },
|
{ PKT_REAL(pkt_send_keystate, x) },
|
||||||
{ PKT_REAL(pkt_send_keystate, y) },
|
{ PKT_REAL(pkt_send_keystate, y) },
|
||||||
|
@ -39,6 +41,7 @@ int32_t pkt_send_keystate_handler(pkt_header *header) {
|
||||||
i->y = table.y;
|
i->y = table.y;
|
||||||
i->use = table.use;
|
i->use = table.use;
|
||||||
i->sprint = table.sprint;
|
i->sprint = table.sprint;
|
||||||
|
debug_replay_record_keystate(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -34,6 +34,3 @@ uint64_t player_spawn(char *name) {
|
||||||
void player_despawn(uint64_t ent_id) {
|
void player_despawn(uint64_t ent_id) {
|
||||||
entity_despawn(ent_id);
|
entity_despawn(ent_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void player_freeze(uint64_t id, uint8_t state, uint8_t clear) {
|
|
||||||
}
|
|
|
@ -3,5 +3,3 @@
|
||||||
|
|
||||||
uint64_t player_spawn(char *name);
|
uint64_t player_spawn(char *name);
|
||||||
void player_despawn(uint64_t ent_id);
|
void player_despawn(uint64_t ent_id);
|
||||||
|
|
||||||
void player_freeze(uint64_t id, uint8_t state, uint8_t clear);
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "modules/systems.h"
|
#include "modules/systems.h"
|
||||||
#include "world/world.h"
|
#include "world/world.h"
|
||||||
#include "entity_view.h"
|
#include "entity_view.h"
|
||||||
|
#include "debug_replay.h"
|
||||||
#include "world/worldgen/worldgen.h"
|
#include "world/worldgen/worldgen.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "profiler.h"
|
#include "profiler.h"
|
||||||
|
@ -151,7 +152,6 @@ int32_t world_init(int32_t seed, uint16_t chunk_size, uint16_t chunk_amount) {
|
||||||
}
|
}
|
||||||
|
|
||||||
world.ecs = ecs_init();
|
world.ecs = ecs_init();
|
||||||
ecs_set_entity_range(world.ecs, 0, UINT32_MAX);
|
|
||||||
|
|
||||||
ECS_IMPORT(world.ecs, Components);
|
ECS_IMPORT(world.ecs, Components);
|
||||||
ECS_IMPORT(world.ecs, Systems);
|
ECS_IMPORT(world.ecs, Systems);
|
||||||
|
@ -257,6 +257,8 @@ int32_t world_update() {
|
||||||
world_tracker_update(0, WORLD_TRACKER_UPDATE_FAST_MS, 2);
|
world_tracker_update(0, WORLD_TRACKER_UPDATE_FAST_MS, 2);
|
||||||
world_tracker_update(1, WORLD_TRACKER_UPDATE_NORMAL_MS, 4);
|
world_tracker_update(1, WORLD_TRACKER_UPDATE_NORMAL_MS, 4);
|
||||||
world_tracker_update(2, WORLD_TRACKER_UPDATE_SLOW_MS, 6);
|
world_tracker_update(2, WORLD_TRACKER_UPDATE_SLOW_MS, 6);
|
||||||
|
|
||||||
|
debug_replay_update();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,3 +410,8 @@ int64_t *world_chunk_query_entities(int64_t e, size_t *ents_len, int8_t radius)
|
||||||
librg_world_query(world.tracker, e, ents, ents_len);
|
librg_world_query(world.tracker, e, ents, ents_len);
|
||||||
return ents;
|
return ents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t world_entity_valid(ecs_entity_t e) {
|
||||||
|
if (!e) return false;
|
||||||
|
return ecs_is_alive(world.ecs, e);
|
||||||
|
}
|
||||||
|
|
|
@ -82,3 +82,5 @@ uint8_t world_chunk_is_dirty(ecs_entity_t e);
|
||||||
int64_t *world_chunk_fetch_entities(librg_chunk chunk_id, size_t *ents_len);
|
int64_t *world_chunk_fetch_entities(librg_chunk chunk_id, size_t *ents_len);
|
||||||
int64_t *world_chunk_fetch_entities_realpos(float x, float y, size_t *ents_len);
|
int64_t *world_chunk_fetch_entities_realpos(float x, float y, size_t *ents_len);
|
||||||
int64_t *world_chunk_query_entities(int64_t e, size_t *ents_len, int8_t radius);
|
int64_t *world_chunk_query_entities(int64_t e, size_t *ents_len, int8_t radius);
|
||||||
|
|
||||||
|
uint8_t world_entity_valid(ecs_entity_t e);
|
Loading…
Reference in New Issue