├── LICENSE ├── README.md └── netdynamics.c /LICENSE: -------------------------------------------------------------------------------- 1 | Microsoft Public License (Ms-PL) 2 | 3 | Copyright (c) 2019 Stanislav Denisov (nxrighthere@gmail.com) 4 | 5 | This license governs use of the accompanying software. If you use the 6 | software, you accept this license. If you do not accept the license, do not 7 | use the software. 8 | 9 | 1. Definitions 10 | The terms "reproduce," "reproduction," "derivative works," and "distribution" 11 | have the same meaning here as under U.S. copyright law. A "contribution" is 12 | the original software, or any additions or changes to the software. A 13 | "contributor" is any person that distributes its contribution under this 14 | license. "Licensed patents" are a contributor's patent claims that read 15 | directly on its contribution. 16 | 17 | 2. Grant of Rights 18 | (A) Copyright Grant- Subject to the terms of this license, including the 19 | license conditions and limitations in section 3, each contributor grants 20 | you a non-exclusive, worldwide, royalty-free copyright license to 21 | reproduce its contribution, prepare derivative works of its contribution, 22 | and distribute its contribution or any derivative works that you create. 23 | 24 | (B) Patent Grant- Subject to the terms of this license, including the 25 | license conditions and limitations in section 3, each contributor grants 26 | you a non-exclusive, worldwide, royalty-free license under its licensed 27 | patents to make, have made, use, sell, offer for sale, import, and/or 28 | otherwise dispose of its contribution in the software or derivative works 29 | of the contribution in the software. 30 | 31 | 3. Conditions and Limitations 32 | (A) No Trademark License- This license does not grant you rights to use 33 | any contributors' name, logo, or trademarks. 34 | 35 | (B) If you bring a patent claim against any contributor over patents that 36 | you claim are infringed by the software, your patent license from such 37 | contributor to the software ends automatically. 38 | 39 | (C) If you distribute any portion of the software, you must retain all 40 | copyright, patent, trademark, and attribution notices that are present in 41 | the software. 42 | 43 | (D) If you distribute any portion of the software in source code form, 44 | you may do so only under this license by including a complete copy of 45 | this license with your distribution. If you distribute any portion of the 46 | software in compiled or object code form, you may only do so under a 47 | license that complies with this license. 48 | 49 | (E) The software is licensed "as-is." You bear the risk of using it. The 50 | contributors give no express warranties, guarantees, or conditions. You 51 | may have additional consumer rights under your local laws which this 52 | license cannot change. To the extent permitted under your local laws, the 53 | contributors exclude the implied warranties of merchantability, fitness 54 | for a particular purpose and non-infringement. 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | alt logo 3 |

4 | 5 | NetDynamics is a data-oriented networking playground for the reliable UDP transports. The application was created for stress testing and debugging a proprietary networking library, but it also supports [ENet](https://github.com/nxrighthere/ENet-CSharp) as an open-source alternative. 6 | 7 |

8 | 9 |

10 | 11 | Purpose 12 | -------- 13 | NetDynamics allows to spawn up to 100,000 dynamic entities, efficiently process data, and render graphics using draw call batching. The application generates a huge amount of data for transfer over a network or on loopback using UDP transport that supports sequenced reliable/unreliable message delivery. The primary goal is to determine problematic spots, bottlenecks, or bugs in a network transport and visualize it in real-time. 14 | 15 | How it works? 16 | -------- 17 | The overall approach is based on the Entity Component System where an entity is just an identifier which decoupled from data and logic. NetDynamics is a client-server application which synchronizes visual representation of entities across connections. The server is serializing and transmitting to clients large batches of components that essentially are entity's data. The systems are used for logic and to process components for designated entities. 18 | 19 | The server has full authority over all entities, clients can only participate in the population of a world by sending an appropriate message. The server can spawn entities as well, and also it can destroy them locally with further synchronization across clients. The server is sending state updates for entities at a fixed interval (20 updates per second by default). Clients are using interpolation to replicate the fluent movement of entities between state updates based on the position and speed components. Extrapolation is not implemented so packet loss will be noticeable. 20 | 21 | The application is designed to generate traffic exponentially with hundreds of thousands of network messages. It's not multi-threaded intentionally to notice performance degradation of the main thread when a network transport is under high-load, thus a single-threaded transport will always perform with higher latencies depending on the application's framerate. Moving transport logic to a separate dedicated thread or making it framerate independent in any other way will solve this, but it's beyond the purpose of NetDynamics. 22 | 23 | Usage 24 | -------- 25 | [Download](https://github.com/nxrighthere/NetDynamics/releases) the application and set the desired parameters in the `settings.ini` file. Run the application, use the left mouse button on server or client to spawn entities, use the right mouse button on server to destroy entities. 26 | 27 | For testing an initial application's rendering and processing performance to get a visual difference in consumption of a frame time by networking logic, you can simply spawn entities on server without any connections. 28 | -------------------------------------------------------------------------------- /netdynamics.c: -------------------------------------------------------------------------------- 1 | /****************************** Module Header ******************************\ 2 | * Module Name: netdynamics.c 3 | * Project: NetDynamics 4 | * Copyright (c) 2019 Stanislav Denisov 5 | * 6 | * Data-oriented networking playground for the reliable UDP transports 7 | * 8 | * This source is subject to the Microsoft Public License. 9 | * See https://opensource.org/licenses/MS-PL 10 | * All other rights reserved. 11 | * 12 | * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 13 | * EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 14 | * WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. 15 | \***************************************************************************/ 16 | 17 | #include 18 | #include "aws/common/clock.h" // https://github.com/awslabs/aws-c-common 19 | #include "aws/common/thread.h" 20 | #include "jemalloc/jemalloc.h" // https://github.com/jemalloc/jemalloc 21 | #include "raylib/raylib.h" // https://github.com/raysan5/raylib 22 | #include "enet/enet.h" // https://github.com/nxrighthere/ENet-CSharp 23 | #include "binn/binn.h" // https://github.com/liteserver/binn 24 | #include "ini/ini.h" // https://github.com/benhoyt/inih 25 | 26 | #define VERSION_MAJOR 1 27 | #define VERSION_MINOR 0 28 | #define VERSION_PATCH 9 29 | 30 | #define NET_TRANSPORT_HYPERNET 0 31 | #define NET_TRANSPORT_ENET 1 32 | 33 | #define NET_MAX_CLIENTS 32 34 | #define NET_MAX_CHANNELS 2 35 | #define NET_MAX_ENTITIES 100000 36 | #define NET_MAX_ENTITY_SPAWN 10 37 | #define NET_MAX_ENTITY_SPEED 80.0f 38 | 39 | #define NET_MESSAGE_SPAWN 0xA 40 | #define NET_MESSAGE_MOVE 0xB 41 | #define NET_MESSAGE_DESTROY 0xC 42 | 43 | typedef struct _Settings { 44 | uint8_t headlessMode; 45 | uint16_t resolutionWidth; 46 | uint16_t resolutionHeight; 47 | uint8_t framerateLimit; 48 | uint8_t vsync; 49 | uint8_t transport; 50 | char* ip; 51 | uint16_t port; 52 | uint8_t sendRate; 53 | uint32_t redundantBytes; 54 | } Settings; 55 | 56 | static uint8_t redundancyBuffer[1024 * 1024]; 57 | 58 | static const Color colors[] = { 59 | { 250, 250, 250, 255 }, 60 | { 255, 0, 90, 255 }, 61 | { 94, 8, 255, 255 }, 62 | { 0, 80, 255, 255 }, 63 | { 0, 220, 255, 255 }, 64 | { 255, 255, 14, 255 } 65 | }; 66 | 67 | static Settings settings; 68 | 69 | static Font font; 70 | static const int fontSize = 25; 71 | static const int textureWidth = 32; 72 | static const int textureHeight = 32; 73 | 74 | static const char* status; 75 | static const char* error; 76 | 77 | #ifdef NETDYNAMICS_CLIENT 78 | static bool connected; 79 | static float worstLag; 80 | #elif NETDYNAMICS_SERVER 81 | static uint32_t connected; 82 | #endif 83 | 84 | // ENet 85 | 86 | static ENetHost* enetHost; 87 | static ENetPeer* enetPeer; 88 | 89 | // Strings 90 | 91 | static const char* string_listening = "Listening for connections"; 92 | static const char* string_connecting = "Connecting to server..."; 93 | static const char* string_connected = "Connected to server"; 94 | static const char* string_disconnected = "Disconnected from server"; 95 | static const char* string_server_failed = "Server creation failed"; 96 | static const char* string_client_failed = "Client creation failed"; 97 | static const char* string_host_failed = "Host creation failed"; 98 | static const char* string_address_failed = "Address assignment failed"; 99 | static const char* string_listening_failed = "Server listening failed"; 100 | static const char* string_connection_failed = "Connection failed"; 101 | 102 | // Entities 103 | 104 | typedef uint32_t Entity; 105 | 106 | static Entity entity; 107 | 108 | // Components 109 | 110 | static Vector2* position; 111 | static Vector2* speed; 112 | static Color* color; 113 | static Texture2D texture; 114 | static Vector2* destination; 115 | 116 | // Systems 117 | 118 | #define ENTITIES_EXIST() color[0].a != 0 119 | 120 | #ifdef NETDYNAMICS_SERVER 121 | inline static void entity_spawn(Vector2 positionComponent, uint32_t quantity) { 122 | uint32_t entities = entity + quantity; 123 | 124 | for (uint32_t i = entity; i < entities; i++) { 125 | if (entity < NET_MAX_ENTITIES) { 126 | entity++; 127 | position[i] = positionComponent; 128 | speed[i] = (Vector2){ (float)RayGetRandomValue(-300, 300) / 60.0f, (float)RayGetRandomValue(-300, 300) / 60.0f }; 129 | color[i] = colors[RayGetRandomValue(0, sizeof(colors) / sizeof(Color) - 1)]; 130 | } 131 | } 132 | } 133 | 134 | inline static void entity_move(float movementSpeed, float deltaTime) { 135 | #define TEXTURE_OFFSET 8 136 | 137 | for (uint32_t i = 0; i < entity; i++) { 138 | position[i].x += speed[i].x * movementSpeed * deltaTime; 139 | position[i].y += speed[i].y * movementSpeed * deltaTime; 140 | 141 | if (((position[i].x + textureWidth / 2 + TEXTURE_OFFSET) > settings.resolutionWidth) || ((position[i].x + textureWidth / 2 - TEXTURE_OFFSET) < 0)) 142 | speed[i].x *= -1; 143 | 144 | if (((position[i].y + textureHeight / 2 + TEXTURE_OFFSET) > settings.resolutionHeight) || ((position[i].y + textureHeight / 2 - TEXTURE_OFFSET) < 0)) 145 | speed[i].y *= -1; 146 | } 147 | } 148 | 149 | inline static void entity_destroy(Entity entityLocal) { 150 | entity = entityLocal; 151 | color[entityLocal].a = 0; 152 | } 153 | #elif NETDYNAMICS_CLIENT 154 | inline static void entity_spawn(Entity entityRemote, Vector2 positionComponent, Vector2 speedComponent, Color colorComponent) { 155 | entity = entityRemote; 156 | position[entityRemote] = positionComponent; 157 | speed[entityRemote] = speedComponent; 158 | color[entityRemote] = colorComponent; 159 | } 160 | 161 | inline static void entity_move(Vector2* positionComponent, Vector2 destinationComponent, float maxDistanceDelta, float movementSpeed, float deltaTime) { 162 | float toVectorX = destinationComponent.x - positionComponent->x; 163 | float toVectorY = destinationComponent.y - positionComponent->y; 164 | float squareDistance = toVectorX * toVectorX + toVectorY * toVectorY; 165 | float step = maxDistanceDelta * movementSpeed * deltaTime; 166 | 167 | if (squareDistance == 0.0f || (step >= 0.0f && squareDistance <= step * step)) { 168 | *positionComponent = destinationComponent; 169 | 170 | return; 171 | } 172 | 173 | float distance = sqrtf(squareDistance); 174 | 175 | *positionComponent = (Vector2){ positionComponent->x + toVectorX / distance * step, positionComponent->y + toVectorY / distance * step }; 176 | } 177 | 178 | inline static void entity_update(Entity entityRemote, Vector2 positionComponent, Vector2 speedComponent) { 179 | destination[entityRemote] = positionComponent; 180 | speed[entityRemote] = speedComponent; 181 | } 182 | 183 | inline static void entity_destroy(Entity entityRemote) { 184 | entity = entityRemote; 185 | color[entityRemote].a = 0; 186 | } 187 | 188 | inline static void entity_flush(void) { 189 | entity = 0; 190 | memset(position, 0, sizeof(Vector2)); 191 | memset(speed, 0, sizeof(Vector2)); 192 | memset(color, 0, sizeof(Color)); 193 | memset(destination, 0, sizeof(Vector2)); 194 | } 195 | #endif 196 | 197 | #ifdef NETDYNAMICS_SERVER 198 | inline static void message_send_to_all(uint8_t transport, uint8_t id, const Entity* entityLocal) { 199 | bool reliable = false; 200 | binn* data = binn_list(); 201 | 202 | binn_list_add_uint8(data, id); 203 | 204 | if (id == NET_MESSAGE_SPAWN) { 205 | reliable = true; 206 | 207 | binn_list_add_uint32(data, *entityLocal); 208 | binn_list_add_float(data, position[*entityLocal].x); 209 | binn_list_add_float(data, position[*entityLocal].y); 210 | binn_list_add_float(data, speed[*entityLocal].x); 211 | binn_list_add_float(data, speed[*entityLocal].y); 212 | binn_list_add_uint8(data, color[*entityLocal].r); 213 | binn_list_add_uint8(data, color[*entityLocal].g); 214 | binn_list_add_uint8(data, color[*entityLocal].b); 215 | } else if (id == NET_MESSAGE_MOVE) { 216 | reliable = false; 217 | 218 | binn_list_add_uint32(data, *entityLocal); 219 | binn_list_add_float(data, position[*entityLocal].x); 220 | binn_list_add_float(data, position[*entityLocal].y); 221 | binn_list_add_float(data, speed[*entityLocal].x); 222 | binn_list_add_float(data, speed[*entityLocal].y); 223 | } else if (id == NET_MESSAGE_DESTROY) { 224 | reliable = true; 225 | 226 | binn_list_add_uint32(data, *entityLocal); 227 | } else { 228 | goto escape; 229 | } 230 | 231 | if (settings.redundantBytes > 0) 232 | binn_list_add_blob(data, redundancyBuffer, settings.redundantBytes); 233 | 234 | if (transport == NET_TRANSPORT_HYPERNET) { 235 | 236 | } else if (transport == NET_TRANSPORT_ENET) { 237 | ENetPacket* packet = enet_packet_create(binn_ptr(data), binn_size(data), !reliable ? ENET_PACKET_FLAG_NONE : ENET_PACKET_FLAG_RELIABLE); 238 | 239 | enet_host_broadcast(enetHost, 1, packet); 240 | } 241 | 242 | escape: 243 | 244 | binn_free(data); 245 | } 246 | #endif 247 | 248 | inline static void message_send(uint8_t transport, void* client, uint8_t id, const Entity* entityLocal) { 249 | bool reliable = false; 250 | binn* data = binn_list(); 251 | 252 | binn_list_add_uint8(data, id); 253 | 254 | if (id == NET_MESSAGE_SPAWN) { 255 | reliable = true; 256 | 257 | #ifdef NETDYNAMICS_SERVER 258 | binn_list_add_uint32(data, *entityLocal); 259 | binn_list_add_float(data, position[*entityLocal].x); 260 | binn_list_add_float(data, position[*entityLocal].y); 261 | binn_list_add_float(data, speed[*entityLocal].x); 262 | binn_list_add_float(data, speed[*entityLocal].y); 263 | binn_list_add_uint8(data, color[*entityLocal].r); 264 | binn_list_add_uint8(data, color[*entityLocal].g); 265 | binn_list_add_uint8(data, color[*entityLocal].b); 266 | #elif NETDYNAMICS_CLIENT 267 | Vector2 mousePosition = RayGetMousePosition(); 268 | 269 | binn_list_add_float(data, mousePosition.x); 270 | binn_list_add_float(data, mousePosition.y); 271 | #endif 272 | } else { 273 | goto escape; 274 | } 275 | 276 | if (settings.redundantBytes > 0) 277 | binn_list_add_blob(data, redundancyBuffer, settings.redundantBytes); 278 | 279 | if (transport == NET_TRANSPORT_HYPERNET) { 280 | 281 | } else if (transport == NET_TRANSPORT_ENET) { 282 | ENetPacket* packet = enet_packet_create(binn_ptr(data), binn_size(data), !reliable ? ENET_PACKET_FLAG_NONE : ENET_PACKET_FLAG_RELIABLE); 283 | 284 | enet_peer_send((ENetPeer*)client, 1, packet); 285 | } 286 | 287 | escape: 288 | 289 | binn_free(data); 290 | } 291 | 292 | inline static uint8_t message_receive(char* packet) { 293 | binn* data = binn_open(packet); 294 | uint8_t id = binn_list_uint8(data, 1); 295 | 296 | if (id == NET_MESSAGE_SPAWN) { 297 | #ifdef NETDYNAMICS_SERVER 298 | entity_spawn((Vector2){ binn_list_float(data, 2), binn_list_float(data, 3) }, NET_MAX_ENTITY_SPAWN); 299 | #elif NETDYNAMICS_CLIENT 300 | entity_spawn((Entity)binn_list_uint32(data, 2), (Vector2){ binn_list_float(data, 3), binn_list_float(data, 4) }, (Vector2){ binn_list_float(data, 5), binn_list_float(data, 6) }, (Color){ binn_list_uint8(data, 7), binn_list_uint8(data, 8), binn_list_uint8(data, 9), 255 }); 301 | #endif 302 | } else if (id == NET_MESSAGE_MOVE) { 303 | #ifdef NETDYNAMICS_CLIENT 304 | static uint64_t currentLag; 305 | static uint64_t lastLag; 306 | 307 | if (aws_high_res_clock_get_ticks(¤tLag) == AWS_OP_SUCCESS) { 308 | if (lastLag > 0) { 309 | float lag = ((currentLag - lastLag) / 1000000.0f) / 1000.0f; 310 | 311 | if (lag > worstLag) 312 | worstLag = lag; 313 | } 314 | 315 | lastLag = currentLag; 316 | } 317 | 318 | entity_update((Entity)binn_list_uint32(data, 2), (Vector2){ binn_list_float(data, 3), binn_list_float(data, 4) }, (Vector2){ binn_list_float(data, 5), binn_list_float(data, 6) }); 319 | #endif 320 | } else if (id == NET_MESSAGE_DESTROY) { 321 | #ifdef NETDYNAMICS_CLIENT 322 | entity_destroy((Entity)binn_list_uint32(data, 2)); 323 | #endif 324 | } 325 | 326 | binn_free(data); 327 | 328 | return id; 329 | } 330 | 331 | inline static float get_frame_time(void) { 332 | if (!settings.headlessMode) 333 | return RayGetFrameTime(); 334 | 335 | static uint64_t currentTime; 336 | static uint64_t lastTime; 337 | static float deltaTime; 338 | 339 | if (aws_high_res_clock_get_ticks(¤tTime) == AWS_OP_SUCCESS) { 340 | if (lastTime > 0) 341 | deltaTime = ((currentTime - lastTime) / 1000000.0f) / 1000.0f; 342 | 343 | lastTime = currentTime; 344 | } 345 | 346 | return deltaTime; 347 | } 348 | 349 | // Callbacks 350 | 351 | static int ini_callback(void* data, const char* section, const char* name, const char* value) { 352 | #define FIELD_MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0 353 | #define PARSE_INTEGER(v) strtoul(v, NULL, 10) 354 | #define PARSE_STRING(v) strdup(v) 355 | 356 | Settings* settings = (Settings*)data; 357 | 358 | if (FIELD_MATCH("Display", "HeadlessMode")) 359 | settings->headlessMode = (uint8_t)PARSE_INTEGER(value); 360 | else if (FIELD_MATCH("Display", "ResolutionWidth")) 361 | settings->resolutionWidth = (uint16_t)PARSE_INTEGER(value); 362 | else if (FIELD_MATCH("Display", "ResolutionHeight")) 363 | settings->resolutionHeight = (uint16_t)PARSE_INTEGER(value); 364 | else if (FIELD_MATCH("Renderer", "FramerateLimit")) 365 | settings->framerateLimit = (uint8_t)PARSE_INTEGER(value); 366 | else if (FIELD_MATCH("Renderer", "VSync")) 367 | settings->vsync = (uint8_t)PARSE_INTEGER(value); 368 | else if (FIELD_MATCH("Network", "Transport")) 369 | settings->transport = (uint8_t)PARSE_INTEGER(value); 370 | else if (FIELD_MATCH("Network", "IP")) 371 | settings->ip = PARSE_STRING(value); 372 | else if (FIELD_MATCH("Network", "Port")) 373 | settings->port = (uint16_t)PARSE_INTEGER(value); 374 | else if (FIELD_MATCH("Network", "SendRate")) 375 | settings->sendRate = (uint8_t)PARSE_INTEGER(value); 376 | else if (FIELD_MATCH("Network", "RedundantBytes")) 377 | settings->redundantBytes = (uint32_t)PARSE_INTEGER(value); 378 | else 379 | return 0; 380 | 381 | return 1; 382 | } 383 | 384 | static uint64_t checksum_callback(const ENetBuffer* buffers, int bufferCount) { 385 | return enet_crc64(buffers, bufferCount); 386 | } 387 | 388 | int main(void) { 389 | // Settings 390 | 391 | if (ini_parse("settings.ini", ini_callback, &settings) < 0) 392 | abort(); 393 | 394 | // Main 395 | 396 | char* title = NULL; 397 | 398 | #ifdef NETDYNAMICS_SERVER 399 | title = "NetDynamics (Server)"; 400 | #elif NETDYNAMICS_CLIENT 401 | title = "NetDynamics (Client)"; 402 | 403 | settings.headlessMode = 0; 404 | #endif 405 | 406 | if (settings.headlessMode) { 407 | static struct aws_allocator allocator; 408 | 409 | aws_common_library_init(&allocator); 410 | } else { 411 | if (settings.vsync > 0) 412 | RaySetConfigFlags(FLAG_VSYNC_HINT); 413 | 414 | RayInitWindow((int)settings.resolutionWidth, (int)settings.resolutionHeight, title); 415 | RaySetTargetFPS((int)settings.framerateLimit); 416 | 417 | font = RayLoadFontEx("share_tech.ttf", fontSize, 0, 0); 418 | 419 | RaySetTextureFilter(font.texture, FILTER_POINT); 420 | } 421 | 422 | // Serialization 423 | 424 | binn_set_alloc_functions(je_malloc, je_realloc, je_free); 425 | 426 | if (settings.redundantBytes > 0) { 427 | if (settings.redundantBytes > sizeof(redundancyBuffer)) 428 | settings.redundantBytes = (uint32_t)sizeof(redundancyBuffer); 429 | 430 | for (uint32_t i = 0; i < settings.redundantBytes; i++) { 431 | redundancyBuffer[i] = i % sizeof(uint8_t); 432 | } 433 | } 434 | 435 | // Network 436 | 437 | char* name = NULL; 438 | 439 | if (settings.transport == NET_TRANSPORT_HYPERNET) { 440 | name = "HyperNet"; 441 | 442 | 443 | } else if (settings.transport == NET_TRANSPORT_ENET) { 444 | name = "ENet"; 445 | 446 | ENetCallbacks callbacks = { 447 | je_malloc, 448 | je_free, 449 | abort 450 | }; 451 | 452 | if (enet_initialize_with_callbacks(enet_linked_version(), &callbacks) < 0) { 453 | error = "ENet initialization failed"; 454 | } else { 455 | ENetAddress address = { 0 }; 456 | 457 | address.port = settings.port; 458 | 459 | #ifdef NETDYNAMICS_SERVER 460 | if ((enetHost = enet_host_create(&address, NET_MAX_CLIENTS, NET_MAX_CHANNELS, 0, 0, 1024 * 1024)) == NULL) 461 | error = string_host_failed; 462 | else 463 | status = string_listening; 464 | #elif NETDYNAMICS_CLIENT 465 | if (enet_address_set_hostname(&address, settings.ip) < 0) { 466 | error = string_address_failed; 467 | } else { 468 | if ((enetHost = enet_host_create(NULL, 1, 0, 0, 0, 1024 * 1024)) == NULL) { 469 | error = string_host_failed; 470 | } else { 471 | if ((enetPeer = enet_host_connect(enetHost, &address, NET_MAX_CHANNELS, 0)) == NULL) 472 | error = string_connection_failed; 473 | else 474 | status = string_connecting; 475 | } 476 | } 477 | #endif 478 | 479 | if (enetHost != NULL) { 480 | enet_host_set_checksum_callback(enetHost, checksum_callback); 481 | } 482 | } 483 | } else { 484 | error = "Set the correct number of a network transport"; 485 | } 486 | 487 | free(settings.ip); 488 | 489 | // Data 490 | 491 | if (error == NULL) { 492 | position = (Vector2*)je_calloc(NET_MAX_ENTITIES, sizeof(Vector2)); 493 | speed = (Vector2*)je_calloc(NET_MAX_ENTITIES, sizeof(Vector2)); 494 | color = (Color*)je_calloc(NET_MAX_ENTITIES, sizeof(Color)); 495 | 496 | if (!settings.headlessMode) 497 | texture = RayLoadTexture("neon_circle.png"); 498 | 499 | #ifdef NETDYNAMICS_CLIENT 500 | destination = (Vector2*)je_calloc(NET_MAX_ENTITIES, sizeof(Vector2)); 501 | #endif 502 | } 503 | 504 | #ifdef NETDYNAMICS_SERVER 505 | float sendInterval = 1.0f / settings.sendRate; 506 | #elif NETDYNAMICS_CLIENT 507 | uint32_t rtt = 0; 508 | #endif 509 | 510 | while (settings.headlessMode || !RayWindowShouldClose()) { 511 | float deltaTime = get_frame_time(); 512 | 513 | if (error == NULL) { 514 | // Transport 515 | if (settings.transport == NET_TRANSPORT_HYPERNET) { 516 | 517 | } else if (settings.transport == NET_TRANSPORT_ENET) { 518 | static ENetEvent event = { 0 }; 519 | 520 | bool polled = false; 521 | 522 | while (!polled) { 523 | if (enet_host_check_events(enetHost, &event) <= 0) { 524 | if (enet_host_service(enetHost, &event, 0) <= 0) 525 | break; 526 | 527 | polled = true; 528 | } 529 | 530 | switch (event.type) { 531 | case ENET_EVENT_TYPE_NONE: 532 | break; 533 | 534 | case ENET_EVENT_TYPE_CONNECT: { 535 | #ifdef NETDYNAMICS_SERVER 536 | connected = enetHost->connectedPeers; 537 | 538 | if (ENTITIES_EXIST()) { 539 | for (uint32_t i = 0; i <= entity; i++) { 540 | message_send(NET_TRANSPORT_ENET, event.peer, NET_MESSAGE_SPAWN, &i); 541 | } 542 | } 543 | #elif NETDYNAMICS_CLIENT 544 | connected = true; 545 | status = string_connected; 546 | #endif 547 | 548 | break; 549 | } 550 | 551 | case ENET_EVENT_TYPE_DISCONNECT: case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: { 552 | #ifdef NETDYNAMICS_SERVER 553 | connected = enetHost->connectedPeers; 554 | #elif NETDYNAMICS_CLIENT 555 | connected = false; 556 | worstLag = 0.0f; 557 | status = string_disconnected; 558 | 559 | entity_flush(); 560 | #endif 561 | 562 | break; 563 | } 564 | 565 | case ENET_EVENT_TYPE_RECEIVE: { 566 | uint8_t id = message_receive((char*)event.packet->data); 567 | 568 | #ifdef NETDYNAMICS_SERVER 569 | if (id == NET_MESSAGE_SPAWN) { 570 | for (uint32_t i = entity - NET_MAX_ENTITY_SPAWN; i <= entity; i++) { 571 | message_send_to_all(NET_TRANSPORT_ENET, NET_MESSAGE_SPAWN, &i); 572 | } 573 | } 574 | #endif 575 | 576 | enet_packet_destroy(event.packet); 577 | 578 | break; 579 | } 580 | } 581 | } 582 | 583 | #ifdef NETDYNAMICS_CLIENT 584 | rtt = enetPeer->roundTripTime; 585 | #endif 586 | } 587 | 588 | // Timer 589 | #ifdef NETDYNAMICS_SERVER 590 | static float sendTime = 0.0f; 591 | 592 | sendTime += deltaTime; 593 | #endif 594 | 595 | // Spawn 596 | if (!settings.headlessMode) { 597 | if (RayIsMouseButtonDown(MOUSE_LEFT_BUTTON) || RayIsKeyPressed(KEY_SPACE)) { 598 | #ifdef NETDYNAMICS_SERVER 599 | entity_spawn(RayGetMousePosition(), NET_MAX_ENTITY_SPAWN); 600 | 601 | if (connected > 0) { 602 | if (settings.transport == NET_TRANSPORT_HYPERNET) { 603 | 604 | } else if (settings.transport == NET_TRANSPORT_ENET) { 605 | enet_host_flush(enetHost); 606 | 607 | for (uint32_t i = entity - NET_MAX_ENTITY_SPAWN; i <= entity; i++) { 608 | message_send_to_all(NET_TRANSPORT_ENET, NET_MESSAGE_SPAWN, &i); 609 | } 610 | } 611 | } 612 | #elif NETDYNAMICS_CLIENT 613 | if (connected) { 614 | if (settings.transport == NET_TRANSPORT_HYPERNET) { 615 | 616 | } else if (settings.transport == NET_TRANSPORT_ENET) { 617 | enet_host_flush(enetHost); 618 | 619 | message_send(NET_TRANSPORT_ENET, enetPeer, NET_MESSAGE_SPAWN, NULL); 620 | } 621 | } 622 | #endif 623 | } 624 | } 625 | 626 | // Move 627 | if (ENTITIES_EXIST()) { 628 | #ifdef NETDYNAMICS_SERVER 629 | entity_move(NET_MAX_ENTITY_SPEED, deltaTime); 630 | 631 | if (connected > 0) { 632 | if (sendTime >= sendInterval) { 633 | sendTime -= sendInterval; 634 | 635 | if (settings.transport == NET_TRANSPORT_HYPERNET) { 636 | 637 | } else if (settings.transport == NET_TRANSPORT_ENET) { 638 | enet_host_flush(enetHost); 639 | 640 | for (uint32_t i = 0; i < entity; i++) { 641 | message_send_to_all(NET_TRANSPORT_ENET, NET_MESSAGE_MOVE, &i); 642 | } 643 | } 644 | } 645 | } 646 | #elif NETDYNAMICS_CLIENT 647 | for (uint32_t i = 0; i < entity; i++) { 648 | if (destination[i].x == 0.0f && destination[i].y == 0.0f) 649 | continue; 650 | 651 | entity_move(&position[i], destination[i], sqrtf(speed[i].x * speed[i].x + speed[i].y * speed[i].y), NET_MAX_ENTITY_SPEED, deltaTime); 652 | } 653 | #endif 654 | } 655 | 656 | // Destroy 657 | #ifdef NETDYNAMICS_SERVER 658 | if (!settings.headlessMode) { 659 | if (ENTITIES_EXIST()) { 660 | if (RayIsMouseButtonDown(MOUSE_RIGHT_BUTTON) || RayIsKeyPressed(KEY_BACKSPACE)) { 661 | Entity entities = entity - NET_MAX_ENTITY_SPAWN; 662 | 663 | entity_destroy(entities); 664 | 665 | if (connected > 0) { 666 | if (settings.transport == NET_TRANSPORT_HYPERNET) { 667 | 668 | } else if (settings.transport == NET_TRANSPORT_ENET) { 669 | enet_host_flush(enetHost); 670 | 671 | message_send_to_all(NET_TRANSPORT_ENET, NET_MESSAGE_DESTROY, &entities); 672 | } 673 | } 674 | } 675 | } 676 | } 677 | #endif 678 | } 679 | 680 | if (!settings.headlessMode) { 681 | // Render 682 | RayBeginDrawing(); 683 | RayClearBackground(CLITERAL{ 20, 0, 48, 255 }); 684 | 685 | if (error != NULL) { 686 | // Error 687 | RayDrawTextEx(font, RayFormatText("ERROR %s", error), (Vector2){ 10, 10 }, fontSize, 0, WHITE); 688 | } else { 689 | // Entities 690 | if (ENTITIES_EXIST()) { 691 | for (uint32_t i = 0; i < entity; i++) { 692 | RayDrawTexture(texture, position[i].x, position[i].y, color[i]); 693 | } 694 | } 695 | 696 | // Stats 697 | static int fps = 0; 698 | static int counter = 0; 699 | static int refreshRate = 20; 700 | 701 | if (counter < refreshRate) { 702 | counter++; 703 | } else { 704 | fps = RayGetFPS(); 705 | refreshRate = fps; 706 | counter = 0; 707 | } 708 | 709 | RayDrawTextEx(font, RayFormatText("FPS %i", fps), (Vector2){ 10, 10 }, fontSize, 0, WHITE); 710 | RayDrawTextEx(font, RayFormatText("ENTITIES %i", entity), (Vector2){ 10, 35 }, fontSize, 0, WHITE); 711 | RayDrawTextEx(font, name, (Vector2){ 10, 75 }, fontSize, 0, WHITE); 712 | RayDrawTextEx(font, RayFormatText("STATUS %s", status), (Vector2){ 10, 100 }, fontSize, 0, WHITE); 713 | 714 | #ifdef NETDYNAMICS_SERVER 715 | RayDrawTextEx(font, RayFormatText("CONNECTED CLIENTS %u/%u", connected, NET_MAX_CLIENTS), (Vector2){ 10, 125 }, fontSize, 0, WHITE); 716 | RayDrawTextEx(font, RayFormatText("SEND RATE %u", settings.sendRate), (Vector2){ 10, 150 }, fontSize, 0, WHITE); 717 | RayDrawTextEx(font, RayFormatText("MESSAGES PER SECOND %u", connected * entity * settings.sendRate), (Vector2){ 10, 175 }, fontSize, 0, WHITE); 718 | #elif NETDYNAMICS_CLIENT 719 | if (settings.transport == NET_TRANSPORT_HYPERNET) { 720 | 721 | } else if (settings.transport == NET_TRANSPORT_ENET) { 722 | RayDrawTextEx(font, RayFormatText("RTT %u", rtt), (Vector2){ 10, 125 }, fontSize, 0, WHITE); 723 | RayDrawTextEx(font, RayFormatText("Packets sent %u", enetPeer->totalPacketsSent), (Vector2){ 10, 150 }, fontSize, 0, WHITE); 724 | RayDrawTextEx(font, RayFormatText("Packets lost %u", enetPeer->totalPacketsLost), (Vector2){ 10, 175 }, fontSize, 0, WHITE); 725 | RayDrawTextEx(font, RayFormatText("Packets throttle %.1f%%", enet_peer_get_packets_throttle(enetPeer)), (Vector2){ 10, 200 }, fontSize, 0, WHITE); 726 | RayDrawTextEx(font, RayFormatText("Worst lag %.2f ms", worstLag), (Vector2){ 10, 225 }, fontSize, 0, WHITE); 727 | } 728 | #endif 729 | } 730 | 731 | RayEndDrawing(); 732 | } else { 733 | aws_thread_current_sleep((1000 / settings.framerateLimit) * 1000000); 734 | } 735 | } 736 | 737 | if (settings.transport == NET_TRANSPORT_HYPERNET) { 738 | 739 | } else if (settings.transport == NET_TRANSPORT_ENET) { 740 | if (enetHost != NULL) { 741 | #ifdef NETDYNAMICS_SERVER 742 | for (uint32_t i = 0; i < enetHost->peerCount; i++) { 743 | enet_peer_disconnect_now(&enetHost->peers[i], 0); 744 | } 745 | #elif NETDYNAMICS_CLIENT 746 | if (enetPeer != NULL) 747 | enet_peer_disconnect_now(enetPeer, 0); 748 | #endif 749 | 750 | enet_host_flush(enetHost); 751 | enet_host_destroy(enetHost); 752 | } 753 | 754 | enet_deinitialize(); 755 | } 756 | 757 | if (error == NULL) { 758 | je_free(position); 759 | je_free(speed); 760 | je_free(color); 761 | 762 | #ifdef NETDYNAMICS_CLIENT 763 | je_free(destination); 764 | #endif 765 | 766 | if (!settings.headlessMode) 767 | RayUnloadTexture(texture); 768 | } 769 | 770 | if (settings.headlessMode) 771 | aws_common_library_clean_up(); 772 | else 773 | RayCloseWindow(); 774 | } 775 | --------------------------------------------------------------------------------