├── .clangd.example ├── .editorconfig ├── .gitattributes ├── .github ├── codeql │ └── config.yml └── workflows │ ├── codeql-analysis.yml │ ├── notify-release.yml │ └── tests.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── code ├── apps │ ├── example-enet.c │ ├── example-packing.c │ ├── example-simple.c │ ├── example-unpacking.c │ ├── example-world.c │ ├── library.c │ ├── manual-testing.c │ └── query-performance.c ├── header │ ├── entity.h │ ├── general.h │ ├── packing.h │ ├── query.h │ └── types.h ├── librg.h ├── librg_enet.h ├── librg_hedley.h ├── source │ ├── entity.c │ ├── general.c │ ├── packing.c │ ├── query.c │ └── types.c ├── tests │ ├── cases │ │ ├── entity.h │ │ ├── general.h │ │ ├── packing.h │ │ └── query.h │ ├── unit.c │ └── unit.h └── vendor │ ├── enet.h │ └── zpl.h ├── docs ├── .nojekyll ├── README.md ├── _coverpage.md ├── _sidebar.md ├── allocators.md ├── compiletime.md ├── defs │ ├── config.md │ ├── entity.md │ ├── events.md │ ├── packing.md │ ├── query.md │ ├── types.md │ ├── utils.md │ └── world.md ├── index.html ├── migration.md └── quickstart.md ├── misc ├── CMakeLists.txt ├── embed.sh └── packager.js ├── package.json └── web ├── clean.sh └── setup.sh /.clangd.example: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | Add: [ 3 | -Wall, 4 | -Wextra, 5 | -Werror, 6 | -Wno-missing-field-initializers, 7 | -Wno-unused-value, 8 | -Wno-unused-function, 9 | -Wno-missing-braces, 10 | -I/absolute/path/to/librg/code, 11 | -I/absolute/path/to/librg/code/vendor, 12 | -DLIBRG_EDITOR=1, 13 | -DLIBRG_IMPLEMENTATION=1, 14 | ] 15 | Compiler: clang 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Set default charset 12 | charset = utf-8 13 | 14 | # 4 space indentation 15 | indent_style = space 16 | indent_size = 4 17 | 18 | [Makefile] 19 | indent_style = tab 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.h linguist-language=C 2 | code/vendor/* linguist-vendored 3 | -------------------------------------------------------------------------------- /.github/codeql/config.yml: -------------------------------------------------------------------------------- 1 | name: "librg CodeQL Config" 2 | 3 | queries: 4 | - uses: security-and-quality 5 | - uses: security-extended 6 | 7 | paths-ignore: 8 | - code/vendor 9 | 10 | # Documentation for configuring CodeQL is located here: 11 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning 12 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | schedule: 9 | - cron: '20 22 * * 0' 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v2 19 | 20 | - name: Initialize CodeQL 21 | uses: github/codeql-action/init@v1 22 | with: 23 | languages: 'cpp' 24 | config-file: ./.github/codeql/config.yml 25 | - run: make test 26 | 27 | - name: Perform CodeQL Analysis 28 | uses: github/codeql-action/analyze@v1 29 | -------------------------------------------------------------------------------- /.github/workflows/notify-release.yml: -------------------------------------------------------------------------------- 1 | name: notify-release 2 | on: 3 | release: 4 | types: 5 | - published 6 | 7 | jobs: 8 | notify: 9 | name: Show a changelog 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: sarisia/actions-status-discord@v1 13 | if: always() 14 | env: 15 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} 16 | with: 17 | description: "**${{ github.repository }}: [${{ github.event.release.name }}](${{ github.event.release.html_url }})**\n\n**Changelog:**\n${{ github.event.release.body }}" 18 | nodetail: true 19 | nofail: true 20 | color: "0x9999ff" 21 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | on: 3 | push: 4 | tags-ignore: 5 | - '[0-9]+.[0-9]+.[0-9]+' 6 | branches: 7 | - master 8 | - review/** 9 | paths: 10 | - 'code/**' 11 | - 'Makefile' 12 | - 'misc/CMakeLists.txt' 13 | pull_request: 14 | branches: 15 | - master 16 | paths: 17 | - 'code/**' 18 | - 'Makefile' 19 | - 'misc/CMakeLists.txt' 20 | 21 | jobs: 22 | prep: 23 | name: Notify about testing 24 | runs-on: ubuntu-latest 25 | if: "!contains(github.event.head_commit.message, 'Release')" 26 | steps: 27 | - uses: inlife/actiord-action@v1 28 | with: 29 | url: ${{ secrets.ACTIORD_URL }} 30 | icon: https://avatars1.githubusercontent.com/u/26773913?s=200&v=4 31 | state: started 32 | discord_channel: 354703318695673857 33 | 34 | build-win: 35 | name: Test Windows 36 | runs-on: windows-latest 37 | needs: [prep] 38 | steps: 39 | - uses: actions/checkout@v1 40 | - name: Run cmake generator 41 | run: mkdir build ; cd build ; cmake ../misc 42 | - name: Run Windows build 43 | run: cd build ; cmake --build . 44 | - name: Run unit tests 45 | run: cd build ; .\Debug\unit.exe 46 | 47 | build-lin: 48 | name: Test Linux 49 | runs-on: ubuntu-latest 50 | needs: [prep] 51 | steps: 52 | - uses: actions/checkout@v1 53 | - name: Run build on Linux 54 | run: make test 55 | - name: Run optimized build on Linux 56 | run: LEVEL=-O3 make test 57 | 58 | build-mac: 59 | name: Test macOS 60 | runs-on: macOS-latest 61 | needs: [prep] 62 | steps: 63 | - uses: actions/checkout@v1 64 | - name: Run build on macOS 65 | run: make test 66 | - name: Run optimized build on macOS 67 | run: LEVEL=-O3 make test 68 | 69 | done: 70 | name: Notify about status 71 | runs-on: ubuntu-latest 72 | needs: [build-mac, build-lin, build-win] 73 | if: "!contains(github.event.head_commit.message, 'Release')" 74 | steps: 75 | - uses: inlife/actiord-action@v1 76 | if: ${{ contains(needs.build-mac.result, 'success') && contains(needs.build-lin.result, 'success') && contains(needs.build-win.result, 'success') }} 77 | with: 78 | url: ${{ secrets.ACTIORD_URL }} 79 | icon: https://avatars1.githubusercontent.com/u/26773913?s=200&v=4 80 | state: succeeded 81 | discord_channel: 354703318695673857 82 | 83 | - uses: inlife/actiord-action@v1 84 | if: ${{ !(contains(needs.build-mac.result, 'success') && contains(needs.build-lin.result, 'success') && contains(needs.build-win.result, 'success')) }} 85 | with: 86 | url: ${{ secrets.ACTIORD_URL }} 87 | icon: https://avatars1.githubusercontent.com/u/26773913?s=200&v=4 88 | state: failed 89 | discord_channel: 354703318695673857 90 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | build/* 3 | build_web/* 4 | emsdk 5 | misc/deploy/ 6 | node_modules/ 7 | pkg/ 8 | 9 | # vs shit 10 | *.dir/ 11 | x64/ 12 | 13 | # Other 14 | .DS_store 15 | 16 | # local build scripts 17 | build.sh 18 | build.bat 19 | package-lock.json 20 | 21 | # configs 22 | .clangd 23 | 24 | # gtags 25 | GPATH 26 | GRTAGS 27 | GTAGS 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-2021 Vladyslav Hrytsenko. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation 10 | and/or other materials provided with the distribution. 11 | 3. Neither the name of the copyright holder nor the names of its contributors 12 | may be used to endorse or promote products derived from this software without 13 | specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC?=gcc 2 | CXX?=g++ 3 | STDC=-std=gnu11 4 | LEVEL?=-g 5 | 6 | ifeq ($(OS),Windows_NT) 7 | CFLAGS += -DWIN32 8 | LDFLAGS += -lws2_32 -lwinmm # only for enet example 9 | else 10 | OSDEF := $(shell uname -s) 11 | ifeq ($(OSDEF),Linux) 12 | LDFLAGS += -pthread -lm 13 | endif 14 | ifeq ($(OSDEF),OpenBSD) 15 | STDC=-std=c11 16 | CC=clang 17 | CXX=clang++ 18 | LDFLAGS += -pthread -lm 19 | endif 20 | ifeq ($(OSDEF),FreeBSD) 21 | STDC=-std=c11 22 | CC=clang 23 | CXX=clang++ 24 | LDFLAGS+=-pthread -lm 25 | endif 26 | endif 27 | 28 | WARNS = -Wall -Wextra -Werror -Wno-missing-field-initializers -Wno-unused-value -Wno-unused-function -Wno-missing-braces 29 | CFLAGS += $(LEVEL) $(STDC) -Icode $(WARNS) 30 | CXXFLAGS += $(LEVEL) -std=c++11 -Icode $(WARNS) 31 | 32 | APPS += $(patsubst %.c,%,$(wildcard code/apps/*.c)) 33 | APPS += $(patsubst %.cc,%,$(wildcard code/apps/*.cc)) 34 | 35 | BUILD_FILES = $(wildcard build/*) 36 | 37 | .PHONY: all clean apps test web 38 | 39 | all: clean apps test 40 | 41 | test: code/tests/unit 42 | @echo '> Building unit tests' 43 | build/unit 44 | 45 | apps: $(APPS) 46 | @echo '> Building apps' 47 | 48 | app: code/apps/$(NAME) 49 | @echo '> Building app $(NAME)' 50 | 51 | web: 52 | emcc $(LEVEL) -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]' \ 53 | -DLIBRG_EMSCRIPTEN=1 -I code/ code/apps/library.c -o build/librg.js 54 | clean: 55 | ifneq ($(BUILD_FILES),) 56 | @echo '> Cleaning up files' 57 | @rm -r $(BUILD_FILES) 58 | endif 59 | 60 | % : %.c 61 | @mkdir -p build 62 | @echo '> Building $(@F)' 63 | $(CC) $(CFLAGS) $^ $(LDFLAGS) -o build/$(@F) 64 | 65 | % : %.cc 66 | @mkdir -p build 67 | @echo '> Building $(@F)' 68 | $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o build/$(@F) 69 | 70 | .SILENT: 71 | 72 | embed: 73 | ./misc/embed.sh 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | librg 3 |
4 | 5 |
6 | build status 7 | version 8 | discord 9 | license 10 |
11 | 12 |
13 | 14 |
15 | Making multi-player game dev simpler since 2017. Single-header cross-platform world replication in pure C99. 16 |
17 | 18 |
19 | 20 | Built with love using zpl 21 | • Brought to you by @inlife, 22 | @zpl-zak 23 | and other contributors 24 | 25 |
26 | 27 | ## Introduction 28 | 29 | **librg** is a lightweight library that serves as a middleware between data-transferring libraries (networking, file-streaming, etc.) and core application/game logic. 30 | 31 | The main responsibilities of the library include: 32 | * entity tracking (tracks which entity belongs to what world and what states they have) 33 | * owner tracking (tracks which participant owns what entity) 34 | * Area of interest management (controls what can and cannot be seen by a participant) 35 | * world replication (re-creates a limited representation in a destination world of what is considered visible to a participant in the source world) 36 | 37 | The library was born to solve the complexities of setting up and managing the flow of multi-player games and dedicated game servers. 38 | It came a long way in stripping out things that were non-essential, slowly sculpting into its current form, which you are able to see and use today. 39 | 40 | ## Why you need it 41 | 42 | #### Without librg 43 | 44 | Usually, the networked game world consists of a set of networked players and a bunch of networked entities. The typical variant of setting up the synchronization relations between entities and players is to set up Everything-to-Everyone connections. 45 | 46 | This is the most basic setup to follow. However, with an increasing amount of entities, it becomes rather bandwidth-inefficient. 47 | 48 | ![world_without_librg](https://user-images.githubusercontent.com/2182108/189517945-afa096dd-f2f5-42cb-a0b9-22c2b81bc03b.png) 49 | 50 | #### With librg 51 | 52 | With librg, you can considerably decrease bandwidth usage by building radius & visibility-based entity relations. Entities will be synchronized only with the players they are visible to. 53 | 54 | ![world_with_librg](https://user-images.githubusercontent.com/2182108/189517948-afb2dfc9-f632-4a87-bf63-47e3bab5cc42.png) 55 | 56 | ## Benefits 57 | 58 | * Support for 2d/3d worlds of various sizes 59 | * Virtual world support 60 | * Custom entity visibility methods 61 | * Advanced entity querying 62 | * Events for entity-to-entity status changes 63 | * Networked LOD support (based on variable radius) 64 | * Cross-platform, lightweight, single-header 65 | 66 | ## Networking integration 67 | 68 | The overall interface of the library was made with the support of most network libraries in mind. 69 | 70 | The networking library has to support: 71 | 72 | 1. Ability to send and receive a `char *` buffer 73 | 2. Ability to read or set that buffer size 74 | 3. Ability to identify who is the receiver or sender of the data with an integer id 75 | 76 | And that is pretty much it! 77 | 78 | A list of what kind of libraries are supported: 79 | 80 | * [`ENet`](https://github.com/zpl-c/enet) 81 | * [`GameNetworkingSockets`](https://github.com/ValveSoftware/GameNetworkingSockets) 82 | * [`yojimbo`](https://github.com/networkprotocol/yojimbo) 83 | * [`SLikeNet`](https://github.com/SLikeSoft/SLikeNet) 84 | * [`KCP`](https://github.com/skywind3000/kcp) 85 | * [`Raknet`](https://github.com/facebookarchive/RakNet) 86 | * `Websocket` 87 | * `WebRTC` 88 | * Any other `UDP` or `TCP` based library 89 | 90 | > Note: you can check an example for the network [integration for enet](https://github.com/zpl-c/librg/blob/master/code/apps/example-enet.c). 91 | 92 | ## Installation 93 | 94 | `librg` is a single-header library, which means to use it, you only need to get the latest (or specific) version of the said header file from the [releases](https://github.com/zpl-c/librg/releases) section of this repository, add it to your project, and start enjoying the benefits. 95 | 96 | Alternatively, if you feel comfortable in your CLI, you can just do the following; 97 | 98 | ```sh 99 | curl -L https://github.com/zpl-c/librg/releases/latest/download/librg.h > librg.h 100 | 101 | # OR 102 | 103 | wget https://github.com/zpl-c/librg/releases/latest/download/librg.h -O librg.h 104 | ``` 105 | 106 | ## F.A.Q. 107 | 108 | 1. **Is this a networking library?** 109 | 110 | * Not really, no. It is intended to be used with networking in mind, but it does not have any networking capabilities on its own. 111 | 112 | 2. **Can I use any networking library with it?** 113 | 114 | * Yes. All you need is the ability to read data to and from the buffer, which most libraries support. Theoretically, it can be anything as low level as pure `UDP`, and up to `WebSocket`/`WebRTC`. 115 | 116 | 3. **The repository contains a bunch of `*.h` and `*.c` files, and yet you suggest it is a single-header library, how is that possible?** 117 | 118 | * The library is spread into multiple files so it is easier to work with it while developing, however, each time a new release is created, a "bundled" version of the header file is created and pushed directly to the [releases](https://github.com/zpl-c/librg/releases) page. 119 | 120 | 4. **Does librg offer an entity system?** 121 | 122 | * No, the library does not manage nor actually create its own entities, it rather expects you to provide your own entity/object handle to attach some internal data onto it, which in the context of the library is called "tracking". 123 | 124 | 5. **How do I pack data, do you provide methods for that?** 125 | 126 | * No, the library does not provide any data-packing/serialization methods. You should use some existing library for that (`protobuf`, `flatbuffers`, `msgpack`, etc.), or make your own implementation. 127 | 128 | 6. **I see you mention chunks, does it mean my game/app should be chunk-based?** 129 | 130 | * No. Chunks are only used internally to artificially divide the world's space into statically sized squares/cubes so that querying would work efficiently. Even if your game does use chunks, their amount/sizes/offsets are not required to much and should be the same. 131 | 132 | ## Documentation 133 | 134 | To read detailed documentation about the library, see examples, and a quick start guide, please visit our [documentation page](https://zpl-c.github.io/librg/#/quickstart). 135 | 136 | Additionally, you can check [code/apps](code/apps) folder for actual code examples. 137 | 138 | ## Illustrations 139 | 140 | ### World Replication 141 | 142 | Here is a simple illustration that attempts to replicate how the library works on a simple 2D world of 4x4 chunks. 143 | For a 3D world of bigger size, everything would work in a very similar way, just in 3 dimensions. 144 | 145 | 146 | librg illustration 147 | 148 | 149 |
150 | (click on the image to view full-size) 151 |
152 | 153 | ### World Protocol 154 | 155 | And this picture showcases the structure of the underlying binary protocol that is used to encode and decode data from/to. The resulting binary buffer could be inserted into any other buffer, saved to disk as a file, or sent via the network using any available method. Putting custom data alongside every entity within the packet allows context-dependant data storage that extends capabilities and allows memory- and bandwidth-efficient entity replication. 156 | 157 | 158 | librg illustration 159 | 160 | 161 |
162 | (click on the image to view full-size) 163 |
164 | 165 | ## Migration 166 | 167 | If you've used the library before version `v6.0.0`, it is recommended to read the migration guide [located here](https://zpl-c.github.io/librg/#/migration). 168 | 169 | ## Support 170 | 171 | We are testing the library for various platforms. This table provides some sort of description for compatibility. 172 | If you have tested it, and your result is different from the one in the table, please feel free to describe the issue in the [issues](https://github.com/zpl-c/librg/issues). 173 | 174 | 175 | | Platform / Result | Windows | macOS | Linux | iOS | Android | Raspberry Pi | OpenBSD | FreeBSD | Emscripten | 176 | |:------------------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|:-------------:| 177 | | ❔ | | | | clang | clang | gcc, clang | gcc, clang | gcc, clang | | 178 | | ✅ | msvc, mingw | gcc, clang | gcc, clang | | | | | | emcc | 179 | 180 | #### Legend: 181 | 182 | * ❔ - The library was not tested on this platform/compiler yet 183 | * ✅ - The library successfully compiles, and all tests are executed properly 184 | 185 | 186 | ## Development 187 | 188 | If you wish to contribute, add new features, optimizations, or overall improvements, here are the instructions on how to do that: 189 | 190 | 1. Clone the repo, f.e.: `git clone https://github.com/zpl-c/librg.git` 191 | 2. Run `make` to build all projects and verify everything works 192 | 3. Develop a feature, add tests for it in `code/tests/` 193 | 4. And eventually run `make test` again to check 194 | 195 | In case you are working from **Windows**, and/or are not able to use `make`, you can also use the built-in `cmake` config file to generate a Visual Studio solution, to do that: 196 | 197 | 1. `mkdir build` 198 | 2. `cd build` 199 | 3. `cmake ../misc -G"Visual Studio 16 2019"` (or any configuration you have) 200 | 4. `cmake --open .` (opens VS with the solution) 201 | 5. And repeat steps from above 202 | 203 | For developers, it offers nice benefits: 204 | 205 | * compile- and run-time configurable 206 | * written in C99 (portability reasons) 207 | * no external dependencies 208 | * built-in unit test coverage 209 | 210 | -------------------------------------------------------------------------------- /code/apps/example-enet.c: -------------------------------------------------------------------------------- 1 | /* this is a client-server demo for librg with enet */ 2 | /* it is inteded to show how librg can be used for managing a simple world */ 3 | /* it consists of 2 parts */ 4 | /* 1. server side, runs a server, hosts a world, creates a entity for each new client, handles position changes */ 5 | /* 2. client size, runs a client, connects to the server, gets updates about other entities, and sends updates about its own entity */ 6 | /* application hosts a server and MAX_CLIENTS amount of clients from within a single thread, runs for N ticks, and then exits */ 7 | /* client side will be constantly printing messages about entities being created or removed, that behavior is inteded */ 8 | /* it demonstrates how entities that are changing positions on the server, getiing in or out of the each other view range */ 9 | 10 | #define LIBRG_IMPL 11 | #include "librg.h" 12 | 13 | #if defined(__GCC__) || defined(__GNUC__) || defined(__clang__) 14 | #pragma GCC diagnostic push 15 | #pragma GCC diagnostic ignored "-Wunused-parameter" 16 | #pragma GCC diagnostic ignored "-Wunknown-pragmas" 17 | #endif 18 | 19 | #define ENET_IMPLEMENTATION 20 | #include "vendor/enet.h" 21 | 22 | #if defined(__GCC__) || defined(__GNUC__) || defined(__clang__) 23 | #pragma GCC diagnostic pop 24 | #endif 25 | 26 | #define MAX_CLIENTS 4 27 | typedef struct { float x, y, z; } vec3; 28 | 29 | // =======================================================================// 30 | // ! 31 | // ! Server-side 32 | // ! 33 | // =======================================================================// 34 | 35 | librg_world *server_world = NULL; 36 | 37 | int32_t server_write_update(librg_world *w, librg_event *e) { 38 | int64_t owner_id = librg_event_owner_get(w, e); 39 | int64_t entity_id = librg_event_entity_get(w, e); 40 | 41 | /* prevent sending updates to users who own that entity */ 42 | /* since they will be responsible on telling where that entity is supposed to be */ 43 | if (librg_entity_owner_get(w, entity_id) == owner_id) { 44 | return LIBRG_WRITE_REJECT; 45 | } 46 | 47 | /* read our current position */ 48 | ENetPeer *peer = (ENetPeer *)librg_entity_userdata_get(w, entity_id); 49 | 50 | char *buffer = librg_event_buffer_get(w, e); 51 | size_t max_length = librg_event_size_get(w, e); 52 | 53 | /* check if we have enough space to write and valid position */ 54 | if (sizeof(vec3) > max_length || !peer->data) { 55 | return LIBRG_WRITE_REJECT; 56 | } 57 | 58 | /* write data and return how much we've written */ 59 | memcpy(buffer, peer->data, sizeof(vec3)); 60 | return sizeof(vec3); 61 | } 62 | 63 | int32_t server_read_update(librg_world *w, librg_event *e) { 64 | int64_t entity_id = librg_event_entity_get(w, e); 65 | size_t actual_length = librg_event_size_get(w, e); 66 | 67 | if (actual_length != sizeof(vec3)) { 68 | printf("[server] Invalid data size coming from client\n"); 69 | return 0; 70 | } 71 | 72 | ENetPeer *peer = (ENetPeer *)librg_entity_userdata_get(w, entity_id); 73 | char *buffer = librg_event_buffer_get(w, e); 74 | 75 | /* read and update actual position */ 76 | vec3 position = {0}; 77 | memcpy(peer->data, buffer, actual_length); 78 | memcpy(&position, buffer, actual_length); 79 | 80 | /* and update librg actual chunk id */ 81 | librg_chunk chunk = librg_chunk_from_realpos(w, position.x, position.y, position.z); 82 | librg_entity_chunk_set(w, entity_id, chunk); 83 | 84 | return 0; 85 | } 86 | 87 | int server_start(int port) { 88 | ENetAddress address = {0}; 89 | 90 | address.host = ENET_HOST_ANY; /* Bind the server to the default localhost. */ 91 | address.port = port; /* Bind the server to port . */ 92 | 93 | /* create a server */ 94 | ENetHost *server = enet_host_create(&address, MAX_CLIENTS, 2, 0, 0); 95 | 96 | if (server == NULL) { 97 | printf("[server] An error occurred while trying to create an ENet server host.\n"); 98 | return 1; 99 | } 100 | 101 | printf("[server] Started an ENet server...\n"); 102 | server_world = librg_world_create(); 103 | 104 | if (server_world == NULL) { 105 | printf("[server] An error occurred while trying to create a server world.\n"); 106 | return 1; 107 | } 108 | 109 | printf("[server] Created a new server world\n"); 110 | 111 | /* store our host to the userdata */ 112 | librg_world_userdata_set(server_world, server); 113 | 114 | /* config our world grid */ 115 | librg_config_chunksize_set(server_world, 16, 16, 16); 116 | librg_config_chunkamount_set(server_world, 9, 9, 9); 117 | librg_config_chunkoffset_set(server_world, LIBRG_OFFSET_MID, LIBRG_OFFSET_MID, LIBRG_OFFSET_MID); 118 | 119 | librg_event_set(server_world, LIBRG_WRITE_UPDATE, server_write_update); 120 | librg_event_set(server_world, LIBRG_READ_UPDATE, server_read_update); 121 | 122 | return 0; 123 | } 124 | 125 | int server_update() { 126 | if (!librg_world_valid(server_world)) 127 | return 1; 128 | 129 | ENetHost *server = (ENetHost *)librg_world_userdata_get(server_world); 130 | ENetEvent event = {0}; 131 | 132 | while (enet_host_service(server, &event, 2) > 0) { 133 | switch (event.type) { 134 | case ENET_EVENT_TYPE_CONNECT: { 135 | printf("[server] A new user %d connected.\n", event.peer->incomingPeerID); 136 | int64_t entity_id = event.peer->incomingPeerID; 137 | 138 | /* we create an entity for our client */ 139 | /* in our case it is going to have same id as owner id */ 140 | /* since we do not really plan on adding more entities per client for now */ 141 | /* and place his entity right in the centerl of the world */ 142 | librg_entity_track(server_world, entity_id); 143 | librg_entity_owner_set(server_world, entity_id, event.peer->incomingPeerID); 144 | librg_entity_chunk_set(server_world, entity_id, 1); 145 | librg_entity_userdata_set(server_world, entity_id, event.peer); /* save ptr to peer */ 146 | 147 | /* allocate and store entity position in the data part of peer */ 148 | vec3 entity_position = {0}; 149 | event.peer->data = malloc(sizeof(vec3)); 150 | *((vec3*)event.peer->data) = entity_position; 151 | 152 | } break; 153 | case ENET_EVENT_TYPE_DISCONNECT: 154 | case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: { 155 | printf("[server] A user %d disconnected.\n", event.peer->incomingPeerID); 156 | int64_t entity_id = event.peer->incomingPeerID; 157 | librg_entity_untrack(server_world, entity_id); 158 | free(event.peer->data); 159 | } break; 160 | 161 | case ENET_EVENT_TYPE_RECEIVE: { 162 | /* handle a newly received event */ 163 | librg_world_read( 164 | server_world, 165 | event.peer->incomingPeerID, 166 | (char *)event.packet->data, 167 | event.packet->dataLength, 168 | NULL 169 | ); 170 | 171 | /* Clean up the packet now that we're done using it. */ 172 | enet_packet_destroy(event.packet); 173 | } break; 174 | 175 | case ENET_EVENT_TYPE_NONE: break; 176 | } 177 | } 178 | 179 | /* iterate peers and send them updates */ 180 | ENetPeer *currentPeer; 181 | for (currentPeer = server->peers; currentPeer < &server->peers[server->peerCount]; ++currentPeer) { 182 | if (currentPeer->state != ENET_PEER_STATE_CONNECTED) { 183 | continue; 184 | } 185 | 186 | char buffer[1024] = {0}; 187 | size_t buffer_length = 1024; 188 | 189 | /* serialize peer's the world view to a buffer */ 190 | librg_world_write( 191 | server_world, 192 | currentPeer->incomingPeerID, 193 | 2, /* chunk radius */ 194 | buffer, 195 | &buffer_length, 196 | NULL 197 | ); 198 | 199 | /* create packet with actual length, and send it */ 200 | ENetPacket *packet = enet_packet_create(buffer, buffer_length, ENET_PACKET_FLAG_RELIABLE); 201 | enet_peer_send(currentPeer, 0, packet); 202 | } 203 | 204 | return 0; 205 | } 206 | 207 | int server_stop() { 208 | if (!librg_world_valid(server_world)) 209 | return 1; 210 | 211 | ENetHost *server = (ENetHost *)librg_world_userdata_get(server_world); 212 | 213 | enet_host_destroy(server); 214 | librg_world_destroy(server_world); 215 | server_world = NULL; 216 | 217 | return 0; 218 | } 219 | 220 | // =======================================================================// 221 | // ! 222 | // ! Client-side 223 | // ! 224 | // =======================================================================// 225 | 226 | int32_t client_read_create(librg_world *w, librg_event *e) { 227 | int64_t owner_id = librg_event_owner_get(w, e); 228 | int64_t entity_id = librg_event_entity_get(w, e); 229 | printf("[client] An entity %d was created for owner: %d\n", (int)entity_id, (int)owner_id); 230 | return 0; 231 | } 232 | 233 | int32_t client_read_remove(librg_world *w, librg_event *e) { 234 | int64_t owner_id = librg_event_owner_get(w, e); 235 | int64_t entity_id = librg_event_entity_get(w, e); 236 | printf("[client] An entity %d was removed for owner: %d\n", (int)entity_id, (int)owner_id); 237 | return 0; 238 | } 239 | 240 | int32_t client_read_update(librg_world *w, librg_event *e) { 241 | // int64_t entity_id = librg_event_entity_get(w, e); 242 | size_t actual_length = librg_event_size_get(w, e); 243 | char *buffer = librg_event_buffer_get(w, e); 244 | if (actual_length != sizeof(vec3)) return 0; 245 | 246 | vec3 position = {0}; 247 | memcpy(&position, buffer, actual_length); 248 | 249 | return 0; 250 | } 251 | 252 | int32_t client_write_update(librg_world *w, librg_event *e) { 253 | // int64_t entity_id = librg_event_entity_get(w, e); 254 | // int64_t owner_id = librg_event_owner_get(w, e); 255 | char *buffer = librg_event_buffer_get(w, e); 256 | size_t max_length = librg_event_size_get(w, e); 257 | 258 | /* check if we have enough space to write and valid position */ 259 | if (sizeof(vec3) > max_length) { 260 | return LIBRG_WRITE_REJECT; 261 | } 262 | 263 | zpl_random r = {0}; 264 | zpl_random_init(&r); 265 | vec3 position = {0}; 266 | 267 | /* write our new random position */ 268 | zpl_u32 x = (zpl_random_gen_u32(&r)%40); position.x = (float)(x-20.0f); 269 | zpl_u32 y = (zpl_random_gen_u32(&r)%40); position.y = (float)(y-20.0f); 270 | zpl_u32 z = (zpl_random_gen_u32(&r)%40); position.z = (float)(z-20.0f); 271 | 272 | memcpy(buffer, &position, sizeof(vec3)); 273 | return sizeof(vec3); 274 | } 275 | 276 | librg_world *client_start(int port) { 277 | ENetAddress address = {0}; address.port = port; 278 | enet_address_set_host(&address, "127.0.0.1"); 279 | 280 | ENetHost *host = enet_host_create(NULL, 1, 2, 0, 0); 281 | ENetPeer *peer = enet_host_connect(host, &address, 2, 0); 282 | 283 | if (peer == NULL) { 284 | printf("[client] Cannot connect\n"); 285 | return NULL; 286 | } 287 | 288 | librg_world *world = librg_world_create(); 289 | librg_world_userdata_set(world, peer); 290 | 291 | librg_event_set(world, LIBRG_READ_CREATE, client_read_create); 292 | librg_event_set(world, LIBRG_READ_UPDATE, client_read_update); 293 | librg_event_set(world, LIBRG_READ_REMOVE, client_read_remove); 294 | librg_event_set(world, LIBRG_WRITE_UPDATE, client_write_update); 295 | 296 | return world; 297 | } 298 | 299 | int client_update(int ID, librg_world *world) { 300 | if (!librg_world_valid(world)) 301 | return 1; 302 | 303 | ENetEvent event = {0}; 304 | 305 | ENetPeer *peer = (ENetPeer *)librg_world_userdata_get(world); 306 | ENetHost *host = peer->host; 307 | 308 | while (enet_host_service(host, &event, 2) > 0) { 309 | switch (event.type) { 310 | case ENET_EVENT_TYPE_CONNECT: { 311 | printf("[client] User %d has connected to the server.\n", ID); 312 | } break; 313 | case ENET_EVENT_TYPE_DISCONNECT: 314 | case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: { 315 | printf("[client] A user %d has disconnected from server.\n", ID); 316 | } break; 317 | 318 | case ENET_EVENT_TYPE_RECEIVE: { 319 | /* handle a newly received event */ 320 | librg_world_read( 321 | world, 322 | ID, 323 | (char *)event.packet->data, 324 | event.packet->dataLength, 325 | NULL 326 | ); 327 | 328 | /* Clean up the packet now that we're done using it. */ 329 | enet_packet_destroy(event.packet); 330 | } break; 331 | 332 | case ENET_EVENT_TYPE_NONE: break; 333 | } 334 | } 335 | 336 | /* send our data to the server */ 337 | char buffer[1024] = {0}; 338 | size_t buffer_length = 1024; 339 | 340 | /* serialize peer's the world view to a buffer */ 341 | librg_world_write( 342 | world, 343 | ID, 344 | 0, 345 | buffer, 346 | &buffer_length, 347 | NULL 348 | ); 349 | 350 | /* create packet with actual length, and send it */ 351 | ENetPacket *packet = enet_packet_create(buffer, buffer_length, ENET_PACKET_FLAG_RELIABLE); 352 | enet_peer_send(peer, 0, packet); 353 | 354 | return 0; 355 | } 356 | 357 | int client_stop(librg_world *world) { 358 | if (!librg_world_valid(world)) 359 | return 1; 360 | 361 | ENetPeer *peer = (ENetPeer *)librg_world_userdata_get(world); 362 | ENetHost *host = peer->host; 363 | 364 | librg_world_destroy(world); 365 | 366 | enet_peer_disconnect_now(peer, 0); 367 | enet_host_destroy(host); 368 | 369 | return 0; 370 | } 371 | 372 | // =======================================================================// 373 | // ! 374 | // ! Main application 375 | // ! 376 | // =======================================================================// 377 | 378 | int main() { 379 | if (enet_initialize() != 0) { 380 | printf("[app] An error occurred while initializing ENet.\n"); 381 | return 1; 382 | } 383 | 384 | int port = 27019; 385 | server_start(port); 386 | 387 | librg_world *clients[MAX_CLIENTS] = {0}; 388 | for (int i = 0; i < MAX_CLIENTS; ++i) 389 | clients[i] = client_start(port); 390 | 391 | /* program will make N iterations, and then exit */ 392 | for (int i = 0; i < 500; ++i) { 393 | server_update(); 394 | 395 | for (int i = 0; i < MAX_CLIENTS; ++i) { 396 | client_update(i, clients[i]); 397 | } 398 | 399 | zpl_sleep_ms(10); 400 | } 401 | 402 | printf("[app] Stopping everyting\n"); 403 | 404 | for (int i = 0; i < MAX_CLIENTS; ++i) 405 | client_stop(clients[i]); 406 | 407 | server_stop(); 408 | enet_deinitialize(); 409 | 410 | printf("[app] Done!\n"); 411 | return 0; 412 | } 413 | -------------------------------------------------------------------------------- /code/apps/example-packing.c: -------------------------------------------------------------------------------- 1 | #define LIBRG_IMPL 2 | #include "librg.h" 3 | 4 | /** 5 | * SECTION WITH HANDLERS 6 | */ 7 | 8 | int32_t mywrite_create(librg_world *w, librg_event *e) { 9 | char *buffer = librg_event_buffer_get(w, e); 10 | const char *str = "Hello world!"; 11 | 12 | /* make sure we have enough space */ 13 | if ((int32_t)strlen(str) > librg_event_size_get(w, e)) 14 | return LIBRG_WRITE_REJECT; 15 | 16 | /* feel free to replace memcpy with any serialization library */ 17 | /* like: protobuf, flatbuf, msgpack, cap'n'proto, etc */ 18 | memcpy(buffer, str, strlen(str)); 19 | 20 | int64_t owner_id = librg_event_owner_get(w, e); 21 | int64_t entity_id = librg_event_entity_get(w, e); 22 | printf("entity: %d was created for owner: %d\n", (int)entity_id, (int)owner_id); 23 | 24 | return strlen(str); 25 | } 26 | 27 | int32_t mywrite_update(librg_world *w, librg_event *e) { 28 | int32_t my_secret_number = 42; 29 | char *buffer = librg_event_buffer_get(w, e); 30 | memcpy(buffer, &my_secret_number, sizeof(int32_t)); 31 | return sizeof(int32_t); 32 | } 33 | 34 | int32_t mywrite_remove(librg_world *w, librg_event *e) { 35 | int64_t owner_id = librg_event_owner_get(w, e); 36 | int64_t entity_id = librg_event_entity_get(w, e); 37 | printf("entity: %d was removed for owner: %d\n", (int)entity_id, (int)owner_id); 38 | return 0; 39 | } 40 | 41 | /** 42 | * SECTION WITH MAIN LOGIC 43 | */ 44 | 45 | int main() { 46 | #define entity_amt 4 47 | const int entities[entity_amt] = { 48 | 32, 34, 55, 67 49 | }; 50 | 51 | librg_world *world = librg_world_create(); 52 | 53 | /* set our event handlers */ 54 | librg_event_set(world, LIBRG_WRITE_CREATE, mywrite_create); 55 | librg_event_set(world, LIBRG_WRITE_UPDATE, mywrite_update); 56 | librg_event_set(world, LIBRG_WRITE_REMOVE, mywrite_remove); 57 | 58 | /* track all of our world entities, put each in its own chunk */ 59 | for (int i = 0; i < entity_amt; ++i) { 60 | librg_entity_track(world, entities[i]); 61 | librg_entity_chunk_set(world, entities[i], i); 62 | } 63 | 64 | /* and track a special entity for our owner */ 65 | const int owner = 42; 66 | const int owner_entity = 777; 67 | const int radius = 1; 68 | librg_entity_track(world, owner_entity); 69 | librg_entity_chunk_set(world, owner_entity, 1); 70 | librg_entity_owner_set(world, owner_entity, owner); 71 | 72 | /* since we've placed our entities in chunks 0,1,2,3 */ 73 | /* and our owner entity is in chunk 1, with visibility radius of 1 */ 74 | /* it means it should see only entities in chunks 0,1,2 */ 75 | /* (3 other entities plus it's own entity from (4+1) total entities in the world) */ 76 | 77 | /* write owner viewport to the buffer */ 78 | char buffer[256] = {0}; size_t total_size = 256; 79 | librg_world_write(world, owner, radius, buffer, &total_size, NULL); 80 | 81 | /* now our buffer contains packed world data */ 82 | /* specific for that given owner id (42) */ 83 | /* you can send this data over the network */ 84 | /* store it in memory or save to a file */ 85 | /* and later on, read the data using librg_world_read */ 86 | 87 | { 88 | /* in this example we going to save buffer to a file */ 89 | /* check out example-unpacking.c for reading demo */ 90 | zpl_file f = {0}; 91 | zpl_file_open_mode(&f, ZPL_FILE_MODE_WRITE, "build/example-data.buf"); 92 | zpl_file_write(&f, buffer, total_size); 93 | zpl_file_close(&f); 94 | } 95 | 96 | /* cleanup */ 97 | librg_world_destroy(world); 98 | return 0; 99 | } 100 | -------------------------------------------------------------------------------- /code/apps/example-simple.c: -------------------------------------------------------------------------------- 1 | #define LIBRG_IMPL 2 | #include "librg.h" 3 | 4 | int main() { 5 | librg_world *world = librg_world_create(); 6 | 7 | /* create our world configuration */ 8 | librg_config_chunksize_set(world, 16, 16, 16); 9 | librg_config_chunkamount_set(world, 9, 9, 9); 10 | librg_config_chunkoffset_set(world, LIBRG_OFFSET_MID, LIBRG_OFFSET_MID, LIBRG_OFFSET_MID); 11 | 12 | /* track an existing game entity and set it's params */ 13 | librg_entity_track(world, 1); 14 | librg_entity_owner_set(world, 1, 1); 15 | librg_entity_chunk_set(world, 1, 1); 16 | 17 | /* fetch entities via query */ 18 | int64_t entities[64] = {0}; 19 | size_t entity_amount = 64; 20 | librg_world_query(world, 1, 0, entities, &entity_amount); 21 | 22 | /* write owner's point of view to a buffer */ 23 | char buffer[256] = {0}; 24 | size_t buffer_legnth = 256; 25 | librg_world_write(world, 1, 0, buffer, &buffer_legnth, NULL); 26 | printf("written a buffer of length %d\n", (int)buffer_legnth); 27 | 28 | librg_world_destroy(world); 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /code/apps/example-unpacking.c: -------------------------------------------------------------------------------- 1 | #define LIBRG_IMPL 2 | #include "librg.h" 3 | #include 4 | 5 | /** 6 | * SECTION WITH HANDLERS 7 | */ 8 | 9 | int32_t myread_create(librg_world *w, librg_event *e) { 10 | int64_t owner_id = librg_event_owner_get(w, e); 11 | int64_t entity_id = librg_event_entity_get(w, e); 12 | printf("entity: %d was created for owner: %d, owner: %d\n", (int)entity_id, (int)owner_id, (int)librg_entity_owner_get(w, entity_id)); 13 | 14 | 15 | char buffer[64] = {0}; 16 | memcpy(buffer, librg_event_buffer_get(w, e), librg_event_size_get(w, e)); 17 | printf("out special message: %s\n", buffer); 18 | 19 | return 0; 20 | } 21 | 22 | int32_t myread_update(librg_world *w, librg_event *e) { 23 | int32_t my_secret_number = -1; 24 | char *buffer = librg_event_buffer_get(w, e); 25 | memcpy(&my_secret_number, buffer, sizeof(int32_t)); 26 | 27 | /* check our secret number */ 28 | assert(my_secret_number == 42); 29 | return 0; 30 | } 31 | 32 | int32_t myread_remove(librg_world *w, librg_event *e) { 33 | int64_t owner_id = librg_event_owner_get(w, e); 34 | int64_t entity_id = librg_event_entity_get(w, e); 35 | printf("entity: %d was removed for owner: %d\n", (int)entity_id, (int)owner_id); 36 | return 0; 37 | } 38 | 39 | /** 40 | * SECTION WITH MAIN LOGIC 41 | */ 42 | 43 | int main() { 44 | librg_world *world = librg_world_create(); 45 | 46 | /* set our event handlers */ 47 | librg_event_set(world, LIBRG_READ_CREATE, myread_create); 48 | librg_event_set(world, LIBRG_READ_UPDATE, myread_update); 49 | librg_event_set(world, LIBRG_READ_REMOVE, myread_remove); 50 | 51 | /* out data buffer */ 52 | char buffer[256] = {0}; 53 | size_t total_size = 0; 54 | 55 | { 56 | /* in this example we going to load existing buffer from a file */ 57 | /* check out example-packing.c for writing demo */ 58 | zpl_file f = {0}; 59 | zpl_file_open_mode(&f, ZPL_FILE_MODE_READ, "build/example-data.buf"); 60 | total_size = (size_t)zpl_file_size(&f); 61 | zpl_file_read(&f, buffer, total_size); 62 | zpl_file_close(&f); 63 | } 64 | 65 | /* owner id does not have to match original owner id from example-packing.c */ 66 | /* think of it as a owner translation, if you encoded data for owner 42 */ 67 | /* the one who reads on this side will be the one who was supposed to read it */ 68 | /* even if his local owner id will be different from original value on other side */ 69 | /* but for simplicity sake, we will keep it here 42 as well */ 70 | 71 | const int owner = 42; 72 | librg_world_read(world, owner, buffer, total_size, NULL); 73 | 74 | /* we should expect to have 4 entities created */ 75 | /* 3 other entities within his view radius */ 76 | /* 1 entity is owner own entity */ 77 | /* you can check original entity ids in example-packing.c */ 78 | 79 | assert(librg_entity_tracked(world, 32)); 80 | assert(librg_entity_tracked(world, 34)); 81 | assert(librg_entity_tracked(world, 55)); 82 | 83 | assert(librg_entity_tracked(world, 777)); 84 | assert(librg_entity_owner_get(world, 777) == owner); 85 | 86 | /* cleanup */ 87 | librg_world_destroy(world); 88 | return 0; 89 | } 90 | -------------------------------------------------------------------------------- /code/apps/example-world.c: -------------------------------------------------------------------------------- 1 | #define LIBRG_IMPL 2 | #include "librg.h" 3 | 4 | int dostuff(librg_world *w) { 5 | if (!librg_world_valid(w)) 6 | return 1; 7 | 8 | int *mydata = librg_world_userdata_get(w); 9 | if (!mydata) 10 | return 2; 11 | 12 | *mydata += 15; 13 | return 0; 14 | } 15 | 16 | int main() { 17 | librg_world *world = librg_world_create(); 18 | 19 | if (!world) { 20 | printf("failed to create a world\n"); 21 | return 1; 22 | } 23 | 24 | int mydata = 235; 25 | librg_world_userdata_set(world, &mydata); 26 | 27 | if (dostuff(world) != 0) { 28 | printf("failed to execute my function\n"); 29 | librg_world_destroy(world); 30 | return 1; 31 | } 32 | 33 | if (mydata == 250) { 34 | printf("we succeeded\n"); 35 | } 36 | 37 | librg_world_destroy(world); 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /code/apps/library.c: -------------------------------------------------------------------------------- 1 | #define LIBRG_IMPL 2 | #include "librg.h" 3 | 4 | #if !defined(LIBRG_LIBRARY_NOMAIN) && !defined(LIBRG_EMSCRIPTEN) 5 | int main() {return 0;} 6 | #endif 7 | -------------------------------------------------------------------------------- /code/apps/manual-testing.c: -------------------------------------------------------------------------------- 1 | /* this file is usually used to manually test some stuff */ 2 | 3 | #include 4 | // #include 5 | // #include 6 | 7 | // void *myalloc(size_t size) { 8 | // void *ptr = malloc(size); 9 | // printf("allocating mem[%zd]: 0x%llx\n", size, (uint64_t)ptr); 10 | // return ptr; 11 | // } 12 | 13 | // void myfree(void *ptr) { 14 | // printf("freeing mem: 0x%llx\n", (uint64_t)ptr); 15 | // free(ptr); 16 | // return; 17 | // } 18 | 19 | // #define LIBRG_MEM_ALLOC(x) myalloc(x) 20 | // #define LIBRG_MEM_FREE(x) myfree(x) 21 | 22 | #define LIBRG_IMPL 23 | #define LIBRG_DEBUG 24 | #define LIBRG_ENABLE_EXTENDED_EVENTBUFFER // <-- if uncommented should error out the app because of line 37 (intentional) 25 | 26 | // #define LIBRG_WORLDWRITE_MAXQUERY 360 27 | #include "librg.h" 28 | 29 | // TODO: add librg_enet code 30 | // TODO: add behavior support 31 | 32 | /* impl part*/ 33 | int32_t _parent_create(librg_world *world, librg_event *event) { 34 | zpl_unused(world); 35 | zpl_unused(event); 36 | // printf("_parent_create %p %d\n", world, event->type); 37 | return ZPL_I32_MAX-1; 38 | } 39 | 40 | int32_t _child_create(librg_world *world, librg_event *event) { 41 | zpl_unused(world); 42 | zpl_unused(event); 43 | // printf("_child_create %p %d\n", world, event->type); 44 | return 0; 45 | } 46 | 47 | int main() { 48 | printf("version %d\n", librg_version()); 49 | 50 | librg_world *world = librg_world_create(); 51 | assert(librg_world_valid(world)); 52 | 53 | librg_config_chunksize_set(world, 16, 16, 16); 54 | librg_config_chunkamount_set(world, 16, 16, 16); 55 | librg_config_chunkoffset_set(world, LIBRG_OFFSET_MID, LIBRG_OFFSET_MID, LIBRG_OFFSET_MID); 56 | 57 | librg_event_set(world, LIBRG_WRITE_CREATE, _parent_create); 58 | librg_event_set(world, LIBRG_READ_CREATE, _child_create); 59 | 60 | const int myId = 24; 61 | const int observerRadius = 1; 62 | const librg_chunk chunkId = librg_chunk_from_chunkpos(world, 0, 0, 0); 63 | 64 | librg_entity_track(world, myId); 65 | zpl_printf("setting chunk to: %lld\n", chunkId); 66 | librg_entity_chunk_set(world, myId, chunkId); 67 | librg_entity_owner_set(world, myId, 1); 68 | 69 | const int totalEnts = 40000; 70 | for (int i=0;i querying...\n"); 77 | 78 | #define RESSIZE 4096 79 | int64_t results[RESSIZE] = {0}; 80 | 81 | #define BUFSIZE 10000 82 | char buffer[BUFSIZE] = {0}; 83 | 84 | zpl_f64 tstart = zpl_time_rel(); 85 | size_t amount = RESSIZE; 86 | librg_world_query(world, 1, observerRadius, results, &amount); 87 | zpl_printf("query found %d results of %d in (%.3f ms)\n", amount, totalEnts, zpl_time_rel() - tstart); 88 | // for (int i=0; i encoding...\n"); 91 | 92 | tstart = zpl_time_rel(); 93 | 94 | size_t buffer_size = 10000; 95 | int32_t result = librg_world_write(world, 1, observerRadius, buffer, &buffer_size, NULL); 96 | 97 | if (result > 0) { 98 | printf("AAA, you didnt have enough space to write stuff in your buffer mister\n"); 99 | } 100 | 101 | zpl_printf("written %zu bytes in (%.3f ms)\n", buffer_size, zpl_time_rel() - tstart); 102 | 103 | librg_world *w2 = librg_world_create(); 104 | 105 | librg_event_set(w2, LIBRG_WRITE_CREATE, _parent_create); 106 | librg_event_set(w2, LIBRG_READ_CREATE, _child_create); 107 | 108 | tstart = zpl_time_rel(); 109 | int r = librg_world_read(w2, 1, buffer, buffer_size, NULL); 110 | zpl_printf("read %zu bytes, result: %d, entities: %d in (%.3f ms)\n", buffer_size, r, librg_entity_count(w2), zpl_time_rel() - tstart); 111 | 112 | librg_entity_untrack(world, myId); 113 | librg_world_destroy(world); 114 | librg_world_destroy(w2); 115 | return 0; 116 | } 117 | -------------------------------------------------------------------------------- /code/apps/query-performance.c: -------------------------------------------------------------------------------- 1 | #define LIBRG_IMPL 2 | // #define LIBRG_ENTITY_MAXCHUNKS 1 3 | #include "librg.h" 4 | 5 | #define MAX_ENTITY 1000 6 | #define QUERY_ATTEMPTS 1000 7 | 8 | int main() { 9 | librg_world *world = librg_world_create(); 10 | 11 | /* create our world configuration */ 12 | librg_config_chunksize_set(world, 16, 16, 0); 13 | librg_config_chunkamount_set(world, 64, 64, 0); 14 | librg_config_chunkoffset_set(world, LIBRG_OFFSET_BEG, LIBRG_OFFSET_BEG, LIBRG_OFFSET_BEG); 15 | 16 | zpl_f64 tstart = zpl_time_rel_ms(); 17 | 18 | /* create set of testing entities */ 19 | for (int i = 0; i < MAX_ENTITY; ++i) { 20 | int chx = rand() % 64; 21 | int chy = rand() % 64; 22 | 23 | librg_entity_track(world, i); 24 | librg_chunk chid = librg_chunk_from_chunkpos(world, chx, chy, 0); 25 | librg_entity_chunk_set(world, i, chid); 26 | } 27 | 28 | zpl_printf("[test] tracked %d entities in (%.3f ms)\n", librg_world_entities_tracked(world), zpl_time_rel_ms() - tstart); 29 | 30 | /* set owner to a single entity */ 31 | int ownerid = 1; 32 | librg_entity_owner_set(world, 0, ownerid); 33 | 34 | /* fetch entities via query */ 35 | int64_t entities[1000] = {0}; 36 | size_t entity_amount = 1000; 37 | int query_radius = 16; 38 | 39 | tstart = zpl_time_rel(); 40 | 41 | for (int i = 0; i < QUERY_ATTEMPTS; ++i) { 42 | size_t entity_limit = 1000; 43 | librg_world_query(world, ownerid, query_radius, entities, &entity_limit); 44 | entity_amount = entity_limit; 45 | } 46 | 47 | zpl_printf("[test] found %d entities in (%.3f ms)\n", entity_amount, zpl_time_rel_ms() - tstart); 48 | 49 | /* results */ 50 | // 51 | // before switch to internal memory 52 | // [test] found 171 entities in (7800.685 ms) 53 | // [test] found 171 entities in (7789.673 ms) 54 | // [test] found 171 entities in (7804.661 ms) 55 | // 56 | // after switch to internal memory 57 | // [test] found 171 entities in (4308.592 ms) 58 | // [test] found 171 entities in (4234.625 ms) 59 | // [test] found 171 entities in (4221.636 ms) 60 | 61 | librg_world_destroy(world); 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /code/header/entity.h: -------------------------------------------------------------------------------- 1 | // file: header/entity.h 2 | 3 | #ifdef LIBRG_EDITOR 4 | #include 5 | #endif 6 | 7 | LIBRG_BEGIN_C_DECLS 8 | 9 | // =======================================================================// 10 | // ! 11 | // ! Basic entity manipulation 12 | // ! 13 | // =======================================================================// 14 | 15 | LIBRG_API int8_t librg_entity_track(librg_world *world, int64_t entity_id); 16 | LIBRG_API int8_t librg_entity_untrack(librg_world *world, int64_t entity_id); 17 | LIBRG_API int8_t librg_entity_tracked(librg_world *world, int64_t entity_id); 18 | LIBRG_API int8_t librg_entity_foreign(librg_world *world, int64_t entity_id); 19 | LIBRG_API int8_t librg_entity_owned(librg_world *world, int64_t entity_id); 20 | LIBRG_API int32_t librg_entity_count(librg_world *world); 21 | 22 | // =======================================================================// 23 | // ! 24 | // ! Entity data methods 25 | // ! 26 | // =======================================================================// 27 | 28 | LIBRG_API int8_t librg_entity_userdata_set(librg_world *world, int64_t entity_id, void *data); 29 | LIBRG_API void * librg_entity_userdata_get(librg_world *world, int64_t entity_id); 30 | LIBRG_API int8_t librg_entity_chunk_set(librg_world *world, int64_t entity_id, librg_chunk); 31 | LIBRG_API librg_chunk librg_entity_chunk_get(librg_world *world, int64_t entity_id); 32 | LIBRG_API int8_t librg_entity_chunkarray_set(librg_world *world, int64_t entity_id, const librg_chunk *chunks, size_t chunk_amount); 33 | LIBRG_API int8_t librg_entity_chunkarray_get(librg_world *world, int64_t entity_id, LIBRG_OUT librg_chunk *chunks, LIBRG_INOUT size_t *chunk_amount); 34 | LIBRG_API int8_t librg_entity_dimension_set(librg_world *world, int64_t entity_id, int32_t dimension); 35 | LIBRG_API int32_t librg_entity_dimension_get(librg_world *world, int64_t entity_id); 36 | LIBRG_API int8_t librg_entity_owner_set(librg_world *world, int64_t entity_id, int64_t owner_id); 37 | LIBRG_API int64_t librg_entity_owner_get(librg_world *world, int64_t entity_id); 38 | LIBRG_API int8_t librg_entity_visibility_global_set(librg_world *world, int64_t entity_id, librg_visibility value); 39 | LIBRG_API int8_t librg_entity_visibility_global_get(librg_world *world, int64_t entity_id); 40 | LIBRG_API int8_t librg_entity_visibility_owner_set(librg_world *world, int64_t entity_id, int64_t owner_id, librg_visibility value); 41 | LIBRG_API int8_t librg_entity_visibility_owner_get(librg_world *world, int64_t entity_id, int64_t owner_id); 42 | 43 | /* deprecated since 7.0 */ 44 | LIBRG_API int8_t librg_entity_radius_set(librg_world *world, int64_t entity_id, int8_t observed_chunk_radius); 45 | LIBRG_API int8_t librg_entity_radius_get(librg_world *world, int64_t entity_id); 46 | 47 | #if 0 /* for future releases */ 48 | LIBRG_API int8_t librg_entity_behavior_set(librg_world *world, int64_t entity_id, librg_behavior key, int32_t value); 49 | LIBRG_API int32_t librg_entity_behavior_get(librg_world *world, int64_t entity_id, librg_behavior key); 50 | #endif 51 | 52 | LIBRG_END_C_DECLS 53 | -------------------------------------------------------------------------------- /code/header/general.h: -------------------------------------------------------------------------------- 1 | // file: header/general.h 2 | 3 | #ifdef LIBRG_EDITOR 4 | #include 5 | #endif 6 | 7 | LIBRG_BEGIN_C_DECLS 8 | 9 | // =======================================================================// 10 | // ! 11 | // ! Context methods 12 | // ! 13 | // =======================================================================// 14 | 15 | LIBRG_API uint32_t librg_version(); 16 | LIBRG_API librg_world * librg_world_create(); 17 | LIBRG_API int8_t librg_world_destroy(librg_world *world); 18 | LIBRG_API int8_t librg_world_valid(librg_world *world); 19 | LIBRG_API int8_t librg_world_userdata_set(librg_world *world, void *data); 20 | LIBRG_API void * librg_world_userdata_get(librg_world *world); 21 | LIBRG_API int64_t librg_world_entities_tracked(librg_world *world); 22 | 23 | // =======================================================================// 24 | // ! 25 | // ! Configuration methods 26 | // ! 27 | // =======================================================================// 28 | 29 | LIBRG_API int8_t librg_config_chunkamount_set(librg_world *world, uint16_t x, uint16_t y, uint16_t z); 30 | LIBRG_API int8_t librg_config_chunkamount_get(librg_world *world, uint16_t *x, uint16_t *y, uint16_t *z); 31 | LIBRG_API int8_t librg_config_chunksize_set(librg_world *world, uint16_t x, uint16_t y, uint16_t z); 32 | LIBRG_API int8_t librg_config_chunksize_get(librg_world *world, uint16_t *x, uint16_t *y, uint16_t *z); 33 | LIBRG_API int8_t librg_config_chunkoffset_set(librg_world *world, int16_t x, int16_t y, int16_t z); 34 | LIBRG_API int8_t librg_config_chunkoffset_get(librg_world *world, int16_t *x, int16_t *y, int16_t *z); 35 | 36 | // =======================================================================// 37 | // ! 38 | // ! Events 39 | // ! 40 | // =======================================================================// 41 | 42 | LIBRG_API int8_t librg_event_set(librg_world *world, librg_event_type, librg_event_fn); 43 | LIBRG_API int8_t librg_event_remove(librg_world *world, librg_event_type); 44 | 45 | LIBRG_API int8_t librg_event_type_get(librg_world *world, librg_event *event); 46 | LIBRG_API int64_t librg_event_owner_get(librg_world *world, librg_event *event); 47 | LIBRG_API int64_t librg_event_entity_get(librg_world *world, librg_event *event); 48 | LIBRG_API char * librg_event_buffer_get(librg_world *world, librg_event *event); 49 | LIBRG_API int32_t librg_event_size_get(librg_world *world, librg_event *event); 50 | LIBRG_API void * librg_event_userdata_get(librg_world *world, librg_event *event); 51 | 52 | // =======================================================================// 53 | // ! 54 | // ! Utility methods 55 | // ! 56 | // =======================================================================// 57 | 58 | LIBRG_API librg_chunk librg_chunk_from_realpos(librg_world *world, double x, double y, double z); 59 | LIBRG_API librg_chunk librg_chunk_from_chunkpos(librg_world *world, int16_t chunk_x, int16_t chunk_y, int16_t chunk_z); 60 | LIBRG_API int8_t librg_chunk_to_chunkpos(librg_world *world, librg_chunk id, int16_t *chunk_x, int16_t *chunk_y, int16_t *chunk_z); 61 | 62 | LIBRG_END_C_DECLS 63 | -------------------------------------------------------------------------------- /code/header/packing.h: -------------------------------------------------------------------------------- 1 | // file: header/packing.h 2 | 3 | #ifdef LIBRG_EDITOR 4 | #include 5 | #endif 6 | 7 | LIBRG_BEGIN_C_DECLS 8 | 9 | // =======================================================================// 10 | // ! 11 | // ! World data (de)packing methods 12 | // ! 13 | // =======================================================================// 14 | 15 | LIBRG_API int32_t librg_world_read(librg_world *world, int64_t owner_id, LIBRG_IN const char *buffer, size_t size, void *userdata); 16 | LIBRG_API int32_t librg_world_write(librg_world *world, int64_t owner_id, uint8_t chunk_radius, LIBRG_OUT char *buffer, LIBRG_INOUT size_t *size, void *userdata); 17 | 18 | LIBRG_END_C_DECLS 19 | -------------------------------------------------------------------------------- /code/header/query.h: -------------------------------------------------------------------------------- 1 | // file: header/query.h 2 | 3 | #ifdef LIBRG_EDITOR 4 | #include 5 | #endif 6 | 7 | LIBRG_BEGIN_C_DECLS 8 | 9 | // =======================================================================// 10 | // ! 11 | // ! World data/query methods 12 | // ! 13 | // =======================================================================// 14 | 15 | LIBRG_API int32_t librg_world_fetch_all(librg_world *world, LIBRG_OUT int64_t *entity_ids, LIBRG_INOUT size_t *entity_amount); 16 | LIBRG_API int32_t librg_world_fetch_chunk(librg_world *world, librg_chunk chunk, LIBRG_OUT int64_t *entity_ids, LIBRG_INOUT size_t *entity_amount); 17 | LIBRG_API int32_t librg_world_fetch_chunkarray(librg_world *world, const librg_chunk *chunks, size_t chunk_amount, LIBRG_OUT int64_t *entity_ids, LIBRG_INOUT size_t *entity_amount); 18 | LIBRG_API int32_t librg_world_fetch_owner(librg_world *world, int64_t owner_id, LIBRG_OUT int64_t *entity_ids, LIBRG_INOUT size_t *entity_amount); 19 | LIBRG_API int32_t librg_world_fetch_ownerarray(librg_world *world, const int64_t *owner_ids, size_t owner_amount, LIBRG_OUT int64_t *entity_ids, LIBRG_INOUT size_t *entity_amount); 20 | LIBRG_API int32_t librg_world_query(librg_world *world, int64_t owner_id, uint8_t chunk_radius, LIBRG_OUT int64_t *entity_ids, LIBRG_INOUT size_t *entity_amount); 21 | 22 | LIBRG_END_C_DECLS 23 | -------------------------------------------------------------------------------- /code/header/types.h: -------------------------------------------------------------------------------- 1 | // file: header/types.h 2 | 3 | #ifdef LIBRG_EDITOR 4 | #include 5 | #endif 6 | 7 | LIBRG_BEGIN_C_DECLS 8 | 9 | // =======================================================================// 10 | // ! 11 | // ! Main type definitions 12 | // ! 13 | // =======================================================================// 14 | 15 | typedef void librg_world; 16 | typedef void librg_event; 17 | typedef int64_t librg_chunk; 18 | 19 | #define LIBRG_OFFSET_BEG ((int16_t)0x8000) 20 | #define LIBRG_OFFSET_MID ((int16_t)0x0000) 21 | #define LIBRG_OFFSET_END ((int16_t)0x7fff) 22 | 23 | #define LIBRG_IN 24 | #define LIBRG_OUT 25 | #define LIBRG_INOUT 26 | 27 | typedef enum librg_event_type { 28 | LIBRG_WRITE_CREATE, 29 | LIBRG_WRITE_UPDATE, 30 | LIBRG_WRITE_REMOVE, 31 | 32 | LIBRG_READ_CREATE, 33 | LIBRG_READ_UPDATE, 34 | LIBRG_READ_REMOVE, 35 | 36 | LIBRG_ERROR_CREATE, 37 | LIBRG_ERROR_UPDATE, 38 | LIBRG_ERROR_REMOVE, 39 | } librg_event_type; 40 | 41 | typedef int32_t (*librg_event_fn)(librg_world *world, librg_event *event); 42 | 43 | typedef enum librg_visibility { 44 | LIBRG_VISIBLITY_DEFAULT, 45 | LIBRG_VISIBLITY_NEVER, 46 | LIBRG_VISIBLITY_ALWAYS, 47 | } librg_visibility; 48 | 49 | 50 | // =======================================================================// 51 | // ! 52 | // ! Errors, statuses, warnings and information message codes 53 | // ! 54 | // =======================================================================// 55 | 56 | #define LIBRG_OK (+0x0000) 57 | #define LIBRG_FAIL(code) (code < 0) 58 | #define LIBRG_TRUE (+0x0001) 59 | #define LIBRG_FALSE (+0x0000) 60 | 61 | #define LIBRG_WORLD_INVALID (-0x0001) 62 | #define LIBRG_OWNER_INVALID (-0x0002) 63 | #define LIBRG_CHUNK_INVALID (-0x0003) 64 | #define LIBRG_ENTITY_INVALID (-0x0004) 65 | #define LIBRG_ENTITY_FOREIGN (-0x0005) 66 | #define LIBRG_EVENT_INVALID (-0x0006) 67 | #define LIBRG_HANDLER_REPLACED (-0x0002) 68 | #define LIBRG_HANDLER_EMPTY (-0x0002) 69 | #define LIBRG_ENTITY_UNTRACKED (-0x0002) 70 | #define LIBRG_ENTITY_ALREADY_TRACKED (-0x0002) 71 | #define LIBRG_ENTITY_VISIBILITY_IGNORED (-0x0003) 72 | #define LIBRG_WRITE_REJECT (-0x0001) 73 | #define LIBRG_READ_INVALID (-0x0003) 74 | #define LIBRG_NULL_REFERENCE (-0x0007) 75 | 76 | LIBRG_END_C_DECLS 77 | -------------------------------------------------------------------------------- /code/librg.h: -------------------------------------------------------------------------------- 1 | /** 2 | * librg - a library for building simple and elegant cross-platform multiplayer client-server solutions. 3 | * 4 | * Usage: 5 | * #define LIBRG_IMPLEMENTATION exactly in ONE source file right BEFORE including the library, like: 6 | * 7 | * #define LIBRG_IMPLEMENTATION 8 | * #include 9 | * 10 | * Credits: 11 | * - Vladyslav Hrytsenko (GitHub: inlife) 12 | * - Dominik Madarasz (GitHub: zaklaus) 13 | * 14 | * Dependencies: 15 | * - librg.h -> zpl.h (built-in) 16 | * - librg_enet.h -> enet.h (built-in) 17 | * 18 | * =================================== 19 | * Version History: 20 | * =================================== 21 | * 22 | * 7.0.0 23 | * - Added argument for the query & write radius 24 | * - Deprecated radius within the entity structure 25 | * - Fix for the dimensional offset calculations 26 | * - Fix for the chunk position calculations 27 | * - Fix for the radius overflows within view range 28 | * - Updated zpl & enet dependencies 29 | * 30 | * 6.0.0 31 | * - Major library rewrite 32 | * 33 | * 5.0.6 - Fix forced_inline on librg__space_insert 34 | * 5.0.5 - Fixes to selection and deduplication flow 35 | * 5.0.3 - Minor fixes by @markatk 36 | * 5.0.2 - Fixed issue related to visibility destruction 37 | * 5.0.1 - Fixed entity visibility states after disconnection 38 | * 39 | * 5.0.0 40 | * - Changed API for visibility feature: 41 | * - Instead of booleans setting whether or not entity would be included in the query or not 42 | * a multiple constant-based states were inroduced: 43 | * - LIBRG_VISIBILITY_DEFAULT - the original state of visibility, entity is only visible if it is in the stream range 44 | * - LIBRG_ALWAYS_VISIBLE - the entity is visible disregarding if it is the stream range or not 45 | * - LIBRG_ALWAYS_INVISIBLE - opposite of the above, entity will be always invisible 46 | * Entity visibility can be set on per entity-to-entity or global levels. The entity-to-entity relation has a bigger 47 | * priority, so f.e. setting entity to be LIBRG_ALWAYS_VISIBLE on relation level, will override global visibility. 48 | * - Aditionally, if the virtual world feature is enabled, it gains main priotity over visibility, thus entities located in 2 different 49 | * virtual worlds will not be able to see each other in spite of visibility settings. 50 | * 51 | * 4.1.5 52 | * - Fix to data send 53 | * 54 | * 4.1.4 55 | * - Fixed issue with async flow inconsitensies of control_generations 56 | * - Fixed boolean validation in disconnection flow 57 | * - Added proxying of user_data from msg to evt in disconnect event 58 | * 59 | * 4.1.1 60 | * - Added compile-time 'features': 61 | * - Ability to enable/disable some librg compile-time features 62 | * - Entity igore tables are now optional, and can be disabled 63 | * - Implmented simple optional Virtual world feature for entities 64 | * - Implemented a feature to enable/disable octree culler (falls back to linear check) 65 | * - Multiple features can be combined 66 | * - Added 'generation' to entity control lists: 67 | * Setting, removing and setting control to the same entity again with same owner 68 | * will now distinct between old and new controllers, and messages still coming 69 | * from old control generation will be rejected in favor of new ones. 70 | * - Added guard to minimum sized packet in receive for both sides 71 | * - Added spherical culler handler, and ability to do runtime switch (LIBRG_USE_RADIUS_CULLING) 72 | * - Added return codes to some functions @markatk 73 | * - Streamed entities are now going to be always returned in query for controlling peer 74 | * - Fixed issue with host setting on the server side 75 | * - Fixed nullptr crash on empty host string for client on connect 76 | * - Removed experimental multithreading code 77 | * 78 | * 4.1.0 - Added new, extended message methods and sending options 79 | * 4.0.0 - Coding style changes and bug fixes 80 | * 81 | * 3.3.1 82 | * - Updated zpl dependencies 83 | * - Removed zpl_math dependency (replaced by internal module in zpl) 84 | * 85 | * 3.3.0 86 | * - Added ipv6 support 87 | * - Added safe bitstream reads for internal methods 88 | * - Updated enet to latest version (2.0.1, ipv6 support) 89 | * - Updated zpl to latest version 90 | * 91 | * 3.2.0 92 | * - Fixed minor memory client-side memory leak with empty control list 93 | * - Fixed issue with client stream update and removed entity on server 94 | * - Updated zpl to new major version, watch out for possible incompatibilities 95 | * - Added method for alloc/dealloc the librg_ctx, librg_data, librg_event for the bindings 96 | * - Added experimental support for update buffering, disabled by default, and not recommended to use 97 | * - Added built-in timesyncer, working on top of monotonic time, syncing client clock to server one 98 | * - Added helper methods: librg_time_now, librg_standard_deviation 99 | * - Changed ctx->tick_delay from u16 to f64 (slightly more precision) 100 | * 101 | * 3.1.0 102 | * - Removed zpl_cull and zpl_event dependencies 103 | * - added librg_network_kick() 104 | * - saving current librg_address to ctx->network 105 | * - refactor to proper disconnection code 106 | * - exclude local client entity from LIBRG_CONNECTION_DISCONNECT 107 | * - moved options and some few other things to the implementation part 108 | * - fixed issue with replacing entity control 109 | * - fixed issue with add control queuing beign sent before create entity packet 110 | * 111 | * 3.0.7 - Fix for entity query dublication for player entities 112 | * 3.0.5 - Patched librg_callback_cb arg value 113 | * 3.0.4 - Fixed Android and iOS support 114 | * 3.0.3 - Small fixes 115 | * 3.0.2 - Dependency updates 116 | * 3.0.1 - minor api patch 117 | * 3.0.0 - contexts, major api changes, fried potato, other stuff 118 | * 119 | * 2.2.3 - fixed mem leak on net event 120 | * 2.2.2 - Fixed client issue with librg_message_send_instream_except 121 | * 2.2.1 - Fixed cpp issues with librg_data pointers 122 | * 2.2.0 - Inner message system rafactor 123 | * 2.1.0 - Inner bitstream refactors, with slight interface changes 124 | * 2.0.2 - C++ and MSVC related fixes 125 | * 2.0.0 - Initial C version rewrite 126 | * 127 | * ============================ 128 | * License notice 129 | * ============================ 130 | * Copyright (c) 2017-2021 Vladyslav Hrytsenko. All rights reserved. 131 | * 132 | * Redistribution and use in source and binary forms, with or without 133 | * modification, are permitted provided that the following conditions are met: 134 | * 135 | * 1. Redistributions of source code must retain the above copyright notice, this 136 | * list of conditions and the following disclaimer. 137 | * 2. Redistributions in binary form must reproduce the above copyright notice, 138 | * this list of conditions and the following disclaimer in the documentation 139 | * and/or other materials provided with the distribution. 140 | * 3. Neither the name of the copyright holder nor the names of its contributors 141 | * may be used to endorse or promote products derived from this software without 142 | * specific prior written permission. 143 | * 144 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 145 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 146 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 147 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 148 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 149 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 150 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 151 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 152 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 153 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 154 | */ 155 | #ifndef LIBRG_H 156 | #define LIBRG_H 157 | 158 | #define LIBRG_VERSION_MAJOR 7 159 | #define LIBRG_VERSION_MINOR 4 160 | #define LIBRG_VERSION_PATCH 0 161 | #define LIBRG_VERSION_PRE "" 162 | 163 | #include "librg_hedley.h" 164 | 165 | #define LIBRG_VERSION LIBRG_VERSION_ENCODE(LIBRG_VERSION_MAJOR, LIBRG_VERSION_MINOR, LIBRG_VERSION_PATCH) 166 | 167 | #ifdef LIBRG_IMPL 168 | #ifndef LIBRG_IMPLEMENTATION 169 | #define LIBRG_IMPLEMENTATION 170 | #endif 171 | #endif 172 | 173 | #if defined(__cplusplus) && !defined(LIBRG_EXTERN) 174 | #define LIBRG_EXTERN extern "C" 175 | #else 176 | #define LIBRG_EXTERN extern 177 | #endif 178 | 179 | #ifndef LIBRG_API 180 | #if defined(LIBRG_SHARED_LIB) 181 | #ifdef LIBRG_IMPLEMENTATION 182 | #define LIBRG_API LIBRG_PUBLIC 183 | #else 184 | #define LIBRG_API LIBRG_IMPORT 185 | #endif 186 | #elif defined(LIBRG_STATIC_LIB) 187 | #ifdef LIBRG_IMPLEMENTATION 188 | #define LIBRG_API 189 | #else 190 | #define LIBRG_API LIBRG_EXTERN 191 | #endif 192 | #elif defined(LIBRG_STATIC) 193 | #define LIBRG_API static 194 | #elif defined(LIBRG_EMSCRIPTEN) 195 | #define LIBRG_API EMSCRIPTEN_KEEPALIVE 196 | #else 197 | #define LIBRG_API LIBRG_EXTERN 198 | #endif 199 | #endif 200 | 201 | #include 202 | #include 203 | 204 | #if defined(__EMSCRIPTEN__) 205 | #include 206 | #endif 207 | 208 | #include "header/types.h" 209 | #include "header/general.h" 210 | #include "header/entity.h" 211 | #include "header/query.h" 212 | #include "header/packing.h" 213 | 214 | /* Implementation part */ 215 | #if defined(LIBRG_IMPLEMENTATION) && !defined(LIBRG_IMPLEMENTATION_DONE) 216 | #define LIBRG_IMPLEMENTATION_DONE 217 | 218 | #ifndef LIBRG_CUSTOM_ZPL 219 | #define ZPL_NANO 220 | #define ZPL_ENABLE_MATH 221 | #define ZPL_IMPL 222 | 223 | #include "vendor/zpl.h" 224 | #endif 225 | 226 | #include "source/types.c" 227 | #include "source/general.c" 228 | #include "source/entity.c" 229 | #include "source/query.c" 230 | #include "source/packing.c" 231 | 232 | #endif // LIBRG_IMPLEMENTATION 233 | 234 | #endif // LIBRG_H 235 | -------------------------------------------------------------------------------- /code/librg_enet.h: -------------------------------------------------------------------------------- 1 | /* not yet sure when the work on this will start */ 2 | 3 | /* net module () */ 4 | 5 | // LIBRG_HANDSHAKE_INIT, 6 | // LIBRG_HANDSHAKE_REQUEST, 7 | // LIBRG_HANDSHAKE_ACCEPT, 8 | // LIBRG_HANDSHAKE_REFUSE, 9 | 10 | // LIBRG_CONNECTION_CONNECT, 11 | // LIBRG_CONNECTION_DISCONNECT, 12 | 13 | // LIBRG_API int librg_config_tickdelay_set(librg_ctx *, float time_ms); 14 | // LIBRG_API float librg_config_tickdelay_get(librg_ctx *); 15 | 16 | // LIBRG_API int librg_server_start(librg_ctx *, const char *hostname, uint16_t port); 17 | // LIBRG_API int librg_server_stop(librg_ctx *); 18 | // LIBRG_API int librg_server_tick(librg_ctx *); 19 | // LIBRG_API int librg_server_status(librg_ctx *); 20 | // LIBRG_API int librg_server_disconnect(librg_ctx *, librg_client); 21 | // LIBRG_API int librg_server_userdata_set(librg_ctx *, librg_client, void *); 22 | // LIBRG_API void * librg_server_userdata_get(librg_ctx *, librg_client); 23 | // LIBRG_API int librg_server_send_single(librg_ctx *, librg_client, void *data, size_t data_size); 24 | // LIBRG_API int librg_server_send_multiple(librg_ctx *, librg_client *, size_t client_amount, void *data, size_t data_size); 25 | // LIBRG_API int librg_server_send_all(librg_ctx *, void *data, size_t data_size); 26 | // LIBRG_API int librg_server_send_allexcept(librg_ctx *, librg_client *, size_t client_amount, void *data, size_t data_size); 27 | // LIBRG_API int librg_server_send_chunks(librg_ctx *, librg_chunk *, size_t chunk_amount, void *data, size_t data_size); 28 | // LIBRG_API int librg_server_send_chunksexcept(librg_ctx *, librg_chunk *, size_t chunk_amount, librg_client *, size_t client_amount, void *data, size_t data_size); 29 | 30 | // LIBRG_API int librg_client_connect(librg_ctx *, const char *hostname, uint16_t port); 31 | // LIBRG_API int librg_client_disconnect(librg_ctx *); 32 | // LIBRG_API int librg_client_tick(librg_ctx *); 33 | // LIBRG_API int librg_client_status(librg_ctx *); 34 | // LIBRG_API int librg_client_send(librg_ctx *, void *data, size_t data_size); 35 | -------------------------------------------------------------------------------- /code/source/entity.c: -------------------------------------------------------------------------------- 1 | // file: source/entity.c 2 | 3 | #ifdef LIBRG_EDITOR 4 | #include 5 | #include 6 | #endif 7 | 8 | LIBRG_BEGIN_C_DECLS 9 | 10 | // =======================================================================// 11 | // ! 12 | // ! Basic entity manipulation 13 | // ! 14 | // =======================================================================// 15 | 16 | int8_t librg_entity_track(librg_world *world, int64_t entity_id) { 17 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 18 | librg_world_t *wld = (librg_world_t *)world; 19 | 20 | if (librg_entity_tracked(world, entity_id) == LIBRG_TRUE) { 21 | return LIBRG_ENTITY_ALREADY_TRACKED; 22 | } 23 | 24 | if (entity_id < 0 || entity_id > ZPL_I64_MAX) { 25 | return LIBRG_ENTITY_INVALID; 26 | } 27 | 28 | librg_entity_t _entity = {0}; 29 | librg_table_ent_set(&wld->entity_map, entity_id, _entity); 30 | 31 | /* set defaults */ 32 | librg_entity_chunk_set(world, entity_id, LIBRG_CHUNK_INVALID); 33 | librg_entity_owner_set(world, entity_id, LIBRG_OWNER_INVALID); 34 | 35 | return LIBRG_OK; 36 | } 37 | 38 | int8_t librg_entity_untrack(librg_world *world, int64_t entity_id) { 39 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 40 | librg_world_t *wld = (librg_world_t *)world; 41 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 42 | 43 | if (!entity) { 44 | return LIBRG_ENTITY_UNTRACKED; 45 | } 46 | 47 | if (entity->flag_foreign == LIBRG_TRUE) { 48 | return LIBRG_ENTITY_FOREIGN; 49 | } 50 | 51 | /* cleanup owner snapshots */ 52 | if (entity->owner_id != LIBRG_OWNER_INVALID) { 53 | size_t owned = 0; 54 | size_t total = zpl_array_count(wld->entity_map.entries); 55 | 56 | /* count already owned entities by this user */ 57 | for (size_t i=0; ientity_map.entries[i].key) == entity->owner_id) 59 | owned++; 60 | } 61 | 62 | librg_table_i64 *snapshot = librg_table_tbl_get(&wld->owner_map, entity->owner_id); 63 | 64 | /* free up our snapshot storage, if owner does not own other entities (except current one) */ 65 | if (snapshot && owned <= 1) { 66 | librg_table_i64_destroy(snapshot); 67 | librg_table_tbl_remove(&wld->owner_map, entity->owner_id); 68 | } 69 | 70 | /* cleanup owner-entity pair */ 71 | for (int i = 0; i < zpl_array_count(wld->owner_entity_pairs); ++i) { 72 | if (wld->owner_entity_pairs[i].entity_id == entity_id) { 73 | zpl_array_remove_at(wld->owner_entity_pairs, i); 74 | break; 75 | } 76 | } 77 | } 78 | 79 | /* cleanup owner visibility */ 80 | if (entity->flag_visbility_owner_enabled) { 81 | entity->flag_visbility_owner_enabled = LIBRG_FALSE; 82 | librg_table_i8_destroy(&entity->owner_visibility_map); 83 | } 84 | 85 | librg_table_ent_remove(&wld->entity_map, entity_id); 86 | return LIBRG_OK; 87 | } 88 | 89 | int8_t librg_entity_tracked(librg_world *world, int64_t entity_id) { 90 | LIBRG_ASSERT(world); if (!world) return LIBRG_FALSE; 91 | librg_world_t *wld = (librg_world_t *)world; 92 | 93 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 94 | return entity == NULL ? LIBRG_FALSE : LIBRG_TRUE; 95 | } 96 | 97 | int8_t librg_entity_foreign(librg_world *world, int64_t entity_id) { 98 | LIBRG_ASSERT(world); if (!world) return LIBRG_FALSE; 99 | librg_world_t *wld = (librg_world_t *)world; 100 | 101 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 102 | if (entity == NULL) return LIBRG_FALSE; 103 | 104 | return entity->flag_foreign == LIBRG_TRUE; 105 | } 106 | 107 | int8_t librg_entity_owned(librg_world *world, int64_t entity_id) { 108 | LIBRG_ASSERT(world); if (!world) return LIBRG_FALSE; 109 | librg_world_t *wld = (librg_world_t *)world; 110 | 111 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 112 | if (entity == NULL) return LIBRG_FALSE; 113 | 114 | return entity->owner_id != LIBRG_OWNER_INVALID; 115 | } 116 | 117 | int32_t librg_entity_count(librg_world *world) { 118 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 119 | librg_world_t *wld = (librg_world_t *)world; 120 | 121 | return (int32_t)zpl_array_count(wld->entity_map.entries); 122 | } 123 | 124 | // =======================================================================// 125 | // ! 126 | // ! Main entity data methods 127 | // ! 128 | // =======================================================================// 129 | 130 | int8_t librg_entity_chunk_set(librg_world *world, int64_t entity_id, librg_chunk chunk) { 131 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 132 | librg_world_t *wld = (librg_world_t *)world; 133 | 134 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 135 | if (entity == NULL) return LIBRG_ENTITY_UNTRACKED; 136 | 137 | for (int i = 0; i < LIBRG_ENTITY_MAXCHUNKS; ++i) entity->chunks[i] = LIBRG_CHUNK_INVALID; 138 | entity->chunks[0] = chunk; 139 | 140 | return LIBRG_OK; 141 | } 142 | 143 | librg_chunk librg_entity_chunk_get(librg_world *world, int64_t entity_id) { 144 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 145 | librg_world_t *wld = (librg_world_t *)world; 146 | 147 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 148 | if (entity == NULL) return LIBRG_ENTITY_UNTRACKED; 149 | 150 | return entity->chunks[0]; 151 | } 152 | 153 | int8_t librg_entity_owner_set(librg_world *world, int64_t entity_id, int64_t owner_id) { 154 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 155 | librg_world_t *wld = (librg_world_t *)world; 156 | 157 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 158 | if (entity == NULL) return LIBRG_ENTITY_UNTRACKED; 159 | 160 | if (entity->flag_foreign == LIBRG_TRUE) { 161 | return LIBRG_ENTITY_FOREIGN; 162 | } 163 | 164 | /* update owner-entity pairing */ 165 | if (owner_id != LIBRG_OWNER_INVALID) { 166 | bool ownership_pair_found = false; 167 | for (int i = 0; i < zpl_array_count(wld->owner_entity_pairs) && !ownership_pair_found; ++i) { 168 | if (wld->owner_entity_pairs[i].entity_id == entity_id) { 169 | ownership_pair_found = true; 170 | 171 | /* update owner if we found the entity */ 172 | if (wld->owner_entity_pairs[i].owner_id != owner_id) { 173 | wld->owner_entity_pairs[i].owner_id = owner_id; 174 | } 175 | } 176 | } 177 | if (!ownership_pair_found) { 178 | librg_owner_entity_pair_t pair = { owner_id, entity_id }; 179 | zpl_array_append(wld->owner_entity_pairs, pair); 180 | } 181 | } else { 182 | if (entity->owner_id != LIBRG_OWNER_INVALID) { 183 | /* cleanup owner-entity pair */ 184 | for (int i = 0; i < zpl_array_count(wld->owner_entity_pairs); ++i) { 185 | if (wld->owner_entity_pairs[i].entity_id == entity_id) { 186 | zpl_array_remove_at(wld->owner_entity_pairs, i); 187 | break; 188 | } 189 | } 190 | } 191 | } 192 | 193 | entity->owner_id = owner_id; 194 | entity->flag_owner_updated = LIBRG_TRUE; 195 | 196 | if (entity->owner_id != LIBRG_OWNER_INVALID) { 197 | /* set new token, and make sure to prevent collisions */ 198 | uint16_t newtoken = 0; 199 | do { newtoken = (uint16_t)(zpl_random_gen_u32(&wld->random) % ZPL_U16_MAX); } 200 | while (newtoken == 0 || newtoken == entity->ownership_token); 201 | entity->ownership_token = newtoken; 202 | 203 | /* fetch or create a new subtable */ 204 | librg_table_i64 *snapshot = librg_table_tbl_get(&wld->owner_map, owner_id); 205 | 206 | if (!snapshot) { 207 | librg_table_i64 _i64 = {0}; 208 | librg_table_tbl_set(&wld->owner_map, owner_id, _i64); 209 | snapshot = librg_table_tbl_get(&wld->owner_map, owner_id); 210 | librg_table_i64_init(snapshot, wld->allocator); 211 | } 212 | } else { 213 | entity->ownership_token = 0; 214 | } 215 | 216 | return LIBRG_OK; 217 | } 218 | 219 | int64_t librg_entity_owner_get(librg_world *world, int64_t entity_id) { 220 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 221 | librg_world_t *wld = (librg_world_t *)world; 222 | 223 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 224 | if (entity == NULL) return LIBRG_ENTITY_UNTRACKED; 225 | 226 | return entity->owner_id; 227 | } 228 | 229 | LIBRG_DEPRECATED(7.0) int8_t librg_entity_radius_set(librg_world *world, int64_t entity_id, int8_t observed_chunk_radius) { 230 | zpl_unused(world); 231 | zpl_unused(entity_id); 232 | zpl_unused(observed_chunk_radius); 233 | return -1; 234 | } 235 | 236 | LIBRG_DEPRECATED(7.0) int8_t librg_entity_radius_get(librg_world *world, int64_t entity_id) { 237 | zpl_unused(world); 238 | zpl_unused(entity_id); 239 | return -1; 240 | } 241 | 242 | int8_t librg_entity_dimension_set(librg_world *world, int64_t entity_id, int32_t dimension) { 243 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 244 | librg_world_t *wld = (librg_world_t *)world; 245 | 246 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 247 | if (entity == NULL) return LIBRG_ENTITY_UNTRACKED; 248 | 249 | entity->dimension = dimension; 250 | return LIBRG_OK; 251 | } 252 | 253 | int32_t librg_entity_dimension_get(librg_world *world, int64_t entity_id) { 254 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 255 | librg_world_t *wld = (librg_world_t *)world; 256 | 257 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 258 | if (entity == NULL) return LIBRG_ENTITY_UNTRACKED; 259 | 260 | return entity->dimension; 261 | } 262 | 263 | int8_t librg_entity_userdata_set(librg_world *world, int64_t entity_id, void *data) { 264 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 265 | librg_world_t *wld = (librg_world_t *)world; 266 | 267 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 268 | if (entity == NULL) return LIBRG_ENTITY_UNTRACKED; 269 | 270 | entity->userdata = data; 271 | return LIBRG_OK; 272 | } 273 | 274 | void *librg_entity_userdata_get(librg_world *world, int64_t entity_id) { 275 | LIBRG_ASSERT(world); if (!world) return NULL; 276 | librg_world_t *wld = (librg_world_t *)world; 277 | 278 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 279 | if (entity == NULL) return NULL; 280 | 281 | return entity->userdata; 282 | } 283 | 284 | int8_t librg_entity_chunkarray_set(librg_world *world, int64_t entity_id, const librg_chunk *values, size_t chunk_amount) { 285 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 286 | librg_world_t *wld = (librg_world_t *)world; 287 | 288 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 289 | if (entity == NULL) return LIBRG_ENTITY_UNTRACKED; 290 | 291 | LIBRG_ASSERT(chunk_amount > 0 && chunk_amount < LIBRG_ENTITY_MAXCHUNKS); 292 | 293 | for (int i = 0; i < LIBRG_ENTITY_MAXCHUNKS; ++i) entity->chunks[i] = LIBRG_CHUNK_INVALID; 294 | zpl_memcopy(entity->chunks, values, sizeof(librg_chunk) * LIBRG_MIN(chunk_amount, LIBRG_ENTITY_MAXCHUNKS)); 295 | 296 | return LIBRG_OK; 297 | 298 | } 299 | 300 | int8_t librg_entity_chunkarray_get(librg_world *world, int64_t entity_id, librg_chunk *results, size_t *chunk_amount) { 301 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 302 | librg_world_t *wld = (librg_world_t *)world; 303 | 304 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 305 | if (entity == NULL) return LIBRG_ENTITY_UNTRACKED; 306 | 307 | LIBRG_ASSERT(results); 308 | size_t count = 0; 309 | size_t buffer_limit = *chunk_amount; 310 | 311 | for (size_t i = 0; i < LIBRG_MIN(buffer_limit, LIBRG_ENTITY_MAXCHUNKS); ++i) { 312 | if (entity->chunks[i] != LIBRG_CHUNK_INVALID) { 313 | results[count++] = entity->chunks[i]; 314 | } 315 | } 316 | 317 | *chunk_amount = count; 318 | return (int8_t)(LIBRG_ENTITY_MAXCHUNKS - buffer_limit); 319 | } 320 | 321 | 322 | int8_t librg_entity_visibility_global_set(librg_world *world, int64_t entity_id, librg_visibility value) { 323 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 324 | librg_world_t *wld = (librg_world_t *)world; 325 | 326 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 327 | if (entity == NULL) return LIBRG_ENTITY_UNTRACKED; 328 | 329 | entity->visibility_global = value; 330 | 331 | return LIBRG_OK; 332 | } 333 | 334 | int8_t librg_entity_visibility_global_get(librg_world *world, int64_t entity_id) { 335 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 336 | librg_world_t *wld = (librg_world_t *)world; 337 | 338 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 339 | if (entity == NULL) return LIBRG_ENTITY_UNTRACKED; 340 | 341 | return entity->visibility_global; 342 | } 343 | 344 | int8_t librg_entity_visibility_owner_set(librg_world *world, int64_t entity_id, int64_t owner_id, librg_visibility value) { 345 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 346 | librg_world_t *wld = (librg_world_t *)world; 347 | 348 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 349 | if (entity == NULL) return LIBRG_ENTITY_UNTRACKED; 350 | 351 | if (!entity->flag_visbility_owner_enabled) { 352 | entity->flag_visbility_owner_enabled = LIBRG_TRUE; 353 | librg_table_i8_init(&entity->owner_visibility_map, wld->allocator); 354 | } 355 | 356 | librg_table_i8_set(&entity->owner_visibility_map, owner_id, value); 357 | 358 | return LIBRG_OK; 359 | } 360 | 361 | int8_t librg_entity_visibility_owner_get(librg_world *world, int64_t entity_id, int64_t owner_id) { 362 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 363 | librg_world_t *wld = (librg_world_t *)world; 364 | 365 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 366 | if (entity == NULL) return LIBRG_ENTITY_UNTRACKED; 367 | 368 | if (!entity->flag_visbility_owner_enabled) 369 | return LIBRG_VISIBLITY_DEFAULT; 370 | 371 | int8_t *value = librg_table_i8_get(&entity->owner_visibility_map, owner_id); 372 | return (value ? *value : LIBRG_VISIBLITY_DEFAULT); 373 | } 374 | 375 | LIBRG_END_C_DECLS 376 | -------------------------------------------------------------------------------- /code/source/general.c: -------------------------------------------------------------------------------- 1 | // file: source/context.c 2 | 3 | #ifdef LIBRG_EDITOR 4 | #include 5 | #include 6 | #endif 7 | 8 | LIBRG_BEGIN_C_DECLS 9 | 10 | // =======================================================================// 11 | // ! 12 | // ! Custom internal zpl alloc proc hanlders 13 | // ! 14 | // =======================================================================// 15 | 16 | LIBRG_PRIVATE zpl_allocator librg_alloc_wrap(); 17 | 18 | ZPL_ALLOCATOR_PROC(librg_allocator_proc) { 19 | void *ptr = NULL; 20 | 21 | zpl_unused(allocator_data); 22 | zpl_unused(old_size); 23 | zpl_unused(flags); 24 | 25 | switch (type) { 26 | case ZPL_ALLOCATION_ALLOC: { ptr = LIBRG_MEM_ALLOC(size); } break; 27 | case ZPL_ALLOCATION_FREE: { LIBRG_MEM_FREE(old_memory); } break; 28 | case ZPL_ALLOCATION_RESIZE: { ptr = zpl_default_resize_align(librg_alloc_wrap(), old_memory, old_size, size, alignment); } break; 29 | case ZPL_ALLOCATION_FREE_ALL: break; 30 | } 31 | 32 | return ptr; 33 | } 34 | 35 | LIBRG_PRIVATE zpl_allocator librg_alloc_wrap() { 36 | zpl_allocator a = {0}; 37 | a.proc = librg_allocator_proc; 38 | a.data = NULL; 39 | return a; 40 | } 41 | 42 | // =======================================================================// 43 | // ! 44 | // ! Context methods 45 | // ! 46 | // =======================================================================// 47 | 48 | uint32_t librg_version() { 49 | return LIBRG_VERSION; 50 | } 51 | 52 | librg_world *librg_world_create() { 53 | librg_world_t *wld = (librg_world_t *)LIBRG_MEM_ALLOC(sizeof(librg_world_t)); 54 | zpl_memset(wld, 0, sizeof(librg_world_t)); 55 | 56 | /* setup initials */ 57 | wld->valid = LIBRG_TRUE; 58 | wld->allocator = librg_alloc_wrap(); 59 | 60 | /* setup defaults */ 61 | librg_config_chunksize_set((librg_world *)wld, 16, 16, 16); 62 | librg_config_chunkamount_set((librg_world *)wld, 256, 256, 256); 63 | librg_config_chunkoffset_set((librg_world *)wld, LIBRG_OFFSET_MID, LIBRG_OFFSET_MID, LIBRG_OFFSET_MID); 64 | 65 | /* initialize internal structs */ 66 | librg_table_ent_init(&wld->entity_map, wld->allocator); 67 | librg_table_tbl_init(&wld->owner_map, wld->allocator); 68 | zpl_random_init(&wld->random); 69 | zpl_array_init(wld->owner_entity_pairs, wld->allocator); 70 | 71 | librg_table_tbl_init(&wld->dimensions, wld->allocator); 72 | 73 | return (librg_world *)wld; 74 | } 75 | 76 | int8_t librg_world_destroy(librg_world *world) { 77 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 78 | librg_world_t *wld = (librg_world_t *)world; 79 | 80 | {/* free up entities */ 81 | for (int i = 0; i < zpl_array_count(wld->entity_map.entries); ++i) { 82 | librg_entity_t *entity = &wld->entity_map.entries[i].value; 83 | 84 | if (entity->flag_visbility_owner_enabled) { 85 | entity->flag_visbility_owner_enabled = LIBRG_FALSE; 86 | librg_table_i8_destroy(&entity->owner_visibility_map); 87 | } 88 | } 89 | 90 | librg_table_ent_destroy(&wld->entity_map); 91 | } 92 | 93 | {/* free up owners */ 94 | for (int i = 0; i < zpl_array_count(wld->owner_map.entries); ++i) 95 | librg_table_i64_destroy(&wld->owner_map.entries[i].value); 96 | 97 | librg_table_tbl_destroy(&wld->owner_map); 98 | } 99 | 100 | zpl_array_free(wld->owner_entity_pairs); 101 | librg_table_tbl_destroy(&wld->dimensions); 102 | 103 | /* mark it invalid */ 104 | wld->valid = LIBRG_FALSE; 105 | 106 | LIBRG_MEM_FREE(world); 107 | return LIBRG_OK; 108 | } 109 | 110 | int8_t librg_world_valid(librg_world *world) { 111 | if (!world) return LIBRG_FALSE; 112 | librg_world_t *wld = (librg_world_t *)world; 113 | return wld->valid; 114 | } 115 | 116 | int8_t librg_world_userdata_set(librg_world *world, void *data) { 117 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 118 | librg_world_t *wld = (librg_world_t *)world; 119 | wld->userdata = data; 120 | return LIBRG_OK; 121 | } 122 | 123 | void *librg_world_userdata_get(librg_world *world) { 124 | LIBRG_ASSERT(world); if (!world) return NULL; 125 | librg_world_t *wld = (librg_world_t *)world; 126 | return wld->userdata; 127 | } 128 | 129 | int64_t librg_world_entities_tracked(librg_world *world) { 130 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 131 | librg_world_t *wld = (librg_world_t *)world; 132 | return zpl_array_count(wld->entity_map.entries); 133 | } 134 | 135 | // =======================================================================// 136 | // ! 137 | // ! Runtime configuration 138 | // ! 139 | // =======================================================================// 140 | 141 | int8_t librg_config_chunkamount_set(librg_world *world, uint16_t x, uint16_t y, uint16_t z) { 142 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 143 | librg_world_t *wld = (librg_world_t *)world; 144 | wld->worldsize.x = x == 0 ? 1 : x; 145 | wld->worldsize.y = y == 0 ? 1 : y; 146 | wld->worldsize.z = z == 0 ? 1 : z; 147 | return LIBRG_OK; 148 | } 149 | 150 | int8_t librg_config_chunkamount_get(librg_world *world, uint16_t *x, uint16_t *y, uint16_t *z) { 151 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 152 | librg_world_t *wld = (librg_world_t *)world; 153 | if (x) *x = wld->worldsize.x; 154 | if (y) *y = wld->worldsize.y; 155 | if (z) *z = wld->worldsize.z; 156 | return LIBRG_OK; 157 | } 158 | 159 | int8_t librg_config_chunksize_set(librg_world *world, uint16_t x, uint16_t y, uint16_t z) { 160 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 161 | librg_world_t *wld = (librg_world_t *)world; 162 | wld->chunksize.x = x == 0 ? 1 : x; 163 | wld->chunksize.y = y == 0 ? 1 : y; 164 | wld->chunksize.z = z == 0 ? 1 : z; 165 | return LIBRG_OK; 166 | } 167 | 168 | int8_t librg_config_chunksize_get(librg_world *world, uint16_t *x, uint16_t *y, uint16_t *z) { 169 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 170 | librg_world_t *wld = (librg_world_t *)world; 171 | if (x) *x = wld->chunksize.x; 172 | if (y) *y = wld->chunksize.y; 173 | if (z) *z = wld->chunksize.z; 174 | return LIBRG_OK; 175 | } 176 | 177 | int8_t librg_config_chunkoffset_set(librg_world *world, int16_t x, int16_t y, int16_t z) { 178 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 179 | librg_world_t *wld = (librg_world_t *)world; 180 | wld->chunkoffset.x = x; 181 | wld->chunkoffset.y = y; 182 | wld->chunkoffset.z = z; 183 | return LIBRG_OK; 184 | } 185 | 186 | int8_t librg_config_chunkoffset_get(librg_world *world, int16_t *x, int16_t *y, int16_t *z) { 187 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 188 | librg_world_t *wld = (librg_world_t *)world; 189 | if (x) *x = wld->chunkoffset.x; 190 | if (y) *y = wld->chunkoffset.y; 191 | if (z) *z = wld->chunkoffset.z; 192 | return LIBRG_OK; 193 | } 194 | 195 | // =======================================================================// 196 | // ! 197 | // ! Events 198 | // ! 199 | // =======================================================================// 200 | 201 | int8_t librg_event_set(librg_world *world, librg_event_type id, librg_event_fn handler) { 202 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 203 | librg_world_t *wld = (librg_world_t *)world; 204 | 205 | if (wld->handlers[id]) { 206 | wld->handlers[id] = handler; 207 | return LIBRG_HANDLER_REPLACED; 208 | } 209 | 210 | wld->handlers[id] = handler; 211 | return LIBRG_OK; 212 | } 213 | 214 | int8_t librg_event_remove(librg_world *world, librg_event_type id) { 215 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 216 | librg_world_t *wld = (librg_world_t *)world; 217 | 218 | if (!wld->handlers[id]) { 219 | return LIBRG_HANDLER_EMPTY; 220 | } 221 | 222 | wld->handlers[id] = NULL; 223 | return LIBRG_OK; 224 | } 225 | 226 | int8_t librg_event_type_get(librg_world *world, librg_event *event) { 227 | LIBRG_ASSERT(event); if (!event) return LIBRG_EVENT_INVALID; 228 | zpl_unused(world); 229 | librg_event_t *e = (librg_event_t*)event; 230 | return (int8_t)e->type; 231 | } 232 | 233 | int64_t librg_event_owner_get(librg_world *world, librg_event *event) { 234 | LIBRG_ASSERT(event); if (!event) return LIBRG_EVENT_INVALID; 235 | zpl_unused(world); 236 | librg_event_t *e = (librg_event_t*)event; 237 | return e->owner_id; 238 | } 239 | 240 | int64_t librg_event_entity_get(librg_world *world, librg_event *event) { 241 | LIBRG_ASSERT(event); if (!event) return LIBRG_EVENT_INVALID; 242 | zpl_unused(world); 243 | librg_event_t *e = (librg_event_t*)event; 244 | return e->entity_id; 245 | } 246 | 247 | char * librg_event_buffer_get(librg_world *world, librg_event *event) { 248 | LIBRG_ASSERT(event); if (!event) return NULL; 249 | zpl_unused(world); 250 | librg_event_t *e = (librg_event_t*)event; 251 | return e->buffer; 252 | } 253 | 254 | int32_t librg_event_size_get(librg_world *world, librg_event *event) { 255 | LIBRG_ASSERT(event); if (!event) return LIBRG_EVENT_INVALID; 256 | zpl_unused(world); 257 | librg_event_t *e = (librg_event_t*)event; 258 | return (int32_t)e->size; 259 | } 260 | 261 | void * librg_event_userdata_get(librg_world *world, librg_event *event) { 262 | LIBRG_ASSERT(event); if (!event) return NULL; 263 | zpl_unused(world); 264 | librg_event_t *e = (librg_event_t*)event; 265 | return e->userdata; 266 | } 267 | 268 | // =======================================================================// 269 | // ! 270 | // ! Utitilites 271 | // ! 272 | // =======================================================================// 273 | 274 | LIBRG_ALWAYS_INLINE int16_t librg_util_chunkoffset_line(int16_t v, int16_t off, int16_t size) { 275 | float o = 0.0f; /* LIBRG_OFFSET_BEG */ 276 | if (off == LIBRG_OFFSET_MID) o = (size/2.0f); 277 | if (off == LIBRG_OFFSET_END) o = (size); 278 | 279 | /* integrate the offset */ 280 | o = o + v; 281 | 282 | return (int16_t)(o >= 0 ? zpl_floor(o) : zpl_ceil(o)); 283 | } 284 | 285 | librg_chunk librg_chunk_from_chunkpos(librg_world *world, int16_t chunk_x, int16_t chunk_y, int16_t chunk_z) { 286 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 287 | librg_world_t *wld = (librg_world_t *)world; 288 | 289 | int16_t chx = librg_util_chunkoffset_line(chunk_x, wld->chunkoffset.x, wld->worldsize.x); 290 | int16_t chy = librg_util_chunkoffset_line(chunk_y, wld->chunkoffset.y, wld->worldsize.y); 291 | int16_t chz = librg_util_chunkoffset_line(chunk_z, wld->chunkoffset.z, wld->worldsize.z); 292 | 293 | /* return error if the size is too far off the max world limits */ 294 | if ((chx < 0 || chx >= wld->worldsize.x) 295 | || (chy < 0 || chy >= wld->worldsize.y) 296 | || (chz < 0 || chz >= wld->worldsize.z)) { 297 | return LIBRG_CHUNK_INVALID; 298 | } 299 | 300 | librg_chunk id = (chz * wld->worldsize.y * wld->worldsize.x) + (chy * wld->worldsize.x) + (chx); 301 | 302 | if (id < 0 || id > (wld->worldsize.x * wld->worldsize.y * wld->worldsize.z)) { 303 | return LIBRG_CHUNK_INVALID; 304 | } 305 | 306 | return id; 307 | } 308 | 309 | int8_t librg_chunk_to_chunkpos(librg_world *world, librg_chunk id, int16_t *chunk_x, int16_t *chunk_y, int16_t *chunk_z) { 310 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 311 | librg_world_t *wld = (librg_world_t *)world; 312 | 313 | if (id < 0 || id > (wld->worldsize.x * wld->worldsize.y * wld->worldsize.z)) { 314 | return LIBRG_CHUNK_INVALID; 315 | } 316 | 317 | int64_t z = (int64_t)(id / (wld->worldsize.x * wld->worldsize.y)); 318 | int64_t r1 = (int64_t)(id % (wld->worldsize.x * wld->worldsize.y)); 319 | int64_t y = r1 / wld->worldsize.x; 320 | int64_t x = r1 % wld->worldsize.x; 321 | 322 | if (chunk_x) *chunk_x = (int16_t)(x - librg_util_chunkoffset_line(0, wld->chunkoffset.x, wld->worldsize.x)); 323 | if (chunk_y) *chunk_y = (int16_t)(y - librg_util_chunkoffset_line(0, wld->chunkoffset.y, wld->worldsize.y)); 324 | if (chunk_z) *chunk_z = (int16_t)(z - librg_util_chunkoffset_line(0, wld->chunkoffset.z, wld->worldsize.z)); 325 | 326 | return LIBRG_OK; 327 | } 328 | 329 | librg_chunk librg_chunk_from_realpos(librg_world *world, double x, double y, double z) { 330 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 331 | librg_world_t *wld = (librg_world_t *)world; 332 | return librg_chunk_from_chunkpos(world, (int16_t)(x/wld->chunksize.x), (int16_t)(y/wld->chunksize.y), (int16_t)(z/wld->chunksize.z)); 333 | } 334 | 335 | LIBRG_END_C_DECLS 336 | -------------------------------------------------------------------------------- /code/source/packing.c: -------------------------------------------------------------------------------- 1 | // file: source/packing.c 2 | 3 | #ifdef LIBRG_EDITOR 4 | #include 5 | #include 6 | #endif 7 | 8 | LIBRG_BEGIN_C_DECLS 9 | 10 | // =======================================================================// 11 | // ! 12 | // ! Primitives 13 | // ! 14 | // =======================================================================// 15 | 16 | /* size of the segment */ 17 | #define LIBRG_SEGMENT_SIZE 8 18 | 19 | /* size of the segment value */ 20 | #ifdef LIBRG_ENABLE_EXTENDED_EVENTBUFFER 21 | #define LIBRG_SEGVAL_SIZE 14 22 | #else 23 | #define LIBRG_SEGVAL_SIZE 12 24 | #endif 25 | 26 | LIBRG_PRAGMA(pack(push, 1)); 27 | typedef struct { 28 | uint64_t id; 29 | uint16_t token; 30 | LIBRG_WORLDWRITE_DATATYPE size; 31 | } librg_segval_t; 32 | 33 | typedef struct { 34 | uint8_t type; 35 | uint8_t unused_; 36 | uint16_t amount; 37 | uint32_t size; 38 | } librg_segment_t; 39 | LIBRG_PRAGMA(pack(pop)); 40 | 41 | LIBRG_STATIC_ASSERT(sizeof(librg_segval_t) == LIBRG_SEGVAL_SIZE, "packed librg_segval_t should have a valid size"); 42 | LIBRG_STATIC_ASSERT(sizeof(librg_segment_t) == LIBRG_SEGMENT_SIZE, "packed librg_segment_t should have a valid size"); 43 | 44 | // =======================================================================// 45 | // ! 46 | // ! World data packing method 47 | // ! 48 | // =======================================================================// 49 | 50 | int32_t librg_world_write(librg_world *world, int64_t owner_id, uint8_t chunk_radius, char *buffer, size_t *size, void *userdata) { 51 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 52 | librg_world_t *wld = (librg_world_t *)world; 53 | librg_table_i64 *last_snapshot = librg_table_tbl_get(&wld->owner_map, owner_id); 54 | 55 | /* no snapshot - means we are asking an invalid owner */ 56 | if (!last_snapshot) { 57 | *size = 0; 58 | return LIBRG_OWNER_INVALID; 59 | } 60 | 61 | /* get old, and preapre new snapshot handlers */ 62 | librg_table_i64 next_snapshot = {0}; 63 | librg_table_i64_init(&next_snapshot, wld->allocator); 64 | 65 | int64_t *results = (int64_t *)LIBRG_MEM_ALLOC(LIBRG_WORLDWRITE_MAXQUERY * sizeof(int64_t)); 66 | size_t total_amount = LIBRG_WORLDWRITE_MAXQUERY; 67 | librg_world_query(world, owner_id, chunk_radius, results, &total_amount); 68 | 69 | size_t total_written = 0; 70 | librg_event_t evt = {0}; 71 | 72 | #define sz_total (total_written + sizeof(librg_segment_t)) 73 | #define sz_value (sz_total + value_written + sizeof(librg_segval_t)) 74 | 75 | uint8_t action_id = LIBRG_WRITE_CREATE; 76 | size_t buffer_limit = *size; 77 | size_t insufficient_size = 0; 78 | 79 | librg_lbl_ww: 80 | 81 | /* create and update */ 82 | if (sz_total < buffer_limit) { 83 | librg_segment_t *seg = (librg_segment_t*)(buffer+total_written); 84 | char *segend = (buffer + sz_total); 85 | 86 | uint16_t amount = 0; 87 | size_t value_written = 0; 88 | size_t iterations = total_amount; 89 | 90 | int64_t entity_id = LIBRG_ENTITY_INVALID; 91 | int32_t condition = LIBRG_TRUE; 92 | librg_entity_t *entity_blob = NULL; 93 | 94 | /* for deletions we are iterating something else */ 95 | if (action_id == LIBRG_WRITE_REMOVE) { 96 | iterations = zpl_array_count(last_snapshot->entries); 97 | } 98 | 99 | for (size_t i = 0; i < iterations; ++i) { 100 | int16_t action_rejected = LIBRG_TRUE; 101 | int32_t data_size = 0; 102 | 103 | /* preparation */ 104 | if (action_id == LIBRG_WRITE_CREATE) { 105 | entity_id = results[i]; /* it did not exist && not foreign */ 106 | entity_blob = librg_table_ent_get(&wld->entity_map, entity_id); 107 | condition = librg_table_i64_get(last_snapshot, entity_id) == NULL 108 | && librg_entity_foreign(world, entity_id) != LIBRG_TRUE; 109 | } 110 | else if (action_id == LIBRG_WRITE_UPDATE) { 111 | entity_id = results[i]; /* it did exist */ 112 | entity_blob = librg_table_ent_get(&wld->entity_map, entity_id); 113 | condition = librg_table_i64_get(last_snapshot, entity_id) != NULL || librg_entity_foreign(world, entity_id) == LIBRG_TRUE; 114 | 115 | /* mark entity as still alive, to prevent it from being removed */ 116 | librg_table_i64_set(last_snapshot, entity_id, 2); 117 | } 118 | else if (action_id == LIBRG_WRITE_REMOVE) { 119 | entity_id = last_snapshot->entries[i].key; /* it was not marked as updated && and not foreign */ 120 | condition = last_snapshot->entries[i].value != 2 121 | && librg_entity_foreign(world, entity_id) != LIBRG_TRUE; 122 | } 123 | else if (action_id == LIBRG_WRITE_OWNER) { 124 | entity_id = results[i]; /* if we are the owner and we havent yet notified reader about that */ 125 | entity_blob = librg_table_ent_get(&wld->entity_map, entity_id); 126 | condition = entity_blob 127 | && entity_blob->owner_id == owner_id 128 | && entity_blob->flag_owner_updated 129 | && librg_table_i64_get(&next_snapshot, entity_id); 130 | } 131 | 132 | /* data write */ 133 | if (condition && sz_value < buffer_limit) { 134 | librg_segval_t *val = (librg_segval_t*)(segend + value_written); 135 | char *valend = (segend + value_written + sizeof(librg_segval_t)); 136 | 137 | /* fill in event */ 138 | evt.entity_id = entity_id; 139 | evt.type = action_id; 140 | evt.size = buffer_limit - sz_value; 141 | evt.buffer = valend; 142 | evt.owner_id = owner_id; 143 | evt.userdata = userdata; 144 | 145 | /* call event handlers */ 146 | if (wld->handlers[action_id]) { 147 | data_size = (int32_t)wld->handlers[action_id](world, (librg_event*)&evt); 148 | 149 | /* if data size is bigger than the limit, we will notify user about that */ 150 | if (data_size > ZPL_I32_MAX) { 151 | ZPL_PANIC("librg: the data size returned by the event handler is too big for the event. \ 152 | Ensure that you are not returning more than %d bytes.", ZPL_I32_MAX); 153 | } 154 | 155 | #ifndef LIBRG_ENABLE_EXTENDED_EVENTBUFFER 156 | if (data_size > (int32_t)ZPL_U16_MAX) { 157 | ZPL_PANIC("librg: the data size returned by the event handler is bigger than the event buffer size. \ 158 | Ensure that you are not returning more than %d bytes.", evt.size); 159 | } 160 | #endif 161 | } 162 | 163 | /* if user returned < 0, we consider that event rejected */ 164 | if (data_size >= 0) { 165 | /* fill in segval */ 166 | val->id = entity_id; 167 | val->size = data_size; 168 | 169 | if (action_id == LIBRG_WRITE_OWNER) { 170 | val->token = entity_blob->ownership_token; 171 | } 172 | else if (action_id == LIBRG_WRITE_CREATE && entity_blob->owner_id == owner_id) { 173 | val->token = 1; 174 | } 175 | else if (action_id == LIBRG_WRITE_UPDATE && entity_blob->flag_foreign) { 176 | val->token = entity_blob->ownership_token; 177 | } else { 178 | val->token = 0; 179 | } 180 | 181 | /* increase the total size written */ 182 | value_written += sizeof(librg_segval_t) + val->size; 183 | action_rejected = LIBRG_FALSE; 184 | amount++; 185 | } 186 | } 187 | 188 | /* accumulate insufficient buffer size */ 189 | if (condition && sz_value >= buffer_limit) { 190 | insufficient_size += (sz_value - buffer_limit); 191 | } 192 | 193 | /* finaliztion */ 194 | if (action_id == LIBRG_WRITE_CREATE && !action_rejected) { 195 | /* mark entity as created, so it can start updating */ 196 | librg_table_i64_set(&next_snapshot, entity_id, 1); 197 | } 198 | else if (action_id == LIBRG_WRITE_UPDATE && condition) { 199 | /* consider entitry updated, without regards was it written or not */ 200 | librg_table_i64_set(&next_snapshot, entity_id, 1); 201 | } 202 | else if (action_id == LIBRG_WRITE_REMOVE && condition && action_rejected) { 203 | /* consider entity alive, till we are able to send it */ 204 | librg_table_i64_set(&next_snapshot, entity_id, 1); 205 | } 206 | else if (action_id == LIBRG_WRITE_OWNER && condition) { 207 | /* mark reader as notified */ 208 | entity_blob->flag_owner_updated = LIBRG_FALSE; 209 | } 210 | } 211 | 212 | if (amount > 0) { 213 | seg->type = action_id; 214 | seg->size = (uint32_t)value_written; 215 | seg->amount = amount; 216 | 217 | total_written += sizeof(librg_segment_t) + seg->size; 218 | } 219 | } else { 220 | insufficient_size += sz_total - buffer_limit; 221 | } 222 | 223 | /* iterate it again till all tasks are finished */ 224 | switch (action_id) { 225 | case LIBRG_WRITE_CREATE: action_id = LIBRG_WRITE_UPDATE; goto librg_lbl_ww; 226 | case LIBRG_WRITE_UPDATE: action_id = LIBRG_WRITE_REMOVE; goto librg_lbl_ww; 227 | case LIBRG_WRITE_REMOVE: action_id = LIBRG_WRITE_OWNER; goto librg_lbl_ww; 228 | } 229 | 230 | /* swap snapshot tables */ 231 | librg_table_i64_destroy(last_snapshot); 232 | librg_table_tbl_set(&wld->owner_map, owner_id, next_snapshot); 233 | LIBRG_MEM_FREE(results); 234 | 235 | /* write our total size */ 236 | *size = total_written; 237 | 238 | #undef sz_total 239 | #undef sz_value 240 | 241 | /* if we didnt have enough space, value will be > 0 */ 242 | return (int32_t)insufficient_size; 243 | } 244 | 245 | // =======================================================================// 246 | // ! 247 | // ! World data unpacking method 248 | // ! 249 | // =======================================================================// 250 | 251 | int32_t librg_world_read(librg_world *world, int64_t owner_id, const char *buffer, size_t size, void *userdata) { 252 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 253 | librg_world_t *wld = (librg_world_t *)world; 254 | 255 | librg_event_t evt = {0}; 256 | size_t total_read = 0; 257 | 258 | #define sz_segment (total_read + sizeof(librg_segment_t)) 259 | #define sz_segval (sz_segment + segment_read + sizeof(librg_segval_t)) 260 | 261 | while ((size-total_read) > sizeof(librg_segment_t)) { 262 | librg_segment_t *seg = (librg_segment_t*)(buffer+total_read); 263 | size_t segment_read = 0; 264 | 265 | /* immidiately exit if we will not be able to read the segment data */ 266 | if (sz_segment+seg->size > size || sz_segment + seg->amount * sizeof(librg_segval_t) > size) { 267 | break; 268 | } 269 | 270 | for (int i = 0; i < seg->amount; ++i) { 271 | librg_segval_t *val = (librg_segval_t*)(buffer+sz_segment+segment_read); 272 | librg_entity_t *entity_blob = librg_table_ent_get(&wld->entity_map, val->id); 273 | int8_t action_id = -1; 274 | 275 | /* do preparation for entity processing */ 276 | if (seg->type == LIBRG_WRITE_CREATE) { 277 | /* attempt to create an entity */ 278 | action_id = (librg_entity_track(world, val->id) == LIBRG_OK) 279 | ? LIBRG_READ_CREATE 280 | : LIBRG_ERROR_CREATE; 281 | } 282 | else if (seg->type == LIBRG_WRITE_UPDATE) { 283 | /* try to check if entity exists, and if it is foreign OR owner and token are correct */ 284 | action_id = (librg_entity_tracked(world, val->id) == LIBRG_TRUE 285 | && entity_blob 286 | && (entity_blob->flag_foreign || (entity_blob->owner_id == owner_id 287 | && entity_blob->ownership_token == val->token) 288 | )) 289 | ? LIBRG_READ_UPDATE 290 | : LIBRG_ERROR_UPDATE; 291 | } 292 | else if (seg->type == LIBRG_WRITE_REMOVE) { 293 | /* attempt to check if it does exist and only foreign */ 294 | action_id = (librg_entity_tracked(world, val->id) == LIBRG_TRUE 295 | && librg_entity_foreign(world, val->id) == LIBRG_TRUE) 296 | ? LIBRG_READ_REMOVE 297 | : LIBRG_ERROR_REMOVE; 298 | } 299 | else if (seg->type == LIBRG_WRITE_OWNER) { 300 | /* attempt to check if it does exist and only foreign */ 301 | action_id = (librg_entity_tracked(world, val->id) == LIBRG_TRUE 302 | && librg_entity_foreign(world, val->id) == LIBRG_TRUE) 303 | ? LIBRG_READ_OWNER 304 | : LIBRG_ERROR_OWNER; 305 | } 306 | 307 | if (action_id == -1) { 308 | return LIBRG_READ_INVALID; 309 | } 310 | 311 | /* do the initial entity processing */ 312 | if (action_id == LIBRG_READ_CREATE) { 313 | /* mark newly created entity as foreign */ 314 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, val->id); 315 | if (!entity) return LIBRG_READ_INVALID; else entity->flag_foreign = LIBRG_TRUE; 316 | if (val->token == 1) entity->owner_id = owner_id; 317 | } 318 | 319 | /* fill in event */ 320 | evt.entity_id = val->id; 321 | evt.type = action_id; 322 | evt.size = val->size; 323 | evt.buffer = (char*)(buffer+sz_segval); 324 | evt.owner_id = owner_id; 325 | evt.userdata = userdata; 326 | 327 | /* call event handlers */ 328 | if (wld->handlers[action_id]) { 329 | /*ignore response*/ 330 | wld->handlers[action_id](world, &evt); 331 | } 332 | 333 | /* do the afterwork processing */ 334 | if (action_id == LIBRG_READ_REMOVE) { 335 | /* remove foreign mark from entity */ 336 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, val->id); 337 | if (!entity) return LIBRG_READ_INVALID; else entity->flag_foreign = LIBRG_FALSE; 338 | librg_entity_untrack(world, val->id); 339 | } 340 | else if (action_id == LIBRG_READ_OWNER) { 341 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, val->id); 342 | if (!entity) return LIBRG_READ_INVALID; 343 | 344 | /* immidiately mark entity as owned, set up & override additional info */ 345 | entity->flag_foreign = LIBRG_FALSE; /* unmark it temp, while owner is set */ 346 | librg_entity_owner_set(world, val->id, owner_id); 347 | entity->ownership_token = val->token; 348 | entity->flag_owner_updated = LIBRG_FALSE; 349 | entity->flag_foreign = LIBRG_TRUE; 350 | } 351 | 352 | segment_read += sizeof(librg_segval_t) + val->size; 353 | } 354 | 355 | /* validate sizes of the data we read */ 356 | if (segment_read != seg->size) { 357 | return LIBRG_READ_INVALID; 358 | } 359 | 360 | total_read += sizeof(librg_segment_t) + segment_read; 361 | } 362 | 363 | #undef sz_segment 364 | #undef sz_segval 365 | 366 | if (total_read != size) { 367 | return (int32_t)(size - total_read); 368 | } 369 | 370 | return LIBRG_OK; 371 | } 372 | 373 | LIBRG_END_C_DECLS 374 | -------------------------------------------------------------------------------- /code/source/query.c: -------------------------------------------------------------------------------- 1 | // file: source/world.c 2 | 3 | #ifdef LIBRG_EDITOR 4 | #include 5 | #include 6 | #endif 7 | 8 | LIBRG_BEGIN_C_DECLS 9 | 10 | // =======================================================================// 11 | // ! 12 | // ! Simple general fetching methods 13 | // ! 14 | // =======================================================================// 15 | 16 | int32_t librg_world_fetch_all(librg_world *world, int64_t *entity_ids, size_t *entity_amount) { 17 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 18 | LIBRG_ASSERT(entity_amount); if (!entity_amount) return LIBRG_NULL_REFERENCE; 19 | librg_world_t *wld = (librg_world_t *)world; 20 | 21 | size_t count = 0; 22 | size_t buffer_limit = *entity_amount; 23 | size_t total_count = zpl_array_count(wld->entity_map.entries); 24 | 25 | for (size_t i=0; i < LIBRG_MIN(buffer_limit, total_count); ++i) { 26 | entity_ids[count++] = wld->entity_map.entries[i].key; 27 | } 28 | 29 | *entity_amount = count; 30 | return LIBRG_MAX(0, (int32_t)(total_count - buffer_limit)); 31 | } 32 | 33 | int32_t librg_world_fetch_chunk(librg_world *world, librg_chunk chunk, int64_t *entity_ids, size_t *entity_amount) { 34 | librg_chunk chunks[1]; chunks[0] = chunk; 35 | return librg_world_fetch_chunkarray(world, chunks, 1, entity_ids, entity_amount); 36 | } 37 | 38 | int32_t librg_world_fetch_chunkarray(librg_world *world, const librg_chunk *chunks, size_t chunk_amount, int64_t *entity_ids, size_t *entity_amount) { 39 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 40 | LIBRG_ASSERT(entity_amount); if (!entity_amount) return LIBRG_NULL_REFERENCE; 41 | librg_world_t *wld = (librg_world_t *)world; 42 | 43 | size_t count = 0; 44 | size_t iterated = 0; 45 | size_t buffer_limit = *entity_amount; 46 | size_t total_count = zpl_array_count(wld->entity_map.entries); 47 | 48 | for (size_t i=0; i < total_count && count < buffer_limit; ++i) { 49 | uint64_t entity_id = wld->entity_map.entries[i].key; 50 | librg_entity_t *entity = &wld->entity_map.entries[i].value; 51 | iterated++; 52 | 53 | for (size_t k = 0; k < chunk_amount; ++k) { 54 | librg_chunk chunk = chunks[k]; 55 | 56 | for (size_t j=0; j < LIBRG_ENTITY_MAXCHUNKS; ++j) { 57 | if (entity->chunks[j] == chunk) { 58 | entity_ids[count++] = entity_id; 59 | break; 60 | } 61 | 62 | /* immidiately exit if chunk is invalid (the rest will also be invalid) */ 63 | if (entity->chunks[j] == LIBRG_CHUNK_INVALID) { 64 | break; 65 | } 66 | } 67 | } 68 | } 69 | 70 | *entity_amount = count; 71 | return LIBRG_MAX(0, (int32_t)(total_count - iterated)); 72 | } 73 | 74 | int32_t librg_world_fetch_owner(librg_world *world, int64_t owner_id, int64_t *entity_ids, size_t *entity_amount) { 75 | int64_t owner_ids[1]; owner_ids[0] = owner_id; 76 | return librg_world_fetch_ownerarray(world, owner_ids, 1, entity_ids, entity_amount); 77 | } 78 | 79 | int32_t librg_world_fetch_ownerarray(librg_world *world, const int64_t *owner_ids, size_t owner_amount, int64_t *entity_ids, size_t *entity_amount) { 80 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 81 | LIBRG_ASSERT(entity_amount); if (!entity_amount) return LIBRG_NULL_REFERENCE; 82 | librg_world_t *wld = (librg_world_t *)world; 83 | 84 | size_t count = 0; 85 | size_t iterated = 0; 86 | size_t buffer_limit = *entity_amount; 87 | size_t total_count = zpl_array_count(wld->entity_map.entries); 88 | 89 | for (size_t i=0; i < total_count && count < buffer_limit; ++i) { 90 | uint64_t entity_id = wld->entity_map.entries[i].key; 91 | librg_entity_t *entity = &wld->entity_map.entries[i].value; 92 | iterated++; 93 | 94 | for (size_t k = 0; k < owner_amount; ++k) { 95 | int64_t owner_id = owner_ids[k]; 96 | if (entity->owner_id == owner_id) entity_ids[count++] = entity_id; 97 | } 98 | } 99 | 100 | *entity_amount = count; 101 | return LIBRG_MAX(0, (int32_t)(total_count - iterated)); 102 | } 103 | 104 | // =======================================================================// 105 | // ! 106 | // ! Main owner entity query method 107 | // ! 108 | // =======================================================================// 109 | 110 | static LIBRG_ALWAYS_INLINE void librg_util_chunkrange(librg_world *w, librg_table_i64 *ch, int cx, int cy, int cz, int8_t radius) { 111 | /* precalculate the radius power 2 for quicker distance check */ 112 | int radius2 = radius * radius; 113 | 114 | /* create a "bubble" by cutting off chunks outside of radius using distance checks */ 115 | for (int z=-radius; z<=radius; z++) { 116 | for (int y=-radius; y<=radius; y++) { 117 | for (int x=-radius; x<=radius; x++) { 118 | if (x*x+y*y+z*z <= radius2) { 119 | librg_chunk id = librg_chunk_from_chunkpos(w, cx+x, cy+y, cz+z); 120 | if (id != LIBRG_CHUNK_INVALID) librg_table_i64_set(ch, id, 1); 121 | } 122 | } 123 | } 124 | } 125 | 126 | return; 127 | } 128 | 129 | int32_t librg_world_query(librg_world *world, int64_t owner_id, uint8_t chunk_radius, int64_t *entity_ids, size_t *entity_amount) { 130 | LIBRG_ASSERT(world); if (!world) return LIBRG_WORLD_INVALID; 131 | LIBRG_ASSERT(entity_amount); if (!entity_amount) return LIBRG_NULL_REFERENCE; 132 | librg_world_t *wld = (librg_world_t *)world; 133 | 134 | size_t buffer_limit = *entity_amount; 135 | size_t total_count = zpl_array_count(wld->entity_map.entries); 136 | size_t result_amount = 0; 137 | 138 | /* mini helper for pushing entity */ 139 | /* if it will overflow do not push, just increase counter for future statistics */ 140 | #define librg_push_entity(entity_id) \ 141 | { if (result_amount + 1 <= buffer_limit) entity_ids[result_amount++] = entity_id; else result_amount++; } 142 | 143 | /* generate a map of visible chunks (only counting owned entities) */ 144 | for (int i = 0; i < zpl_array_count(wld->owner_entity_pairs); ++i) { 145 | if (wld->owner_entity_pairs[i].owner_id != owner_id) continue; 146 | 147 | uint64_t entity_id = wld->owner_entity_pairs[i].entity_id; 148 | librg_entity_t *entity = librg_table_ent_get(&wld->entity_map, entity_id); 149 | 150 | /* allways add self-owned entities */ 151 | int8_t vis_owner = librg_entity_visibility_owner_get(world, entity_id, owner_id); 152 | if (vis_owner != LIBRG_VISIBLITY_NEVER) { 153 | /* prevent from being included */ 154 | librg_push_entity(entity_id); 155 | } 156 | 157 | /* immidiately skip, if entity was not placed correctly */ 158 | if (entity->chunks[0] == LIBRG_CHUNK_INVALID) continue; 159 | /* and skip, if used is not an owner of the entity */ 160 | if (entity->owner_id != owner_id) continue; 161 | 162 | /* fetch, or create chunk set in this dimension if does not exist */ 163 | librg_table_i64 *dim_chunks = librg_table_tbl_get(&wld->dimensions, entity->dimension); 164 | 165 | if (!dim_chunks) { 166 | librg_table_i64 _chunks = {0}; 167 | librg_table_tbl_set(&wld->dimensions, entity->dimension, _chunks); 168 | dim_chunks = librg_table_tbl_get(&wld->dimensions, entity->dimension); 169 | librg_table_i64_init(dim_chunks, wld->allocator); 170 | } 171 | 172 | /* add entity chunks to the total visible chunks */ 173 | for (int k = 0; k < LIBRG_ENTITY_MAXCHUNKS; ++k) { 174 | if (entity->chunks[k] == LIBRG_CHUNK_INVALID) break; 175 | 176 | int16_t chx=0, chy=0, chz=0; 177 | librg_chunk_to_chunkpos(world, entity->chunks[k], &chx, &chy, &chz); 178 | librg_util_chunkrange(world, dim_chunks, chx, chy, chz, chunk_radius); 179 | } 180 | } 181 | 182 | /* iterate on all entities, and check if they are inside of the interested chunks */ 183 | for (size_t i=0; i < total_count; ++i) { 184 | uint64_t entity_id = wld->entity_map.entries[i].key; 185 | librg_entity_t *entity = &wld->entity_map.entries[i].value; 186 | librg_table_i64 *chunks = librg_table_tbl_get(&wld->dimensions, entity->dimension); 187 | 188 | if (entity->owner_id == owner_id) continue; 189 | 190 | /* owner visibility (personal)*/ 191 | int8_t vis_owner = librg_entity_visibility_owner_get(world, entity_id, owner_id); 192 | if (vis_owner == LIBRG_VISIBLITY_NEVER) { 193 | continue; /* prevent from being included */ 194 | } 195 | else if (vis_owner == LIBRG_VISIBLITY_ALWAYS) { 196 | librg_push_entity(entity_id); 197 | continue; 198 | } 199 | 200 | /* global entity visibility */ 201 | int8_t vis_global = librg_entity_visibility_global_get(world, entity_id); 202 | if (vis_global == LIBRG_VISIBLITY_NEVER) { 203 | continue; /* prevent from being included */ 204 | } 205 | else if (vis_global == LIBRG_VISIBLITY_ALWAYS) { 206 | librg_push_entity(entity_id); 207 | continue; 208 | } 209 | 210 | /* skip if there are no chunks in this dimension */ 211 | if (!chunks) continue; 212 | size_t chunk_amount = zpl_array_count(chunks->entries); 213 | 214 | for (size_t k = 0; k < chunk_amount; ++k) { 215 | librg_chunk chunk = chunks->entries[k].key; 216 | 217 | for (size_t j=0; j < LIBRG_ENTITY_MAXCHUNKS; ++j) { 218 | /* immidiately exit if chunk is invalid (the rest will also be invalid) */ 219 | if (entity->chunks[j] == LIBRG_CHUNK_INVALID) break; 220 | 221 | /* add entity and continue to the next one */ 222 | if (entity->chunks[j] == chunk) { 223 | librg_push_entity(entity_id); 224 | break; 225 | } 226 | } 227 | } 228 | } 229 | 230 | /* free up temp data */ 231 | for (int i = 0; i < zpl_array_count(wld->dimensions.entries); ++i) 232 | librg_table_i64_destroy(&wld->dimensions.entries[i].value); 233 | 234 | librg_table_tbl_clear(&wld->dimensions); 235 | 236 | #undef librg_push_entity 237 | 238 | *entity_amount = LIBRG_MIN(buffer_limit, result_amount); 239 | return LIBRG_MAX(0, (int32_t)(result_amount - buffer_limit)); 240 | } 241 | 242 | LIBRG_END_C_DECLS 243 | -------------------------------------------------------------------------------- /code/source/types.c: -------------------------------------------------------------------------------- 1 | // file: source/types.c 2 | 3 | #ifdef LIBRG_EDITOR 4 | #include 5 | #include 6 | #endif 7 | 8 | LIBRG_BEGIN_C_DECLS 9 | 10 | // =======================================================================// 11 | // ! 12 | // ! Macro based helpers, and functions available for redefintion 13 | // ! 14 | // =======================================================================// 15 | 16 | /* allows to define a custom allocator */ 17 | #ifndef LIBRG_MEM_ALLOC 18 | #define LIBRG_MEM_ALLOC(size) zpl_malloc(size) 19 | #endif 20 | 21 | /* allows to define a custom de-allocator */ 22 | #ifndef LIBRG_MEM_FREE 23 | #define LIBRG_MEM_FREE(ptr) zpl_mfree(ptr) 24 | #endif 25 | 26 | /* allows to define a custom assert handler */ 27 | #ifndef LIBRG_ASSERT 28 | #if defined(_DEBUG) || defined(LIBRG_DEBUG) 29 | #define LIBRG_ASSERT(x) ZPL_ASSERT(x) 30 | #else 31 | #define LIBRG_ASSERT(x) 32 | #endif 33 | #endif 34 | 35 | #ifndef LIBRG_MIN 36 | #define LIBRG_MIN(a, b) ((a) < (b) ? (a) : (b)) 37 | #endif 38 | 39 | #ifndef LIBRG_MAX 40 | #define LIBRG_MAX(a, b) ((a) > (b) ? (a) : (b)) 41 | #endif 42 | 43 | // =======================================================================// 44 | // ! 45 | // ! Compile-time static configuration settings 46 | // ! 47 | // =======================================================================// 48 | 49 | 50 | /* defines how many max chunks an entity */ 51 | /* can be located in simultaneously */ 52 | #ifndef LIBRG_ENTITY_MAXCHUNKS 53 | #define LIBRG_ENTITY_MAXCHUNKS 8 54 | #endif 55 | 56 | /* defines how many max entity ids could be used */ 57 | /* inside of the librg_world_write call */ 58 | #ifndef LIBRG_WORLDWRITE_MAXQUERY 59 | #define LIBRG_WORLDWRITE_MAXQUERY 16384 60 | #endif 61 | 62 | /* validate that value is less than maximum allowed */ 63 | #if LIBRG_WORLDWRITE_MAXQUERY > ZPL_U16_MAX 64 | #error "LIBRG_WORLDWRITE_MAXQUERY must have value less than 65535" 65 | #endif 66 | 67 | /* enables the increased data-buffer size for world packing */ 68 | #ifdef LIBRG_ENABLE_EXTENDED_EVENTBUFFER 69 | #define LIBRG_WORLDWRITE_DATATYPE uint32_t 70 | #else 71 | #define LIBRG_WORLDWRITE_DATATYPE uint16_t 72 | #endif 73 | 74 | // =======================================================================// 75 | // ! 76 | // ! Internal data structures 77 | // ! 78 | // =======================================================================// 79 | 80 | ZPL_TABLE(static inline, librg_table_i8, librg_table_i8_, int8_t); 81 | ZPL_TABLE(static inline, librg_table_i64, librg_table_i64_, int64_t); 82 | ZPL_TABLE(static inline, librg_table_tbl, librg_table_tbl_, librg_table_i64); 83 | 84 | enum { 85 | LIBRG_WRITE_OWNER = (LIBRG_ERROR_REMOVE+1), 86 | LIBRG_READ_OWNER, 87 | LIBRG_ERROR_OWNER, 88 | LIBRG_PACKAGING_TOTAL, 89 | }; 90 | 91 | typedef struct librg_entity_t { 92 | uint8_t type : 2; 93 | uint8_t visibility_global : 2; 94 | uint8_t flag_owner_updated : 1; 95 | uint8_t flag_foreign : 1; 96 | uint8_t flag_visbility_owner_enabled : 1; 97 | uint8_t flag_unused2 : 1; 98 | 99 | uint16_t ownership_token; 100 | 101 | int32_t dimension; 102 | int64_t owner_id; 103 | 104 | librg_chunk chunks[LIBRG_ENTITY_MAXCHUNKS]; 105 | librg_table_i8 owner_visibility_map; 106 | 107 | void *userdata; 108 | } librg_entity_t; 109 | 110 | ZPL_TABLE(static inline, librg_table_ent, librg_table_ent_, librg_entity_t); 111 | 112 | typedef struct librg_event_t { 113 | uint8_t type; /* type of the event that was called, might be useful in bindings */ 114 | int64_t owner_id; /* id of the owner who this event is called for */ 115 | int64_t entity_id; /* id of an entity which this event is called about */ 116 | char * buffer; /* ptr to the buffer data */ 117 | size_t size; /* depending on the event type, can show maximum amount of data you are able to write, or amount of data you can read */ 118 | void * userdata; /* userpointer that is passed from librg_world_write/librg_world_read fns */ 119 | } librg_event_t; 120 | 121 | typedef struct librg_owner_entity_pair_t { 122 | int64_t owner_id; /* id of the owner who this event is called for */ 123 | int64_t entity_id; /* id of an entity which this event is called about */ 124 | } librg_owner_entity_pair_t; 125 | 126 | typedef struct librg_world_t { 127 | uint8_t valid; 128 | zpl_allocator allocator; 129 | zpl_random random; 130 | 131 | struct { uint16_t x, y, z; } worldsize; 132 | struct { uint16_t x, y, z; } chunksize; 133 | struct { int16_t x, y, z; } chunkoffset; 134 | 135 | librg_event_fn handlers[LIBRG_PACKAGING_TOTAL]; 136 | librg_table_ent entity_map; 137 | librg_table_tbl owner_map; 138 | 139 | librg_table_tbl dimensions; 140 | 141 | /* owner-entity pair, needed for more effective query */ 142 | /* achieved by caching only owned entities and reducing the first iteration cycle */ 143 | zpl_array(librg_owner_entity_pair_t) owner_entity_pairs; 144 | 145 | void *userdata; 146 | } librg_world_t; 147 | 148 | LIBRG_END_C_DECLS 149 | -------------------------------------------------------------------------------- /code/tests/cases/entity.h: -------------------------------------------------------------------------------- 1 | MODULE(entity, { 2 | int8_t r = -1; 3 | 4 | IT("should be able to track and untrack an entity", { 5 | librg_world *world = librg_world_create(); 6 | 7 | r = librg_entity_tracked(world, 15); EQUALS(r, LIBRG_FALSE); 8 | r = librg_entity_track(world, 15); EQUALS(r, LIBRG_OK); 9 | r = librg_entity_tracked(world, 15); EQUALS(r, LIBRG_TRUE); 10 | r = librg_entity_untrack(world, 15); EQUALS(r, LIBRG_OK); 11 | r = librg_entity_tracked(world, 15); EQUALS(r, LIBRG_FALSE); 12 | 13 | librg_world_destroy(world); 14 | }); 15 | 16 | IT("should be able to properly set a userdata", { 17 | librg_world *world = librg_world_create(); 18 | librg_entity_track(world, 15); 19 | 20 | r = librg_entity_userdata_set(world, 15, (void *)15); EQUALS(r, LIBRG_OK); 21 | r = (int)(intptr_t)librg_entity_userdata_get(world, 15); EQUALS(r, 15); 22 | 23 | librg_entity_untrack(world, 15); 24 | librg_world_destroy(world); 25 | }); 26 | 27 | IT("should be able to properly set a chunk", { 28 | librg_world *world = librg_world_create(); 29 | librg_entity_track(world, 15); 30 | 31 | r = librg_entity_chunk_set(world, 15, 15); EQUALS(r, LIBRG_OK); 32 | r = (int8_t)librg_entity_chunk_get(world, 15); EQUALS(r, 15); 33 | 34 | librg_entity_untrack(world, 15); 35 | librg_world_destroy(world); 36 | }); 37 | 38 | IT("should be able to properly set an owner", { 39 | librg_world *world = librg_world_create(); 40 | librg_entity_track(world, 15); 41 | 42 | r = librg_entity_owner_set(world, 15, 15); EQUALS(r, LIBRG_OK); 43 | r = (int8_t)librg_entity_owner_get(world, 15); EQUALS(r, 15); 44 | 45 | librg_entity_untrack(world, 15); 46 | librg_world_destroy(world); 47 | }); 48 | 49 | IT("should be able to properly set a dimension", { 50 | librg_world *world = librg_world_create(); 51 | librg_entity_track(world, 15); 52 | 53 | r = librg_entity_dimension_set(world, 15, 15); EQUALS(r, LIBRG_OK); 54 | r = librg_entity_dimension_get(world, 15); EQUALS(r, 15); 55 | 56 | librg_entity_untrack(world, 15); 57 | librg_world_destroy(world); 58 | }); 59 | 60 | IT("should be able to properly set an array of chunks", { 61 | librg_world *world = librg_world_create(); 62 | librg_entity_track(world, 15); 63 | 64 | librg_chunk arr[3]; arr[0] = 1; arr[1] = 2; arr[2] = 3; 65 | r = librg_entity_chunkarray_set(world, 15, arr, 3); EQUALS(r, LIBRG_OK); 66 | 67 | librg_chunk res[8]; size_t amt = 8; 68 | int code = librg_entity_chunkarray_get(world, 15, res, &amt); 69 | 70 | EQUALS(code, LIBRG_OK); 71 | EQUALS(amt, 3); 72 | EQUALS(res[0], 1); 73 | EQUALS(res[1], 2); 74 | EQUALS(res[2], 3); 75 | 76 | librg_entity_untrack(world, 15); 77 | librg_world_destroy(world); 78 | }); 79 | 80 | IT("should be able to properly set an array of chunks with overflow", { 81 | librg_world *world = librg_world_create(); 82 | librg_entity_track(world, 15); 83 | 84 | librg_chunk arr[3]; arr[0] = 1; arr[1] = 2; arr[2] = 3; 85 | r = librg_entity_chunkarray_set(world, 15, arr, 3); EQUALS(r, LIBRG_OK); 86 | 87 | librg_chunk res[3] = {0}; size_t amt = 2; 88 | int code = librg_entity_chunkarray_get(world, 15, res, &amt); 89 | 90 | EQUALS(code, 6); 91 | EQUALS(amt, 2); 92 | EQUALS(res[0], 1); 93 | EQUALS(res[1], 2); 94 | EQUALS(res[2], 0); 95 | 96 | librg_entity_untrack(world, 15); 97 | librg_world_destroy(world); 98 | }); 99 | 100 | IT("should fail when attempting to set data onto untracked entity", { 101 | librg_world *world = librg_world_create(); 102 | 103 | r = librg_entity_userdata_set(world, 15, (void *)15); NEQUALS(r, LIBRG_OK); 104 | r = librg_entity_chunk_set(world, 15, 15); NEQUALS(r, LIBRG_OK); 105 | r = librg_entity_owner_set(world, 15, 15); NEQUALS(r, LIBRG_OK); 106 | r = librg_entity_dimension_set(world, 15, 15); NEQUALS(r, LIBRG_OK); 107 | 108 | librg_world_destroy(world); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /code/tests/unit.c: -------------------------------------------------------------------------------- 1 | #define LIBRG_IMPL 2 | #define LIBRG_DEBUG 3 | #include 4 | 5 | #include "unit.h" 6 | 7 | /* TEST CATEGORIES */ 8 | #include "cases/general.h" 9 | #include "cases/entity.h" 10 | #include "cases/query.h" 11 | #include "cases/packing.h" 12 | 13 | int main() { 14 | UNIT_CREATE("librg"); 15 | 16 | UNIT_MODULE(general); 17 | UNIT_MODULE(entity); 18 | UNIT_MODULE(query); 19 | UNIT_MODULE(packing); 20 | 21 | return UNIT_RUN(); 22 | } 23 | -------------------------------------------------------------------------------- /code/tests/unit.h: -------------------------------------------------------------------------------- 1 | /** 2 | ZPL - Unit testing framework 3 | 4 | Usage: 5 | #include "unit.h" in EXACTLY one source file, usually the one containing your testing app's entry point. 6 | 7 | There really is no need to include this file multiple times within a project, unless you wish to run 8 | multiple tests within a single executable or split test cases to multiple compilation units, in such case 9 | define UNIT_STATIC to ensure the library won't leak symbols outside compilation units. 10 | 11 | and cover your beautiful code already! 12 | 13 | GitHub: 14 | https://github.com/zpl-c/tester 15 | 16 | Version History: 17 | 1.0.1 - Small tweaks 18 | 1.0.0 - Where it all started... (not really) 19 | 20 | License: 21 | This Software is dual licensed under the following licenses: 22 | 23 | Unlicense 24 | This is free and unencumbered software released into the public domain. 25 | 26 | Anyone is free to copy, modify, publish, use, compile, sell, or 27 | distribute this software, either in source code form or as a compiled 28 | binary, for any purpose, commercial or non-commercial, and by any 29 | means. 30 | 31 | In jurisdictions that recognize copyright laws, the author or authors 32 | of this software dedicate any and all copyright interest in the 33 | software to the public domain. We make this dedication for the benefit 34 | of the public at large and to the detriment of our heirs and 35 | successors. We intend this dedication to be an overt act of 36 | relinquishment in perpetuity of all present and future rights to this 37 | software under copyright law. 38 | 39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 40 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 41 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 42 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 43 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 44 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 45 | OTHER DEALINGS IN THE SOFTWARE. 46 | 47 | For more information, please refer to 48 | 49 | BSD 3-Clause 50 | Copyright (c) 2016-2021 Dominik Madarász. All rights reserved. 51 | 52 | Redistribution and use in source and binary forms, with or without 53 | modification, are permitted provided that the following conditions are met: 54 | 55 | 1. Redistributions of source code must retain the above copyright notice, this 56 | list of conditions and the following disclaimer. 57 | 2. Redistributions in binary form must reproduce the above copyright notice, 58 | this list of conditions and the following disclaimer in the documentation 59 | and/or other materials provided with the distribution. 60 | 3. Neither the name of the copyright holder nor the names of its contributors 61 | may be used to endorse or promote products derived from this software without 62 | specific prior written permission. 63 | 64 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 65 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 66 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 67 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 68 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 69 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 70 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 71 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 72 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 73 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 74 | 75 | */ 76 | 77 | /* Adjust it to your needs, preferably to the number of modules you wish to test against. */ 78 | #ifndef UNIT_MAX_MODULES 79 | #define UNIT_MAX_MODULES 256 80 | #endif 81 | 82 | #ifndef UNIT_SKIP_MAGIC 83 | #define UNIT_SKIP_MAGIC 0xFF 84 | #endif 85 | 86 | #ifndef UNIT_JOIN2 87 | #define UNIT_JOIN2(a,b) a##b 88 | #endif 89 | 90 | /* Isolates test results within compilation units, this allows for running 91 | multiple test suites per single binary. */ 92 | #ifdef UNIT_STATIC 93 | #define UNIT_DEF static 94 | #else 95 | #define UNIT_DEF 96 | #endif 97 | 98 | #include 99 | #include 100 | #include 101 | #include 102 | 103 | #define MODULE(name, scope) \ 104 | int32_t UNIT_JOIN2(module__,name)() { \ 105 | printf("--------------------------------------\n"); \ 106 | printf(" module: %s\n", #name); \ 107 | printf("--------------------------------------\n"); \ 108 | fflush(stdout); \ 109 | _g_modules++; \ 110 | int32_t _total = 0; \ 111 | int32_t _errors = 0; \ 112 | int32_t _lasterr = 0; \ 113 | char *_errstr = 0; \ 114 | scope; \ 115 | fflush(stdout); \ 116 | printf("\n results: %d total, %s%d failed\x1B[0m, %s%d passed\x1B[0m\n", _total, _errors>0?"\x1B[31m":"", _errors, _errors==0?"\x1B[32m":"", _total - _errors); \ 117 | _g_total += _total; \ 118 | _g_errors += _errors; \ 119 | if (_errors) _g_modules_err++; \ 120 | return (_errors); \ 121 | } 122 | 123 | #define IT(desc, scope) \ 124 | _lasterr = 0; \ 125 | _errstr = ""; \ 126 | _total += 1; \ 127 | do scope while(0); \ 128 | if (_lasterr != UNIT_SKIP_MAGIC) _errors += _lasterr; \ 129 | printf(" * [%s]: It %s %s\n", (_lasterr == UNIT_SKIP_MAGIC) ? "\x1B[33mSKIP\x1B[0m" : (_lasterr) ? "\x1B[31mFAIL\x1B[0m" : "\x1B[32mPASS\x1B[0m", desc, _errstr); 130 | 131 | /* TEST CHECKS */ 132 | #define FAIL(a, b) { _errstr = unit__bprintf("\n\n\tassert: \x1B[31m%s:%lld %s %s:%lld\x1B[0m\n\tat %s:%d\n", #a, a, (a == b)?"==":"!=", #b, b, __FILE__, __LINE__); _lasterr = 1; break; } 133 | #define UFAIL(a, b) { _errstr = unit__bprintf("\n\n\tassert: \x1B[31m%s:%llu %s %s:%llu\x1B[0m\n\tat %s:%d\n", #a, a, (a == b)?"==":"!=", #b, b, __FILE__, __LINE__); _lasterr = 1; break; } 134 | #define STRFAIL(a, b) { _errstr = unit__bprintf("\n\n\tassert: \x1B[31m%s:%s %s %s:%s\x1B[0m\n\tat %s:%d\n", #a, (char *)a, (!strcmp(a,b))?"==":"!=", #b, b, __FILE__, __LINE__); _lasterr = 1; break; } 135 | #define EQUALS(a, b) if (a != b) { FAIL(a, b); } 136 | #define UEQUALS(a, b) if (a != b) { UFAIL(a, b); } 137 | #define STREQUALS(a, b) if (!!strcmp(a,b)) { STRFAIL(a, b); } 138 | #define STRCEQUALS(a, b, c) if (!!strncmp(a,b, c)) { STRFAIL(a, b); } 139 | #define STRCNEQUALS(a, b, c) if (!strncmp(a,b, c)) { STRFAIL(a, b); } 140 | #define STRNEQUALS(a, b) if (!strcmp(a,b)) { STRFAIL(a, b); } 141 | #define NEQUALS(a, b) if (a == b) { FAIL(a, b); } 142 | #define LESSER(a, b) if (a >= b) { FAIL(a, b); } 143 | #define GREATER(a, b) if (a <=b) { FAIL(a, b); } 144 | #define LESSEREQ(a, b) if (a < b) { FAIL(a, b); } 145 | #define GREATEREQ(a, b) if (a > b) { FAIL(a, b); } 146 | #define SKIP() { _lasterr = UNIT_SKIP_MAGIC; break; } 147 | 148 | #if defined(__GCC__) || defined(__GNUC__) || defined(__clang__) 149 | #pragma GCC diagnostic push 150 | #pragma GCC diagnostic ignored "-Wattributes" 151 | #pragma GCC diagnostic ignored "-Wunused-value" 152 | #pragma GCC diagnostic ignored "-Wignored-qualifiers" 153 | #pragma GCC diagnostic ignored "-Wunused-variable" 154 | #pragma GCC diagnostic ignored "-Wunused-function" 155 | #pragma GCC diagnostic ignored "-Wwrite-strings" 156 | #pragma GCC diagnostic ignored "-Wunused-parameter" 157 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 158 | #pragma GCC diagnostic ignored "-Wmissing-braces" 159 | #pragma GCC diagnostic ignored "-Wmissing-field-initializers" 160 | #pragma GCC diagnostic ignored "-Wpointer-to-int-cast" 161 | #endif 162 | 163 | #if defined(_MSC_VER) 164 | #pragma warning(push) 165 | #pragma warning(disable : 4201) 166 | #pragma warning(disable : 4127) // Conditional expression is constant 167 | #endif 168 | 169 | typedef int32_t (*unit_case)(); 170 | 171 | #define UNIT_CREATE(name) \ 172 | const char *unit_name = name; \ 173 | unit_case unit_modules[UNIT_MAX_MODULES] = {0}; \ 174 | int32_t unit_count = 0; 175 | 176 | #define UNIT_MODULE(name) \ 177 | unit_modules[unit_count++] = UNIT_JOIN2(module__,name) 178 | 179 | #define UNIT_RUN() \ 180 | unit_main(unit_name, unit_modules, unit_count) 181 | 182 | /* INTERNALS */ 183 | 184 | UNIT_DEF int32_t _g_modules = 0; 185 | UNIT_DEF int32_t _g_modules_err = 0; 186 | UNIT_DEF int32_t _g_total = 0; 187 | UNIT_DEF int32_t _g_errors = 0; 188 | 189 | UNIT_DEF int32_t unit_main(const char *name, unit_case *cases, int32_t count) { 190 | int32_t err = 0, cnt = count; 191 | printf("> testing suite:\n\n"); 192 | printf(" * suite: %s\n", name); 193 | printf(" * modules: %d\n", cnt); 194 | printf("\n"); 195 | 196 | for (int32_t i = 0; i < count; ++i) { 197 | err += cases[i](); 198 | } 199 | 200 | fflush(stdout); 201 | printf("--------------------------------------\n"); \ 202 | printf("> total:\n\n"); 203 | printf(" * modules: %d total, %s%d failed\x1B[0m, %s%d passed\x1B[0m\n", _g_modules, _g_modules_err>0?"\x1B[31m":"" ,_g_modules_err, _g_modules_err==0?"\x1B[32m":"", _g_modules - _g_modules_err); 204 | printf(" * tests: %d total, %s%d failed\x1B[0m, %s%d passed\x1B[0m\n", _g_total, _g_errors>0?"\x1B[31m":"" ,_g_errors, _g_errors==0?"\x1B[32m":"", _g_total - _g_errors); 205 | printf("\n"); 206 | 207 | return -err; 208 | } 209 | 210 | // Locally persisting buffer 211 | static inline char* unit__bprintf(const char* fmt, ...) 212 | { 213 | static char buf[128]; 214 | va_list args; 215 | va_start(args, fmt); 216 | vsprintf(buf, fmt, args); 217 | va_end(args); 218 | return buf; 219 | } 220 | 221 | #if defined(ZPL_COMPILER_MSVC) 222 | #pragma warning(pop) 223 | #endif 224 | 225 | #if defined(__GCC__) || defined(__GNUC__) || defined(__clang__) 226 | #pragma GCC diagnostic pop 227 | #endif 228 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zpl-c/librg/a89821aaf5a5ba85868a8d50d7f75cef44eae29a/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Welcome 2 | 3 | [readme.md](https://raw.githubusercontent.com/zpl-c/librg/master/README.md ':include') 4 | -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | > Making multi-player gamedev simpler since 2017 6 | 7 | * cross-platform support 8 | * light-weight, single-header 9 | * compile- and run-time configurable 10 | * written in C99 (portability reasons) 11 | * no external dependencies 12 | * built-in unit test coverage 13 | 14 | [GitHub](https://github.com/zpl-c/librg/) 15 | [Get Started](#welcome) 16 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - Getting started 4 | 5 | - [Welcome](/) 6 | - [Quick start](quickstart.md) 7 | - [Migration](migration.md) 8 | 9 | - General 10 | 11 | - [Basic types](defs/types.md) 12 | - [World](defs/world.md) 13 | - [Configuration](defs/config.md) 14 | - [Utilities](defs/utils.md) 15 | - [Entity](defs/entity.md) 16 | - [Querying](defs/query.md) 17 | - [Events](defs/events.md) 18 | - [Packing](defs/packing.md) 19 | 20 | - Advanced 21 | 22 | - [Compile-time configuration](compiletime.md) 23 | - [Custom allocators](allocators.md) 24 | 25 | -------------------------------------------------------------------------------- /docs/allocators.md: -------------------------------------------------------------------------------- 1 | # Custom allocators 2 | 3 | By default the library uses basic system heap allocator (`malloc`/`free`). 4 | However, it provides a way for you to change the default allocator and deallocator by redefining macro: 5 | 6 | ## LIBRG_MEM_ALLOC 7 | 8 | Allows to define a custom allocator 9 | 10 | ```c 11 | void *my_custom_alloc(size_t size); 12 | 13 | #define LIBRG_MEM_ALLOC(size) \ 14 | my_custom_alloc(size) 15 | 16 | #define LIBRG_IMPL 17 | #include "librg.h" 18 | 19 | void *my_custom_alloc(size_t size) { 20 | return /* some allocation stuff */; 21 | } 22 | ``` 23 | ## LIBRG_MEM_FREE 24 | 25 | Allows to define a custom deallocator 26 | 27 | ```c 28 | void my_custom_free(void *ptr); 29 | 30 | #define LIBRG_MEM_FREE(ptr) \ 31 | my_custom_free(ptr) 32 | 33 | #define LIBRG_IMPL 34 | #include "librg.h" 35 | 36 | void my_custom_free(void *ptr) { 37 | /* some deallocation stuff */; 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/compiletime.md: -------------------------------------------------------------------------------- 1 | # Compile-time configuration 2 | 3 | There are some options of the library that can be changed at compile time. 4 | All of them need to be defined only for the implementation part of the code (the one that has `LIBRG_IMPL` or `LIBRG_IMPLEMENTATION` defined). 5 | 6 | ## LIBRG_DEBUG 7 | 8 | A macro that defines [LIBRG_ASSERT](#librg_assert) macro. Automatically defined if `_DEBUG` macro is defined as well. 9 | 10 | ## LIBRG_ASSERT(x) 11 | 12 | A helper macro that can be used for debugging purposes, mainly to catch `NULL` pointers in the flow of the code. 13 | Can be redefined to any other function or macro. 14 | 15 | ### Example: 16 | 17 | ```c 18 | #define LIBRG_IMPL 19 | #define LIBRG_ASSERT(x) assert(x) 20 | #include "librg.h" 21 | #include 22 | ``` 23 | 24 | ## LIBRG_ENTITY_MAXCHUNKS 25 | 26 | Defines how many max chunks an entity can be located in simultaneously. Default value is `8`. 27 | Can be redefined to increase the amount: 28 | 29 | ```c 30 | #define LIBRG_IMPL 31 | #define LIBRG_ENTITY_MAXCHUNKS 16 32 | #include "librg.h" 33 | ``` 34 | 35 | ## LIBRG_WORLDWRITE_MAXQUERY 36 | 37 | Defines how many max entity ids could be used inside of the [librg_world_write](defs/packing.md#librg_world_write) call. Default value is `8192`. 38 | Can be redefined to increase the amount: 39 | 40 | ```c 41 | #define LIBRG_IMPL 42 | #define LIBRG_WORLDWRITE_MAXQUERY 16000 43 | #include "librg.h" 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/defs/config.md: -------------------------------------------------------------------------------- 1 | # World configuration methods 2 | 3 | All world configuration methods can be called at runtime. There will be no re-allocations happening, since methods only change world properties, 4 | and mostly only influece how chunk id will be calculated. 5 | 6 | ## librg_config_chunkamount_set 7 | 8 | Method allows you to set current amount of chunks in each axis. Minimal value for each axis is `1`. The best alternative name for this property is **world size**. 9 | As soon as it is set, the data will be kept, until the world will be destroyed, or data will be overwritten by another call to this method. 10 | 11 | Existing set of values can be fetched using [librg_config_chunkamount_get](#librg_config_chunkamount_get) method. 12 | 13 | * Setting values of `[1, 1, 1]` is an equialent of having a chunk-less world, where all the entities will always be in the same chunk 14 | * Setting values of `[16, 1, 1]` is an equialent of having a 1D world, consisting of 16 chunks (might be useful for side-scroller kind of things) 15 | * Setting values of `[16, 16, 1]` is an equialent of having a 2D world, consisting of a grid 16x16 chunks (might be useful for top-down or isometric things) 16 | * Setting values of `[16, 16, 16]` is an equialent of having a 3D world, consisting of a cube 16x16x16 chunks (might be useful for big 3d worlds) 17 | 18 | ##### Signature 19 | ```c 20 | int8_t librg_config_chunkamount_set( 21 | librg_world *world, 22 | uint16_t x, 23 | uint16_t y, 24 | uint16_t z 25 | ) 26 | ``` 27 | 28 | ##### Returns 29 | 30 | * In case of success return code is `LIBRG_OK` (defined as `0`) 31 | * In case of error return code is `LIBRG_WORLD_INVALID` 32 | 33 | ------------------------------- 34 | 35 | ## librg_config_chunkamount_get 36 | 37 | Method allows you to get current amounts of chunks in each axis, that was previously pushed there by [librg_config_chunkamount_set](#librg_config_chunkamount_set) method. 38 | If no data was ever pushed, the default set of values will be as following: `[256, 256, 256]`. 39 | 40 | > Note: in case of success, the returned values will be put into variables you've provided by the reference 41 | 42 | ##### Signature 43 | ```c 44 | int8_t librg_config_chunkamount_get( 45 | librg_world *world, 46 | uint16_t *x, /* out */ 47 | uint16_t *y, /* out */ 48 | uint16_t *z /* out */ 49 | ) 50 | ``` 51 | 52 | ##### Returns 53 | 54 | * In case of success return code is `LIBRG_OK` (defined as `0`) 55 | * In case of error return code is `LIBRG_WORLD_INVALID` 56 | 57 | ------------------------------- 58 | 59 | ## librg_config_chunksize_set 60 | 61 | Method allows you to set current size of a chunk in each axis. The best alternative name for this property is **chunk unit size**. 62 | As soon as it is set, the data will be kept, until the world will be destroyed, or data will be overwritten by another call to this method. 63 | 64 | Unit, in context of your application, can represent any arbitrary value of any scale (`pixel`, `meter`, `inch`, etc.) 65 | 66 | Existing set of values can be fetched using [librg_config_chunksize_get](#librg_config_chunksize_get) method. 67 | 68 | ##### Signature 69 | ```c 70 | int8_t librg_config_chunksize_set( 71 | librg_world *world, 72 | uint16_t x, 73 | uint16_t y, 74 | uint16_t z 75 | ) 76 | ``` 77 | 78 | ##### Returns 79 | 80 | * In case of success return code is `LIBRG_OK` (defined as `0`) 81 | * In case of error return code is `LIBRG_WORLD_INVALID` 82 | 83 | ------------------------------- 84 | 85 | ## librg_config_chunksize_get 86 | 87 | Method can be used to fetch currently stored size of a chunk in each axis, that was previously pushed there by [librg_config_chunksize_set](#librg_config_chunksize_set) method. 88 | If no data was ever pushed, the default set of values will be as following: `[16, 16, 16]`. 89 | 90 | > Note: in case of success, the returned values will be put into variables you've provided by the reference 91 | 92 | ##### Signature 93 | ```c 94 | int8_t librg_config_chunksize_get( 95 | librg_world *world, 96 | uint16_t *x, /* out */ 97 | uint16_t *y, /* out */ 98 | uint16_t *z /* out */ 99 | ) 100 | ``` 101 | 102 | ##### Returns 103 | 104 | * In case of success return code is `LIBRG_OK` (defined as `0`) 105 | * In case of error return code is `LIBRG_WORLD_INVALID` 106 | 107 | ------------------------------- 108 | 109 | ## librg_config_chunkoffset_set 110 | 111 | Method allows you set rules for how much the each chunk axis will be offset from the original value when calculating the chunk ids. 112 | As soon as it is set, the data will be kept, until the world will be destroyed, or data will be overwritten by another call to this method. 113 | 114 | Possible values for a single axis: 115 | * `LIBRG_OFFSET_MID` - the **default** value, chunk id will be calculated based on the half offset of the chunk amount. 116 | So in case chunk amount is `256`, and target position is `0`, the relative chunk position will be resolved to `256/2=128` 117 | * `LIBRG_OFFSET_BEG` - chunk id will be calculated without offset. 118 | So in case chunk amount is `256`, and target position is `0`, the relative chunk position will be resolved to `0` 119 | * `LIBRG_OFFSET_END` - chunk id will be calculated based on full offset of the chunk amount. 120 | So in case chunk amount is `256`, and target position is `0`, the relative chunk position will be resolved to `256` 121 | * Any arbitrary value (positive/negative) can be used as well. It will be used in the caclulations directly 122 | So target position is `0`, the relative chunk position will be resolved to `0+value` 123 | 124 | Existing set of values can be fetched using [librg_config_chunkoffset_get](#librg_config_chunkoffset_get) method. 125 | 126 | ##### Signature 127 | ```c 128 | int8_t librg_config_chunkoffset_set( 129 | librg_world *world, 130 | int16_t x, 131 | int16_t y, 132 | int16_t z 133 | ) 134 | ``` 135 | 136 | ##### Returns 137 | 138 | * In case of success return code is `LIBRG_OK` (defined as `0`) 139 | * In case of error return code is `LIBRG_WORLD_INVALID` 140 | 141 | ------------------------------- 142 | 143 | ## librg_config_chunkoffset_get 144 | 145 | Method can be used to fetch currently stored chunk offsets in the world, that was previously pushed there by [librg_config_chunkoffset_set](#librg_config_chunkoffset_set) method. 146 | If no data was ever pushed, the default set of values will be as following: `[LIBRG_OFFSET_MID, LIBRG_OFFSET_MID, LIBRG_OFFSET_MID]`. 147 | 148 | > Note: in case of success, the returned values will be put into variables you've provided by the reference 149 | 150 | ##### Signature 151 | ```c 152 | int8_t librg_config_chunkoffset_get( 153 | librg_world *world, 154 | int16_t *x, /* out */ 155 | int16_t *y, /* out */ 156 | int16_t *z /* out */ 157 | ) 158 | ``` 159 | 160 | ##### Returns 161 | 162 | * In case of success return code is `LIBRG_OK` (defined as `0`) 163 | * In case of error return code is `LIBRG_WORLD_INVALID` 164 | -------------------------------------------------------------------------------- /docs/defs/entity.md: -------------------------------------------------------------------------------- 1 | # Entities 2 | 3 | The library provides a way for developer to attach (track) his own entities, and deal with issues of ownership and visibility. 4 | 5 | ## librg_entity_track 6 | 7 | Method takes a number that represents a unique entity identifier inside of your own system/application. The entity_id is considered valid if it is in range of `[0 ... MAX_SIGNED_INT64]`. 8 | Internally it uses a hash-table to store pairs of id <-> entity data struct. There is an allocation happening inside of the method, mainly in cases when the hashtable needs to grow. 9 | 10 | ##### Signature 11 | ```c 12 | int8_t librg_entity_track( 13 | librg_world *world, 14 | int64_t entity_id 15 | ) 16 | ``` 17 | 18 | ##### Returns 19 | 20 | * In case of success: `LIBRG_OK` 21 | * In case of invalid world: `LIBRG_WORLD_INVALID` 22 | * In case of invalid entity id: `LIBRG_ENTITY_INVALID` 23 | * In case of collision (`entity_id` is taken): `LIBRG_ENTITY_ALREADY_TRACKED` 24 | 25 | ------------------------------ 26 | 27 | ## librg_entity_untrack 28 | 29 | Method removes provided entity from internal storage, cleaning up anything that was allocated within the context of that entity during time it was being tracked. 30 | Cannot be called on entities that were tracked in the world, different from current (check [librg_entity_foreign](#librg_entity_foreign) for more info). 31 | 32 | If entity is owned at the moment when it is being untracked, additional routine will be launched, that will scan all entities owned by the same owner, 33 | and in case there are no more found, any references about the owner will be removed from internal storage. 34 | 35 | Additionally, if [librg_entity_visibility_owner_set](#librg_entity_visibility_owner_set) was ever used on this entity, it also will launch a routine to cleanup the internal storage for that. 36 | 37 | ##### Signature 38 | ```c 39 | int8_t librg_entity_untrack( 40 | librg_world *world, 41 | int64_t entity_id 42 | ) 43 | ``` 44 | 45 | ##### Returns 46 | 47 | * In case of success: `LIBRG_OK` 48 | * In case of invalid world: `LIBRG_WORLD_INVALID` 49 | * In case of unknown entity: `LIBRG_ENTITY_UNTRACKED` 50 | * In case of foreign entity: `LIBRG_ENTITY_FOREIGN` 51 | 52 | ------------------------------ 53 | 54 | ## librg_entity_tracked 55 | 56 | Checks if particular entity is being tracked 57 | 58 | ##### Signature 59 | ```c 60 | int8_t librg_entity_tracked( 61 | librg_world *world, 62 | int64_t entity_id 63 | ) 64 | ``` 65 | 66 | ##### Returns 67 | 68 | * In case of truth: `LIBRG_TRUE` 69 | * In all other cases: `LIBRG_FALSE` 70 | 71 | ------------------------------ 72 | 73 | ## librg_entity_foreign 74 | 75 | Checks if a particular entity is actually a foreign entity. 76 | 77 | Foreign entity is an entity that was tracked in a different [world](defs/types.md#world), 78 | and was replicated in this world as an instance via [librg_world_read](defs/query.md#librg_world_read) method. 79 | Such entity cannot change it's current owner, nor it cannot be manually untracked in this world. 80 | The only way it can disappear from this world is by remove "instruction" from world where it has originated from also via [librg_world_read](defs/query.md#librg_world_read) call. 81 | 82 | ##### Signature 83 | ```c 84 | int8_t librg_entity_foreign( 85 | librg_world *world, 86 | int64_t entity_id 87 | ) 88 | ``` 89 | 90 | ##### Returns 91 | 92 | * In case of truth: `LIBRG_TRUE` 93 | * In all other cases: `LIBRG_FALSE` 94 | 95 | ------------------------------ 96 | 97 | ## librg_entity_owned 98 | 99 | Checks if a particular entity is actually a owned entity. 100 | 101 | ##### Signature 102 | ```c 103 | int8_t librg_entity_owned( 104 | librg_world *world, 105 | int64_t entity_id 106 | ) 107 | ``` 108 | 109 | ##### Returns 110 | 111 | * In case of truth: `LIBRG_TRUE` 112 | * In all other cases: `LIBRG_FALSE` 113 | 114 | ------------------------------ 115 | 116 | ## librg_entity_count 117 | 118 | A helper method that returns current amount of entities that are being tracked in the world 119 | 120 | ##### Signature 121 | ```c 122 | int32_t librg_entity_count( 123 | librg_world *world 124 | ) 125 | ``` 126 | 127 | ##### Returns 128 | 129 | * In case of success: a positive number represeting current count 130 | * In case of invalid world: `LIBRG_WORLD_INVALID` 131 | 132 | ------------------------------- 133 | 134 | ## librg_entity_userdata_set 135 | 136 | Method allows you to store a custom peice of data (pointer to data) in the internal entity structure. 137 | As soon as it is set, the data will be kept, until the entity get untracked, or data overwritten by another call to this method. 138 | 139 | Existing data can be fetched using [librg_entity_userdata_get](#librg_entity_userdata_get) method. 140 | 141 | ##### Signature 142 | ```c 143 | int8_t librg_entity_userdata_set( 144 | librg_world *world, 145 | int64_t entity_id, 146 | void *data 147 | ) 148 | ``` 149 | 150 | ##### Returns 151 | 152 | * In case of success: `LIBRG_OK` 153 | * In case of invalid world: `LIBRG_WORLD_INVALID` 154 | * In case of unknown entity: `LIBRG_ENTITY_UNTRACKED` 155 | 156 | ------------------------------- 157 | 158 | ## librg_entity_userdata_get 159 | 160 | Method can be used to fetch currently stored data in the entity, that was previously pushed there by [librg_entity_userdata_set](#librg_entity_userdata_set) method. 161 | If no data was ever pushed, the default value will be `NULL`. 162 | 163 | ##### Signature 164 | ```c 165 | void * librg_entity_userdata_get( 166 | librg_world *world, 167 | int64_t entity_id 168 | ) 169 | ``` 170 | 171 | ##### Returns 172 | 173 | * In case of success: pointer with the data 174 | * In all other cases: `NULL` 175 | 176 | ------------------------------ 177 | 178 | ## librg_entity_chunk_set 179 | 180 | Sets current "chunk" location of the entity. 181 | Since entity can simultaneously be located in up to `8` chunks by default ([compile-time configuration](compiletime.md)), 182 | setting this value overwrites any existing values for this entity and entity is placed strictly in a single chunk. 183 | 184 | To set entity location to multiple chunks at the same time, you can use [librg_entity_chunkarray_set](#librg_entity_chunkarray_set) method. 185 | 186 | ##### Signature 187 | ```c 188 | int8_t librg_entity_chunk_set( 189 | librg_world *world, 190 | int64_t entity_id, 191 | librg_chunk chunk 192 | ) 193 | ``` 194 | 195 | ##### Returns 196 | 197 | * In case of success: `LIBRG_OK` 198 | * In case of invalid world: `LIBRG_WORLD_INVALID` 199 | * In case of unknown entity: `LIBRG_ENTITY_UNTRACKED` 200 | 201 | ------------------------------ 202 | 203 | ## librg_entity_chunk_get 204 | 205 | Gets current "chunk" location of the entity. 206 | Since entity can simultaneously be located in up to `8` chunks by default ([compile-time configuration](compiletime.md)), 207 | the returned value will be the first chunk id element in the array of chunks that is entity currently in. 208 | 209 | > Note: Unless you are sure that entity is located only in a single chunk, it might be better to use method provided below 210 | 211 | To get entity location within multiple chunks at the same time, you can use [librg_entity_chunkarray_get](#librg_entity_chunkarray_get) method. 212 | 213 | ##### Signature 214 | ```c 215 | librg_chunk librg_entity_chunk_get( 216 | librg_world *world, 217 | int64_t entity_id 218 | ) 219 | ``` 220 | 221 | ##### Returns 222 | 223 | * In case of success: a valid [chunk](defs/types.md#chunk) id 224 | * In case of invalid world: `LIBRG_WORLD_INVALID` 225 | * In case of unknown entity: `LIBRG_ENTITY_UNTRACKED` 226 | * In all other cases: `LIBRG_CHUNK_INVALID` 227 | 228 | ------------------------------ 229 | 230 | ## librg_entity_chunkarray_set 231 | 232 | Sets current "chunk" location of the entity in form of an array. 233 | Since entity can simultaneously be located in up to `8` chunks by default ([compile-time configuration](compiletime.md)), 234 | setting this value overwrites any existing values for this entity and entity is placed in all of the chunks provided in argugments. 235 | The chunk ids will be copied from the buffer that you provide into an internal storage, so you do not need to keep that buffer allocated after the call. 236 | 237 | > Note: last argument tells method how many elemets are in the array, however no more than `8` (by default) will be used 238 | 239 | ##### Signature 240 | ```c 241 | int8_t librg_entity_chunkarray_set( 242 | librg_world *world, 243 | int64_t entity_id, 244 | const librg_chunk *values, /* in */ 245 | size_t chunk_amount 246 | ) 247 | ``` 248 | 249 | ##### Returns 250 | 251 | * In case of success: `LIBRG_OK` 252 | * In case of invalid world: `LIBRG_WORLD_INVALID` 253 | * In case of unknown entity: `LIBRG_ENTITY_UNTRACKED` 254 | 255 | ------------------------------ 256 | 257 | ## librg_entity_chunkarray_get 258 | 259 | Gets current chunk positions of the entity. 260 | Since entity can simultaneously be located in up to `8` chunks by default ([compile-time configuration](compiletime.md)), 261 | the returned value will be copied from an internal buffer to the array you provide. 262 | 263 | > Note: 264 | > * last argument tells method maximum number of elements of your array, and the method will respect that count 265 | > * last argument is in-out reference value, the resulting count will be written back to that variable 266 | > * please make sure to always provide a buffer of appropriately sized length, since you might not read all of the existing data 267 | 268 | ##### Signature 269 | ```c 270 | int8_t librg_entity_chunkarray_get( 271 | librg_world *world, 272 | int64_t entity_id, 273 | librg_chunk *values, /* out */ 274 | size_t *chunk_amount /* in-out */ 275 | ) 276 | ``` 277 | 278 | ##### Returns 279 | 280 | * In case of success: `LIBRG_OK` 281 | * Alternatively, in case of success: positive aproximated amount by which your buffer should be increased 282 | * In case of invalid world: `LIBRG_WORLD_INVALID` 283 | * In case of unknown entity: `LIBRG_ENTITY_UNTRACKED` 284 | 285 | ------------------------------ 286 | 287 | ## librg_entity_dimension_set 288 | 289 | Sets current entity dimension. 290 | Any given entity can be located only inside a single given dimension at any given time. 291 | There is no limit on how many entities can share a given dimension. 292 | 293 | ##### Signature 294 | ```c 295 | int8_t librg_entity_dimension_set( 296 | librg_world *world, 297 | int64_t entity_id, 298 | int32_t dimension 299 | ) 300 | ``` 301 | 302 | ##### Returns 303 | 304 | * In case of success: `LIBRG_OK` 305 | * In case of invalid world: `LIBRG_WORLD_INVALID` 306 | * In case of unknown entity: `LIBRG_ENTITY_UNTRACKED` 307 | 308 | ------------------------------ 309 | 310 | ## librg_entity_dimension_get 311 | 312 | Gets current entity dimension. 313 | 314 | ##### Signature 315 | ```c 316 | int32_t librg_entity_dimension_get( 317 | librg_world *world, 318 | int64_t entity_id 319 | ) 320 | ``` 321 | 322 | ##### Returns 323 | 324 | * In case of success: number representing a dimension 325 | * In case of invalid world: `LIBRG_WORLD_INVALID` 326 | * In case of unknown entity: `LIBRG_ENTITY_UNTRACKED` 327 | 328 | ------------------------------ 329 | 330 | ## librg_entity_owner_set 331 | 332 | Sets current entity owner. 333 | Owner, is an artificial concept that represents a relation of the entity to someone or something else. 334 | 335 | Making an entity owned allows to do multiple things: 336 | 337 | * Fetching entities owned by specific user via [librg_world_fetch_owner](defs/query.md#librg_world_fetch_owner) 338 | * Querying nearby entities based of current via [librg_world_query](defs/query.md#librg_world_query) 339 | * Setting up global and relational visibility via [librg_entity_visibility_owner_set](#librg_entity_visibility_owner_set) that will be used by query methods 340 | * Writing and reading world (serialization and replication) via [librg_world_write](defs/query.md#librg_world_write) methods 341 | 342 | Each call to the method will generate and set a new ownership token, which is in turn will be used in the next [librg_world_write](defs/query.md#librg_world_write) call. 343 | Ownership token is used to validate control settings for an entity, and with outdated token and updates coming into 344 | [librg_world_read](defs/query.md#librg_world_read) of local world that are targeting the entity will be discarded. 345 | For more details please refer to the documentation for those methods. 346 | 347 | ##### Signature 348 | ```c 349 | int8_t librg_entity_owner_set( 350 | librg_world *world, 351 | int64_t entity_id, 352 | int64_t owner_id 353 | ) 354 | ``` 355 | 356 | ##### Returns 357 | 358 | * In case of success: `LIBRG_OK` 359 | * In case of invalid world: `LIBRG_WORLD_INVALID` 360 | * In case of unknown entity: `LIBRG_ENTITY_UNTRACKED` 361 | 362 | ------------------------------ 363 | 364 | ## librg_entity_owner_get 365 | 366 | Returns current entity owner. 367 | 368 | ##### Signature 369 | ```c 370 | int64_t librg_entity_owner_get( 371 | librg_world *world, 372 | int64_t entity_id 373 | ) 374 | ``` 375 | 376 | ##### Returns 377 | 378 | * In case of success: number representing an owner 379 | * In case of invalid world: `LIBRG_WORLD_INVALID` 380 | * In case of unknown entity: `LIBRG_ENTITY_UNTRACKED` 381 | 382 | ------------------------------ 383 | 384 | ## librg_entity_visibility_global_set 385 | 386 | Set entity global visibility value. 387 | It affects how this entity will be viewed by other owners. 388 | 389 | Global visibility has lower priority than relation (owner) visibility, 390 | meaning globally invisible entity, will still be visibile to an owner if it has `LIBRG_VISIBLITY_ALWAYS` set for that owner and vice verca. 391 | 392 | Possible values: 393 | * `LIBRG_VISIBLITY_DEFAULT` - the default value of the visibility setting 394 | * `LIBRG_VISIBLITY_NEVER` - specified entity will be always invisible, regardless of proximity 395 | * `LIBRG_VISIBLITY_ALWAYS` - specified entity will be always visible, regardless of proximity 396 | 397 | > Note: For the owner of the entity the entity will remain visible unless [owner-to-entity invisibility](#librg_entity_visibility_owner_set) is set. 398 | 399 | ##### Signature 400 | ```c 401 | int8_t librg_entity_visibility_global_set( 402 | librg_world *world, 403 | int64_t entity_id, 404 | librg_visibility value 405 | ) 406 | ``` 407 | 408 | ##### Returns 409 | 410 | * In case of success: `LIBRG_OK` 411 | * In case of invalid world: `LIBRG_WORLD_INVALID` 412 | * In case of unknown entity: `LIBRG_ENTITY_UNTRACKED` 413 | 414 | ------------------------------ 415 | 416 | ## librg_entity_visibility_global_get 417 | 418 | Get entity global visibility value. 419 | It affects how this entity will be viewed by other owners. 420 | 421 | Possible values: 422 | * `LIBRG_VISIBLITY_DEFAULT` - the default value of the visibility setting 423 | * `LIBRG_VISIBLITY_NEVER` - specified entity will be always invisible, regardless of proximity 424 | * `LIBRG_VISIBLITY_ALWAYS` - specified entity will be always visible, regardless of proximity 425 | 426 | > Note: For the owner of the entity the entity will remain visible unless [owner-to-entity invisibility](#librg_entity_visibility_owner_set) is set. 427 | 428 | ##### Signature 429 | ```c 430 | int8_t librg_entity_visibility_global_get( 431 | librg_world *world, 432 | int64_t entity_id 433 | ) 434 | ``` 435 | 436 | ##### Returns 437 | 438 | * In case of success: current visibility value 439 | * In case of invalid world: `LIBRG_WORLD_INVALID` 440 | * In case of unknown entity: `LIBRG_ENTITY_UNTRACKED` 441 | 442 | ------------------------------ 443 | 444 | ## librg_entity_visibility_owner_set 445 | 446 | Set entity relational visibility value. 447 | It affects how this entity will be viewed by provided owner. 448 | 449 | Relational (owner) visibility has higher priority than global visibility, 450 | meaning globally invisible entity, will still be visibile to an owner if it has `LIBRG_VISIBLITY_ALWAYS` set for that owner and vice verca. 451 | 452 | 453 | Possible values: 454 | * `LIBRG_VISIBLITY_DEFAULT` - the default value of the visibility setting 455 | * `LIBRG_VISIBLITY_NEVER` - specified entity will be always invisible, regardless of proximity 456 | * `LIBRG_VISIBLITY_ALWAYS` - specified entity will be always visible, regardless of proximity 457 | 458 | ##### Signature 459 | ```c 460 | int8_t librg_entity_visibility_owner_set( 461 | librg_world *world, 462 | int64_t entity_id, 463 | int64_t owner_id, 464 | librg_visibility value 465 | ) 466 | ``` 467 | 468 | ##### Returns 469 | 470 | * In case of success: `LIBRG_OK` 471 | * In case of invalid world: `LIBRG_WORLD_INVALID` 472 | * In case of unknown entity: `LIBRG_ENTITY_UNTRACKED` 473 | 474 | ------------------------------ 475 | 476 | ## librg_entity_visibility_owner_get 477 | 478 | Get entity relational visibility value. 479 | It affects how this entity will be viewed by provided owner. 480 | 481 | Possible values: 482 | * `LIBRG_VISIBLITY_DEFAULT` - the default value of the visibility setting 483 | * `LIBRG_VISIBLITY_NEVER` - specified entity will be always invisible, regardless of proximity 484 | * `LIBRG_VISIBLITY_ALWAYS` - specified entity will be always visible, regardless of proximity 485 | 486 | ##### Signature 487 | ```c 488 | int8_t librg_entity_visibility_owner_get( 489 | librg_world *world, 490 | int64_t entity_id, 491 | int64_t owner_id 492 | ) 493 | ``` 494 | 495 | ##### Returns 496 | 497 | * In case of success: current visibility value 498 | * In case of invalid world: `LIBRG_WORLD_INVALID` 499 | * In case of unknown entity: `LIBRG_ENTITY_UNTRACKED` 500 | -------------------------------------------------------------------------------- /docs/defs/events.md: -------------------------------------------------------------------------------- 1 | # Event handling 2 | 3 | There 3 general event types that are shared between both reading and writing, they represent 3 states of the entity within a world: `created`, `updated`, `removed`. 4 | Following that model, events then are separated onto 2 distinct cateogries: 5 | 6 | * write-related (events that are triggered by [librg_world_write](defs/packing.md#librg_world_write) method) 7 | * read-related (events that are triggered by [librg_world_read](defs/packing.md#librg_world_read) method) 8 | 9 | ### Write events 10 | 11 | Write events can be considered as a simple dialogue interface, that asks you (developer), which additional data would you like to pack in, to the buffer, 12 | so that it could be unpacked read and easily assosiated with a particular entity in the destination world. 13 | 14 | Important note, write events are optional, and you don't have to handle them. 15 | Additionally, every write event can be rejected, and that influece what will happen to it. 16 | More details about rejection in each type below: 17 | 18 | * `LIBRG_WRITE_CREATE` - called when an entity has become visible for the owner. 19 | Might happen for a few reasons, for example it was just created, or it "walked" in his view radius, or one of the entities owned by owner was moved/teleported to a different place. 20 | Both entity and owner might have existed in the world for quite long time, however, they never have intersected their view radius before. 21 | Rejecting this event will prevent entity from being marked as created for that owner, which will cause this event to be called again in the next write method call. 22 | * `LIBRG_WRITE_UPDATE` - called when an entity is considered known to the owner. 23 | This event will be called on constant basis. 24 | Rejecting this event will not do anything except skipping data portion for that entity. 25 | This can be used as a with "update-only-when-changed" apporach, packing updates only when changes indeed occured, or creating some sort of throttling to control update frequency. 26 | * `LIBRG_WRITE_REMOVE` - called when an entity disappeared from owner's view radius. 27 | It might have been removed or just changed its position. 28 | Rejecting this event will cause it to be called on the next call. 29 | Note, however, this even is considered **dangerous**, since underlying entity might have been already untracked, use with caution. 30 | 31 | > **Important note**: The `return` parameter of the event is limited to UINT16_MAX, meaning maximum size of the per-event-buffer would also be limited to that size. If you exceed that limit, the library cause a PANIC and terminate the application. However, there is an option to increase that limit via a compile-time flag: `LIBRG_ENABLE_EXTENDED_EVENTBUFFER`. This expands per-event-buffer size to INT32_MAX, which should be more than enough for most of the cases. 32 | 33 | ### Read events 34 | 35 | Read events can be considered as a set of instructions coming from source world to the destination world, telling which entities should be created/updated/removedº there, 36 | passing all additional data that was written in the data buffers by inside custom event callbacks. 37 | 38 | Each event contains a buffer, and a size, that tells how much extra data was written there. 39 | 40 | * `LIBRG_READ_CREATE` - an instruction for destination world to create provided entity. 41 | If a given entity id was already taken (a collision occured, with existing entity in that world), `LIBRG_ERROR_CREATE` will be called instead. 42 | * `LIBRG_READ_UPDATE` - an instruction for destination world to update provided entity. 43 | IF a given entity id is not tracked, or owner of the entity does not much the `owner_id` argument of the read method call, 44 | or there is an ownership token mismatch, `LIBRRG_ERROR_UPDATE` will be called instead. 45 | * `LIBRG_READ_REMOVE` - an instruction for destination world to remove provided entity. 46 | IF a given entity id is not tracked, or it is not considered to be a foreign entity, `LIBRG_ERROR_REMOVE` will be called instead. 47 | 48 | ## librg_event_set 49 | 50 | Sets a [callback/handler](defs/types.md#librg_event_fn) for a provided event type/id. 51 | 52 | ##### Signature 53 | ```c 54 | int8_t librg_event_set( 55 | librg_world *world, 56 | librg_event_type id, 57 | librg_evetn_fn callback 58 | ) 59 | ``` 60 | 61 | ##### Returns 62 | 63 | * In case of success return code is `LIBRG_OK` 64 | * In case an existing event was replaced: `LIBRG_HANDLER_REPLACED` 65 | * In case of invalid world: `LIBRG_WORLD_INVALID` 66 | 67 | ------------------------------- 68 | 69 | ## librg_event_remove 70 | 71 | Removes a [callback/handler](defs/types.md#librg_event_fn) for a provided event type/id. 72 | 73 | ##### Signature 74 | ```c 75 | int8_t librg_event_remove( 76 | librg_world *world, 77 | librg_event_type id 78 | ) 79 | ``` 80 | 81 | ##### Returns 82 | 83 | * In case of success return code is `LIBRG_OK` 84 | * In case a non-existing event: `LIBRG_HANDLER_EMPTY` 85 | * In case of invalid world: `LIBRG_WORLD_INVALID` 86 | 87 | ------------------------------- 88 | 89 | ## librg_event_type_get 90 | 91 | Returns current event type of the provided event. 92 | Usually this method is called within the event callback itself. 93 | 94 | ##### Signature 95 | ```c 96 | int8_t librg_event_type_get( 97 | librg_world *world, 98 | librg_event *event 99 | ) 100 | ``` 101 | 102 | ##### Returns 103 | 104 | * In case of success: positive integer representing current event type (from `librg_event_type`enum) 105 | * In case of invalid event: `LIBRG_EVENT_INVALID` 106 | 107 | ------------------------------- 108 | 109 | ## librg_event_owner_get 110 | 111 | Returns owner id, which the event is called in context of. 112 | The owner id is provided from a parent [librg_world_write](defs/packing.md#librg_world_write) or [librg_world_read](defs/packing.md#librg_world_read) call. 113 | Usually this method is called within the event callback itself. 114 | 115 | ##### Signature 116 | ```c 117 | int64_t librg_event_owner_get( 118 | librg_world *world, 119 | librg_event *event 120 | ) 121 | ``` 122 | 123 | ##### Returns 124 | 125 | * In case of success: valid id 126 | * In case of invalid event: `LIBRG_EVENT_INVALID` 127 | 128 | ------------------------------- 129 | 130 | ## librg_event_entity_get 131 | 132 | Returns entity id, which the event is called for. 133 | Usually this method is called within the event callback itself. 134 | 135 | ##### Signature 136 | ```c 137 | int64_t librg_event_entity_get( 138 | librg_world *world, 139 | librg_event *event 140 | ) 141 | ``` 142 | 143 | ##### Returns 144 | 145 | * In case of success: valid id 146 | * In case of invalid event: `LIBRG_EVENT_INVALID` 147 | 148 | ------------------------------- 149 | 150 | ## librg_event_buffer_get 151 | 152 | Returns pointer to the data buffer, where data can be written to, or read from (depending on the event type). 153 | Usually this method is called within the event callback itself. 154 | 155 | ##### Signature 156 | ```c 157 | char * librg_event_buffer_get( 158 | librg_world *world, 159 | librg_event *event 160 | ) 161 | ``` 162 | 163 | ##### Returns 164 | 165 | * In case of success: valid pointer 166 | * In case of error: `NULL` 167 | 168 | ------------------------------- 169 | 170 | ## librg_event_size_get 171 | 172 | For read events, returns current size of the data stored in the buffer. 173 | For write events, returns maximum allowed length of the data that can be written to the buffer (based on the buffer passed to [librg_world_write](defs/packing.md#librg_world_write) call). 174 | Usually this method is called within the event callback itself. 175 | 176 | ##### Signature 177 | ```c 178 | int64_t librg_event_size_get( 179 | librg_world *world, 180 | librg_event *event 181 | ) 182 | ``` 183 | 184 | ##### Returns 185 | 186 | * In case of success: positive number represening length 187 | * In case of invalid event: `LIBRG_EVENT_INVALID` 188 | 189 | ------------------------------- 190 | 191 | ## librg_event_userdata_get 192 | 193 | Returns pointer with userdata that might be stored within the event. 194 | The userdata is provided from a parent [librg_world_write](defs/packing.md#librg_world_write) or [librg_world_read](defs/packing.md#librg_world_read) call. 195 | Usually this method is called within the event callback itself. 196 | 197 | ##### Signature 198 | ```c 199 | char * librg_event_userdata_get( 200 | librg_world *world, 201 | librg_event *event 202 | ) 203 | ``` 204 | 205 | ##### Returns 206 | 207 | * In case of success: passed pointer 208 | * In case of error: `NULL` 209 | -------------------------------------------------------------------------------- /docs/defs/packing.md: -------------------------------------------------------------------------------- 1 | # Packing 2 | 3 | ## librg_world_write 4 | 5 | Method is used to pack a set of visible entities to a given owner. 6 | 7 | In some way method is similar to [librg_world_query](defs/query.md#librg_world_query), and actually is using it inside. 8 | However, instead of returning set of results directly to you, it rather builds a snapshot of what is currently visible to the given owner, 9 | and compares it with a previous snapshot (from previous call to this method). 10 | This way method is able to keep track which entities are considered to be new, existing or forgotten for each owner within his view radius (based on entities that he owns). 11 | 12 | Visibility (Chunk) radius represents a linear/circular/spherical (depending on world configuration) radius of visibility in terms of nearby chunks. 13 | 14 | Additionally, an event is fired for each entity within owner's view radius, allowing you to write any additional information specific for this owner or this entity. 15 | For more details on events, please refer to the [events](defs/events.md) page. 16 | In case you wish to write any additional information, you would need to return length of the data you copied to the buffer. More details in the [example](#example) below. 17 | 18 | All of that information is then written to the buffer that provided as an argument, and is ready to be transferred/saved, and then later on read by [librg_world_read](#librg_world_read) method. 19 | 20 | Important: the [librg_world_query](defs/query.md#librg_world_query) will use a **temporary allocated** buffer of size [LIBRG_WORLDWRITE_MAXQUERY](compiletime.md#LIBRG_WORLDWRITE_MAXQUERY) elements. 21 | If the provided space will not be enough, you need to redefine the macro to increase the limit. 22 | 23 | > Note: 24 | > * pre-last argument tells method maximum length of your buffer, and the method will respect that length 25 | > * pre-last argument is in-out reference value, the resulting length will be written back to that variable 26 | > * please make sure to always provide a buffer of appropriately sized length 27 | > * last argument allows you to pass a custom user pointer, it will be injected to the event structure, allowing you to use that in the event callback 28 | 29 | ##### Signature 30 | ```c 31 | int32_t librg_world_write( 32 | librg_world *world, 33 | int64_t owner_id, 34 | uint8_t chunk_radius, 35 | char *buffer, /* out */ 36 | size_t *size, /* in-out */ 37 | void *userdata 38 | ) 39 | ``` 40 | 41 | ##### Returns 42 | 43 | * In case of success: `LIBRG_OK` 44 | * Alternatively, in case of success: positive aproximated length by which your buffer should be increased 45 | * In case of invalid world: `LIBRG_WORLD_INVALID` 46 | * In case of invalid owner: `LIBRG_OWNER_INVALID` 47 | 48 | 49 | ### **Example** 50 | 51 | [example-packing.c](https://raw.githubusercontent.com/zpl-c/librg/master/code/apps/example-packing.c ':include :type=code') 52 | 53 | ------------------------------ 54 | 55 | ## librg_world_read 56 | 57 | Method is used to unpack a previously packed buffer with data, containing a snapshot of the world for a specific owner. 58 | 59 | This method will read the buffer, and apply all the "instructions" from a source world to the current (destination) world. 60 | It will create entities that were not there before, update existing entities, and remove ones that are not considered visible by the source world, 61 | calling all appopriate events alonside, allowing you to read any additional data that you've passed from the source world. 62 | 63 | Check out [example](#example-1) below, for more details and demo. 64 | 65 | > Note: 66 | > * pre-last argument tells method maximum length of your buffer, and the method will respect that length 67 | > * last argument allows you to pass a custom user pointer, it will be injected to the event structure, allowing you to use that in the event callback 68 | 69 | ##### Signature 70 | ```c 71 | int32_t librg_world_read( 72 | librg_world *world, 73 | int64_t owner_id, 74 | char *buffer, /* in */ 75 | size_t size, 76 | void *userdata 77 | ) 78 | ``` 79 | 80 | ##### Returns 81 | 82 | * In case of success: `LIBRG_OK` 83 | * Alternatively, in case of success: positive aproximated length of how much data it was not able to read 84 | * In case of invalid/mismatching data size: `LIBRG_READ_INVALID` 85 | * In case of invalid world: `LIBRG_WORLD_INVALID` 86 | * In case of invalid owner: `LIBRG_OWNER_INVALID` 87 | 88 | ### **Example** 89 | 90 | [example-unpacking.c](https://raw.githubusercontent.com/zpl-c/librg/master/code/apps/example-unpacking.c ':include :type=code') 91 | -------------------------------------------------------------------------------- /docs/defs/query.md: -------------------------------------------------------------------------------- 1 | # Querying 2 | 3 | ## librg_world_fetch_all 4 | 5 | Method is used to fetch all tracked entities across all dimensions within the world. 6 | 7 | > Note: 8 | > * last argument tells method maximum number of elements of your array, and the method will respect that count 9 | > * last argument is in-out reference value, the resulting count will be written back to that variable 10 | > * please make sure to always provide a buffer of appropriately sized length, since you might not read all of the existing data 11 | 12 | ##### Signature 13 | ```c 14 | int32_t librg_world_fetch_all( 15 | librg_world *world, 16 | int64_t *entity_ids, /* out */ 17 | size_t *entity_amount /* in-out */ 18 | ) 19 | ``` 20 | 21 | ##### Returns 22 | 23 | * In case of success: `LIBRG_OK` 24 | * Alternatively, in case of success: positive aproximated amount by which your buffer should be increased 25 | * In case of invalid world: `LIBRG_WORLD_INVALID` 26 | 27 | ------------------------------ 28 | 29 | ## librg_world_fetch_chunk 30 | 31 | Method is used to fetch all tracked entities across all dimensions within a single chunk. 32 | 33 | > Note: 34 | > * last argument tells method maximum number of elements of your array, and the method will respect that count 35 | > * last argument is in-out reference value, the resulting count will be written back to that variable 36 | > * please make sure to always provide a buffer of appropriately sized length, since you might not read all of the existing data 37 | 38 | ##### Signature 39 | ```c 40 | int32_t librg_world_fetch_chunk( 41 | librg_world *world, 42 | librg_chunk chunk, 43 | int64_t *entity_ids, /* out */ 44 | size_t *entity_amount /* in-out */ 45 | ) 46 | ``` 47 | 48 | ##### Returns 49 | 50 | * In case of success: `LIBRG_OK` 51 | * Alternatively, in case of success: positive aproximated amount by which your buffer should be increased 52 | * In case of invalid world: `LIBRG_WORLD_INVALID` 53 | 54 | ------------------------------ 55 | 56 | ## librg_world_fetch_chunkarray 57 | 58 | Method is used to fetch all tracked entities across all dimensions within an array of chunks. 59 | 60 | > Note: 61 | > * last argument tells method maximum number of elements of your array, and the method will respect that count 62 | > * last argument is in-out reference value, the resulting count will be written back to that variable 63 | > * please make sure to always provide a buffer of appropriately sized length, since you might not read all of the existing data 64 | 65 | ##### Signature 66 | ```c 67 | int32_t librg_world_fetch_chunkarray( 68 | librg_world *world, 69 | const librg_chunk *chunks, /* in */ 70 | size_t chunk_amount, 71 | int64_t *entity_ids, /* out */ 72 | size_t *entity_amount /* in-out */ 73 | ) 74 | ``` 75 | 76 | ##### Returns 77 | 78 | * In case of success: `LIBRG_OK` 79 | * Alternatively, in case of success: positive aproximated amount by which your buffer should be increased 80 | * In case of invalid world: `LIBRG_WORLD_INVALID` 81 | 82 | ------------------------------ 83 | 84 | ## librg_world_fetch_owner 85 | 86 | Method is used to fetch all tracked entities across all dimensions owned by a given owner. 87 | 88 | > Note: 89 | > * last argument tells method maximum number of elements of your array, and the method will respect that count 90 | > * last argument is in-out reference value, the resulting count will be written back to that variable 91 | > * please make sure to always provide a buffer of appropriately sized length, since you might not read all of the existing data 92 | 93 | ##### Signature 94 | ```c 95 | int32_t librg_world_fetch_owner( 96 | librg_world *world, 97 | int64_t owner_id, 98 | int64_t *entity_ids, /* out */ 99 | size_t *entity_amount /* in-out */ 100 | ) 101 | ``` 102 | 103 | ##### Returns 104 | 105 | * In case of success: `LIBRG_OK` 106 | * Alternatively, in case of success: positive aproximated amount by which your buffer should be increased 107 | * In case of invalid world: `LIBRG_WORLD_INVALID` 108 | 109 | ------------------------------ 110 | 111 | ## librg_world_fetch_ownerarray 112 | 113 | Method is used to fetch all tracked entities across all dimensions owned by any of the given owners. 114 | 115 | > Note: 116 | > * last argument tells method maximum number of elements of your array, and the method will respect that count 117 | > * last argument is in-out reference value, the resulting count will be written back to that variable 118 | > * please make sure to always provide a buffer of appropriately sized length, since you might not read all of the existing data 119 | 120 | ##### Signature 121 | ```c 122 | int32_t librg_world_fetch_ownerarray( 123 | librg_world *world, 124 | const int64_t *owner_ids, /* in */ 125 | size_t owner_amount, 126 | int64_t *entity_ids, /* out */ 127 | size_t *entity_amount /* in-out */ 128 | ) 129 | ``` 130 | 131 | ##### Returns 132 | 133 | * In case of success: `LIBRG_OK` 134 | * Alternatively, in case of success: positive aproximated amount by which your buffer should be increased 135 | * In case of invalid world: `LIBRG_WORLD_INVALID` 136 | 137 | ------------------------------ 138 | 139 | ## librg_world_query 140 | 141 | Method is used for spatial entity filtering. 142 | It returns all entities that are "visible" to a provided `owner_id`. 143 | 144 | Visibility (Chunk) radius represents a linear/circular/spherical (depending on world configuration) radius of visibility in terms of nearby chunks. 145 | If entity was not properly placed onto a **valid chunk**, it will be filtered out from the query. 146 | Additionally any visibility overrides are applied on per-entity basis, filtering out those entities that should be (in)visible for the given owner. 147 | 148 | **Important**: owned entities will **always** be included in the query, even if they are located in the invalid chunk. 149 | 150 | > Note: 151 | > * last argument tells method maximum number of elements of your array, and the method will respect that count 152 | > * last argument is in-out reference value, the resulting count will be written back to that variable 153 | > * please make sure to always provide a buffer of appropriately sized length, since you might not read all of the existing data 154 | 155 | ##### Signature 156 | ```c 157 | int32_t librg_world_query( 158 | librg_world *world, 159 | int64_t owner_id, 160 | uint8_t chunk_radius, 161 | int64_t *entity_ids, /* out */ 162 | size_t *entity_amount /* in-out */ 163 | ) 164 | ``` 165 | 166 | ##### Returns 167 | 168 | * In case of success: `LIBRG_OK` 169 | * Alternatively, in case of success: positive aproximated amount by which your buffer should be increased 170 | * In case of invalid world: `LIBRG_WORLD_INVALID` 171 | -------------------------------------------------------------------------------- /docs/defs/types.md: -------------------------------------------------------------------------------- 1 | # General types 2 | 3 | ## world 4 | 5 | An opaque pointer used by public api, that points to an internal persisted, allocated world structure 6 | 7 | ```c 8 | typedef void librg_world; 9 | ``` 10 | 11 | ## event 12 | 13 | An opaque pointer used by public api, that points to a temporary event structure on the stack 14 | 15 | ```c 16 | typedef void librg_event; 17 | ``` 18 | 19 | ## chunk 20 | 21 | A number that reprensents a single chunk, all negative values are considered invalid 22 | 23 | ```c 24 | typedef int64_t librg_chunk; 25 | ``` 26 | 27 | ## LIBRG_OK 28 | 29 | ```c 30 | #define LIBRG_OK 0 31 | ``` 32 | 33 | ## LIBRG_TRUE 34 | 35 | ```c 36 | #define LIBRG_TRUE 1 37 | ``` 38 | 39 | ## LIBRG_FALSE 40 | 41 | ```c 42 | #define LIBRG_FALSE 0 43 | ``` 44 | 45 | ## librg_event_fn 46 | 47 | ```c 48 | typedef int32_t (*librg_event_fn)( 49 | librg_world *world, 50 | librg_event *event 51 | ); 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/defs/utils.md: -------------------------------------------------------------------------------- 1 | # Utility methods 2 | 3 | Generally used on different places of the application 4 | 5 | 6 | ## librg_chunk_from_chunkpos 7 | 8 | A helper function to convert a set of chunk axis coordinates into a single [chunk](defs/types.md#chunk) id, which can be used to represent same coordinates. 9 | Resulting value depends not only on the provided set of arguments, but also on the world configuration, 10 | more specifically on the values that were set by [librg_config_chunkamount_set](defs/config.md#librg_config_chunkamount_set) method. 11 | 12 | If resulting value goes outside of the valid bounds (being less than `0`, or bigger than a calculated world size `chunkamount(x * y * z)`), a `LIBRG_CHUNK_INVALID` will be returned instead. 13 | 14 | ##### Signature 15 | ```c 16 | librg_chunk librg_chunk_from_chunkpos( 17 | librg_world *world, 18 | int16_t chunk_x, 19 | int16_t chunk_y, 20 | int16_t chunk_z 21 | ) 22 | ``` 23 | 24 | ##### Returns 25 | 26 | * In case of success returns a valid [chunk](defs/types.md#chunk) id 27 | * In case of error returns `LIBRG_CHUNK_INVALID` 28 | 29 | ------------------------------- 30 | 31 | ## librg_chunk_from_realpos 32 | 33 | A helper function to convert a set of "real" axis coordinates into a single [chunk](defs/types.md#chunk) id. Very similar to the [librg_chunk_from_chunkpos](#librg_chunk_from_chunkpos) method. 34 | Resulting value depends not only on the provided set of arguments, but also on the world configuration, 35 | more specifically on the values that were set by both [librg_config_chunkamount_set](defs/config.md#librg_config_chunkamount_set) and [librg_config_chunksize_set](defs/config.md#librg_config_chunksize_set) methods. 36 | 37 | If resulting value goes outside of the valid bounds (being less than `0`, or bigger than a calculated world size `chunkamount(x * y * z)`), a `LIBRG_CHUNK_INVALID` will be returned instead. 38 | 39 | ##### Signature 40 | ```c 41 | librg_chunk librg_chunk_from_realpos( 42 | librg_world *world, 43 | double x, 44 | double y, 45 | double z 46 | ); 47 | ``` 48 | 49 | ##### Returns 50 | 51 | * In case of success returns a valid [chunk](defs/types.md#chunk) id 52 | * In case of error returns `LIBRG_CHUNK_INVALID` 53 | 54 | ------------------------------- 55 | 56 | ## librg_chunk_to_chunkpos 57 | 58 | A helper function, that does opposite of what [librg_chunk_from_chunkpos](#librg_chunk_from_chunkpos) is supposed to do. 59 | It "coverts" a single [chunk](defs/types.md#chunk) id, into a set of values (chunk coordinates) that correspond to the specified chunk. 60 | Relies on both [librg_config_chunkamount_set](defs/config.md#librg_config_chunkamount_set) and [librg_config_chunksize_set](defs/config.md#librg_config_chunksize_set) methods to calculate the values. 61 | 62 | > Note: resulting values will be written to the arguments that are passed by reference. 63 | 64 | ##### Signature 65 | ```c 66 | int8_t librg_chunk_to_chunkpos( 67 | librg_world *world, 68 | librg_chunk id, 69 | int16_t *chunk_x, /* out */ 70 | int16_t *chunk_y, /* out */ 71 | int16_t *chunk_z /* out */ 72 | ) 73 | ``` 74 | 75 | ##### Returns 76 | 77 | * In case of success: `LIBRG_OK` 78 | * In case of invalid world: `LIBRG_WORLD_INVLAID` 79 | * In case of invalid chunk id: `LIBRG_CHUNK_INVALID` 80 | -------------------------------------------------------------------------------- /docs/defs/world.md: -------------------------------------------------------------------------------- 1 | # World manipulation methods 2 | 3 | World can be considered a container of sorts, responsible for tracking a set of entities and their relationships. 4 | Single application can have a theoretically unlimited amout of worlds created. Tracked entities can share multiple worlds. 5 | 6 | ## librg_world_create 7 | 8 | This function is responsible for creating a [world](defs/types.md#world) instance. 9 | Internally it allocates all needed memory for the main world structure, and returns an opaque pointer. 10 | Developer can choose to create as many worlds as he needs to. All [world](defs/types.md#world) pointers will need to be freed (or destroyed) by [librg_world_destroy](#librg_world_destroy) method. 11 | 12 | > Note: All other library calls require this pointer to be provided. 13 | 14 | ##### Signature 15 | ```c 16 | librg_world * librg_world_create(); 17 | ``` 18 | 19 | ##### Returns 20 | 21 | * In case of success this function returns an opaque pointer, which you will need to later use in other calls 22 | * In case of error - the return will be a `NULL` value 23 | 24 | ------------------------------- 25 | 26 | ## librg_world_destroy 27 | 28 | This function is responsible for destroying a [world](defs/types.md#world) instance. 29 | Internally it frees all needed memory for the main world structure, and returns a result indicating if the call succeeded. 30 | 31 | > Note: after you've destroyed a world, it is recommended to manually set it's pointer to `NULL`, to prevent it from being considered as valid by other methods. 32 | 33 | ##### Signature 34 | ```c 35 | int8_t librg_world_destroy( 36 | librg_world *world 37 | ) 38 | ``` 39 | 40 | ##### Returns 41 | 42 | * In case of success return code is `LIBRG_OK` (defined as `0`) 43 | * In case of error return code is `LIBRG_WORLD_INVALID` 44 | 45 | ------------------------------- 46 | 47 | ## librg_world_valid 48 | 49 | Method allows you to check whether provided world is a valid one. 50 | 51 | ##### Signature 52 | ```c 53 | int8_t librg_world_valid( 54 | librg_world *world 55 | ) 56 | ``` 57 | 58 | ##### Returns 59 | 60 | * In case of success return code is `LIBRG_TRUE` (defined as `1`) 61 | * In all other cases return code is `LIBRG_FALSE` (defined as `0`) 62 | 63 | ------------------------------- 64 | 65 | ## librg_world_userdata_set 66 | 67 | Method allows you to store a custom peice of data (pointer to data) in the current world container. 68 | As soon as it is set, the data will be kept, until the world will be destroyed, or data will be overwritten by another call to this method. 69 | 70 | Existing data can be fetched using [librg_world_userdata_get](#librg_world_userdata_get) method. 71 | 72 | ##### Signature 73 | ```c 74 | int8_t librg_world_userdata_set( 75 | librg_world *world, 76 | void *data 77 | ) 78 | ``` 79 | 80 | ##### Returns 81 | 82 | * In case of success return code is `LIBRG_OK` (defined as `0`) 83 | * In case of error return code is `LIBRG_WORLD_INVALID` 84 | 85 | ------------------------------- 86 | 87 | ## librg_world_userdata_get 88 | 89 | Method can be used to fetch currently stored data in the world, that was previously pushed there by [librg_world_userdata_set](#librg_world_userdata_set) method. 90 | If no data was ever pushed, the default value will be `NULL`. 91 | 92 | ##### Signature 93 | ```c 94 | void * librg_world_userdata_get( 95 | librg_world *world 96 | ) 97 | ``` 98 | 99 | ##### Returns 100 | 101 | * In case of success returned value is the data 102 | * In all other cases returned value is `NULL` 103 | 104 | ------------------------------- 105 | 106 | ## librg_version 107 | 108 | Method returns current library version in form of an integer. Can be used to compare against different librg versions. 109 | 110 | ##### Signature 111 | ```c 112 | int32_t librg_version() 113 | ``` 114 | 115 | ##### Returns 116 | 117 | * Integer representing current version 118 | 119 | ------------------------------- 120 | 121 | ## **Examples** 122 | 123 | A general example that shows how all methods above can be used together 124 | 125 | [example-world.c](https://raw.githubusercontent.com/zpl-c/librg/master/code/apps/example-world.c ':include :type=code') 126 | 127 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | librg - Single-header cross-platform world replication library in pure C99 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 38 |
39 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /docs/migration.md: -------------------------------------------------------------------------------- 1 | # Migration guide 2 | 3 | There were multiple major changes, to the library coming from version `5.*.*` to version `6.0.0`. 4 | This page attempts to summirize most of them so that existing programs could be migrated much more quickly. 5 | 6 | Biggest change coming from an older version, is obvious lack of any methods related to the networking. 7 | Decoupling the library from the netwokring part allows it to have a much better focus on what it was and is supposed to do. 8 | Additionally, that change now makes it possible to use any other networking library, stack or even approach, since it is working with much simpler primitives. 9 | 10 | ## Naming 11 | 12 | 1. `librg_ctx` -> `librg_world` What used to be known as context, now is named a world. 13 | 2. `librg_client` -> `owner` Since library does not handle networking, a client was an inappropriate name for it. 14 | 3. `entity controller` -> `entity owner` That name better represents the relation of the owner to an entity 15 | 4. `stream range` -> `view radius` Still describes a radius around an entity, but under a more appropriate name. 16 | 5. `librg_entity_create` -> `librg_entity_track` A changed name, that attempts to show that entities are coming from the host application, and the library is actually only "tracking" them. 17 | 5. `librg_entity_destroy` -> `librg_entity_untrack` Same point as in explanation above. 18 | 5. All entity run-time events where essentially split onto 2 main categories, `write`-related and `read`-related: 19 | * `LIBRG_WRITE_CREATE` 20 | * `LIBRG_WRITE_UPDATE` 21 | * `LIBRG_WRITE_REMOVE` 22 | * `LIBRG_READ_CREATE` 23 | * `LIBRG_READ_UPDATE` 24 | * `LIBRG_READ_REMOVE` 25 | 26 | ## Structures 27 | 28 | After version `6.0.0` all "public" structures were moved to an hidden internal part of the code, and outside those are provided only as a set of opaque pointers. 29 | Everything else is provided via funcitons/methods, which use only primitive standart types, with an exception of user-provided buffers for arrays. 30 | That approach should make it much easier to use the library with bindings for different languages, since no structures need to be "re-created" on the binding side. 31 | 32 | ## Positioning 33 | 34 | Older version of the library havily relied on a vector based real positions to manage the visibility and distance checking. 35 | Additionally internally it operated based on a dynamically allocated, variable-sized sets of structures that were used for culling. 36 | 37 | Current version simplifies this approach a lot, by getting rid of actual positions, and rather relying on the integer based chunk identifiers. 38 | And all distance checks are replaced with a simple integer comparation. 39 | That approach reduces complexity, and should result in much less posibilities for edge cases and bugs occuring in the run-time. 40 | 41 | ## Querying 42 | 43 | Previous version relied on `iteration` based methods to go over all list of the entities. 44 | Current version is based on user-provided buffers where the entities are copied to, 45 | alongside the total amount of data that was written. 46 | 47 | ## Buffers and data 48 | 49 | Older version relied heavily on `librg_data` structure, and methods for writing and reading various types of data primitives. 50 | This was dropped to reduce overall complexity of the library, and allowing users to actually rely on existing set of libraries that do that specific job and do it very well: 51 | 52 | * [protobuf](https://developers.google.com/protocol-buffers/) 53 | * [flatbuffers](https://google.github.io/flatbuffers/) 54 | * [msgpack](https://msgpack.org/) 55 | * [cap'n'proto](https://capnproto.org/) 56 | * any other serialization library not in that list 57 | * or good old `memcpy` :) 58 | 59 | ## Networking integration 60 | 61 | Since networking part of the library was dropped, the new overall interface of the library was made in such a way that majority of the networking libraries could be supported. 62 | 63 | All you would need to have from a library is: 64 | 65 | 1. Ability to send and receive a `char *` buffer 66 | 2. Ability to read or set that buffer size 67 | 3. Ability to indentify who is the receiver or sender of the data with an integer id 68 | 69 | And that is pretty much it! 70 | 71 | Just a theoretical list of what kind of libraries should be supported: 72 | 73 | * `ENet` 74 | * `yojimbo` 75 | * `Raknet/SLikeNet` 76 | * `Websocket` 77 | * `WebRTC` 78 | * Any other `UDP` or `TCP` based library 79 | 80 | > Note: you can check an example for network [integration for enet](https://github.com/zpl-c/librg/blob/master/code/apps/example-enet.c). 81 | 82 | ## Other 83 | 84 | This section will be expanded with additional questions and answers coming from the users. 85 | 86 | Feel free to do the same, by opening an [issue](https://github.com/zpl-c/librg/issues) or asking us directly in our [discord](https://discord.gg/2fZVEym). 87 | -------------------------------------------------------------------------------- /docs/quickstart.md: -------------------------------------------------------------------------------- 1 | # Quick start 2 | 3 | A simple guide with insturction on how to setup, configure, compile and use the library. 4 | 5 | ## Downloading 6 | 7 | First thing you need to do is to get/install the library, and the easiest way of doing that would be downloading it from our releases section 8 | 9 | ```sh 10 | $ wget https://github.com/zpl-c/librg/releases/latest/download/librg.h 11 | ``` 12 | 13 | or via shorter link: 14 | 15 | ```sh 16 | $ wget http://zpl.pw/fetch/librg 17 | ``` 18 | 19 | Alternatively, you can just visit [releases](https://github.com/zpl-c/librg/releases/) page, and grab a release manually. 20 | 21 | ## Installation 22 | 23 | To add `librg` to your project, just include it. 24 | 25 | ```c 26 | #include "librg.h" 27 | ``` 28 | 29 | This, however, will include only a **header** section of the library. 30 | And if you try to compile your project, linker will eventually tell you that you lack implemntation methods for bunch of functions. 31 | That happens because the implemntation part of the library is never compiled. 32 | 33 | To fix that, choose one of your source files (any `*.c` or `*.cpp` file), and define there a macro `LIBRG_IMPL` before including the library, just like so: 34 | 35 | ```c 36 | #define LIBRG_IMPL 37 | #include "librg.h" 38 | ``` 39 | 40 | That will cause your compiler to include the source part of the library and compile it alonside your application. 41 | Additionally, to keep library code and your code fully separated at compile time, you can create a special source file, consisting only of 2 lines of code shown above. 42 | 43 | 44 | ## First world 45 | 46 | This small example shows how can you create your first world, confgure it, add an entity, and use a querying and buffer packing methods. 47 | 48 | [example-simple.c](https://raw.githubusercontent.com/zpl-c/librg/master/code/apps/example-simple.c ':include :type=code') 49 | -------------------------------------------------------------------------------- /misc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(librg) 3 | 4 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 5 | include_directories("../code/") 6 | 7 | set(apps_dir "../code/apps/") 8 | file(GLOB apps ${apps_dir}/*) 9 | 10 | foreach(apps_source ${apps}) 11 | get_filename_component(apps_name ${apps_source} NAME) 12 | string(REPLACE ".c" "${OUTPUT_EXT}" apps_name ${apps_name}) 13 | 14 | # Setup the apps 15 | add_executable(${apps_name} ${apps_source}) 16 | endforeach() 17 | 18 | if (EMSCRIPTEN) 19 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g") 20 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -g --profiling -s ASSERTIONS=1 -s WASM=1 -s INITIAL_MEMORY=268435456 -s FORCE_FILESYSTEM=1") 21 | set(CMAKE_COMPILE_WARNING_AS_ERROR OFF) 22 | endif () 23 | 24 | add_executable(unit ../code/tests/unit.c) 25 | -------------------------------------------------------------------------------- /misc/embed.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd $(dirname $0) 4 | node -e 'require("./packager.js").embedIncludes(true)' 5 | -------------------------------------------------------------------------------- /misc/packager.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const {Plugin} = require('release-it') 4 | 5 | const basefile = path.join(__dirname, '..', 'code', 'librg.h') 6 | const workdir = path.join(__dirname, 'deploy') 7 | 8 | const versionGet = () => { 9 | const data = fs.readFileSync(basefile, 'utf8') 10 | 11 | const major = data.match(/LIBRG_VERSION_MAJOR\s+([0-9]+)\n/)[1] 12 | const minor = data.match(/LIBRG_VERSION_MINOR\s+([0-9]+)\n/)[1] 13 | const patch = data.match(/LIBRG_VERSION_PATCH\s+([0-9]+)\n/)[1] 14 | const pre = data.match(/LIBRG_VERSION_PRE\s+\"([\.a-z0-9]+)\"\n/) 15 | 16 | return `${major}.${minor}.${patch}${pre ? '-' + pre[1] : ''}` 17 | } 18 | 19 | const versionSet = (version) => { 20 | let data = fs.readFileSync(basefile, 'utf8') 21 | 22 | let [base, pre] = version.split('-') 23 | let [major, minor, patch] = base.split('.').map(a => parseInt(a)) 24 | 25 | if (!pre) pre = '' 26 | 27 | data = data.replace(/LIBRG_VERSION_MAJOR\s+([0-9]+)\n/, `LIBRG_VERSION_MAJOR ${major}\n`) 28 | data = data.replace(/LIBRG_VERSION_MINOR\s+([0-9]+)\n/, `LIBRG_VERSION_MINOR ${minor}\n`) 29 | data = data.replace(/LIBRG_VERSION_PATCH\s+([0-9]+)\n/, `LIBRG_VERSION_PATCH ${patch}\n`) 30 | data = data.replace(/LIBRG_VERSION_PRE\s+\"([\.0-9a-z]+)\"\n/, `LIBRG_VERSION_PRE "${pre}"\n`) 31 | 32 | fs.writeFileSync(basefile, data) 33 | } 34 | 35 | const embedIncludes = (print) => { 36 | if (!fs.existsSync(workdir)) fs.mkdirSync(workdir) 37 | 38 | let data = fs.readFileSync(basefile, 'utf8') 39 | let lines = data.split('\n') 40 | 41 | const hedley = lines.find(a => a.indexOf('librg_hedley.h') !== -1) 42 | const hedleyIndex = lines.indexOf(hedley) 43 | 44 | lines = lines.map((line, i) => { 45 | if (i < hedleyIndex) return line 46 | if (line.indexOf('#include') === -1) return line 47 | if (line.indexOf('<') !== -1) return line 48 | 49 | const parts = line.split('#include') 50 | const spaces = parts[0] 51 | const filename = parts[1].trim().replace(/"/g, '') 52 | 53 | const content = fs 54 | .readFileSync(path.join(__dirname, '..', 'code', filename), 'utf8') 55 | .split('\n') 56 | .map(l => spaces + l) 57 | .map(l => l === spaces ? '' : l) 58 | .join('\n') 59 | .replace(/\s+$/g, '') 60 | 61 | return content 62 | }) 63 | 64 | const code = lines.join('\n') 65 | if (print) console.log(code) 66 | else fs.writeFileSync(path.join(workdir, 'librg.h'), code) 67 | } 68 | 69 | class Bumper extends Plugin { 70 | getLatestVersion() { 71 | return versionGet() 72 | } 73 | 74 | bump(version) { 75 | this.version = version; 76 | versionSet(version) 77 | } 78 | 79 | async beforeRelease() { 80 | embedIncludes() 81 | console.log('done') 82 | } 83 | 84 | afterRelease() { 85 | if (fs.existsSync(path.join(workdir, 'librg.h'))) { 86 | fs.unlinkSync(path.join(workdir, 'librg.h')) 87 | } 88 | } 89 | } 90 | 91 | module.exports = Bumper 92 | module.exports.embedIncludes = embedIncludes 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "librg.c", 3 | "version": "7.4.0", 4 | "author": "Inlife", 5 | "description": "Pure C library for building simple and elegant cross-platform multiplayer client-server solutions.", 6 | "homepage": "https://github.com/zpl-c/librg#readme", 7 | "license": "Apache-2.0", 8 | "main": "code/librg.h", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/zpl-c/librg.git" 12 | }, 13 | "scripts": { 14 | "docs-serve": "docsify serve ./docs", 15 | "embed": "node -e 'require(\"./misc/packager\").embedIncludes()'", 16 | "release": "release-it", 17 | "release-patch": "release-it patch --ci", 18 | "release-minor": "release-it minor --ci", 19 | "release-major": "release-it major --ci", 20 | "release-patch-pre": "release-it patch --preRelease=pre --ci", 21 | "release-minor-pre": "release-it minor --preRelease=pre --ci", 22 | "release-major-pre": "release-it major --preRelease=pre --ci" 23 | }, 24 | "devDependencies": { 25 | "docsify-cli": "^4.4.0", 26 | "release-it": "^13.6.1" 27 | }, 28 | "release-it": { 29 | "npm": { 30 | "publish": true 31 | }, 32 | "github": { 33 | "release": true, 34 | "assets": [ 35 | "misc/deploy/librg.h" 36 | ] 37 | }, 38 | "plugins": { 39 | "./misc/packager.js": {} 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /web/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xe 4 | 5 | cmake --build build_web --target clean 6 | -------------------------------------------------------------------------------- /web/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xe 4 | 5 | # Setup emsdk 6 | if [ ! -d "emsdk" ]; then 7 | wget https://github.com/emscripten-core/emsdk/archive/refs/heads/main.zip -O emscripten.zip 8 | unzip emscripten.zip 9 | mv emsdk-main emsdk 10 | rm -rf emscripten.zip 11 | fi 12 | 13 | source ./emsdk/emsdk_env.sh 14 | emsdk update 15 | emsdk install latest 16 | emsdk activate latest 17 | source ./emsdk/emsdk_env.sh 18 | 19 | # Setup web build 20 | emcmake cmake -S misc -B build_web -DCMAKE_BUILD_TYPE=Release 21 | --------------------------------------------------------------------------------