Skip to content

Commit dfeebf3

Browse files
Tchips46Exeloo
authored andcommitted
feat(docs): network
1 parent 0751418 commit dfeebf3

File tree

9 files changed

+332
-2
lines changed

9 files changed

+332
-2
lines changed

.idea/[NanoForge] Engine.iml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/network/index.rst

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
Network Overview
2+
================
3+
4+
This page describes the global design and rationale for the engine's networking
5+
libraries: the `network-server` and `network-client` TypeScript packages. These
6+
libraries provide a small, pragmatic networking layer used by the example
7+
`pong-network` game and designed to be easy to understand and integrate.
8+
9+
Goals
10+
-----
11+
12+
- Minimal, predictable protocol for multiplayer games.
13+
- Use well-understood transports: TCP for reliable control messages and UDP
14+
for low-latency, best-effort state updates.
15+
- Provide clear validation hooks (magic value, versioning) to avoid processing
16+
```restructuredtext
17+
Network Overview
18+
================
19+
20+
This page explains how the engine's TypeScript networking packages actually
21+
work and the rationale behind important implementation choices. The two
22+
packages are `network-server` and `network-client` and are used by the
23+
`example/pong-network` project.
24+
25+
Design summary
26+
--------------
27+
28+
- Two logical transports are provided: a reliable, ordered channel (called
29+
"TCP" in the packages) and an unreliable, unordered channel (called
30+
"UDP"). Important: these names refer to channel semantics in the library,
31+
not to raw OS sockets. Implementation details:
32+
- The reliable channel is implemented over WebSocket (node `ws` and
33+
browser `WebSocket`) to provide an ordered, byte-stream-like channel.
34+
- The unreliable channel is implemented as a WebRTC `RTCDataChannel`
35+
created with `ordered: false, maxRetransmits: 0`. WebSocket is used for
36+
signaling/ICE exchange between client and server.
37+
38+
- For packet framing and terminator semantics see the dedicated note:
39+
`docs/network/packet-framing.rst`.
40+
41+
Why these choices
42+
------------------
43+
44+
- WebSocket for reliable messages: WebSocket is universally available in
45+
browsers and easy to host in Node.js. Using it for the "TCP" channel avoids
46+
needing a separate TCP server and simplifies browser + native client parity.
47+
48+
- WebRTC DataChannel for unreliable messages: Browsers cannot open raw UDP
49+
sockets; WebRTC provides a browser-friendly unreliable datagram channel
50+
with low latency. The repository uses WebSocket for ICE signaling and
51+
negotiates a `RTCDataChannel` for actual game-state messages.
52+
53+
- Terminator (magic value) appended at packet end: appending a terminator is
54+
robust against fragmented transport frames. Because WebSocket and RTC
55+
DataChannels can split or aggregate application messages, a terminator
56+
allows the receiver to detect full logical packets regardless of chunking.
57+
58+
- Configurable `magicValue`: The default (`PACKET_END`) is a human-readable
59+
sentinel that makes debugging easier; it is configurable via the server or
60+
client config objects if you prefer a shorter or binary marker.
61+
62+
Serialization and extensibility
63+
-------------------------------
64+
65+
- Example code uses JSON payloads for clarity (easy to inspect and debug).
66+
The transport layer operates on `Uint8Array` buffers, so you can replace
67+
JSON with any binary encoding for
68+
production.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
TCPClient
2+
~~~~~~~~~
3+
4+
**connect** ()
5+
Initiate a WebSocket connection to the server (e.g. `ws://<ip>:<port>`).
6+
7+
:return: Promise<void>
8+
9+
**isConnected** ()
10+
Return `true` when the underlying WebSocket is open.
11+
12+
:return: boolean
13+
14+
**sendData** (*data*)
15+
Send a payload to the server.
16+
17+
:param data: Uint8Array — raw payload bytes.
18+
:return: void
19+
20+
**getReceivedPackets** ()
21+
Return an array of complete packets that were reassembled from received chunks.
22+
23+
:return: Uint8Array[] — array of packet buffers.
24+
25+
26+
UDPClient
27+
~~~~~~~~~
28+
29+
**connect** ()
30+
Open a WebSocket for signaling, create an RTCPeerConnection and initiate an SDP offer.
31+
32+
:return: Promise<void>
33+
34+
**isConnected** ()
35+
Return `true` when the RTCDataChannel is open.
36+
37+
:return: boolean
38+
39+
**sendData** (*data*)
40+
Send a payload on the data channel.
41+
42+
:param data: Uint8Array — raw payload bytes.
43+
:return: void
44+
45+
**getReceivedPackets** ()
46+
Return an array of complete packets reassembled from received data-channel chunks.
47+
48+
:return: Uint8Array[] — array of packet buffers.
49+

docs/network/network-client.rst

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
Client Networking (network-client)
2+
==================================
3+
4+
This document describes how the `network-client` package actually connects
5+
to a `NetworkServerLibrary` instance and the concrete APIs you will use from
6+
client-side code.
7+
8+
Overview
9+
--------
10+
11+
A client connects to a single server instance (one TCP/WebSocket control
12+
channel and optionally a single WebRTC data channel for unreliable traffic).
13+
Client responsibilities in a game are typically:
14+
15+
- Initiate a TCP connection and send control commands (join/play/input).
16+
- Optionally negotiate a WebRTC data channel for receiving server snapshots
17+
or sending low-latency updates.
18+
19+
Minimal usage pattern (as in `example/pong-network`)
20+
--------------------------------------------------
21+
22+
.. code-block:: javascript
23+
24+
// Ensure TCP is connected
25+
await network.tcp.connect();
26+
27+
// Wait for UDP (RTC data channel) if used by the server
28+
await waitForConnection();
29+
30+
// Send a simple reliable control message (play)
31+
network.tcp.sendData(new TextEncoder().encode(JSON.stringify({ type: 'play' })));
32+
33+
// Send input messages (example)
34+
network.tcp.sendData(new TextEncoder().encode(JSON.stringify({ type: 'input', key: 'up' })));
35+
36+
Notes
37+
-----
38+
39+
- See `docs/network/network-client-api.rst` for the client functions exposed to
40+
your game code.
41+
- For packet framing/terminator semantics see `docs/network/packet-framing.rst`.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
TCPServer
2+
~~~~~~~~~
3+
4+
**listen** ()
5+
Start the WebSocket server and begin accepting clients.
6+
7+
:return: void
8+
9+
**getConnectedClients** ()
10+
Return a snapshot array of numeric client IDs currently connected.
11+
12+
:return: number[] — an array of client IDs.
13+
14+
**sendToEverybody** (*data*)
15+
Send a payload to every connected client.
16+
17+
:param data: Uint8Array — raw payload bytes.
18+
:return: void
19+
:throws: none (errors are logged, not thrown)
20+
21+
**sendToClient** (*clientId, data*)
22+
Send a payload to the client identified by `clientId`.
23+
24+
:param clientId: number — numeric client identifier.
25+
:param data: Uint8Array — payload bytes.
26+
:return: void
27+
:throws: none (logs if client unknown)
28+
29+
**getReceivedPackets** ()
30+
Parse and return complete packets received from each client. Each packet is a `Uint8Array` buffer.
31+
32+
:return: Map<number, Uint8Array[]> — mapping client ID to array of packets.
33+
34+
35+
UDPServer
36+
~~~~~~~~~
37+
38+
**listen** ()
39+
Start the signaling WebSocket and accept incoming client offers (SDP/ICE).
40+
41+
:return: void
42+
43+
**getConnectedClients** ()
44+
Return a snapshot array of client IDs with active data channels.
45+
46+
:return: number[] — list of client IDs.
47+
48+
**sendToEverybody** (*data*)
49+
Send a payload to every connected data channel.
50+
51+
:param data: Uint8Array — raw payload bytes.
52+
:return: void
53+
54+
**sendToClient** (*clientId, data*)
55+
Send a payload to a single client data channel.
56+
57+
:param clientId: number
58+
:param data: Uint8Array
59+
:return: void
60+
61+
**getReceivedPackets** ()
62+
Parse incoming channel chunks and return a map of complete packets per client.
63+
64+
:return: Map<number, Uint8Array[]> — mapping client ID to array of packets.

docs/network/network-server.rst

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
Server Networking (network-server)
2+
==================================
3+
4+
This document explains the actual `network-server` package implementation and
5+
the APIs you will use from server-side code.
6+
7+
Overview
8+
--------
9+
10+
The server listens on configured ports and accepts client connections.
11+
A single server process can accept many clients; typical server responsibilities
12+
in a game are:
13+
14+
- Accept reliable control messages from clients over the TCP (WebSocket) channel.
15+
- Optionally establish `RTCPeerConnection`s (via WebSocket signaling) to receive
16+
unreliable, unordered data channels for low-latency state updates.
17+
18+
Minimal usage pattern (as in `example/pong-network`)
19+
--------------------------------------------------
20+
21+
- Server reads incoming TCP packets and handles simple single-message commands
22+
(JSON frames in the example):
23+
24+
.. code-block:: javascript
25+
26+
// In a server system: read incoming messages
27+
const clientPackets = network.tcp.getReceivedPackets();
28+
clientPackets.forEach((packets, clientId) => {
29+
packets.forEach((packet) => {
30+
const msg = JSON.parse(new TextDecoder().decode(packet));
31+
if (msg.type === 'play') {
32+
// register client and reply using network.tcp.sendToClient(...)
33+
}
34+
});
35+
});
36+
37+
- To broadcast authoritative state, the server can use `network.tcp.sendToEverybody`
38+
or `network.tcp.sendToClient(clientId, data)`, and use the UDP-like data channels
39+
(WebRTC) for fast, best-effort updates.
40+
41+
Notes
42+
-----
43+
44+
- See `docs/network/network-server-api.rst` for the exact list of server
45+
functions available to your systems.
46+
- For packet framing and terminator semantics see:
47+
`docs/network/packet-framing.rst`.

docs/network/packet-framing.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
Packet Framing — Magic Terminator
2+
================================
3+
4+
This short note explains how packets are framed and reassembled in the
5+
network packages, at a logical level. The framing strategy is
6+
simple and robust to transport fragmentation or aggregation.
7+
8+
Concept
9+
-------
10+
11+
- Each logical packet is a payload of bytes that the application wants to
12+
send (JSON or binary).
13+
- Before sending, the library *appends* a configurable terminator string
14+
(the "magic value") to the end of the payload. The default value in the
15+
project config is `PACKET_END`.
16+
- The terminator is applied as bytes (UTF-8 encoding of the configured
17+
string) and therefore forms a unique byte suffix that marks the end of a
18+
logical packet.
19+
20+
Why an end terminator
21+
---------------------
22+
23+
- Transports like WebSocket and RTC DataChannels may split or combine
24+
application messages into arbitrary chunks. A terminator lets a receiver
25+
reliably detect the end of each logical packet regardless of how the
26+
transport fragments or aggregates bytes.
27+
- Using a short, human-readable terminator makes debugging easier.
28+
- The terminator is configurable so you can pick a value that does not
29+
collide with your payload contents (especially important if using raw or
30+
binary payloads).
31+
32+
Sender logic
33+
---------------------------
34+
35+
1. Serialize the application message into bytes (e.g., JSON => UTF-8
36+
bytes, or a binary codec output).
37+
2. Append the configured terminator bytes to the end of the payload.
38+
3. Send the resulting buffer on the transport (WebSocket or DataChannel).
39+
40+
Receiver logic
41+
-----------------------------
42+
43+
1. Accumulate incoming chunks of bytes into a per-connection buffer.
44+
2. Repeatedly search the accumulated buffer for the terminator sequence.
45+
3. For each occurrence, extract bytes from buffer start up to (but not
46+
including) the terminator — this is a complete logical packet.
47+
4. Remove the extracted packet and trailing terminator from the buffer and
48+
continue searching; keep any leftover bytes (partial packet) for the
49+
next incoming chunk.

packages/ecs-lib/wasm/Registry.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,9 @@ namespace nfo {
159159
*/
160160
Entity entity_from_index(const std::size_t entity_id)
161161
{
162-
if (std::ranges::find(_dead_entities, id) != _dead_entities.end() || id >= _next_entity)
162+
if (std::ranges::find(_dead_entities, entity_id) != _dead_entities.end() || entity_id >= _next_entity)
163163
throw std::runtime_error("Entity index out of range.");
164-
return Entity(id);
164+
return Entity(entity_id);
165165
}
166166

167167
/**

0 commit comments

Comments
 (0)