Promul is a simple, lightweight, and easy-to-use relay server and session management implementation for Unity Netcode for GameObjects.
Promul is designed to quickly allow independent and small developers to have fully functional peer-to-peer (P2P) gameplay over top of an open-source relay server, allowing players to play together without complicated port-forwarding configurations, and with minimal infrastructure investment and expenditure for the development team.
It works by providing a relay server and wire protocol for clients, allowing players to both host and join games using Unity's Netcode for GameObjects library. It ships with its own UDP-based networking solution, which can be used independently of the relay protocol.
Promul uses a heavily-modified version of the LibNetLib library for its internal networking, converting the library from a thread-based non-async model to a Task-based Asynchronous Programming (TAP)-based implementation. Technical details of the networking are explained in engineering section below.
Promul is intended to be a free and open-source alternative to Unity Relay. It supports join secret-based session management, with a REST API for creating and deleting sessions, and an admin UI for managing active sessions.
Clone this repository and build and run the program under /Server. The relay server will bind on UDP port 4098 while the API server will bind on TCP port 3000.
To create a new session, call PUT /session/create
. The API will respond with the join code.
Install the transport by adding it in the Unity Package Manager by git URL:
https://github.com/jacksonrakena/Promul.git
Next, add PromulTransport
to your NetworkManager GameObject. Set the Address
and Port
to the address and port of your relay server.
You should now be ready to use Promul as a relay server for your Unity project. You can call NetworkManager
's StartHost
/StartClient
/StartServer
methods as normal.
flowchart TD
subgraph Unity
Code[Your Unity code] -->|Unity Netcode| Runtime[Promul.Runtime.Unity]
end
subgraph Unreal Engine
UECode[Your UE code] --> RuntimeUE["Promul.Runtime.Unreal\n(planned)"]
end
subgraph "Protocol layer"
Protocol[Promul.Relay.Protocol]
end
Protocol-->Core
Runtime-->Protocol
RuntimeUE-->Protocol
subgraph Relay server
API[Promul.Server.API\nFront-facing]-->Server[Promul.Server]
Server-->Protocol
end
subgraph Transit layer
Core[Promul\nKey networking capabilities]
Core-->Native[Native sockets]
Core-->Managed[Managed sockets]
end
Promul's included networking solution is a very heavily modified version of the amazing LibNetLib by Ruslan Pyrch.
Promul is probably compatible with LibNetLib clients and servers. I don't know, and I don't check.
Compatibility with LibNetLib is not a goal of Promul.
Among other differences:
- The library requires .NET Standard 2.1.
- This limits the Unity versions that can use the library, but also
allows the library to use newer constructs such as
ArraySegment<T>
.
- This limits the Unity versions that can use the library, but also
allows the library to use newer constructs such as
- The
NetDataReader
andNetDataWriter
classes have been removed in favour ofBinaryReader
andBinaryWriter
fromSystem.IO
.- This means support for
INetSerializable
has been removed. Clients are expected to create extension overloads forBinaryWriter.Write
andBinaryReader.Read{Type}
.
- This means support for
- The logic and receive threads have been removed, in favour of a long-running
Task
spawned by theNetManager
. - Most of the library's blocking methods have been replaced with Task-based Asynchronous Programming (TAP) methods, using
Task
. This requires a significant rewrite of most parts of the library. - NAT punch-through and manual mode have both been removed.
- The transport will now put transport events onto a queue,
and
NetworkTransport#PollEvents
will dequeue events and pass them to Unity Netcode.
- The transport will now put transport events onto a queue,
and
NetManager#Start
has been replaced byPromulManager#ListenAsync
, which will begin infinitely looping, waiting for incoming messages.- This method will block, so in the Unity transport, it is spawned on an un-awaited
Task
. - Servers are expected to use a paradigm such as
BackgroundService
to run the long-runningTask
.
- This method will block, so in the Unity transport, it is spawned on an un-awaited
- Documentation has been improved.
NetPacket
no longer has jurisdiction over its data. It is a wrapper around anArraySegment<byte>
.- Most (if not all) usages of
byte[] data, int offset, int count
have been replaced withArraySegment<byte>
. INetBasedListener
and related classes have been removed. All events are invoked on thePromulManager
class as regular CLRevent
types.- In many classes, extraneous overloads have been removed in favour of default parameters.
Promul's Unity relay uses its own wire protocol over the transit layer. The protocol is shown in the following diagram:
sequenceDiagram
autonumber
Host (ID 0)->>+Relay API: PUT /session/create
Relay API->>Relay: Creates session
Relay API->>-Host (ID 0): Sends join code "KDS92F" + connection info
Host (ID 0)->>Relay: UDP Connect (KDS92F)
Client (ID 1)->>+Relay API: PUT /session/join KDS92F
note right of Relay API: Join code is transferred out-of-band
Relay API->>-Client (ID 1): Sends connection address and port
Client (ID 1)->>+Relay: UDP Connect (KDS92F)
Relay->>Host (ID 0): 0x11 CLIENT_CONNECTED
Relay->>-Client (ID 1): 0x10 CONNECTED
rect rgba(0, 0, 255, .1)
note right of Relay: Data is now relayed by 0x00 DATA messages
Client (ID 1)->>+Relay: 0x00 DATA, target=0
Relay-->>-Host (ID 0): 0x00 DATA, author=1
Host (ID 0)->>+Relay: 0x00 DATA, target=1,
Relay->>-Client (ID 1): 0x00 DATA, author=0
note right of Host (ID 0): author field becomes target when sending to relay
end
Client (ID 1)--xRelay: Connection lost
Relay->>+Host (ID 0): 0x12 CLIENT_DISCONNECTED
note right of Relay: Relay notifies host of disconnection
Host (ID 0)--xRelay: Connection lost
Relay->>Relay API: Destroys session
- Task-based asynchronous programming rewrite
- Authentication and binding tests
- MTU negotation tests
- Replace logging with a better solution
- Restore merged sending support
- Restore NAT punch-through(?)
- Improve memory and throughput performance
- More real-world replications in tests
- More uses of
ValueTask<T>
over regular tasks
- Basic create/join session by join code
- Update session manager
- Basic web interface
- Further development of web interface
- Multicast(?)
© 2023 Firework Eyes Studio (NZBN 9429048922678) under the MIT License.
This project uses a heavily modified version of LibNetLib.
LiteNetLib © 2020 Ruslan Pyrch, under the following license:
MIT License
Copyright (c) 2020 Ruslan Pyrch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.