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_SLIDER,
|
||||
DITEM_LIST,
|
||||
DITEM_COND,
|
||||
DITEM_END,
|
||||
|
||||
DITEM_FORCE_UINT8 = UINT8_MAX
|
||||
|
@ -52,6 +53,8 @@ typedef struct debug_item {
|
|||
} slider;
|
||||
|
||||
void (*on_click)(void);
|
||||
|
||||
uint8_t (*on_success)(void);
|
||||
};
|
||||
|
||||
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,
|
||||
.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;
|
||||
for (debug_item *it = list; it->kind != DITEM_END; it += 1) {
|
||||
switch (it->kind) {
|
||||
case DITEM_COND: {
|
||||
assert(it->on_success);
|
||||
|
||||
if (!it->on_success()) {
|
||||
it += 1;
|
||||
}
|
||||
}break;
|
||||
case DITEM_LIST: {
|
||||
// NOTE(zaklaus): calculate and cache name width for future use
|
||||
if (it->name_width == 0) {
|
||||
|
@ -166,16 +199,19 @@ debug_draw_result debug_draw_list(debug_item *list, float xpos, float ypos, bool
|
|||
}break;
|
||||
|
||||
case DITEM_BUTTON: {
|
||||
assert(it->on_click);
|
||||
char const *text = TextFormat("> %s", it->name);
|
||||
if (it->name_width == 0) {
|
||||
it->name_width = UIMeasureText(text, DBG_FONT_SIZE);
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
if (!it->on_click) {
|
||||
color = GRAY;
|
||||
}
|
||||
|
||||
debug_draw_result res = DrawColoredText(xpos, ypos, text, color);
|
||||
ypos = res.y;
|
||||
}break;
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
#include "modules/components.h"
|
||||
|
||||
#include "debug_replay.c"
|
||||
|
||||
static inline void
|
||||
ActExitGame(void) {
|
||||
game_request_close();
|
||||
|
@ -21,3 +23,41 @@ ActSpawnCar(void) {
|
|||
Position * dest = ecs_get_mut(world_ecs(), e, Position, NULL);
|
||||
*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) {
|
||||
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_owner_set(world_tracker(), e, (int64_t)e);
|
||||
}
|
||||
|
||||
return (uint64_t)e;
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
#include "profiler.h"
|
||||
|
||||
#include "flecs/flecs.h"
|
||||
#include "flecs/flecs_dash.h"
|
||||
#include "flecs/flecs_systems_civetweb.h"
|
||||
//#include "flecs/flecs_dash.h"
|
||||
//#include "flecs/flecs_systems_civetweb.h"
|
||||
#include "flecs/flecs_os_api_stdcpp.h"
|
||||
|
||||
#include "modules/components.h"
|
||||
|
@ -98,10 +98,12 @@ void game_world_view_set_active(world_view *view) {
|
|||
}
|
||||
|
||||
void flecs_dash_init() {
|
||||
#if 0
|
||||
ECS_IMPORT(world_ecs(), FlecsDash);
|
||||
ECS_IMPORT(world_ecs(), FlecsSystemsCivetweb);
|
||||
|
||||
ecs_set(world_ecs(), 0, EcsDashServer, {.port = 27001});
|
||||
#endif
|
||||
}
|
||||
|
||||
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 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);
|
||||
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")) {
|
||||
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);
|
||||
|
||||
// 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++) {
|
||||
uint64_t e = entity_spawn(EKIND_DEMO_NPC);
|
||||
ecs_add(world_ecs(), e, EcsDemoNPC);
|
||||
|
@ -81,6 +81,7 @@ int main(int argc, char** argv) {
|
|||
v->y = (rand()%3-1) * 100;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
while (game_is_running()) {
|
||||
profile (PROF_MAIN_LOOP) {
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include "modules/systems.h"
|
||||
#include "world/world.h"
|
||||
|
||||
#include "debug_replay.h"
|
||||
|
||||
pkt_desc pkt_send_keystate_desc[] = {
|
||||
{ PKT_REAL(pkt_send_keystate, x) },
|
||||
{ PKT_REAL(pkt_send_keystate, y) },
|
||||
|
@ -39,6 +41,7 @@ int32_t pkt_send_keystate_handler(pkt_header *header) {
|
|||
i->y = table.y;
|
||||
i->use = table.use;
|
||||
i->sprint = table.sprint;
|
||||
debug_replay_record_keystate(table);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -34,6 +34,3 @@ uint64_t player_spawn(char *name) {
|
|||
void player_despawn(uint64_t 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);
|
||||
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 "world/world.h"
|
||||
#include "entity_view.h"
|
||||
#include "debug_replay.h"
|
||||
#include "world/worldgen/worldgen.h"
|
||||
#include "platform.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();
|
||||
ecs_set_entity_range(world.ecs, 0, UINT32_MAX);
|
||||
|
||||
ECS_IMPORT(world.ecs, Components);
|
||||
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(1, WORLD_TRACKER_UPDATE_NORMAL_MS, 4);
|
||||
world_tracker_update(2, WORLD_TRACKER_UPDATE_SLOW_MS, 6);
|
||||
|
||||
debug_replay_update();
|
||||
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);
|
||||
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_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);
|
||||
|
||||
uint8_t world_entity_valid(ecs_entity_t e);
|
Loading…
Reference in New Issue