Replaced the 'Turn' protocol by the Update' protocol.
Bumped the UQM and netplay protocol versions. Some cleanups. git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@3537 8092fc87-c524-0410-9efc-e669fe64eaf9
This commit is contained in:
+138
-33
@@ -5,7 +5,7 @@ of a network connection.
|
||||
This is used when both parties need to sending information to the
|
||||
other side, but what each party is doing does not interfere with
|
||||
what the other party is doing.
|
||||
- Only speak in your own turn ("Turn")
|
||||
- Agree on changes ("Update")
|
||||
This is used when the parties have changes to make to common data.
|
||||
- Mutual agreement on an action ("Confirm")
|
||||
This is used to end a state where both parties are modifying
|
||||
@@ -49,43 +49,148 @@ local decision -> Send READY goto 3
|
||||
|
||||
============================================================================
|
||||
|
||||
"Turn" negotiation.
|
||||
"Update" negotiation.
|
||||
|
||||
For some actions (like changing a shared configuration option), it is
|
||||
important that both sides don't just send changes at once.
|
||||
To handle this, only one party may send these packets at any moment.
|
||||
If the party whose turn it isn't wants to speak, or if the party whose
|
||||
turn it is doesn't have anything further to say, he can send an ENDTURN
|
||||
packet. The other party should confirm this by sending another ENDTURN
|
||||
packet back.
|
||||
During configuration, both sides may change the same properties. So that the
|
||||
two sides don't have to take turns, the changes are made locally, and then
|
||||
the changes are synchronised.
|
||||
|
||||
States:
|
||||
0. myTurn - I may speak
|
||||
myTurn && !endTurn
|
||||
1. endMyTurn - I've given up speaking, waiting for confirmation
|
||||
myTurn && endTurn
|
||||
2. yourTurn - You may speak
|
||||
!myTurn && !endTurn
|
||||
3. endYourTurn - I want to speak, waiting for confirmation
|
||||
!myTurn && endTurn
|
||||
To this end, each side has a state containing two copies of each property:
|
||||
1. The value of the property as it is locally
|
||||
2. The last value which it sent to the other side (until it is no longer
|
||||
relevant for the protocol)
|
||||
|
||||
Messages:
|
||||
- ENDTURN - "this party ready to change turns"
|
||||
The basic idea of the Update protocol is:
|
||||
- both sides each send and receive one packet before being allowed to
|
||||
send another one (we'll call this a "turn" here)
|
||||
- when a packet has been sent and one has been received in a turn,
|
||||
and the sent and received values are the same, then that value is the
|
||||
agreed upon value. If the sent and received values differ, then a
|
||||
tie breaker determines which one prevails.
|
||||
- when the first local change of a turn is made, the change is sent to
|
||||
the other side
|
||||
- when a local change has been made while a packet has already been
|
||||
sent this turn, the change will be made locally, but communicating the
|
||||
change will be postponed
|
||||
- when a turn ends while a change has been postponed, and this change isn't
|
||||
negated by a remote change, then the change will be sent
|
||||
- when a remote change arrives, and a packet has not been sent this turn,
|
||||
the local state is updated with the change, and the same packet is sent
|
||||
to the other side to confirm the change
|
||||
|
||||
The tie breaker is required to always let one side win, and the other
|
||||
side lose, given the same property.
|
||||
Any function satisfying this requirement is usable, but the currently
|
||||
used one will return true for the side which 'owns' the property, and
|
||||
false on the other side.
|
||||
Another tie breaker could be one which always lets the same side win,
|
||||
regardless of the property.
|
||||
|
||||
|
||||
From state 0 (myTurn):
|
||||
local decision -> Send ENDTURN, goto 1
|
||||
received ENDTURN -> Send ENDTURN, goto 2
|
||||
The protocol:
|
||||
|
||||
From state 1 (endMyTurn):
|
||||
received ENDTURN -> Send ENDTURN, goto 2
|
||||
From state 1, {own=x0, sent=--}:
|
||||
1a Local change x1 -> send(x1); state=2:{own=x1,sent=x1}
|
||||
1b Received UPDATE(x1) -> send(x1); state=1:{own=x1,sent=--}
|
||||
|
||||
From state 2 (yourTurn):
|
||||
local decision -> Send ENDTURN, goto 3
|
||||
received ENDTURN -> Send ENDTURN, goto 0
|
||||
From state 2, {own=x0, sent=x0}:
|
||||
2a Local change x1 -> state=3:{own=x1,sent=x0}
|
||||
2b Received UPDATE(x0) -> state=1:{own=x0,sent=--}
|
||||
2c+ Received UPDATE(x1) -> state=1:{own=x0,sent=--} if winTieBreak
|
||||
2c- Received UPDATE(x1) -> state=1:{own=x1,sent=--} if !winTieBreak
|
||||
|
||||
From state 3 (endYourTurn):
|
||||
received ENDTURN -> Send ENDTURN, goto 0
|
||||
From state 3, {own=x1, sent=x0}:
|
||||
3a Local change x0 -> state=2:{own=x0,sent=x0}
|
||||
3b Local change !x0 -> state=3:{own=xN,sent=x0}
|
||||
3c Received UPDATE(x0) -> send(x1); state=2:{own=x1,sent=x1}
|
||||
3d+ Received UPDATE(!x0) -> send(x1); state=2:{own=x1,sent=x1} if winTieBreak
|
||||
3d- Received UPDATE(!x0) -> state=1:{own=x?,sent=--} if !winTieBreak
|
||||
|
||||
|
||||
Explanation:
|
||||
We keep track of the local value ('own'), and whether or not we sent a packet
|
||||
in this turn ('sent' != '--'), and if we did, the last packet which we sent
|
||||
('sent'). When we proceed to the next turn, 'sent' is set to '--'.
|
||||
|
||||
State 1: We have not yet sent a packet this turn (which implies that we
|
||||
haven't made a local change this turn).
|
||||
1a. A local change is made. We update our local value, and send this to
|
||||
the remote side.
|
||||
1b. A remote change arrives. We don't have any local change ourselves,
|
||||
so we accept the remote change, and sent it back to confirm.
|
||||
With both a packet sent and one received, the turn ends, and 'sent'
|
||||
is set back to '--'.
|
||||
|
||||
State 2: We have sent a packet this turn (after making a local change), and
|
||||
have not changed our local value since (or we have changed it and changed
|
||||
it back).
|
||||
2a. A local change is made. We update our local value, but we have already
|
||||
sent a packet this turn, so we can't report it until the next turn.
|
||||
2b. A remote change arrives, and it is equal to both our local value and
|
||||
the value which we sent to the other side this turn.
|
||||
This remote notification may be a confirmation of our update, or a
|
||||
coincidental identical remote change.
|
||||
Either way, the packet acts as a confirmation, and we do not need
|
||||
to change anything. With both a packet sent and received, the turn ends
|
||||
and so 'sent' is set back to '--'.
|
||||
2c. A remote change arrives, and it is not equal to our local value (which
|
||||
is the same as the value which we sent).
|
||||
The tie breaker decides which value prevails. The same value will
|
||||
prevail on the remote side, so there is no need to send any confirmation
|
||||
packets. The turn ends, and 'sent' is set back to '--'.
|
||||
|
||||
State 3: We have sent a packet this turn, and have made a local change since.
|
||||
3a. A local change is made back to the value which we sent. No action
|
||||
is required.
|
||||
3b. A local change is made. We update our local value, but we have already
|
||||
sent a packet this turn, so we can't report it until the next turn.
|
||||
3c. A remote change arrives, and it is equal to the value which we sent
|
||||
(which isn't equal to the current local value).
|
||||
The sent/received value is the accepted value and the turn ends.
|
||||
But we have changed our value since already, so as the next turn
|
||||
starts, we immediately send an update.
|
||||
3d. A remote change arrives, and it is not equal to the value which we sent
|
||||
this turn.
|
||||
+ We win the tie break, so the value which we sent prevails.
|
||||
But we have changed our value since already, so as the next turn
|
||||
starts, we immediately send an update.
|
||||
- We lose the tie break, so the value which the remote value sent
|
||||
prevails. We accept the value and the turn ends.
|
||||
(An alternative would be to consider the local change(s) which
|
||||
we made after our change was sent as made in the following turn,
|
||||
in which case we would send our current local value in the next turn.
|
||||
The advantage would be that 3d- would become equal to 3d+,
|
||||
so these could be joined (with 3c too), which saves a few
|
||||
if-statements in the code, but this requires another packet to be sent
|
||||
and replied to in what currently is 3d-.)
|
||||
|
||||
|
||||
Proof outline that this works:
|
||||
- The states can never get out of sync:
|
||||
After both a packet has been sent and received, both sides (temporarilly)
|
||||
have accepted the same value
|
||||
- There can be no indefinite loop without ongoing local changes.
|
||||
Once there are no more local changes:
|
||||
from state 3 we always go to state 2 or 1,
|
||||
from state 2 we always go to state 1
|
||||
from state 1 we can only go back to state 1, and only when a packet
|
||||
has been received.
|
||||
So eventually, both sides are in states 1. With both sides in state 1,
|
||||
no packets have been sent in the current turn, so no more packets are
|
||||
there to be received.
|
||||
- Both sides can make changes, as long as the side which wins the tie breaks
|
||||
stops making changes now and then:
|
||||
Without making local changes, a side will go to state 1 eventually.
|
||||
If a side makes a local change, and this is received by the other side
|
||||
while it is in state 1, then that other side will accept the change.
|
||||
|
||||
|
||||
The Confirm negatiation is used to finish the Update negotiation.
|
||||
This works because local changes triggered by the reception of remote changes
|
||||
are treated as local modifications for the purpose of the Confirm negotiation.
|
||||
And this can be done because whenever the Confirm negotiation is in a state
|
||||
where remote changes may be expected, local modifications are still allowed
|
||||
(possily after sending a CANCEL packet).
|
||||
|
||||
|
||||
============================================================================
|
||||
@@ -130,7 +235,7 @@ States:
|
||||
|
||||
|
||||
Handshake messages:
|
||||
- CONFIRM1 - "the current local configuration OK for me"
|
||||
- CONFIRM1 - "the current local configuration is OK for me"
|
||||
- CONFIRM2 - "acknowledging your CONFIRM1; my own configuration is unchanged
|
||||
since I sent CONFIRM1 (after the last CANCEL)"
|
||||
- CANCEL - "forget about my earlier CONFIRM1"
|
||||
@@ -148,7 +253,7 @@ From state 1: (localOk)
|
||||
local cancel -> Send CANCEL, goto 4
|
||||
received CONFIRM1 -> Send CONFIRM2, goto 3
|
||||
received CONFIRM2 -> Send CONFIRM2, goto 8
|
||||
received MESSAGE(changes) -> Process(changes), Send CANCEL, goto 4
|
||||
received MESSAGE(changes) -> Send CANCEL, Process(changes), goto 4
|
||||
|
||||
From state 2: (remoteOk)
|
||||
local confirmation -> Send CONFIRM2, goto 3
|
||||
@@ -204,7 +309,7 @@ aren't expected.
|
||||
|
||||
"Reset" negotiation.
|
||||
|
||||
See src/sc2code/netplay/proto.c
|
||||
See src/sc2code/netplay/proto/reset.c
|
||||
|
||||
|
||||
============================================================================
|
||||
|
||||
+141
-26
@@ -1,63 +1,178 @@
|
||||
NetState_unconnected is the initial state. When a connection attempt is made,
|
||||
the state is set to NetState_connecting.
|
||||
The state field of a NetConnection is NULL.
|
||||
== Any connected state ==
|
||||
Some packets may be sent and received in any state except
|
||||
NetState_unconnected.
|
||||
These are: PING, ACK, ABORT, RESET
|
||||
These are not listed below at each individual state.
|
||||
|
||||
NetState_connecting indicates a connection is in progress.
|
||||
When the connection is established, an INIT packet is sent, the state
|
||||
is changed to NetState_init and InputFunc is set to DoNetworkInit.
|
||||
The state field of a NetConnection is a ConnectStateData structure.
|
||||
Whenever a connection is aborted, the state is returned to
|
||||
NetState_unconnected. This state transition is not listed below at each
|
||||
individual state.
|
||||
|
||||
|
||||
== NetState_unconnected ==
|
||||
NetState_unconnected is the initial state.
|
||||
|
||||
NetConnection.state: NULL
|
||||
|
||||
Packets ok to send: none
|
||||
Packets ok to receive: none
|
||||
|
||||
Next state:
|
||||
NetState_connecting -- connection attempt in progress
|
||||
|
||||
|
||||
== NetState_connecting ==
|
||||
NetState_connecting indicates that a connection is in progress.
|
||||
|
||||
When the connection is established, the state is changed to NetState_init
|
||||
and InputFunc is set to DoNetworkInit.
|
||||
|
||||
NetConnection.state: instance of ConnectStateData
|
||||
|
||||
Packets ok to send: none
|
||||
Packets ok to receive: none
|
||||
|
||||
Next state:
|
||||
NetState_init -- connection established
|
||||
|
||||
|
||||
== NetState_init ==
|
||||
NetState_init is for initialising the connection before actual game
|
||||
information is sent. When an INIT packet is received, the state is set
|
||||
to NetState_inSetup and InputFunc is set to DoMelee.
|
||||
The state field of a NetConnection is a BattleStateData structure.
|
||||
information is sent.
|
||||
|
||||
As this state is entered, an INIT packet is sent. When an INIT packet
|
||||
has also been received, the state is set to NetState_inSetup and
|
||||
InputFunc is set to DoMelee.
|
||||
|
||||
NetConnection.state: instance of BattleStateData
|
||||
|
||||
Packets ok to send: INIT
|
||||
Packets ok to receive: INIT
|
||||
|
||||
Next state:
|
||||
NetState_inSetup -- received an INIT packet
|
||||
|
||||
|
||||
== NetState_inSetup ==
|
||||
NetState_inSetup is the state in which the fleet configuration is negotiated.
|
||||
Only the side who has myTurn set may send this ship information, by means
|
||||
of FLEET and TEAMNAME packets.
|
||||
The Turn negotiation is used to change myTurn.
|
||||
|
||||
This does not necessarilly mean that the fleet setup screen is visible;
|
||||
this state is also held after a battle when the battle outcome is still
|
||||
displayed.
|
||||
|
||||
Each side may send fleet configuration changes to the other side, by means of
|
||||
FLEET and TEAMNAME packets. Agreement on configuration settings is provided
|
||||
through the Update negotiation.
|
||||
The Confirm negotiation is used to end this state and go to
|
||||
NetState_preBattle. At this time InputFunc is set to DoPreMelee.
|
||||
The state field of a NetConnection is a BattleStateData structure.
|
||||
|
||||
NetConnection.state: instance of BattleStateData
|
||||
|
||||
Packets ok to send: FLEET, TEAMNAME, HANDSHAKE0, HANDSHAKE1,
|
||||
HANDSHAKECANCEL, HANDSHAKECANCELACK
|
||||
Packets ok to receive: FLEET, TEAMNAME, HANDSHAKE0, HANDSHAKE1,
|
||||
HANDSHAKECANCEL, HANDSHAKECANCELACK
|
||||
|
||||
Next state:
|
||||
NetState_preBattle -- configuration has been confirmed
|
||||
|
||||
|
||||
== NetState_preBattle ==
|
||||
NetState_preBattle is used for non-interactive battle negotiations.
|
||||
|
||||
One side sends the random seed; the other receives it.
|
||||
Both sides send their input delay value.
|
||||
The Ready negotiation is used to end this state and go to
|
||||
NetState_interBattle.
|
||||
The state field of a NetConnection is a BattleStateData structure.
|
||||
|
||||
NetState_interBattle is used to allow either side to do some local
|
||||
NetConnection.state: instance of BattleStateData
|
||||
|
||||
Packets ok to send: SEEDRANDOM, INPUTDELAY, READY
|
||||
Packets ok to receive: SEEDRANDOM, INPUTDELAY, READY
|
||||
|
||||
Next state:
|
||||
NetState_interBattle -- ready to continue
|
||||
|
||||
|
||||
== NetState_interBattle ==
|
||||
NetState_interBattle is used to allow each side to do some local
|
||||
initialisations before moving on.
|
||||
The Ready negotiation is used to end this state and go to
|
||||
NetState_selectShip, or if there are no more ships to be selected,
|
||||
to NetState_inBattle.
|
||||
The state field of a NetConnection is a BattleStateData structure.
|
||||
NetState_selectShip, or if all sides have selected a ship, to
|
||||
NetState_inBattle, or if there are no more ships in a fleet,
|
||||
to NetState_inSetup.
|
||||
|
||||
NetState_selectShip is where a side may select his ship. The other
|
||||
If there are no more ships, the the Ready negotiation is used to
|
||||
enter NetState_inSetup.
|
||||
|
||||
NetConnection.state: instance of BattleStateData
|
||||
|
||||
Packets ok to send: READY
|
||||
Packets ok to receive: READY
|
||||
|
||||
Next state:
|
||||
NetState_selectShip -- ready to select the next ship
|
||||
NetState_inBattle -- ready to start the battle
|
||||
NetState_inSetup -- no more ships; game over
|
||||
|
||||
|
||||
== NetState_selectShip ==
|
||||
NetState_selectShip is where a side may select their ship. The other
|
||||
side is waiting for notice of this selection.
|
||||
As soon as the selection has been sent or received, the state is changed
|
||||
back to NetState_interBattle.
|
||||
The state field of a NetConnection is a BattleStateData structure.
|
||||
|
||||
NetConnection.state: instance of BattleStateData
|
||||
|
||||
Packets ok to send: SELECTSHIP
|
||||
Packets ok to receive: SELECTSHIP
|
||||
|
||||
Next state:
|
||||
NetState_interBattle -- a selection has been made
|
||||
|
||||
|
||||
== NetState_inBattle ==
|
||||
NetState_inBattle is where the actual melee takes place.
|
||||
Both sides send their input until the game is over, at which point
|
||||
the Ready negotiation is used to end this state and go to
|
||||
the NetState_endingBattle state. Until the Ready negotiation has been
|
||||
completed, the simulation is continuing.
|
||||
The state field of a NetConnection is a BattleStateData structure.
|
||||
|
||||
NetConnection.state: instance of BattleStateData
|
||||
|
||||
Packets ok to send: BATTLEINPUT, READY
|
||||
Packets ok to receive: BATTLEINPUT, READY
|
||||
|
||||
Next state:
|
||||
NetState_endingBattle -- ready to end the battle
|
||||
|
||||
|
||||
== NetState_endingBattle ==
|
||||
NetState_endingBattle is where the local side waits for the remote
|
||||
battle frame count, after it has sent its own. When it arrives,
|
||||
the state changes to NetState_endingBattle2.
|
||||
The state field of a NetConnection is a BattleStateData structure.
|
||||
|
||||
NetConnection.state: instance of BattleStateData
|
||||
|
||||
Packets ok to send: BATTLEINPUT, FRAMECOUNT
|
||||
Packets ok to receive: BATTLEINPUT, FRAMECOUNT
|
||||
|
||||
Next state:
|
||||
NetState_endingBattle2 -- we know when to end the battle
|
||||
|
||||
|
||||
== NetState_endingBattle2 ==
|
||||
NetState_endingBattle2 is where the side with the lowest battle frame count
|
||||
catches up with the other other side, while the other side waits.
|
||||
The Ready negotiation is used to signal that each side is ready,
|
||||
and the state changes back to NetState_interBattle.
|
||||
The state field of a NetConnection is a BattleStateData structure.
|
||||
and the state changes to NetState_interBattle.
|
||||
|
||||
NetConnection.state: instance of BattleStateData
|
||||
|
||||
When a connection is aborted, the state is returned to NetState_unconnected.
|
||||
Packets ok to send: BATTLEINPUT, READY
|
||||
Packets ok to receive: BATTLEINPUT, READY
|
||||
|
||||
Next state:
|
||||
NetState_interBattle -- get ready for the next ship
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user