diff options
Diffstat (limited to 'src/uqm/supermelee/netplay/netmelee.c')
-rw-r--r-- | src/uqm/supermelee/netplay/netmelee.c | 740 |
1 files changed, 740 insertions, 0 deletions
diff --git a/src/uqm/supermelee/netplay/netmelee.c b/src/uqm/supermelee/netplay/netmelee.c new file mode 100644 index 0000000..e9368fd --- /dev/null +++ b/src/uqm/supermelee/netplay/netmelee.c @@ -0,0 +1,740 @@ +/* + * Copyright 2006 Serge van den Boom <svdb@stack.nl> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define PORT_WANT_ERRNO +#include "port.h" +#include "netmelee.h" +#include "libs/async.h" +#include "libs/callback.h" +#include "libs/log.h" +#include "libs/net.h" +#include "netinput.h" +#include "netmisc.h" +#include "netsend.h" +#include "notify.h" +#include "packetq.h" +#include "proto/npconfirm.h" +#include "proto/ready.h" +#include "proto/reset.h" + +#include "../../battlecontrols.h" + // for NetworkInputContext +#include "../../controls.h" + // for BATTLE_INPUT_STATE +#include "../../init.h" + // for NUM_PLAYERS +#include "../../globdata.h" + // for GLOBAL + +#include <errno.h> +#include <stdlib.h> + + +//////////////////////////////////////////////////////////////////////////// + + +NetConnection *netConnections[NUM_PLAYERS]; +size_t numNetConnections; + +void +addNetConnection(NetConnection *conn, int playerNr) { + netConnections[playerNr] = conn; + numNetConnections++; +} + +void +removeNetConnection(int playerNr) { + netConnections[playerNr] = NULL; + numNetConnections--; +} + +size_t +getNumNetConnections(void) { + return numNetConnections; +} + +// If the callback function returns 'false', the function will immediately +// return with 'false'. Otherwise it will return 'true' after calling +// the callback function for each connected player. +bool +forEachConnectedPlayer(ForEachConnectionCallback callback, void *arg) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + if (!(*callback)(conn, arg)) + return false; + } + return true; +} + +void +closeAllConnections(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + + if (conn != NULL) + closePlayerNetworkConnection(player); + } +} + +void +closeDisconnectedConnections(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + + if (conn != NULL && !NetConnection_isConnected(conn)) + closePlayerNetworkConnection(player); + } +} + +//////////////////////////////////////////////////////////////////////////// + + +struct melee_state * +NetMelee_getMeleeState(NetConnection *conn) { + if (NetConnection_getState(conn) > NetState_connecting) { + BattleStateData *battleStateData = + (BattleStateData *) NetConnection_getStateData(conn); + return battleStateData->meleeState; + } else { + return (struct melee_state *) NetConnection_getExtra(conn); + } +} + +struct battlestate_struct * +NetMelee_getBattleState(NetConnection *conn) { + if (NetConnection_getState(conn) > NetState_connecting) { + BattleStateData *battleStateData = + (BattleStateData *) NetConnection_getStateData(conn); + return battleStateData->battleState; + } else { + return NULL; + } +} + +//////////////////////////////////////////////////////////////////////////// + +static inline void +netInputAux(uint32 timeoutMs) { + NetManager_process(&timeoutMs); + // This may cause more packets to be queued, hence the + // flushPacketQueues(). + Async_process(); + flushPacketQueues(); + // During the flush, a disconnect may be noticed, which triggers + // another callback. It must be handled immediately, before + // another flushPacketQueue() can occur, which would not know + // that the socket is no longer valid. + // TODO: modify the close handling so this order isn't + // necessary. + Callback_process(); +} + +// Check the network connections for input. +void +netInput(void) { + netInputAux(0); +} + +void +netInputBlocking(uint32 timeoutMs) { + uint32 nextAsyncMs; + + nextAsyncMs = Async_timeBeforeNextMs(); + if (nextAsyncMs < timeoutMs) + timeoutMs = nextAsyncMs; + + netInputAux(timeoutMs); +} + + +//////////////////////////////////////////////////////////////////////////// + + +// Send along all pending network packets. +void +flushPacketQueues(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn; + int flushStatus; + + conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + flushStatus = flushPacketQueue(conn); + if (flushStatus == -1 && errno != EAGAIN && errno != EWOULDBLOCK) + closePlayerNetworkConnection(player); + } +} + +void +confirmConnections(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + Netplay_confirm(conn); + } +} + +void +cancelConfirmations(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + Netplay_cancelConfirmation(conn); + } +} + +void +connectionsLocalReady(NetConnection_ReadyCallback callback, void *arg) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + Netplay_localReady(conn, callback, arg, true); + } +} + +bool +allConnected(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + return false; + } + return true; +} + +void +initBattleStateDataConnections(void) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + BattleStateData *battleStateData; + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + battleStateData = + (BattleStateData *) NetConnection_getStateData(conn); + battleStateData->endFrameCount = 0; + } +} + +void +setBattleStateConnections(struct battlestate_struct *bs) { + COUNT player; + + for (player = 0; player < NUM_PLAYERS; player++) + { + BattleStateData *battleStateData; + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + battleStateData = + (BattleStateData *) NetConnection_getStateData(conn); + battleStateData->battleState = bs; + } +} + +BATTLE_INPUT_STATE +networkBattleInput(NetworkInputContext *context, STARSHIP *StarShipPtr) { + BattleInputBuffer *bib = getBattleInputBuffer(context->playerNr); + BATTLE_INPUT_STATE result; + + for (;;) { + bool ok; + +#if 0 + // This is a useful debugging trick. By enabling this #if + // block, this side will always lag the maximum number of frames + // behind the other side. When the remote side stops on some event + // (a breakpoint or so), this side will stop too, waiting for input + // in the loop below, but it won't have processed the frame that + // triggered the event yet. If you then jump over this 'if' + // statement here, you can walk through the decisive frames + // manually. Works best with no input delay. + if (bib->size <= getBattleInputDelay() + 1) { + ok = false; + } else +#endif + ok = BattleInputBuffer_pop(bib, &result); + // Get the input from the front of the + // buffer. + if (ok) + break; + + { + NetConnection *conn = netConnections[context->playerNr]; + + // First try whether there is incoming data, without blocking. + // If there isn't any, only then give a warning, and then + // block after all. + netInput(); + if (!NetConnection_isConnected(conn)) + { + // Connection aborted. + GLOBAL(CurrentActivity) |= CHECK_ABORT; + return (BATTLE_INPUT_STATE) 0; + } + + if (GLOBAL(CurrentActivity) & CHECK_ABORT) + return (BATTLE_INPUT_STATE) 0; + +#if 0 + log_add(log_Warning, "NETPLAY: [%d] stalling for " + "network input. Increase the input delay if this " + "happens a lot.\n", context->playerNr); +#endif +#define MAX_BLOCK_TIME 500 + netInputBlocking(MAX_BLOCK_TIME); + if (!NetConnection_isConnected(conn)) + { + // Connection aborted. + GLOBAL(CurrentActivity) |= CHECK_ABORT; + return (BATTLE_INPUT_STATE) 0; + } + } + } + + (void) StarShipPtr; + return result; +} + +static void +deleteConnectionCallback(NetConnection *conn) { + removeNetConnection(NetConnection_getPlayerNr(conn)); +} + +NetConnection * +openPlayerNetworkConnection(COUNT player, void *extra) { + NetConnection *conn; + + assert(netConnections[player] == NULL); + + conn = NetConnection_open(player, + &netplayOptions.peer[player], NetMelee_connectCallback, + NetMelee_closeCallback, NetMelee_errorCallback, + deleteConnectionCallback, extra); + + addNetConnection(conn, player); + return conn; +} + +void +closePlayerNetworkConnection(COUNT player) { + assert(netConnections[player] != NULL); + + NetConnection_close(netConnections[player]); +} + +bool +setupInputDelay(size_t localInputDelay) { + COUNT player; + bool haveNetworkPlayer = false; + // We have at least one network controlled player. + size_t inputDelay = 0; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) + continue; + + haveNetworkPlayer = true; + if (NetConnection_getInputDelay(conn) > inputDelay) + inputDelay = NetConnection_getInputDelay(conn); + } + + if (haveNetworkPlayer && inputDelay < localInputDelay) + inputDelay = localInputDelay; + + setBattleInputDelay(inputDelay); + return true; +} + +static bool +setStateConnection(NetConnection *conn, void *arg) { + const NetState *state = (NetState *) arg; + NetConnection_setState(conn, *state); + return true; +} + +bool +setStateConnections(NetState state) { + return forEachConnectedPlayer(setStateConnection, &state); +} + +static bool +sendAbortConnection(NetConnection *conn, void *arg) { + const NetplayAbortReason *reason = (NetplayAbortReason *) arg; + sendAbort(conn, *reason); + return true; +} + +bool +sendAbortConnections(NetplayAbortReason reason) { + return forEachConnectedPlayer(sendAbortConnection, &reason); +} + +static bool +resetConnection(NetConnection *conn, void *arg) { + const NetplayResetReason *reason = (NetplayResetReason *) arg; + Netplay_localReset(conn, *reason); + return true; +} + +bool +resetConnections(NetplayResetReason reason) { + return forEachConnectedPlayer(resetConnection, &reason); +} + +///////////////////////////////////////////////////////////////////////////// + +typedef struct { + NetConnection_ReadyCallback readyCallback; + void *readyCallbackArg; + bool notifyRemote; +} LocalReadyConnectionArg; + +static bool +localReadyConnection(NetConnection *conn, void *arg) { + LocalReadyConnectionArg *readyArg = (LocalReadyConnectionArg *) arg; + Netplay_localReady(conn, readyArg->readyCallback, + readyArg->readyCallbackArg, readyArg->notifyRemote); + return true; +} + +bool +localReadyConnections(NetConnection_ReadyCallback readyCallback, + void *readyArg, bool notifyRemote) { + LocalReadyConnectionArg arg; + arg.readyCallback = readyCallback; + arg.readyCallbackArg = readyArg; + arg.notifyRemote = notifyRemote; + + return forEachConnectedPlayer(localReadyConnection, &arg); +} + + +///////////////////////////////////////////////////////////////////////////// + +#define NETWORK_POLL_DELAY (ONE_SECOND / 24) + +typedef struct NegotiateReadyState NegotiateReadyState; +struct NegotiateReadyState { + // Common fields of INPUT_STATE_DESC, from which this structure + // "inherits". + BOOLEAN(*InputFunc)(void *pInputState); + + NetConnection *conn; + NetState nextState; + bool done; +}; + +static BOOLEAN +negotiateReadyInputFunc(NegotiateReadyState *state) { + netInputBlocking(NETWORK_POLL_DELAY); + // The timing out is necessary so that immediate key presses get + // handled while we wait. If we could do without the timeout, + // we wouldn't even need negotiateReadyInputFunc() and the + // DoInput() call. + + // No need to call flushPacketQueues(); nothing needs to be sent + // right now. + + if (!NetConnection_isConnected(state->conn)) + return FALSE; + + return !state->done; +} + +// Called when both sides are ready +static void +negotiateReadyBothReadyCallback(NetConnection *conn, void *arg) { + NegotiateReadyState *state =(NegotiateReadyState *) arg; + + NetConnection_setState(conn, state->nextState); + // This has to be done immediately, as more packets in the + // receive queue may be handled by the netInput() call that + // triggered this callback. + // This is the reason for the nextState argument to + // negotiateReady(); setting the state after the call to + // negotiateReady() would be too late. + state->done = true; +} + +bool +negotiateReady(NetConnection *conn, bool notifyRemote, NetState nextState) { + NegotiateReadyState state; + state.InputFunc = (BOOLEAN(*)(void *)) negotiateReadyInputFunc; + state.conn = conn; + state.nextState = nextState; + state.done = false; + + Netplay_localReady(conn, negotiateReadyBothReadyCallback, + (void *) &state, notifyRemote); + flushPacketQueue(conn); + if (!state.done) + DoInput(&state, FALSE); + + return NetConnection_isConnected(conn); +} + +// Wait for all connections to get ready. +// XXX: Right now all connections are handled one by one. Handling them all +// at once would be faster but would require more work, which is +// not worth it as the time is minimal and this function is not +// time critical. +bool +negotiateReadyConnections(bool notifyRemote, NetState nextState) { + COUNT player; + size_t numDisconnected = 0; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) { + numDisconnected++; + continue; + } + + negotiateReady(conn, notifyRemote, nextState); + } + + return numDisconnected == 0; +} + +typedef struct WaitReadyState WaitReadyState; +struct WaitReadyState { + // Common fields of INPUT_STATE_DESC, from which this structure + // "inherits". + BOOLEAN (*InputFunc)(void *pInputState); + + NetConnection *conn; + NetConnection_ReadyCallback readyCallback; + void *readyCallbackArg; + bool done; +}; + +static void +waitReadyCallback(NetConnection *conn, void *arg) { + WaitReadyState *state =(WaitReadyState *) arg; + state->done = true; + + // Call the original callback. + state->readyCallback(conn, state->readyCallbackArg); +} + +static BOOLEAN +waitReadyInputFunc(WaitReadyState *state) { + netInputBlocking(NETWORK_POLL_DELAY); + // The timing out is necessary so that immediate key presses get + // handled while we wait. If we could do without the timeout, + // we wouldn't even need negotiateReadyInputFunc() and the + // DoInput() call. + + // No need to call flushPacketQueues(); nothing needs to be sent + // right now. + + if (!NetConnection_isConnected(state->conn)) + return FALSE; + + return !state->done; +} + +bool +waitReady(NetConnection *conn) { + WaitReadyState state; + state.InputFunc =(BOOLEAN(*)(void *)) waitReadyInputFunc; + state.conn = conn; + state.readyCallback = NetConnection_getReadyCallback(conn); + state.readyCallbackArg = NetConnection_getReadyCallbackArg(conn); + state.done = false; + + NetConnection_setReadyCallback(conn, waitReadyCallback, (void *) &state); + + DoInput(&state, FALSE); + + return NetConnection_isConnected(conn); +} + + +//////////////////////////////////////////////////////////////////////////// + +typedef struct WaitResetState WaitResetState; +struct WaitResetState { + // Common fields of INPUT_STATE_DESC, from which this structure + // "inherits". + BOOLEAN(*InputFunc)(void *pInputState); + + NetConnection *conn; + NetState nextState; + bool done; +}; + +static BOOLEAN +waitResetInputFunc(WaitResetState *state) { + netInputBlocking(NETWORK_POLL_DELAY); + // The timing out is necessary so that immediate key presses get + // handled while we wait. If we could do without the timeout, + // we wouldn't even need waitResetInputFunc() and the + // DoInput() call. + + // No need to call flushPacketQueues(); nothing needs to be sent + // right now. + + if (!NetConnection_isConnected(state->conn)) + return FALSE; + + return !state->done; +} + +// Called when both sides are reset. +static void +waitResetBothResetCallback(NetConnection *conn, void *arg) { + WaitResetState *state = (WaitResetState *) arg; + + if (state->nextState != (NetState) -1) { + NetConnection_setState(conn, state->nextState); + // This has to be done immediately, as more packets in the + // receive queue may be handled by the netInput() call that + // triggered this callback. + // This is the reason for the nextState argument to + // waitReset(); setting the state after the call to + // waitReset() would be too late. + } + state->done = true; +} + +bool +waitReset(NetConnection *conn, NetState nextState) { + WaitResetState state; + state.InputFunc = (BOOLEAN(*)(void *)) waitResetInputFunc; + state.conn = conn; + state.nextState = nextState; + state.done = false; + + Netplay_setResetCallback(conn, waitResetBothResetCallback, + (void *) &state); + if (state.done) + goto out; + + + if (!Netplay_isLocalReset(conn)) { + Netplay_localReset(conn, ResetReason_manualReset); + flushPacketQueue(conn); + } + + if (!state.done) + DoInput(&state, FALSE); + +out: + return NetConnection_isConnected(conn); +} + +// Wait until we have received a reset packet from all connections. If we +// ourselves have not sent a reset packet, one is sent, with reason +// 'manualReset'. +// XXX: Right now all connections are handled one by one. Handling them all +// at once would be faster but would require more work, which is +// not worth it as the time is minimal and this function is not +// time critical. +// Use '(NetState) -1' for nextState to keep the current state. +bool +waitResetConnections(NetState nextState) { + COUNT player; + size_t numDisconnected = 0; + + for (player = 0; player < NUM_PLAYERS; player++) + { + NetConnection *conn = netConnections[player]; + if (conn == NULL) + continue; + + if (!NetConnection_isConnected(conn)) { + numDisconnected++; + continue; + } + + waitReset(conn, nextState); + } + + return numDisconnected == 0; +} + +//////////////////////////////////////////////////////////////////////////// + |