F4MP/f4mp_originalcode/thirdparty/librg/include/librg.h
Jous99 37b16f1547 code upload
codigo original de f4mp y tilted para referencias
2026-01-06 18:45:00 +01:00

3383 lines
122 KiB
C

/**
* librg - a library for building simple and elegant cross-platform multiplayer client-server solutions.
*
* Usage:
* #define LIBRG_IMPLEMENTATION exactly in ONE source file right BEFORE including the library, like:
*
* #define LIBRG_IMPLEMENTATION
* #include <librg.h>
*
* Credits:
* Vladyslav Hrytsenko (GitHub: inlife)
* Dominik Madarasz (GitHub: zaklaus)
*
* Dependencies:
* zpl.h
* enet.h
*
* For the demo:
* sdl2.h
*
* Version History:
*
* 5.0.6
* - Fix forced_inline on librg__space_insert
*
* 5.0.5
* - Fixes to selection and deduplication flow
*
* 5.0.3
* - Minor fixes by @markatk
*
* 5.0.2
* - Fixed issue related to visiblity destruction
*
* 5.0.1
* - Fixed entity visiblity states after disconnection
*
* 5.0.0
* - Changed API for visibility feature:
* - Instead of booleans setting whether or not entity would be included in the query or not
* a multiple constant-based states were inroduced:
* - LIBRG_VISIBILITY_DEFAULT - the original state of visibility, entity is only visible if it is in the stream range
* - LIBRG_ALWAYS_VISIBLE - the entity is visible disregarding if it is the stream range or not
* - LIBRG_ALWAYS_INVISIBLE - opposite of the above, entity will be always invisible
* Entity visibility can be set on per entity-to-entity or global levels. The entity-to-entity relation has a bigger
* priority, so f.e. setting entity to be LIBRG_ALWAYS_VISIBLE on relation level, will override global visibility.
* - Aditionally, if the virtual world feature is enabled, it gains main priotity over visibility, thus entities located in 2 different
* virtual worlds will not be able to see each other in spite of visibility settings.
*
* 4.1.5
* - Fix to data send
*
* 4.1.4
* - Fixed issue with async flow inconsitensies of control_generations
* - Fixed boolean validation in disconnection flow
* - Added proxying of user_data from msg to evt in disconnect event
*
* 4.1.1
* - Added compile-time 'features':
* - Ability to enable/disable some librg compile-time features
* - Entity igore tables are now optional, and can be disabled
* - Implmented simple optional Virtual world feature for entities
* - Implemented a feature to enable/disable octree culler (falls back to linear check)
* - Multiple features can be combined
* - Added 'generation' to entity control lists:
* Setting, removing and setting control to the same entity again with same owner
* will now distinct between old and new controllers, and messages still coming
* from old control generation will be rejected in favor of new ones.
* - Added guard to minimum sized packet in receive for both sides
* - Added spherical culler handler, and ability to do runtime switch (LIBRG_USE_RADIUS_CULLING)
* - Added return codes to some functions @markatk
* - Streamed entities are now going to be always returned in query for controlling peer
* - Fixed issue with host setting on the server side
* - Fixed nullptr crash on empty host string for client on connect
* - Removed experimental multithreading code
*
* 4.1.0
* - Added new, extended message methods and sending options
*
* 4.0.0
* - Coding style changes and bug fixes
*
* 3.3.1
* - Updated zpl dependencies
* - Removed zpl_math dependency (replaced by internal module in zpl)
*
* 3.3.0
* - Added ipv6 support
* - Added safe bitstream reads for internal methods
* - Updated enet to latest version (2.0.1, ipv6 support)
* - Updated zpl to latest version
*
* 3.2.0
* - Fixed minor memory client-side memory leak with empty control list
* - Fixed issue with client stream update and removed entity on server
* - Updated zpl to new major version, watch out for possible incompatibilities
* - Added method for alloc/dealloc the librg_ctx, librg_data, librg_event for the bindings
* - Added experimental support for update buffering, disabled by default, and not recommended to use
* - Added built-in timesyncer, working on top of monotonic time, syncing client clock to server one
* - Added helper methods: librg_time_now, librg_standard_deviation
* - Changed ctx->tick_delay from u16 to f64 (slightly more precision)
*
* Version History:
* 3.1.0
* - Removed zpl_cull and zpl_event dependencies
* - added librg_network_kick()
* - saving current librg_address to ctx->network
* - refactor to proper disconnection code
* - exclude local client entity from LIBRG_CONNECTION_DISCONNECT
* - moved options and some few other things to the implementation part
* - fixed issue with replacing entity control
* - fixed issue with add control queuing beign sent before create entity packet
*
* 3.0.7 - Fix for entity query dublication for player entities
* 3.0.5 - Patched librg_callback_cb arg value
* 3.0.4 - Fixed Android and iOS support
* 3.0.3 - Small fixes
* 3.0.2 - Dependency updates
* 3.0.1 - minor api patch
* 3.0.0 - contexts, major api changes, fried potato, other stuff
*
* 2.2.3 - fixed mem leak on net event
* 2.2.2 - Fixed client issue with librg_message_send_instream_except
* 2.2.1 - Fixed cpp issues with librg_data pointers
* 2.2.0 - Inner message system rafactor
* 2.1.0 - Inner bitstream refactors, with slight interface changes
* 2.0.2 - C++ and MSVC related fixes
* 2.0.0 - Initial C version rewrite
*
* Things TODO:
* find a nice way to decrease size on client for peer struct
* try to remember stuff about bistram safety
* fix disconnection code issues
* - Add method to check if entity is in stream of other entity
* - Add DEBUG packet size validation (FEATURE)
* - refactoring librg_table (FEATURE)
* - remove entity ignore for target entity that was disconnected/deleted (BUG)
* - remove controller peer for entity, on owner disconnect (BUG)
* - possibly adding stream_range to the query, to make it bi-sided (FEATURE)
* - tree/space node flattening (FEATURE)
*
* Useful links:
* - https://antriel.com/post/online-platformer-5/#server-update-data
* - https://github.com/fnuecke/Space/blob/5ddfec5fc8139c4b0def8fdfc16b5f5357a8cd38/EngineController/AbstractTssClient.cs#L325
*
* License notice:
*
* Copyright 2017-2019 Vladyslav Hrytsenko
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef LIBRG_INCLUDE_H
#define LIBRG_INCLUDE_H
#define LIBRG_VERSION_MAJOR 5
#define LIBRG_VERSION_MINOR 0
#define LIBRG_VERSION_PATCH 5
#define LIBRG_VERSION_CREATE(major, minor, patch) (((major)<<16) | ((minor)<<8) | (patch))
#define LIBRG_VERSION_GET_MAJOR(version) (((version)>>16)&0xFF)
#define LIBRG_VERSION_GET_MINOR(version) (((version)>>8)&0xFF)
#define LIBRG_VERSION_GET_PATCH(version) ((version)&0xFF)
#define LIBRG_VERSION LIBRG_VERSION_CREATE(LIBRG_VERSION_MAJOR, LIBRG_VERSION_MINOR, LIBRG_VERSION_PATCH)
/* include definitions */
#ifndef LIBRG_CUSTOM_INCLUDES
#ifdef LIBRG_IMPLEMENTATION
#define ZPL_IMPLEMENTATION
#define ENET_IMPLEMENTATION
#endif
#include "zpl.h"
//#ifdef ZPL_SYSTEM_WINDOWS
// #define _WINSOCK_DEPRECATED_NO_WARNINGS
//#endif
#include "enet.h"
#endif
/* library mode (stastic or dynamic) */
#ifdef LIBRG_SHARED
#if defined(_WIN32)
#define LIBRG_API ZPL_EXTERN __declspec(dllexport)
#else
#define LIBRG_API ZPL_EXTERN __attribute__((visibility("default")))
#endif
#else
#ifndef LIBRG_API
#define LIBRG_API ZPL_DEF
#endif
#endif
#ifndef LIBRG_INTERNAL
#define LIBRG_INTERNAL zpl_internal
#endif
#define librg_global zpl_global
#define librg_inline ZPL_INLINE //zpl_inline
#define librg_internal zpl_internal
#define librg_assert ZPL_ASSERT
#define librg_assert_msg ZPL_ASSERT_MSG
#define librg_lambda(name) name
/* default constants/methods used */
#ifndef LIBRG_DATA_GROW_FORMULA
#define LIBRG_DATA_GROW_FORMULA(x) (2*(x) + 16)
#endif
#if !defined(librg_log)
#define librg_log zpl_printf
#endif
#if defined(LIBRG_DEBUG)
#define librg_dbg(fmt, ...) librg_log(fmt, ##__VA_ARGS__)
#else
#define librg_dbg(fmt, ...)
#endif
/* keeping enabled by default depecrecated */
/* naming support till next major release */
#ifndef LIBRG_NO_DEPRECATIONS
#define librg_ctx_t librg_ctx
#define librg_data_t librg_data
#define librg_entity_t librg_entity
#define librg_address_t librg_address
#define librg_message_t librg_message
#define librg_event_t librg_event
#define librg_peer_t librg_peer
#define librg_host_t librg_host
#define librg_packet_t librg_packet
#endif
#ifdef __cplusplus
extern "C" {
#endif
// =======================================================================//
// !
// ! Library compile-time configure features
// !
// =======================================================================//
#define LIBRG_FEATURE_VIRTUAL_WORLDS 1 // enabled by default (define LIBRG_DISABLE_FEATURE_VIRTUAL_WORLDS before including to disable)
#define LIBRG_FEATURE_ENTITY_VISIBILITY 1 // enabled by default (define LIBRG_DISABLE_FEATURE_ENTITY_VISIBILITY before including to disable)
#define LIBRG_FEATURE_OCTREE_CULLER 1 // disabled by default (define LIBRG_DISABLE_FEATURE_OCTREE_CULLER before including to disable)
#if defined(LIBRG_FEATURE_VIRTUAL_WORLDS) && defined(LIBRG_DISABLE_FEATURE_VIRTUAL_WORLDS)
#undef LIBRG_FEATURE_VIRTUAL_WORLDS
#endif
#if defined(LIBRG_FEATURE_ENTITY_VISIBILITY) && defined(LIBRG_DISABLE_FEATURE_ENTITY_VISIBILITY)
#undef LIBRG_FEATURE_ENTITY_VISIBILITY
#endif
#if defined(LIBRG_FEATURE_OCTREE_CULLER) && defined(LIBRG_DISABLE_FEATURE_OCTREE_CULLER)
#undef LIBRG_FEATURE_OCTREE_CULLER
#endif
// =======================================================================//
// !
// ! Public API
// !
// =======================================================================//
struct librg_ctx;
struct librg_data;
struct librg_entity;
struct librg_address;
struct librg_message;
struct librg_event;
typedef ENetPeer librg_peer;
typedef ENetHost librg_host;
typedef ENetPacket librg_packet;
enum librg_mode { LIBRG_MODE_SERVER, LIBRG_MODE_CLIENT };
enum librg_space_type { LIBRG_SPACE_2D = 2, LIBRG_SPACE_3D = 3 };
enum librg_pointer_type { LIBRG_POINTER_CTX, LIBRG_POINTER_DATA, LIBRG_POINTER_EVENT };
enum librg_thread_state { LIBRG_THREAD_IDLE, LIBRG_THREAD_WORK, LIBRG_THREAD_EXIT };
/**
* Simple host address
* used to configure network on start
*/
typedef struct librg_address { i32 port; char *host; } librg_address;
typedef void (librg_entity_cb)(struct librg_ctx *ctx, struct librg_entity *entity);
typedef void (librg_message_cb)(struct librg_message *msg);
typedef void (librg_event_cb)(struct librg_event *event);
typedef librg_event_cb **librg_event_block; ///< zpl_array
ZPL_TABLE_DECLARE(extern, librg_table, librg_table_, u32);
ZPL_TABLE_DECLARE(static, librg_event_pool, librg_event_pool_, librg_event_block);
/**
* Allocate librg ctx
* (to be used inside bindings)
*/
LIBRG_API void *librg_allocate_ptr(enum librg_pointer_type pointer_type);
/**
* Frees a pointer allocated by library
* usually used in bindings.
*/
LIBRG_API void librg_release_ptr(void *ptr);
/**
* Main initialization method
* MUST BE called in the begging of your application
*/
LIBRG_API u32 librg_init(struct librg_ctx *ctx);
/**
* Should be called at the end of
* execution of the program
*/
LIBRG_API void librg_free(struct librg_ctx *ctx);
/**
* Main tick method
* MUST BE called in your loop
* preferably w/o delays
*
* Calling tick ensures that packets will be sent and received,
* world state will be recalculated and new update data will be created/used
*/
LIBRG_API void librg_tick(struct librg_ctx *ctx);
// =======================================================================//
// !
// ! Entities
// !
// =======================================================================//
#define librg_entity_id u32
enum librg_entity_flag {
LIBRG_ENTITY_NONE = 0, /* general flag, all destroyed/non-created entities have it */
LIBRG_ENTITY_ALIVE = (1 << 0), /* general flag, all created entities have it */
LIBRG_ENTITY_CLIENT = (1 << 1), /* flag describing entities created for client peer */
LIBRG_ENTITY_VISIBILITY = (1 << 2), /* flag showing that entity has visibility overrides */
LIBRG_ENTITY_QUERIED = (1 << 3), /* flag showing that entity has a cached culler query */
LIBRG_ENTITY_CONTROLLED = (1 << 4), /* flag showing if the entity is controlled(streamed) by some peer */
LIBRG_ENTITY_CONTROL_REQUESTED = (1 << 5), /* flag showing whether or not an entity control has been requested, but not yet sent */
LIBRG_ENTITY_MARKED_REMOVAL = (1 << 6), /* flag showing whether or not an entity is marked to be removed */
LIBRG_ENTITY_UNUSED = (1 << 7), /* flag showing whether the entity's space is unused */
LIBRG_ENTITY_FLAG_LAST = (1 << 8),
};
typedef struct librg_entity {
u32 id;
u32 type;
u64 flags;
zpl_vec3 position;
f32 stream_range;
void *user_data;
struct librg_space *stream_branch;
#ifdef LIBRG_FEATURE_ENTITY_VISIBILITY
librg_table visibility;
#endif
#ifdef LIBRG_FEATURE_VIRTUAL_WORLDS
u32 virual_world;
#endif
librg_table last_snapshot;
librg_peer *client_peer;
librg_peer *control_peer;
u8 control_generation;
librg_entity_id *last_query; ///< zpl_array
} librg_entity;
/**
* Create entity and return handle
*/
LIBRG_API struct librg_entity *librg_entity_create(struct librg_ctx *ctx, u32 type);
/**
* Try to find an existing entity by a provided id, or NULL
*/
LIBRG_API struct librg_entity *librg_entity_fetch(struct librg_ctx *ctx, librg_entity_id entity_id);
/**
* Reuqest an entity destruction (will be marked as destroyed, and send to be destroyed on clients on the next tick)
*/
LIBRG_API void librg_entity_destroy(struct librg_ctx *ctx, librg_entity_id entity_id);
/**
* Query for entities that are in stream zone
* for current entity, and are visible to this entity
* an uninitialized pointer to the pointer of librg_entity_id should be provided:
*
* EXAMPLE:
* librg_entity_id *result;
* usize amount = librg_entity_query(&ctx, 15, &result);
*/
LIBRG_API usize librg_entity_query(struct librg_ctx *ctx, librg_entity_id entity_id, librg_entity_id **result);
/**
* Check if provided entity is a valid entity
*/
LIBRG_API b32 librg_entity_valid(struct librg_ctx *ctx, librg_entity_id id);
/**
* Try to find an entity that is assossiated with a particular peer, or NULL
*/
LIBRG_API struct librg_entity *librg_entity_find(struct librg_ctx *ctx, librg_peer *peer);
/**
* Concept of entity control describes an entity behavior, data of which is constantly sent to the server from a particular peer (controller).
* And, updates about that entity are later sent to other peers, except the original controller.
*
* New data will arrive, accordingly to configured client tick delay, and the LIBRG_CLIENT_STREAMER_UPDATE event will be triggered.
* librg_entity_control_set is an async method, it will send a message to the client, notifiyng him of becoming a controller.
* librg_entity_control_remove is a hybrid sync+async method. Behaves similar wat to _set, described above, however,
* even thought client will still be sending control messages for some time, server will reject them if method has been called.
*
* For occasions, where some sort of an authoritative pause is needed for the server, in the controlled data stream,
* for example you want to change some property data of the entity, authoritatively from the server, like position.
* Calling librg_entity_control_remove(ctx, id), making the neccessary changes, and calling librg_entity_control_set(ctx, id, peer)
* will make sure all current streamed data from the client will be ignored, and new data will be force-delivered to client
*/
LIBRG_API librg_peer *librg_entity_control_get(struct librg_ctx *ctx, librg_entity_id entity_id);
LIBRG_API void librg_entity_control_set(struct librg_ctx *ctx, librg_entity_id entity_id, librg_peer *peer);
LIBRG_API void librg_entity_control_remove(struct librg_ctx *ctx, librg_entity_id entity_id);
LIBRG_API void librg_entity_control_ignore_next_update(struct librg_ctx *ctx, librg_entity_id entity_id);
/**
* Iterate over all the entities with a flag
*/
LIBRG_API void librg_entity_iterate(struct librg_ctx *ctx, u64 flags, librg_entity_cb callback);
/**
* C based entity iteration macro
*/
#define librg_entity_iteratex(ctx, cflags, cname, code) do { \
for (u32 _ent = 0, _valid = 0; _ent < ctx->max_entities && _valid < ctx->entity.count; ++_ent) { \
if ((ctx->entity.list[_ent].flags & (LIBRG_ENTITY_ALIVE | cflags)) == (LIBRG_ENTITY_ALIVE | cflags)) { \
_valid++; librg_entity_id cname = _ent; code; \
} \
} \
} while (0);
/**
* Return entity type
*/
LIBRG_API u32 librg_entity_type(struct librg_ctx *ctx, librg_entity_id entity_id);
#ifdef LIBRG_FEATURE_ENTITY_VISIBILITY
typedef enum {
LIBRG_DEFAULT_VISIBILITY,
LIBRG_ALWAYS_VISIBLE,
LIBRG_ALWAYS_INVISIBLE,
} librg_visibility;
/**
* A complex api allowing to change visibility of the entities via specific set of rules.
*
* Calling librg_entity_visibility_set, will change global visibility of partiulcar entity for others.
* Meaning, the entity is still gonna be able to see other entities, while others won't be able to see it back.
*
* Calling librg_entity_visibility_set_for, has a similar effect to method mentioned above,
* however extends also to specify entity inivisibilty for other specific entity.
*/
LIBRG_API void librg_entity_visibility_set(struct librg_ctx *ctx, librg_entity_id entity_id, librg_visibility state);
LIBRG_API librg_visibility librg_entity_visibility_get(struct librg_ctx *ctx, librg_entity_id entity_id);
LIBRG_API void librg_entity_visibility_set_for(struct librg_ctx *ctx, librg_entity_id entity_id, librg_entity_id target, librg_visibility state);
LIBRG_API librg_visibility librg_entity_visibility_get_for(struct librg_ctx *ctx, librg_entity_id entity_id, librg_entity_id target);
#endif
#ifdef LIBRG_FEATURE_VIRTUAL_WORLDS
/**
* A simple thin api allowing to change virtual world for specific entity
* "Virtual world" is a concept where entities can see other entities near
* each other only when they share the virtual world.
*
* If entity is being removed from the virtual world, it's, LIBRG_ENTITY_REMOVED
* will be called for all the clients that were sharing same world with target entity.
*/
LIBRG_API u32 librg_entity_world_get(struct librg_ctx *ctx, librg_entity_id entity_id);
LIBRG_API void librg_entity_world_set(struct librg_ctx *ctx, librg_entity_id entity_id, u32 world);
#endif
// =======================================================================//
// !
// ! Events
// !
// =======================================================================//
/**
* Default built-in events
* define your events likes this:
* enum {
* MY_NEW_EVENT_1 = LIBRG_LAST_EVENT,
* MY_NEW_EVENT_2,
* MY_NEW_EVENT_3,
* };
*/
enum librg_events {
LIBRG_CONNECTION_INIT,
LIBRG_CONNECTION_REQUEST,
LIBRG_CONNECTION_REFUSE,
LIBRG_CONNECTION_ACCEPT,
LIBRG_CONNECTION_DISCONNECT,
LIBRG_CONNECTION_TIMEOUT,
LIBRG_CONNECTION_TIMESYNC,
LIBRG_ENTITY_CREATE,
LIBRG_ENTITY_UPDATE,
LIBRG_ENTITY_REMOVE,
LIBRG_CLIENT_STREAMER_ADD,
LIBRG_CLIENT_STREAMER_REMOVE,
LIBRG_CLIENT_STREAMER_UPDATE,
LIBRG_EVENT_LAST,
};
enum librg_event_flags {
LIBRG_EVENT_NONE = 0, /* default empty user-created event */
LIBRG_EVENT_REJECTED = (1 << 0), /* whether or not this event was rejected */
LIBRG_EVENT_REJECTABLE = (1 << 1), /* can this event be rejected by user */
LIBRG_EVENT_REMOTE = (1 << 2), /* event was based on network message */
LIBRG_EVENT_LOCAL = (1 << 3), /* event was created locally */
};
typedef struct librg_event {
u32 id; /* librg event id, that is being triggered */
struct librg_ctx *ctx; /* librg context where event has been called */
struct librg_data *data; /* optional: data is used for built-in events */
struct librg_entity *entity; /* optional: entity is used for built-in events */
librg_peer *peer; /* optional: peer is used for built-in events */
u64 flags; /* flags for that event */
void *user_data; /* optional: user information */
} librg_event;
/**
* Used to attach event handler
* You can bind as many event handlers onto
* single event, as you want
*
* In the callback you will need to cast event
* to type of structure that you've triggered this event with
*
* @param id usually you define event ids inside enum
* @param callback
* @return index of added event, can be used to remove particular event handler
*/
LIBRG_API u64 librg_event_add(struct librg_ctx *ctx, u64 id, librg_event_cb callback);
/**
* Used to trigger execution of all attached
* event handlers for particlar event
*
* You can provide pointer to any data, which will be
* passed inside the event callback
*
* @param id usually you define event ids inside enum
* @param event pointer onto data or NULL
*/
LIBRG_API void librg_event_trigger(struct librg_ctx *ctx, u64 id, struct librg_event *event);
/**
* Used to remove particular callback from
* event chain, so it wont be called ever again
*
* @param id usually you define event ids inside enum
* @param index returned by librg_event_add
*/
LIBRG_API void librg_event_remove(struct librg_ctx *ctx, u64 id, u64 index);
/**
* Used to reject some event from triggering from
* inside of executing callback
*/
LIBRG_API void librg_event_reject(struct librg_event *event);
/**
* Used to check if some event can be rejected
*/
LIBRG_API b32 librg_event_rejectable(struct librg_event *event);
/**
* Checks if current event was not rejected
* inside any of the callbacks
*/
LIBRG_API b32 librg_event_succeeded(struct librg_event *event);
// =======================================================================//
// !
// ! Binary data (bitstream)
// !
// =======================================================================//
typedef struct librg_data {
usize capacity;
usize read_pos;
usize write_pos;
void *rawptr;
zpl_allocator allocator;
} librg_data;
/**
* Initialize new bitstream with default mem size
*/
LIBRG_API void librg_data_init(struct librg_data *data);
/**
* Initialize new bitstream with custom mem size
*/
LIBRG_API void librg_data_init_size(struct librg_data *data, usize size);
/**
* Initialize new bitstream with custom mem size (bindings stuff)
*/
LIBRG_API struct librg_data *librg_data_init_new();
/**
* Free initialized bitstream
*/
LIBRG_API void librg_data_free(struct librg_data *data);
/**
* Reset initialized bitstream
* NOTE: doesnt remove any data, just resets read and write pos to 0
*/
LIBRG_API void librg_data_reset(struct librg_data *data);
/**
* Increase size of bitstream
*/
LIBRG_API void librg_data_grow(struct librg_data *data, usize min_size);
/**
* Methods for getting various parameters of bitstream
*/
LIBRG_API usize librg_data_capacity(struct librg_data *data);
LIBRG_API usize librg_data_get_rpos(struct librg_data *data);
LIBRG_API usize librg_data_get_wpos(struct librg_data *data);
LIBRG_API void librg_data_set_rpos(struct librg_data *data, usize position);
LIBRG_API void librg_data_set_wpos(struct librg_data *data, usize position);
/**
* Read and write methods for custom sized data
*/
LIBRG_API b32 librg_data_rptr(struct librg_data *data, void *ptr, usize size);
LIBRG_API void librg_data_wptr(struct librg_data *data, void *ptr, usize size);
/**
* Read and write methods for custom sized data
* at particular position in memory
*/
LIBRG_API b32 librg_data_rptr_at(struct librg_data *data, void *ptr, usize size, isize position);
LIBRG_API void librg_data_wptr_at(struct librg_data *data, void *ptr, usize size, isize position);
/**
* A helprer macro for onliner methods
*/
#define LIBRG_GEN_DATA_READWRITE(TYPE) \
LIBRG_API TYPE ZPL_JOIN2(librg_data_r,TYPE)(struct librg_data *data); \
LIBRG_API void ZPL_JOIN2(librg_data_w,TYPE)(struct librg_data *data, TYPE value); \
LIBRG_API TYPE ZPL_JOIN3(librg_data_r,TYPE,_at)(struct librg_data *data, isize position); \
LIBRG_API void ZPL_JOIN3(librg_data_w,TYPE,_at)(struct librg_data *data, TYPE value, isize position);
/**
* General one-line methods for reading/writing different types
*/
LIBRG_GEN_DATA_READWRITE( i8);
LIBRG_GEN_DATA_READWRITE( u8);
LIBRG_GEN_DATA_READWRITE(i16);
LIBRG_GEN_DATA_READWRITE(u16);
LIBRG_GEN_DATA_READWRITE(i32);
LIBRG_GEN_DATA_READWRITE(u32);
LIBRG_GEN_DATA_READWRITE(i64);
LIBRG_GEN_DATA_READWRITE(u64);
LIBRG_GEN_DATA_READWRITE(f32);
LIBRG_GEN_DATA_READWRITE(f64);
LIBRG_GEN_DATA_READWRITE( b8);
LIBRG_GEN_DATA_READWRITE(b16);
LIBRG_GEN_DATA_READWRITE(b32);
#undef LIBRG_GEN_DATA_READWRITE
/**
* Read/write methods for entity (aliases for u32)
*/
#define librg_data_went ZPL_JOIN2(librg_data_w, librg_entity_id)
#define librg_data_rent ZPL_JOIN2(librg_data_r, librg_entity_id)
// =======================================================================//
// !
// ! Network
// !
// =======================================================================//
#define librg_message_id u16
#define librg_data_wmid ZPL_JOIN2(librg_data_w, librg_message_id)
#define librg_data_rmid ZPL_JOIN2(librg_data_r, librg_message_id)
/**
* Message structure
* created inside network handler
* and injected to each incoming message
*/
typedef struct librg_message {
u32 id;
struct librg_ctx *ctx;
struct librg_data *data;
librg_peer *peer;
librg_packet *packet;
void *user_data; /* optional: user information */
} librg_message;
/**
* Check are we connected
*/
LIBRG_API b32 librg_is_connected(struct librg_ctx *ctx);
/**
* Is librg instance is running
* in the server mode
*/
LIBRG_API b32 librg_is_server(struct librg_ctx *ctx);
/**
* Is librg instance is running
* in the client mode
*/
LIBRG_API b32 librg_is_client(struct librg_ctx *ctx);
/**
* Starts network connection
* Requires you to provide .port (if running as server)
* or both .port and .host (if running as client)
*
* For server mode - starts server
* For client mode - starts client, and connects to provided host & port
*/
LIBRG_API u32 librg_network_start(struct librg_ctx *ctx, librg_address address);
/**
* Disconnects (if connected), stops network
* and releases resources
*/
LIBRG_API void librg_network_stop(struct librg_ctx *ctx);
/**
* Forces disconnection for provided peer
* @param ctx
* @param peer
*/
LIBRG_API void librg_network_kick(struct librg_ctx *ctx, librg_peer *peer);
/**
* Can be used to add handler
* to a particular message id
*/
LIBRG_API void librg_network_add(struct librg_ctx *ctx, librg_message_id id, librg_message_cb callback);
/**
* Can be used to remove a handler
* from particular message id
*/
LIBRG_API void librg_network_remove(struct librg_ctx *ctx, librg_message_id id);
/**
* General reliable message/data sending API
* (original message api version since v2.0.0, might be subject for changes in the future)
*
* Message id - is an identifier which librg will use to route the message from
* (to particlar handlers which are added via librg_network_add)
*
* method _all - sends the data to all connected peers
* method _to - sends the data to particular peer
* method _except - sends the data to all but particular peer
* method _instream - sends the data to all connected peers in the particular range (based off entity's stream_range)
* method _instream_except - similar to method above, however excludes particular peer
*
* The data which is provided inside the method will be copied inside packet struct, so it can be safely freed after you've called a method.
*/
LIBRG_API void librg_message_send_all (struct librg_ctx *ctx, librg_message_id id, void *data, usize size);
LIBRG_API void librg_message_send_to (struct librg_ctx *ctx, librg_message_id id, librg_peer *target, void *data, usize size);
LIBRG_API void librg_message_send_except (struct librg_ctx *ctx, librg_message_id id, librg_peer *target, void *data, usize size);
LIBRG_API void librg_message_send_instream (struct librg_ctx *ctx, librg_message_id id, librg_entity_id entity_id, void *data, usize size);
LIBRG_API void librg_message_send_instream_except (struct librg_ctx *ctx, librg_message_id id, librg_entity_id entity_id, librg_peer *target, void *data, usize size);
/**
* The basic message/data sending API
* Supports sending both reliable and unreliable messages, and ability to set which channel to use for message sending.
*
* All previous message sending methods are using these extended variants internally.
*
* method sendex:
* if target is NULL - will send the data to all connected peers, else - to a particular peer
* if except is NULL - argument does nothing, else - prevents data from being sent to a particlar peer
* channel - provides which network channel is supposed to be used for message sending
* (make that channel id is always < librg_option_get(LIBRG_NETWORK_CHANNELS), or use librg_option_set to change the default value)
* if reliable is 1 (true) - message will be sent using reliable methods, else using unreliable
*/
LIBRG_API void librg_message_sendex (struct librg_ctx *ctx, librg_message_id id, librg_peer *target, librg_peer *except, u16 channel, b8 reliable, void *data, usize size);
LIBRG_API void librg_message_sendex_instream (struct librg_ctx *ctx, librg_message_id id, librg_entity_id entity_id, librg_peer *except, u16 channel, b8 reliable, void *data, usize size);
// =======================================================================//
// !
// ! Helper methods
// !
// =======================================================================//
/**
* Get current execution time
* On server side returns time aprox since process is up and running
* On client side returns synced time client time to server time.
* Ideally calling this method on both connected client and server at the same time
* will return pretty much similar time, or at least 2 that are quite close to one another
*
* Retuned time is a CLOCK_MONOTONIC, the format is second before comma,
* and parts lesser than second after comma
*
* EXAMPLE:
* 1.2424244 - 1 second 242 milliseconds
* 255.4224244 - 255 seconds
*/
LIBRG_API f64 librg_time_now(struct librg_ctx *ctx);
/**
* Calcualte standard devaiation for a set of values
* Example:
* f64 values[3] = { 1, 2, 3 };
* f64 dev = librg_standard_deviation(&values, 3);
*/
LIBRG_API f64 librg_standard_deviation(f64 *values, usize size);
// =======================================================================//
// !
// ! Options
// !
// =======================================================================//
enum librg_options {
LIBRG_PLATFORM_ID,
LIBRG_PLATFORM_PROTOCOL,
LIBRG_PLATFORM_BUILD,
LIBRG_DEFAULT_CLIENT_TYPE,
LIBRG_DEFAULT_STREAM_RANGE,
LIBRG_DEFAULT_DATA_SIZE,
LIBRG_NETWORK_CAPACITY,
LIBRG_NETWORK_CHANNELS,
LIBRG_NETWORK_PRIMARY_CHANNEL,
LIBRG_NETWORK_SECONDARY_CHANNEL,
LIBRG_NETWORK_MESSAGE_CHANNEL,
LIBRG_NETWORK_BUFFER_SIZE,
LIBRG_MAX_ENTITIES_PER_BRANCH,
LIBRG_USE_RADIUS_CULLING,
LIBRG_OPTIONS_SIZE,
};
/**
* Get global cross-instance option for librg
*/
LIBRG_API u32 librg_option_get(u32 option);
/**
* Set global cross-instance option for librg
*/
LIBRG_API void librg_option_set(u32 option, u32 value);
// =======================================================================//
// !
// ! Main context definition
// !
// =======================================================================//
typedef struct librg_snapshot {
f64 time;
void *data;
usize size;
librg_peer *peer;
} librg_snapshot;
ZPL_RING_DECLARE(librg_snapshot);
typedef struct librg_space_node {
struct librg_entity *blob;
b32 unused;
} librg_space_node;
typedef struct librg_space {
zpl_allocator allocator;
u32 max_nodes;
isize dimensions;
zpl_aabb3 boundary;
zpl_vec3 min_bounds;
b32 use_min_bounds;
usize *free_nodes; ///< zpl_array
struct librg_space *spaces; ///< zpl_array
struct librg_space_node *nodes; ///< zpl_array
} librg_space;
#define LIBRG_TIMESYNC_SIZE 5
#define LIBRG_DATA_STREAMS_AMOUNT 4
/**
* Context + config struct
*/
typedef struct librg_ctx {
// core
u16 mode;
f64 tick_delay;
// configuration
u16 max_connections;
u32 max_entities;
zpl_vec3 world_size;
zpl_vec3 min_branch_size;
f64 last_update;
void *user_data;
struct {
librg_peer *peer;
librg_host *host;
librg_table connected_peers;
librg_address last_address;
b32 created;
b32 connected;
} network;
struct {
u32 count;
u32 cursor;
#ifdef LIBRG_FEATURE_ENTITY_VISIBILITY
librg_table visibility;
#endif
struct librg_entity *list;
librg_entity_id *remove_queue; ///< zpl_array
librg_message **add_control_queue; ///< zpl_array
} entity;
union {
struct {
librg_data stream_input;
librg_data stream_output;
librg_data stream_upd_reliable;
librg_data stream_upd_unreliable;
};
librg_data streams[LIBRG_DATA_STREAMS_AMOUNT];
};
struct {
f64 start_time;
f64 offset_time;
f64 median;
f64 history[LIBRG_TIMESYNC_SIZE];
f32 server_delay;
zpl_timer *timer;
} timesync;
usize buffer_size;
zpl_timer *buffer_timer;
zpl_ring_librg_snapshot buffer;
librg_message_cb **messages; ///< zpl_buffer
zpl_allocator allocator;
zpl_timer_pool timers;
librg_event_pool events;
librg_space world;
} librg_ctx;
// =======================================================================//
// !
// ! Extensions
// !
// =======================================================================//
#if 1
#define librg__send_internal(CTX, ID, NAME, CALLBACK_CODE, SEND_CODE) \
librg_data NAME; \
librg_data_init(&NAME); \
CALLBACK_CODE; SEND_CODE; \
librg_data_free(&NAME);
#define librg_send_all(CTX, ID, NAME, CALLBACK_CODE) do { \
librg__send_internal(CTX, ID, NAME, CALLBACK_CODE, { \
librg_message_send_all(CTX, ID, NAME.rawptr, librg_data_get_wpos(&NAME)); \
}); \
} while(0);
#define librg_send_to(CTX, ID, PEER, NAME, CALLBACK_CODE) do { \
librg__send_internal(CTX, ID, NAME, CALLBACK_CODE, { \
librg_message_send_to(CTX, ID, PEER, NAME.rawptr, librg_data_get_wpos(&NAME)); \
}); \
} while(0);
#define librg_send_except(CTX, ID, PEER, NAME, CALLBACK_CODE) do { \
librg__send_internal(CTX, ID, NAME, CALLBACK_CODE, { \
librg_message_send_except(CTX, ID, PEER, NAME.rawptr, librg_data_get_wpos(&NAME)); \
}); \
} while(0);
#define librg_send_instream(CTX, ID, ENTITY, NAME, CALLBACK_CODE) do { \
librg__send_internal(CTX, ID, NAME, CALLBACK_CODE, { \
librg_message_send_instream(CTX, ID, ENTITY, NAME.rawptr, librg_data_get_wpos(&NAME)); \
}); \
} while(0);
#define librg_send_instream_except(CTX, ID, ENTITY, PEER, NAME, CALLBACK_CODE) do { \
librg__send_internal(CTX, ID, NAME, CALLBACK_CODE, { \
librg_message_send_instream(CTX, ID, ENTITY, PEER, NAME.rawptr, librg_data_get_wpos(&NAME)); \
}); \
} while(0);
#define librg_send librg_send_all
#endif
#ifdef __cplusplus
}
#endif
#if defined(LIBRG_IMPLEMENTATION) && !defined(LIBRG_IMPLEMENTATION_DONE)
#define LIBRG_IMPLEMENTATION_DONE
#ifdef __cplusplus
extern "C" {
#endif
// =======================================================================//
// !
// ! Implementation part
// !
// =======================================================================//
ZPL_TABLE_DEFINE(librg_event_pool, librg_event_pool_, librg_event_block);
ZPL_TABLE_DEFINE(librg_table, librg_table_, u32);
ZPL_RING_DEFINE(librg_snapshot);
/* internal declarations */
LIBRG_INTERNAL void librg__callback_connection_init(librg_message *msg);
LIBRG_INTERNAL void librg__callback_connection_request(librg_message *msg);
LIBRG_INTERNAL void librg__callback_connection_refuse(librg_message *msg);
LIBRG_INTERNAL void librg__callback_connection_accept(librg_message *msg);
LIBRG_INTERNAL void librg__callback_connection_disconnect(librg_message *msg);
LIBRG_INTERNAL void librg__callback_connection_timesync(librg_message *msg);
LIBRG_INTERNAL void librg__callback_entity_create(librg_message *msg);
LIBRG_INTERNAL void librg__callback_entity_update(librg_message *msg);
LIBRG_INTERNAL void librg__callback_entity_client_streamer_add(librg_message *msg);
LIBRG_INTERNAL void librg__callback_entity_client_streamer_remove(librg_message *msg);
LIBRG_INTERNAL void librg__callback_entity_client_streamer_update(librg_message *msg);
LIBRG_INTERNAL void librg__timesync_start(librg_ctx *ctx);
LIBRG_INTERNAL void librg__timesync_tick(void *usrptr);
LIBRG_INTERNAL void librg__timesync_stop(librg_ctx *ctx);
LIBRG_INTERNAL void librg__buffer_init(librg_ctx *ctx, usize size);
LIBRG_INTERNAL void librg__buffer_free(librg_ctx *ctx);
LIBRG_INTERNAL void librg__buffer_push(librg_ctx *ctx, f64 time, librg_peer *peer, void *data, usize size);
LIBRG_INTERNAL void librg__buffer_tick(void *usrptr);
// short internal helper macro
#define LIBRG_MESSAGE_TO_EVENT(NAME, MSG) \
librg_event NAME = {0}; \
NAME.peer = MSG->peer; \
NAME.data = MSG->data; \
NAME.flags = LIBRG_EVENT_REMOTE;
// =======================================================================//
// !
// ! Work in progress area
// ! TODO: move stuff below
// !
// =======================================================================//
/* world stuff */
typedef struct {
usize id;
usize offset;
usize count;
struct librg_ctx *ctx;
} librg_update_worker_si_t;
LIBRG_INTERNAL void librg__world_update(void *);
LIBRG_INTERNAL void librg__world_entity_query(librg_ctx *ctx, librg_entity_id entity, librg_space *c, zpl_aabb3 bounds, librg_entity_id **out_entities);
LIBRG_INTERNAL b32 librg__world_entity_destroy(librg_ctx *ctx, librg_entity_id id);
/* space stuff */
LIBRG_INTERNAL void librg__space_init(librg_space *c, zpl_allocator a, isize dims, zpl_aabb3 bounds, zpl_vec3 min_bounds, u32 max_nodes);
LIBRG_INTERNAL void librg__space_clear(librg_space *c);
b32 librg__space_remove_node(librg_space *c, librg_entity *tag);
LIBRG_INTERNAL b32 librg__space_intersects(isize dims, zpl_aabb3 a, zpl_aabb3 b) {
for (i32 i = 0; i < dims; ++i) {
if (zpl_abs(a.centre.e[i] - b.centre.e[i]) > (a.half_size.e[i] + b.half_size.e[i])) return false;
}
return true;
}
LIBRG_INTERNAL b32 librg__space_contains(isize dims, zpl_aabb3 a, f32 *point) {
for (i32 i = 0; i < dims; ++i) {
if (!( a.centre.e[i] - a.half_size.e[i] <= point[i]
&& a.centre.e[i] + a.half_size.e[i] >= point[i])) {
return false;
}
}
return true;
}
// =======================================================================//
// !
// ! Genaral methods
// !
// =======================================================================//
#if 1
void *librg_allocate_ptr(enum librg_pointer_type type) {
void *ptr = NULL;
switch (type) {
case LIBRG_POINTER_CTX:
ptr = (void *)zpl_malloc(sizeof(librg_ctx));
zpl_zero_size(ptr, sizeof(librg_ctx));
break;
case LIBRG_POINTER_DATA:
ptr = (void *)zpl_malloc(sizeof(librg_data));
zpl_zero_size(ptr, sizeof(librg_data));
break;
case LIBRG_POINTER_EVENT:
ptr = (void *)zpl_malloc(sizeof(librg_event));
zpl_zero_size(ptr, sizeof(librg_event));
break;
}
return ptr;
}
void librg_release_ptr(void *ptr) {
librg_assert(ptr);
zpl_mfree(ptr);
}
u32 librg_init(librg_ctx *ctx) {
librg_dbg("[dbg] librg_init\n");
#define librg_set_default(expr, value) if (!expr) expr = value
// apply default settings (if no user provided)
librg_set_default(ctx->tick_delay, 32.0);
librg_set_default(ctx->max_connections, 16);
librg_set_default(ctx->max_entities, 8192);
librg_set_default(ctx->world_size.x, 5000.0f);
librg_set_default(ctx->world_size.y, 5000.0f);
librg_set_default(ctx->min_branch_size.x, 50.0f);
librg_set_default(ctx->min_branch_size.y, 50.0f);
librg_set_default(ctx->mode, LIBRG_MODE_SERVER);
if (!ctx->allocator.proc && !ctx->allocator.data) {
ctx->allocator = zpl_heap_allocator();
}
#undef librg_set_default
// init entities system
ctx->entity.list = (librg_entity*)zpl_alloc(ctx->allocator, sizeof(librg_entity) * ctx->max_entities);
for (librg_entity_id i = 0; i < ctx->max_entities; i++) {
librg_entity blob = { i, 0 };
ctx->entity.list[i] = blob;
}
zpl_array_init(ctx->entity.remove_queue, ctx->allocator);
zpl_array_init(ctx->entity.add_control_queue, ctx->allocator);
for (i8 i = 0; i < LIBRG_DATA_STREAMS_AMOUNT; ++i) {
librg_data_init(&ctx->streams[i]);
}
// streamer
zpl_aabb3 world = {0};
world.centre = zpl_vec3f(0, 0, 0);
world.half_size = zpl_vec3f(ctx->world_size.x, ctx->world_size.y, ctx->world_size.z);
u32 dimension = ctx->world_size.z == 0.0f ? LIBRG_SPACE_2D : LIBRG_SPACE_3D;
if (ctx->min_branch_size.x == -1.0f &&
ctx->min_branch_size.y == -1.0f &&
ctx->min_branch_size.z == -1.0f) {
zpl_vec3 no_min_bounds = { 0 };
ctx->min_branch_size = no_min_bounds;
}
librg__space_init(&ctx->world, ctx->allocator, dimension, world, ctx->min_branch_size, librg_option_get(LIBRG_MAX_ENTITIES_PER_BRANCH));
#ifdef LIBRG_FEATURE_ENTITY_VISIBILITY
librg_table_init(&ctx->entity.visibility, ctx->allocator);
#endif
// events
librg_event_pool_init(&ctx->events, ctx->allocator);
zpl_buffer_init(ctx->messages, ctx->allocator, librg_option_get(LIBRG_NETWORK_CAPACITY));
// init timers
zpl_array_init(ctx->timers, ctx->allocator);
zpl_timer *tick_timer = zpl_timer_add(ctx->timers);
tick_timer->user_data = (void *)ctx; /* provide ctx as a argument to timer */
zpl_timer_set(tick_timer, ctx->tick_delay * 0.001, -1, librg__world_update);
zpl_timer_start(tick_timer, 0.250);
ctx->timesync.timer = zpl_timer_add(ctx->timers);
ctx->timesync.timer->user_data = (void *)ctx; /* provide ctx as a argument to timer */
ctx->buffer_timer = zpl_timer_add(ctx->timers);
ctx->buffer_timer->user_data = (void *)ctx; /* provide ctx as a argument to timer */
// network
u8 enet_init = enet_initialize();
librg_assert_msg(enet_init == 0, "cannot initialize enet");
if (enet_init != 0) {
return -1;
}
// add event handlers for our network stufz
ctx->messages[LIBRG_CONNECTION_INIT] = librg__callback_connection_init;
ctx->messages[LIBRG_CONNECTION_REQUEST] = librg__callback_connection_request;
ctx->messages[LIBRG_CONNECTION_REFUSE] = librg__callback_connection_refuse;
ctx->messages[LIBRG_CONNECTION_ACCEPT] = librg__callback_connection_accept;
ctx->messages[LIBRG_CONNECTION_DISCONNECT] = librg__callback_connection_disconnect;
ctx->messages[LIBRG_CONNECTION_TIMESYNC] = librg__callback_connection_timesync;
ctx->messages[LIBRG_ENTITY_CREATE] = librg__callback_entity_create;
ctx->messages[LIBRG_ENTITY_UPDATE] = librg__callback_entity_update;
ctx->messages[LIBRG_CLIENT_STREAMER_ADD] = librg__callback_entity_client_streamer_add;
ctx->messages[LIBRG_CLIENT_STREAMER_REMOVE] = librg__callback_entity_client_streamer_remove;
ctx->messages[LIBRG_CLIENT_STREAMER_UPDATE] = librg__callback_entity_client_streamer_update;
return 0;
}
void librg_free(librg_ctx *ctx) {
librg_dbg("[dbg] librg_free\n");
// free all timers and events first
zpl_array_free(ctx->timers);
zpl_buffer_free(ctx->messages, ctx->allocator);
for (isize i = 0; i < zpl_array_count(ctx->events.entries); ++i) {
librg_event_block *block = &ctx->events.entries[i].value;
if (block) {
zpl_array_free(*block);
}
}
librg_event_pool_destroy(&ctx->events);
zpl_free(ctx->allocator, ctx->entity.list);
zpl_array_free(ctx->entity.remove_queue);
zpl_array_free(ctx->entity.add_control_queue);
// streamer
librg__space_clear(&ctx->world);
#ifdef LIBRG_FEATURE_ENTITY_VISIBILITY
librg_table_destroy(&ctx->entity.visibility);
#endif
for (isize i = 0; i < LIBRG_DATA_STREAMS_AMOUNT; ++i) {
librg_data_free(&ctx->streams[i]);
}
enet_deinitialize();
}
void librg_tick(librg_ctx *ctx) {
zpl_timer_update(ctx->timers);
if (!ctx->network.host) {
return; /* occasion where we are not started network yet */
}
ENetEvent event;
while (enet_host_service(ctx->network.host, &event, 0) > 0) {
librg_message msg = {0}; {
msg.ctx = ctx;
msg.data = NULL;
msg.peer = event.peer;
msg.packet = event.packet;
}
switch (event.type) {
case ENET_EVENT_TYPE_RECEIVE: {
librg_data data = {0};
data.rawptr = event.packet->data;
data.capacity = event.packet->dataLength;
if (!data.rawptr || data.capacity < sizeof(librg_message_id)) {
librg_assert(false);
librg_dbg("[dbg] corrupted packet in librg_tick, on receive\n");
continue;
}
// get curernt packet id
librg_message_id id = librg_data_rmid(&data);
f64 server_time = 0;
msg.id = id;
if (id == LIBRG_ENTITY_UPDATE) {
server_time = librg_data_rf64(&data);
// librg_log("server_time: %f, client_predicted: %f, diff: %f\n", server_time, librg_time_now(ctx), librg_time_now(ctx) - server_time);
if (librg_option_get(LIBRG_NETWORK_BUFFER_SIZE) > 1) {
librg__buffer_push(ctx, server_time, event.peer, data.rawptr, data.capacity);
enet_packet_destroy(event.packet);
return;
}
}
if (ctx->messages[id]) {
msg.data = &data;
ctx->messages[id](&msg);
}
else {
/* print unknown message id */
librg_dbg("[dbg] unknown message: %u\n", id);
}
enet_packet_destroy(event.packet);
} break;
case ENET_EVENT_TYPE_CONNECT: ctx->messages[LIBRG_CONNECTION_INIT](&msg); break;
case ENET_EVENT_TYPE_DISCONNECT: ctx->messages[LIBRG_CONNECTION_DISCONNECT](&msg); break;
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: {
msg.user_data = (void *)1; /* mark the timeout */
ctx->messages[LIBRG_CONNECTION_DISCONNECT](&msg);
} break;
case ENET_EVENT_TYPE_NONE: break;
}
}
}
#endif
// =======================================================================//
// !
// ! Entities
// !
// =======================================================================//
#if 1
librg_entity *librg_entity_create(librg_ctx *ctx, u32 type) {
librg_assert(ctx);
librg_assert(librg_is_server(ctx));
librg_assert_msg(ctx->entity.count < ctx->max_entities, "reached max_entities limit");
if (ctx->entity.count >= ctx->max_entities) {
return NULL;
}
++ctx->entity.count;
if (ctx->entity.cursor >= (ctx->max_entities - 1) || ctx->max_entities == 0) {
ctx->entity.cursor = 0;
}
for (; ctx->entity.cursor < ctx->max_entities; ++ctx->entity.cursor) {
librg_entity *entity = &ctx->entity.list[ctx->entity.cursor]; librg_assert(entity);
if (entity->flags & LIBRG_ENTITY_ALIVE) continue;
entity->type = type;
entity->flags = LIBRG_ENTITY_ALIVE;
entity->position = zpl_vec3f_zero();
entity->stream_range = librg_option_get(LIBRG_DEFAULT_STREAM_RANGE) * 1.0f;
entity->stream_branch = NULL;
entity->control_generation = 0;
return entity;
}
librg_assert_msg(false, "no entities to spawn");
return NULL;
}
librg_entity *librg_entity_fetch(librg_ctx *ctx, librg_entity_id id) {
if (librg_entity_valid(ctx, id))
return &ctx->entity.list[id];
return NULL;
}
void librg_entity_destroy(librg_ctx *ctx, librg_entity_id id) {
librg_assert(librg_is_server(ctx));
librg_entity *blob = librg_entity_fetch(ctx, id);
blob->flags |= LIBRG_ENTITY_MARKED_REMOVAL;
zpl_array_append(ctx->entity.remove_queue, id);
}
usize librg_entity_query(librg_ctx *ctx, librg_entity_id entity, librg_entity_id **out_entities) {
librg_assert(ctx && out_entities);
librg_assert(librg_is_server(ctx));
librg_entity *blob = librg_entity_fetch(ctx, entity);
if (!(blob->flags & LIBRG_ENTITY_QUERIED)) {
blob->flags |= LIBRG_ENTITY_QUERIED;
zpl_array_init(blob->last_query, ctx->allocator);
}
// reset array to 0
zpl_array(librg_entity_id) temp_array;
zpl_array_init(temp_array, ctx->allocator);
zpl_array_count(blob->last_query) = 0;
/* add all currently streamed entities automatically */
librg_entity_iteratex(ctx, LIBRG_ENTITY_CONTROLLED, librg_lambda(controlled), {
if (blob->client_peer && librg_entity_control_get(ctx, controlled) == blob->client_peer) {
if (librg_entity_valid(ctx, controlled) && !(librg_entity_fetch(ctx, controlled)->flags & LIBRG_ENTITY_MARKED_REMOVAL)) {
zpl_array_append(temp_array, controlled);
}
}
});
#ifdef LIBRG_FEATURE_ENTITY_VISIBILITY
/* add related visible entities */
if (blob->flags & LIBRG_ENTITY_VISIBILITY) {
for (int i = 0; i < zpl_array_count(blob->visibility.entries); ++i) {
#ifdef LIBRG_FEATURE_VIRTUAL_WORLDS
if (librg_entity_world_get(ctx, blob->id) != librg_entity_world_get(ctx, blob->visibility.entries[i].key)) continue;
#endif
if (blob->visibility.entries[i].value == LIBRG_ALWAYS_VISIBLE) {
if (librg_entity_valid(ctx, blob->visibility.entries[i].key) &&
!(librg_entity_fetch(ctx, blob->visibility.entries[i].key)->flags & LIBRG_ENTITY_MARKED_REMOVAL)) {
zpl_array_append(temp_array, blob->visibility.entries[i].key);
}
}
}
}
/* add global visible entities */
for (int i = 0; i < zpl_array_count(ctx->entity.visibility.entries); ++i) {
#ifdef LIBRG_FEATURE_VIRTUAL_WORLDS
if (librg_entity_world_get(ctx, blob->id) != librg_entity_world_get(ctx, ctx->entity.visibility.entries[i].key)) continue;
#endif
if (ctx->entity.visibility.entries[i].value == LIBRG_ALWAYS_VISIBLE) {
if (librg_entity_visibility_get_for(ctx, blob->id, ctx->entity.visibility.entries[i].key) != LIBRG_ALWAYS_INVISIBLE) {
if (librg_entity_valid(ctx, ctx->entity.visibility.entries[i].key) &&
!(librg_entity_fetch(ctx, ctx->entity.visibility.entries[i].key)->flags & LIBRG_ENTITY_MARKED_REMOVAL)) {
zpl_array_append(temp_array, ctx->entity.visibility.entries[i].key);
}
}
}
}
#endif
zpl_aabb3 search_bounds; {
search_bounds.centre = blob->position;
search_bounds.half_size = zpl_vec3f(blob->stream_range, blob->stream_range, blob->stream_range);
};
librg__world_entity_query(ctx, entity, &ctx->world, search_bounds, &temp_array);
/* remove duplicate entities */
for (int i = 0; i < zpl_array_count(temp_array); ++i) {
bool isduplicate = false;
for (int j = 0; j < zpl_array_count(blob->last_query); ++j) {
if (temp_array[i] == blob->last_query[j]) {
isduplicate = true;
break;
}
}
if (!isduplicate) {
zpl_array_append(blob->last_query, temp_array[i]);
}
}
zpl_array_free(temp_array);
*out_entities = blob->last_query;
return zpl_array_count(blob->last_query);
}
b32 librg_entity_valid(librg_ctx *ctx, librg_entity_id id) {
librg_assert(ctx);
return (id < ctx->max_entities && (ctx->entity.list[id].flags & LIBRG_ENTITY_ALIVE));
}
librg_entity *librg_entity_find(librg_ctx *ctx, librg_peer *peer) {
librg_assert(ctx && librg_is_server(ctx) && peer);
librg_entity_id *id = librg_table_get(&ctx->network.connected_peers, (u64)peer);
if (id) {
return librg_entity_fetch(ctx, *id);
}
return NULL;
}
void librg_entity_iterate(librg_ctx *ctx, u64 flags, librg_entity_cb callback) {
librg_entity_iteratex(ctx, flags, librg_lambda(entity), { callback(ctx, librg_entity_fetch(ctx, entity)); });
}
u32 librg_entity_type(librg_ctx *ctx, librg_entity_id id) {
librg_assert(librg_entity_valid(ctx, id));
return ctx->entity.list[id].type;
}
#ifdef LIBRG_FEATURE_ENTITY_VISIBILITY
void librg_entity_visibility_set(librg_ctx *ctx, librg_entity_id entity, librg_visibility state) {
librg_assert(librg_is_server(ctx) && librg_entity_valid(ctx, entity));
librg_table_set(&ctx->entity.visibility, entity, (u32)state);
}
void librg_entity_visibility_set_for(librg_ctx *ctx, librg_entity_id entity, librg_entity_id target, librg_visibility state) {
librg_assert(librg_is_server(ctx) && librg_entity_valid(ctx, entity));
librg_entity *blob = librg_entity_fetch(ctx, entity);
/* prevent any operations on the same entity*/
if (entity == target) return;
if (!(blob->flags & LIBRG_ENTITY_VISIBILITY)) {
blob->flags |= LIBRG_ENTITY_VISIBILITY;
librg_table_init(&blob->visibility, ctx->allocator);
}
librg_table_set(&blob->visibility, target, (u32)state);
}
librg_visibility librg_entity_visibility_get(librg_ctx *ctx, librg_entity_id entity) {
librg_assert(librg_is_server(ctx) && librg_entity_valid(ctx, entity));
u32 *visibility = librg_table_get(&ctx->entity.visibility, entity);
return (librg_visibility)(visibility ? *visibility : LIBRG_DEFAULT_VISIBILITY);
}
librg_visibility librg_entity_visibility_get_for(librg_ctx *ctx, librg_entity_id entity, librg_entity_id target) {
librg_assert(librg_is_server(ctx));
librg_entity *blob = librg_entity_fetch(ctx, entity);
/* prevent any operations on the same entity*/
if (entity == target) return LIBRG_DEFAULT_VISIBILITY;
if (!(blob->flags & LIBRG_ENTITY_VISIBILITY)) {
return LIBRG_DEFAULT_VISIBILITY;
}
u32 *visibility = librg_table_get(&blob->visibility, target);
return (librg_visibility)(visibility ? *visibility : LIBRG_DEFAULT_VISIBILITY);
}
#endif
#ifdef LIBRG_FEATURE_VIRTUAL_WORLDS
u32 librg_entity_world_get(struct librg_ctx *ctx, librg_entity_id entity_id) {
librg_assert(ctx);
librg_entity *entity = librg_entity_fetch(ctx, entity_id);
if (entity) {
return entity->virual_world;
}
return 0;
}
void librg_entity_world_set(struct librg_ctx *ctx, librg_entity_id entity_id, u32 world) {
librg_assert(ctx);
librg_entity *entity = librg_entity_fetch(ctx, entity_id);
if (entity) {
entity->virual_world = world;
}
}
#endif
void librg_entity_control_set(librg_ctx *ctx, librg_entity_id entity, librg_peer *peer) {
librg_assert(ctx && peer && librg_entity_valid(ctx, entity));
librg_assert(librg_is_server(ctx));
librg_entity *blob = librg_entity_fetch(ctx, entity);
// replace current entity owner
if (blob->flags & LIBRG_ENTITY_CONTROLLED) {
if (blob->control_peer == peer) {
return;
}
librg_send_to(ctx, LIBRG_CLIENT_STREAMER_REMOVE, blob->control_peer, librg_lambda(data), {
librg_data_went(&data, entity);
});
}
// attach new entity owner
blob->flags |= (LIBRG_ENTITY_CONTROLLED | LIBRG_ENTITY_CONTROL_REQUESTED);
blob->control_peer = peer;
// main compare/validation thingy
blob->control_generation++;
librg_message *msg = (librg_message *)zpl_alloc(ctx->allocator, sizeof(librg_message)); {
librg_message_id id = LIBRG_CLIENT_STREAMER_ADD;
librg_data data = {0};
librg_data_init(&data);
librg_data_went(&data, entity);
librg_data_wu8(&data, blob->control_generation);
msg->id = id;
msg->peer = peer;
msg->packet = enet_packet_create_offset(
data.rawptr, librg_data_get_wpos(&data), sizeof(librg_message_id), ENET_PACKET_FLAG_RELIABLE
);
zpl_memcopy(msg->packet->data, &id, sizeof(librg_message_id));
librg_data_free(&data);
}
zpl_array_append(ctx->entity.add_control_queue, msg);
}
librg_peer *librg_entity_control_get(librg_ctx *ctx, librg_entity_id entity) {
librg_assert(ctx);
librg_assert(librg_is_server(ctx));
librg_entity *blob = librg_entity_fetch(ctx, entity);
return (blob && blob->flags & LIBRG_ENTITY_CONTROLLED) ? blob->control_peer : NULL;
}
void librg_entity_control_remove(librg_ctx *ctx, librg_entity_id entity) {
librg_assert(ctx && librg_entity_valid(ctx, entity));
librg_assert(librg_is_server(ctx));
librg_entity *blob = librg_entity_fetch(ctx, entity);
// some minor validations
if (!(blob->flags & LIBRG_ENTITY_CONTROLLED)) { return; }
librg_send_to(ctx, LIBRG_CLIENT_STREAMER_REMOVE, blob->control_peer, librg_lambda(data), {
librg_data_went(&data, entity);
});
blob->flags &= ~LIBRG_ENTITY_CONTROLLED;
blob->control_peer = NULL;
}
void librg_entity_control_ignore_next_update(librg_ctx *ctx, librg_entity_id entity) {
librg_assert(ctx && librg_entity_valid(ctx, entity));
librg_assert(librg_is_server(ctx));
librg_entity *blob = librg_entity_fetch(ctx, entity);
// some minor validations
if (!(blob->flags & LIBRG_ENTITY_CONTROLLED)) { return; }
if (blob->flags & LIBRG_ENTITY_CONTROL_REQUESTED) { return; }
// and super complex algo to reset the peer, and update the control_generation
librg_peer *client_peer = librg_entity_control_get(ctx, entity);
librg_entity_control_remove(ctx, entity);
librg_entity_control_set(ctx, entity, client_peer);
}
#endif
// =======================================================================//
// !
// ! Events
// !
// =======================================================================//
#if 1
u64 librg_event_add(librg_ctx *ctx, u64 id, librg_event_cb callback) {
librg_assert(ctx);
librg_event_block *block = librg_event_pool_get(&ctx->events, id);
if (!block) {
librg_event_block arr;
zpl_array_init(arr, ctx->allocator);
librg_event_pool_set(&ctx->events, id, arr);
block = librg_event_pool_get(&ctx->events, id);
}
u64 offset = zpl_array_count(block);
zpl_array_append(*block, callback);
return offset;
}
void librg_event_trigger(librg_ctx *ctx, u64 id, librg_event *event) {
librg_assert(event);
event->id = id;
event->ctx = ctx;
librg_event_block *block = librg_event_pool_get(&ctx->events, id);
if (!block) return;
for (isize i = 0; i < zpl_array_count(*block) && !(event->flags & LIBRG_EVENT_REJECTED); ++i) {
(*block)[i](event);
}
}
void librg_event_remove(librg_ctx *ctx, u64 id, u64 index) {
librg_assert(ctx);
librg_event_block *block = librg_event_pool_get(&ctx->events, id);
if (block) {
zpl_array_remove_at(*block, (isize)index);
}
}
void librg_event_reject(librg_event *event) {
librg_assert(event);
event->flags = (event->flags | LIBRG_EVENT_REJECTED);
}
b32 librg_event_rejectable(librg_event *event) {
librg_assert(event);
return (event->flags & LIBRG_EVENT_REJECTABLE);
}
b32 librg_event_succeeded(librg_event *event) {
librg_assert(event);
return !(event->flags & LIBRG_EVENT_REJECTED);
}
#endif
// =======================================================================//
// !
// ! Binary data (bitstream)
// !
// =======================================================================//
#if 1
void librg_data_init_size(librg_data *data, usize size) {
librg_assert(data);
data->capacity = size;
data->read_pos = 0;
data->write_pos = 0;
data->allocator = zpl_heap_allocator();
data->rawptr = zpl_alloc(data->allocator, size);
}
void librg_data_init(librg_data *data) {
librg_assert(data);
librg_data_init_size(data, librg_option_get(LIBRG_DEFAULT_DATA_SIZE));
}
librg_data *librg_data_init_new() {
librg_data *data = (librg_data *)zpl_malloc(sizeof(librg_data));
librg_data_init(data);
return data;
}
void librg_data_free(librg_data *data) {
librg_assert(data && data->rawptr);
zpl_free(data->allocator, data->rawptr);
}
void librg_data_reset(librg_data *data) {
librg_data_set_wpos(data, 0);
librg_data_set_rpos(data, 0);
}
void librg_data_grow(librg_data *data, usize min_size) {
librg_assert(data && data->rawptr);
usize new_capacity = LIBRG_DATA_GROW_FORMULA(data->capacity);
if (new_capacity < (min_size))
new_capacity = (min_size);
void *newptr = zpl_alloc(data->allocator, new_capacity);
zpl_memcopy(newptr, data->rawptr, data->write_pos);
zpl_free(data->allocator, data->rawptr);
data->capacity = new_capacity;
data->rawptr = newptr;
}
usize librg_data_capacity(librg_data *data) {
librg_assert(data); return data->capacity;
}
usize librg_data_get_rpos(librg_data *data) {
librg_assert(data); return data->read_pos;
}
usize librg_data_get_wpos(librg_data *data) {
librg_assert(data); return data->write_pos;
}
void librg_data_set_rpos(librg_data *data, usize position) {
librg_assert(data); data->read_pos = position;
}
void librg_data_set_wpos(librg_data *data, usize position) {
librg_assert(data); data->write_pos = position;
}
b32 librg_data_rptr_at(librg_data *data, void *ptr, usize size, isize position) {
librg_assert(data && data->rawptr && ptr);
librg_assert_msg(position + size <= librg_data_capacity(data),
"librg_data: trying to read from outside of the bounds");
if (position + size > librg_data_capacity(data)) {
return false;
}
zpl_memcopy(ptr, (char *)data->rawptr + position, size);
return true;
}
void librg_data_wptr_at(librg_data *data, void *ptr, usize size, isize position) {
librg_assert(data && data->rawptr && ptr);
if (position + size > librg_data_capacity(data)) {
librg_data_grow(data, librg_data_capacity(data) + size + position);
}
zpl_memcopy((char *)data->rawptr + position, ptr, size);
}
b32 librg_data_rptr(librg_data *data, void *ptr, usize size) {
librg_assert(data && data->rawptr && ptr);
if (!librg_data_rptr_at(data, ptr, size, librg_data_get_rpos(data))) {
return false;
}
data->read_pos += size;
return true;
}
void librg_data_wptr(librg_data *data, void *ptr, usize size) {
librg_assert(data && data->rawptr && ptr);
librg_data_wptr_at(data, ptr, size, librg_data_get_wpos(data));
data->write_pos += size;
}
/**
* Value writers and readers
*/
#define LIBRG_GEN_DATA_READWRITE(TYPE) \
TYPE ZPL_JOIN2(librg_data_r,TYPE)(librg_data *data) { \
TYPE value; librg_data_rptr(data, &value, sizeof(value)); return value; \
} \
void ZPL_JOIN2(librg_data_w,TYPE)(librg_data *data, TYPE value) { \
librg_data_wptr(data, &value, sizeof(value)); \
} \
TYPE ZPL_JOIN3(librg_data_r,TYPE,_at)(librg_data *data, isize position) { \
TYPE value; librg_data_rptr_at(data, &value, sizeof(value), position); return value; \
} \
void ZPL_JOIN3(librg_data_w,TYPE,_at)(librg_data *data, TYPE value, isize position) { \
librg_data_wptr_at(data, &value, sizeof(value), position); \
}
/**
* General one-line methods for reading/writing different types
*/
LIBRG_GEN_DATA_READWRITE( i8);
LIBRG_GEN_DATA_READWRITE( u8);
LIBRG_GEN_DATA_READWRITE(i16);
LIBRG_GEN_DATA_READWRITE(u16);
LIBRG_GEN_DATA_READWRITE(i32);
LIBRG_GEN_DATA_READWRITE(u32);
LIBRG_GEN_DATA_READWRITE(i64);
LIBRG_GEN_DATA_READWRITE(u64);
LIBRG_GEN_DATA_READWRITE(f32);
LIBRG_GEN_DATA_READWRITE(f64);
LIBRG_GEN_DATA_READWRITE( b8);
LIBRG_GEN_DATA_READWRITE(b16);
LIBRG_GEN_DATA_READWRITE(b32);
#undef LIBRG_GEN_DATA_READWRITE
/**
* Special method for internal packet-safe reading
* @param TYPE variable type
* @param VAR variable name
* @param DATA librg_data ptr
*/
#define librg_data_read_safe(TYPE, VAR, DATA) \
TYPE VAR = 0; \
if ((librg_data_get_rpos(DATA) + sizeof(TYPE)) <= librg_data_capacity(DATA)) { \
VAR = ZPL_JOIN2(librg_data_r,TYPE)(DATA); \
} else { \
librg_dbg("[dbg] corrupted packet in method (%s::%s) at line: %d\n", _LIBRG_METHOD, "librg_data_r"#TYPE, __LINE__); \
return; \
}
#endif
// =======================================================================//
// !
// ! Network
// !
// =======================================================================//
#if 1
b32 librg_is_connected(librg_ctx *ctx) {
return ctx->network.connected;
}
b32 librg_is_server(librg_ctx *ctx) {
return ctx->mode == LIBRG_MODE_SERVER;
}
b32 librg_is_client(librg_ctx *ctx) {
return ctx->mode == LIBRG_MODE_CLIENT;
}
u32 librg_network_start(librg_ctx *ctx, librg_address addr) {
librg_dbg("[dbg] librg_network_start\n");
ENetAddress address = {0};
if (librg_is_server(ctx)) {
if (addr.host && zpl_strcmp(addr.host, "localhost") != 0) {
enet_address_set_host(&address, addr.host);
} else {
address.host = ENET_HOST_ANY;
}
address.port = addr.port;
// setup server host
ctx->network.host = enet_host_create(&address, ctx->max_connections, librg_option_get(LIBRG_NETWORK_CHANNELS), 0, 0);
librg_assert_msg(ctx->network.host, "could not start server at provided port");
if (!ctx->network.host) {
return -1;
}
}
else {
const char *ipv6lclhst = "::1";
if (!addr.host || zpl_strcmp(addr.host, "localhost") == 0) {
addr.host = (char *)ipv6lclhst;
}
address.port = addr.port;
enet_address_set_host(&address, addr.host);
// setup client host
// TODO: add override for bandwidth
ctx->network.host = enet_host_create(NULL, 1, librg_option_get(LIBRG_NETWORK_CHANNELS), 0, 0);
librg_assert_msg(ctx->network.host, "could not start client");
if (!ctx->network.host) {
return -1;
}
// create peer connecting to server
librg_dbg("[dbg] connecting to server %s:%u\n", addr.host, addr.port);
ctx->network.peer = enet_host_connect(ctx->network.host, &address, librg_option_get(LIBRG_NETWORK_CHANNELS), 0);
librg_assert_msg(ctx->network.peer, "could not setup peer for provided address");
if (!ctx->network.peer) {
return -2;
}
}
if (librg_is_server(ctx)) {
librg_table_init(&ctx->network.connected_peers, ctx->allocator);
}
ctx->network.last_address = addr;
ctx->network.created = true;
return 0;
}
void librg_network_stop(librg_ctx *ctx) {
librg_dbg("[dbg] librg_network_stop\n");
ENetEvent event;
if (!ctx->network.created) {
return;
}
ctx->network.created = false;
if (librg_is_server(ctx)) {
/* disconnect all users */
for (librg_peer *currentPeer = ctx->network.host->peers; currentPeer < &ctx->network.host->peers[ctx->network.host->peerCount]; ++currentPeer) {
if (currentPeer->state != ENET_PEER_STATE_CONNECTED) continue;
enet_peer_disconnect(currentPeer, 0);
}
librg_table_destroy(&ctx->network.connected_peers);
enet_host_service(ctx->network.host, &event, 100);
// destroy all server entities that are currently created
for (usize i = 0; i < ctx->max_entities; ++i) {
if (librg_entity_valid(ctx, i)) {
librg_event event = {0}; {
event.entity = librg_entity_fetch(ctx, i);
event.flags = LIBRG_EVENT_LOCAL;
}
librg_event_trigger(ctx, LIBRG_ENTITY_REMOVE, &event);
librg__world_entity_destroy(ctx, i);
}
}
}
else if (librg_is_client(ctx)) {
librg_message msg = {0}; {
msg.ctx = ctx;
msg.peer = ctx->network.peer;
};
/* simulate a diconnect message */
librg__callback_connection_disconnect(&msg);
enet_peer_disconnect(ctx->network.peer, 0);
enet_host_service(ctx->network.host, &event, 100);
enet_peer_reset(ctx->network.peer);
}
}
void librg_network_kick(librg_ctx *ctx, librg_peer *peer) {
librg_assert(ctx && peer);
enet_peer_disconnect_later(peer, 0);
}
void librg_network_add(librg_ctx *ctx, librg_message_id id, librg_message_cb callback) {
ctx->messages[id] = callback;
}
void librg_network_remove(librg_ctx *ctx, librg_message_id id) {
ctx->messages[id] = NULL;
}
void librg_message_sendex(struct librg_ctx *ctx, librg_message_id id, librg_peer *target, librg_peer *except, u16 channel, b8 reliable, void *data, usize size) {
librg_packet *packet = enet_packet_create_offset(
data, size, sizeof(librg_message_id), reliable ? ENET_PACKET_FLAG_RELIABLE : 0
);
// write id at the beginning
zpl_memcopy(packet->data, &id, sizeof(librg_message_id));
librg_assert(channel <= librg_option_get(LIBRG_NETWORK_CHANNELS));
if (target) {
enet_peer_send(target, channel, packet);
} else {
ENetPeer *currentPeer;
for (currentPeer = ctx->network.host->peers; currentPeer < &ctx->network.host->peers[ctx->network.host->peerCount]; ++currentPeer) {
if (currentPeer->state != ENET_PEER_STATE_CONNECTED) {
continue;
}
if (currentPeer == except) {
continue;
}
/* TODO: add a skip if not peer's entity is not 'alive' */
enet_peer_send(currentPeer, channel, packet);
}
}
if (packet->referenceCount == 0) {
enet_packet_destroy(packet);
}
}
void librg_message_sendex_instream(struct librg_ctx *ctx, librg_message_id id, librg_entity_id entity_id, librg_peer *except, u16 channel, b8 reliable, void *data, usize size) {
librg_packet *packet = enet_packet_create_offset(
data, size, sizeof(librg_message_id), reliable ? ENET_PACKET_FLAG_RELIABLE : 0
);
// write id at the beginning
zpl_memcopy(packet->data, &id, sizeof(librg_message_id));
librg_assert(channel <= librg_option_get(LIBRG_NETWORK_CHANNELS));
zpl_array(librg_entity_id) queue;
usize count = librg_entity_query(ctx, entity_id, &queue);
for (isize i = 0; i < count; i++) {
librg_entity_id target = queue[i];
librg_entity *blob = librg_entity_fetch(ctx, target);
if (!(blob->flags & LIBRG_ENTITY_CLIENT)) continue;
librg_peer *peer = blob->client_peer;
librg_assert(peer);
if (peer == except) {
continue;
}
enet_peer_send(peer, channel, packet);
}
if (packet->referenceCount == 0) {
enet_packet_destroy(packet);
}
}
librg_inline void librg_message_send_all(librg_ctx *ctx, librg_message_id id, void *data, usize size) {
librg_message_sendex(ctx, id, NULL, NULL, librg_option_get(LIBRG_NETWORK_MESSAGE_CHANNEL), true, data, size);
}
librg_inline void librg_message_send_to(librg_ctx *ctx, librg_message_id id, librg_peer *target, void *data, usize size) {
librg_message_sendex(ctx, id, target, NULL, librg_option_get(LIBRG_NETWORK_MESSAGE_CHANNEL), true, data, size);
}
librg_inline void librg_message_send_except(librg_ctx *ctx, librg_message_id id, librg_peer *except, void *data, usize size) {
librg_message_sendex(ctx, id, NULL, except, librg_option_get(LIBRG_NETWORK_MESSAGE_CHANNEL), true, data, size);
}
librg_inline void librg_message_send_instream(librg_ctx *ctx, librg_message_id id, librg_entity_id entity_id, void *data, usize size) {
librg_message_sendex_instream(ctx, id, entity_id, NULL, librg_option_get(LIBRG_NETWORK_MESSAGE_CHANNEL), true, data, size);
}
librg_inline void librg_message_send_instream_except(librg_ctx *ctx, librg_message_id id, librg_entity_id entity_id, librg_peer *except, void *data, usize size) {
librg_message_sendex_instream(ctx, id, entity_id, except, librg_option_get(LIBRG_NETWORK_MESSAGE_CHANNEL), true, data, size);
}
#endif
// =======================================================================//
// !
// ! Helper methods
// !
// =======================================================================//
#if 1
f64 librg_time_now(librg_ctx *ctx) {
librg_assert(ctx);
if (librg_is_server(ctx)) {
return zpl_time_now();
}
return zpl_time_now() - ctx->timesync.start_time + ctx->timesync.offset_time;
}
f64 librg_standard_deviation(f64 *values, usize size) {
f64 sum = 0.0, mean = 0.0, deviation = 0.0;
for (isize i = 0; i < size; i++) { sum += values[i]; }
mean = sum / (f64)size;
for (isize i = 0; i < size; i++) {
deviation += zpl_pow(values[i] - mean, 2);
}
return zpl_sqrt(deviation / (f64)size);
}
#endif
// =======================================================================//
// !
// ! Time-syncer
// !
// =======================================================================//
#if 1
void librg__timesync_tick(void *usrptr) {
librg_send((librg_ctx *)usrptr, LIBRG_CONNECTION_TIMESYNC, data, {
librg_data_wf64(&data, zpl_time_now());
});
}
void librg__timesync_start(librg_ctx *ctx) {
if (librg_is_client(ctx)) {
zpl_zero_item(ctx->timesync.history);
ctx->timesync.median = 0.0;
ctx->timesync.offset_time = 0.0;
ctx->timesync.start_time = 0.0;
zpl_timer_set(ctx->timesync.timer, 5.0, -1, librg__timesync_tick);
zpl_timer_start(ctx->timesync.timer, 0);
}
}
void librg__timesync_stop(librg_ctx *ctx) {
if (librg_is_client(ctx) && ctx->timesync.timer && ctx->timesync.timer->enabled) {
zpl_timer_stop(ctx->timesync.timer);
}
}
#endif
// =======================================================================//
// !
// ! Update buffer
// !
// =======================================================================//
#if 1
void librg__buffer_init(librg_ctx *ctx, usize size) {
if (librg_is_server(ctx)) return;
zpl_ring_librg_snapshot_init(&ctx->buffer, ctx->allocator, size);
ctx->buffer_size = size;
zpl_timer_set(ctx->buffer_timer, ctx->timesync.server_delay, -1, librg__buffer_tick);
zpl_timer_start(ctx->buffer_timer, 0);
}
void librg__buffer_free(librg_ctx *ctx) {
if (librg_is_server(ctx)) return;
if (!ctx->buffer_timer || !ctx->buffer_timer->enabled) return;
zpl_timer_stop(ctx->buffer_timer);
for (isize i = 0; i < ctx->buffer.capacity; ++i) {
zpl_mfree(ctx->buffer.buf[i].data);
}
zpl_ring_librg_snapshot_free(&ctx->buffer);
}
void librg__buffer_push(librg_ctx *ctx, f64 time, librg_peer *peer, void *data, usize size) {
librg_snapshot snap = { 0 };
snap.time = time;
snap.peer = peer;
snap.size = size;
snap.data = zpl_alloc_copy(zpl_heap(), data, size);
librg_assert(data && snap.data);
zpl_ring_librg_snapshot_append(&ctx->buffer, snap);
}
void librg__buffer_tick(void *usrptr) {
librg_ctx *ctx = (librg_ctx *)usrptr;
if (!zpl_ring_librg_snapshot_full(&ctx->buffer)) {
return;
}
librg_snapshot *snap = zpl_ring_librg_snapshot_get(&ctx->buffer);
f64 time_diff = ((ctx->buffer_size + 1) * ctx->timesync.server_delay);
// if current update if too old, just skip it, and call next one
if (snap->time < (librg_time_now(ctx) - time_diff)) {
librg_dbg("[dbg] librg__buffer_tick: dropping old update packet\n");
zpl_mfree(snap->data);
librg__buffer_tick((void *)ctx);
return;
}
librg_data data = {0}; {
data.rawptr = snap->data;
data.capacity = snap->size;
}
// skip our message id length and timestamp lenth
// TODO: copy not all the stuff but only needed (skiping those two)
librg_data_set_rpos(&data, sizeof(librg_message_id) + sizeof(f64));
librg_message msg = {0}; {
msg.id = LIBRG_ENTITY_UPDATE;
msg.ctx = ctx;
msg.data = &data;
msg.peer = snap->peer;
msg.packet = NULL;
}
ctx->messages[LIBRG_ENTITY_UPDATE](&msg);
zpl_mfree(snap->data);
}
#endif
// =======================================================================//
// !
// ! Default network callbacks
// !
// =======================================================================//
#if 1
/* Execution side: SHARED */
LIBRG_INTERNAL void librg__callback_connection_init(librg_message *msg) {
#define _LIBRG_METHOD "librg__callback_connection_init"
librg_dbg("[dbg] %s\n", _LIBRG_METHOD);
#if defined(LIBRG_DEBUG)
char my_host[16];
enet_address_get_host_ip(&msg->peer->address, my_host, 16);
librg_dbg("[dbg] %s: a new connection attempt at %s:%u.\n", _LIBRG_METHOD, my_host, msg->peer->address.port);
#endif
if (librg_is_client(msg->ctx)) {
librg_data data;
librg_data_init_size(&data, sizeof(u16) * 3);
librg_data_wu32(&data, librg_option_get(LIBRG_PLATFORM_ID));
librg_data_wu32(&data, librg_option_get(LIBRG_PLATFORM_BUILD));
librg_data_wu32(&data, librg_option_get(LIBRG_PLATFORM_PROTOCOL));
librg_data_wf64(&data, zpl_time_now());
librg_event event = {0}; {
event.peer = msg->peer;
event.data = &data;
event.flags = (LIBRG_EVENT_REJECTABLE | LIBRG_EVENT_REMOTE);
}
librg_event_trigger(msg->ctx, LIBRG_CONNECTION_REQUEST, &event);
if (librg_event_succeeded(&event)) {
librg_message_send_to(msg->ctx, LIBRG_CONNECTION_REQUEST,
msg->peer, data.rawptr, librg_data_get_wpos(&data)
);
}
librg_data_free(&data);
}
#undef _LIBRG_METHOD
}
/* Execution side: SERVER */
LIBRG_INTERNAL void librg__callback_connection_request(librg_message *msg) {
#define _LIBRG_METHOD "librg__callback_connection_request"
librg_dbg("[dbg] %s\n", _LIBRG_METHOD);
librg_data_read_safe(u32, platform_id, msg->data);
librg_data_read_safe(u32, platform_build, msg->data);
librg_data_read_safe(u32, platform_protocol, msg->data);
librg_data_read_safe(f64, client_time, msg->data);
b32 blocked = (platform_id != librg_option_get(LIBRG_PLATFORM_ID) || platform_protocol != librg_option_get(LIBRG_PLATFORM_PROTOCOL));
if (platform_build != librg_option_get(LIBRG_PLATFORM_BUILD)) {
librg_dbg("[dbg] NOTICE: librg platform build mismatch client %u, server: %u\n", platform_build, librg_option_get(LIBRG_PLATFORM_BUILD));
}
if (blocked) {
librg_dbg("[dbg] BLOCKED: our platform: %d %d, their platform: %d %d\n",
librg_option_get(LIBRG_PLATFORM_ID),
librg_option_get(LIBRG_PLATFORM_PROTOCOL),
platform_id, platform_protocol
);
}
librg_event event = {0}; {
event.peer = msg->peer;
event.data = msg->data;
event.flags = (LIBRG_EVENT_REJECTABLE | LIBRG_EVENT_REMOTE);
}
librg_event_trigger(msg->ctx, LIBRG_CONNECTION_REQUEST, &event);
if (librg_event_succeeded(&event) && !blocked) {
librg_entity *entity = librg_entity_create(msg->ctx, librg_option_get(LIBRG_DEFAULT_CLIENT_TYPE));
// assign default
entity->flags |= LIBRG_ENTITY_CLIENT;
entity->client_peer = msg->peer;
librg_table_init(&entity->last_snapshot, msg->ctx->allocator);
// add client peer to storage
librg_table_set(&msg->ctx->network.connected_peers, cast(u64)msg->peer, entity->id);
// send accept
librg_send_to(msg->ctx, LIBRG_CONNECTION_ACCEPT, msg->peer, librg_lambda(data), {
librg_data_wf32(&data, msg->ctx->tick_delay / 1000.0f);
librg_data_wf64(&data, client_time);
librg_data_wf64(&data, zpl_time_now());
librg_data_went(&data, entity->id);
});
event.data = NULL;
event.entity = entity;
event.flags = LIBRG_EVENT_LOCAL;
librg_event_trigger(msg->ctx, LIBRG_CONNECTION_ACCEPT, &event);
}
else {
librg_dbg("[dbg] librg__connection_refuse\n");
librg_send_to(msg->ctx, LIBRG_CONNECTION_REFUSE, msg->peer, librg_lambda(data), {
event.data = &data;
event.flags = LIBRG_EVENT_LOCAL;
librg_event_trigger(msg->ctx, LIBRG_CONNECTION_REFUSE, &event);
});
}
#undef _LIBRG_METHOD
}
/* Execution side: CLIENT */
LIBRG_INTERNAL void librg__callback_connection_refuse(librg_message *msg) {
librg_dbg("[dbg] librg__connection_refuse\n");
LIBRG_MESSAGE_TO_EVENT(event, msg);
librg_event_trigger(msg->ctx, LIBRG_CONNECTION_REFUSE, &event);
}
/* Execution side: CLIENT */
LIBRG_INTERNAL void librg__callback_connection_accept(librg_message *msg) {
librg_dbg("[dbg] librg__connection_accept\n");
f32 server_delay = librg_data_rf32(msg->data);
f64 client_diff = (zpl_time_now() - librg_data_rf64(msg->data)) / 2.0;
f64 server_time = librg_data_rf64(msg->data);
librg_entity_id entity = librg_data_rent(msg->data);
librg_entity *blob = &msg->ctx->entity.list[entity];
msg->ctx->entity.count++;
msg->ctx->network.connected = true;
blob->type = librg_option_get(LIBRG_DEFAULT_CLIENT_TYPE);
blob->flags = (LIBRG_ENTITY_ALIVE | LIBRG_ENTITY_CLIENT);
blob->position = zpl_vec3f_zero();
// add server peer to storage
librg_table_init(&msg->ctx->network.connected_peers, msg->ctx->allocator);
librg_table_set(&msg->ctx->network.connected_peers, cast(u64)msg->peer, entity);
// trigger damn events!
LIBRG_MESSAGE_TO_EVENT(event, msg); event.entity = blob;
librg_event_trigger(msg->ctx, LIBRG_CONNECTION_ACCEPT, &event);
librg__timesync_start(msg->ctx);
msg->ctx->timesync.start_time = zpl_time_now();
msg->ctx->timesync.offset_time = server_time + client_diff;
msg->ctx->timesync.server_delay = server_delay;
if (librg_option_get(LIBRG_NETWORK_BUFFER_SIZE) > 1) {
librg__buffer_init(msg->ctx, librg_option_get(LIBRG_NETWORK_BUFFER_SIZE));
}
}
/* Execution side: SHARED */
LIBRG_INTERNAL void librg__callback_connection_disconnect(librg_message *msg) {
librg_dbg("[dbg] librg__connection_disconnect; is timeout: %lld\n", (i64) msg->user_data);
/* we were connected, and now are disconnected */
if (librg_is_client(msg->ctx) && librg_is_connected(msg->ctx)) {
librg_entity_id *local = librg_table_get(&msg->ctx->network.connected_peers, cast(u64)msg->peer);
for (usize i = 0; i < msg->ctx->max_entities; ++i) {
if (!librg_entity_valid(msg->ctx, i)) continue;
/* skip our local entity from being inlcuded */
if (local && *local == i) {
/* reset controlled flag, otherwise it will be called while being disconnected */
librg_entity *blob = librg_entity_fetch(msg->ctx, i);
blob->flags &= ~LIBRG_ENTITY_CONTROLLED;
blob->control_generation = 0;
continue;
}
librg_event event = {0}; {
event.entity = librg_entity_fetch(msg->ctx, i);
event.flags = LIBRG_EVENT_LOCAL;
};
librg_event_trigger(msg->ctx, LIBRG_ENTITY_REMOVE, &event);
librg__world_entity_destroy(msg->ctx, i);
}
librg_event event = {0}; {
event.ctx = msg->ctx;
event.peer = msg->peer;
event.data = NULL;
event.entity = local ? librg_entity_fetch(msg->ctx, *local) : NULL;
event.flags = (LIBRG_EVENT_REJECTABLE);
event.user_data = msg->user_data;
};
if (msg->user_data) {
librg_event_trigger(msg->ctx, LIBRG_CONNECTION_TIMEOUT, &event);
}
librg_event_trigger(msg->ctx, LIBRG_CONNECTION_DISCONNECT, &event);
/* dont forget to clean up our entity*/
if (local) {
librg__world_entity_destroy(msg->ctx, *local);
}
librg_table_destroy(&msg->ctx->network.connected_peers);
librg__timesync_stop(msg->ctx);
if (librg_option_get(LIBRG_NETWORK_BUFFER_SIZE) > 1) {
librg__buffer_free(msg->ctx);
}
msg->ctx->network.connected = false;
}
/* we were NOT connected, and now are disconnected */
else if (librg_is_client(msg->ctx) && !librg_is_connected(msg->ctx)) {
librg_event event = {0}; {
event.ctx = msg->ctx;
event.peer = NULL;
event.data = NULL;
event.entity = NULL;
event.flags = (LIBRG_EVENT_REJECTABLE);
};
librg_event_trigger(msg->ctx, LIBRG_CONNECTION_TIMEOUT, &event);
}
/* someone was connected, and now is disconnected */
else if (librg_is_server(msg->ctx)) {
librg_entity_id *entity = librg_table_get(&msg->ctx->network.connected_peers, cast(u64)msg->peer);
if (entity && librg_entity_valid(msg->ctx, *entity)) {
librg_entity *blob = librg_entity_fetch(msg->ctx, *entity);
librg_event event = {0}; {
event.peer = msg->peer;
event.data = msg->data;
event.entity = blob;
event.flags = (LIBRG_EVENT_REJECTABLE | LIBRG_EVENT_REMOTE);
}
if (msg->user_data) {
librg_event_trigger(msg->ctx, LIBRG_CONNECTION_TIMEOUT, &event);
}
librg_event_trigger(msg->ctx, LIBRG_CONNECTION_DISCONNECT, &event);
librg_entity_destroy(msg->ctx, *entity);
}
}
}
/* Execution side: SHARED */
/* Based on: http://www.mine-control.com/zack/timesync/timesync.html */
void librg__callback_connection_timesync(librg_message *msg) {
#define _LIBRG_METHOD "librg__callback_connection_timesync"
if (librg_is_server(msg->ctx)) {
librg_data_read_safe(f64, client_time, msg->data);
f64 server_time = librg_time_now(msg->ctx);
librg_send_to(msg->ctx, LIBRG_CONNECTION_TIMESYNC, msg->peer, librg_lambda(data), {
librg_data_wf64(&data, client_time);
librg_data_wf64(&data, server_time);
});
} else {
librg_ctx *ctx = msg->ctx;
f64 client_diff = (zpl_time_now() - librg_data_rf64(msg->data)) / 2.0;
f64 server_time = librg_data_rf64(msg->data);
// our first time sync packet, apply the value directly
if (ctx->timesync.history[LIBRG_TIMESYNC_SIZE - 1] == 0.0) {
ctx->timesync.start_time = zpl_time_now();
ctx->timesync.offset_time = server_time + client_diff;
}
// we are still collecting delta data
if (ctx->timesync.history[0] == 0.0) {
ctx->timesync.history[0] = client_diff;
zpl_sort(&ctx->timesync.history, LIBRG_TIMESYNC_SIZE, sizeof(f64), zpl_f64_cmp(0));
} else {
// we just finished collecting, this is our median
if (ctx->timesync.median == 0.0) {
ctx->timesync.median = ctx->timesync.history[LIBRG_TIMESYNC_SIZE / 2];
ctx->timesync.timer->duration = 60.0; /* slower our time update rate to 1 per minute */
}
// samples above approximately 1 standard-deviation from the median are discarded
f64 sample[2] = { ctx->timesync.median, client_diff };
if (librg_standard_deviation(sample, 2) > 1.0) {
return;
}
ctx->timesync.start_time = zpl_time_now();
ctx->timesync.offset_time = server_time + ctx->timesync.median;
}
}
#undef _LIBRG_METHOD
}
/* Execution side: CLIENT */
LIBRG_INTERNAL void librg__callback_entity_create(librg_message *msg) {
u32 query_size = librg_data_ru32(msg->data);
for (usize i = 0; i < query_size; ++i) {
librg_entity_id entity = librg_data_rent(msg->data);
u32 entity_type = librg_data_ru32(msg->data);
u8 control_generation = librg_data_ru8(msg->data);
zpl_vec3 position;
librg_data_rptr(msg->data, &position, sizeof(zpl_vec3));
// Create new entity on client side
librg_entity *blob = &msg->ctx->entity.list[entity]; librg_assert(blob); {
blob->type = entity_type;
blob->flags = LIBRG_ENTITY_ALIVE;
blob->position = position;
blob->control_generation = control_generation;
};
msg->ctx->entity.count++;
LIBRG_MESSAGE_TO_EVENT(event, msg); event.entity = blob;
librg_event_trigger(msg->ctx, LIBRG_ENTITY_CREATE, &event);
}
u32 remove_size = librg_data_ru32(msg->data);
for (usize i = 0; i < remove_size; ++i) {
librg_entity_id entity = librg_data_rent(msg->data);
if (librg_entity_valid(msg->ctx, entity)) {
LIBRG_MESSAGE_TO_EVENT(event, msg); {
event.entity = librg_entity_fetch(msg->ctx, entity);
};
librg_event_trigger(msg->ctx, LIBRG_ENTITY_REMOVE, &event);
librg__world_entity_destroy(msg->ctx, entity);
}
else {
librg_dbg("[dbg] unexpected entity %u on remove\n", entity);
}
}
}
/* Execution side: CLIENT */
LIBRG_INTERNAL void librg__callback_entity_update(librg_message *msg) {
u32 query_size = librg_data_ru32(msg->data);
for (usize i = 0; i < query_size; ++i) {
librg_entity_id entity = librg_data_rent(msg->data);
zpl_vec3 position;
librg_data_rptr(msg->data, &position, sizeof(position));
if (!librg_entity_valid(msg->ctx, entity)) {
continue;
}
librg_entity *blob = librg_entity_fetch(msg->ctx, entity);
blob->position = position;
LIBRG_MESSAGE_TO_EVENT(event, msg); event.entity = blob;
librg_event_trigger(msg->ctx, LIBRG_ENTITY_UPDATE, &event);
}
}
/* Execution side: CLIENT */
LIBRG_INTERNAL void librg__callback_entity_client_streamer_add(librg_message *msg) {
librg_entity_id entity = librg_data_rent(msg->data);
u8 received_generation = librg_data_ru8(msg->data);
if (!librg_entity_valid(msg->ctx, entity)) {
librg_dbg("[dbg] trying to add unknown entity to clientstream!\n");
return;
}
librg_entity *blob = librg_entity_fetch(msg->ctx, entity);
if (!(blob->flags & LIBRG_ENTITY_CONTROLLED)) {
blob->flags |= LIBRG_ENTITY_CONTROLLED;
blob->control_generation = received_generation;
LIBRG_MESSAGE_TO_EVENT(event, msg); event.entity = blob;
librg_event_trigger(msg->ctx, LIBRG_CLIENT_STREAMER_ADD, &event);
}
}
/* Execution side: SERVER */
LIBRG_INTERNAL void librg__callback_entity_client_streamer_update(librg_message *msg) {
#define _LIBRG_METHOD "librg__callback_entity_client_streamer_update"
librg_data_read_safe(u32, amount, msg->data);
for (usize i = 0; i < amount; i++) {
librg_data_read_safe(u32, entity, msg->data);
librg_data_read_safe(u8, control_generation, msg->data);
librg_data_read_safe(u32, size, msg->data);
librg_data_read_safe(f32, x, msg->data);
librg_data_read_safe(f32, y, msg->data);
librg_data_read_safe(f32, z, msg->data);
if (librg_data_capacity(msg->data) < librg_data_get_rpos(msg->data) + size) {
librg_dbg("[dbg] invalid packet size on client streamer update\n");
return;
}
#define LIBRG_LOCAL_ASSERT(cond, alert) \
if (!(cond)) { \
librg_data_set_rpos(msg->data, librg_data_get_rpos(msg->data) + size); \
librg_dbg(alert); continue; \
}
if (!librg_entity_valid(msg->ctx, entity)) {
librg_dbg("[dbg] invalid entity on client streamer update\n");
librg_data_set_rpos(msg->data, librg_data_get_rpos(msg->data) + size);
continue;
}
LIBRG_LOCAL_ASSERT(librg_entity_valid(msg->ctx, entity), "[dbg] client_streamer_update: invalid entity\n");
librg_entity *blob = librg_entity_fetch(msg->ctx, entity);
LIBRG_LOCAL_ASSERT(blob->flags & LIBRG_ENTITY_CONTROLLED, "[dbg] client_streamer_update: entity has no LIBRG_ENTITY_CONTROLLED flag\n");
LIBRG_LOCAL_ASSERT(blob->control_peer == msg->peer, "[dbg] client_streamer_update: entity controller peer and msg->peer are different\n");
LIBRG_LOCAL_ASSERT(!(blob->flags & LIBRG_ENTITY_CONTROL_REQUESTED), "[dbg] client_streamer_update: entity still has LIBRG_ENTITY_CONTROL_REQUESTED flag\n");
LIBRG_LOCAL_ASSERT(blob->control_generation == control_generation, "[dbg] client_streamer_update: control_generation is different\n");
LIBRG_LOCAL_ASSERT(blob->control_peer->state == ENET_PEER_STATE_CONNECTED, "[dbg] client_streamer_update: peer is no logner connected\n");
#undef LIBRG_LOCAL_ASSERT
blob->position.x = x;
blob->position.y = y;
blob->position.z = z;
LIBRG_MESSAGE_TO_EVENT(event, msg); event.entity = blob;
librg_event_trigger(msg->ctx, LIBRG_CLIENT_STREAMER_UPDATE, &event);
}
#undef _LIBRG_METHOD
}
/* Execution side: CLIENT */
LIBRG_INTERNAL void librg__callback_entity_client_streamer_remove(librg_message *msg) {
librg_entity_id entity = librg_data_rent(msg->data);
if (!librg_entity_valid(msg->ctx, entity)) {
librg_dbg("[dbg] trying to remove unknown entity from clientstream!\n");
return;
}
librg_entity *blob = librg_entity_fetch(msg->ctx, entity);
if (blob->flags & LIBRG_ENTITY_CONTROLLED) {
blob->flags &= ~LIBRG_ENTITY_CONTROLLED;
LIBRG_MESSAGE_TO_EVENT(event, msg); event.entity = blob;
librg_event_trigger(msg->ctx, LIBRG_CLIENT_STREAMER_REMOVE, &event);
}
}
#endif
// =======================================================================//
// !
// ! World methods (TODO)
// !
// =======================================================================//
#if 1
/**
* CLIENT-SIDE
*
* Responsive for updating the client side streamer
*/
librg_internal void librg__execute_client_update(librg_ctx *ctx) {
u32 amount = 0;
librg_data data;
librg_data_init(&data);
librg_data_wmid(&data, LIBRG_CLIENT_STREAMER_UPDATE);
librg_data_wu32(&data, 0); // amount of entities to be sent (updates)
librg_entity_iteratex(ctx, LIBRG_ENTITY_CONTROLLED, librg_lambda(entity), {
librg_entity *blob = librg_entity_fetch(ctx, entity);
librg_data subdata = {0};
librg_data_init(&subdata);
librg_event event = {0}; {
event.entity = blob;
event.data = &subdata;
event.flags = (LIBRG_EVENT_REJECTABLE | LIBRG_EVENT_LOCAL);
}
librg_event_trigger(ctx, LIBRG_CLIENT_STREAMER_UPDATE, &event);
// check if user rejected the event
if (!(event.flags & LIBRG_EVENT_REJECTED)) {
librg_data_went(&data, entity);
librg_data_wu8(&data, blob->control_generation);
librg_data_wu32(&data, librg_data_get_wpos(&subdata));
librg_data_wptr(&data, &blob->position, sizeof(zpl_vec3));
// write sub-bitstream to main bitstream
librg_data_wptr(&data, subdata.rawptr, librg_data_get_wpos(&subdata));
amount++;
}
librg_data_free(&subdata);
});
if (amount < 1) {
librg_data_free(&data);
return;
}
// write amount after packet id
librg_data_wu32_at(&data, amount, sizeof(librg_message_id));
enet_peer_send(ctx->network.peer, librg_option_get(LIBRG_NETWORK_SECONDARY_CHANNEL),
enet_packet_create(data.rawptr, librg_data_get_wpos(&data), 0)
);
librg_data_free(&data);
}
/**
* SERVER-SIDE
*
* Responsive for updating the server-side streamer
*/
librg_internal void librg__execute_server_entity_update(librg_ctx *ctx) {
librg_assert(ctx);
// use preallocated data stream objects
librg_data *reliable = &ctx->stream_upd_reliable;
librg_data *unreliable = &ctx->stream_upd_unreliable;
for (usize j = 0; j < ctx->max_entities; j++) {
librg_entity *blob = &ctx->entity.list[j];
if (!(blob->flags & LIBRG_ENTITY_CLIENT)) continue;
// assume that entity is valid, having the client
librg_entity_id player = j;
// get old, and preapre new snapshot handlers
librg_table *last_snapshot = &blob->last_snapshot;
librg_table next_snapshot = { 0 };
librg_table_init(&next_snapshot, ctx->allocator);
// fetch entities in the steram zone
zpl_array(librg_entity_id) queue;
usize queue_count = librg_entity_query(ctx, player, &queue);
u32 created_entities = 0;
u32 updated_entities = cast(u32)queue_count;
u32 removed_entities = 0;
// write packet headers
librg_data_wmid(reliable, LIBRG_ENTITY_CREATE);
librg_data_wu32(reliable, created_entities);
librg_data_wmid(unreliable, LIBRG_ENTITY_UPDATE);
librg_data_wf64(unreliable, zpl_time_now());
librg_data_wu32(unreliable, updated_entities);
// add entity creates and updates
for (usize i = 0; i < queue_count; ++i) {
librg_entity_id entity = cast(librg_entity_id)queue[i];
// fetch value of entity in the last snapshot
u32 *existed_in_last = librg_table_get(last_snapshot, entity);
librg_entity *eblob = librg_entity_fetch(ctx, entity);
/* to additionally prevent issue with entity updating after removal */
if (eblob->flags & LIBRG_ENTITY_MARKED_REMOVAL) {
updated_entities--;
continue;
}
// write create
if (entity != player && !existed_in_last) {
updated_entities--;
// skip entity create if this is player's entity
if (entity == player) continue;
// increase write amount for create counter
created_entities++;
// save write size before writing stuff
// (in case we will need reject the event)
u32 curr_wsize = librg_data_get_wpos(reliable);
// write all basic data
librg_data_went(reliable, entity);
librg_data_wu32(reliable, eblob->type);
librg_data_wu8(reliable, eblob->control_generation);
librg_data_wptr(reliable, &eblob->position, sizeof(eblob->position));
// request custom data from user
librg_event event = {0}; {
event.peer = blob->client_peer;
event.data = reliable;
event.entity = eblob;
event.flags = (LIBRG_EVENT_REJECTABLE | LIBRG_EVENT_LOCAL);
}
librg_event_trigger(ctx, LIBRG_ENTITY_CREATE, &event);
// check if event was rejected
if (event.flags & LIBRG_EVENT_REJECTED) {
created_entities--;
librg_data_set_wpos(reliable, curr_wsize);
}
}
else {
// mark entity as still alive, for the remove cycle
librg_table_set(last_snapshot, entity, 0);
// if this entity is client streamable and this client is owner
if (eblob && (eblob->flags & LIBRG_ENTITY_CONTROLLED) && (!(eblob->flags & LIBRG_ENTITY_CONTROL_REQUESTED)) && eblob->control_peer == blob->client_peer) {
updated_entities--;
}
// write update
else {
// save write size before writing stuff
// (in case we will need reject the event)
u32 curr_wsize = librg_data_get_wpos(unreliable);
librg_data_went(unreliable, entity);
librg_data_wptr(unreliable, &eblob->position, sizeof(eblob->position));
// request custom data from user
librg_event event = { 0 }; {
event.peer = blob->client_peer;
event.data = unreliable;
event.entity = eblob;
event.flags = (LIBRG_EVENT_REJECTABLE | LIBRG_EVENT_LOCAL);
}
librg_event_trigger(ctx, LIBRG_ENTITY_UPDATE, &event);
// check if event was rejected
if (event.flags & LIBRG_EVENT_REJECTED) {
updated_entities--;
librg_data_set_wpos(unreliable, curr_wsize);
}
}
}
// mark entity as existed for the next update
librg_table_set(&next_snapshot, entity, 1);
}
// write our calcualted amounts right after packet id (from the beginning)
librg_data_wu32_at(reliable, created_entities, sizeof(librg_message_id));
librg_data_wu32_at(unreliable, updated_entities, sizeof(librg_message_id) + sizeof(f64));
// save pos for remove data counter
usize write_pos = librg_data_get_wpos(reliable);
librg_data_wu32(reliable, 0);
// add entity removes
for (isize i = 0; i < zpl_array_count(last_snapshot->entries); ++i) {
librg_entity_id entity = (librg_entity_id)last_snapshot->entries[i].key;
// check if entity existed before
b32 not_existed = last_snapshot->entries[i].value;
if (not_existed == 0) continue;
// skip entity delete if this is player's entity
if (entity == player) continue;
// save write size before writing stuff
// (in case we will need reject the event)
u32 curr_wsize = librg_data_get_wpos(reliable);
// write id
librg_data_went(reliable, entity);
removed_entities++;
// write the rest
librg_event event = { 0 }; {
event.peer = blob->client_peer;
event.data = reliable;
event.entity = &ctx->entity.list[entity];
event.flags = (LIBRG_EVENT_REJECTABLE | LIBRG_EVENT_LOCAL);
}
librg_event_trigger(ctx, LIBRG_ENTITY_REMOVE, &event);
// check if even was rejected
if (event.flags & LIBRG_EVENT_REJECTED) {
removed_entities--;
librg_data_set_wpos(reliable, curr_wsize);
}
}
// write remove amount
librg_data_wu32_at(reliable, removed_entities, write_pos);
// swap snapshot tables
librg_table_destroy(&blob->last_snapshot);
*last_snapshot = next_snapshot;
// send the data, via differnt channels and reliability setting
if (librg_data_get_wpos(reliable) > (sizeof(librg_message_id) + sizeof(u32) * 2)) {
enet_peer_send(blob->client_peer, librg_option_get(LIBRG_NETWORK_PRIMARY_CHANNEL),
enet_packet_create(reliable->rawptr, librg_data_get_wpos(reliable), ENET_PACKET_FLAG_RELIABLE)
);
}
enet_peer_send(blob->client_peer, librg_option_get(LIBRG_NETWORK_SECONDARY_CHANNEL),
enet_packet_create(unreliable->rawptr, librg_data_get_wpos(unreliable), 0)
);
// and cleanup
librg_data_reset(reliable);
librg_data_reset(unreliable);
}
}
#endif
// =======================================================================//
// !
// ! World Spaces (TODO)
// !
// =======================================================================//
#if 1
b32 librg__space_bounds_small_enough(zpl_aabb3 a, zpl_vec3 b) {
//TODO(zaklaus): Is this the best way we can determine bounds for k-d ?
return a.half_size.x <= b.x && a.half_size.y <= b.y && a.half_size.z <= b.z;
}
zpl_global f32 librg__space_tpl[][3] = {
{-1.0f, -1.0f, -1.0f},
{+1.0f, -1.0f, -1.0f},
{-1.0f, +1.0f, -1.0f},
{+1.0f, +1.0f, -1.0f},
{-1.0f, -1.0f, +1.0f},
{+1.0f, -1.0f, +1.0f},
{-1.0f, +1.0f, +1.0f},
{+1.0f, +1.0f, +1.0f},
};
void librg__space_split(librg_space *c) {
zpl_aabb3 hd = c->boundary;
for (i32 i = 0; i < c->dimensions; ++i) {
hd.half_size.e[i] /= 2.0f;
}
i32 loops = 4;
if (c->dimensions == LIBRG_SPACE_3D)
loops = 8;
f32 p[3] = {0};
for (i32 i = 0; i < loops; ++i) {
librg_space space = {0};
zpl_aabb3 bounds = {0};
p[0] = c->boundary.centre.e[0] + hd.half_size.e[0]*librg__space_tpl[i][0];
p[1] = c->boundary.centre.e[1] + hd.half_size.e[1]*librg__space_tpl[i][1];
p[2] = c->boundary.centre.e[2] + hd.half_size.e[2]*librg__space_tpl[i][2];
zpl_memcopy(bounds.centre.e, p, 3 * sizeof(f32));
bounds.half_size = hd.half_size;
space.boundary = bounds;
space.min_bounds = c->min_bounds;
space.use_min_bounds = c->use_min_bounds;
space.max_nodes = c->max_nodes;
space.dimensions = c->dimensions;
space.allocator = c->allocator;
zpl_array_append(c->spaces, space);
}
}
b32 librg__space_remove_node(librg_space *c, librg_entity *tag) {
if (c->nodes == NULL) return false;
for (i32 i = 0; i < zpl_array_count(c->nodes); ++i) {
librg_space_node *node = &c->nodes[i];
if (node->blob == tag) {
if (node->unused) return false;
if (c->free_nodes == NULL) {
zpl_array_init_reserve(c->free_nodes, c->allocator, c->max_nodes);
}
zpl_array_append(c->free_nodes, i);
node->unused = true;
return true;
}
}
return false;
}
void librg__space_init(librg_space *c, zpl_allocator a, isize dims, zpl_aabb3 bounds, zpl_vec3 min_bounds, u32 max_nodes) {
librg_space c_ = {0};
*c = c_;
c->allocator = a;
c->boundary = bounds;
c->min_bounds = min_bounds;
c->max_nodes = max_nodes;
c->dimensions = dims;
c->use_min_bounds = zpl_vec3_mag(min_bounds) > 0.0f;
}
void librg__space_clear(librg_space *c) {
// TODO(ZaKlaus): Support more allocators?
if (c->allocator.proc == zpl_arena_allocator_proc) {
zpl_free_all(c->allocator);
c->nodes = NULL;
c->spaces = NULL;
return;
}
if (c->nodes) {
zpl_array_free(c->nodes);
c->nodes = NULL;
}
if (!c->spaces) return;
isize spaces_count = zpl_array_count(c->spaces);
for (i32 i = 0; i < spaces_count; ++i) {
librg__space_clear((c->spaces+i));
}
zpl_array_free(c->spaces);
if (c->free_nodes) zpl_array_free(c->free_nodes);
c->spaces = NULL;
c->free_nodes = NULL;
}
librg_space *librg__space_insert(librg_ctx *ctx, librg_space *space, librg_space_node node) {
if (!librg__space_contains(space->dimensions, space->boundary, node.blob->position.e)) return NULL;
if (space->nodes == NULL) {
zpl_array_init(space->nodes, space->allocator);
}
if (space->free_nodes && zpl_array_count(space->free_nodes) > 0) {
node.unused = false;
space->nodes[space->free_nodes[zpl_array_count(space->free_nodes)-1]] = node;
zpl_array_pop(space->free_nodes);
return space;
}
if ((usize)zpl_array_count(space->nodes) < space->max_nodes) {
insert:
zpl_array_append(space->nodes, node);
return space;
}
if (space->use_min_bounds && librg__space_bounds_small_enough(space->boundary, space->min_bounds)) {
goto insert;
}
if (space->spaces == NULL) {
zpl_array_init(space->spaces, space->allocator);
}
isize spaces_count = zpl_array_count(space->spaces);
if (spaces_count == 0) {
librg__space_split(space);
}
spaces_count = zpl_array_count(space->spaces);
for (i32 i = 0; i < spaces_count; ++i) {
librg_space *sub = librg__space_insert(ctx, (space->spaces+i), node);
if (sub) return sub;
}
return NULL;
}
librg_inline void librg__execute_server_entity_insert(librg_ctx *ctx) {
librg_assert(ctx);
// fill up
librg_entity_iteratex(ctx, LIBRG_ENTITY_ALIVE, entity, {
librg_entity *blob = &ctx->entity.list[entity];
librg_space_node node = {0}; {
node.blob = blob;
}
if (blob->stream_branch == NULL) {
blob->stream_branch = librg__space_insert(ctx, &ctx->world, node);
}
else {
librg_space *branch = blob->stream_branch;
b32 contains = librg__space_contains(branch->dimensions, branch->boundary, blob->position.e);
if (!contains) {
librg__space_remove_node(branch, blob);
blob->stream_branch = librg__space_insert(ctx, &ctx->world, node);
}
}
});
}
librg_inline void librg__execute_server_entity_destroy(librg_ctx *ctx) {
for (isize i = 0; i < zpl_array_count(ctx->entity.remove_queue); i++) {
librg__world_entity_destroy(ctx, ctx->entity.remove_queue[i]);
}
zpl_array_clear(ctx->entity.remove_queue);
}
librg_inline void librg__execture_server_entity_control(librg_ctx *ctx) {
librg_entity_iteratex(ctx, LIBRG_ENTITY_CONTROL_REQUESTED, (entity), {
librg_entity_fetch(ctx, entity)->flags &= ~LIBRG_ENTITY_CONTROL_REQUESTED;
});
for (isize i = 0; i < zpl_array_count(ctx->entity.add_control_queue); i++) {
librg_message *msg = ctx->entity.add_control_queue[i];
enet_peer_send(msg->peer, librg_option_get(LIBRG_NETWORK_MESSAGE_CHANNEL), msg->packet);
zpl_free(ctx->allocator, msg);
}
zpl_array_clear(ctx->entity.add_control_queue);
}
librg_internal void librg__world_update(void *data) {
f64 start = zpl_time_now();
librg_ctx *ctx = (librg_ctx *)data;
librg_assert(ctx);
if (librg_is_server(ctx)) {
#if defined(LIBRG_FEATURE_OCTREE_CULLER)
librg__execute_server_entity_insert(ctx); /* create the server cull tree */
#endif
librg__execute_server_entity_update(ctx); /* create and send updates to all clients */
librg__execute_server_entity_destroy(ctx); /* destroy queued entities */
librg__execture_server_entity_control(ctx); /* send controll add for created entities */
} else {
librg__execute_client_update(ctx); /* send information about client updates */
}
ctx->last_update = zpl_time_now() - start;
}
void librg__world_entity_query(librg_ctx *ctx, librg_entity_id entity, librg_space *c, zpl_aabb3 bounds, librg_entity_id **out_entities) {
#if defined(LIBRG_FEATURE_OCTREE_CULLER)
if (c->nodes == NULL) return;
if (!librg__space_intersects(c->dimensions, c->boundary, bounds)) return;
#endif
b32 use_radius = librg_option_get(LIBRG_USE_RADIUS_CULLING);
librg_entity *ent_blob = librg_entity_fetch(ctx, entity);
librg_assert(ent_blob);
#if defined(LIBRG_FEATURE_OCTREE_CULLER)
isize nodes_count = zpl_array_count(c->nodes);
for (i32 i = 0; i < nodes_count; ++i) {
if (c->nodes[i].unused) continue;
librg_entity_id target = c->nodes[i].blob->id;
#else
for (u32 target = 0; target < ctx->max_entities; ++target) {
if (!librg_entity_valid(ctx, target)) { continue; }
if (target == entity) { continue; }
#endif
if (librg_entity_valid(ctx, target)) {
#if defined(LIBRG_FEATURE_OCTREE_CULLER)
librg_entity *blob = c->nodes[i].blob;
#else
librg_entity *blob = librg_entity_fetch(ctx, target);
#endif
#ifdef LIBRG_FEATURE_VIRTUAL_WORLDS
if (librg_entity_world_get(ctx, target) != librg_entity_world_get(ctx, entity)) continue;
#endif
#ifdef LIBRG_FEATURE_ENTITY_VISIBILITY
librg_visibility g_vis = librg_entity_visibility_get(ctx, target);
librg_visibility l_vis = librg_entity_visibility_get_for(ctx, entity, target);
/* relation visibility always has the highest priority */
if (l_vis == LIBRG_ALWAYS_INVISIBLE) {
continue;
}
/* global visibility has a lower priority */
if (g_vis == LIBRG_ALWAYS_INVISIBLE) {
continue;
}
#endif
b32 inside = false;
if (!use_radius) {
inside = librg__space_contains(c->dimensions, bounds, blob->position.e);
} else {
zpl_vec3 diff;
zpl_vec3_sub(&diff, ent_blob->position, blob->position);
inside = zpl_vec3_mag2(diff) < zpl_square(ent_blob->stream_range);
}
if (inside && !(blob->flags & LIBRG_ENTITY_MARKED_REMOVAL)) {
zpl_array_append(*out_entities, target);
}
}
}
#if defined(LIBRG_FEATURE_OCTREE_CULLER)
if(c->spaces == NULL) return;
isize spaces_count = zpl_array_count(c->spaces);
if (spaces_count == 0) return;
for (i32 i = 0; i < spaces_count; ++i) {
librg__world_entity_query(ctx, entity, (c->spaces+i), bounds, out_entities);
}
#endif
}
b32 librg__world_entity_destroy(librg_ctx *ctx, librg_entity_id id) {
librg_assert(ctx);
librg_assert(librg_entity_valid(ctx, id));
librg_assert(ctx->entity.count > 0);
ctx->entity.count--;
librg_entity *entity = &ctx->entity.list[id];
if (entity->flags & LIBRG_ENTITY_CLIENT) {
entity->client_peer = NULL;
librg_table_destroy(&entity->last_snapshot);
}
// remove entity from the streamer
if (entity->stream_branch) {
librg__space_remove_node(entity->stream_branch, entity);
}
if (entity->flags & LIBRG_ENTITY_QUERIED) {
zpl_array_free(entity->last_query);
}
#ifdef LIBRG_FEATURE_ENTITY_VISIBILITY
if (entity->flags & LIBRG_ENTITY_VISIBILITY) {
librg_table_destroy(&entity->visibility);
}
if (librg_is_server(ctx)) {
/* remove from global visibility */
librg_entity_visibility_set(ctx, entity->id, LIBRG_DEFAULT_VISIBILITY);
/* remove from relation visibilities */
librg_entity_iteratex(ctx, LIBRG_ENTITY_VISIBILITY, librg_lambda(relation), {
librg_entity_visibility_set_for(ctx, relation, entity->id, LIBRG_DEFAULT_VISIBILITY);
});
}
#endif
entity->flags = LIBRG_ENTITY_NONE;
entity->position = zpl_vec3f_zero();
entity->type = 0;
entity->stream_branch = NULL;
#ifdef LIBRG_FEATURE_VIRTUAL_WORLDS
entity->virual_world = 0;
#endif
return true;
}
#endif
// =======================================================================//
// !
// ! Options
// !
// =======================================================================//
/* Global option storage */
librg_global u32 librg_options[LIBRG_OPTIONS_SIZE] = {
/*LIBRG_PLATFORM_ID*/ 1,
/*LIBRG_PLATFORM_PROTOCOL*/ 1,
/*LIBRG_PLATFORM_BUILD*/ 1,
/*LIBRG_DEFAULT_CLIENT_TYPE*/ 0,
/*LIBRG_DEFAULT_STREAM_RANGE*/ 250,
/*LIBRG_DEFAULT_DATA_SIZE*/ 1024,
/*LIBRG_NETWORK_CAPACITY*/ 2048,
/*LIBRG_NETWORK_CHANNELS*/ 4,
/*LIBRG_NETWORK_PRIMARY_CHANNEL*/ 1,
/*LIBRG_NETWORK_SECONDARY_CHANNEL*/ 2,
/*LIBRG_NETWORK_MESSAGE_CHANNEL*/ 3,
/*LIBRG_NETWORK_BUFFER_SIZE*/ 0,
/*LIBRG_MAX_ENTITIES_PER_BRANCH*/ 4,
/*LIBRG_USE_RADIUS_CULLING*/ 0,
};
void librg_option_set(u32 option, u32 value) {
librg_options[option] = value;
}
u32 librg_option_get(u32 option) {
return librg_options[option];
}
#undef LIBRG_MESSAGE_TO_EVENT
#ifdef __cplusplus
}
#endif
#endif // LIBRG_IMPLEMENTATION
#endif // LIBRG_INCLUDE_H