├── 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 |
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 |
--------------------------------------------------------------------------------