You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Currently GGRS expects input to be submitted for a player, and there can be multiple local players for a given client.
An alternative framing worth considering is that GGRS could expect input to be submitted once for a single client, and it's the responsibility of ggrs-consuming-code to merge local player inputs into one input before submitting it to ggrs (and then split it into per-player input when advancing frames).
This would effectively make GGRS "player agnostic" - GGRS wouldn't know about players at all anymore, only clients.
Why
'Free' replication of arbitrary state
This allows replicating state that is not intrinsically tied to any one player's input - such as "what debug settings are currently enabled". Due to delta encoding, replicating debug settings is very cheap, and by using the Input type to achieve this replication, the debug settings are able to impact the game state without causing desyncs.
This can also be useful for other state like "is the game paused". E.g. if there are 3 players playing on gamepads and someone presses Esc to pause on the keyboard, which player would send the game paused input? You could arbitrarily say keyboard input just gets attached to the first player, but then you need to special case it if keyboard and gamepad players are playing at once.. in such cases I think it's simpler to have some state not tied to player inputs.
Hot join local players
If GGRS doesn't know about local players, it's possible to add local players later (even after a new controller is connected) without restarting the GGRS session; the trick is to have your input track arbitrary commands, and have a command which is "add player with local id x":
// this is what you'd submit to GGRSstructClientInput{player_inputs:Vec<LocalPlayerInput>
commands:Vec<Command>}typeLocalPlayerId = usize;structLocalPlayerInput{for_local_player:LocalPlayerId,jumping:bool,// etc}enumCommand{AddLocalPlayer(LocalPlayerId)}structClientPlayerInput{// "PlayerHandle" is basically "ClientHandle" since there's only 1 ggrs player per client this wayfor_player:(ggrs::PlayerHandle,LocalPlayerId)
jumping:bool,// etc}
Then when processing an AdvanceFrame request from ggrs, you:
unpack the LocalPlayerInputs in MyInput and copy their data into a ClientPlayerInput.
remember which clients issued which commands, so every client can spawn the local player character with a (ggrs::PlayerHandle, LocalPlayerId) identifier, and know which client owns it + which input device is assigned to it.
With this technique, you can ensure all connected clients add a local player that hot-joins on client X, without having to restart the ggrs session, without using any out of band communication, and without getting a desync. (I implemented it in my game here.)
Why Not
You can 100% implement the "player agnostic pattern" in GGRS today just by regarding a GGRS player as a client (i.e. only ever adding one ggrs player per client - regardless of number of local players); that's what I've done in my game and it works great.
The approach of having to merge and split local player inputs yourself is more work for ggrs-consumers that don't care about local player hot join, especially if someone is just trying to get a game jam going.
The impact here could probably be reduced by providing some helper types and functions.
The "issue a command via Input" pattern needs custom input prediction (Custom input prediction function? #69) to avoid causing guaranteed mis-predictions & rollbacks.
From a networking perspective, it results in fewer but larger packets being sent over the wire - which can cause fragmentation in cases of really large inputs. (Also, the current implementation of delta encoding would potentially not work so well with packing input for multiple players into one packet - but that can be fixed.)
For hot-joining players specifically, you could also work around it by adding dummy players to the GGRS session that don't get used until necessary. E.g. "Only 1 player is playing now, but we support up to 4 local players, so always tell GGRS there are 4 local players" - and then set a "player has joined" flag in their input to false. But, that would result in sending small amounts of dummy data to all clients all the time, which is kinda wasteful.
Worth it?
Overall, I figure it's worth considering, because probably most games that have multiple local players would benefit from supporting local player hot join, and so far I've been pretty happy with this technique (disclaimer: I've only used ggrs for about a month!).
And even if we/you decide not to go down this path, then at least here is a written-up sketch of how to support local player hot join for anyone else interested.
The text was updated successfully, but these errors were encountered:
GGRS actually was like that a while ago. I decided to separate clients/players at some point since people were asking for it. The workaround you describe kind of worked, with the caveat that all inputs of all clients had to have the same length at that point. So one would have to add useless zeros to buffer an input (or similarly hacky).
with the caveat that all inputs of all clients had to have the same length at that point. So one would have to add useless zeros to buffer an input (or similarly hacky).
Yup, that is exactly what I did: my Config::Input was [u8; 450] (so always 450 bytes), and then I relied on the run-length-encoding of inputs that ggrs does to shrink it down to a sane size before it was sent.
For the benefit of others reading this: with #82 merged now, that workaround wouldn't be necessary anymore, though without #69 there is a tiny performance impact to this approach I've sketched out (an extra rollback each time a Command is submitted by a player).
What
Currently GGRS expects input to be submitted for a player, and there can be multiple local players for a given client.
An alternative framing worth considering is that GGRS could expect input to be submitted once for a single client, and it's the responsibility of ggrs-consuming-code to merge local player inputs into one input before submitting it to ggrs (and then split it into per-player input when advancing frames).
This would effectively make GGRS "player agnostic" - GGRS wouldn't know about players at all anymore, only clients.
Why
'Free' replication of arbitrary state
This allows replicating state that is not intrinsically tied to any one player's input - such as "what debug settings are currently enabled". Due to delta encoding, replicating debug settings is very cheap, and by using the
Input
type to achieve this replication, the debug settings are able to impact the game state without causing desyncs.This can also be useful for other state like "is the game paused". E.g. if there are 3 players playing on gamepads and someone presses Esc to pause on the keyboard, which player would send the game paused input? You could arbitrarily say keyboard input just gets attached to the first player, but then you need to special case it if keyboard and gamepad players are playing at once.. in such cases I think it's simpler to have some state not tied to player inputs.
Hot join local players
If GGRS doesn't know about local players, it's possible to add local players later (even after a new controller is connected) without restarting the GGRS session; the trick is to have your input track arbitrary commands, and have a command which is "add player with local id x":
Then when processing an
AdvanceFrame
request from ggrs, you:LocalPlayerInput
s inMyInput
and copy their data into aClientPlayerInput
.(ggrs::PlayerHandle, LocalPlayerId)
identifier, and know which client owns it + which input device is assigned to it.With this technique, you can ensure all connected clients add a local player that hot-joins on client X, without having to restart the ggrs session, without using any out of band communication, and without getting a desync. (I implemented it in my game here.)
Why Not
Worth it?
Overall, I figure it's worth considering, because probably most games that have multiple local players would benefit from supporting local player hot join, and so far I've been pretty happy with this technique (disclaimer: I've only used ggrs for about a month!).
And even if we/you decide not to go down this path, then at least here is a written-up sketch of how to support local player hot join for anyone else interested.
The text was updated successfully, but these errors were encountered: