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:
Meep-Eep
2010-04-24 20:59:07 +00:00
parent 91fde6f5e7
commit e4886e60ee
32 changed files with 835 additions and 535 deletions
+138 -33
View File
@@ -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
View File
@@ -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