added basic prediction + pkt_dump_struct

isolation_bkp/dynres
Dominik Madarász 2021-05-09 12:27:10 +02:00
parent c5df24cefc
commit 7d1e5c1d61
15 changed files with 163 additions and 29 deletions

View File

@ -6,6 +6,7 @@ add_library(client-common STATIC
source/game.c
source/camera.c
source/world_view.c
source/prediction.c
source/utils/options.c

View File

@ -0,0 +1,5 @@
#pragma once
#include "entity_view.h"
float smooth_val(float cur, float tgt, float dt);
void predict_receive_update(entity_view *d, entity_view *data);

View File

@ -86,6 +86,10 @@ void game_world_view_set_active_by_idx(uint16_t idx) {
game_world_view_set_active(&world_viewers[idx]);
}
void game_world_view_active_entity_map(void (*map_proc)(uint64_t key, entity_view value)) {
entity_view_map(&active_viewer->entities, map_proc);
}
void game_world_view_set_active(world_view *view) {
active_viewer = view;
camera_set_follow(view->owner_id);
@ -146,12 +150,13 @@ void game_input() {
}
void game_update() {
if (is_viewer_only) network_client_tick();
if (is_viewer_only) {
network_client_tick();
}
else world_update();
}
void game_render() {
camera_update();
platform_render();
}

View File

@ -33,6 +33,7 @@ int main(int argc, char** argv)
zpl_opts_add(&opts, "bs", "block-size", "amount of units within a block (single axis)", ZPL_OPTS_INT);
zpl_opts_add(&opts, "cs", "chunk-size", "amount of blocks within a chunk (single axis)", ZPL_OPTS_INT);
zpl_opts_add(&opts, "ws", "world-size", "amount of chunks within a world (single axis)", ZPL_OPTS_INT);
zpl_opts_add(&opts, "n", "npc-count", "amount of demo npcs to spawn", ZPL_OPTS_INT);
uint32_t ok = zpl_opts_compile(&opts, argc, argv);
@ -49,6 +50,7 @@ int main(int argc, char** argv)
uint16_t block_size = zpl_opts_integer(&opts, "block-size", DEFAULT_BLOCK_SIZE);
uint16_t 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", 100);
if (zpl_opts_has_arg(&opts, "random-seed")) {
zpl_random rnd={0};
@ -72,7 +74,7 @@ int main(int argc, char** argv)
ECS_IMPORT(world_ecs(), Controllers);
ECS_IMPORT(world_ecs(), Physics);
uint16_t half_world_dim = world_dim() / 2;
for (int i = 0; i < 100; i++) {
for (int i = 0; i < npc_count; i++) {
uint64_t e = entity_spawn(NULL);
ecs_add(world_ecs(), e, EcsDemoNPC);
Position *pos = ecs_get_mut(world_ecs(), e, Position, NULL);

View File

@ -4,6 +4,7 @@
#include "network.h"
#include "game.h"
#include "entity_view.h"
#include "prediction.h"
#include "camera.h"
#include "math.h"
@ -51,7 +52,7 @@ void DrawRectangleEco(int posX, int posY, int width, int height, Color color)
void platform_init() {
InitWindow(screenWidth, screenHeight, "eco2d - client");
SetWindowState(FLAG_WINDOW_UNDECORATED|FLAG_WINDOW_HIGHDPI|FLAG_WINDOW_MAXIMIZED|FLAG_WINDOW_RESIZABLE);
SetWindowState(FLAG_WINDOW_UNDECORATED|FLAG_WINDOW_MAXIMIZED|FLAG_WINDOW_RESIZABLE);
SetTargetFPS(60);
screenWidth = GetScreenWidth();
@ -61,6 +62,17 @@ void platform_init() {
render_camera.offset = (Vector2){screenWidth/2.0f, screenHeight/2.0f};
render_camera.rotation = 0.0f;
render_camera.zoom = 4.0f/GFX_WORLD_SCALE;
// NOTE(zaklaus): Paint the screen before we load the game
// TODO(zaklaus): Render a cool loading screen background maybe? :wink: :wink:
BeginDrawing();
ClearBackground(GetColor(0x222034));
char const *loading_text = "zpl.eco2d is loading...";
int text_w = MeasureText(loading_text, 120);
DrawText(loading_text, GetScreenWidth()-text_w-15, GetScreenHeight()-135, 120, RAYWHITE);
EndDrawing();
}
void platform_shutdown() {
@ -122,15 +134,20 @@ void display_conn_status();
void DEBUG_draw_entities(uint64_t key, entity_view data);
void DEBUG_draw_ground(uint64_t key, entity_view data);
void lerp_entity_positions(uint64_t key, entity_view data);
void platform_render() {
game_world_view_active_entity_map(lerp_entity_positions);
camera_update();
camera game_camera = camera_get();
render_camera.target = (Vector2){game_camera.x * GFX_WORLD_SCALE, game_camera.y * GFX_WORLD_SCALE};
BeginDrawing();
ClearBackground(BLACK);
ClearBackground(GetColor(0x222034));
BeginMode2D(render_camera);
entity_view_map(&game_world_view_get_active()->entities, DEBUG_draw_ground);
entity_view_map(&game_world_view_get_active()->entities, DEBUG_draw_entities);
game_world_view_active_entity_map(DEBUG_draw_ground);
game_world_view_active_entity_map(DEBUG_draw_entities);
EndMode2D();
display_conn_status();
EndDrawing();
@ -164,6 +181,7 @@ void DEBUG_draw_ground(uint64_t key, entity_view data) {
double x = data.x * size + offset;
double y = data.y * size + offset;
DrawRectangleEco((int)x-offset, (int)y-offset, size+offset, size+offset, BLACK);
DrawRectangleEco((int)x, (int)y, size-offset, size-offset, LIME);
for (uint16_t i = 0; i < chunk_size*chunk_size; i++) {
@ -210,4 +228,15 @@ void DEBUG_draw_entities(uint64_t key, entity_view data) {
}break;
default:break;
}
}
void lerp_entity_positions(uint64_t key, entity_view data) {
world_view *view = game_world_view_get_active();
if (data.flag == EFLAG_INTERP) {
entity_view *e = entity_view_get(&view->entities, key);
e->x = smooth_val(e->x, e->tx, 0);
e->y = smooth_val(e->y, e->ty, 0);
}
}

View File

@ -0,0 +1,25 @@
#include "zpl.h"
#include "prediction.h"
#include "world/world.h"
#define PREDICT_SMOOTH_FACTOR_LO 0.8
#define PREDICT_SMOOTH_FACTOR_HI 0.12
float smooth_val(float cur, float tgt, float dt) {
#if 0
return zpl_lerp(cur, tgt, zpl_lerp(PREDICT_SMOOTH_FACTOR_HI, PREDICT_SMOOTH_FACTOR_LO, zpl_unlerp(dt, WORLD_TRACKER_UPDATE_FAST_MS, WORLD_TRACKER_UPDATE_SLOW_MS)));
#endif
return zpl_lerp(cur, tgt, PREDICT_SMOOTH_FACTOR_HI);
}
void predict_receive_update(entity_view *d, entity_view *data) {
if (d && data->flag & EFLAG_INTERP) {
// NOTE(zaklaus): store target pos but keep x,y unchanged
float tx = data->x;
float ty = data->y;
data->x = d->x;
data->y = d->y;
data->tx = tx;
data->ty = ty;
}
}

View File

@ -1,4 +1,6 @@
#include "world_view.h"
#include "entity_view.h"
#include "prediction.h"
#include "librg.h"
#include "zpl.h"
@ -19,6 +21,8 @@ int32_t tracker_read_update(librg_world *w, librg_event *e) {
world_view *view = (world_view*)librg_world_userdata_get(w);
entity_view data = entity_view_unpack_struct(buffer, actual_length);
entity_view *d = entity_view_get(&view->entities, entity_id);
predict_receive_update(d, &data);
entity_view_update_or_create(&view->entities, entity_id, data);
return 0;
}
@ -32,6 +36,10 @@ int32_t tracker_read_create(librg_world *w, librg_event *e) {
world_view *view = (world_view*)librg_world_userdata_get(w);
entity_view data = entity_view_unpack_struct(buffer, actual_length);
if (data.flag & EFLAG_INTERP) {
data.tx = data.x;
data.ty = data.y;
}
entity_view_update_or_create(&view->entities, entity_id, data);
return 0;
}

View File

@ -5,8 +5,11 @@ ZPL_TABLE_DEFINE(entity_view_tbl, entity_view_tbl_, entity_view);
pkt_desc pkt_entity_view_desc[] = {
{ PKT_UINT(entity_view, kind) },
{ PKT_REAL(entity_view, x) },
{ PKT_REAL(entity_view, y) },
{ PKT_UINT(entity_view, flag) },
{ PKT_HALF(entity_view, x) },
{ PKT_HALF(entity_view, y) },
{ PKT_HALF(entity_view, vx) },
{ PKT_HALF(entity_view, vy) },
{ PKT_END },
};
@ -23,6 +26,7 @@ entity_view entity_view_unpack_struct(void *data, size_t len) {
entity_view view = {0};
pkt_unpack_struct(&uc, pkt_entity_view_desc, PKT_STRUCT_PTR(&view));
return view;
}

View File

@ -7,16 +7,26 @@
#include "packet_utils.h"
typedef enum {
EKIND_PLAYER,
EKIND_PLAYER = 0,
EKIND_THING,
EKIND_CHUNK,
FORCE_EKIND_UINT16 = UINT16_MAX
} entity_kind;
typedef enum {
EFLAG_INTERP = (1 << 0),
FORCE_EFLAG_UINT16 = UINT16_MAX
} entity_flag;
typedef struct entity_view {
entity_kind kind;
entity_flag flag;
float x;
float y;
float vx;
float vy;
float tx;
float ty;
} entity_view;
ZPL_TABLE_DECLARE(, entity_view_tbl, entity_view_tbl_, entity_view);

View File

@ -18,6 +18,7 @@ world_view *game_world_view_get(uint16_t idx);
void game_world_view_set_active_by_idx(uint16_t idx);
void game_world_view_set_active(world_view *view);
void game_world_view_cycle_active(uint8_t dir);
void game_world_view_active_entity_map(void (*map_proc)(uint64_t key, entity_view value));
//~ NOTE(zaklaus): viewer -> host actions
void game_action_send_keystate(float x, float y, uint8_t use, uint8_t sprint);

View File

@ -135,3 +135,33 @@ int32_t pkt_pack_struct(cw_pack_context *pc, pkt_desc *desc, void *raw_blob, uin
return 0;
}
void pkt_dump_struct(pkt_desc *desc, void* raw_blob, uint32_t blob_size) {
uint8_t *blob = (uint8_t*)raw_blob;
zpl_printf("{\n");
for (pkt_desc *field = desc; field->type != CWP_NOT_AN_ITEM; ++field) {
zpl_printf(" \"%s\": ", field->name);
switch (field->type) {
case CWP_ITEM_BIN: {
// TODO(zaklaus): print memory dump as array of hex bytes
}break;
case CWP_ITEM_POSITIVE_INTEGER: {
zpl_printf("%u\n", *(uint64_t*)(blob + field->offset));
}break;
case CWP_ITEM_NEGATIVE_INTEGER: {
zpl_printf("%d\n", *(int64_t*)(blob + field->offset));
}break;
case CWP_ITEM_FLOAT: {
zpl_printf("%f\n", *(float*)(blob + field->offset));
}break;
case CWP_ITEM_DOUBLE: {
zpl_printf("%f\n", *(double*)(blob + field->offset));
}break;
default: {
zpl_printf("[WARN] unsupported pkt field type %lld !\n", field->type);
return; // unsupported field
}break;
}
}
zpl_printf("}\n");
}

View File

@ -74,27 +74,27 @@ static inline int32_t pkt_world_write(pkt_messages id, size_t pkt_size, int8_t i
#endif
#ifndef PKT_FIELD
#define PKT_FIELD(k, t, a) .type = k, .offset = PKT_OFFSETOF(t, a), .size = PKT_FIELD_SIZEOF(t,a), .it_size = PKT_FIELD_SIZEOF(t,a)
#define PKT_FIELD(k, t, a) .type = k, .offset = PKT_OFFSETOF(t, a), .size = PKT_FIELD_SIZEOF(t,a), .it_size = PKT_FIELD_SIZEOF(t,a), .name = #a
#endif
#ifndef PKT_UINT
#define PKT_UINT(t, a) .type = CWP_ITEM_POSITIVE_INTEGER, .offset = PKT_OFFSETOF(t, a), .size = PKT_FIELD_SIZEOF(t,a), .it_size = PKT_FIELD_SIZEOF(t,a)
#define PKT_UINT(t, a) .type = CWP_ITEM_POSITIVE_INTEGER, .offset = PKT_OFFSETOF(t, a), .size = PKT_FIELD_SIZEOF(t,a), .it_size = PKT_FIELD_SIZEOF(t,a), .name = #a
#endif
#ifndef PKT_SINT
#define PKT_SINT(t, a) .type = CWP_ITEM_NEGATIVE_INTEGER, .offset = PKT_OFFSETOF(t, a), .size = PKT_FIELD_SIZEOF(t,a), .it_size = PKT_FIELD_SIZEOF(t,a)
#define PKT_SINT(t, a) .type = CWP_ITEM_NEGATIVE_INTEGER, .offset = PKT_OFFSETOF(t, a), .size = PKT_FIELD_SIZEOF(t,a), .it_size = PKT_FIELD_SIZEOF(t,a), .name = #a
#endif
#ifndef PKT_REAL
#define PKT_REAL(t, a) .type = CWP_ITEM_DOUBLE, .offset = PKT_OFFSETOF(t, a), .size = PKT_FIELD_SIZEOF(t,a), .it_size = PKT_FIELD_SIZEOF(t,a)
#define PKT_REAL(t, a) .type = CWP_ITEM_DOUBLE, .offset = PKT_OFFSETOF(t, a), .size = PKT_FIELD_SIZEOF(t,a), .it_size = PKT_FIELD_SIZEOF(t,a), .name = #a
#endif
#ifndef PKT_HALF
#define PKT_HALF(t, a) .type = CWP_ITEM_FLOAT, .offset = PKT_OFFSETOF(t, a), .size = PKT_FIELD_SIZEOF(t,a), .it_size = PKT_FIELD_SIZEOF(t,a)
#define PKT_HALF(t, a) .type = CWP_ITEM_FLOAT, .offset = PKT_OFFSETOF(t, a), .size = PKT_FIELD_SIZEOF(t,a), .it_size = PKT_FIELD_SIZEOF(t,a), .name = #a
#endif
#ifndef PKT_ARRAY
#define PKT_ARRAY(t, a) .type = CWP_ITEM_BIN, .offset = PKT_OFFSETOF(t, a), .size = PKT_FIELD_SIZEOF(t,a), .it_size = PKT_FIELD_SIZEOF(t,a[0])
#define PKT_ARRAY(t, a) .type = CWP_ITEM_BIN, .offset = PKT_OFFSETOF(t, a), .size = PKT_FIELD_SIZEOF(t,a), .it_size = PKT_FIELD_SIZEOF(t,a[0]), .name = #a
#endif
#ifndef PKT_END
@ -106,6 +106,7 @@ static inline int32_t pkt_world_write(pkt_messages id, size_t pkt_size, int8_t i
#endif
typedef struct pkt_desc {
const char *name;
cwpack_item_types type;
size_t offset;
size_t size;
@ -127,3 +128,5 @@ static inline size_t pkt_pack_desc_args(pkt_desc *desc) {
for (pkt_desc *field = desc; field->type != CWP_NOT_AN_ITEM; ++field, ++cnt) {}
return cnt;
}
void pkt_dump_struct(pkt_desc *desc, void* raw_blob, uint32_t blob_size);

View File

@ -2,6 +2,7 @@
#include "librg.h"
#include "modules/general.h"
#include "modules/net.h"
#include "modules/physics.h"
#include "world/world.h"
#include "entity_view.h"
@ -24,14 +25,11 @@ typedef struct {
static world_data world = {0};
#define WORLD_TRACKER_UPDATE_FAST_MS 10
#define WORLD_TRACKER_UPDATE_NORMAL_MS 100
#define WORLD_TRACKER_UPDATE_SLOW_MS 800
int32_t world_gen();
entity_view world_build_entity_view(int64_t e) {
ECS_IMPORT(world_ecs(), General);
ECS_IMPORT(world_ecs(), Physics);
ECS_IMPORT(world_ecs(), Net);
entity_view view = {0};
@ -41,7 +39,13 @@ entity_view world_build_entity_view(int64_t e) {
view.kind = ecs_has(world_ecs(), e, EcsClient) ? EKIND_PLAYER : EKIND_THING;
view.x = pos->x;
view.y = pos->y;
return view;
}
const Velocity *vel = ecs_get(world_ecs(), e, Velocity);
if (vel) {
view.flag |= EFLAG_INTERP;
view.vx = vel->x;
view.vy = vel->y;
}
const Chunk *chpos = ecs_get(world_ecs(), e, Chunk);
@ -49,7 +53,6 @@ entity_view world_build_entity_view(int64_t e) {
view.kind = EKIND_CHUNK;
view.x = chpos->x;
view.y = chpos->y;
return view;
}
return view;
@ -91,7 +94,7 @@ int32_t world_init(int32_t seed, uint16_t block_size, uint16_t chunk_size, uint1
world.chunk_amount = chunk_amount;
world.block_size = block_size;
world.dim = (world.chunk_size * world.chunk_amount);;
world.dim = (world.chunk_size * world.chunk_amount);
world.size = world.dim * world.dim;
if (world.tracker == NULL) {
@ -104,8 +107,8 @@ int32_t world_init(int32_t seed, uint16_t block_size, uint16_t chunk_size, uint1
}
/* config our world grid */
librg_config_chunksize_set(world.tracker, block_size * chunk_size, block_size * chunk_size, 0);
librg_config_chunkamount_set(world.tracker, chunk_amount, chunk_amount, 0);
librg_config_chunksize_set(world.tracker, block_size * world.chunk_size, block_size * world.chunk_size, 0);
librg_config_chunkamount_set(world.tracker, world.chunk_amount, world.chunk_amount, 0);
librg_config_chunkoffset_set(world.tracker, LIBRG_OFFSET_BEG, LIBRG_OFFSET_BEG, 0);
librg_event_set(world.tracker, LIBRG_WRITE_CREATE, tracker_write_create);
@ -120,10 +123,12 @@ int32_t world_init(int32_t seed, uint16_t block_size, uint16_t chunk_size, uint1
world.ecs = ecs_init();
ecs_set_entity_range(world.ecs, 0, UINT32_MAX);
int32_t world_build_status = world_gen();
ZPL_ASSERT(world_build_status >= 0);
ECS_IMPORT(world.ecs, General);
for (int i = 0; i < chunk_amount * chunk_amount; ++i) {
for (int i = 0; i < world.chunk_amount * world.chunk_amount; ++i) {
ecs_entity_t e = ecs_new(world.ecs, 0);
Chunk *chunk = ecs_get_mut(world.ecs, e, Chunk, NULL);
librg_entity_track(world.tracker, e);
@ -133,7 +138,7 @@ int32_t world_init(int32_t seed, uint16_t block_size, uint16_t chunk_size, uint1
zpl_printf("[INFO] Created a new server world\n");
return world_gen();
return world_build_status;
}
int32_t world_destroy(void) {
@ -181,8 +186,8 @@ static void world_tracker_update(uint8_t ticker, uint8_t freq, uint8_t radius) {
int32_t world_update() {
ecs_progress(world.ecs, 0);
world_tracker_update(0, WORLD_TRACKER_UPDATE_FAST_MS, 2);
//world_tracker_update(1, WORLD_TRACKER_UPDATE_NORMAL_MS, 4);
//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);
return 0;
}

View File

@ -11,6 +11,10 @@
#define WORLD_ERROR_INVALID_BUFFER -0x0004
#define WORLD_ERROR_TRACKER_FAILED -0x0005
#define WORLD_TRACKER_UPDATE_FAST_MS 10
#define WORLD_TRACKER_UPDATE_NORMAL_MS 100
#define WORLD_TRACKER_UPDATE_SLOW_MS 800
#define WORLD_PKT_READER(name) int32_t name(void* data, uint32_t datalen, void *udata)
typedef WORLD_PKT_READER(world_pkt_reader_proc);

View File

@ -1,6 +1,7 @@
#pragma once
#include "flecs/flecs.h"
#include "flecs/flecs_meta.h"
#include "world/world.h"
ECS_STRUCT(Vector2D, {
float x;
@ -10,6 +11,7 @@ ECS_STRUCT(Vector2D, {
ECS_STRUCT(Chunk, {
int16_t x;
int16_t y;
ecs_vector(uint8_t) blocks;
});
ECS_STRUCT(Drawable, {