-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Network communication design #530
Comments
In https://github.com/OpenRCT2/OpenRCT2 we have added multiplayer capability to the game, and included a simple lobby, master server, which lists online public games. At first there was no authentication, but people abused publicly available servers to destroy maps. We have then added ability to password-protect servers and assign users into groups with specified permissions, but it turned out to not be enough, as we could not make those permissions persist. Our plan was to include centralised authorisation server, but this met some substantial resistance, as it was "against the spirit of open source", even when we wanted to publish the code for running your own auth service. The solution we came up was to utilise OpenSSL's public-key cryptography to generate key on client and have the client sign server-generated token, client then sends signature to the server, gets verified and if that step is successful, the client is granted permissions he was assigned last time. If you would like to employ similar scheme in your project, you can use OpenRCT2/OpenRCT2#3699 as a reference. |
Thanks for the hints, doesn't sound bad. But this issue is rather about the simple server-client interaction, without all the lobby stuff. I just opened another issue for that: #562. I don't think we'll have a griefing problem, because games are rather short-lived and only spectators should be able to join afterwards. For client authentification, we thought about using GPG, so achievements can be issued to key owners for example. Match-rejoins after a disconnect can also be performed that way securely. But it's not that much a difference to OpenSSL, except maybe licensing fuckup. |
When I was designing the system, I found out about https://keybase.io/, this looks nice and something we could've probably used, but it still in invite-only mode. If you use crypto keys nevertheless, perhaps something worth taking a look at. |
I tried to understand the proposed netcode: sources: |
One interesting bit in this diagram is limiting network messages clients receive to only what they can see. This is a very sound approach, but one that could probably make syncing game state much much harder. I'm eager to see how you guys solve that. Another piece of documentation you may perhaps find useful or a source of inspiration would be https://github.com/OpenTTD/OpenTTD/blob/master/docs/desync.txt Have you already given any thought how would your network stream look like? Are you going to use something to encapsulate it, like protobuf? |
protobuf looks exactly what we need for sending messages. It seems simple enough, so all developers can use without knowledge about the network system. You need somthing synct to the client? No problem create message format and send it. desync: we should not have problems with desync. The Server is authoritative. We could do something like: sending only relevant information to the client:
|
Desync is still an issue, because the info we're getting from the server is about evolution of a unit in next several seconds. Bad implementation will jingle or create a lot of traffic. Even if our implementation has no desync bugs, it still needs to handle things like de-spawning a unit as a part of the normal flow. Also, don't forget in the diagram another prediction loop when units start to move before we even send command to the server. |
You are right, there will be small desyncs until the server sends updated path, hitpoints, etc for the unit.
I don't understand where is the problem with de-spawning units? send a message with unit A is killed/will be killed at 2:45 is the prediction loop necessary/possible? We cant know when the message exactly arrives at the server and will be processed. We wont send a timestamp with it, because this may be a big loophole for cheating. Therefore, A command will be executed when the server says so, not before. If we do prediction client side, we may be undo building creation, ..., I think that would be disappointing? experience for the player if he wants to build a castle and we give the visual feedback and 80ms later we remove it from the map because another player builds a tower there. |
Message from server: "Unit is in the prod queue, will be ready in 10 seconds". Ok, client spawns a unit in 10 seconds. Message from server: "Sorry, actually rax were destroyed and there never was any unit spawned". Client makes the unit vanish. About prediction: you click - they move. No time to wait for the server roundtrip, because after playing with 20ms ping, the players will be getting nausea on 100ms. |
The best thing you could do about floats is not to use them for game state. It may be hard, but other than following IEEE754 to a t, or using something like libfixmath*, you face differences in implementations. Check out also https://www.reddit.com/r/gamedev/comments/3tx6gh/article_i_wrote_minimizing_the_pain_of_lockstep/ * I know you guys, you will try to roll your own. |
There is one already. I was like "wtf, these uint64_t values are making no sense when I'm printing them!". |
new term proposal: @ChipmunkV good point. But we could send a message: "building destroyed","units newer existed: 4,5,6" (so no body will be shown instead when the units killed message would be used) for referencing the ids: every client get a pool assigned with ids(global unique),so he chooses them and the server can reference them when they are killed before the queue message arrives at the server New Algorithm:
maybe client side pre prediction is not needed or it is only useful in some cases. Client side pre prediction would be extremly hard to implement. now the server must look at the timestamps from clients and choose if they are plausible. Clients with different Pings have different time to send their messages. If we allow pre prediction, we must replay all frames which happend since then. => this may be highly cpu intense My opinion: Server accept messages as they come in as if they were triggered now. What clients may can do to reduce lag: if we know ping is 40ms, than we can assume out message(which should be correct) will be processed in 20ms. So we buffer it until then and we wont be so far off. |
@timohaas jup that sound like a good way. We should abstract the communication in several ways, the Then we need to create/extend the This is all gonna be very hard and needs lots of restructuring, but we can do it! |
Possible protobuf alternative: https://capnproto.org/ |
Yes, protobuf is not the only kid on the block. capnproto has nice summarising feature matrix and some decent comparisons on this page: https://capnproto.org/news/2014-06-17-capnproto-flatbuffers-sbe.html |
I would implement a custom protocol based on a deterministic lockstep method. The idea is to transfer "single occurence, event like" information in a reliable way (more later) and "status updates" like unit positions etc. in a non-reliable way. To transfer a piece of information reliable, you transmit it with every packet every frame (maybe more than one eventframe per frame), until you recieve an ACK from the other end, that events until the specific frame number have been transmitted. Status updates are used to fill up a packet to MTU size (because if a packet is split on the way because it is bigger than the MTU, you again loose precious milliseconds) So more in detail a example packet can look like that:
This means for the remaining concept:
Please give me your comments what you think about this concept. |
@Tomatower, you may start writing I'm making a sketch of what should be between (branch VelorumS@dd6aa96) |
Currently I was more thinking about applying the different packages pulled from the server (via a "get events for frame 5"-method), and then interpolating from there with the exact same codebase as it exists on the server. If one needs to extrapolate etc. it would be better to apply a "what-if" interface: "I am in Frame 8. Now the Server said: In Frame 5 unit 3 started to move. Where is it now?" |
netcode Roadmap proposal:
at this point we have a running connection, dumb client
at this point we can play on the server and the clients can render it
at this point we have a running multiplayer game
@Tomatower we should not use UDP. It would make the network stack more complex. We dont need the speed down to the last ms. |
The first Network-Client should be to start a second low spec tool, that can blindly render, what the full spec Server (the current version: You can move units etc.). This will test the basic communication. Then we should provide the possibility for the client to create input events to be transmitted to the server, and be mapped to another player than the one currently running. At this point in time we start implementing the need to know principle Then we can start designing a lobby, that enables to connect different players together, and start removing the input in the server (maybe keep the rendering without FOW?) Then we can think about creating fancy stuff like hot-seat switching, AI via Network, ... So as Checklist:
|
The unknown is an integration with the game data. The subsystem should use the right types of curves for the unit/player properties, defined in nyan. And it must be transparent, so if there is a change in the property definition - it's only in the
Have you considered implementing them as curves? |
In that networking model, server sends events, but some of them are already extrapolations. So, server is at frame 5, but already telling that the unit will finish being produced on the frame 105 (200ms logic frame, so - in 20s). Then server corrects the predictions as it goes. The nice thing is that the client can throw in its own extrapolations into the same array, and render that as usual. Later it will be overwritten by the corrections from the server anyways. |
The nyan system is synced by just sending the time of a patch application. The database has to be identical at the start, and patches are applied by event-curves ("apply patch |
After having a more in-depth look into the features, i have found SCTP as a possible network wrapper. It has good support in Linux, and there are libraries available for windows. It supports especially multiple streams and off-band messages. |
I have an Idea about a possible Curve-API and Code in the Backend, and I welcome feedback The IdeaThe Data to be accessed seems to look like that without curve (a rought example, that shall be seen only as example for the API idea) struct single_unit { //Obejct Store
vector<2, float> position; //MultidimensionalContinuou
float hp; //SimpleContinuous
int ammo; //Discrete
};
struct gameContainer { //Object Store
std::unordered_map<int, single_unit> units; //Identificators
};
void event(event); //Event triggering Curve APIThe derived API shall be like that: class SingleUnit : public curve::Object {
curve::Array<2, float> position;
curve::Continuous<float> hp;
curve::Discrete<int> ammo;
};
class GameContainer : public CurveObject {
curve::unordered_map<int, SingleUnit> units;
};
class curve::Event {
int event_type;
vector<int32_t> data;
};
curve::event_iterator events(time start, time end); extra parsers for the event iterator stuff can be implemented. The API Interface IdeaThe core definitions will look like: event_iterator {
//Standard iterator stuff
bool valid(); //if there are still elements in the queue
};
Reading: Data is stored within the data functions on non-list types.
data getters on single-dimensional values
data getters on multi-dimensional values (Array and unordered map)
data functions on the unordered map:
|
We escalated this even more, so the transmission over the network is still curves via keyframes, internally we create the predictions with #740. |
Protobuf alternative Why not use Protocol Buffers, or .. ?
Nice! This is a simple web front end for the FlatBuffers Compiler (flatc 1.10.0): |
@simonsan AoE also uses flat buffers in their multiplayer protocol I think. |
Is it possible to take NAT into consideration when we choose protocols? Also looking into the future it might be useful to make our game compatible with IPv6. |
@duanqn I would support that and we would have to think about that anyway since other mechanisms such as DSLite make direct IP connections impossible. Committing to IPv6 would also be a good idea. |
For now we assume that the dedicated server is reachable somehow (directly (v4/v6), VPN, portforward, ...), so we don't plan for any nat-traversal mechanisms. These can be added later as an extension when we deem them useful. |
@TheJJ I agree that NAT is not a top concern for now, but it's probably beneficial to keep it in mind. My concern is that if we choose some protocols, then NAT traversal may become impossible. For example I think any TCP-based protocols cannot work with NAT. |
@duanqn Do you know which type of NAT we would be dealing with in Chinese networks? I have little knowledge on how the network over there work :) For most applications, STUN/ICE/TURN and NAT64 should work for the UDP side, and also for TCP if I remember correctly. Some of these methods would also require a relay server to be reachable from China which could be tricky. |
@heinezen Good question... I don't know actually. I think the network environment varies from region to region, but in general there is heavy presence of NAT and firewalls. |
@duanqn as a client use the Trickle ICE WebRTC sample for testing. It's just a local web page that can communicate with STUN and TURN servers. Can test with some public servers. As a STUN/TURN server the resiprocate-turn-server works fine. It's in the Ubuntu repo. My current job is a VoIP server, routers, recorders. |
I asked one of my friend to test with https://github.com/jselbie/stunserver |
tl;wr: the server does all calculations, clients only render the state they received. state is transmitted by keyframes at "predicted" end points.
The design is pretty much like on would do it for a FPS.
The basic idea is this: The server sends a packet which updates the "target" state of the future for clients. They interpolate the received movement/update/... functions and try reaching the target state all the time.
This is the low-level part for transmitting the results of the simulation itself.
To understand how the prediction works, see #740.
The text was updated successfully, but these errors were encountered: