I've implemented a basic networking system now. Servers will set up networking, clients can connect to servers.
Message sending works up until the client receiving the message containing server info, including the map name, which it uses to load the BSP now.
In theory you could just launch with a different +map command to load any map, but i haven't tried that yet.
For those interested in seeing how much it takes to add a new message, these are the steps you have to take:
- Make sure your dev environment is set up first by adding the path to protoc (Protobuf compiler) to your PATH environment variable. The tool is included in a NuGet package that's a dependency of the SharpLife.Networking.Shared library, so it will be located in your .nuget/packages directory. You can also download the tool from Google's Protobuf website
- Create a .proto file in SharpLife.Networking.Shared.Messages (can be in a child namespace) containing your message definition, make sure to specify the package name
For example, here's the message that the server sends to the client to give it basic server info: https://github.com/SamVanheer/SharpLife-Engine/blob/23f235a39ca16423d35ab6007731d9df2a5c3f7f/src/SharpLife.Networking.Shared/Messages/Server/ServerInfo.proto
- Rebuild SharpLife.Networking.Shared to automatically generate the message class. This is done using a pre-build step that executes a PowerShell script
You need to add the descriptor to the list, and adding new messages post-release should also come with a change to the protocol version constant: https://github.com/SamVanheer/SharpLife-Engine/blob/23f235a39ca16423d35ab6007731d9df2a5c3f7f/src/SharpLife.Networking.Shared/NetConstants.cs#L46
Depending on whether it's a server-to-client or client-to-server message you need to add it to either list.
The order of the messages is important, since it's used to generate the IDs used to identify incoming messages.
- On the receiving side you'll need to register a handler:
Message handlers implement the IMessageReceiveHandler<TMessage> interface. TMessage is the class type of your message. The same class can implement multiple handlers. If a handler is not implemented, an exception will be thrown when a message for it is received.
- That's about it. You can send messages pretty easily:
Adding a message will immediately add it to a list of outgoing messages and serializes it to a buffer. The message object is not kept after that.
Servers can send both reliable and unreliable data, to allow you to send non-critical data that can be lost or ignored without consequence (e.g. visual effect creation).
The engine will send messages automatically, but you can force the immediate sending of all pending data if you want. That's generally reserved for engine code that needs that done, and will not be exposed to game code.
Game code will also be able to register messages the same way, the only difference will be that message handlers will get an object that represents a client rather than a connection.
Note that the message IDs are internal and should never be used for any persistent uses. They can change easily and are normally handled entirely by the message transmission and dispatch systems. Unlike GoldSource, game messages won't require you to store off IDs, so you only have to make sure that your code uses the same list of message descriptors on the client and server.
There is currently no hard limit to the size of messages, but that will be needed to avoid issues later on.
The only hard limit is the number of clients, and that can easily be made a configuration variable.
SVC_BAD should not be possible with this system since message sizes are sent along, and messages are always parsed fully. Only malformed packets could cause it to happen.
Now the next things to do are:
- Build a simple UI to display the console and accept commands
- Implement a system to network arbitrary add-only string lists with binary data
The first is straightforward, i'll be implementing it in the game codebase so all UI code is there.
The second will take some doing. I'll have to look into a good way to network strings because sending them uncompressed could be a problem.
I don't want to just send whole lists at once, that could easily use a ton of memory and take a long time when there's packet drops happening.
Sending lists so each packet contains a self-contained section to process would be the best way to handle this. Whatever system i come up with should also be usable for game data networking to ensure that the client gets what it needs without having to wait to reassemble the packets containing the data.
The binary data will simply be Protobuf messages, it's the most efficient way to handle that.
Once that's done i can create a couple lists to store model, sound and generic precache data in them. Then i can implement proper resource precaching and let the client load everything up.
That's a fair amount of work, so i'll keep it at that for now.