code: Improvements to the replay system

isolation_bkp/dynres
Dominik Madarász 2021-08-10 20:31:05 +02:00
parent 3c46cca96d
commit d887a0987d
11 changed files with 497 additions and 11 deletions

BIN
art/driving.dem 100644

Binary file not shown.

View File

@ -38,6 +38,6 @@ add_executable(eco2d
target_compile_definitions(eco2d PRIVATE CLIENT)
include_directories(src ../modules ../../art/gen)
target_link_libraries(eco2d raylib cwpack eco2d-modules flecs-bundle)
target_link_libraries(eco2d raylib cwpack eco2d-modules flecs-bundle vendors-bundle)
link_system_libs(eco2d)

View File

@ -2,7 +2,17 @@
#include "camera.h"
#include "entity.h"
#include "cwpack/cwpack.h"
typedef enum {
RPKIND_KEY,
// NOTE(zaklaus): Special actions
RPKIND_SPAWN_CAR,
} replay_kind;
typedef struct {
replay_kind kind;
pkt_send_keystate pkt;
uint64_t delay;
} replay_record;
@ -17,6 +27,66 @@ static uint64_t playback_time = 0;
static ecs_entity_t mime = 0;
static ecs_entity_t plr = 0;
#define REPLAY_MAGIC 0x421DC97E
#define REPLAY_VERSION 2
static char replay_filename[1024] = {0};
static char replaybuf[UINT16_MAX];
void debug_replay_store(void) {
assert(replay_filename[0]);
assert(records);
cw_pack_context pc = {0};
cw_pack_context_init(&pc, replaybuf, sizeof(replaybuf), 0);
cw_pack_unsigned(&pc, REPLAY_MAGIC);
cw_pack_unsigned(&pc, REPLAY_VERSION);
cw_pack_array_size(&pc, zpl_array_count(records));
for (int i = 0; i < zpl_array_count(records); i++) {
cw_pack_bin(&pc, &records[i], sizeof(replay_record));
}
zpl_file f = {0};
zpl_file_create(&f, replay_filename);
zpl_file_write(&f, replaybuf, pc.current - pc.start);
zpl_file_close(&f);
}
void debug_replay_load(void) {
assert(replay_filename[0]);
zpl_file f = {0};
assert(zpl_file_open(&f, replay_filename) == ZPL_FILE_ERROR_NONE);
size_t file_size = zpl_file_size(&f);
zpl_file_read(&f, replaybuf, file_size);
zpl_file_close(&f);
cw_unpack_context uc = {0};
cw_unpack_context_init(&uc, replaybuf, file_size, 0);
cw_unpack_next(&uc);
assert(uc.item.type == CWP_ITEM_POSITIVE_INTEGER && uc.item.as.u64 == REPLAY_MAGIC);
cw_unpack_next(&uc);
assert(uc.item.type == CWP_ITEM_POSITIVE_INTEGER && uc.item.as.u64 == REPLAY_VERSION);
cw_unpack_next(&uc);
assert(uc.item.type == CWP_ITEM_ARRAY);
size_t items = uc.item.as.array.size;
zpl_array_init_reserve(records, zpl_heap(), sizeof(replay_record)*items);
for (size_t i = 0; i < items; i++) {
cw_unpack_next(&uc);
assert(uc.item.type == CWP_ITEM_BIN);
replay_record rec = {0};
zpl_memcopy(&rec, uc.item.as.bin.start, sizeof(replay_record));
zpl_array_append(records, rec);
}
}
void debug_replay_start(void) {
is_recording = true;
@ -46,7 +116,7 @@ void debug_replay_run(void) {
plr = camera_get().ent_id;
Position const *p1 = ecs_get(world_ecs(), plr, Position);
mime = entity_spawn(EKIND_DEMO_NPC);
mime = entity_spawn(EKIND_MACRO_BOT);
Position *pos = ecs_get_mut(world_ecs(), mime, Position, NULL);
*pos = *p1;
@ -62,11 +132,25 @@ void debug_replay_update(void) {
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;
switch (r->kind) {
case RPKIND_KEY: {
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;
}break;
case RPKIND_SPAWN_CAR: {
ecs_entity_t e = vehicle_spawn();
Position const *origin = ecs_get(world_ecs(), mime, Position);
Position *dest = ecs_get_mut(world_ecs(), e, Position, NULL);
*dest = *origin;
}break;
default: {
ZPL_PANIC("unreachable");
}break;
}
record_pos += 1;
@ -85,6 +169,7 @@ void debug_replay_record_keystate(pkt_send_keystate state) {
float record_time = zpl_time_rel_ms();
replay_record rec = {
.kind = RPKIND_KEY,
.pkt = state,
.delay = (record_time - last_record_time),
};
@ -92,3 +177,17 @@ void debug_replay_record_keystate(pkt_send_keystate state) {
zpl_array_append(records, rec);
last_record_time = zpl_time_rel_ms();
}
void debug_replay_special_action(replay_kind kind) {
assert(kind != RPKIND_KEY);
if (!is_recording || is_playing) return;
float record_time = zpl_time_rel_ms();
replay_record rec = {
.kind = kind,
.delay = (record_time - last_record_time),
};
zpl_array_append(records, rec);
last_record_time = zpl_time_rel_ms();
}

View File

@ -4,11 +4,13 @@
#include "camera.h"
#include "world/world.h"
#include "game.h"
#include "sfd.h"
#include "modules/components.h"
typedef enum {
DITEM_RAW,
DITEM_GAP,
DITEM_TEXT,
DITEM_BUTTON,
DITEM_SLIDER,
@ -31,6 +33,7 @@ typedef struct {
#define DBG_SHADOW_OFFSET_XPOS 1
#define DBG_SHADOW_OFFSET_YPOS 1
#define DBG_CTRL_HANDLE_DIM 10
#define DBG_GAP_HEIGHT DBG_FONT_SPACING * 0.5f
static uint8_t is_shadow_rendered;
static uint8_t is_debug_open = 1;
@ -115,10 +118,14 @@ static debug_item items[] = {
.name = "replay system",
.list = {
.items = (debug_item[]) {
{ .kind = DITEM_TEXT, .name = "macro", .text = "<unnamed>", .proc = DrawLiteral },
{ .kind = DITEM_TEXT, .name = "macro", .proc = DrawReplayFileName },
{ .kind = DITEM_TEXT, .name = "samples", .proc = DrawReplaySamples },
{ .kind = DITEM_BUTTON, .name = "load", .on_click = NULL },
{ .kind = DITEM_BUTTON, .name = "save", .on_click = NULL },
{ .kind = DITEM_BUTTON, .name = "new", .on_click = ActReplayNew },
{ .kind = DITEM_BUTTON, .name = "load", .on_click = ActReplayLoad },
{ .kind = DITEM_BUTTON, .name = "save", .on_click = ActReplaySave },
{ .kind = DITEM_BUTTON, .name = "save as...", .on_click = ActReplaySaveAs },
{ .kind = DITEM_GAP },
{ .kind = DITEM_COND, .on_success = CondReplayStatusOff },
{ .kind = DITEM_BUTTON, .name = "record", .on_click = ActReplayBegin },
@ -164,6 +171,9 @@ 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_GAP: {
ypos += DBG_GAP_HEIGHT;
}break;
case DITEM_COND: {
assert(it->on_success);

View File

@ -13,6 +13,8 @@ ActSpawnCar(void) {
Position const* origin = ecs_get(world_ecs(), plr, Position);
Position * dest = ecs_get_mut(world_ecs(), e, Position, NULL);
*dest = *origin;
debug_replay_special_action(RPKIND_SPAWN_CAR);
}
// NOTE(zaklaus): Replay system
@ -52,3 +54,61 @@ ActReplayClear(void) {
debug_replay_clear();
}
static inline void
ActReplayNew(void) {
debug_replay_clear();
zpl_zero_size(replay_filename, sizeof(replay_filename));
}
static inline void
ActReplaySaveAs(void) {
if (!records) return;
char const *workdir = GetWorkingDirectory();
sfd_Options sfd = {
.title = "Save Macro",
.path = "art",
.filter_name = "eco2d Macro",
.filter = "*.dem",
};
char const *path = sfd_save_dialog(&sfd);
ChangeDirectory(workdir);
if (path) {
zpl_strcpy(replay_filename, zpl_bprintf("%s.dem", path));
debug_replay_store();
}
}
static inline void
ActReplaySave(void) {
if (!replay_filename[0]) {
ActReplaySaveAs();
}
else debug_replay_store();
}
static inline void
ActReplayLoad(void) {
char const *workdir = GetWorkingDirectory();
sfd_Options sfd = {
.title = "Load Macro",
.path = "art",
.filter_name = "eco2d Macro",
.filter = "*.dem",
};
char const *path = sfd_open_dialog(&sfd);
ChangeDirectory(workdir);
if (path) {
zpl_strcpy(replay_filename, path);
debug_replay_clear();
debug_replay_load();
}
}

View File

@ -70,3 +70,9 @@ DrawReplaySamples(debug_item *it, float xpos, float ypos) {
}
return DrawFormattedText(xpos, ypos, TextFormat("%d of %d", record_pos, cnt));
}
static inline debug_draw_result
DrawReplayFileName(debug_item *it, float xpos, float ypos) {
(void)it;
return DrawFormattedText(xpos, ypos, TextFormat("%s", replay_filename[0] ? replay_filename : "<unnamed>"));
}

View File

@ -10,6 +10,7 @@ typedef enum {
EKIND_VEHICLE,
EKIND_DEMO_NPC,
EKIND_MONSTER,
EKIND_MACRO_BOT,
EKIND_CHUNK,
FORCE_EKIND_UINT16 = UINT16_MAX
} entity_kind;

View File

@ -108,6 +108,7 @@ void platform_input() {
void display_conn_status();
void DEBUG_draw_entities_low(uint64_t key, entity_view * data);
void DEBUG_draw_entities(uint64_t key, entity_view * data);
void DEBUG_draw_ground(uint64_t key, entity_view * data);
@ -134,6 +135,7 @@ void platform_render() {
ClearBackground(GetColor(0x222034));
BeginMode2D(render_camera);
game_world_view_active_entity_map(DEBUG_draw_ground);
game_world_view_active_entity_map(DEBUG_draw_entities_low);
game_world_view_active_entity_map(DEBUG_draw_entities);
EndMode2D();
display_conn_status();
@ -217,7 +219,24 @@ void DEBUG_draw_entities(uint64_t key, entity_view * data) {
DrawTextEco(title, x-title_w/2, y-size-font_size-fixed_title_offset, font_size, ColorAlpha(RAYWHITE, data->tran_time), font_spacing);
#endif
DrawCircleEco(x, y, size, ColorAlpha(YELLOW, data->tran_time));
}break;
}break;
case EKIND_MACRO_BOT: {
float x = data->x;
float y = data->y;
const char *title = TextFormat("Bot %d", key);
int title_w = MeasureTextEco(title, font_size, font_spacing);
DrawRectangleEco(x-title_w/2-title_bg_offset/2, y-size-font_size-fixed_title_offset, title_w+title_bg_offset, font_size, ColorAlpha(GRAY, data->tran_time));
DrawTextEco(title, x-title_w/2, y-size-font_size-fixed_title_offset, font_size, ColorAlpha(BLACK, data->tran_time), font_spacing);
DrawCircleEco(x, y, size, ColorAlpha(PURPLE, data->tran_time));
}break;
default:break;
}
}
void DEBUG_draw_entities_low(uint64_t key, entity_view * data) {
(void)key;
switch (data->kind) {
case EKIND_VEHICLE: {
float x = data->x;
float y = data->y;

View File

@ -1,2 +1,7 @@
add_subdirectory(flecs)
add_subdirectory(cwpack)
add_library(vendors-bundle STATIC
sfd.c
)

261
code/vendors/sfd.c vendored 100644
View File

@ -0,0 +1,261 @@
/*
* Copyright (c) 2017 rxi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include "sfd.h"
static const char *last_error;
const char* sfd_get_error(void) {
const char *res = last_error;
last_error = NULL;
return res;
}
static int next_filter(char *dst, const char **p) {
int len;
*p += strspn(*p, "|");
if (**p == '\0') {
return 0;
}
len = strcspn(*p, "|");
memcpy(dst, *p, len);
dst[len] = '\0';
*p += len;
return 1;
}
/******************************************************************************
** Windows
*******************************************************************************/
#ifdef _WIN32
#include <windows.h>
typedef struct {
unsigned long process_id;
void* handle_root;
void* handle_first;
} FindMainWindowInfo;
static int find_main_window_callback(HWND handle, LPARAM lParam) {
FindMainWindowInfo* info = (FindMainWindowInfo*)lParam;
unsigned long process_id = 0;
GetWindowThreadProcessId(handle, &process_id);
if (info->process_id == process_id) {
info->handle_first = handle;
if (GetWindow(handle, GW_OWNER) == 0 && IsWindowVisible(handle)) {
info->handle_root = handle;
return 0;
}
}
return 1;
}
static HWND find_main_window() {
FindMainWindowInfo info = {
.process_id = GetCurrentProcessId()
};
EnumWindows(find_main_window_callback, (LPARAM)&info);
return info.handle_root;
}
static const char* make_filter_str(sfd_Options *opt) {
static char buf[1024];
int n;
buf[0] = '\0';
n = 0;
if (opt->filter) {
const char *p;
char b[32];
const char *name = opt->filter_name ? opt->filter_name : opt->filter;
n += sprintf(buf + n, "%s", name) + 1;
p = opt->filter;
while (next_filter(b, &p)) {
n += sprintf(buf + n, "%s;", b);
}
buf[++n] = '\0';
}
n += sprintf(buf + n, "All Files") + 1;
n += sprintf(buf + n, "*.*");
buf[++n] = '\0';
return buf;
}
static void init_ofn(OPENFILENAME *ofn, sfd_Options *opt) {
static char result_buf[2048];
result_buf[0] = '\0';
memset(ofn, 0, sizeof(*ofn));
ofn->hwndOwner = find_main_window();
ofn->lStructSize = sizeof(*ofn);
ofn->lpstrFilter = make_filter_str(opt);
ofn->nFilterIndex = 1;
ofn->lpstrFile = result_buf;
ofn->Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
ofn->nMaxFile = sizeof(result_buf) - 1;
ofn->lpstrInitialDir = opt->path;
ofn->lpstrTitle = opt->title;
ofn->lpstrDefExt = opt->extension;
}
const char* sfd_open_dialog(sfd_Options *opt) {
int ok;
OPENFILENAME ofn;
last_error = NULL;
init_ofn(&ofn, opt);
ok = GetOpenFileName(&ofn);
return ok ? ofn.lpstrFile : NULL;
}
const char* sfd_save_dialog(sfd_Options *opt) {
int ok;
OPENFILENAME ofn;
last_error = NULL;
init_ofn(&ofn, opt);
ok = GetSaveFileName(&ofn);
return ok ? ofn.lpstrFile : NULL;
}
#endif
/******************************************************************************
** Zenity
*******************************************************************************/
#ifndef _WIN32
static const char* file_dialog(sfd_Options *opt, int save) {
static char result_buf[2048];
char buf[2048];
char *p;
const char *title;
FILE *fp;
int n, len;
last_error = NULL;
fp = popen("zenity --version", "r");
if (fp == NULL || pclose(fp) != 0) {
last_error = "could not open zenity";
return NULL;
}
n = sprintf(buf, "zenity --file-selection");
if (save) {
n += sprintf(buf + n, " --save --confirm-overwrite");
}
if (opt->title) {
title = opt->title;
} else {
title = save ? "Save File" : "Open File";
}
n += sprintf(buf + n, " --title=\"%s\"", title);
if (opt->path && opt->path[0] != '\0') {
n += sprintf(buf + n, " --filename=\"");
p = realpath(opt->path, buf + n);
if (p == NULL) {
last_error = "call to realpath() failed";
return NULL;
}
n += strlen(buf + n);
n += sprintf(buf + n, "/\"");
}
if (opt->filter) {
char b[64];
const char *p;
n += sprintf(buf + n, " --file-filter=\"");
if (opt->filter_name) {
n += sprintf(buf + n, "%s | ", opt->filter_name);
}
p = opt->filter;
while (next_filter(b, &p)) {
n += sprintf(buf + n, "\"%s\" ", b);
}
n += sprintf(buf + n, "\"");
}
n += sprintf(buf + n, " --file-filter=\"All Files | *\"");
fp = popen(buf, "r");
len = fread(result_buf, 1, sizeof(result_buf) - 1, fp);
pclose(fp);
if (len > 0) {
result_buf[len - 1] = '\0';
if (save && opt->extension && !strstr(result_buf, opt->extension)) {
sprintf(&result_buf[len - 1], ".%s", opt->extension);
}
return result_buf;
}
return NULL;
}
const char* sfd_open_dialog(sfd_Options *opt) {
return file_dialog(opt, 0);
}
const char* sfd_save_dialog(sfd_Options *opt) {
return file_dialog(opt, 1);
}
#endif

25
code/vendors/sfd.h vendored 100644
View File

@ -0,0 +1,25 @@
/**
* Copyright (c) 2017 rxi
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MIT license. See `sfd.c` for details.
*/
#ifndef SFD_H
#define SFD_H
#define SFD_VERSION "0.1.0"
typedef struct {
const char *title;
const char *path;
const char *filter_name;
const char *filter;
const char *extension;
} sfd_Options;
const char* sfd_get_error(void);
const char* sfd_open_dialog(sfd_Options *opt);
const char* sfd_save_dialog(sfd_Options *opt);
#endif