Merge pull request #9914 from ZehMatt/refactor/gameaction-queue

Refactor network queue out and one desync fix
This commit is contained in:
Michael Steenbeek 2019-08-22 12:07:19 +02:00 committed by GitHub
commit af2ad8045c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 127 additions and 134 deletions

View File

@ -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();

View File

@ -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();

View File

@ -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++;

View File

@ -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;
}

View File

@ -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);

View File

@ -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)
{
}