mirror of https://github.com/OpenRCT2/OpenRCT2.git
Merge pull request #9914 from ZehMatt/refactor/gameaction-queue
Refactor network queue out and one desync fix
This commit is contained in:
commit
af2ad8045c
|
@ -24,6 +24,7 @@
|
|||
#include "PlatformEnvironment.h"
|
||||
#include "ReplayManager.h"
|
||||
#include "Version.h"
|
||||
#include "actions/GameAction.h"
|
||||
#include "audio/AudioContext.h"
|
||||
#include "audio/audio.h"
|
||||
#include "config/Config.h"
|
||||
|
@ -153,6 +154,7 @@ namespace OpenRCT2
|
|||
_objectManager->UnloadAll();
|
||||
}
|
||||
|
||||
GameActions::ClearQueue();
|
||||
network_close();
|
||||
window_close_all();
|
||||
gfx_object_check_all_images_freed();
|
||||
|
|
|
@ -705,6 +705,7 @@ void game_load_init()
|
|||
|
||||
if (network_get_mode() != NETWORK_MODE_CLIENT)
|
||||
{
|
||||
GameActions::ClearQueue();
|
||||
reset_sprite_spatial_index();
|
||||
}
|
||||
reset_all_sprite_quadrant_placements();
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "Input.h"
|
||||
#include "OpenRCT2.h"
|
||||
#include "ReplayManager.h"
|
||||
#include "actions/GameAction.h"
|
||||
#include "config/Config.h"
|
||||
#include "interface/Screenshot.h"
|
||||
#include "localisation/Date.h"
|
||||
|
@ -150,6 +151,8 @@ void GameState::Update()
|
|||
|
||||
// Special case because we set numUpdates to 0, otherwise in game_logic_update.
|
||||
network_process_pending();
|
||||
|
||||
GameActions::ProcessQueue();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -298,10 +301,9 @@ void GameState::UpdateLogic()
|
|||
gLastAutoSaveUpdate = Platform::GetTicks();
|
||||
}
|
||||
|
||||
// Separated out processing commands in network_update which could call scenario_rand where gInUpdateCode is false.
|
||||
// All commands that are received are first queued and then executed where gInUpdateCode is set to true.
|
||||
network_process_pending();
|
||||
GameActions::ProcessQueue();
|
||||
|
||||
network_process_pending();
|
||||
network_flush();
|
||||
|
||||
gCurrentTicks++;
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "../platform/platform.h"
|
||||
#include "../scenario/Scenario.h"
|
||||
#include "../world/Park.h"
|
||||
#include "../world/Scenery.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
@ -46,7 +47,35 @@ GameActionResult::GameActionResult(GA_ERROR error, rct_string_id title, rct_stri
|
|||
|
||||
namespace GameActions
|
||||
{
|
||||
struct QueuedGameAction
|
||||
{
|
||||
uint32_t tick;
|
||||
uint32_t uniqueId;
|
||||
GameAction::Ptr action;
|
||||
|
||||
explicit QueuedGameAction(uint32_t t, std::unique_ptr<GameAction>&& ga, uint32_t id)
|
||||
: tick(t)
|
||||
, uniqueId(id)
|
||||
, action(std::move(ga))
|
||||
{
|
||||
}
|
||||
|
||||
bool operator<(const QueuedGameAction& comp) const
|
||||
{
|
||||
// First sort by tick
|
||||
if (tick < comp.tick)
|
||||
return true;
|
||||
if (tick > comp.tick)
|
||||
return false;
|
||||
|
||||
// If the ticks are equal sort by commandIndex
|
||||
return uniqueId < comp.uniqueId;
|
||||
}
|
||||
};
|
||||
|
||||
static GameActionFactory _actions[GAME_COMMAND_COUNT];
|
||||
static std::multiset<QueuedGameAction> _actionQueue;
|
||||
static uint32_t _nextUniqueId = 0;
|
||||
|
||||
GameActionFactory Register(uint32_t id, GameActionFactory factory)
|
||||
{
|
||||
|
@ -66,6 +95,77 @@ namespace GameActions
|
|||
return false;
|
||||
}
|
||||
|
||||
void Enqueue(const GameAction* ga, uint32_t tick)
|
||||
{
|
||||
auto action = Clone(ga);
|
||||
Enqueue(std::move(action), tick);
|
||||
}
|
||||
|
||||
void Enqueue(GameAction::Ptr&& ga, uint32_t tick)
|
||||
{
|
||||
if (ga->GetPlayer() == -1 && network_get_mode() != NETWORK_MODE_NONE)
|
||||
{
|
||||
// Server can directly invoke actions and will have no player id assigned
|
||||
// as that normally happens when receiving them over network.
|
||||
ga->SetPlayer(network_get_current_player_id());
|
||||
}
|
||||
_actionQueue.emplace(tick, std::move(ga), _nextUniqueId++);
|
||||
}
|
||||
|
||||
void ProcessQueue()
|
||||
{
|
||||
const uint32_t currentTick = gCurrentTicks;
|
||||
|
||||
while (_actionQueue.begin() != _actionQueue.end())
|
||||
{
|
||||
// run all the game commands at the current tick
|
||||
const QueuedGameAction& queued = *_actionQueue.begin();
|
||||
|
||||
if (network_get_mode() == NETWORK_MODE_CLIENT)
|
||||
{
|
||||
if (queued.tick < currentTick)
|
||||
{
|
||||
// This should never happen.
|
||||
Guard::Assert(
|
||||
false,
|
||||
"Discarding game action from tick behind current tick, ID: %08X, Action Tick: %08X, Current Tick: "
|
||||
"%08X\n",
|
||||
queued.uniqueId, queued.tick, currentTick);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove ghost scenery so it doesn't interfere with incoming network command
|
||||
switch (queued.action->GetType())
|
||||
{
|
||||
case GAME_COMMAND_PLACE_WALL:
|
||||
case GAME_COMMAND_PLACE_LARGE_SCENERY:
|
||||
case GAME_COMMAND_PLACE_BANNER:
|
||||
case GAME_COMMAND_PLACE_SCENERY:
|
||||
scenery_remove_ghost_tool_placement();
|
||||
break;
|
||||
}
|
||||
|
||||
GameAction* action = queued.action.get();
|
||||
action->SetFlags(action->GetFlags() | GAME_COMMAND_FLAG_NETWORKED);
|
||||
|
||||
Guard::Assert(action != nullptr);
|
||||
|
||||
GameActionResult::Ptr result = Execute(action);
|
||||
if (result->Error == GA_ERROR::OK && network_get_mode() == NETWORK_MODE_SERVER)
|
||||
{
|
||||
// Relay this action to all other clients.
|
||||
network_send_game_action(action);
|
||||
}
|
||||
|
||||
_actionQueue.erase(_actionQueue.begin());
|
||||
}
|
||||
}
|
||||
|
||||
void ClearQueue()
|
||||
{
|
||||
_actionQueue.clear();
|
||||
}
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
static bool initialized = false;
|
||||
|
@ -274,7 +374,7 @@ namespace GameActions
|
|||
if (!(actionFlags & GA_FLAGS::CLIENT_ONLY) && !(flags & GAME_COMMAND_FLAG_NETWORKED))
|
||||
{
|
||||
log_verbose("[%s] GameAction::Execute %s (Queue)", GetRealm(), action->GetName());
|
||||
network_enqueue_game_action(action);
|
||||
Enqueue(action, gCurrentTicks);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -254,6 +254,12 @@ namespace GameActions
|
|||
void Initialize();
|
||||
void Register();
|
||||
bool IsValidId(uint32_t id);
|
||||
|
||||
void Enqueue(const GameAction* ga, uint32_t tick);
|
||||
void Enqueue(GameAction::Ptr&& ga, uint32_t tick);
|
||||
void ProcessQueue();
|
||||
void ClearQueue();
|
||||
|
||||
GameAction::Ptr Create(uint32_t id);
|
||||
GameAction::Ptr Clone(const GameAction* action);
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
// This string specifies which version of network stream current build uses.
|
||||
// It is used for making sure only compatible builds get connected, even within
|
||||
// single OpenRCT2 version.
|
||||
#define NETWORK_STREAM_VERSION "6"
|
||||
#define NETWORK_STREAM_VERSION "7"
|
||||
#define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION
|
||||
|
||||
static Peep* _pickup_peep = nullptr;
|
||||
|
@ -134,8 +134,6 @@ public:
|
|||
void ProcessPlayerList();
|
||||
void ProcessPlayerInfo();
|
||||
void ProcessDisconnectedClients();
|
||||
void ProcessGameCommands();
|
||||
void EnqueueGameAction(const GameAction* action);
|
||||
std::vector<std::unique_ptr<NetworkPlayer>>::iterator GetPlayerIteratorByID(uint8_t id);
|
||||
NetworkPlayer* GetPlayerByID(uint8_t id);
|
||||
std::vector<std::unique_ptr<NetworkGroup>>::iterator GetGroupIteratorByID(uint8_t id);
|
||||
|
@ -233,44 +231,6 @@ private:
|
|||
bool LoadMap(IStream* stream);
|
||||
bool SaveMap(IStream* stream, const std::vector<const ObjectRepositoryItem*>& objects) const;
|
||||
|
||||
struct GameCommand
|
||||
{
|
||||
GameCommand(uint32_t t, uint32_t* args, uint8_t p, uint8_t cb, uint32_t id)
|
||||
{
|
||||
tick = t;
|
||||
playerid = p;
|
||||
action = nullptr;
|
||||
commandIndex = id;
|
||||
}
|
||||
|
||||
GameCommand(uint32_t t, std::unique_ptr<GameAction>&& ga, uint32_t id)
|
||||
{
|
||||
tick = t;
|
||||
action = std::move(ga);
|
||||
commandIndex = id;
|
||||
}
|
||||
|
||||
~GameCommand()
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t tick = 0;
|
||||
GameAction::Ptr action;
|
||||
uint8_t playerid = 0;
|
||||
uint32_t commandIndex = 0;
|
||||
bool operator<(const GameCommand& comp) const
|
||||
{
|
||||
// First sort by tick
|
||||
if (tick < comp.tick)
|
||||
return true;
|
||||
if (tick > comp.tick)
|
||||
return false;
|
||||
|
||||
// If the ticks are equal sort by commandIndex
|
||||
return commandIndex < comp.commandIndex;
|
||||
}
|
||||
};
|
||||
|
||||
struct PlayerListUpdate
|
||||
{
|
||||
std::vector<NetworkPlayer> players;
|
||||
|
@ -302,7 +262,6 @@ private:
|
|||
uint32_t last_ping_sent_time = 0;
|
||||
uint8_t player_id = 0;
|
||||
std::list<std::unique_ptr<NetworkConnection>> client_connection_list;
|
||||
std::multiset<GameCommand> game_command_queue;
|
||||
std::vector<uint8_t> chunk_buffer;
|
||||
std::string _host;
|
||||
uint16_t _port = 0;
|
||||
|
@ -311,7 +270,6 @@ private:
|
|||
MemoryStream _serverGameState;
|
||||
uint32_t server_connect_time = 0;
|
||||
uint8_t default_group = 0;
|
||||
uint32_t _commandId;
|
||||
uint32_t _actionId;
|
||||
uint32_t _lastUpdateTime = 0;
|
||||
uint32_t _currentDeltaTime = 0;
|
||||
|
@ -368,7 +326,6 @@ Network::Network()
|
|||
mode = NETWORK_MODE_NONE;
|
||||
status = NETWORK_STATUS_NONE;
|
||||
last_ping_sent_time = 0;
|
||||
_commandId = 0;
|
||||
_actionId = 0;
|
||||
client_command_handlers.resize(NETWORK_COMMAND_MAX, nullptr);
|
||||
client_command_handlers[NETWORK_COMMAND_AUTH] = &Network::Client_Handle_AUTH;
|
||||
|
@ -457,7 +414,7 @@ void Network::Close()
|
|||
CloseConnection();
|
||||
|
||||
client_connection_list.clear();
|
||||
game_command_queue.clear();
|
||||
GameActions::ClearQueue();
|
||||
player_list.clear();
|
||||
group_list.clear();
|
||||
_serverTickData.clear();
|
||||
|
@ -1917,7 +1874,6 @@ void Network::ProcessPacket(NetworkConnection& connection, NetworkPacket& packet
|
|||
// This is called at the end of each game tick, this where things should be processed that affects the game state.
|
||||
void Network::ProcessPending()
|
||||
{
|
||||
ProcessGameCommands();
|
||||
if (GetMode() == NETWORK_MODE_SERVER)
|
||||
{
|
||||
ProcessDisconnectedClients();
|
||||
|
@ -2038,76 +1994,6 @@ void Network::ProcessDisconnectedClients()
|
|||
}
|
||||
}
|
||||
|
||||
void Network::ProcessGameCommands()
|
||||
{
|
||||
while (game_command_queue.begin() != game_command_queue.end())
|
||||
{
|
||||
// run all the game commands at the current tick
|
||||
const GameCommand& gc = (*game_command_queue.begin());
|
||||
|
||||
if (mode == NETWORK_MODE_CLIENT)
|
||||
{
|
||||
if (game_command_queue.begin()->tick < gCurrentTicks)
|
||||
{
|
||||
// Having old command from a tick where we have not been active yet or malicious server,
|
||||
// the command is useless so lets not keep it.
|
||||
log_warning(
|
||||
"Discarding game command from tick behind current tick, CMD: %08X, CMD Tick: %08X, Current Tick: %08X\n",
|
||||
gc.commandIndex, gc.tick, gCurrentTicks);
|
||||
|
||||
game_command_queue.erase(game_command_queue.begin());
|
||||
|
||||
// At this point we should not return, would add the possibility to skip commands this tick.
|
||||
continue;
|
||||
}
|
||||
|
||||
// exit the game command processing loop to still have a chance at finding desync.
|
||||
if (game_command_queue.begin()->tick != gCurrentTicks)
|
||||
break;
|
||||
}
|
||||
|
||||
if (gc.action != nullptr)
|
||||
{
|
||||
// Remove ghost scenery so it doesn't interfere with incoming network command
|
||||
switch (gc.action->GetType())
|
||||
{
|
||||
case GAME_COMMAND_PLACE_WALL:
|
||||
case GAME_COMMAND_PLACE_LARGE_SCENERY:
|
||||
case GAME_COMMAND_PLACE_BANNER:
|
||||
case GAME_COMMAND_PLACE_SCENERY:
|
||||
scenery_remove_ghost_tool_placement();
|
||||
break;
|
||||
}
|
||||
|
||||
GameAction* action = gc.action.get();
|
||||
action->SetFlags(action->GetFlags() | GAME_COMMAND_FLAG_NETWORKED);
|
||||
|
||||
Guard::Assert(action != nullptr);
|
||||
|
||||
GameActionResult::Ptr result = GameActions::Execute(action);
|
||||
if (result->Error == GA_ERROR::OK)
|
||||
{
|
||||
Server_Send_GAME_ACTION(action);
|
||||
Server_Send_PLAYERINFO(action->GetPlayer());
|
||||
}
|
||||
}
|
||||
game_command_queue.erase(game_command_queue.begin());
|
||||
}
|
||||
}
|
||||
|
||||
void Network::EnqueueGameAction(const GameAction* action)
|
||||
{
|
||||
std::unique_ptr<GameAction> ga = GameActions::Clone(action);
|
||||
if (ga->GetPlayer() == -1 && GetMode() != NETWORK_MODE_NONE)
|
||||
{
|
||||
// Server can directly invoke actions and will have no player id assigned
|
||||
// as that normally happens when receiving them over network.
|
||||
ga->SetPlayer(network_get_current_player_id());
|
||||
}
|
||||
|
||||
game_command_queue.emplace(gCurrentTicks, std::move(ga), _commandId++);
|
||||
}
|
||||
|
||||
void Network::AddClient(std::unique_ptr<ITcpSocket>&& socket)
|
||||
{
|
||||
// Log connection info.
|
||||
|
@ -2722,6 +2608,14 @@ void Network::Client_Handle_MAP([[maybe_unused]] NetworkConnection& connection,
|
|||
{
|
||||
return;
|
||||
}
|
||||
if (offset == 0)
|
||||
{
|
||||
// Start of a new map load, clear the queue now as we have to buffer them
|
||||
// until the map is fully loaded.
|
||||
GameActions::ClearQueue();
|
||||
_serverTickData.clear();
|
||||
_clientMapLoaded = false;
|
||||
}
|
||||
if (size > chunk_buffer.size())
|
||||
{
|
||||
chunk_buffer.resize(size);
|
||||
|
@ -2768,10 +2662,7 @@ void Network::Client_Handle_MAP([[maybe_unused]] NetworkConnection& connection,
|
|||
if (LoadMap(&ms))
|
||||
{
|
||||
game_load_init();
|
||||
game_command_queue.clear();
|
||||
_serverTickData.clear();
|
||||
_serverState.tick = gCurrentTicks;
|
||||
_serverTickData.clear();
|
||||
// window_network_status_open("Loaded new map from network");
|
||||
_serverState.state = NETWORK_SERVER_STATE_OK;
|
||||
_clientMapLoaded = true;
|
||||
|
@ -2955,12 +2846,11 @@ void Network::Client_Handle_GAME_ACTION([[maybe_unused]] NetworkConnection& conn
|
|||
if (itr != _gameActionCallbacks.end())
|
||||
{
|
||||
action->SetCallback(itr->second);
|
||||
|
||||
_gameActionCallbacks.erase(itr);
|
||||
}
|
||||
}
|
||||
|
||||
game_command_queue.emplace(tick, std::move(action), _commandId++);
|
||||
GameActions::Enqueue(std::move(action), tick);
|
||||
}
|
||||
|
||||
void Network::Server_Handle_GAME_ACTION(NetworkConnection& connection, NetworkPacket& packet)
|
||||
|
@ -3029,7 +2919,7 @@ void Network::Server_Handle_GAME_ACTION(NetworkConnection& connection, NetworkPa
|
|||
// Set player to sender, should be 0 if sent from client.
|
||||
ga->SetPlayer(NetworkPlayerId_t{ connection.Player->Id });
|
||||
|
||||
game_command_queue.emplace(tick, std::move(ga), _commandId++);
|
||||
GameActions::Enqueue(std::move(ga), tick);
|
||||
}
|
||||
|
||||
void Network::Client_Handle_TICK([[maybe_unused]] NetworkConnection& connection, NetworkPacket& packet)
|
||||
|
@ -3830,11 +3720,6 @@ void network_send_game_action(const GameAction* action)
|
|||
}
|
||||
}
|
||||
|
||||
void network_enqueue_game_action(const GameAction* action)
|
||||
{
|
||||
gNetwork.EnqueueGameAction(action);
|
||||
}
|
||||
|
||||
void network_send_password(const std::string& password)
|
||||
{
|
||||
utf8 keyPath[MAX_PATH];
|
||||
|
@ -3987,9 +3872,6 @@ bool network_check_desynchronisation()
|
|||
void network_request_gamestate_snapshot()
|
||||
{
|
||||
}
|
||||
void network_enqueue_game_action(const GameAction* action)
|
||||
{
|
||||
}
|
||||
void network_send_game_action(const GameAction* action)
|
||||
{
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue