git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@2463 8092fc87-c524-0410-9efc-e669fe64eaf9
This commit is contained in:
meep-eep
2006-10-26 22:17:03 +00:00
parent ea1657a589
commit 620c00c52c
117 changed files with 12189 additions and 425 deletions
+21
View File
@@ -0,0 +1,21 @@
As the game currently unfortunately works with polling, this is how
to integrate network handling with the game.
In the function called periodically by DoInput(), there should be a call
to netInput() somewhere at the beginning, and a call to flushPacketQueues()
somewhere at the end.
netInput() checks all connections for incoming packets, and calls
the appropriate packet handlers.
flushPacketQueues() sends all pending packets on their way, for all
connections.
In between, you can call functions that enqueue packets.
You would also check the network state here to determine whether you need to
act on some packet that has been delivered to the local party.
+223
View File
@@ -0,0 +1,223 @@
There are three 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.
- Only speak in your own turn ("Turn")
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.
============================================================================
"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
============================================================================
"Turn" 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.
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
Messages:
- ENDTURN - "this party ready to change turns"
From state 0 (myTurn):
local decision -> Send ENDTURN, goto 1
received ENDTURN -> Send ENDTURN, goto 2
From state 1 (endMyTurn):
received ENDTURN -> Send ENDTURN, goto 2
From state 2 (yourTurn):
local decision -> Send ENDTURN, goto 3
received ENDTURN -> Send ENDTURN, goto 0
From state 3 (endYourTurn):
received ENDTURN -> Send ENDTURN, goto 0
============================================================================
"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 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) -> Process(changes), Send CANCEL, 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.
============================================================================
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()
+63
View File
@@ -0,0 +1,63 @@
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.
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.
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.
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.
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.
NetState_preBattle is used for non-interactive battle negotiations.
One side sends the random seed; the other receives it.
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
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 is where a side may select his 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.
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.
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.
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.
When a connection is aborted, the state is returned to NetState_unconnected.
+103
View File
@@ -0,0 +1,103 @@
High priority items:
- test compilation on release mode (on both Linux and Windows)
Medium-priority:
- Test disconnection at various points.
Whenever there's a call to NetInput(), connections may get lost, and
this needs to be checked.
- Setup menu for network options.
- Don't check PlayerControl in netmelee etc. Make it an invariant that
(netConnections[player] != NULL) if
((PlayerControl[player] & NETWORK_CONTROL) == NETWORK_CONTROL)
- For the battle ending synchronisation, set the end at at least
getBattleInputDelay() + 1 frames in the future (instead of just 1),
so that there will be no hickup during the end synchronisation.
Also check this value for incoming packets.
- Disconnect notification.
- If a player only moves away from 'Battle!' there's no need for the other
to have to reconfirm.
- decent pause handling
- make compilation of crc.c and checksum.c conditional.
- negotiate checksum interval
- disconnect during battle will trigger the setup disconnect notification.
Low-priority:
- Different coloured icons for unconnected/connecting/connected.
- Send error packets; at least when there's a protocol version mismatch.
- Check whether the random seed and frame delay have been agreed before
continuing (in doConfirmSettings).
- Replacement for TOS. It is IPv4 only.
- Integrate network check functions with doInput
It will be easy to get rid of the separate threads then too.
- The state changes from interBattle to interBattle. That shouldn't happen,
but it doesn't seem to cause any problems. Need to investigate.
Addition: negotiateReadyConnections() is called again just to make sure
all sides pass this checkpoint. This is not a problem. It should be
documented in STATES though.
- More files define NETCONNECTION_INTERNAL than they should.
- voice transmission during the game (using an external lib)
read ramjee94adaptive.pdf
- Keep-alive packets. Store time of last packet sent, use alarms to determine
when to send the next one. Count received messages?
Future improvements/optimisations:
- For BSD sockets: use dup2() to move fds to lower values, so that less fds
have to be checked on select().
- Use writev() to send multiple packets in one syscall, instead of
calling send() for each packet.
- Refusing games with both parties network controlled is not always
necessary. In theory it should be possible to have a client work
as "proxy". The client can watch as the actual players play the game.
Checking for "loops" would be tricky (eventually there should be a human
or computer controller for each side).
- Concurrent selection of ships. Note that if this is handled properly, it
will also be easy to take care of the "Allow Shofixti to choose last" bug.
Note that one party will still have to send his choice to the other side
first, which may be "eploited". Encryption would take care of this,
but at the least make sure the same player who gets to chose first
every time.
- meta-server. Use HTTP? Existing libs can be used, no problems with NAT,
human-readable. Speed is not an issue.
- move to UDP. Repeat past battleinput packets for each new packet that
is sent until they are confirmed.
Bugs:
- as positions are dependant on the screen resolution, you won't be able
to keep sync on games with a different resolution.
- Both sides need identical battle frame rates. This value is not
negotiated.
- When melee selection is aborted, the game crashes.
Final actions:
- Check out TODO, XXX, WORK tags
- memleak testing
- check for and remove mtrace()/muntrace() calls.
- update documentation
- FILES, also note which part of the separation they are in
- check coding style (search for '\>(')
- compile with maximum warnings
When done:
- cvs diff
- Update uqm version number
- Update changelog
To put in the announcement of Netplay:
- Slow connections is acceptable. Packet loss isn't.
Bugs and todos unrelated to netplay.
- When you insert a new ship, the cursor moves to the next square,
but the picture at the right doesn't change.
- The "Battle!" icon is not positioned correctly.
- other player being able to choose the next ship after 3 seconds
of inactivity
- DoRunAway() shouldn't be handled in ProcessInput()