diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..89f760d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Set default charset +charset = utf-8 + +# 4 space indentation +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore index a917762..126e6cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,15 @@ # Folders build/* node_modules/ +build-win/* + +# vs shit +*.dir/ +x64/ # Other .DS_store + +# local build scripts +build.sh +build.bat diff --git a/.travis.yml b/.travis.yml index 6bd9b7f..4b1d05a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,21 @@ language: c -compiler: - - clang - - gcc before_install: - - curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - - - sudo apt-get install -y nodejs + - curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - + - sudo apt-get install -y nodejs + +after_failure: + - curl -v -H User-Agent:bot -H Content-Type:application/json -d '{"content":"Latest build has FAILED!"}' https://discordapp.com/api/webhooks/$DISCORD_WEBHOOK_CHANNEL_ID/$DISCORD_WEBHOOK_TOKEN + +after_success: + - curl -v -H User-Agent:bot -H Content-Type:application/json -d '{"content":"Latest build has SUCCEEDED!"}' https://discordapp.com/api/webhooks/$DISCORD_WEBHOOK_CHANNEL_ID/$DISCORD_WEBHOOK_TOKEN script: - - npm install - - cmake -DLIBRG_TEST=1 - - make - - ./librg_test + - npm install + - mkdir -p build + - export INCLUDES="-I include -I node_modules/zpl.c/include -I node_modules/zpl_math.c/include -I node_modules/zpl_cull.c/include -I node_modules/zpl_event.c/include -I node_modules/enet.c/include -DHAS_SOCKLEN_T=1" + - export LINKER="-pthread -lm -ldl" + - gcc -g -std=c99 $INCLUDES test/build-test.c $LINKER -o build/test-gcc-c.o && build/test-gcc-c.o + - clang -g -std=c99 $INCLUDES test/build-test.c $LINKER -o build/test-clang-c.o && build/test-clang-c.o + - g++ -g -std=c++11 $INCLUDES test/build-test.cpp $LINKER -o build/test-gpp-cpp.o && build/test-gpp-cpp.o + - clang++ -g -std=c++11 $INCLUDES test/build-test.cpp $LINKER -o build/test-clang-cpp.o && build/test-clang-cpp.o diff --git a/CMakeLists.txt b/CMakeLists.txt index e0a7e4f..1084d12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,10 +13,6 @@ else() set(LIBRG_VENDOR_FOLDER ${CMAKE_SOURCE_DIR}/${LIBRG_VENDOR_FOLDER}) endif() -add_subdirectory( - ${LIBRG_VENDOR_FOLDER}/enet${LIBRG_POSTFIX} - ${CMAKE_CURRENT_BINARY_DIR}/enet) - # define librg as a library (not really) add_library(librg INTERFACE) @@ -27,6 +23,61 @@ else() target_compile_options(librg INTERFACE -std=c99) endif() +### ENET STUFF +# The "configure" step. +include(CheckFunctionExists) +include(CheckStructHasMember) +include(CheckTypeSize) +check_function_exists("fcntl" HAS_FCNTL) +check_function_exists("poll" HAS_POLL) +check_function_exists("getaddrinfo" HAS_GETADDRINFO) +check_function_exists("getnameinfo" HAS_GETNAMEINFO) +check_function_exists("gethostbyname_r" HAS_GETHOSTBYNAME_R) +check_function_exists("gethostbyaddr_r" HAS_GETHOSTBYADDR_R) +check_function_exists("inet_pton" HAS_INET_PTON) +check_function_exists("inet_ntop" HAS_INET_NTOP) +check_struct_has_member("struct msghdr" "msg_flags" "sys/types.h;sys/socket.h" HAS_MSGHDR_FLAGS) +set(CMAKE_EXTRA_INCLUDE_FILES "sys/types.h" "sys/socket.h") +check_type_size("socklen_t" HAS_SOCKLEN_T BUILTIN_TYPES_ONLY) +unset(CMAKE_EXTRA_INCLUDE_FILES) +if(MSVC) + add_definitions(-W3) +else() + add_definitions(-Wno-error) +endif() + +if(HAS_FCNTL) + add_definitions(-DHAS_FCNTL=1) +endif() +if(HAS_POLL) + add_definitions(-DHAS_POLL=1) +endif() +if(HAS_GETNAMEINFO) + add_definitions(-DHAS_GETNAMEINFO=1) +endif() +if(HAS_GETADDRINFO) + add_definitions(-DHAS_GETADDRINFO=1) +endif() +if(HAS_GETHOSTBYNAME_R) + add_definitions(-DHAS_GETHOSTBYNAME_R=1) +endif() +if(HAS_GETHOSTBYADDR_R) + add_definitions(-DHAS_GETHOSTBYADDR_R=1) +endif() +if(HAS_INET_PTON) + add_definitions(-DHAS_INET_PTON=1) +endif() +if(HAS_INET_NTOP) + add_definitions(-DHAS_INET_NTOP=1) +endif() +if(HAS_MSGHDR_FLAGS) + add_definitions(-DHAS_MSGHDR_FLAGS=1) +endif() +if(HAS_SOCKLEN_T) + add_definitions(-DHAS_SOCKLEN_T=1) +endif() +### END ENET STUFF + # proxy our includes to outside world target_include_directories(librg INTERFACE include ${LIBRG_VENDOR_FOLDER}/zpl${LIBRG_POSTFIX}/include @@ -37,12 +88,11 @@ target_include_directories(librg INTERFACE include ${LIBRG_VENDOR_FOLDER}/enet${LIBRG_POSTFIX}/include) # link all the deps -target_link_libraries(librg INTERFACE - enet ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(librg INTERFACE ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) if (WIN32) # windows libraries for enet - target_link_libraries(librg INTERFACE ws2_32 winmm) + target_link_libraries(librg INTERFACE Ws2_32 Winmm) elseif (UNIX) # unix math library for us target_link_libraries(librg INTERFACE m) @@ -60,8 +110,69 @@ if (LIBRG_DEMO) target_link_libraries(librg_demo_client librg SDL2) endif() +if (LIBRG_CLI_TEST) + add_executable(librg_cli_server test/cli-server.c) + add_executable(librg_cli_client test/cli-client.c) + + target_link_libraries(librg_cli_server librg) + target_link_libraries(librg_cli_client librg) +endif() + # test for travis/etc if (LIBRG_TEST) add_executable(librg_test test/build-test.c) target_link_libraries(librg_test librg) endif() + +# static library +if (LIBRG_STATIC) + # special flags for MSVC + if (MSVC OR "${CMAKE_GENERATOR}" MATCHES "(Win64|IA64)") + + # This piece of crap could be very well replaced by something + # like replace(ARRAY PRED VALUE) + # But instead, we get to use this. Uh! + set(CompilerFlags + CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELEASE + CMAKE_C_FLAGS + CMAKE_C_FLAGS_DEBUG + CMAKE_C_FLAGS_RELEASE + ) + foreach(CompilerFlag ${CompilerFlags}) + string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}") + endforeach() + endif() + + add_definitions(-DLIBRG_STATIC) + + # Once again, one has to hack in cmake to achieve what he needs... + add_library(librg_static STATIC test/library.c) + + # This doesn't actually make any significant difference, only + # makes sure the includes are further propagated. + target_link_libraries(librg_static librg) +endif() + +# shared library +if (LIBRG_SHARED) + add_definitions(-DLIBRG_SHARED -DENET_DLL) + add_library(librg_shared SHARED test/library.c) + + # if (WIN32) + # set_target_properties(librg_shared PROPERTIES + # LINK_FLAGS "/WHOLEARCHIVE" + # ) + # elseif (APPLE) + # set_target_properties(librg_shared PROPERTIES + # LINK_FLAGS "-Wl,-all_load" + # ) + # else () + # set_target_properties(librg_shared PROPERTIES + # LINK_FLAGS "-Wl,--whole-archive" + # ) + # endif () + + target_link_libraries(librg_shared librg) +endif() diff --git a/README.md b/README.md index 9d5702e..3d03314 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ NPM version Dependencies IRC server - Discord server + Discord server license @@ -25,323 +25,249 @@ -## Table of Contents - -* [Purpose](#purpose) -* [Use-cases](#use-cases) -* [Features](#features) -* [Plans](#plans) -* [How it works](#how-it-works) - * [General](#general) - * [Server streaming](#server-streaming) - * [Client streaming](#client-streaming) - * [Custom data](#custom-data) -* [Structure](#structure) - * [List](#list) - * [Information](#information) - * [Build instructions](#build-instructions) -* [Installation](#installation) -* [Examples](#examples) - * [Server](#server) - * [Client](#client) - * [Additional](#additional) -* [Testing](#testing) -* [Contributors](#contributors) -* [License](#license) - ## Desciption -Essentially, librg is not an network library, as you might think from the first glimpse. -It contains entity-component system, bundled with event system which are both connected to network library. All this connected together, and mixed with thought-out event flow, gives you a simple way of creating something. It feels not just like a library, but like an architectural framework. - -## Purpose +Have you ever wondered why is it always so hard to make a multi-player game, even the most basic one? +And the complexity even increases with bigger amounts of players and things you need to sync between your clients. +This library is designed exactly to solve that problem. It has a simple interface with highly flexible base which allows you to build great things! -Many people think that implementing networking solution for your game project is the most complicated and time consuming task   +Many people think that implementing networking solution for your game project is the most complicated and time consuming task. We believe many people are afraid to even try, which results in the fact, that you almost never see small games made by indie developers having any type of networking involved. Thus we hope that with this library, which is just a small step in the direction, we might help anyone and everyone who wants to add a multi-player capabilities inside one's game. -## Use-cases - -* As "built-in" server: - Server can be attached to the client and run as a subprocess. It will result in a quite common concept of client-hosted game sessions for **friends**. - However it requires user, or you, to deal with stuff like public ip/visibility. - -* As proxy server: - Would you like to have a **dedicated server** solution? Server will be used as a proxy to store and send accumulated player data. - Considering the fact that you don't have (almost) any game logic on the server side, it can handle big amounts of clients. - Great example can be something like [San-Andreas Multiplayer](http://www.sa-mp.com/). - -* As thin client (advanced): - For client side, you will use thin-client "pattern". All user actions will be sent directly to the server. - Requires you to write all the server side systems, like physics handling, game world management, and **gameloop on the server side**. - -* Any other possible way that was not mentioned. - ## Features -* simple to use **entity-component** system -* fast and performant **event** system -* convenient interface for binary data reading/writing (**bitstream**) +* high performance +* minimalistic interface +* server-side network culling +* client-side entity streaming +* fast and performant event system * highly configurable, both at compile- and run-time -* high performant **server-side network-culling**, or in other words **"streaming"** -* single header-only file format (however it has some [dependencies](#structure)) +* lightweight, minimal dependencies, single-header * written in C99 (portability reasons) -* small (only 1160LOC) -* ready for C/C++ projects -* CMake support -* npm support (for dependency management) - -## Plans - -We are currently fixing issues on different platforms and possibly adding small features. However, the main idea for library and it's interface is done. -In the future, we would like to see this library being used in different platforms and environments. - -For example, creating bindings for different platforms and languages (e.g `librg-love2d` or `librg-csharp`) would be useful. -If you want to get involved - please contact us, we would gladly answer your questions and provide you support with a repository reserved in our organisation. - +* npm support, cmake support ## How it works #### General -General concept is very simple. Both server and client operate on an array of entities Entities have attached **components**. -User can create his own components and attach them onto entities. - -librg entities have few default components, like: `transform`, `streamable`, `client` (for client) and `clientstream` (for entities which are controlled by client). -You can find more information about those by checking out the source header file. +As we've mentioned before, librg has a simple interface. Like many C libraries, it has some sort of application context, which makes it thread-safe and offers the ability to run multiple instances under the same process. Here is sample server code: -#### Server streaming +```c +int main(int argc, char const *argv[]) { + librg_ctx_t ctx = { 0 }; + librg_init(&ctx); -Server always has all the entities in the game world. Clients will have only **snapshot** of the current world, which is limited by the **stream-range**. -Client will receive periodic updates from the server, containing information about the world (snaphot). + librg_address_t addr = { 27010 }; + librg_network_start(&ctx, addr); -When entity receives an entity which was not in his stream zone for the first time, the `LIBRG_ENTITY_CREATE` event will be called, -there you can create your in-game object, attach a component with custom data, etc. -You should consider sending as much data as possible about the entity on it's creation, for example: color, hair-style, vehicle model id, etc. + bool running = true; + while (running) { + librg_tick(&ctx); + } -If entity already exists in the client's local game world, `LIBRG_ENTITY_UPDATE` event will be called. (Note: it will be called each time server sends update, and entity is still in the stream-zone). -It's recommended that you send as **less data** as possible in the update, otherwise you will be polluting the network. + librg_network_stop(&ctx); + librg_free(&ctx); + return 0; +} +``` -At last, if entity is no longer in the client's stream-zone the `LIBRG_ENTITY_REMOVE` event will be triggered. There you should remove previously created in-game object, -detach all components, and de-allocate all your game data related to that entity (or cache it, it's your call). +#### Events -#### Client streaming +Everything is built around events, something gets created - the related event gets triggered. Your task as a developer is to use these events and write appropriate handlers. -If you want your client to send updates about one or more entities to the server. -For example, you have a huge game world (e.g. open world sandbox game), where there are some entities which are not players, however you need someone to control them. -Considering the fact that you probably don't have any game logic on the server, you need one of your clients to send updates about ingame entities to other clients. +Let's look at the example, client connects to the server, spawns on the map, and librg triggers `LIBRG_ENTIY_CREATE` event for every entity in the player's range: -This can be achieved quite easily, you can just call method `librg_streamer_client_set` on the server, for specific entity. -It will make provided **client responsive** for streaming this entity to the server. +```c +void mygame_entity_create(librg_event_t *event) { + int entity_id = event->entity->id; + int entity_type = event->entity->type; + vec3 position = event->entity->position; + + // and call your game method to spawn entity in the game world + MyEntity *entity = MyGame_CreateEntity(entity_type, position); + myEntities[entity_id] = entity; +} -Now, what you need to do is just update that entity components like `transform` and so on from your local client's game world. And that `transform` will be automatically sent to the server. + // ... + // register the handler + librg_event_add(&ctx, LIBRG_ENTITY_CREATE, mygame_entity_create); + // ... +``` +This way, the client on his side creates all entities that server tells him to create. +And the best part is that, server will tell only about those entities that are in the specified range. +This allows you to have bigger amounts of synced data, using the same amount of bandwidth. -#### Custom data +Same concept applies when your client's entity needs to be removed, you just register the `LIBRG_ENTITY_REMOVE` handler, and unspawn the entity from the game world. -Now, you probably have a question, `how do i send custom data?` +#### Server sync -That is also quite simple indeed. Before triggering, let's say `LIBRG_ENTITY_UPDATE` event on the client, -same event will be triggered on the server, allowing you to write something in the `data` parameter. +What if you need, (and most likely you do), to send some data, on every server tick, like object position or rotation, or maybe even object color. -For example: +You need to register a handler for the `LIBRG_ENTITY_UDPATE` event, but this time you need to do it on both sides! ```c -void on_entity_update(librg_event_t *event) { - librg_data_wu32(event->data, 11223345); // write unsigned long - librg_data_wptr(event->data, &mycomponent, sizeof(mycomponent)); // write data right from the struct +/* server side */ +void myserver_entity_update(librg_event_t *event) { + // change the position, it will be sent automatically + event->entity->position.x += 5.0f; + + // send some u32 numbers by manually writing to the stream + librg_data_wu32(event->data, 0); /* black color */ + librg_data_wu32(event->data, 300); /* speed is 300 */ } -``` -Now on the other side, you are required to read the data with in the same exact sequence: +/* client side */ +void myclient_entity_update(librg_event_t *event) { + int entity_id = event->entity->id; + MyEntity *entity = myEntities[entity_id]; -```c -void on_entity_update(librg_event_t *event) { - u32 mynumber = librg_data_ru32(&event->data); // read unsigned long + // position has been updated, we can set the ingame object's position + MyGame_SetPosition(entity, event->entity->position); - mycompoonent_t mycomponent; - librg_data_rptr(event->data, &mycomponent, sizeof(mycompoonent_t)); // read data right to the struct + // read u32 numbers manually from the stream + MyGame_SetColor(entity, librg_data_ru32(event->data)); + MyGame_SetSpeed(entity, librg_data_ru32(event->data)); } + ``` -This concept can be applied to all events that have been described above: `LIBRG_ENTITY_CREATE`, `LIBRG_ENTITY_UPDATE`, `LIBRG_ENTITY_REMOVE`, `LIBRG_CLIENT_STREAMER_REMOVE`, and some others. +Now, when you write data on the server, you just read it back on the client in the same order as it was written. That's it, you have a server streamed sync! -## Structure +#### Messages -The library is built with **single header-only file design**. So everything librg provides is located in [includes/librg.h](https://github.com/librg/librg/blob/master/include/librg.h) file. -However it has **dependencies**. Most of them are also single header-only libraries. +Considering you've already synced your server and clients after reading the previous section, you might want to trigger some actions/commands on the server, so it could become interactive. +And the way you can do it is quite simple, it is similar to events you are familiar already, so it should be an easy task: -### List -Current list and description of dependencies looks like this: - -| Library | Version | Description | -|:-:|:-:|---| -| [zpl](https://github.com/zpl-c/zpl) | ![](https://img.shields.io/npm/v/zpl.c.svg?maxAge=3600) | **zpl** is a basic library containing many functions you would use in everyday programming.| -| [zpl-cull](https://github.com/zpl-c/zpl-cull) | ![](https://img.shields.io/npm/v/zpl_cull.c.svg?maxAge=3600) | **zpl-cull** is culling library. Used to create an entity tree, and query objects based on that. | -| [zpl-math](https://github.com/zpl-c/zpl-math) | ![](https://img.shields.io/npm/v/zpl_math.c.svg?maxAge=3600) | **zpl-math** is obviously a math library. Used mostly in the streamer part. | -| [zpl-event](https://github.com/zpl-c/zpl-event) | ![](https://img.shields.io/npm/v/zpl_event.c.svg?maxAge=3600) | **zpl-event** is simple and yet powerful event library. Most of the public interfaces of librg are build using zpl-event. | -| [enet](https://github.com/zpl-c/enet) | ![](https://img.shields.io/npm/v/enet.c.svg?maxAge=3600) | **enet** is a quite popular high performant low-level network library. A core for the librg. | - -### Information -**enet** is the only dependency which has a multi-file structure and source parts (*.c files). So it should be compiled separately and linked together with your project. +```c +/* server side */ +void myserver_onmessage1(librg_message_t *msg) { + printf("we got message 1\n"); +} -### Build instructions -librg comes with a **[CMakeLists.txt](CMakeLists.txt)** file. You can use it to integrate the library inside your project. Or use it as a boilerplate for new project. -There is also the **[build.sh](build.sh)** shell script, which is usually used for the development purposes, however it also can be used as an example of instructions you need to provide to your compiler. +void myserver_onmessage2(librg_message_t *msg) { + printf("we got message 2\n"); -## Installation + YourData yourData; /* read data back */ + librg_data_rptr(msg->data, &yourData, sizeof(YourData)); +} -There are multiple way of how you can "install" the library: + // ... + librg_message_add(&ctx, MESSAGE_1, myserver_onmessage1); + librg_message_add(&ctx, MESSAGE_2, myserver_onmessage2); + // ... -* automatic - * using **[npm](https://www.npmjs.com/get-npm)**. Just run `npm install librg.c` in folder with your project, that's it! - (maybe you will not like the `node_modules` folder, however you can move it or rename it, if you are *not planning* to fetch any udpates via npm) -* manual: - * downloading/copying only [librg.h](https://raw.githubusercontent.com/librg/librg/master/include/librg.h) file, and doing same for each dependency. - * downloading/cloning repos for the librg and for each other dependency. +/* client side */ + // ... + librg_messsage_send_all(&ctx, MESSAGE_1, NULL, 0); /* send an empty message */ + librg_messsage_send_all(&ctx, MESSAGE_2, &yourData, sizeof(yourData)); /* send some clean data */ +``` +#### Client sync -## Examples +If you want your client to send updates about one or more entities to the server, and you need a simple and fast solution, you can stream your entities right from your clients. +For example, you have a huge game world (e.g. open world sandbox game), where there are some entities which are not players, however you need someone to control them. +Considering the fact that you probably don't have any game logic on the server, you need one of your clients to send updates about ingame entities to other clients. -### Server +This can be achieved quite easily, you can just call method `librg_entity_control_set` on the server, for specific entity. +It will make provided client responsive for streaming this entity to the server. -Simple server, which will behave like a **proxy**, creating entity for each joining client, and showing him his network-culled zone of view. Some people call it **streaming**. -Updates will be sent to the client each `config.tick_delay` ms. You can add your own logic of moving objects on the server, -and all **changes** of the position and other parameters will be automatically sent to all clients. +Now, what you need to do is just to update that entity data from your local client's game world. And that can also be achieved quite easily: ```c -#define LIBRG_DEBUG -#define LIBRG_IMPLEMENTATION -#include +/* client side */ +void mygame_client_stream(librg_event_t *event) { + // write new entity position (will be sent automatically) + event->entity->position = MyGame_GetPosition(event->entity->id); -void on_connect_accepted(librg_event_t *event) { - librg_log("someone connected to the server!\n"); + // write some data + librg_data_wf32(&data, MyGame_GetDotVelocity(event->entity->id)); } -int main() { - // initialization - librg_config_t config = {0}; - - config.tick_delay = 64; - config.mode = LIBRG_MODE_SERVER; - config.world_size = zplm_vec2(5000.0f, 5000.0f); - - librg_init(config); - - // adding event handlers - librg_event_add(LIBRG_CONNECTION_ACCEPT, on_connect_accepted); + // ... + librg_event_add(&ctx, LIBRG_CLIENT_STREAMER_UPDATE, mygame_client_stream); + // ... +``` - // starting server - librg_address_t address = {0}; address.port = 22331; - librg_network_start(address); +Now on the server side, you pretty much do the same, it is very similar to what we did for entity update, with only difference being that we use `LIBRG_CLIENT_STREAMER_UPDATE` event id. - // starting main loop - while (true) { - librg_tick(); - zpl_sleep_ms(1); - } +#### Additional - // stopping network and resources disposal - librg_network_stop(); - librg_free(); - return 0; -} -``` +You can and **MUST** (not really, but we really advise you to) look into the source code ([librg.h](include/librg.h)). +There we have many helpful (and not really) comments that will guide you or at very least give you explanation what is this or that, why it's needed, and how to use it. -### Client +Also you can look inside our [test](test/) folder, there are many different things. It's usually used for the development purposes, but I guarantee you can find something interesting in there. -Client receives a **snapshot** of network-culled game world and calls methods for creating entities according to what server tells them. -Moving objects (on the server) may go out of "stream-zone" and client will trigger entity `remove` event if object is still visible for the player, `update` event will be triggered, -it will contain current server's information about the object. +## Installation -```c -#define LIBRG_DEBUG -#define LIBRG_IMPLEMENTATION -#include - -void on_entity_create(librg_event_t *event) { - zplm_vec3_t position = librg_fetch_transform(event->entity)->position; - - // call to ingame method of creating object - librg_log("creating entity with id: %u, of type: %u, on position: %f %f %f \n", - event->entity, librg_entity_type(event->entity), - position.x, position.y, position.z - ); -} +There are multiple way of how you can "download" the library: -void on_entity_update(librg_event_t *event) { - zplm_vec3_t position = librg_fetch_transform(event->entity)->position; +* automatic + * using **[npm](https://www.npmjs.com/get-npm)**. Just run `npm install librg.c` in folder with your project, that's it! + (maybe you will not like the `node_modules` folder, however you can move it or rename it, if you are *not planning* to fetch any udpates via npm) - // call to ingame method of updating object - librg_log("updating position for entity with id: %u, of type: %u, to position: %f %f %f \n", - event->entity, librg_entity_type(event->entity), - position.x, position.y, position.z - ); -} +* manual: + * downloading/copying only [librg.h](https://raw.githubusercontent.com/librg/librg/master/include/librg.h) file, and doing same for each dependency. + * downloading/cloning repos for the librg and for each other dependency. -void on_entity_remove(librg_event_t *event) { - // call to ingame method of destroying object - librg_log("destroying entity with id: %u\n", event->entity); -} +Essentially, what you will need to have in the result is a set of single-header libraries. Which you can just put in the same folder and point the compiler include ther. -int main() { - // initialization - librg_config_t config = {0}; +## Structure - config.tick_delay = 64; - config.mode = LIBRG_MODE_CLIENT; - config.world_size = zplm_vec2(5000.0f, 5000.0f); +The library is built with **single header-only file design**. So everything librg provides is located in [includes/librg.h](https://github.com/librg/librg/blob/master/include/librg.h) file. +However it has **dependencies**. All of them are also single header-only libraries. - librg_init(config); +### List +Current list and description of dependencies looks like this: - // adding event handlers - librg_event_add(LIBRG_ENTITY_CREATE, on_entity_create); - librg_event_add(LIBRG_ENTITY_UPDATE, on_entity_update); - librg_event_add(LIBRG_ENTITY_REMOVE, on_entity_remove); +| Library | Version | Description | +|:-:|:-:|---| +| [enet](https://github.com/zpl-c/enet) | ![](https://img.shields.io/npm/v/enet.c.svg?maxAge=3600) | **enet** is a quite popular high performant low-level network library. A core for the librg. | +| [zpl](https://github.com/zpl-c/zpl) | ![](https://img.shields.io/npm/v/zpl.c.svg?maxAge=3600) | **zpl** is a basic library containing many functions you would use in everyday programming.| +| [zpl-cull](https://github.com/zpl-c/zpl-cull) | ![](https://img.shields.io/npm/v/zpl_cull.c.svg?maxAge=3600) | **zpl-cull** is culling library. Used to create an entity tree, and query objects based on that. | +| [zpl-math](https://github.com/zpl-c/zpl-math) | ![](https://img.shields.io/npm/v/zpl_math.c.svg?maxAge=3600) | **zpl-math** is obviously a math library. Used mostly in the streamer part. | +| [zpl-event](https://github.com/zpl-c/zpl-event) | ![](https://img.shields.io/npm/v/zpl_event.c.svg?maxAge=3600) | **zpl-event** is simple and yet powerful event library. Most of the public interfaces of librg are build using zpl-event. | - // connect to the server - librg_address_t address = { "localhost", 22331 }; - librg_network_start(address); +### Build instructions +librg comes with a **[CMakeLists.txt](CMakeLists.txt)** file. You can use it to integrate the library inside your project. Or use it as a boilerplate for new project. +There is also the **[build.sh](build.sh)** shell script, which is usually used for the development purposes, however it also can be used as an example of instructions you need to provide to your compiler. - // starting main loop - while (true) { - librg_tick(); - zpl_sleep_ms(1); - } - // disconnection from the server - // and resource disposal - librg_network_stop(); - librg_free(); - return 0; -} -``` +## Use-cases -### Additional +* As "built-in" server: + Server can be merged with the client and run even from the same thread. It will result in a quite common concept of client-hosted game sessions for **friends**. + However it requires user, or you, to deal with stuff like public ip/visibility. -You can and **MUST** (not really, but i really advise you to) look into the source code ([librg.h](include/librg.h)). -There we have many helpful (and not really) comments that will guide you or at very least give you explanation what is this or that, why it's needed, and how to use it. +* As proxy server: + Would you like to have a **dedicated server** solution? Server will be used as a proxy to store and send accumulated player data. + Considering the fact that you don't have (almost) any game logic on the server side, it can handle big amounts of clients. + Similar example can be something like [San-Andreas Multiplayer](http://www.sa-mp.com/). -Also you can look inside our [test](test/) folder, there are many different things. It's usually used for the development purposes, but I guarantee you can find something interesting in there. +* As thin client (advanced): + For client side, you will use thin-client "pattern". All user actions will be sent directly to the server. + Requires you to write all the server side systems, like physics handling, game world management, and **gameloop on the server side**. +* Any other possible way that was not mentioned. ## Testing We started testing the library for different platforms. This table provides some sort of description for compatibility. If you have tested it, and it compiles, or it perhaps stopped compiling, please feel free to describe the issue in the [issues](https://github.com/librg/librg/issues). -| *Compiler* / Target | Windows | Linux | macOS | iOS | Android | -|:-:|:-:|:-:|:-:|:-:|:-:| -| *clang C* | :grey_question: | :white_check_mark: | :white_check_mark: | :grey_question: | :grey_question: | -| *clang C++* | :grey_question: | :grey_question: | :white_check_mark: | :grey_question: | :grey_question: | -| *gcc C* | | :white_check_mark: | :white_check_mark: | | :grey_question: | -| *gcc C++* | | :grey_question: | :grey_question: | | :grey_question: | -| *msvc C++* | :white_check_mark: | | | | | -| *msvc C* | :white_check_mark: | | | | | -| *mingw C++* | :white_check_mark: | | | | | -| *mingw C* | :white_check_mark: | | | | | +| *Compiler* / Target | Windows | Linux | macOS | iOS | Android | +|:-:|:-:|:-:|:-:|:-:|:-: | +| *clang C* | :grey_question: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :grey_question: | +| *gcc C* | | :white_check_mark: | :white_check_mark: | | | +| *msvc C* | :white_check_mark: | | | | | +| *mingw C* | :white_check_mark: | | | | | +| *clang C++* | :grey_question: | :white_check_mark: | :white_check_mark: | :grey_question: | :white_check_mark: | +| *gcc C++* | | :white_check_mark: | :grey_question: | | | +| *msvc C++* | :white_check_mark: | | | | | +| *mingw C++* | :white_check_mark: | | | | | :white_check_mark: - compiles/runs without any errors. diff --git a/include/librg.h b/include/librg.h index 3b91335..5193a9a 100644 --- a/include/librg.h +++ b/include/librg.h @@ -1,4 +1,4 @@ -/** +/** * LIBRG - reguider library * * A library for building simple and elegant cross-platform mmo client-server solutions. @@ -10,7 +10,7 @@ * #include * * Credits: - * Vladislav Gritsenko (GitHub: inlife) + * Vladyslav Hrytsenko (GitHub: inlife) * Dominik Madarasz (GitHub: zaklaus) * * Dependencies: @@ -24,6 +24,8 @@ * sdl2.h * * Version History: + * 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_t pointers @@ -32,7 +34,7 @@ * 2.0.2 - C++ and MSVC related fixes * 2.0.0 - Initial C version rewrite * - * Copyright 2017 Vladislav Gritsenko + * Copyright 2017 Vladyslav Hrytsenko * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,15 +51,20 @@ #ifndef LIBRG_INCLUDE_H #define LIBRG_INCLUDE_H -#define LIBRG_VERSION_MAJOR 2 -#define LIBRG_VERSION_MINOR 2 -#define LIBRG_VERSION_PATCH 1 +#define LIBRG_VERSION_MAJOR 3 +#define LIBRG_VERSION_MINOR 0 +#define LIBRG_VERSION_PATCH 0 #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) +// disable asserts for release build +#if !defined(LIBRG_DEBUG) +#define ZPL_ASSERT_MSG(cond, msg, ...) +#endif + #ifndef LIBRG_CUSTOM_INCLUDES #ifdef LIBRG_IMPLEMENTATION @@ -65,13 +72,34 @@ #define ZPLM_IMPLEMENTATION #define ZPLC_IMPLEMENTATION #define ZPLEV_IMPLEMENTATION +#define ENET_IMPLEMENTATION #endif #include #include #include #include -#include + +#ifdef ZPL_SYSTEM_WINDOWS +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#endif + +#include + +#endif + +#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 @@ -80,18 +108,22 @@ extern "C" { #endif - /** * * BASIC DEFINITOINS * */ - #define LIBRG_API ZPL_DEF - #define LIBRG_ENTITY_ALLOCATOR zpl_heap_allocator - #define LIBRG_NETWORK_STREAM_PRIMARY_CHANNEL 1 - #define LIBRG_NETWORK_STREAM_SECONDARY_CHANNEL 1 - #define LIBRG_NETWORK_MESSAGE_CHANNEL 2 + #ifndef librg_log + #define librg_log zpl_printf + #endif + + #define librg_global zpl_global + #define librg_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 #if defined(__cplusplus) || defined(_MSC_VER) #define librg_void char @@ -111,19 +143,14 @@ extern "C" { #define librg_measure(TITLE, CODE) #endif - #define librg_log zpl_printf - #define librg_assert ZPL_ASSERT - #define librg_assert_msg ZPL_ASSERT_MSG - #define librg_inline zpl_inline - #define librg_internal zpl_internal - #define librg_lambda(name) name - #define librg_bit_size 32 #define librg_ceiling(x,y) ( ((x) + (y) - 1) / (y) ) #define librg_bit_set(A,k) ( A[(k/32)] |= (1 << (k%32)) ) #define librg_bit_clear(A,k) ( A[(k/32)] &= ~(1 << (k%32)) ) #define librg_bit_test(A,k) ( A[(k/32)] & (1 << (k%32)) ) + #define LIBRG_MESSAGE_ID u16 + #define LIBRG_DATA_STREAMS_AMOUNT 4 /** * @@ -131,33 +158,62 @@ extern "C" { * */ - #ifndef LIBRG_PLATFORM_ID - #define LIBRG_PLATFORM_ID 1 - #endif + typedef enum { + LIBRG_PLATFORM_ID, + LIBRG_PLATFORM_PROTOCOL, + LIBRG_PLATFORM_BUILD, - #ifndef LIBRG_PLATFORM_PROTOCOL - #define LIBRG_PLATFORM_PROTOCOL 1 - #endif + LIBRG_DEFAULT_CLIENT_TYPE, + LIBRG_DEFAULT_STREAM_RANGE, + LIBRG_DEFAULT_DATA_SIZE, - #ifndef LIBRG_PLATFORM_BUILD - #define LIBRG_PLATFORM_BUILD 1 - #endif + LIBRG_NETWORK_CAPACITY, + LIBRG_NETWORK_CHANNELS, + LIBRG_NETWORK_PRIMARY_CHANNEL, + LIBRG_NETWORK_SECONDARY_CHANNEL, + LIBRG_NETWORK_MESSAGE_CHANNEL, - #ifndef LIBRG_DEFAULT_BS_SIZE - #define LIBRG_DEFAULT_BS_SIZE 64 - #endif + LIBRG_MAX_ENTITIES_PER_BRANCH, + LIBRG_MAX_THREADS_PER_UPDATE, - #ifndef LIBRG_NETWORK_MESSAGE_CAPACITY - #define LIBRG_NETWORK_MESSAGE_CAPACITY 2048 - #endif + LIBRG_OPTIONS_SIZE, + } librg_option_e; - #ifndef LIBRG_NETWORK_CHANNELS - #define LIBRG_NETWORK_CHANNELS 4 - #endif + enum { + LIBRG_MODE_SERVER, + LIBRG_MODE_CLIENT, + }; - #ifndef LIBRG_DEFAULT_CLIENT_TYPE - #define LIBRG_DEFAULT_CLIENT_TYPE 0 - #endif + /** + * 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_CONNECTION_INIT, + LIBRG_CONNECTION_REQUEST, + LIBRG_CONNECTION_REFUSE, + LIBRG_CONNECTION_ACCEPT, + LIBRG_CONNECTION_DISCONNECT, + + LIBRG_ENTITY_CREATE, + LIBRG_ENTITY_UPDATE, + LIBRG_ENTITY_REMOVE, + LIBRG_CLIENT_STREAMER_ADD, + LIBRG_CLIENT_STREAMER_REMOVE, + LIBRG_CLIENT_STREAMER_UPDATE, + + LIBRG_EVENT_LAST, + }; + + /** + * Table for various entity bool storages + */ + ZPL_TABLE_DECLARE(extern, librg_table_t, librg_table_, u32); /** * @@ -165,62 +221,276 @@ extern "C" { * */ - typedef enum librg_mode_e { + struct librg_ctx_t; - /* non-blocking server with run-once loop, manual librg_tick() call required */ - LIBRG_MODE_SERVER, + typedef ENetPeer librg_peer_t; + typedef ENetHost librg_host_t; + typedef ENetPacket librg_packet_t; - /* non-blocking client with run-once loop, manual librg_tick() call required */ - LIBRG_MODE_CLIENT, + #define librg_entity_id u32 + + typedef struct { + usize capacity; + usize read_pos; + usize write_pos; + + void *rawptr; + + zpl_allocator_t allocator; + } librg_data_t; + + + /** + * Simple host address + * used to configure network on start + */ + typedef struct { + i32 port; + char *host; + } librg_address_t; + + + /** + * Entity flags + */ + + enum { + 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_IGNORING = (1 << 2), /* flag showing that entity has ignore 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 */ + }; + + /** + * Entity blob + */ + + typedef struct librg_entity_t { + u32 id; + u32 type; + u64 flags; + + zplm_vec3_t position; + f32 stream_range; + + void *user_data; + zplc_t *stream_branch; + + librg_table_t ignored; + librg_table_t last_snapshot; + + librg_peer_t *client_peer; + librg_peer_t *control_peer; + + zpl_array_t(librg_entity_id) last_query; + } librg_entity_t; + + + /** + * Message structure + * created inside network handler + * and injected to each incoming message + */ + typedef struct { + struct librg_ctx_t *ctx; + + librg_data_t *data; + librg_peer_t *peer; + librg_packet_t *packet; + + void *user_data; /* optional: user information */ + } librg_message_t; + + + typedef enum { + 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 */ + } librg_event_flag_e; + + + /** + * Event structure + * usually created in various + */ + typedef struct { + struct librg_ctx_t *ctx; /* librg context where event has been called */ + + librg_data_t *data; /* optional: data is used for built-in events */ + librg_peer_t *peer; /* optional: peer is used for built-in events */ + librg_entity_t *entity; /* optional: entity is used for built-in events */ + + u64 flags; /* flags for that event */ + void *user_data; /* optional: user information */ + } librg_event_t; + + + /** + * Callbacks + */ + + typedef void (librg_entity_cb)(struct librg_ctx_t *ctx, librg_entity_id entity); + typedef void (librg_message_cb)(librg_message_t *msg); + typedef void (librg_event_cb)(librg_event_t *event); + + + /** + * Multithreading stuff + */ + + enum { + librg_thread_idle, + librg_thread_work, + librg_thread_exit, + }; + + typedef struct { + usize id; + usize offset; + usize count; + struct librg_ctx_t *ctx; + } librg_update_worker_si_t; - } librg_mode_e; - typedef struct librg_config_t { + /** + * Context + config struct + */ + + typedef struct librg_ctx_t { // core + u16 mode; u16 tick_delay; - // streamer configuration - zplm_vec2_t world_size; + // configuration + u16 max_connections; u32 max_entities; - // network configuration - u16 max_connections; - librg_mode_e mode; + zplm_vec3_t world_size; + zplm_vec3_t min_branch_size; + + f32 last_update; + void *user_data; + + struct { + librg_peer_t *peer; + librg_host_t *host; + librg_table_t connected_peers; + } network; + + struct { + u32 count; + u32 cursor; + librg_table_t ignored; + struct librg_entity_t *list; + zpl_array_t(librg_entity_id) remove_queue; + } entity; + + union { + struct { + librg_data_t stream_input; + librg_data_t stream_output; + librg_data_t stream_upd_reliable; + librg_data_t stream_upd_unreliable; + }; + + librg_data_t streams[LIBRG_DATA_STREAMS_AMOUNT]; + }; + +#ifdef LIBRG_MULTITHREADED + struct { + zpl_atomic32_t signal; + zpl_atomic32_t work_count; + zpl_thread_t *update_workers; + zpl_mutex_t *send_lock; + } threading; +#endif + + zpl_buffer_t(librg_message_cb *) messages; + + zpl_allocator_t allocator; + zpl_timer_pool timers; + zplev_pool events; + zplc_t streamer; + } librg_ctx_t; + + + /** + * Global option storage + */ - } librg_config_t; + 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_MAX_ENTITIES_PER_BRANCH*/ 4, + /*LIBRG_MAX_THREADS_PER_UPDATE*/ 0, /* MT is disabled by default = 0 */ + }; + + + /** + * Set global cross-instance option for librg + */ + LIBRG_API void librg_option_set(librg_option_e option, u32 value); + + /** + * Get global cross-instance option for librg + */ + LIBRG_API u32 librg_option_get(librg_option_e option); /** * Main initialization method * MUST BE called in the begging of your application */ - LIBRG_API void librg_init(librg_config_t config); + LIBRG_API void librg_init(librg_ctx_t *ctx); /** * Main tick method * MUST BE called in your loop * preferably w/o delays */ - LIBRG_API void librg_tick(); + LIBRG_API void librg_tick(librg_ctx_t *ctx); /** * Should be called at the end of * execution of the program */ - LIBRG_API void librg_free(); + LIBRG_API void librg_free(librg_ctx_t *ctx); + + /** + * Frees a pointer allocated by library + * usually used in bindings. + */ + LIBRG_API void librg_release(void *ptr); + + /** + * Frees an array allocated by library + * usually used in bindings. + */ + LIBRG_API void librg_release_array(void *ptr); /** * Is librg instance is running * in the server mode */ - LIBRG_API b32 librg_is_server(); + LIBRG_API b32 librg_is_server(librg_ctx_t *ctx); /** * Is librg instance is running * in the client mode */ - LIBRG_API b32 librg_is_client(); - - + LIBRG_API b32 librg_is_client(librg_ctx_t *ctx); /** @@ -229,206 +499,107 @@ extern "C" { * */ - typedef u32 librg_entity_t; - /** - * Entity filter - * Used for ruinning complex iterations on entity pool - * - * Can be used to retrieve only entites containing all - * of the provided components (logical AND operation). - * And entities that exclude provided components (logical NOT). - * - * Supports up to 8 components for "contains" operation. - * And up to 4 components for "excludes" operation. - * - * First undefined component index in the list will skip all other - * components for that operation: - * { .contains1 = librg_index_c1(), .contains3 = librg_index_c3() } - * In this case librg_index_c3() WILL NOT be added to condition. - * - * If you want to enable this behavior: make sure - * you define LIBRG_ENTITY_UNOPTIMIZED_CYCLES before including the librg.h - * - * - * EXAMPLES: - * librg_entity_filter_t filter = { librg_index_c1(), librg_index_c2() }; - * OR - * librg_entity_filter_t filter = { librg_index_c1(), .excludes1 = librg_index_c2() }; + * Create entity and return handle */ - typedef union librg_entity_filter_t { - struct { - u32 contains1; u32 contains2; u32 contains3; u32 contains4; - u32 contains5; u32 contains6; u32 contains7; u32 contains8; - u32 excludes1; u32 excludes2; u32 excludes3; u32 excludes4; - }; - - struct { - u32 contains[8]; - u32 excludes[4]; - }; - } librg_entity_filter_t; - + LIBRG_API librg_entity_id librg_entity_create(librg_ctx_t *ctx, u32 type); /** - * Callback that will be used to pass - * the entity inside the function-handler + * Check if provided entity is a valid entity */ - typedef void (librg_entity_cb_t)(librg_entity_t entity); + LIBRG_API b32 librg_entity_valid(librg_ctx_t *ctx, librg_entity_id entity); /** - * Table for various entity bool storages + * Return entity type */ - ZPL_TABLE_DECLARE(extern, librg_table_t, librg_table_, u32); + LIBRG_API u32 librg_entity_type(librg_ctx_t *ctx, librg_entity_id entity); /** - * Create entity and return handle + * Return entity blob pointer */ - LIBRG_API librg_entity_t librg_entity_create(u32 type); + LIBRG_API librg_entity_t *librg_entity_fetch(librg_ctx_t *ctx, librg_entity_id entity); /** - * Create shared entity and return handle + * Destroy entity */ - LIBRG_API librg_entity_t librg_entity_create_shared(librg_entity_t remote, u32 type); + LIBRG_API void librg_entity_destroy(librg_ctx_t *ctx, librg_entity_id entity); /** - * Check if provided entity is a valid entity + * Query for entities that are in stream zone + * for current entity, and are visible to this entity */ - LIBRG_API b32 librg_entity_valid(librg_entity_t entity); + LIBRG_API usize librg_entity_query(librg_ctx_t *ctx, librg_entity_id entity, librg_entity_id **result); /** - * Return entity type + * Get entity by peer */ - LIBRG_API u32 librg_entity_type(librg_entity_t entity); + LIBRG_API librg_entity_id librg_entity_find(librg_ctx_t *ctx, librg_peer_t *peer); /** - * Auto detach all attached components - * and destroy entity + * Set particular entity visible or invisible + * for other entities in stream zone */ - LIBRG_API void librg_entity_destroy(librg_entity_t entity); + LIBRG_API void librg_entity_visibility_set(librg_ctx_t *ctx, librg_entity_id entity, b32 state); /** - * Method used for interation on the collection of entities - * filtered by provided conditions, and calls callback for each found entity - * - * Filter condition is formbed by naming component ids that are need to be included - * or excluded, depending on the number or key of element in the structure - * - * NOTE: Component id order IS RELEVANT, if you want to have as much performance as possible, - * make sure you put component with lowest amount of attached entities in the first slot - * - * @param filter - * @param callback + * Set particular entity visible or invisible + * for other particular entity */ - LIBRG_API void librg_entity_each(librg_entity_filter_t filter, librg_entity_cb_t callback); + LIBRG_API void librg_entity_visibility_set_for(librg_ctx_t *ctx, librg_entity_id entity, librg_entity_id target, b32 state); /** - * Macro that declares component structs and methods - * SHOULD BE called IN ALL c/cpp files that will be using it - * with EXACTLY THE SAME sturcture and description as in other c/cpp files - * - * Describes these public API methods: - * - * 1) attach - * Used to attach a component onto entity. - * Returns pointer onto attached component. - * Contains lazy-initialization call for itself. - * - * EXAMPLE: librg_attach_{component}(ent, somedata); - * - * - * 2) fetch - * Used to fetch attached component from the entity. - * If entiy doesnt have a component, NULL will be returned. - * Contains lazy-initialization call for itself. - * - * EXAMPLE: {component}_t *foo = librg_fetch_{component}(ent); - * - * - * 3) detach - * Used to remove attached component form the entity - * Contains lazy-initialization call for itself. - * - * EXAMPLE: librg_detach_{component}(ent); - * - * - * 4) index - * Used to get run-time component index inside inner storage. - * MUST NOT be saved in any static parsistant storage, considering the fact - * that indexes are not persistant, and can change between application calls. - * - * EXAMPLE: u32 index = librg_index_{component}(); - * + * Get particular entity visible or invisible + * for other entities in stream zone */ - #define librg_component_declare_inner(PREFIX, NAME) \ - ZPL_JOIN3(PREFIX, NAME, _t); \ - \ - typedef struct ZPL_JOIN3(librg_, NAME, _meta_ent_t) { \ - librg_entity_t handle; \ - u32 used; \ - } ZPL_JOIN3(librg_, NAME, _meta_ent_t); \ - \ - typedef struct ZPL_JOIN3(librg__component_, NAME, _pool_t) { \ - zpl_allocator_t backing; \ - usize count; \ - u32 index; \ - zpl_buffer_t(ZPL_JOIN3(librg_, NAME, _meta_ent_t)) entities; \ - zpl_buffer_t(ZPL_JOIN3(PREFIX, NAME, _t)) data; \ - } ZPL_JOIN3(librg__component_, NAME, _pool_t); \ - \ - librg_inline void ZPL_JOIN2(librg__init_, NAME) (ZPL_JOIN3(librg__component_, NAME, _pool_t) *h, zpl_allocator_t a); \ - librg_inline void ZPL_JOIN2(librg__free_, NAME) (ZPL_JOIN3(librg__component_, NAME, _pool_t) *h); \ - librg_inline ZPL_JOIN3(PREFIX, NAME, _t) * ZPL_JOIN2(librg_attach_, NAME) (librg_entity_t handle, ZPL_JOIN3(PREFIX, NAME, _t) data); \ - librg_inline void ZPL_JOIN2(librg_detach_, NAME) (librg_entity_t handle); \ - librg_inline ZPL_JOIN3(PREFIX, NAME, _t) * ZPL_JOIN2(librg_fetch_, NAME) (librg_entity_t handle); \ - librg_inline b32 ZPL_JOIN2(librg_has_, NAME) (librg_entity_t handle); \ - librg_inline u32 ZPL_JOIN2(librg_index_, NAME) (); - - + LIBRG_API b32 librg_entity_visibility_get(librg_ctx_t *ctx, librg_entity_id entity); + /** + * Get particular entity visible or invisible + * for other particular entity + */ + LIBRG_API b32 librg_entity_visibility_get_for(librg_ctx_t *ctx, librg_entity_id entity, librg_entity_id target); /** + * Set some entity as client streamable + * Which means, that client will become responsive for sending + * updates about this entity * - * EVENTS + * And this entity wont be sent to the client, until he stops being the streamer * + * Setting other client as streamer, will remove previous streamer from entity */ + LIBRG_API void librg_entity_control_set(librg_ctx_t *ctx, librg_entity_id entity, librg_peer_t *peer); - typedef struct librg_event_t { - b32 rejected; - librg_void **data; - librg_entity_t entity; - } librg_event_t; + /** + * Get controller of the entity + */ + LIBRG_API librg_peer_t *librg_entity_control_get(librg_ctx_t *ctx, librg_entity_id entity); /** - * Callback for event + * Remove some entity from stream ownership of the client */ - typedef void (librg_event_cb_t)(librg_event_t *event); + LIBRG_API void librg_entity_control_remove(librg_ctx_t *ctx, librg_entity_id entity); /** - * Default built-in events - * define your events likes this: - * enum { - * MY_NEW_EVENT_1 = LIBRG_LAST_ENUM_NUMBER, - * MY_NEW_EVENT_2, - * MY_NEW_EVENT_3, - * }; + * Iterate over all the entities with a flag */ - enum { - LIBRG_CONNECTION_INIT, - LIBRG_CONNECTION_REQUEST, - LIBRG_CONNECTION_REFUSE, - LIBRG_CONNECTION_ACCEPT, - LIBRG_CONNECTION_DISCONNECT, + LIBRG_API void librg_entity_iterate(librg_ctx_t *ctx, u64 flags, librg_entity_cb callback); - LIBRG_ENTITY_CREATE, - LIBRG_ENTITY_UPDATE, - LIBRG_ENTITY_REMOVE, - LIBRG_CLIENT_STREAMER_ADD, - LIBRG_CLIENT_STREAMER_REMOVE, - LIBRG_CLIENT_STREAMER_UPDATE, - LIBRG_LAST_ENUM_NUMBER, - }; + #define librg_entity_iteratex(ctx, cflags, cname, code) do { \ + for (int _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); + + + /** + * + * EVENTS + * + */ /** * Used to attach event handler @@ -442,7 +613,7 @@ extern "C" { * @param callback * @return index of added event, can be used to remove particular event handler */ - LIBRG_API u64 librg_event_add(u64 id, librg_event_cb_t callback); + LIBRG_API u64 librg_event_add(librg_ctx_t *ctx, u64 id, librg_event_cb callback); /** * Used to trigger execution of all attached @@ -454,7 +625,7 @@ extern "C" { * @param id usually you define event ids inside enum * @param event pointer onto data or NULL */ - LIBRG_API void librg_event_trigger(u64 id, librg_event_t *event); + LIBRG_API void librg_event_trigger(librg_ctx_t *ctx, u64 id, librg_event_t *event); /** * Used to remove particular callback from @@ -463,34 +634,32 @@ extern "C" { * @param id usually you define event ids inside enum * @param index returned by librg_event_add */ - LIBRG_API void librg_event_remove(u64 id, u64 index); + LIBRG_API void librg_event_remove(librg_ctx_t *ctx, u64 id, u64 index); /** * Used to reject some event from triggering from * inside of executing callback - * - * @param pointer on the event */ LIBRG_API void librg_event_reject(librg_event_t *event); + /** + * Used to check if some event can be rejected + */ + LIBRG_API b32 librg_event_rejectable(librg_event_t *event); + /** * Checks if current event was not rejected * inside any of the callbacks - * - * @param pointer on the event */ LIBRG_API b32 librg_event_succeeded(librg_event_t *event); - /** * * BINARY DATA (BITSTREAM) * */ - typedef librg_void *librg_data_t; - /** * Initialize new bitstream with default mem size */ @@ -567,9 +736,11 @@ extern "C" { /** * Read/write methods for entity (aliases for u32) */ - #define librg_data_rentity librg_data_ru32 - #define librg_data_wentity librg_data_wu32 + #define librg_data_went ZPL_JOIN2(librg_data_w, librg_entity_id) + #define librg_data_rent ZPL_JOIN2(librg_data_r, librg_entity_id) + #define librg_data_wmid ZPL_JOIN2(librg_data_w, LIBRG_MESSAGE_ID) + #define librg_data_rmid ZPL_JOIN2(librg_data_r, LIBRG_MESSAGE_ID) /** @@ -578,58 +749,10 @@ extern "C" { * */ - typedef ENetPeer *librg_peer_t; - typedef ENetHost *librg_host_t; - typedef ENetPacket *librg_packet_t; - - /** - * Simple host address - * used to configure network on start - */ - typedef struct { - char *host; - i32 port; - } librg_address_t; - - /** - * Message strure - * gets returned inside network handler - * on each incoming message - */ - typedef struct { - librg_data_t *data; - librg_peer_t peer; - librg_packet_t packet; - } librg_message_t; - - /** - * Callback definition - * for network message handler - */ - typedef void (librg_message_handler_t)(librg_message_t *msg); - - /** - * Storage for entities stored by peer - * Hashtable, peer pointer is key, associated entity is value - */ - ZPL_TABLE_DECLARE(extern, librg_peers_t, librg_peers_, librg_entity_t); - - /** - * Builtin network storage table - */ - typedef struct librg_network_t { - librg_peer_t peer; - librg_host_t host; - - librg_peers_t connected_peers; - } librg_network_t; - - extern librg_network_t librg_network; - /** * Check are we connected */ - LIBRG_API b32 librg_is_connected(); + LIBRG_API b32 librg_is_connected(librg_ctx_t *ctx); /** * Starts network connection @@ -639,46 +762,46 @@ extern "C" { * For server mode - starts server * For client mode - starts client, and connects to provided host & port */ - LIBRG_API void librg_network_start(librg_address_t address); + LIBRG_API void librg_network_start(librg_ctx_t *ctx, librg_address_t address); /** * Disconnects (if connected), stops network * and releases resources */ - LIBRG_API void librg_network_stop(); + LIBRG_API void librg_network_stop(librg_ctx_t *ctx); /** * Can be used to add handler * to a particular message id */ - LIBRG_API void librg_network_add(u64 id, librg_message_handler_t callback); + LIBRG_API void librg_network_add(librg_ctx_t *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(u64 id); + LIBRG_API void librg_network_remove(librg_ctx_t *ctx, LIBRG_MESSAGE_ID id); /** * Part of message API * Takes in initialized void of size pointer with written packet id * and sends data to all connected peers ( or to server if its client ) */ - LIBRG_API void librg_message_send_all(librg_void *data, usize size); + LIBRG_API void librg_message_send_all(librg_ctx_t *ctx, LIBRG_MESSAGE_ID id, void *data, usize size); /** * Part of message API * Applies all from previous mehod * But data will be sent only to particular provided peer */ - LIBRG_API void librg_message_send_to(librg_peer_t peer, librg_void *data, usize size); + LIBRG_API void librg_message_send_to(librg_ctx_t *ctx, LIBRG_MESSAGE_ID id, librg_peer_t *peer, void *data, usize size); /** * Part of message API * Applies all from previous mehod * But data will be sent to all except provided peer */ - LIBRG_API void librg_message_send_except(librg_peer_t peer, librg_void *data, usize size); + LIBRG_API void librg_message_send_except(librg_ctx_t *ctx, LIBRG_MESSAGE_ID id, librg_peer_t *peer, void *data, usize size); /** * Part of message API @@ -686,7 +809,7 @@ extern "C" { * Data will be sent only to entities, which are inside streamzone * for provided entity */ - LIBRG_API void librg_message_send_instream(librg_entity_t entity, librg_void *data, usize size); + LIBRG_API void librg_message_send_instream(librg_ctx_t *ctx, LIBRG_MESSAGE_ID id, librg_entity_id entity, void *data, usize size); /** * Part of message API @@ -694,89 +817,7 @@ extern "C" { * Data will be sent only to entities, which are inside streamzone * for provided entity except peer */ - LIBRG_API void librg_message_send_instream_except(librg_entity_t entity, librg_peer_t peer, librg_void *data, usize size); - - - - - /** - * - * STREAMER - * - */ - - /** - * Query for entities that are in stream zone - * for current entity, and are visible to this entity - */ - LIBRG_API zpl_array_t(librg_entity_t) librg_streamer_query(librg_entity_t entity); - - /** - * Set particular entity visible or invisible - * for other entities in stream zone - */ - LIBRG_API void librg_streamer_set_visible(librg_entity_t entity, b32 state); - - /** - * Set particular entity visible or invisible - * for other particular entity - */ - LIBRG_API void librg_streamer_set_visible_for(librg_entity_t entity, librg_entity_t target, b32 state); - - /** - * Set some entity as client streamable - * Which means, that client will become responsive for sending - * updates about this entity - * - * And this entity wont be sent to the client, until he stops being the streamer - * - * Setting other client as streamer, will remove previous streamer from entity - */ - LIBRG_API void librg_streamer_client_set(librg_entity_t entity, librg_peer_t peer); - - /** - * Remove some entity from stream ownership of the client - */ - LIBRG_API void librg_streamer_client_remove(librg_entity_t entity); - - /** - * Get entity by peer - */ - LIBRG_API librg_entity_t librg_get_client_entity(librg_peer_t peer); - - - - /** - * - * COMPONENTS - * - */ - - typedef struct { - zplm_vec3_t position; - zplm_quat_t rotation; - } librg_component_declare_inner(librg_, transform); - - typedef struct { - u32 range; - } librg_component_declare_inner(librg_, streamable); - - typedef struct { - u32 type; - librg_table_t ignored; - } librg_component_declare_inner(librg_, entitymeta); - - typedef struct { - librg_peer_t peer; - } librg_component_declare_inner(librg_, clientstream); - - typedef struct { - librg_peer_t peer; - librg_table_t last_snapshot; - } librg_component_declare_inner(librg_, client); - - - + LIBRG_API void librg_message_send_instream_except(librg_ctx_t *ctx, LIBRG_MESSAGE_ID id, librg_entity_id entity, librg_peer_t *peer, void *data, usize size); /** @@ -785,50 +826,44 @@ extern "C" { * */ - #define librg_send_all(ID, NAME, CALLBACK_CODE) do { \ - librg_data_t NAME; librg_data_init(&NAME); \ - librg_data_wu64(&NAME, ID); CALLBACK_CODE; \ - librg_message_send_all(NAME, librg_data_get_wpos(&NAME)); \ - librg_data_free(&NAME); \ - } while(0); + #define librg__send_internal(CTX, ID, NAME, CALLBACK_CODE, SEND_CODE) \ + librg_data_t NAME; \ + librg_data_init(&NAME); \ + CALLBACK_CODE; SEND_CODE; \ + librg_data_free(&NAME); - #define librg_send_to(ID, PEER, NAME, CALLBACK_CODE) do { \ - librg_data_t NAME; librg_data_init(&NAME); \ - librg_data_wu64(&NAME, ID); CALLBACK_CODE; \ - librg_message_send_to(PEER, NAME, \ - librg_data_get_wpos(&NAME)); \ - 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_except(ID, PEER, NAME, CALLBACK_CODE) do { \ - librg_data_t NAME; librg_data_init(&NAME); \ - librg_data_wu64(&NAME, ID); CALLBACK_CODE; \ - librg_message_send_except(PEER, NAME, \ - librg_data_get_wpos(&NAME)); \ - librg_data_free(&NAME); \ + #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_instream(ID, ENTITY, NAME, CALLBACK_CODE) do { \ - librg_data_t NAME; librg_data_init(&NAME); \ - librg_data_wu64(&NAME, ID); CALLBACK_CODE; \ - librg_message_send_instream(ENTITY, NAME, \ - librg_data_get_wpos(&NAME)); \ - librg_data_free(&NAME); \ + #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_except(ID, ENTITY, PEER, NAME, CALLBACK_CODE) do { \ - librg_data_t NAME; librg_data_init(&NAME); \ - librg_data_wu64(&NAME, ID); CALLBACK_CODE; \ - librg_message_send_instream_except(ENTITY, PEER, NAME, \ - librg_data_get_wpos(&NAME)); \ - librg_data_free(&NAME); \ + #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 - #define librg_component_declare(NAME) librg_component_declare_inner(,NAME) - #define librg_component(NAME) librg_component_declare(NAME) #ifdef __cplusplus } @@ -840,346 +875,55 @@ extern "C" { #ifdef __cplusplus extern "C" { #endif - #define librg__set_default(expr, value) if (!expr) expr = value - /** - * Create dummy component to - * use it as template for removal in destroy - */ - typedef struct { u8 a; } librg_component_declare(_dummy); - #define __dummypool_t librg__component__dummy_pool_t - #define __dummymeta_t librg__dummy_meta_ent_t - - - ZPL_TABLE_DEFINE(librg_peers_t, librg_peers_, librg_entity_t); ZPL_TABLE_DEFINE(librg_table_t, librg_table_, u32); - - /** - * Storage containers - * for inner librg stuff - */ - zplc_t librg__streamer; - zplev_pool librg__events; - librg_config_t librg__config; - zpl_timer_pool librg__timers; - librg_network_t librg_network; - - struct { - librg_data_t input; - librg_data_t output; - } librg__streams; - - typedef struct { - u32 cursor; - u32 count; - u32 limit_upper; - u32 limit_lower; - } librg__entity_pool_t; - - struct { - librg_table_t ignored; - librg__entity_pool_t shared; - librg__entity_pool_t native; - u32 count; - } librg__entity; - - zpl_array_t(void *) librg__component_pool; - zpl_array_t(librg_entity_t) librg__entity_remove_queue; - zpl_buffer_t(librg_message_handler_t *) librg__messages; - - - - - - - - /** - * - * ENTITIES - * - */ - - - /** - * Entity create methods - * - * Attaches some default components - * and inits custom data storages inside (if needed) - */ - - librg_inline void librg__entity_attach_default(librg_entity_t entity) { - librg_transform_t t = {0}; - librg_entitymeta_t m = {0}; - librg_streamable_t s = {250}; - - librg_attach_transform(entity, t); - librg_attach_entitymeta(entity, m); - librg_attach_streamable(entity, s); - } - - librg_inline librg_entity_t librg__entity_create(librg__entity_pool_t *pool) { - librg_assert_msg(++pool->count < pool->limit_upper, "entity limit"); - - if (pool->cursor == pool->limit_upper || pool->cursor == 0) { - pool->cursor = pool->limit_lower; - } - - for (; pool->cursor < pool->limit_upper; ++pool->cursor) { - librg_entity_t entity = pool->cursor; - if (librg_entity_valid(entity)) continue; - librg__entity_attach_default(entity); - - if (librg_is_server()) { - librg_table_init(&librg_fetch_entitymeta(entity)->ignored, zpl_heap_allocator()); - } - - return entity; - } - - librg_assert_msg(false, "no entities to spawn"); - return 0; - } - - librg_entity_t librg_entity_create(u32 type) { - librg__entity_pool_t *pool = &librg__entity.shared; - - if (librg_is_client()) { - pool = &librg__entity.native; - } - - librg_entity_t entity = librg__entity_create(pool); - librg_fetch_entitymeta(entity)->type = type; - return entity; - } - - librg_entity_t librg_entity_create_shared(u32 entity, u32 type) { - librg_assert_msg(librg_is_client(), "librg_entity_create_shared: can be executed only on client"); - librg_assert_msg(!librg_entity_valid(entity), "entity with such id already exsits"); - - librg__entity_pool_t *pool = &librg__entity.shared; - librg_assert_msg(++pool->count < pool->limit_upper, "entity limit"); - librg__entity_attach_default(entity); - librg_fetch_entitymeta(entity)->type = type; - - return entity; - } - - librg_internal void librg__entity_destroy(librg_entity_t entity) { - librg__entity_pool_t *pool = &librg__entity.shared; - if (entity > librg__entity.shared.limit_upper) { - pool = &librg__entity.native; - } - - librg_assert(librg_entity_valid(entity)); - - if (librg_is_server()) { - librg_table_destroy(&librg_fetch_entitymeta(entity)->ignored); - librg_streamer_set_visible(entity, true); - } - - // decrease amount - pool->count--; - - // detach all components - for (i32 i = 0; i < zpl_array_count(librg__component_pool); i++) { - __dummymeta_t *meta = (cast(__dummypool_t*)librg__component_pool[i])->entities+entity; - librg_assert(meta); - meta->used = false; - } - } - - librg_inline b32 librg_entity_valid(librg_entity_t entity) { - return (librg_fetch_entitymeta(entity) != NULL); - } - - /** - * Entity destructors - */ - - void librg__entity_execute_destroy() { - for (isize i = 0; i < zpl_array_count(librg__entity_remove_queue); i++) { - librg__entity_destroy(librg__entity_remove_queue[i]); - } - - zpl_array_clear(librg__entity_remove_queue); - } - - void librg_entity_destroy(librg_entity_t id) { - if (librg_is_client()) { - return librg__entity_destroy(id); - } - - zpl_array_append(librg__entity_remove_queue, id); - } - - /** - * Entity types - */ - - librg_inline u32 librg_entity_type(librg_entity_t entity) { - return librg_fetch_entitymeta(entity)->type; + librg_inline void librg_option_set(librg_option_e option, u32 value) { + librg_options[option] = value; } - /** - * Entity iterators - */ - #define librg__eachmeta(INDEX, ENTITY) \ - __dummypool_t *pool = cast(__dummypool_t *)librg__component_pool[INDEX - 1]; librg_assert(pool); \ - __dummymeta_t *meta = cast(__dummymeta_t *)(pool->entities + ENTITY); librg_assert(meta); - - #define librg_entity_eachx(FILTER, NAME, CODE) do { \ - u32 entitymeta_id = librg_index_entitymeta(); \ - if (entitymeta_id == 0 || FILTER.contains1 == 0) break; \ - for (usize _ent = 0, valid = 0; valid < (librg__entity.native.count + librg__entity.shared.count) \ - && _ent < (librg_is_server() ? librg__config.max_entities : librg__config.max_entities * 2); _ent++) { \ - /* check if entity valid */ \ - { librg__eachmeta(entitymeta_id, _ent); if (!meta->used) continue; } valid++; \ - b32 _used = true; \ - /* check for included components */ \ - for (isize k = 0; k < 8 && _used; k++) { \ - if (FILTER.contains[k] == 0) break; \ - librg__eachmeta(FILTER.contains[k], _ent); \ - if (!meta->used) { _used = false; } \ - } \ - /* check for excluded components */ \ - for (isize k = 0; k < 4 && _used; k++) { \ - if (FILTER.excludes[k] == 0) break; \ - librg__eachmeta(FILTER.excludes[k], _ent); \ - if (meta->used) { _used = false; } \ - } \ - /* execute code */ \ - if (_used) { librg_entity_t NAME = _ent; CODE; } \ - } \ - } while(0) - - #define librg__entity_each(INDEX, NAME, CODE) do { \ - if (INDEX == 0) {break;} u32 entitymeta_id = librg_index_entitymeta(); \ - __dummypool_t *pool = cast(__dummypool_t *)librg__component_pool[INDEX - 1]; librg_assert(pool); \ - for (usize _ent = 0, valid = 0; valid < (librg__entity.native.count + librg__entity.shared.count) \ - && _ent < (librg_is_server() ? librg__config.max_entities : librg__config.max_entities * 2); _ent++) { \ - /*{ librg__eachmeta(entitymeta_id, _ent); if (!meta->used) continue; } valid++;*/ \ - __dummymeta_t *meta = cast(__dummymeta_t *)(pool->entities[_ent]); librg_assert(meta); \ - if (meta->used) { librg_entity_t NAME = _ent; CODE; } \ - } \ - } while (0) - - void librg_entity_each(librg_entity_filter_t filter, librg_entity_cb_t callback) { - librg_entity_eachx(filter, librg_lambda(entity), { callback(entity); }); + librg_inline u32 librg_option_get(librg_option_e option) { + return librg_options[option]; } - - - /** - * - * COMPONENTS - * - */ - // librg_dbg("initializing %s pool, approximate size: %f kb\n", #NAME, (zpl_size_of(ZPL_JOIN3(PREFIX, NAME, _t))*h->count) / 1000.0) \ - - #define librg_component_define_inner(PREFIX, NAME) \ - ZPL_JOIN3(librg__component_, NAME, _pool_t) ZPL_JOIN3(librg__component_, NAME, _pool); \ - \ - librg_inline void ZPL_JOIN2(librg__init_, NAME) (ZPL_JOIN3(librg__component_, NAME, _pool_t) *h, zpl_allocator_t a) { \ - librg_assert(h); h->backing = a; \ - h->count = librg_is_server() ? librg__config.max_entities : librg__config.max_entities * 2; \ - zpl_buffer_init(h->entities, a, zpl_size_of(ZPL_JOIN3(librg_, NAME, _meta_ent_t)) * h->count); \ - zpl_buffer_init(h->data, a, zpl_size_of(ZPL_JOIN3(PREFIX, NAME, _t)) * h->count); \ - zpl_array_append(librg__component_pool, cast(void *)h); \ - h->index = zpl_array_count(librg__component_pool); \ - } \ - librg_inline void ZPL_JOIN2(librg__free_, NAME) (ZPL_JOIN3(librg__component_, NAME, _pool_t) *h) { \ - librg_assert(h); \ - zpl_buffer_free(h->entities, h->backing); \ - zpl_buffer_free(h->data, h->backing); \ - } \ - librg_inline u32 ZPL_JOIN2(librg_index_, NAME)() { \ - return ZPL_JOIN3(librg__component_, NAME, _pool).index; \ - } \ - librg_inline ZPL_JOIN3(PREFIX, NAME, _t) *ZPL_JOIN2(librg_attach_, NAME) (librg_entity_t handle, ZPL_JOIN3(PREFIX, NAME, _t) data) {\ - if (ZPL_JOIN3(librg__component_, NAME, _pool).count == 0) { \ - ZPL_JOIN2(librg__init_, NAME)(&ZPL_JOIN3(librg__component_, NAME, _pool), LIBRG_ENTITY_ALLOCATOR()); \ - } \ - ZPL_JOIN3(librg_, NAME, _meta_ent_t) *meta_ent = (ZPL_JOIN3(librg__component_, NAME, _pool).entities+handle); \ - meta_ent->handle = handle; \ - meta_ent->used = true; \ - *(ZPL_JOIN3(librg__component_, NAME, _pool).data+handle) = data; \ - return (ZPL_JOIN3(librg__component_, NAME, _pool).data+handle); \ - } \ - librg_inline void ZPL_JOIN2(librg_detach_, NAME) (librg_entity_t handle) { \ - if (ZPL_JOIN3(librg__component_, NAME, _pool).count == 0) { \ - ZPL_JOIN2(librg__init_, NAME)(&ZPL_JOIN3(librg__component_, NAME, _pool), LIBRG_ENTITY_ALLOCATOR()); \ - } \ - ZPL_JOIN3(librg_, NAME, _meta_ent_t) *meta_ent = (ZPL_JOIN3(librg__component_, NAME, _pool).entities+handle); \ - meta_ent->used = false; \ - } \ - librg_inline b32 ZPL_JOIN2(librg_has_, NAME) (librg_entity_t handle) { \ - if (ZPL_JOIN3(librg__component_, NAME, _pool).count == 0) { \ - ZPL_JOIN2(librg__init_, NAME)(&ZPL_JOIN3(librg__component_, NAME, _pool), LIBRG_ENTITY_ALLOCATOR()); \ - } \ - ZPL_JOIN3(librg_, NAME, _meta_ent_t) *meta_ent = (ZPL_JOIN3(librg__component_, NAME, _pool).entities+handle); \ - return ((meta_ent->used) && (meta_ent->handle == handle)); \ - } \ - librg_inline ZPL_JOIN3(PREFIX, NAME, _t) * ZPL_JOIN2(librg_fetch_, NAME) (librg_entity_t handle) { \ - if (ZPL_JOIN3(librg__component_, NAME, _pool).count == 0) { \ - ZPL_JOIN2(librg__init_, NAME)(&ZPL_JOIN3(librg__component_, NAME, _pool), LIBRG_ENTITY_ALLOCATOR()); \ - } \ - ZPL_JOIN3(librg_, NAME, _meta_ent_t) *meta_ent = (ZPL_JOIN3(librg__component_, NAME, _pool).entities+handle); \ - if ((meta_ent->used) && (meta_ent->handle == handle)) { \ - return (ZPL_JOIN3(librg__component_, NAME, _pool).data+handle); \ - } \ - else { \ - return NULL; \ - } \ - } - - - /** - * Built-in components defintion - */ - - librg_component_define_inner(librg_, transform); - librg_component_define_inner(librg_, streamable); - librg_component_define_inner(librg_, entitymeta); - librg_component_define_inner(librg_, clientstream); - librg_component_define_inner(librg_, client); - - - - - /** * * EVENTS * */ - librg_inline u64 librg_event_add(u64 id, librg_event_cb_t callback) { - return zplev_add(&librg__events, id, (zplev_cb *)callback); + librg_inline u64 librg_event_add(librg_ctx_t *ctx, u64 id, librg_event_cb callback) { + return zplev_add(&ctx->events, id, (zplev_cb *)callback); } - void librg_event_trigger(u64 id, librg_event_t *event) { - zplev_block *block = zplev_pool_get(&librg__events, id); + void librg_event_trigger(librg_ctx_t *ctx, u64 id, librg_event_t *event) { + librg_assert(event); event->ctx = ctx; + zplev_block *block = zplev_pool_get(&ctx->events, id); if (!block) return; - for (isize i = 0; i < zpl_array_count(*block) && !event->rejected; ++i) { + for (isize i = 0; i < zpl_array_count(*block) && !(event->flags & LIBRG_EVENT_REJECTED); ++i) { (*block)[i](event); } } - librg_inline void librg_event_remove(u64 id, u64 index) { - zplev_remove(&librg__events, id, index); + librg_inline void librg_event_remove(librg_ctx_t *ctx, u64 id, u64 index) { + zplev_remove(&ctx->events, id, index); } librg_inline void librg_event_reject(librg_event_t *event) { librg_assert(event); - event->rejected = true; + event->flags = (librg_event_flag_e)(event->flags | LIBRG_EVENT_REJECTED); + } + + librg_inline b32 librg_event_rejectable(librg_event_t *event) { + librg_assert(event); + return (event->flags & LIBRG_EVENT_REJECTABLE); } librg_inline b32 librg_event_succeeded(librg_event_t *event) { librg_assert(event); - return !event->rejected; + return !(event->flags & LIBRG_EVENT_REJECTED); } @@ -1191,16 +935,23 @@ extern "C" { */ librg_inline void librg_data_init_size(librg_data_t *data, usize size) { - librg_assert_msg(data, "librg_data_init: you need to provide data with &"); - zpl_bs_init(*data, zpl_heap_allocator(), 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); } librg_inline void librg_data_init(librg_data_t *data) { - librg_data_init_size(data, LIBRG_DEFAULT_BS_SIZE); + librg_assert(data); + librg_data_init_size(data, librg_option_get(LIBRG_DEFAULT_DATA_SIZE)); } librg_inline void librg_data_free(librg_data_t *data) { - zpl_bs_free(*data); + librg_assert(data && data->rawptr); + zpl_free(data->allocator, data->rawptr); } librg_inline void librg_data_reset(librg_data_t *data) { @@ -1209,27 +960,39 @@ extern "C" { } librg_inline void librg_data_grow(librg_data_t *data, usize min_size) { - zpl_bs_grow(*data, min_size); + librg_assert(data && data->rawptr); + + usize new_capacity = ZPL_BS_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; } librg_inline usize librg_data_capacity(librg_data_t *data) { - return ZPL_BS_HEADER(*data)->capacity; + librg_assert(data); return data->capacity; } librg_inline usize librg_data_get_rpos(librg_data_t *data) { - return ZPL_BS_HEADER(*data)->read_pos; + librg_assert(data); return data->read_pos; } librg_inline usize librg_data_get_wpos(librg_data_t *data) { - return ZPL_BS_HEADER(*data)->write_pos; + librg_assert(data); return data->write_pos; } librg_inline void librg_data_set_rpos(librg_data_t *data, usize position) { - ZPL_BS_HEADER(*data)->read_pos = position; + librg_assert(data); data->read_pos = position; } librg_inline void librg_data_set_wpos(librg_data_t *data, usize position) { - ZPL_BS_HEADER(*data)->write_pos = position; + librg_assert(data); data->write_pos = position; } @@ -1237,31 +1000,35 @@ extern "C" { * Pointer writers and readers */ - librg_inline void librg_data_rptr(librg_data_t *data, void *ptr, usize size) { - librg_data_rptr_at(data, ptr, size, librg_data_get_rpos(data)); - ZPL_BS_HEADER(*data)->read_pos += size; - } - - librg_inline void librg_data_wptr(librg_data_t *data, void *ptr, usize size) { - librg_data_wptr_at(data, ptr, size, librg_data_get_wpos(data)); - ZPL_BS_HEADER(*data)->write_pos += size; - } - librg_inline void librg_data_rptr_at(librg_data_t *data, void *ptr, usize size, isize position) { - librg_assert(*data); + 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"); - zpl_memcopy(ptr, *data + position, size); + zpl_memcopy(ptr, (char *)data->rawptr + position, size); } librg_inline void librg_data_wptr_at(librg_data_t *data, void *ptr, usize size, isize position) { - librg_assert(*data); + 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(*data + position, ptr, size); + zpl_memcopy((char *)data->rawptr + position, ptr, size); + } + + librg_inline void librg_data_rptr(librg_data_t *data, void *ptr, usize size) { + librg_assert(data && data->rawptr && ptr); + librg_data_rptr_at(data, ptr, size, librg_data_get_rpos(data)); + data->read_pos += size; + } + + librg_inline void librg_data_wptr(librg_data_t *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; } /** @@ -1280,8 +1047,7 @@ extern "C" { } \ librg_inline void ZPL_JOIN3(librg_data_w,TYPE,_at)(librg_data_t *data, TYPE value, isize position) { \ librg_data_wptr_at(data, &value, sizeof(value), position); \ - } \ - + } LIBRG_GEN_DATA_READWRITE( i8); LIBRG_GEN_DATA_READWRITE( u8); @@ -1303,213 +1069,276 @@ extern "C" { /** * - * NETWORK + * ENTITIES * */ - void librg_network_start(librg_address_t addr) { - librg_dbg("librg_network_start\n"); - - librg_peers_init(&librg_network.connected_peers, zpl_heap_allocator()); - - if (librg_is_server()) { - ENetAddress address; + librg_entity_id librg_entity_create(librg_ctx_t *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"); - address.port = addr.port; - address.host = ENET_HOST_ANY; + ++ctx->entity.count; - // setup server host - librg_network.host = enet_host_create(&address, librg__config.max_connections, LIBRG_NETWORK_CHANNELS, 0, 0); - librg_assert_msg(librg_network.host, "could not start server at provided port"); + if (ctx->entity.cursor >= (ctx->max_entities - 1) || ctx->max_entities == 0) { + ctx->entity.cursor = 0; } - else { - ENetAddress address; - address.port = addr.port; - enet_address_set_host(&address, addr.host); + for (; ctx->entity.cursor < ctx->max_entities; ++ctx->entity.cursor) { + librg_entity_t *entity = &ctx->entity.list[ctx->entity.cursor]; librg_assert(entity); - // setup client host - librg_network.host = enet_host_create(NULL, 1, LIBRG_NETWORK_CHANNELS, 57600 / 8, 14400 / 8); - librg_assert_msg(librg_network.host, "could not start client"); + if (entity->flags & LIBRG_ENTITY_ALIVE) continue; - // create peer connecting to server - librg_dbg("connecting to server %s:%u\n", addr.host, addr.port); - librg_network.peer = enet_host_connect(librg_network.host, &address, LIBRG_NETWORK_CHANNELS, 0); - librg_assert_msg(librg_network.peer, "could not setup peer for provided address"); + entity->type = type; + entity->flags = LIBRG_ENTITY_ALIVE; + entity->position = zplm_vec3_zero(); + entity->stream_range = librg_option_get(LIBRG_DEFAULT_STREAM_RANGE) * 1.0f; + entity->stream_branch = NULL; + + return entity->id; } + + librg_assert_msg(false, "no entities to spawn"); + return 0; } - void librg_network_stop() { - librg_dbg("librg_network_stop\n"); - if (librg_network.peer) { - ENetEvent event; + /** + * Entity destructors + */ - // disconnect and emit event - enet_peer_disconnect(librg_network.peer, 0); - enet_host_service(librg_network.host, &event, 100); + b32 librg__entity_destroy(librg_ctx_t *ctx, librg_entity_id id) { + librg_assert(ctx); + librg_assert(librg_entity_valid(ctx, id)); + librg_assert(ctx->entity.count > 0); - // reset our peer - enet_peer_reset(librg_network.peer); + ctx->entity.count--; + librg_entity_t *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) { + zplc_remove(entity->stream_branch, entity->id); + } } - librg_peers_destroy(&librg_network.connected_peers); - } + if (entity->flags & LIBRG_ENTITY_QUERIED) { + zpl_array_free(entity->last_query); + } - /** - * Network helpers - */ + if (entity->flags & LIBRG_ENTITY_IGNORING) { + librg_table_destroy(&entity->ignored); + } - librg_inline b32 librg_is_server() { - return librg__config.mode == LIBRG_MODE_SERVER; + if (librg_is_server(ctx)) { + librg_entity_visibility_set(ctx, entity->id, true); + } + + entity->flags = LIBRG_ENTITY_NONE; + entity->position = zplm_vec3_zero(); + entity->type = 0; + entity->stream_branch = NULL; + + return true; } - librg_inline b32 librg_is_client() { - return librg__config.mode == LIBRG_MODE_CLIENT; + void librg_entity_destroy(librg_ctx_t *ctx, librg_entity_id id) { + librg_assert(librg_is_server(ctx)); + zpl_array_append(ctx->entity.remove_queue, id); } - librg_inline b32 librg_is_connected() { - return librg_network.peer && librg_network.peer->state == ENET_PEER_STATE_CONNECTED; + librg_inline b32 librg_entity_valid(librg_ctx_t *ctx, librg_entity_id id) { + librg_assert(ctx && id < ctx->max_entities); + return (ctx->entity.list[id].flags & LIBRG_ENTITY_ALIVE); } + librg_inline u32 librg_entity_type(librg_ctx_t *ctx, librg_entity_id id) { + librg_assert(librg_entity_valid(ctx, id)); + return ctx->entity.list[id].type; + } - /** - * Network messages - */ + librg_inline librg_entity_t *librg_entity_fetch(librg_ctx_t *ctx, librg_entity_id id) { + if (librg_entity_valid(ctx, id)) + return &ctx->entity.list[id]; - librg_inline void librg_network_add(u64 id, librg_message_handler_t callback) { - librg__messages[id] = callback; + return NULL; } - librg_inline void librg_network_remove(u64 id) { - librg__messages[id] = NULL; + librg_inline void librg_entity_visibility_set(librg_ctx_t *ctx, librg_entity_id entity, b32 state) { + librg_assert(librg_is_server(ctx) && librg_entity_valid(ctx, entity)); + librg_table_set(&ctx->entity.ignored, entity, (u32)!state); } - /** - * Senders - */ + librg_inline b32 librg_entity_visibility_get(librg_ctx_t *ctx, librg_entity_id entity) { + librg_assert(librg_is_server(ctx) && librg_entity_valid(ctx, entity)); + u32 *ignored = librg_table_get(&ctx->entity.ignored, entity); + return !(ignored && *ignored); + } - void librg_message_send_all(librg_void *data, usize size) { - if (librg_is_client()) { - return librg_message_send_to(librg_network.peer, data, size); - } + librg_inline void librg_entity_visibility_set_for(librg_ctx_t *ctx, librg_entity_id entity, librg_entity_id target, b32 state) { + librg_assert(librg_is_server(ctx) && librg_entity_valid(ctx, entity)); + librg_entity_t *blob = librg_entity_fetch(ctx, entity); - librg_message_send_except(NULL, data, size); - } + if (!(blob->flags & LIBRG_ENTITY_IGNORING)) { + blob->flags |= LIBRG_ENTITY_IGNORING; + librg_table_init(&blob->ignored, ctx->allocator); + } - void librg_message_send_to(librg_peer_t peer, librg_void *data, usize size) { - enet_peer_send(peer, LIBRG_NETWORK_MESSAGE_CHANNEL, enet_packet_create( - data, size, ENET_PACKET_FLAG_RELIABLE - )); + librg_table_set(&blob->ignored, target, (u32)!state); } - void librg_message_send_except(librg_peer_t peer, librg_void *data, usize size) { - librg_entity_filter_t filter = { librg_index_client() }; + librg_inline b32 librg_entity_visibility_get_for(librg_ctx_t *ctx, librg_entity_id entity, librg_entity_id target) { + librg_assert(librg_is_server(ctx)); + librg_entity_t *blob = librg_entity_fetch(ctx, entity); - librg_entity_eachx(filter, librg_lambda(entity2), { - librg_client_t *client = librg_fetch_client(entity2); + if (!(blob->flags & LIBRG_ENTITY_IGNORING)) { + return true; + } - if (client->peer != peer) { - enet_peer_send(client->peer, LIBRG_NETWORK_MESSAGE_CHANNEL, enet_packet_create( - data, size, ENET_PACKET_FLAG_RELIABLE - )); - } - }); + u32 *ignored = librg_table_get(&blob->ignored, target); + return !(ignored && *ignored); } - librg_inline void librg_message_send_instream(librg_entity_t entity, librg_void *data, usize size) { - librg_message_send_instream_except(entity, NULL, data, size); + librg_inline void librg_entity_iterate(librg_ctx_t *ctx, u64 flags, librg_entity_cb callback) { + librg_entity_iteratex(ctx, flags, librg_lambda(entity), { callback(ctx, entity); }); } - void librg_message_send_instream_except(librg_entity_t entity, librg_peer_t ignored, librg_void *data, usize size) { - zpl_array_t(librg_entity_t) queue = librg_streamer_query(entity); - for (isize i = 0; i < zpl_array_count(queue); i++) { - librg_entity_t target = queue[i]; + /** + * Queriying + */ - if (!librg_has_client(target)) continue; - librg_peer_t peer = librg_fetch_client(target)->peer; - librg_assert(peer); + // custom query method + void librg__entity_query(librg_ctx_t *ctx, librg_entity_id entity, zplc_t *c, zplc_bounds_t bounds, librg_entity_id **out_entities) { + if (c->nodes == NULL) return; + if (!zplc__intersects(c->dimensions, c->boundary, bounds)) return; - if (peer == ignored) { - continue; - } + 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 = (librg_entity_id)c->nodes[i].tag; + + if (librg_entity_valid(ctx, target)) { + librg_entity_t *blob = librg_entity_fetch(ctx, target); + b32 inside = zplc__contains(c->dimensions, bounds, blob->position.e); - enet_peer_send(peer, LIBRG_NETWORK_MESSAGE_CHANNEL, enet_packet_create( - data, size, ENET_PACKET_FLAG_RELIABLE - )); + if (inside) { + if (!librg_entity_visibility_get(ctx, target)) continue; + if (!librg_entity_visibility_get_for(ctx, target, entity)) continue; + + zpl_array_append(*out_entities, target); + } + } } - zpl_array_free(queue); - } + if(c->trees == NULL) return; - /** - * Main ticker - */ + isize trees_count = zpl_array_count(c->trees); + if (trees_count == 0) return; - void librg_tick() { - zpl_timer_update(librg__timers); + for (i32 i = 0; i < trees_count; ++i) { + librg__entity_query(ctx, entity, (c->trees+i), bounds, out_entities); + } + } - if (!librg_network.host) { - return; /* occasion where we are not started network yet */ + usize librg_entity_query(librg_ctx_t *ctx, librg_entity_id entity, librg_entity_id **out_entities) { + librg_assert(ctx && out_entities); + librg_assert(librg_is_server(ctx)); + librg_entity_t *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); } - ENetEvent event; - librg_data_t data; - librg_data_init(&data); + // reset array to 0 + zpl_array_count(blob->last_query) = 0; - while (enet_host_service(librg_network.host, &event, 0) > 0) { - librg_message_t msg; - msg.data = NULL; - msg.peer = event.peer; - msg.packet = event.packet; + zplc_bounds_t search_bounds; + search_bounds.centre = blob->position; + search_bounds.half_size = zplm_vec3(blob->stream_range, blob->stream_range, blob->stream_range); - switch (event.type) { - case ENET_EVENT_TYPE_RECEIVE: { - // read our data (TODO: remove copying, insert raw poninter from enet) - librg_data_wptr(&data, - event.packet->data, - event.packet->dataLength - ); + librg__entity_query(ctx, entity, &ctx->streamer, search_bounds, &blob->last_query); + *out_entities = blob->last_query; - // get curernt packet id - u64 id = librg_data_ru64(&data); + return zpl_array_count(blob->last_query); + } - if (librg__messages[id]) { - msg.data = &data; - librg__messages[id](&msg); - } - else { - librg_dbg("network: unknown message: %llu\n", id); - } + librg_entity_id librg_entity_find(librg_ctx_t *ctx, librg_peer_t *peer) { + librg_assert(librg_is_server(ctx)); + librg_assert(ctx && peer); + librg_entity_id *entity = librg_table_get(&ctx->network.connected_peers, (u64)peer); + librg_assert(entity); + return *entity; + } - librg_data_reset(&data); - enet_packet_destroy(event.packet); - } break; - case ENET_EVENT_TYPE_CONNECT: librg__messages[LIBRG_CONNECTION_INIT](&msg); break; - case ENET_EVENT_TYPE_DISCONNECT: librg__messages[LIBRG_CONNECTION_DISCONNECT](&msg); break; - case ENET_EVENT_TYPE_NONE: break; + void librg_entity_control_set(librg_ctx_t *ctx, librg_entity_id entity, librg_peer_t *peer) { + librg_assert(ctx && peer && librg_entity_valid(ctx, entity)); + librg_assert(librg_is_server(ctx)); + librg_entity_t *blob = librg_entity_fetch(ctx, entity); + + // replace current entity owner + if (blob->flags & LIBRG_ENTITY_CONTROLLED) { + if (blob->control_peer == peer) { + return; } + + blob->control_peer = peer; + } + // attach new entity owner + else { + blob->flags |= LIBRG_ENTITY_CONTROLLED; + blob->control_peer = peer; } - librg_data_free(&data); + librg_send_to(ctx, LIBRG_CLIENT_STREAMER_ADD, peer, librg_lambda(data), { + librg_data_went(&data, entity); + }); } + librg_inline librg_peer_t *librg_entity_control_get(librg_ctx_t *ctx, librg_entity_id entity) { + librg_assert(ctx && librg_entity_valid(ctx, entity)); + librg_assert(librg_is_server(ctx)); + librg_entity_t *blob = librg_entity_fetch(ctx, entity); + return (blob->flags & LIBRG_ENTITY_CONTROLLED) ? blob->control_peer : NULL; + } + void librg_entity_control_remove(librg_ctx_t *ctx, librg_entity_id entity) { + librg_assert(ctx && librg_entity_valid(ctx, entity)); + librg_assert(librg_is_server(ctx)); + librg_entity_t *blob = librg_entity_fetch(ctx, entity); + 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; + } /** * * NETWORK BUILTIN CALLBACKS * */ -#if 1 - /** - * SHARED - */ + // short helper macro + #define librg__event_create(NAME, MSG) \ + librg_event_t NAME = {0}; \ + NAME.peer = MSG->peer; \ + NAME.data = MSG->data; \ + NAME.flags = LIBRG_EVENT_REMOTE; + + + // SHARED librg_internal void librg__callback_connection_init(librg_message_t *msg) { librg_dbg("librg__connection_init\n"); @@ -1520,22 +1349,33 @@ extern "C" { librg_dbg("librg__connection_init: a new connection attempt at %s:%u.\n", my_host, msg->peer->address.port); #endif - if (librg_is_client()) { - librg_send_to(LIBRG_CONNECTION_REQUEST, msg->peer, librg_lambda(data), { - librg_data_wu16(&data, LIBRG_PLATFORM_ID); - librg_data_wu16(&data, LIBRG_PLATFORM_BUILD); - librg_data_wu16(&data, LIBRG_PLATFORM_PROTOCOL); + if (librg_is_client(msg->ctx)) { + librg_data_t data; + librg_data_init_size(&data, sizeof(u16) * 3); - librg_event_t event = { 0 }; - event.data = &data; - librg_event_trigger(LIBRG_CONNECTION_REQUEST, &event); - }); + librg_data_wu16(&data, librg_option_get(LIBRG_PLATFORM_ID)); + librg_data_wu16(&data, librg_option_get(LIBRG_PLATFORM_BUILD)); + librg_data_wu16(&data, librg_option_get(LIBRG_PLATFORM_PROTOCOL)); + + librg_event_t 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); } } - /** - * SERVER SIDE - */ + // SERVER librg_internal void librg__callback_connection_request(librg_message_t *msg) { librg_dbg("librg__connection_request\n"); @@ -1543,119 +1383,152 @@ extern "C" { u16 platform_build = librg_data_ru16(msg->data); u16 platform_protocol = librg_data_ru16(msg->data); - b32 blocked = (platform_id != LIBRG_PLATFORM_ID || platform_protocol != LIBRG_PLATFORM_PROTOCOL); + 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("NOTICE: librg platform build mismatch client %u, server: %u\n", platform_build, librg_option_get(LIBRG_PLATFORM_BUILD)); + } + + if (blocked) { + librg_dbg("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 + ); + } - if (platform_build != LIBRG_PLATFORM_BUILD) { - librg_dbg("NOTICE: librg platform build mismatch client %u, server: %u\n", platform_build, LIBRG_PLATFORM_BUILD); + librg_event_t event = {0}; { + event.peer = msg->peer; + event.data = msg->data; + event.flags = (LIBRG_EVENT_REJECTABLE | LIBRG_EVENT_REMOTE); } - librg_event_t event = { 0 }; event.data = msg->data; - librg_event_trigger(LIBRG_CONNECTION_REQUEST, &event); + librg_event_trigger(msg->ctx, LIBRG_CONNECTION_REQUEST, &event); if (librg_event_succeeded(&event) && !blocked) { - librg_entity_t entity = librg_entity_create(LIBRG_DEFAULT_CLIENT_TYPE); + librg_entity_id entity = librg_entity_create(msg->ctx, librg_option_get(LIBRG_DEFAULT_CLIENT_TYPE)); + librg_entity_t *blob = librg_entity_fetch(msg->ctx, entity); - // assign default compoenents - librg_client_t client = { msg->peer }; - librg_attach_client(entity, client); - librg_table_init(&librg_fetch_client(entity)->last_snapshot, zpl_heap_allocator()); + // assign default + blob->flags |= LIBRG_ENTITY_CLIENT; + blob->client_peer = msg->peer; + librg_table_init(&blob->last_snapshot, msg->ctx->allocator); // add client peer to storage - librg_peers_set(&librg_network.connected_peers, cast(u64)msg->peer, entity); + librg_table_set(&msg->ctx->network.connected_peers, cast(u64)msg->peer, entity); // send accept - librg_send_to(LIBRG_CONNECTION_ACCEPT, msg->peer, librg_lambda(data), { - librg_data_wu32(&data, entity); + librg_send_to(msg->ctx, LIBRG_CONNECTION_ACCEPT, msg->peer, librg_lambda(data), { + librg_data_went(&data, entity); }); - librg_event_t acptevt = { 0 }; acptevt.entity = entity; - librg_event_trigger(LIBRG_CONNECTION_ACCEPT, &acptevt); + event.data = NULL; + event.entity = blob; + event.flags = LIBRG_EVENT_LOCAL; + + librg_event_trigger(msg->ctx, LIBRG_CONNECTION_ACCEPT, &event); } else { - librg_send_to(LIBRG_CONNECTION_REFUSE, msg->peer, librg_lambda(data), {}); + librg_message_send_to(msg->ctx, LIBRG_CONNECTION_REFUSE, msg->peer, NULL, 0); - librg_event_t rfsevt = { 0 }; - librg_event_trigger(LIBRG_CONNECTION_REFUSE, &rfsevt); + event.data = NULL; + event.flags = LIBRG_EVENT_LOCAL; + + librg_event_trigger(msg->ctx, LIBRG_CONNECTION_REFUSE, &event); } } - /** - * CLIENT SIDE - */ + // CLIENT librg_internal void librg__callback_connection_refuse(librg_message_t *msg) { - librg_event_t event = { 0 }; event.data = msg->data; - librg_event_trigger(LIBRG_CONNECTION_REFUSE, &event); + librg__event_create(event, msg); + librg_event_trigger(msg->ctx, LIBRG_CONNECTION_REFUSE, &event); } - /** - * CLIENT SIDE - */ + // CLIENT librg_internal void librg__callback_connection_accept(librg_message_t *msg) { librg_dbg("librg__connection_accept\n"); - librg_entity_t remote = librg_data_ru32(msg->data); - librg_entity_t entity = librg_entity_create_shared(remote, LIBRG_DEFAULT_CLIENT_TYPE); + librg_entity_id entity = librg_data_rent(msg->data); + librg_entity_t *blob = &msg->ctx->entity.list[entity]; + + msg->ctx->entity.count++; + + blob->type = librg_option_get(LIBRG_DEFAULT_CLIENT_TYPE); + blob->flags = (LIBRG_ENTITY_ALIVE | LIBRG_ENTITY_CLIENT); + blob->position = zplm_vec3_zero(); // add server peer to storage - librg_peers_set(&librg_network.connected_peers, cast(u64)msg->peer, entity); + librg_table_set(&msg->ctx->network.connected_peers, cast(u64)msg->peer, entity); - librg_event_t event = { 0 }; - event.data = msg->data; event.entity = entity; - librg_event_trigger(LIBRG_CONNECTION_ACCEPT, &event); + librg__event_create(event, msg); event.entity = blob; + librg_event_trigger(msg->ctx, LIBRG_CONNECTION_ACCEPT, &event); } - /** - * SHARED - */ + // SHARED librg_internal void librg__callback_connection_disconnect(librg_message_t *msg) { librg_dbg("librg__connection_disconnect\n"); - if (librg_is_server()) { - librg_entity_t *entity = librg_peers_get(&librg_network.connected_peers, cast(u64)msg->peer); - if (!entity || !librg_entity_valid(*entity)) return; + 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)) return; + + librg_entity_t *blob = librg_entity_fetch(msg->ctx, *entity); + + librg__event_create(event, msg); + + event.entity = blob; - librg_event_t event = {0}; - event.entity = *entity; event.data = (librg_void**)msg->peer; - librg_event_trigger(LIBRG_CONNECTION_DISCONNECT, &event); + librg_event_trigger(msg->ctx, LIBRG_CONNECTION_DISCONNECT, &event); - librg_table_destroy(&librg_fetch_client(*entity)->last_snapshot); - librg_detach_client(*entity); - librg_entity_destroy(*entity); + // destroy last snapshot stuff on disconnect + librg_table_destroy(&blob->last_snapshot); + + blob->flags &= ~LIBRG_ENTITY_CLIENT; + blob->client_peer = NULL; + + librg_entity_destroy(msg->ctx, *entity); } else { - librg_event_t event = {0}; - librg_event_trigger(LIBRG_CONNECTION_DISCONNECT, &event); + librg__event_create(event, msg); + librg_event_trigger(msg->ctx, LIBRG_CONNECTION_DISCONNECT, &event); } } + // CLIENT librg_internal void librg__callback_entity_create(librg_message_t *msg) { u32 query_size = librg_data_ru32(msg->data); for (usize i = 0; i < query_size; ++i) { - librg_entity_t entity = librg_data_rentity(msg->data); - u32 type = librg_data_ru32(msg->data); + librg_entity_id entity = librg_data_rent(msg->data); + u32 type = librg_data_ru32(msg->data); + + zplm_vec3_t position; + librg_data_rptr(msg->data, &position, sizeof(zplm_vec3_t)); + + // Create new entity on client side + librg_entity_t *blob = &msg->ctx->entity.list[entity]; + librg_assert(blob); - librg_transform_t transform; - librg_data_rptr(msg->data, &transform, sizeof(transform)); + blob->type = type; + blob->flags = LIBRG_ENTITY_ALIVE; + blob->position = position; - librg_entity_create_shared(entity, type); - librg_attach_transform(entity, transform); + msg->ctx->entity.count++; - librg_event_t event = {0}; - event.data = msg->data; event.entity = entity; - librg_event_trigger(LIBRG_ENTITY_CREATE, &event); + librg__event_create(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_t entity = librg_data_rentity(msg->data); + librg_entity_id entity = librg_data_rent(msg->data); - if (librg_entity_valid(entity)) { - librg_event_t event = {0}; - event.data = msg->data; event.entity = entity; - librg_event_trigger(LIBRG_ENTITY_REMOVE, &event); - librg_entity_destroy(entity); + if (librg_entity_valid(msg->ctx, entity)) { + librg__event_create(event, msg); + event.entity = librg_entity_fetch(msg->ctx, entity); + librg_event_trigger(msg->ctx, LIBRG_ENTITY_REMOVE, &event); + librg__entity_destroy(msg->ctx, entity); } else { librg_dbg("unexpected entity %u on remove\n", entity); @@ -1663,201 +1536,151 @@ extern "C" { } } + // CLIENT librg_internal void librg__callback_entity_update(librg_message_t *msg) { u32 query_size = librg_data_ru32(msg->data); for (usize i = 0; i < query_size; ++i) { - librg_entity_t entity = librg_data_ru32(msg->data); + librg_entity_id entity = librg_data_rent(msg->data); - librg_transform_t transform; - librg_data_rptr(msg->data, &transform, sizeof(transform)); + zplm_vec3_t position; + librg_data_rptr(msg->data, &position, sizeof(position)); - if (!librg_entity_valid(entity)) { + if (!librg_entity_valid(msg->ctx, entity)) { continue; } - *librg_fetch_transform(entity) = transform; + librg_entity_t *blob = librg_entity_fetch(msg->ctx, entity); + blob->position = position; - librg_event_t event = {0}; - event.data = msg->data; event.entity = entity; - librg_event_trigger(LIBRG_ENTITY_UPDATE, &event); + librg__event_create(event, msg); event.entity = blob; + librg_event_trigger(msg->ctx, LIBRG_ENTITY_UPDATE, &event); } } + // CLIENT librg_internal void librg__callback_entity_client_streamer_add(librg_message_t *msg) { - librg_entity_t entity = librg_data_ru32(msg->data); + librg_entity_id entity = librg_data_rent(msg->data); - if (!librg_entity_valid(entity)) { + if (!librg_entity_valid(msg->ctx, entity)) { librg_dbg("trying to add unknown entity to clientstream!"); return; } - librg_clientstream_t *cli_stream = librg_fetch_clientstream(entity); + librg_entity_t *blob = librg_entity_fetch(msg->ctx, entity); - if (!cli_stream) { - librg_clientstream_t cs = {0}; - librg_attach_clientstream(entity, cs); + if (!(blob->flags & LIBRG_ENTITY_CONTROLLED)) { + blob->flags |= LIBRG_ENTITY_CONTROLLED; - librg_event_t event = {0}; - event.data = msg->data; event.entity = entity; - librg_event_trigger(LIBRG_CLIENT_STREAMER_ADD, &event); + librg_entity_t *blob = librg_entity_fetch(msg->ctx, entity); + + librg__event_create(event, msg); event.entity = blob; + librg_event_trigger(msg->ctx, LIBRG_CLIENT_STREAMER_ADD, &event); } } + // CLIENT librg_internal void librg__callback_entity_client_streamer_remove(librg_message_t *msg) { - librg_entity_t entity = librg_data_ru32(msg->data); + librg_entity_id entity = librg_data_rent(msg->data); - if (!librg_entity_valid(entity)) { + if (!librg_entity_valid(msg->ctx, entity)) { librg_dbg("trying to remove unknown entity from clientstream!\n"); return; } - librg_clientstream_t *cli_stream = librg_fetch_clientstream(entity); + librg_entity_t *blob = librg_entity_fetch(msg->ctx, entity); - if (cli_stream) { - librg_detach_clientstream(entity); + if (blob->flags & LIBRG_ENTITY_CONTROLLED) { + blob->flags &= ~LIBRG_ENTITY_CONTROLLED; - librg_event_t event = {0}; - event.data = msg->data; event.entity = entity; - librg_event_trigger(LIBRG_CLIENT_STREAMER_REMOVE, &event); + librg__event_create(event, msg); event.entity = blob; + librg_event_trigger(msg->ctx, LIBRG_CLIENT_STREAMER_REMOVE, &event); } } + + // SERVER librg_internal void librg__callback_entity_client_streamer_update(librg_message_t *msg) { u32 amount = librg_data_ru32(msg->data); for (usize i = 0; i < amount; i++) { - librg_entity_t entity = librg_data_ru32(msg->data); + librg_entity_id entity = librg_data_rent(msg->data); u32 size = librg_data_ru32(msg->data); - if (!librg_entity_valid(entity)) { + if (!librg_entity_valid(msg->ctx, entity)) { librg_dbg("invalid entity on client streamer update\n"); librg_data_set_rpos(msg->data, librg_data_get_rpos(msg->data) + size); librg_assert(false); continue; } - librg_clientstream_t *cli_stream = librg_fetch_clientstream(entity); + librg_entity_t *blob = librg_entity_fetch(msg->ctx, entity); - if (!cli_stream || cli_stream->peer != msg->peer) { + if (!(blob->flags & LIBRG_ENTITY_CONTROLLED) || blob->control_peer != msg->peer) { librg_dbg("no component, or peer is different\n"); librg_data_set_rpos(msg->data, librg_data_get_rpos(msg->data) + size); continue; } - librg_event_t event = {0}; - event.data = msg->data; event.entity = entity; - librg_event_trigger(LIBRG_CLIENT_STREAMER_UPDATE, &event); - - librg_transform_t transform; - librg_data_rptr(msg->data, &transform, sizeof(transform)); - *librg_fetch_transform(entity) = transform; + librg__event_create(event, msg); event.entity = blob; + librg_event_trigger(msg->ctx, LIBRG_CLIENT_STREAMER_UPDATE, &event); + librg_data_rptr(msg->data, &blob->position, sizeof(blob->position)); } } -#endif - - - - - - /** - * - * STREAMER - * - */ - - - void librg_streamer_set_visible(librg_entity_t entity, b32 state) { - librg_table_set(&librg__entity.ignored, entity, (u32)!state); - } - - void librg_streamer_set_visible_for(librg_entity_t entity, librg_entity_t target, b32 state) { - librg_assert(librg_fetch_entitymeta(entity)); - librg_table_set(&librg_fetch_entitymeta(entity)->ignored, target, (u32)!state); - } - - zpl_array_t(librg_entity_t) librg_streamer_query(librg_entity_t entity) { - zpl_array_t(zplc_node_t) search_temp; - zpl_array_t(librg_entity_t) search_result; - - zpl_array_init(search_temp, zpl_heap_allocator()); - zpl_array_init(search_result, zpl_heap_allocator()); - librg_transform_t *transform = librg_fetch_transform(entity); - librg_streamable_t *streamable = librg_fetch_streamable(entity); - librg_assert(transform && streamable); - - zplc_bounds_t search_bounds; - search_bounds.centre = transform->position; - search_bounds.half_size = zplm_vec3((f32)streamable->range, (f32)streamable->range, (f32)streamable->range); - - zplc_query(&librg__streamer, search_bounds, &search_temp); - - for (isize i = 0; i < zpl_array_count(search_temp); i++) { - librg_entity_t target = (u32)search_temp[i].tag; - if (!librg_entity_valid(target)) continue; - - u32 *global = librg_table_get(&librg__entity.ignored, target); - u32 *local = librg_table_get(&librg_fetch_entitymeta(target)->ignored, entity); - - if ((global && *global) || (local && *local)) continue; - - zpl_array_append(search_result, target); - } - zpl_array_free(search_temp); - return search_result; - } /** * CLIENT-SIDE * * Responsive for updating the client side streamer */ - librg_internal void librg__update_client() { + librg_internal void librg__execute_client_update(librg_ctx_t *ctx) { u32 amount = 0; librg_data_t data; librg_data_init(&data); - librg_data_wu64(&data, LIBRG_CLIENT_STREAMER_UPDATE); + librg_data_wmid(&data, LIBRG_CLIENT_STREAMER_UPDATE); librg_data_wu32(&data, 0); // amount of entities to be sent (updates) - librg_entity_filter_t filter = { - librg_index_clientstream(), - librg_index_streamable(), - }; - - librg_entity_eachx(filter, librg_lambda(entity), { - librg_transform_t *transform = librg_fetch_transform(entity); + librg_entity_iteratex(ctx, LIBRG_ENTITY_CONTROLLED, librg_lambda(entity), { + librg_entity_t *blob = librg_entity_fetch(ctx, entity); librg_data_t subdata; librg_data_init(&subdata); - librg_event_t event = {0}; - event.data = &subdata; event.entity = entity; - librg_event_trigger(LIBRG_CLIENT_STREAMER_UPDATE, &event); + librg_event_t 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); - librg_data_wptr(&subdata, transform, sizeof(librg_transform_t)); - librg_data_wu32(&data, entity); - librg_data_wu32(&data, librg_data_get_wpos(&subdata)); + // check if user rejected the event + if (!(event.flags & LIBRG_EVENT_REJECTED)) { + librg_data_wptr(&subdata, &blob->position, sizeof(zplm_vec3_t)); + librg_data_went(&data, entity); + librg_data_wu32(&data, librg_data_get_wpos(&subdata)); - // write sub-bitstream to main bitstream - librg_data_wptr(&data, subdata, librg_data_get_wpos(&subdata)); - librg_data_free(&subdata); + // write sub-bitstream to main bitstream + librg_data_wptr(&data, subdata.rawptr, librg_data_get_wpos(&subdata)); + librg_data_free(&subdata); - amount++; + amount++; + } }); if (amount < 1) { return; } - // write amountafter packet id - librg_data_wu32_at(&data, amount, sizeof(u64)); + // write amount after packet id + librg_data_wu32_at(&data, amount, sizeof(LIBRG_MESSAGE_ID)); - enet_peer_send(librg_network.peer, LIBRG_NETWORK_STREAM_SECONDARY_CHANNEL, enet_packet_create( - data, librg_data_get_wpos(&data), ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT - )); + enet_peer_send(ctx->network.peer, librg_option_get(LIBRG_NETWORK_SECONDARY_CHANNEL), + enet_packet_create(data.rawptr, librg_data_get_wpos(&data), ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT) + ); librg_data_free(&data); } @@ -1865,47 +1688,47 @@ extern "C" { /** * SERVER-SIDE * - * Responsive for udpating the server-side streamer + * Responsive for updating the server-side streamer */ - librg_internal void librg__update_server() { - librg_entity_filter_t filter = { librg_index_client() }; + void librg__execute_server_entity_update_proc(librg_ctx_t *ctx, librg_data_t *reliable, librg_data_t *unreliable, usize offset, usize count) { + for (isize j = offset; j < offset+count; j++) { + librg_entity_t *blob = &ctx->entity.list[j]; - // create data and write inital stuff - librg_data_t for_create; - librg_data_t for_update; + if (!(blob->flags & LIBRG_ENTITY_CLIENT)) continue; - librg_data_init(&for_create); - librg_data_init(&for_update); - - librg_entity_eachx(filter, librg_lambda(player), { - librg_client_t *client = librg_fetch_client(player); + // assume that entity is valid, having the client + librg_entity_id player = j; // get old, and preapre new snapshot handlers - librg_table_t *last_snapshot = &client->last_snapshot; + librg_table_t *last_snapshot = &blob->last_snapshot; librg_table_t next_snapshot = { 0 }; - librg_table_init(&next_snapshot, zpl_heap_allocator()); + librg_table_init(&next_snapshot, ctx->allocator); // fetch entities in the steram zone - zpl_array_t(librg_entity_t) queue = librg_streamer_query(player); + zpl_array_t(librg_entity_id) queue; + usize queue_count = librg_entity_query(ctx, player, &queue); u32 created_entities = 0; - u32 updated_entities = (u32) zpl_array_count(queue); + u32 updated_entities = cast(u32)queue_count; u32 removed_entities = 0; // write packet headers - librg_data_wu64(&for_create, LIBRG_ENTITY_CREATE); - librg_data_wu32(&for_create, created_entities); + librg_data_wmid(reliable, LIBRG_ENTITY_CREATE); + librg_data_wu32(reliable, created_entities); - librg_data_wu64(&for_update, LIBRG_ENTITY_UPDATE); - librg_data_wu32(&for_update, updated_entities); + librg_data_wmid(unreliable, LIBRG_ENTITY_UPDATE); + librg_data_wu32(unreliable, updated_entities); // add entity creates and updates - for (isize i = 0; i < zpl_array_count(queue); ++i) { - librg_entity_t entity = (u32)queue[i]; + for (isize 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_t *eblob = librg_entity_fetch(ctx, entity); + // write create if (!existed_in_last) { updated_entities--; @@ -1916,36 +1739,62 @@ extern "C" { // 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_wu32(&for_create, entity); - librg_data_wu32(&for_create, librg_fetch_entitymeta(entity)->type); - librg_data_wptr(&for_create, librg_fetch_transform(entity), sizeof(librg_transform_t)); + librg_data_went(reliable, entity); + librg_data_wu32(reliable, eblob->type); + librg_data_wptr(reliable, &eblob->position, sizeof(eblob->position)); // request custom data from user - librg_event_t event = {0}; - event.data = &for_create; event.entity = entity; - librg_event_trigger(LIBRG_ENTITY_CREATE, &event); + librg_event_t event = {0}; { + 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); - // fetch client streamer - librg_clientstream_t *cli_stream = librg_fetch_clientstream(entity); - // if this entity is client streamable and this client is owner - if (cli_stream && cli_stream->peer == client->peer) { + if ((eblob->flags & LIBRG_ENTITY_CONTROLLED) && eblob->control_peer == blob->client_peer) { updated_entities--; } // write update else { - librg_data_wu32(&for_update, entity); - librg_data_wptr(&for_update, librg_fetch_transform(entity), sizeof(librg_transform_t)); + + // 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_t event = {0}; - event.data = &for_update; event.entity = entity; - librg_event_trigger(LIBRG_ENTITY_UPDATE, &event); + librg_event_t event = { 0 }; { + 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); + } } } @@ -1954,245 +1803,578 @@ extern "C" { } // write our calcualted amounts right after packet id (from the beginning) - librg_data_wu32_at(&for_create, created_entities, sizeof(u64)); - librg_data_wu32_at(&for_update, updated_entities, sizeof(u64)); + librg_data_wu32_at(reliable, created_entities, sizeof(LIBRG_MESSAGE_ID)); + librg_data_wu32_at(unreliable, updated_entities, sizeof(LIBRG_MESSAGE_ID)); // save pos for remove data counter - usize write_pos = librg_data_get_wpos(&for_create); - librg_data_wu32(&for_create, 0); + 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_t entity = (u32)last_snapshot->entries[i].key; + 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_wu32(&for_create, entity); + librg_data_went(reliable, entity); removed_entities++; // write the rest - librg_event_t event = {0}; - event.data = &for_create; event.entity = entity; - librg_event_trigger(LIBRG_ENTITY_REMOVE, &event); + librg_event_t event = { 0 }; { + event.data = reliable; + event.entity = blob; + 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); + } } - librg_data_wu32_at(&for_create, removed_entities, write_pos); + // write remove amount + librg_data_wu32_at(reliable, removed_entities, write_pos); - librg_table_destroy(&client->last_snapshot); + // swap snapshot tables + librg_table_destroy(&blob->last_snapshot); *last_snapshot = next_snapshot; +#ifdef LIBRG_MULTITHREADED + if (librg_option_get(LIBRG_MAX_THREADS_PER_UPDATE) > 0) zpl_mutex_lock(ctx->threading.send_lock); +#endif // send the data, via differnt channels and reliability setting - if (librg_data_get_wpos(&for_create) > (sizeof(u64) + sizeof(u32) * 2)) { - enet_peer_send(client->peer, LIBRG_NETWORK_STREAM_PRIMARY_CHANNEL, enet_packet_create( - for_create, librg_data_get_wpos(&for_create), ENET_PACKET_FLAG_RELIABLE)); + 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(client->peer, LIBRG_NETWORK_STREAM_SECONDARY_CHANNEL, enet_packet_create( - for_update, librg_data_get_wpos(&for_update), ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT)); + enet_peer_send(blob->client_peer, librg_option_get(LIBRG_NETWORK_SECONDARY_CHANNEL), + enet_packet_create(unreliable->rawptr, librg_data_get_wpos(unreliable), ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT) + ); + +#ifdef LIBRG_MULTITHREADED + if (librg_option_get(LIBRG_MAX_THREADS_PER_UPDATE) > 0) zpl_mutex_unlock(ctx->threading.send_lock); +#endif // and cleanup - zpl_array_free(queue); - librg_data_reset(&for_create); - librg_data_reset(&for_update); - }); + librg_data_reset(reliable); + librg_data_reset(unreliable); + } + } + +#ifdef LIBRG_MULTITHREADED + isize librg__execute_server_entity_update_worker(zpl_thread_t *thread) { + librg_update_worker_si_t *si = cast(librg_update_worker_si_t *)thread->user_data; + librg_ctx_t *ctx = si->ctx; + + librg_data_t reliable, unreliable; + librg_data_init(&reliable); + librg_data_init(&unreliable); + + while (true) { + i32 signal = zpl_atomic32_load(&ctx->threading.signal); - librg_data_free(&for_create); - librg_data_free(&for_update); + while (signal == librg_thread_idle) { + zpl_sleep_ms(1); + signal = zpl_atomic32_load(&ctx->threading.signal); + zpl_mfence(); + }; + + if (signal == librg_thread_exit) break; + + librg__execute_server_entity_update_proc(ctx, &reliable, &unreliable, si->offset, si->count); + zpl_atomic32_fetch_add(&ctx->threading.work_count, -1); + } + + librg_data_free(&reliable); + librg_data_free(&unreliable); + + zpl_free(ctx->allocator, si); + thread->return_value = 0; + + return 0; } +#endif - librg_inline void librg__entity_execute_insert() { - librg_entity_filter_t filter = { librg_index_streamable() }; + librg_internal void librg__execute_server_entity_update(librg_ctx_t *ctx) { + librg_assert(ctx); + + if (librg_option_get(LIBRG_MAX_THREADS_PER_UPDATE) == 0) { + librg__execute_server_entity_update_proc(ctx, &ctx->stream_upd_reliable, &ctx->stream_upd_unreliable, 0, ctx->max_entities); + return; + } + +#ifdef LIBRG_MULTITHREADED + zpl_atomic32_store(&ctx->threading.signal, librg_thread_work); + zpl_atomic32_store(&ctx->threading.work_count, librg_option_get(LIBRG_MAX_THREADS_PER_UPDATE)); + + i32 work_count = zpl_atomic32_load(&ctx->threading.work_count); + while (work_count > 0) { + zpl_sleep_ms(1); + work_count = zpl_atomic32_load(&ctx->threading.work_count); + zpl_mfence(); + } + + zpl_atomic32_store(&ctx->threading.signal, librg_thread_idle); +#endif + } - // clear - zplc_clear(&librg__streamer); + librg_inline void librg__execute_server_entity_insert(librg_ctx_t *ctx) { + librg_assert(ctx); // fill up - librg_entity_eachx(filter, librg_lambda(entity), { - librg_transform_t *transform = librg_fetch_transform(entity); - librg_assert(transform); + librg_entity_iteratex(ctx, LIBRG_ENTITY_ALIVE, entity, { + librg_entity_t *blob = &ctx->entity.list[entity]; zplc_node_t node = { 0 }; - node.tag = entity; - node.position = transform->position; - - zplc_insert(&librg__streamer, node); + node.tag = entity; + node.position = blob->position; + + if (blob->stream_branch == NULL) { + blob->stream_branch = zplc_insert(&ctx->streamer, node); + } + else { + zplc_t *branch = blob->stream_branch; + b32 contains = zplc__contains(branch->dimensions, branch->boundary, blob->position.e); + + if (!contains) { + zplc_remove(branch, entity); + blob->stream_branch = zplc_insert(&ctx->streamer, node); + } + } }); } - librg_internal ZPL_TIMER_CB(librg__tick_cb) { - u64 start = zpl_utc_time_now(); - - if (librg_is_client()) { - return librg__update_client(); + librg_inline void librg__execute_server_entity_destroy(librg_ctx_t *ctx) { + for (isize i = 0; i < zpl_array_count(ctx->entity.remove_queue); i++) { + librg__entity_destroy(ctx, ctx->entity.remove_queue[i]); } - else { - // create the server cull tree - librg__entity_execute_insert(); - librg__update_server(); + zpl_array_clear(ctx->entity.remove_queue); + } + + librg_internal void librg__tick_cb(void *data) { + u64 start = zpl_utc_time_now(); + librg_ctx_t *ctx = (librg_ctx_t *)data; + librg_assert(ctx); - // destroy entities queued for removal - librg__entity_execute_destroy(); + if (librg_is_server(ctx)) { + librg__execute_server_entity_insert(ctx); /* create the server cull tree */ + librg__execute_server_entity_update(ctx); /* create and send updates to all clients */ + librg__execute_server_entity_destroy(ctx); /* destroy queued entities */ + } else { + librg__execute_client_update(ctx); /* send information about client updates */ } - librg_dbg(" [update: %fms] \r", (zpl_utc_time_now() - start) / 1000.f); + ctx->last_update = (zpl_utc_time_now() - start) / 1000.0f; } - void librg_streamer_client_set(librg_entity_t entity, librg_peer_t peer) { - librg_assert(peer); - librg_clientstream_t *component = librg_fetch_clientstream(entity); - // replace current entity owner - if (component) { - if (component->peer == peer) { - return; + + /** + * + * NETWORK + * + */ + + void librg_network_start(librg_ctx_t *ctx, librg_address_t addr) { + librg_dbg("librg_network_start\n"); + + librg_table_init(&ctx->network.connected_peers, ctx->allocator); + + if (librg_is_server(ctx)) { + ENetAddress address; + + if (addr.host && zpl_strcmp(addr.host, "localhost") == 0) { + enet_address_set_host(&address, addr.host); + } else { + address.host = ENET_HOST_ANY; } - component->peer = peer; + 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"); } - // attach new entity owner else { - librg_clientstream_t cs = { peer }; - librg_attach_clientstream(entity, cs); + ENetAddress address; + + 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"); + + // create peer connecting to server + librg_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"); } + } - librg_send_to(LIBRG_CLIENT_STREAMER_ADD, peer, librg_lambda(data), { - librg_data_wentity(&data, entity); - }); + void librg_network_stop(librg_ctx_t *ctx) { + librg_dbg("librg_network_stop\n"); + + if (ctx->network.peer) { + ENetEvent event; + + // disconnect and emit event + enet_peer_disconnect(ctx->network.peer, 0); + enet_host_service(ctx->network.host, &event, 100); + + // reset our peer + enet_peer_reset(ctx->network.peer); + } + + // destroy all the entities that are currently created + for (usize i = 0; i < ctx->max_entities; ++i) { + if (librg_entity_valid(ctx, i)) { + librg_event_t event = {0}; { + event.entity = librg_entity_fetch(ctx, i); + event.flags = LIBRG_EVENT_LOCAL; + } + + librg_event_trigger(ctx, LIBRG_ENTITY_REMOVE, &event); + librg__entity_destroy(ctx, i); + } + } + + librg_table_destroy(&ctx->network.connected_peers); + } + + /** + * Network helpers + */ + + librg_inline b32 librg_is_server(librg_ctx_t *ctx) { + return ctx->mode == LIBRG_MODE_SERVER; + } + + librg_inline b32 librg_is_client(librg_ctx_t *ctx) { + return ctx->mode == LIBRG_MODE_CLIENT; + } + + librg_inline b32 librg_is_connected(librg_ctx_t *ctx) { + return ctx->network.peer && ctx->network.peer->state == ENET_PEER_STATE_CONNECTED; + } + + + /** + * Network messages + */ + + librg_inline void librg_network_add(librg_ctx_t *ctx, LIBRG_MESSAGE_ID id, librg_message_cb callback) { + ctx->messages[id] = callback; + } + + librg_inline void librg_network_remove(librg_ctx_t *ctx, LIBRG_MESSAGE_ID id) { + ctx->messages[id] = NULL; } - void librg_streamer_client_remove(librg_entity_t entity) { - librg_clientstream_t *component = librg_fetch_clientstream(entity); + /** + * Senders + */ - if (!component) { + void librg_message_send_all(librg_ctx_t *ctx, LIBRG_MESSAGE_ID id, void *data, usize size) { + if (librg_is_client(ctx)) { + librg_message_send_to(ctx, id, ctx->network.peer, data, size); return; } - librg_send_to(LIBRG_CLIENT_STREAMER_REMOVE, component->peer, librg_lambda(data), { - librg_data_wentity(&data, entity); + librg_message_send_except(ctx, id, NULL, data, size); + } + + void librg_message_send_to(librg_ctx_t *ctx, LIBRG_MESSAGE_ID id, librg_peer_t *peer, void *data, usize size) { + zpl_unused(ctx); + + librg_packet_t *packet = enet_packet_create_offset( + data, size, sizeof(LIBRG_MESSAGE_ID), ENET_PACKET_FLAG_RELIABLE + ); + + // write id at the beginning + zpl_memcopy(packet->data, &id, sizeof(LIBRG_MESSAGE_ID)); + enet_peer_send(peer, librg_option_get(LIBRG_NETWORK_MESSAGE_CHANNEL), packet); + } + + void librg_message_send_except(librg_ctx_t *ctx, LIBRG_MESSAGE_ID id, librg_peer_t *peer, void *data, usize size) { + librg_entity_iteratex(ctx, LIBRG_ENTITY_CLIENT, librg_lambda(entity), { + librg_entity_t *blob = librg_entity_fetch(ctx, entity); + + if (blob->client_peer != peer) { + librg_packet_t *packet = enet_packet_create_offset( + data, size, sizeof(LIBRG_MESSAGE_ID), ENET_PACKET_FLAG_RELIABLE + ); + + // write id at the beginning + zpl_memcopy(packet->data, &id, sizeof(LIBRG_MESSAGE_ID)); + enet_peer_send(blob->client_peer, librg_option_get(LIBRG_NETWORK_MESSAGE_CHANNEL), packet); + } }); + } - librg_detach_clientstream(entity); + librg_inline void librg_message_send_instream(librg_ctx_t *ctx, LIBRG_MESSAGE_ID id, librg_entity_id entity, void *data, usize size) { + librg_message_send_instream_except(ctx, id, entity, NULL, data, size); } - librg_entity_t librg_get_client_entity(librg_peer_t peer) { - librg_assert(peer); - librg_entity_t *entity = librg_peers_get(&librg_network.connected_peers, (u64)peer); - librg_assert(entity); + void librg_message_send_instream_except(librg_ctx_t *ctx, LIBRG_MESSAGE_ID id, librg_entity_id entity, librg_peer_t * ignored, void *data, usize size) { + zpl_array_t(librg_entity_id) queue; + usize count = librg_entity_query(ctx, entity, &queue); - return *entity; + for (isize i = 0; i < count; i++) { + librg_entity_id target = queue[i]; + + librg_entity_t *blob = librg_entity_fetch(ctx, target); + if (!(blob->flags & LIBRG_ENTITY_CLIENT)) continue; + + librg_peer_t *peer = blob->client_peer; + librg_assert(peer); + + if (peer == ignored) { + continue; + } + + librg_packet_t *packet = enet_packet_create_offset( + data, size, sizeof(LIBRG_MESSAGE_ID), ENET_PACKET_FLAG_RELIABLE + ); + + // write id at the beginning + zpl_memcopy(packet->data, &id, sizeof(LIBRG_MESSAGE_ID)); + enet_peer_send(peer, librg_option_get(LIBRG_NETWORK_MESSAGE_CHANNEL), packet); + } + } + + /** + * Main ticker + */ + + void librg_tick(librg_ctx_t *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_t 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_t data = {0}; + + data.rawptr = event.packet->data; + data.capacity = event.packet->dataLength; + + // get curernt packet id + LIBRG_MESSAGE_ID id = librg_data_rmid(&data); + + if (ctx->messages[id]) { + msg.data = &data; + ctx->messages[id](&msg); + } + else { + /* print message id, casted to biggest size */ + librg_dbg("network: unknown message: %llu\n", (u64)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_NONE: break; + } + } } + /** * * CORE * */ - void librg_init(librg_config_t config) { + void librg_init(librg_ctx_t *ctx) { librg_dbg("librg_init\n"); - librg__config = config; + + #define librg_set_default(expr, value) if (!expr) expr = value // apply default settings (if no user provided) - librg__set_default(librg__config.tick_delay, 32); - librg__set_default(librg__config.max_connections, 16); - librg__set_default(librg__config.max_entities, 8192); - librg__set_default(librg__config.world_size.x, 4096.0f); - librg__set_default(librg__config.world_size.y, 4096.0f); - librg__set_default(librg__config.mode, LIBRG_MODE_SERVER); - - // init entity system - librg__entity.shared.limit_lower = 0; - librg__entity.shared.limit_upper = librg__config.max_entities; - librg__entity.native.limit_lower = librg__config.max_entities; - librg__entity.native.limit_upper = librg__config.max_entities * 2; - - zpl_array_init(librg__component_pool, LIBRG_ENTITY_ALLOCATOR()); - zpl_array_init(librg__entity_remove_queue, zpl_heap_allocator()); + librg_set_default(ctx->tick_delay, 32); + 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_t*)zpl_alloc(ctx->allocator, sizeof(librg_entity_t) * ctx->max_entities); + for (librg_entity_id i = 0; i < ctx->max_entities; i++) { + librg_entity_t blob = { i, 0 }; + ctx->entity.list[i] = blob; + } + + zpl_array_init(ctx->entity.remove_queue, ctx->allocator); + + for (i8 i = 0; i < LIBRG_DATA_STREAMS_AMOUNT; ++i) { + librg_data_init(&ctx->streams[i]); + } // streamer zplc_bounds_t world = {0}; world.centre = zplm_vec3(0, 0, 0); - world.half_size = zplm_vec3(librg__config.world_size.x, librg__config.world_size.y, 0); - zplc_init(&librg__streamer, zpl_heap_allocator(), zplc_dim_2d_ev, world, 4); - librg_table_init(&librg__entity.ignored, zpl_heap_allocator()); + world.half_size = zplm_vec3(ctx->world_size.x, ctx->world_size.y, ctx->world_size.z); + zplc_dim_e dimension = ctx->world_size.z == 0.0f ? zplc_dim_2d_ev : zplc_dim_3d_ev; + + if (ctx->min_branch_size.x == -1.0f && + ctx->min_branch_size.y == -1.0f && + ctx->min_branch_size.z == -1.0f) { + zplm_vec3_t no_min_bounds = { 0 }; + ctx->min_branch_size = no_min_bounds; + } + + zplc_init(&ctx->streamer, ctx->allocator, dimension, world, ctx->min_branch_size, librg_option_get(LIBRG_MAX_ENTITIES_PER_BRANCH)); + librg_table_init(&ctx->entity.ignored, ctx->allocator); + +#ifdef LIBRG_MULTITHREADED + // threading + usize thread_count = librg_option_get(LIBRG_MAX_THREADS_PER_UPDATE); + if (thread_count > 0) { + librg_log("librg: warning, LIBRG_MAX_THREADS_PER_UPDATE is experimental, and highly unstable!\n"); + + ctx->threading.update_workers = (zpl_thread_t *)zpl_alloc(ctx->allocator, sizeof(zpl_thread_t)*thread_count); + usize step = ctx->max_entities / thread_count; + ctx->threading.send_lock = (zpl_mutex_t *)zpl_alloc(ctx->allocator, sizeof(zpl_mutex_t)); + zpl_mutex_init(ctx->threading.send_lock); + + usize offset = 0; + for (usize i = 0; i < thread_count; ++i) { + zpl_thread_init(ctx->threading.update_workers + i); + + librg_update_worker_si_t *si = (librg_update_worker_si_t *)zpl_alloc(ctx->allocator, sizeof(librg_update_worker_si_t)); + librg_update_worker_si_t si_ = { 0 }; + *si = si_; + si->count = step; + si->offset = offset; + si->ctx = ctx; + si->id = i; + + offset += step; + + zpl_thread_start(ctx->threading.update_workers + i, librg__execute_server_entity_update_worker, si); + } + } +#endif // events - zplev_init(&librg__events, zpl_heap_allocator()); - zpl_buffer_init(librg__messages, zpl_heap_allocator(), LIBRG_NETWORK_MESSAGE_CAPACITY); + zplev_init(&ctx->events, ctx->allocator); + zpl_buffer_init(ctx->messages, ctx->allocator, librg_option_get(LIBRG_NETWORK_CAPACITY)); // init timers - zpl_array_init(librg__timers, zpl_heap_allocator()); - zpl_timer_t *tick_timer = zpl_timer_add(librg__timers); - zpl_timer_set(tick_timer, 1000 * librg__config.tick_delay, -1, librg__tick_cb); + zpl_array_init(ctx->timers, ctx->allocator); + zpl_timer_t *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, 1000 * ctx->tick_delay, -1, librg__tick_cb); zpl_timer_start(tick_timer, 1000); // network - librg_assert_msg(enet_initialize() == 0, "cannot initialize enet"); - - // add event handlers for our network stuufz - librg__messages[LIBRG_CONNECTION_INIT] = librg__callback_connection_init; - librg__messages[LIBRG_CONNECTION_REQUEST] = librg__callback_connection_request; - librg__messages[LIBRG_CONNECTION_REFUSE] = librg__callback_connection_refuse; - librg__messages[LIBRG_CONNECTION_ACCEPT] = librg__callback_connection_accept; - librg__messages[LIBRG_CONNECTION_DISCONNECT] = librg__callback_connection_disconnect; - librg__messages[LIBRG_ENTITY_CREATE] = librg__callback_entity_create; - librg__messages[LIBRG_ENTITY_UPDATE] = librg__callback_entity_update; - librg__messages[LIBRG_CLIENT_STREAMER_ADD] = librg__callback_entity_client_streamer_add; - librg__messages[LIBRG_CLIENT_STREAMER_REMOVE] = librg__callback_entity_client_streamer_remove; - librg__messages[LIBRG_CLIENT_STREAMER_UPDATE] = librg__callback_entity_client_streamer_update; - } - - void librg_free() { + u8 enet_init = enet_initialize(); + librg_assert_msg(enet_init == 0, "cannot initialize enet"); + + // 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_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; + } + + void librg_free(librg_ctx_t *ctx) { librg_dbg("librg_free\n"); // free all timers and events first - zpl_array_free(librg__timers); - zpl_buffer_free(librg__messages, zpl_heap_allocator()); - zplev_destroy(&librg__events); - zpl_array_free(librg__entity_remove_queue); + zpl_array_free(ctx->timers); + zpl_buffer_free(ctx->messages, ctx->allocator); + zplev_destroy(&ctx->events); + + zpl_free(ctx->allocator, ctx->entity.list); + zpl_array_free(ctx->entity.remove_queue); // streamer - zplc_free(&librg__streamer); - librg_table_destroy(&librg__entity.ignored); - - // free the entity component pools - for (i32 i = 0; i < zpl_array_count(librg__component_pool); i++) { - librg_assert(librg__component_pool[i]); - __dummypool_t *h = cast(__dummypool_t*)librg__component_pool[i]; - zpl_buffer_free(h->entities, h->backing); - zpl_buffer_free(h->data, h->backing); + zplc_free(&ctx->streamer); + librg_table_destroy(&ctx->entity.ignored); + +#ifdef LIBRG_MULTITHREADED + // threading + usize thread_count = librg_option_get(LIBRG_MAX_THREADS_PER_UPDATE); + if (thread_count > 0) { + zpl_atomic32_store(&ctx->threading.signal, librg_thread_exit); + + for (usize i = 0; i < thread_count; ++i) { + zpl_thread_join(ctx->threading.update_workers + i); + } + + zpl_free(ctx->allocator, ctx->threading.update_workers); + zpl_mutex_destroy(ctx->threading.send_lock); + zpl_free(ctx->allocator, ctx->threading.send_lock); } +#endif - // free containers and entity pool - zpl_array_free(librg__component_pool); + for (isize i = 0; i < LIBRG_DATA_STREAMS_AMOUNT; ++i) { + librg_data_free(&ctx->streams[i]); + } enet_deinitialize(); } + zpl_inline void librg_release(void *ptr) { + zpl_mfree(ptr); + } - /** - * - * EXTENSIONS - * - */ - - #undef __dummypool_t - #undef __dummymeta_t + zpl_inline void librg_release_array(void *ptr) { + librg_assert(ptr); + zpl_array_free(ptr); + } - #define librg_component_define(NAME) librg_component_define_inner(,NAME) - #undef librg_component - #define librg_component(NAME) \ - librg_component_declare(NAME) \ - librg_component_define(NAME) + #undef librg__event_create #ifdef __cplusplus } diff --git a/include/librg_components.h b/include/librg_components.h new file mode 100644 index 0000000..4858fde --- /dev/null +++ b/include/librg_components.h @@ -0,0 +1,203 @@ +/** + * WANRNING: its just a prototype + * DON'T USE IT IN YOUR PROJECTS + */ + +#error "Don't include librg_components.h !" + +#ifndef LIBRG_COMPONENTS_INCLUDE_H +#define LIBRG_COMPONENTS_INCLUDE_H + +#ifdef __cplusplus +extern "C" { +#endif + + /** + * Entity filter + * Used for ruinning complex iterations on entity pool + * + * Can be used to retrieve only entites containing all + * of the provided components (logical AND operation). + * And entities that exclude provided components (logical NOT). + * + * Supports up to 8 components for "contains" operation. + * And up to 4 components for "excludes" operation. + * + * First undefined component index in the list will skip all other + * components for that operation: + * { .contains1 = COMPONENT_ID_1, .contains3 = COMPONENT_ID_3 } + * In this case COMPONENT_ID_3 WILL NOT be added to condition. + * + * If you want to enable this behavior: make sure + * you define LIBRG_ENTITY_UNOPTIMIZED_CYCLES before including the librg.h + * + * + * EXAMPLES: + * librg_filter_t filter = { COMPONENT_ID_1, COMPONENT_ID_2 }; + * OR + * librg_filter_t filter = { COMPONENT_ID_1, .excludes1 = COMPONENT_ID_4 }; + */ + typedef union librg_filter_t { + struct { + u32 contains1; u32 contains2; u32 contains3; u32 contains4; + u32 contains5; u32 contains6; u32 contains7; u32 contains8; + u32 excludes1; u32 excludes2; u32 excludes3; u32 excludes4; + }; + + struct { + u32 contains[8]; + u32 excludes[4]; + }; + } librg_filter_t; + + /** + * Storage containers + * for inner librg stuff + */ + typedef struct { + usize offset; + usize size; + zpl_buffer_t(b32) used; + } librg_component_meta; + + + struct { + librg_void *data; + usize size; + usize count; + zpl_buffer_t(librg_component_meta) headers; + } librg_component_ctx; + + + /** + * Register provided component + */ + LIBRG_API void librg_component_add(librg_component_ctx *ctx, u32 index, usize component_size); + + /** + * Allocate memory for components + * @param ctx [description] + */ + LIBRG_API void librg_component_init(librg_component_ctx *ctx); + + LIBRG_API void librg_component_free(librg_component_ctx *ctx); + + /** + * Try to attach provided component to/from a particular entity + */ + LIBRG_API librg_void *librg_component_attach(librg_component_ctx *ctx, u32 index, librg_entity_t entity, librg_void *data); + + /** + * Try to fetch provided component to/from a particular entity + */ + LIBRG_API librg_void *librg_component_fetch(librg_component_ctx *ctx, u32 index, librg_entity_t entity); + + /** + * Try to detach provided component to/from a particular entity + */ + LIBRG_API void librg_component_detach(librg_component_ctx *ctx, u32 index, librg_entity_t entity); + + /** + * Try to interate on each entity with provided component filter + */ + LIBRG_API void librg_component_each(librg_component_ctx *ctx, librg_filter_t filter, librg_entity_cb callback); + + /** + * Try to interate on each entity with provided component filter + * (macro version, can be used in C/C++ based projects) + */ + #define librg_component_eachx(ctx, filter, name, code) do { \ + librg_assert(ctx); if (filter.contains1 == 0) break; \ + for (usize _ent = 0, valid = 0; valid < (ctx->entity.native.count + ctx->entity.shared.count) \ + && _ent < (librg_is_server(ctx) ? ctx->max_entities : ctx->max_entities * 2); _ent++) { \ + /* check if entity valid */ \ + if (!ctx->components.headers[librg_meta].used[_ent]) continue; else valid++; \ + b32 _used = true; \ + /* check for included components */ \ + for (isize k = 0; k < 8 && _used; k++) { \ + if (filter.contains[k] == 0) break; \ + librg_component_meta *header = &ctx->components.headers[filter.contains[k]]; librg_assert(header); \ + if (!header->used[_ent]) { _used = false; } \ + } \ + /* check for excluded components */ \ + for (isize k = 0; k < 4 && _used; k++) { \ + if (filter.excludes[k] == 0) break; \ + librg_component_meta *header = &ctx->components.headers[filter.excludes[k]]; librg_assert(header); \ + if (header->used[_ent]) { _used = false; } \ + } \ + /* execute code */ \ + if (_used) { librg_entity_t name = _ent; code; } \ + } \ + } while(0) + + #define librg_component(NAME, INDEX, COMP) \ + static librg_inline COMP *ZPL_JOIN2(librg_attach_,NAME) (librg_ctx_t *ctx, librg_entity_t entity, COMP *component) { return (COMP *)librg_component_attach(ctx, INDEX, entity, (char *)component); } \ + static librg_inline COMP *ZPL_JOIN2(librg_fetch_ ,NAME) (librg_ctx_t *ctx, librg_entity_t entity) { return (COMP *)librg_component_fetch(ctx, INDEX, entity); } \ + static librg_inline void ZPL_JOIN2(librg_detach_,NAME) (librg_ctx_t *ctx, librg_entity_t entity) { librg_component_detach(ctx, INDEX, entity); } + +#ifdef __cplusplus +} +#endif + +#if defined(LIBRG_COMPONENTS_IMPLEMENTATION) && !defined(LIBRG_COMPONENTS_IMPLEMENTATION_DONE) +#define LIBRG_COMPONENTS_IMPLEMENTATION_DONE + +#ifdef __cplusplus +extern "C" { +#endif + + void librg_component_add(librg_component_ctx *ctx, u32 index, usize component_size) { + librg_assert(ctx); librg_assert(component_size); + librg_assert_msg(ctx->components.count == index, "you should register components in order"); + + librg_component_meta *header = &ctx->components.headers[index]; librg_assert(header); + usize size = component_size * ctx->max_entities; + + header->offset = ctx->components.size; + header->size = component_size; + + ctx->components.size += size; + ctx->components.count++; + + zpl_buffer_init(header->used, ctx->allocator, ctx->max_entities); + } + + void librg_component_init(librg_component_ctx *ctx) { + + } + + void librg_component_free(librg_component_ctx *ctx) { + + } + + librg_void *librg_component_attach(librg_component_ctx *ctx, u32 index, librg_entity_t entity, librg_void *data) { + librg_component_meta *header = &ctx->components.headers[index]; + librg_assert_msg(header && header->size, "make sure you registered component you are trying to use"); + header->used[entity] = true; + librg_void *cdata = ctx->components.data + header->offset; + if (data == NULL) zpl_memset(&cdata[entity * header->size], 0, (usize)header->size); + else zpl_memcopy(&cdata[entity * header->size], data, (usize)header->size); + return &cdata[entity * header->size]; + } + + librg_inline librg_void *librg_component_fetch(librg_component_ctx *ctx, u32 index, librg_entity_t entity) { + librg_component_meta *header = &ctx->components.headers[index]; librg_assert(header); + librg_void *cdata = ctx->components.data + header->offset; + return header->used[entity] ? &cdata[entity * header->size] : NULL; + } + + librg_inline void librg_component_detach(librg_component_ctx *ctx, u32 index, librg_entity_t entity) { + librg_component_meta *header = &ctx->components.headers[index]; librg_assert(header); + header->used[entity] = false; + } + + void librg_component_each(librg_component_ctx *ctx, librg_filter_t filter, librg_entity_cb callback) { + librg_component_eachx(ctx, filter, entity, { callback(ctx, entity); }); + } + +#ifdef __cplusplus +} +#endif + +#endif // LIBRG_COMPONENTS_IMPLEMENTATION +#endif // LIBRG_COMPONENTS_INCLUDE_H diff --git a/include/librg_limiter.h b/include/librg_limiter.h new file mode 100644 index 0000000..6dd3871 --- /dev/null +++ b/include/librg_limiter.h @@ -0,0 +1,84 @@ +/** + * An example limiter implementation + * + * Can be used to reduce number of udpate sends for every entity + * Can be easily modified to one's needs + */ + +#ifndef LIBRG_LIMITER_INCLUDE_H +#define LIBRG_LIMITER_INCLUDE_H + +#ifdef __cplusplus +extern "C" { +#endif + + + #define LIBRG_LIMITER_DETEORIATION 0.25f + + typedef struct { + b32 update_now; + + f32 update_initial_delay; + f32 update_delay; + f32 update_max_delay; + f32 update_time; + f32 update_deteoriation; + f32 update_moving_treshold; + + zplm_vec3_t last_position; + } librg_limiter_t; + + LIBRG_API void librg_limiter_init(librg_limiter_t *entity_limit); + LIBRG_API void librg_limiter_fire(librg_event_t *event, librg_limiter_t *entity_limit); + + +#if defined(LIBRG_LIMITER_IMPLEMENTATION) && !defined(LIBRG_LIMITER_IMPLEMENTATION_DONE) +#define LIBRG_LIMITER_IMPLEMENTATION_DONE + + + void librg_limiter_init(librg_limiter_t *entity_limit) { + entity_limit->update_deteoriation = LIBRG_LIMITER_DETEORIATION; + entity_limit->update_initial_delay = entity_limit->update_delay = 0.0f; + entity_limit->update_moving_treshold = 0.03f; + } + + void librg_limiter_fire(librg_event_t *event, librg_limiter_t *entity_limit) { + librg_assert(event && event->ctx); + + librg_ctx_t *ctx = event->ctx; + librg_entity_t *entity = event->entity; + + if (entity_limit->update_time < entity_limit->update_delay) { + entity_limit->update_time += ctx->tick_delay; + { + zplm_vec3_t dir; + zplm_vec3_sub(&dir, entity_limit->last_position, entity->position); + + b32 is_moving = (zplm_vec3_dot(dir, dir) > entity_limit->update_moving_treshold); + + if (is_moving || entity_limit->update_now) { + entity_limit->update_delay = entity_limit->update_initial_delay; + entity_limit->last_position = entity->position; + entity_limit->update_now = false; + } + } + + librg_event_reject(event); + return; + } + else { + entity_limit->update_time = 0.0f; + + if (entity_limit->update_delay < entity_limit->update_max_delay) { + entity_limit->update_delay += entity_limit->update_delay * entity_limit->update_deteoriation; + } + } + } + +#endif + +#ifdef __cplusplus +} +#endif + +#endif // LIBRG_LIMITER_INCLUDE_H diff --git a/package-lock.json b/package-lock.json index 9251d03..ec74aed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,26 @@ { "name": "librg.c", - "version": "2.2.3", + "version": "3.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { "enet.c": { - "version": "1.3.15", - "resolved": "https://registry.npmjs.org/enet.c/-/enet.c-1.3.15.tgz", - "integrity": "sha512-eRQfGHZ10+VRqbyQgo6lTzt/MhkmboQw42KLz3HmnUDjEBP6+oVaFeskvP4t2oXirU5xepi+NvxXxQxxgobQng==" + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/enet.c/-/enet.c-1.4.5.tgz", + "integrity": "sha512-3vT2kHdxcsW8K4aKy90il81WxGQ6tw2smD96k8fdAuwljSS1CwuArYRqjVbqbQjrtcUQIJyY68JIz8KQQmIrPg==" + }, + "zpl.c": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/zpl.c/-/zpl.c-3.3.2.tgz", + "integrity": "sha512-3gGIgICRpB2migPqJc1N/+6/bQ8ya/cdbndNvx+yyiKLlBUm5XoBzYuMmSYyaJhtunzKSJr46J5l7Cqr2etr+g==" }, "zpl_cull.c": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/zpl_cull.c/-/zpl_cull.c-2.1.2.tgz", - "integrity": "sha512-r/SSZGoE8AsJ23z/XkFxstKEo9P6LK863ZbhCVnp3yNfajPv4q5rKdYbXIdKDlVWHm/nhR/E5AtX+xqcZOCcNw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/zpl_cull.c/-/zpl_cull.c-3.1.2.tgz", + "integrity": "sha512-dKoQgsD+DhiPtAbKl9qZE9yQhK1ZwN4WSMfTarOl3/a8stalzHKk/hxCoBLAKnwlHftd4U4Wp2Cm6XYvUuB2pA==", "requires": { - "zpl_math.c": "1.0.1", - "zpl.c": "3.0.3" + "zpl.c": "3.3.2", + "zpl_math.c": "1.0.1" } }, "zpl_event.c": { @@ -23,18 +28,13 @@ "resolved": "https://registry.npmjs.org/zpl_event.c/-/zpl_event.c-1.0.8.tgz", "integrity": "sha512-ksr2x1YZEJlVUuxQXscrJozt56BsmJpVKhtEdNCLPzD9W0ev16J55hPfDM2oFL07ZWN+6L7Ks7AoJqOefF2+Hg==", "requires": { - "zpl.c": "3.0.3" + "zpl.c": "3.3.2" } }, "zpl_math.c": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/zpl_math.c/-/zpl_math.c-1.0.1.tgz", "integrity": "sha512-nZCG41KzHzI1sK9N53rP7qb8NTj15PC06KLfW7z+2VcxHvwrVjJ09HWDEvR4WzuMTd1B2brKimZ4eEmXvIccJw==" - }, - "zpl.c": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/zpl.c/-/zpl.c-3.0.3.tgz", - "integrity": "sha512-u6BdmtdmyjbIii4VX+lEOMj3MDmKYfztC5oPXCzVeH5MwuCf/WVMn7ZpGvo3tXAesW9MkrdlYJS5/dE+6udbDQ==" } } } diff --git a/package.json b/package.json index b9ad823..4ac5ca1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "librg.c", - "version": "2.2.3", + "version": "3.0.0", "description": "Pure C library for building simple and elegant cross-platform mmo client-server solutions.", "main": "include/librg.h", "directories": { @@ -20,9 +20,9 @@ }, "homepage": "https://github.com/reguider/librg#readme", "dependencies": { - "enet.c": "^1.3.15", - "zpl.c": "^3.0.3", - "zpl_cull.c": "^2.1.2", + "enet.c": "^1.4.5", + "zpl.c": "^3.3.2", + "zpl_cull.c": "^3.1.2", "zpl_event.c": "^1.0.8", "zpl_math.c": "^1.0.1" } diff --git a/build.sh b/scripts/build-c.sh similarity index 53% rename from build.sh rename to scripts/build-c.sh index 9b957f9..ff977c7 100755 --- a/build.sh +++ b/scripts/build-c.sh @@ -8,13 +8,4 @@ clang -g -std=c99 \ -I node_modules/enet.c/include \ $(sdl2-config --cflags --libs) \ test/$1.c \ - node_modules/enet.c/callbacks.c \ - node_modules/enet.c/compress.c \ - node_modules/enet.c/host.c \ - node_modules/enet.c/list.c \ - node_modules/enet.c/packet.c \ - node_modules/enet.c/peer.c \ - node_modules/enet.c/protocol.c \ - node_modules/enet.c/win32.c \ - node_modules/enet.c/unix.c \ -o build/$1 && build/$1 diff --git a/scripts/build-cpp.sh b/scripts/build-cpp.sh new file mode 100755 index 0000000..1ec71a8 --- /dev/null +++ b/scripts/build-cpp.sh @@ -0,0 +1,11 @@ +g++ -g -std=c++11 \ + -I include \ + -I node_modules/zpl.c/include \ + -I node_modules/zpl_ent.c/include \ + -I node_modules/zpl_math.c/include \ + -I node_modules/zpl_cull.c/include \ + -I node_modules/zpl_event.c/include \ + -I node_modules/enet.c/include \ + $(sdl2-config --cflags --libs) \ + test/$1.cpp \ + -o build/$1 && build/$1 diff --git a/build-emscripten.sh b/scripts/build-emscripten.sh similarity index 57% rename from build-emscripten.sh rename to scripts/build-emscripten.sh index b592335..251ec8d 100755 --- a/build-emscripten.sh +++ b/scripts/build-emscripten.sh @@ -9,13 +9,4 @@ docker run -v $(pwd):/src trzeci/emscripten:sdk-tag-1.35.4-64bit \ -I node_modules/enet.c/include \ $(sdl2-config --cflags --libs) \ test/$1.c \ - node_modules/enet.c/callbacks.c \ - node_modules/enet.c/compress.c \ - node_modules/enet.c/host.c \ - node_modules/enet.c/list.c \ - node_modules/enet.c/packet.c \ - node_modules/enet.c/peer.c \ - node_modules/enet.c/protocol.c \ - node_modules/enet.c/win32.c \ - node_modules/enet.c/unix.c \ -o build/$1.js diff --git a/build.bat b/scripts/build.bat similarity index 100% rename from build.bat rename to scripts/build.bat diff --git a/test/bitstream.c b/test/bitstream.c index ed476e2..9078514 100644 --- a/test/bitstream.c +++ b/test/bitstream.c @@ -10,7 +10,7 @@ typedef struct { zplm_vec3_t d; zplm_vec3_t e; zplm_vec3_t f; -} librg_component(foo); +} foo_t; // typedef struct temp { // zpl_allocator_t allocator; @@ -29,11 +29,11 @@ void aaaaa(librg_event_t *e, foo_t foo) { int main() { - librg_init((librg_config_t) { - .tick_delay = 32, - .mode = LIBRG_MODE_SERVER, - .world_size = zplm_vec2(5000.0f, 5000.0f), - }); + // librg_init((librg_config_t) { + // .tick_delay = 32, + // .mode = LIBRG_MODE_SERVER, + // .world_size = zplm_vec2(5000.0f, 5000.0f), + // }); librg_data_t data; librg_data_init(&data); @@ -47,9 +47,9 @@ int main() { librg_data_ru32(&data) ); - librg_log("current wpos: %u\n", librg_data_get_wpos(&data)); + librg_log("current wpos: %zu\n", librg_data_get_wpos(&data)); librg_data_reset(&data); - librg_log("current wpos: %u\n", librg_data_get_wpos(&data)); + librg_log("current wpos: %zu\n", librg_data_get_wpos(&data)); foo_t foo = { 0 }; @@ -59,11 +59,12 @@ int main() { aaaaa(&e, foo); librg_data_wptr(&data, &foo, sizeof(foo_t)); } + librg_log("current wpos: %zu\n", librg_data_get_wpos(&data)); // librg_data_rptr() librg_data_free(&data); - librg_free(); + // librg_free(); return 0; } diff --git a/test/build-test.c b/test/build-test.c index 33d0b05..ea7bfbd 100644 --- a/test/build-test.c +++ b/test/build-test.c @@ -7,29 +7,29 @@ void on_connect_accepted(librg_event_t *event) { int main() { // initialization - librg_config_t config = {0}; + librg_ctx_t ctx = {0}; - config.tick_delay = 32; - config.mode = LIBRG_MODE_SERVER; - config.world_size = zplm_vec2(5000.0f, 5000.0f); + ctx.tick_delay = 32; + ctx.mode = LIBRG_MODE_SERVER; + ctx.world_size = zplm_vec3(5000.0f, 5000.0f, 0.0f); - librg_init(config); + librg_init(&ctx); // adding event handlers - librg_event_add(LIBRG_CONNECTION_ACCEPT, on_connect_accepted); + librg_event_add(&ctx, LIBRG_CONNECTION_ACCEPT, on_connect_accepted); // starting server librg_address_t address = {0}; address.port = 27010; - librg_network_start(address); + librg_network_start(&ctx, address); // starting main loop (run 100 times for test) for (int i = 0; i < 100; ++i) { - librg_tick(); + librg_tick(&ctx); zpl_sleep_ms(1); } // stopping network and freeing resources - librg_network_stop(); - librg_free(); + librg_network_stop(&ctx); + librg_free(&ctx); return 0; } diff --git a/test/build-test.cpp b/test/build-test.cpp new file mode 100644 index 0000000..0a03b0e --- /dev/null +++ b/test/build-test.cpp @@ -0,0 +1,39 @@ +#define LIBRG_IMPLEMENTATION +#define LIBRG_CXX11_EXTENSIONS +#include + +void on_connect_accepted(librg_event_t *event) { + librg_log("someone connected to the server!\n"); +} + +int main() { + // initialization + librg_ctx_t ctx = {0}; + + ctx.tick_delay = 32; + ctx.mode = LIBRG_MODE_SERVER; + ctx.world_size = zplm_vec3(5000.0f, 5000.0f, 0.0f); + + librg_init(&ctx); + + // adding event handlers + librg_event_add(&ctx, LIBRG_CONNECTION_ACCEPT, on_connect_accepted); + + librg_event_t e = {0}; + librg_event_trigger(&ctx, 42, &e); + + // starting server + librg_address_t address = {0}; address.port = 27010; + librg_network_start(&ctx, address); + + // starting main loop (run 100 times for test) + for (int i = 0; i < 100; ++i) { + librg_tick(&ctx); + zpl_sleep_ms(1); + } + + // stopping network and freeing resources + librg_network_stop(&ctx); + librg_free(&ctx); + return 0; +} diff --git a/test/cli-client.c b/test/cli-client.c index abc2db4..afea77d 100644 --- a/test/cli-client.c +++ b/test/cli-client.c @@ -2,15 +2,6 @@ #define LIBRG_DEBUG #include -typedef struct { - zplm_vec3_t a; - zplm_vec3_t b; - zplm_vec3_t c; - zplm_vec3_t d; - zplm_vec3_t e; - zplm_vec3_t f; -} librg_component(foo); - void on_connect_request(librg_event_t *event) { librg_data_wu32(event->data, 42); librg_log("on_connect_request\n"); @@ -26,46 +17,13 @@ void on_connect_refused(librg_event_t *event) { } void on_entity_create(librg_event_t *event) { - foo_t foo; - librg_data_rptr(event->data, &foo, sizeof(foo_t)); - librg_attach_foo(event->entity, foo); + // ... } void on_entity_update(librg_event_t *event) { - librg_data_rf32(event->data); - // librg_log("sent: %f on upd", librg_data_rf32(event->data)); + // ... } - -// // client -// void damage_car(librg_entity_t entity) { -// librg_log("client: damanging the car\n"); -// librg_send(21, librg_lambda(data), { librg_data_wentity(&data, entity); }); -// } - -// void onvehcielcreate(librg_message_t *msg) { -// u32 guid = librg_data_ru32(&msg->data); - -// librg_entity_t entity = librg_entity_create_shared(guid, 0); -// librg_attach_foo(entity, (foo_t) { 123 }); - -// librg_log("server created vehicle %lu\n", entity.id); - -// damage_car(entity); -// } - -// void on_damage_finished(librg_message_t *msg) { -// u32 guid = librg_data_ru32(&msg->data); -// librg_entity_t entity = librg_entity_get(guid); - -// foo_t *foo = librg_fetch_foo(entity); - -// ZPL_ASSERT(foo && foo->a == 123); - -// librg_log("damaged car finished\n"); -// } - - int main() { char *test = "=============== CLIENT =================\n" \ "== ==\n" \ @@ -74,27 +32,37 @@ int main() { "==================================================\n"; librg_log("%s\n\n", test); - librg_init((librg_config_t) { - .tick_delay = 1000, - .mode = LIBRG_MODE_CLIENT, - .world_size = zplm_vec2(5000.0f, 5000.0f), - }); + librg_ctx_t original = {0}; + original.tick_delay = 1000; + original.mode = LIBRG_MODE_CLIENT; + original.world_size = zplm_vec3(5000.0f, 5000.0f, 0.f); + original.max_entities = 60000; + + #define size 16 + librg_ctx_t *ctxs = zpl_malloc(size*sizeof(librg_ctx_t)); - librg_event_add(LIBRG_CONNECTION_REQUEST, on_connect_request); - librg_event_add(LIBRG_CONNECTION_ACCEPT, on_connect_accepted); - librg_event_add(LIBRG_CONNECTION_REFUSE, on_connect_refused); + for (int i = 0; i < size; ++i) { + ctxs[i] = original; - librg_event_add(LIBRG_ENTITY_CREATE, on_entity_create); - librg_event_add(LIBRG_ENTITY_UPDATE, on_entity_update); + librg_init(&ctxs[i]); - librg_network_start((librg_address_t) { .host = "localhost", .port = 27010 }); + librg_event_add(&ctxs[i], LIBRG_CONNECTION_REQUEST, on_connect_request); + librg_event_add(&ctxs[i], LIBRG_CONNECTION_ACCEPT, on_connect_accepted); + librg_event_add(&ctxs[i], LIBRG_CONNECTION_REFUSE, on_connect_refused); + + librg_event_add(&ctxs[i], LIBRG_ENTITY_CREATE, on_entity_create); + librg_event_add(&ctxs[i], LIBRG_ENTITY_UPDATE, on_entity_update); + + librg_network_start(&ctxs[i], (librg_address_t) { .host = "localhost", .port = 7777 }); + } while (true) { - librg_tick(); - zpl_sleep_ms(500); + for (int i = 0; i < size; ++i) { + librg_tick(&ctxs[i]); + } + + zpl_sleep_ms(1); } - librg_network_stop(); - librg_free(); return 0; } diff --git a/test/cli-server.c b/test/cli-server.c index 836b78c..f7b3d67 100644 --- a/test/cli-server.c +++ b/test/cli-server.c @@ -2,40 +2,27 @@ #define LIBRG_DEBUG #include -enum { - TYPE_VEHICLE = 242, -}; - -typedef struct { - zplm_vec3_t a; - zplm_vec3_t b; - zplm_vec3_t c; - zplm_vec3_t d; - zplm_vec3_t e; - zplm_vec3_t f; -} librg_component(foo); - void on_connect_request(librg_event_t *event) { u32 secret = librg_data_ru32(event->data); - librg_transform_t *transform = librg_fetch_transform(event->entity); - transform->position.x = (float)(2000 - rand() % 4000); - transform->position.y = (float)(2000 - rand() % 4000); - - librg_log("spawning player at: %f %f %f\n", - transform->position.x, - transform->position.y, - transform->position.z - ); - if (secret != 42) { - return librg_event_reject(event); + librg_event_reject(event); } } void on_connect_accepted(librg_event_t *event) { librg_log("on_connect_accepted\n"); - librg_attach_foo(event->entity, (foo_t){0}); + librg_entity_t *blob = event->entity; + + blob->position.x = (float)(2000 - rand() % 4000); + blob->position.y = (float)(2000 - rand() % 4000); + + librg_log("spawning player %u at: %f %f %f\n", + event->entity->id, + blob->position.x, + blob->position.y, + blob->position.z + ); } void on_connect_refused(librg_event_t *event) { @@ -43,13 +30,28 @@ void on_connect_refused(librg_event_t *event) { } void on_entity_create(librg_event_t *event) { - foo_t *foo = librg_fetch_foo(event->entity); - if (foo) - librg_data_wptr(event->data, foo, sizeof(foo_t)); + } void on_entity_update(librg_event_t *event) { - librg_data_wf32(event->data, librg_fetch_foo(event->entity)->a.x); + +} + +void measure(void *userptr) { + librg_ctx_t *ctx = (librg_ctx_t *)userptr; + + if (!ctx || !ctx->network.host) return; + + static u32 lastdl = 0; + static u32 lastup = 0; + + f32 dl = (ctx->network.host->totalReceivedData - lastdl) * 8.0f / ( 1000.0f * 1000 ) ; // mbps + f32 up = (ctx->network.host->totalSentData - lastup) * 8.0f / ( 1000.0f * 1000 ) ; // mbps + + lastdl = ctx->network.host->totalReceivedData; + lastup = ctx->network.host->totalSentData; + + librg_log("librg_update: took %f ms. Current used bandwidth D/U: (%f / %f) mbps. \r", ctx->last_update, dl, up); } int main() { @@ -60,39 +62,49 @@ int main() { "==================================================\n"; librg_log("%s\n\n", test); - librg_init((librg_config_t) { - .tick_delay = 1000, - .mode = LIBRG_MODE_SERVER, - .world_size = zplm_vec2(5000.0f, 5000.0f), - .max_entities = 15000, - .max_connections = 1000, - }); + librg_option_set(LIBRG_MAX_ENTITIES_PER_BRANCH, 4); + + librg_ctx_t ctx = {0}; + + ctx.tick_delay = 1000; + ctx.mode = LIBRG_MODE_SERVER; + ctx.world_size = zplm_vec3(5000.0f, 5000.0f, 0.f); + ctx.min_branch_size = zplm_vec3(-1, -1, -1); + ctx.max_entities = 60000; + ctx.max_connections = 1200; - librg_event_add(LIBRG_CONNECTION_REQUEST, on_connect_request); - librg_event_add(LIBRG_CONNECTION_ACCEPT, on_connect_accepted); - librg_event_add(LIBRG_CONNECTION_REFUSE, on_connect_refused); + librg_init(&ctx); - librg_event_add(LIBRG_ENTITY_CREATE, on_entity_create); - librg_event_add(LIBRG_ENTITY_UPDATE, on_entity_update); + librg_event_add(&ctx, LIBRG_CONNECTION_REQUEST, on_connect_request); + librg_event_add(&ctx, LIBRG_CONNECTION_ACCEPT, on_connect_accepted); + librg_event_add(&ctx, LIBRG_CONNECTION_REFUSE, on_connect_refused); + librg_event_add(&ctx, LIBRG_ENTITY_CREATE, on_entity_create); + librg_event_add(&ctx, LIBRG_ENTITY_UPDATE, on_entity_update); - librg_network_start((librg_address_t) { .host = "localhost", .port = 27010 }); + librg_network_start(&ctx, (librg_address_t) { .host = "localhost", .port = 7777 }); for (isize i = 0; i < 10000; i++) { - librg_entity_t enemy = librg_entity_create(0); - librg_transform_t *transform = librg_fetch_transform(enemy); - librg_attach_foo(enemy, (foo_t){0}); - transform->position.x = (float)(2000 - rand() % 4000); - transform->position.y = (float)(2000 - rand() % 4000); + librg_entity_id enemy = librg_entity_create(&ctx, 0); + librg_entity_t *blob = librg_entity_fetch(&ctx, enemy); + + //librg_attach_foo(&ctx, enemy, NULL); + blob->position.x = (float)(2000 - rand() % 4000); + blob->position.y = (float)(2000 - rand() % 4000); } + zpl_timer_t *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, 1000 * 1000, -1, measure); + zpl_timer_start(tick_timer, 1000); + while (true) { - librg_tick(); + librg_tick(&ctx); zpl_sleep_ms(1); } - librg_network_stop(); - librg_free(); + librg_network_stop(&ctx); + librg_free(&ctx); return 0; } diff --git a/test/demo-client.c b/test/demo-client.c index 3944614..8c5a37d 100644 --- a/test/demo-client.c +++ b/test/demo-client.c @@ -1,13 +1,18 @@ #define LIBRG_DEBUG #define LIBRG_IMPLEMENTATION -#define LIBRG_PLATFORM_BUILD 3 #include +#include #include + +#define DEMO_CLIENT #include "demo-defines.h" #define SIZE_X 800 #define SIZE_Y 600 +zpl_global f64 last_delta; +zpl_global f64 last_time; + /** * SDL PART */ @@ -32,6 +37,7 @@ int init_sdl() { // Set size of renderer to the same as window SDL_RenderSetLogicalSize(sdl_renderer, SIZE_X, SIZE_Y); + SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND); // Set color of renderer to green SDL_SetRenderDrawColor(sdl_renderer, 39, 40, 34, 150); @@ -50,7 +56,7 @@ void free_sdl() { * LIBRG PART */ SDL_Rect camera; -librg_entity_t player; +librg_entity_id player; void on_connect_request(librg_event_t *event) { librg_log("on_connect_request\n"); @@ -59,7 +65,7 @@ void on_connect_request(librg_event_t *event) { void on_connect_accepted(librg_event_t *event) { librg_log("on_connect_accepted\n"); - player = event->entity; + player = event->entity->id; librg_log("spawned me with id: %u\n", player); } @@ -69,37 +75,33 @@ void on_connect_refused(librg_event_t *event) { } void on_entity_create(librg_event_t *event) { - switch (librg_entity_type(event->entity)) { - case DEMO_TYPE_PLAYER: + switch (librg_entity_type(event->ctx, event->entity->id)) { + case DEMO_TYPE_PLAYER: + break; case DEMO_TYPE_NPC: { - hero_t hero_; - librg_data_rptr(event->data, &hero_, sizeof(hero_)); - - librg_attach_hero(event->entity, hero_); + event->entity->user_data = zpl_malloc(sizeof(hero_t)); + hero_t *hero = (hero_t *)event->entity->user_data; + librg_data_rptr(event->data, event->entity->user_data, sizeof(hero->stream)); + + hero->curr_pos = event->entity->position; + hero->last_pos = event->entity->position; + hero->target_pos = event->entity->position; + hero->delta = 0.0f; } break; } } void on_entity_update(librg_event_t *event) { - // librg_transform_t *transform = librg_fetch_transform(event->entity); - - // librg_log("moving entity %u at: %f %f %f\n", - // event->entity, - // transform->position.x, - // transform->position.y, - // transform->position.z - // ); + hero_t *hero = (hero_t *)event->entity->user_data; + if (!hero) return; + + hero->last_pos = hero->target_pos; + hero->target_pos = event->entity->position; + hero->delta = .0f; } void on_client_entity_update(librg_event_t *event) { -// librg_transform_t *transform = librg_fetch_transform(player); -// if (!transform) return; - -// librg_log("sending pos: %f %f %f\n", -// transform->position.x, -// transform->position.y, -// transform->position.z -// ); + // .. } /** @@ -115,15 +117,15 @@ SDL_Rect default_position() { return position; } -void render_entity(librg_entity_t entity) { +void render_entity(librg_ctx_t *ctx, librg_entity_id entity) { // set render color if (entity == player) { SDL_SetRenderDrawColor( sdl_renderer, 150, 250, 150, 255 ); } - else if (librg_entity_type(entity) == DEMO_TYPE_NPC) { + else if (librg_entity_type(ctx, entity) == DEMO_TYPE_NPC) { SDL_SetRenderDrawColor( sdl_renderer, 150, 25, 25, 255 ); } - else if (librg_entity_type(entity) == DEMO_TYPE_NPC) { + else if (librg_entity_type(ctx, entity) == DEMO_TYPE_NPC) { SDL_SetRenderDrawColor( sdl_renderer, 25, 25, 150, 255 ); } else { @@ -131,11 +133,18 @@ void render_entity(librg_entity_t entity) { } SDL_Rect position = default_position(); - librg_transform_t *transform = librg_fetch_transform(entity); - hero_t *hero = librg_fetch_hero(entity); + librg_entity_t *blob = librg_entity_fetch(ctx, entity); - position.x += transform->position.x - 10; - position.y += transform->position.y - 10; + hero_t *hero = (hero_t *)blob->user_data; + + if (entity == player || !hero) { + position.x += blob->position.x - 10; + position.y += blob->position.y - 10; + } + else { + position.x += hero->curr_pos.x - 10; + position.y += hero->curr_pos.y - 10; + } position.w = 20; position.h = 20; @@ -143,23 +152,27 @@ void render_entity(librg_entity_t entity) { // render SDL_RenderFillRect( sdl_renderer, &position ); - if (hero && hero->cur_hp > 0) { - position.h = 5; - SDL_SetRenderDrawColor(sdl_renderer, 255, 0, 0, 150); - SDL_RenderFillRect(sdl_renderer, &position); + if (entity == player) { + SDL_Rect zone = default_position(); - position.w = 20 * (hero->cur_hp / (float)hero->max_hp); + zone.x = position.x - 250; + zone.y = position.y - 250; + zone.w = 500; + zone.h = 500; - SDL_SetRenderDrawColor(sdl_renderer, 0, 255, 0, 150); - SDL_RenderFillRect(sdl_renderer, &position); + SDL_SetRenderDrawColor( sdl_renderer, 133, 133, 133, 75 ); + SDL_RenderFillRect( sdl_renderer, &zone ); } + + position.h = 5; + SDL_SetRenderDrawColor(sdl_renderer, 255, 0, 0, 150); + SDL_RenderFillRect(sdl_renderer, &position); } -void render() +void render(librg_ctx_t *ctx) { // clear the window and make it all green SDL_RenderClear( sdl_renderer ); - SDL_SetRenderDrawColor( sdl_renderer, 90, 90, 90, 255 ); // render world @@ -176,13 +189,40 @@ void render() } // render entities - librg_entity_each((librg_entity_filter_t) {librg_index_transform() }, render_entity); + librg_entity_iterate(ctx, LIBRG_ENTITY_ALIVE, render_entity); // render the changes above SDL_SetRenderDrawColor( sdl_renderer, 75, 75, 76, 10 ); SDL_RenderPresent( sdl_renderer ); } +void on_entity_remove(librg_event_t *event) { + if (event->entity->type == DEMO_TYPE_NPC) { + zpl_mfree(event->entity->user_data); + } +} + +void interpolate_npcs(librg_ctx_t *ctx) { + for (u32 i = 0; i < ctx->max_entities; i++) { + if (i == player) continue; + + librg_entity_t *entity = librg_entity_fetch(ctx, i); + + if (!entity) continue; + + hero_t *hero = (hero_t *)entity->user_data; + if (!hero) continue; + + hero->delta += (last_delta /(f32) (ctx->tick_delay)); + + zplm_vec3_t delta_pos; + zplm_vec3_lerp(&delta_pos, hero->last_pos, hero->target_pos, zpl_clamp01(hero->delta)); + + hero->curr_pos = delta_pos; + } +} + + bool shooting = false; bool keys_held[323] = { false }; @@ -208,24 +248,31 @@ int main(int argc, char *argv[]) { "== ==\n" \ "==================================================\n"); - librg_init((librg_config_t) { - .tick_delay = 32, - .mode = LIBRG_MODE_CLIENT, - .world_size = zplm_vec2(5000.0f, 5000.0f), - }); + librg_ctx_t ctx = {0}; + ctx.tick_delay = 64; + ctx.mode = LIBRG_MODE_CLIENT; + ctx.world_size = zplm_vec3(5000.0f, 5000.0f, 0.f); + ctx.max_entities = 2000; - librg_event_add(LIBRG_CONNECTION_REQUEST, on_connect_request); - librg_event_add(LIBRG_CONNECTION_ACCEPT, on_connect_accepted); - librg_event_add(LIBRG_CONNECTION_REFUSE, on_connect_refused); - librg_event_add(LIBRG_ENTITY_CREATE, on_entity_create); - librg_event_add(LIBRG_ENTITY_UPDATE, on_entity_update); - librg_event_add(LIBRG_CLIENT_STREAMER_UPDATE, on_client_entity_update); + librg_init(&ctx); - librg_network_start((librg_address_t) { .host = "localhost", .port = 27010 }); + librg_event_add(&ctx, LIBRG_CONNECTION_REQUEST, on_connect_request); + librg_event_add(&ctx, LIBRG_CONNECTION_ACCEPT, on_connect_accepted); + librg_event_add(&ctx, LIBRG_CONNECTION_REFUSE, on_connect_refused); + librg_event_add(&ctx, LIBRG_ENTITY_CREATE, on_entity_create); + librg_event_add(&ctx, LIBRG_ENTITY_UPDATE, on_entity_update); + librg_event_add(&ctx, LIBRG_ENTITY_REMOVE, on_entity_remove); + librg_event_add(&ctx, LIBRG_CLIENT_STREAMER_UPDATE, on_client_entity_update); + + librg_network_start(&ctx, (librg_address_t) { .host = "localhost", .port = 7777 }); bool loop = true; while (loop) { + f64 curr_time = zpl_utc_time_now(); + last_delta = (curr_time - last_time) / 1000.f; + last_time = curr_time; + SDL_Event event; while (SDL_PollEvent(&event)) { @@ -260,30 +307,24 @@ int main(int argc, char *argv[]) { if (keys_held[SDLK_s]) { camera.y += speed; } - - librg_transform_t *transform = librg_fetch_transform(player); - - if (keys_held[SDLK_t] && transform) { - zpl_printf("triggering 1 entity spawn server-side.\n"); - - librg_send_all(42, librg_lambda(data), { - librg_data_wptr(&data, transform, sizeof(librg_transform_t)); - }); - - // keys_held[SDLK_t] = false; + if (keys_held[SDLK_f]) { + loop = false; } - if (transform) { - transform->position.x = (f32)camera.x; - transform->position.y = (f32)camera.y; + if (librg_entity_valid(&ctx, player)) { + librg_entity_t *blob = librg_entity_fetch(&ctx, player); + + blob->position.x = (f32)camera.x; + blob->position.y = (f32)camera.y; } - librg_tick(); - render(); + librg_tick(&ctx); + interpolate_npcs(&ctx); + render(&ctx); } - librg_network_stop(); - librg_free(); + librg_network_stop(&ctx); + librg_free(&ctx); free_sdl(); return 0; diff --git a/test/demo-defines.h b/test/demo-defines.h index 5721f3b..a3af98a 100644 --- a/test/demo-defines.h +++ b/test/demo-defines.h @@ -1,5 +1,5 @@ enum { - DEMO_SPAWN_BLOCK = LIBRG_LAST_ENUM_NUMBER, + DEMO_SPAWN_BLOCK = LIBRG_EVENT_LAST, }; enum { @@ -7,11 +7,27 @@ enum { DEMO_TYPE_NPC, }; +// enum { +// component_hero = librg_component_last, +// }; typedef struct { - zplm_vec3_t accel; - f32 walk_time; - f32 cooldown; - i32 max_hp; - i32 cur_hp; -} librg_component(hero); \ No newline at end of file + + struct { + zplm_vec3_t accel; + f32 walk_time; + f32 cooldown; + i32 max_hp; + i32 cur_hp; + librg_limiter_t limiter; + } stream; + +#ifdef DEMO_CLIENT + // interpolation + f32 delta; + zplm_vec3_t curr_pos, last_pos, target_pos; +#endif +} hero_t; + +// generate methods for components +// librg_component(hero, component_hero, hero_t); diff --git a/test/demo-server.c b/test/demo-server.c index b0d74c5..1287d48 100644 --- a/test/demo-server.c +++ b/test/demo-server.c @@ -1,105 +1,109 @@ #define LIBRG_DEBUG #define LIBRG_IMPLEMENTATION +#define LIBRG_LIMITER_IMPLEMENTATION #include +#include + +#define DEMO_SERVER #include "demo-defines.h" void on_connect_request(librg_event_t *event) { if (librg_data_ru32(event->data) != 42) { - return librg_event_reject(event); + librg_event_reject(event); } } void on_connect_accepted(librg_event_t *event) { librg_log("on_connect_accepted\n"); - librg_transform_t *transform = librg_fetch_transform(event->entity); - librg_client_t *client = librg_fetch_client(event->entity); - hero_t hero_ = {0}; - hero_.max_hp = hero_.cur_hp = 100; - - hero_t *hero = librg_attach_hero(event->entity, hero_); - - // transform->position.x = (float)(2000 - rand() % 4000); - // transform->position.y = (float)(2000 - rand() % 4000); - // transform->position.x = 200; - // transform->position.y = 200; - librg_log("spawning player %u at: %f %f %f\n", - event->entity, - transform->position.x, - transform->position.y, - transform->position.z + event->entity->id, + event->entity->position.x, + event->entity->position.y, + event->entity->position.z ); - librg_streamer_client_set(event->entity, client->peer); -} - -void on_spawn_npc(librg_message_t *msg) { - librg_transform_t tr; - librg_data_rptr(msg->data, &tr, sizeof(librg_transform_t)); - - librg_entity_t npc = librg_entity_create(1); - librg_attach_transform(npc, tr); - - // librg_streamer_client_remove(librg_get_client_entity(msg->peer)); + librg_entity_control_set(event->ctx, event->entity->id, event->entity->client_peer); } void on_entity_create_forplayer(librg_event_t *event) { - switch (librg_entity_type(event->entity)) { - case DEMO_TYPE_PLAYER: - case DEMO_TYPE_NPC: { - hero_t* hero = librg_fetch_hero(event->entity); + switch (event->entity->type) { + case DEMO_TYPE_PLAYER: + break; + case DEMO_TYPE_NPC: { + hero_t *hero = (hero_t *)event->entity->user_data; + librg_data_wptr(event->data, event->entity->user_data, sizeof(hero->stream)); + } break; + } +} - librg_data_wptr(event->data, hero, sizeof(*hero)); - } break; - } +void on_entity_update_forplayer(librg_event_t *event) { + // .. } -void entity_think_cb(librg_entity_t node) { - if (librg_entity_type(node) == DEMO_TYPE_NPC) { - hero_t *hero = librg_fetch_hero(node); - librg_transform_t *tran = librg_fetch_transform(node); - if (hero->walk_time == 0) { - hero->walk_time = 1000; - hero->accel.x += (rand() % 3 - 1.0) / 10.0; - hero->accel.y += (rand() % 3 - 1.0) / 10.0; +void ai_think(librg_ctx_t *ctx) { + for (int i = 0; i < ctx->max_entities; i++) + { + if (!librg_entity_valid(ctx, i)) continue; + librg_entity_t *entity = librg_entity_fetch(ctx, i); + if (entity->type == DEMO_TYPE_NPC) { - hero->accel.x = (hero->accel.x > -1.0) ? ((hero->accel.x < 1.0) ? hero->accel.x : 1.0) : -1.0; - hero->accel.y = (hero->accel.y > -1.0) ? ((hero->accel.y < 1.0) ? hero->accel.y : 1.0) : -1.0; - } - else { - zplm_vec3_t curpos = tran->position; + hero_t *hero = entity->user_data; - curpos.x += hero->accel.x; - curpos.y += hero->accel.y; + if (hero->stream.walk_time == 0) { + hero->stream.walk_time = 1000; + hero->stream.accel.x += (rand() % 3 - 1.0) / 10.0; + hero->stream.accel.y += (rand() % 3 - 1.0) / 10.0; - if (curpos.x < 0 || curpos.x >= 5000) { - curpos.x += hero->accel.x * -2; - hero->accel.x *= -1; + hero->stream.accel.x = (hero->stream.accel.x > -1.0) ? ((hero->stream.accel.x < 1.0) ? hero->stream.accel.x : 1.0) : -1.0; + hero->stream.accel.y = (hero->stream.accel.y > -1.0) ? ((hero->stream.accel.y < 1.0) ? hero->stream.accel.y : 1.0) : -1.0; } + else { + zplm_vec3_t curpos = entity->position; - if (curpos.y < 0 || curpos.y >= 5000) { - curpos.y += hero->accel.y * -2; - hero->accel.y *= -1; - } + curpos.x += hero->stream.accel.x; + curpos.y += hero->stream.accel.y; + + if (curpos.x < 0 || curpos.x >= 5000) { + curpos.x += hero->stream.accel.x * -2; + hero->stream.accel.x *= -1; + } + + if (curpos.y < 0 || curpos.y >= 5000) { + curpos.y += hero->stream.accel.y * -2; + hero->stream.accel.y *= -1; + } #define PP(x) x*x - if (zplm_vec3_mag2(hero->accel) > PP(0.3)) { - tran->position = curpos; - } + if (zplm_vec3_mag2(hero->stream.accel) > PP(0.3)) { + entity->position = curpos; + } #undef PP - hero->walk_time -= 32.0f; + hero->stream.walk_time -= 32.0f; - if (hero->walk_time < 0) { - hero->walk_time = 0; + if (hero->stream.walk_time < 0) { + hero->stream.walk_time = 0; + } } } } } -void ai_think() { - librg_entity_filter_t filter = { librg_index_hero() }; - librg_entity_each(filter, entity_think_cb); +void measure(void *userptr) { + librg_ctx_t *ctx = (librg_ctx_t *)userptr; + + if (!ctx || !ctx->network.host) return; + + static u32 lastdl = 0; + static u32 lastup = 0; + + f32 dl = (ctx->network.host->totalReceivedData - lastdl) * 8.0f / (1000.0f * 1000); // mbps + f32 up = (ctx->network.host->totalSentData - lastup) * 8.0f / (1000.0f * 1000); // mbps + + lastdl = ctx->network.host->totalReceivedData; + lastup = ctx->network.host->totalSentData; + + librg_log("librg_update: took %f ms. Current used bandwidth D/U: (%f / %f) mbps. \r", ctx->last_update, dl, up); } int main() { @@ -110,53 +114,56 @@ int main() { "==================================================\n"; librg_log("%s\n\n", test); - librg_init((librg_config_t) { - .tick_delay = 32, - .mode = LIBRG_MODE_SERVER, - .world_size = zplm_vec2(5000.0f, 5000.0f), - .max_connections = 1000, - .max_entities = 16000, - }); - - librg_event_add(LIBRG_CONNECTION_REQUEST, on_connect_request); - librg_event_add(LIBRG_CONNECTION_ACCEPT, on_connect_accepted); - librg_event_add(LIBRG_ENTITY_CREATE, on_entity_create_forplayer); + librg_ctx_t ctx = {0}; + ctx.mode = LIBRG_MODE_SERVER; + ctx.tick_delay = 64; + ctx.world_size = zplm_vec3(5000.0f, 5000.0f, 0.f); + ctx.max_connections = 128; + ctx.max_entities = 2000, - //librg_network_add(42, on_spawn_npc); + librg_init(&ctx); - librg_network_start((librg_address_t) { .port = 27010 }); + librg_event_add(&ctx, LIBRG_CONNECTION_REQUEST, on_connect_request); + librg_event_add(&ctx, LIBRG_CONNECTION_ACCEPT, on_connect_accepted); + librg_event_add(&ctx, LIBRG_ENTITY_CREATE, on_entity_create_forplayer); + librg_event_add(&ctx, LIBRG_ENTITY_UPDATE, on_entity_update_forplayer); -#if 0 - for (int i = 0; i < 15; ++i) - librg_fetch_transform(librg_entity_create(0))->position.x = i * 20; -#endif + librg_network_start(&ctx, (librg_address_t) { .port = 7777 }); #if 1 - for (isize i = 0; i < 10000; i++) { - librg_entity_t enemy = librg_entity_create(DEMO_TYPE_NPC); - librg_transform_t *transform = librg_fetch_transform(enemy); - transform->position.x = (float)(2000 - rand() % 4000); - transform->position.y = (float)(2000 - rand() % 4000); + for (isize i = 0; i < 1200; i++) { + librg_entity_id enemy = librg_entity_create(&ctx, DEMO_TYPE_NPC); + librg_entity_t *blob = librg_entity_fetch(&ctx, enemy); + + blob->position.x = (float)(2000 - rand() % 4000); + blob->position.y = (float)(2000 - rand() % 4000); hero_t hero_ = {0}; - hero_.max_hp = 100; - hero_.cur_hp = 40; + hero_.stream.max_hp = 100; + hero_.stream.cur_hp = 40; - hero_.accel.x = (rand() % 3 - 1.0); - hero_.accel.y = (rand() % 3 - 1.0); + hero_.stream.accel.x = (rand() % 3 - 1.0); + hero_.stream.accel.y = (rand() % 3 - 1.0); - hero_t *hero = librg_attach_hero(enemy, hero_); + blob->user_data = zpl_malloc(sizeof(hero_)); + *(hero_t *)blob->user_data = hero_; + librg_limiter_init(&((hero_t *)blob->user_data)->stream.limiter); } #endif + zpl_timer_t *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, 1000 * 1000, -1, measure); + zpl_timer_start(tick_timer, 1000); + while (true) { - librg_tick(); - ai_think(); - zpl_sleep_ms(32); + librg_tick(&ctx); + ai_think(&ctx); + zpl_sleep_ms(1); } - librg_network_stop(); - librg_free(); + librg_network_stop(&ctx); + librg_free(&ctx); return 0; } diff --git a/test/entity.c b/test/entity.c new file mode 100644 index 0000000..18e48b8 --- /dev/null +++ b/test/entity.c @@ -0,0 +1,94 @@ +#define LIBRG_IMPLEMENTATION +#define LIBRG_DEBUG +#include + + + +// entity_create - server +// entity_destroy - server +// entity_valid - both +// entity_type - both +// entity_blob - both +// entity_visibility_set - server +// entity_visibility_get - server +// entity_visibility_set_for - server +// entity_visibility_get_for - server +// entity_control_set - server +// entity_control_get - server +// entity_iterate - both + + + + // librg_inline void librg_ex_entity_iterate(librg_ctx_t *ctx, u64 flags, ) + +zpl_array_t(int) z; + +void zer(int **a) { + for (int i = 0; i < 15000; i++) { + zpl_array_append(*a, i); + } +} + +int bar(int **a) { + zer(a); + return 0; +} + +int query(int **a) { + static bool foo = false; + if (!foo) { + zpl_array_init(z, zpl_heap_allocator()); + foo = true; + } + + zpl_array_count(z) = 0; + + bar(&z); + *a = z; + + return 0; +} + + +int main() { + librg_ctx_t ctx = {0}; + ctx.max_entities = 100; + + zpl_array_t(int) a; + + for (int i = 0; i < 10000; ++i){ + query(&a); + } + + for (int i = 0; i < 15000; ++i) { + librg_assert(i == a[i]); + } + + librg_log("%td\n", zpl_array_count(a)); + + + librg_init(&ctx); + + for (int i = 0; i < 100; ++i) { + librg_log("created entity %d\n", librg_entity_create(&ctx, 0)); + } + + librg__entity_destroy(&ctx, 1); + librg__entity_destroy(&ctx, 2); + librg__entity_destroy(&ctx, 3); + librg__entity_destroy(&ctx, 5); + + librg_log("created entity %d\n", librg_entity_create(&ctx, 0)); + librg_log("created entity %d\n", librg_entity_create(&ctx, 0)); + librg_log("created entity %d\n", librg_entity_create(&ctx, 0)); + librg_log("created entity %d\n", librg_entity_create(&ctx, 0)); + + librg_network_start(&ctx, (librg_address_t) { 7777 }); + librg_tick(&ctx); + librg_tick(&ctx); + librg_tick(&ctx); + librg_network_stop(&ctx); + + librg_free(&ctx); + return 0; +} diff --git a/test/library.c b/test/library.c new file mode 100644 index 0000000..cc28be1 --- /dev/null +++ b/test/library.c @@ -0,0 +1,11 @@ +#define LIBRG_DEBUG +#define LIBRG_IMPLEMENTATION + +// Windows stub, it is needed since CMake won't do this for us.. +// +#ifdef WIN32 +# pragma comment(lib, "ws2_32.lib") +# pragma comment(lib, "winmm.lib") +#endif + +#include