/** * 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 * * 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