SFNUL - Simple and Fast Network Utility LibraryI originally intended it to just be called NUL, but go figure why I couldn't name it that... I guess the SF is fitting because if you ask me, it sticks with the SF philosophy somewhat
.
Background HistorySFNUL was envisioned a veeeery long time ago, while I was still actively working on my super awesome project
Wyrm before even touching SFGUI because Wyrm needed a UI
. Back then I already considered making the networking more reusable because I tended to have to rewrite the exact same code in every networked game I coded, and it got quite annoying. Time passed, and I had
more interesting things to do than actively work on Wyrm so that idea kind of went with it for a while.
It wasn't until helping Jungletoe debug his network related problems earlier this year that I realized SFML networking had a lot of shortcomings that I also found annoying, not only because I found that it didn't provide a level of abstraction of networking that was high enough, but because in certain situations it sacrifices standards conformance for a bit of usability. Being a computer science student for so long, I've come to revere standards which is why SFNUL aims to warn the developer about non-standard behaviour that they might cause.
General IdeaThe main goal of SFNUL is to cut down on the network boilerplate code that can be seen in most multiplayer games. I already experienced this first hand while working on Wyrm, and browsing through the SFML network forum reviewing code and problems many have, I've gotten a feeling for what multiplayer game developers might need to spend less time writing "the same old annoying code that has to be written in every single project". SFNUL's sole purpose is to give developers more time to work on the code that makes their project unique from all others instead of code that is common among all projects. I want to see less code/problems and more playable multiplayer games
.
FeaturesFully asynchronous socket API.Process data according to your own pace, it will trickle into an internal buffer but this doesn't mean you need to dedicate a thread to a blocking call or resort to selectors to check if any data is available. If data is available, you will be able to retrieve it and process it, if not then do something else.
Fully asynchronous TCP connection establishment/accept.TCP connections will continue trying to connect and will automatically transmit/receive queued data when it is established. Incoming TCP connection requests are also queued into an internal buffer for you to process when you are ready.
TLS/SSL support.You can set up a TLS transport and use it like you would use a TCP connection. All TLS specific features are added on top of the ReliableTransport interface which a normal TCP socket also implements.
HTTP/HTTPS client support.Send arbitrarily constructed HTTP requests through the HTTPClient and it will receive and parse the reply sent back from the server which you can pick up from the HTTPClient, as with everything else, when you feel like it. HTTPClient supports HTTP/1.1 chunked transfers, persistent connections and request pipelining for efficient throughput utilization.
sfn::MessageThose familiar with sf::Packet will understand how sfn::Message works. It is the SFNUL version of sf::Packet that can do a lot more useful things
. Let's use some code to demonstrate the differences:
sf::Packet packet;
sfn::Message message;
std::vector<int> vec = { 1, 2, 3, 4 };
packet << vec; // Doesn't compile, no operator<<( const std::vector<int>& )
message << vec; // Compiles, all objects that conform to the STL container interface are supported.
// Nesting containers works as well.
std::vector<std::list<std::deque<std::string>>> something;
// Insert some data here...
message << something; // Works fine.
message >> something; // Works fine as well.
int i = 42;
packet.prepend( &i, sizeof( i ) ); // Doesn't exist. No easy way to prepend data to a packet.
i >> message; // Intuitively insert data into the front of the message to prepend.
struct A {
int a, b, c;
float d, e, f;
char g[16];
};
A a;
packet << a; // What?? How could this possibly be implemented?
message << a; // Yes... this works... any trivial data type is supported for insertion and extraction.
message >> a; // Just to complete the cycle.
LinksDid you ever wonder how you can separate multiple different networking concerns from each other when communicating between hosts? Say you need to transmit data every time the player hits a key to move, that is one concern, then you need to transmit object states, that is another concern, and on top of that the players want to chat with each other, yet another concern. A typical approach would be to label each of these with a separate packet type ID to properly identify their payload, or the worse solution that many big games go for: open up multiple connections between the same pair of hosts. With the sfn::Link, that is taken care of automatically. sfn::Link multiplexes multiple distinct streams into a single network connection and demultiplexes them at the other end. All you need to do is assign each concern a stream ID and send/receive over that stream when dealing with that concern.
Object SynchronizationThe centrepiece of SFNUL, seeking to solve the most annoying part of multiplayer coding. When you have multiple hosts which share the same world state with each other, performing the proper synchronization between them can become quite a tedious task. You need to assign IDs, perform required serialization/deserialization, object lifetime management, connection management and many other annoying things that have nothing to do with your vision of the game. This is where the sfn::Synchronizer comes in. Simply put: The sfn::Synchronizer makes sure that state changes to objects that it manages are propagated to synchronized remote hosts automatically without
any extra coding effort from the developer. A simple example with a lot of things omitted for demonstration purposes:
class Blob : public sfn::SyncedObject {
public:
// ... type id / constructors omitted ...
sfn::SyncedInt32 x;
sfn::SyncedInt32 y;
sfn::SyncedType<sf::Color> c;
};
sfn::SynchronizerServer synchronizer_server;
auto blob = synchronizer.CreateObject<Blob>();
sfn::SynchronizerClient synchronizer_client;
// We assume the blob is already created on the client for brevity sake.
// On the server we do:
blob.x = 10;
blob.y = 20;
blob.c = sf::Color::Green;
// On the client we do.... absolutely nothing. The changes are performed automatically.
No more wasted time on getting the synchronization up and running and even more wasted time debugging it when it doesn't work. Do what you would do for a single player game and get multiplayer for free... in some sense
.
Here is a diagram showing how the SFNUL facilities and dependencies can interact with each other:
These were very brief examples/descriptions of the ideas behind SFNUL. If you want more demonstrations (that also work) look in the examples directory.
OK, enough with the features, where's the...Yes... the repository can be found at GitHub:
https://github.com/binary1248/SFNULIf it isn't already apparent, SFNUL makes
very heavy use of C++11 features. This means that you not only need a compiler that can compile C++11 code somewhat, but one that is
feature complete and more importantly
conforms to the standard. This already rules out Visual Studio 2012 and earlier, so if you haven't hopped on the Visual Studio 2013 train, well this could be another reason to consider if you want to try SFNUL.
SFNUL is licensed under the Mozilla Public License Version 2.0. Put simply, it is a file-based license, as long as you don't modify any of SFNUL's files you are free to do/distribute whatever/however you want, all subject to the terms of the MPL of course. If you distribute software using modified MPL code, you need to make available the modifications under the MPL as well. See this as a contribution back to the open source community for all of its efforts you have taken advantage of in the past
. As with any other license, read through the MPL text if you want to understand what you can/cannot/have to do.
Frequently Asked QuestionsQ: Where are the pre-compiled libraries for XYZ compiler?
A: Learn to compile something yourself, it isn't as hard as you think.
Q: I downloaded the zip archive off GitHub but it's complaining about missing headers during compile. Why?
A: zip archive won't work. Clone via git and initialize all submodules. Explained in the README.
Q: But I don't have git, surely there must be another way?
A: There is another way, it's called install git and use it.
Q: Wait... so what has this got to do with SFML?
A: Initially, it interacted a lot with SFML's classes, but then I realized that it would be easier for me to implement my own take on SFML's classes with many more features I found were missing and were feasible with C++11. So right now, it has very little to do with SFML. It can even be used independently of SFML. But you will recognize a bit of SFML's interface design still left in SFNUL.
Q: Do you plan on ....
A: Yes I have plans for a lot of things, and they will be implemented when I have a general idea of how to implement them. Either that, or I run into a dead end in one of my other projects and know that it is something SFNUL should be able to solve, so I implement it into SFNUL to be able to use it in the project and share it with the world at the same time.
Q: I know this isn't a question but I found a bug and I am sure it isn't intended behaviour described in a footnote of the documentation.
A: GitHub tracker.
Q: Where is the documentation?
A: doxygen inside the doc directory, or just generate the doc target in CMake.
Q: This is too complicated for me. Is there a simpler way?
A: What? This is supposed to make your life simpler. Write a few games with decent network support and come back here so that you can realize how that question doesn't make much sense.
Q: Are those the only questions in the FAQ?
A: Nope, more will be added if needed.