Netplay
git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@2463 8092fc87-c524-0410-9efc-e669fe64eaf9
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
To use Netplay you need to pass some parameters to uqm. In future versions
|
||||
you should be able to set everything you need to set from within the
|
||||
game itself.
|
||||
|
||||
--netport1 specifies the port the bottom player will connect to or accept
|
||||
incoming connections on. If this parameter is not specified, 21837 will
|
||||
be used.
|
||||
Your firewall needs to be set up to allow TCP connections from/to
|
||||
the used port.
|
||||
--nethost1 specifies the name or ip number of the host to connect to for
|
||||
the bottom player. If this parameter is not specified, UQM will
|
||||
instead accept incoming connections.
|
||||
--netport2
|
||||
As --netport1, but for the top player
|
||||
--nethost2
|
||||
As --netport2, but for the bottom player
|
||||
--netdelay
|
||||
The desired input delay (in frames). A keypress or keyrelease
|
||||
will only take effect after this many frames. The result is that while
|
||||
higher values make your ship to seem to respond more sluggishly,
|
||||
they give the keypress information more time to reach the remote party.
|
||||
If the game stutters, this is because it is waiting for this
|
||||
keypress information to arrive, which is an indication that the
|
||||
input delay is too low.
|
||||
As SuperMelee runs at 24 frames per second, each frame delayed will
|
||||
delay the input by about 42ms.
|
||||
The delay used is the maximum of the desired value for both parties.
|
||||
The default is 0. Values lower than 4 will be hardly noticable
|
||||
in terms of responsiveness.
|
||||
Future versions may automatically decide on the best value to use.
|
||||
|
||||
In SuperMelee, there is now an extra control method, "network control".
|
||||
If you change the control method to that, and press LEFT, UQM will start
|
||||
paying attention to incoming connections or try to establish an outgoing
|
||||
connection, depending on the specified parameters. Pressing RIGHT will
|
||||
cancel connection attempts or abort an already established connection.
|
||||
You'll get a message when a connection has been established. In the mean
|
||||
time you can already set up your own team.
|
||||
The game won't start until both parties have confirmed the current setup.
|
||||
If one player moves away from the 'Battle!' button, both parties need to
|
||||
reconfirm.
|
||||
|
||||
Reference in New Issue
Block a user