summaryrefslogtreecommitdiff
path: root/doc/devel/netplay/protocol
blob: 5b82a1f7aeaf60bb768afded5bea28fbe3b57823 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
There are several types of negotiations used to synchronised the parties
of a network connection.

- Continue when we know the other is ready ("Ready")
  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.
- 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
  common data. Both parties have to agree with the data for either
  party to continue.
- Reset a connection. This is used to abort a game in progress and return
  to the fleet setup menu.

============================================================================

"Ready" negotiation.

Sometimes the parties need to notify eachother of their local state,
and then go on when both are ready. For this purpose both parties signify
that they are ready by sending the READY message. When a party is ready
and has received notice that the other party is ready, it can go on.

States:
0. notReady                     - send nor received READY
   !localReady && !remoteReady
1. localReady                   - sent READY, not yet received READY
   localReady && !remoteReady
2. remoteReady                  - received READY, not yet sent READY
   !localReady && remoteReady
3. ready                        - sent and received READY

Messages:
- READY - "I have nothing further to send at this point"


From state 0 (notReady):
local decision  -> Send READY,                    goto 1
received READY  ->                                goto 2

From state 1 (localReady):
received READY  ->                                goto 3

From state 2 (remoteReady):
local decision  -> Send READY                     goto 3


============================================================================

"Update" negotiation.

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.

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)

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.


The protocol:

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, {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, {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).


============================================================================

"Confirm" negotiation.

Some actions (like agreeing on a configuration) require confirmation
from both parties. This section documents the handshaking protocol involved.

Each player must manually confirm the action.
After a player has confirmed an action, he may cancel it as long as
he hasn't received a confirmation from the other party.

All messages arrive in the order sent.


States:
0. waiting
   !handshake.canceling && !handshake.localOk && !handshake.remoteOk
1. localOk (cancelable)          - sent CONFIRM1 (since last CANCEL)
   !handshake.canceling && handshake.localOk && !handshake.remoteOk
2. remoteOk (cancelable)         - received CONFIRM1
   !handshake.canceling && !handshake.localOk && handshake.remoteOk
3. committed                     - sent CONFIRM1 (since last CANCEL,
                                   received CONFIRM1,
                                   sent CONFIRM2 (since last CANCEL)
   !handshake.canceling && handshake.localOk && handshake.remoteOk
4. cancelWaiting                 - sent CANCEL
   handshake.canceling && !handshake.localOk && !handshake.remoteOk
5. cancelLocalOk                 - sent CANCEL and ready to send CONFIRM1,
                                   but received no CANCELACK
   handshake.canceling && handshake.localOk && !handshake.remoteOk
6. cancelRemoteOk                - sent CANCEL and received CONFIRM1,
                                   but received no CANCELACK
   handshake.canceling && !handshake.localOk && handshake.remoteOk
7. cancelCommitted               - sent CANCEL and ready to send CONFIRM2,
                                   received CONFIRM1,
                                   but received no CANCELACK
   handshake.canceling && handshake.localOk && handshake.remoteOk
8. done                          - sent and received CONFIRM1 and CONFIRM2
                                   (since last CANCEL)


Handshake messages:
- 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"
- CANCELACK - "received your CANCEL"
MESSAGE(x) indicates any other message.


From state 0: (waiting)
local confirmation         -> Send CONFIRM1,                  goto 1
local changes              -> Send MESSAGE(changes)           (goto 0)
received CONFIRM1          ->                                 goto 2
received MESSAGE(changes)  -> Process(changes)                (goto 0)

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)  -> Send CANCEL, Process(changes),  goto 4

From state 2: (remoteOk)
local confirmation         -> Send CONFIRM2,                  goto 3
local changes              -> Send MESSAGE(changes),          (goto 2)
received CANCEL            -> Send CANCELACK,                 goto 0

From state 3: (committed)
received CONFIRM2          ->                                 goto 8
received CANCEL            -> Send CANCELACK,                 goto 1

From state 4: (cancelWaiting)
local changes              -> Send MESSAGE(changes),          (goto 4)
local confirmation         ->                                 goto 5
received CONFIRM1          ->                                 goto 6
received CONFIRM2          ->                                 goto 6
received CANCELACK         ->                                 goto 0
received MESSAGE(changes)  -> Process(changes),               (goto 4)

From state 5: (cancelLocalOk)
local cancel               ->                                 goto 4
received CONFIRM1          ->                                 goto 7
received CONFIRM2          ->                                 goto 7
received CANCELACK         -> SEND CONFIRM1                   goto 1
received MESSAGE(changes)  -> Process(changes),               goto 4

From state 6: (cancelRemoteOk)
local confirmation         ->                                 goto 7
local changes              -> Send MESSAGE(changes),          (goto 6)
received CONFIRM2          ->                                 (goto 6)
received CANCEL            -> Send CANCELACK,                 goto 4
received CANCELACK         ->                                 goto 2

From state 7: (cancelCommitted)
received CONFIRM2          ->                                 (goto 7)
received CANCEL            -> Send CANCELACK,                 goto 5
received CANCELACK         -> Send CONFIRM2,                  goto 3

On receiving local confirmation, sending CONFIRM2 is a shortcut for
sending CONFIRM1 followed by CONFIRM2. Receiving CONFIRM2 from localOk
and cancelLocalOk is accepted just for this shortcut.


To prove there are no race conditions, I examine all the combinations
of states and messages that are underway. Whenever the order of actions
isn't fixed, the result should be the same (eg. recv(CONFIRM1) followed
by send(CANCEL) should leave the party in the same state as when
the send(CANCEL) preceded the recv(CONFIRM1)).
I also check whether it is possible for packets to arrive that
aren't expected.


============================================================================

"Reset" negotiation.

See src/sc2code/netplay/proto/reset.c


============================================================================

Battle ending negotiation.

This negotation consists of:
1. a 'Ready' negotiation, before stopping sending frame data
2. communication of each sides current battle frame count
3. the side running behind processes more frames to catch up


States:

0. Playing
1. localReady
2. remoteReady
3. countSent
4. catchingUp
5. awaitingCatchup

// Unfinished... partially described in readyForBattleEndPlayer()