diff --git a/src/citymania/CMakeLists.txt b/src/citymania/CMakeLists.txt index 1459c573ea..75ec6477fe 100644 --- a/src/citymania/CMakeLists.txt +++ b/src/citymania/CMakeLists.txt @@ -26,6 +26,8 @@ add_files( cm_cargo_table_gui.cpp cm_client_list_gui.hpp cm_client_list_gui.cpp + cm_commands.hpp + cm_commands.cpp cm_commands_gui.hpp cm_commands_gui.cpp cm_highlight.hpp diff --git a/src/citymania/cm_blueprint.cpp b/src/citymania/cm_blueprint.cpp index 1f8af7d279..b48ec7d5c6 100644 --- a/src/citymania/cm_blueprint.cpp +++ b/src/citymania/cm_blueprint.cpp @@ -2,6 +2,7 @@ #include "cm_blueprint.hpp" +#include "cm_commands.hpp" #include "cm_highlight.hpp" #include "../command_func.h" @@ -37,31 +38,6 @@ bool operator!=(const TileIndexDiffC &a, const TileIndexDiffC &b) { return a.x != b.x || a.y != b.y; } -typedef std::tuple CommandTuple; -std::map> _command_callbacks; - -void AddCommandCallback(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback callback) { - _command_callbacks[std::make_tuple(tile, p1, p2, cmd)].push_back(callback); -} - -void DoCommandWithCallback(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback callback) { - AddCommandCallback(tile, p1, p2, cmd & CMD_ID_MASK, callback); - DoCommandP(tile, p1, p2, cmd); -} - -void DoCommandWithCallback(const CommandContainer &cc, CommandCallback callback) { - DoCommandWithCallback(cc.tile, cc.p1, cc.p2, cc.cmd, callback); -} - -void CommandExecuted(bool res, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd) { - CommandTuple ct {tile, p1, p2, cmd & CMD_ID_MASK}; - auto p = _command_callbacks.find(ct); - if (p == _command_callbacks.end()) return; - for (auto &cb : p->second) - cb(res); - _command_callbacks.erase(p); -} - template void IterateStation(TileIndex start_tile, Axis axis, byte numtracks, byte plat_len, Func visitor) { auto plat_delta = (axis == AXIS_X ? TileDiffXY(1, 0) : TileDiffXY(0, 1)); @@ -641,8 +617,7 @@ void BuildBlueprint(sp &blueprint, TileIndex start) { return true; }; if (last_rail.cmd != CMD_END) { // there can't be any signals if there are no rails - if (_networking) AddCommandCallback(last_rail.tile, last_rail.p1, last_rail.p2, last_rail.cmd, signal_callback); - else signal_callback(true); + AddCommandCallback(last_rail.tile, last_rail.p1, last_rail.p2, last_rail.cmd, "", signal_callback); } } diff --git a/src/citymania/cm_blueprint.hpp b/src/citymania/cm_blueprint.hpp index 539607ed3e..c87f6c6197 100644 --- a/src/citymania/cm_blueprint.hpp +++ b/src/citymania/cm_blueprint.hpp @@ -7,9 +7,6 @@ namespace citymania { -typedef std::function CommandCallback; -void AddCommandCallback(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback callback); - void BlueprintCopyArea(TileIndex start, TileIndex end); void ResetActiveBlueprint(); void SetBlueprintHighlight(const TileInfo *ti, TileHighlight &th); @@ -21,6 +18,6 @@ void RotateActiveBlueprint(); void CommandExecuted(bool res, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd); -} //- namespace citymania +} // namespace citymania #endif diff --git a/src/citymania/cm_commands.cpp b/src/citymania/cm_commands.cpp new file mode 100644 index 0000000000..7c8970f1e1 --- /dev/null +++ b/src/citymania/cm_commands.cpp @@ -0,0 +1,121 @@ +#include "../stdafx.h" + +#include "cm_commands.hpp" + +#include "../command_func.h" +#include "../network/network.h" +#include "../network/network_client.h" + +#include + +namespace citymania { + +const uint32 MAX_CALLBACK_LIFETIME = 30; // it should be executed within few frames so 30 more than enough + +std::map>> _command_callbacks; +std::queue> _command_sent; + +uint GetCurrentQueueDelay(); + +template +void hash_combine(std::size_t& seed, const T& v, const Rest&... rest) +{ + seed ^= std::hash{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + (hash_combine(seed, rest), ...); +} + +size_t GetCommandHash(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, const std::string &text) { + size_t res = 0; + hash_combine(res, tile, p1, p2, cmd, text); + return res; +} + +void AddCommandCallback(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, const std::string &text, CommandCallback callback) { + if (!_networking) { + callback(true); + return; + } + auto hash = GetCommandHash(tile, p1, p2, cmd & CMD_ID_MASK, text); + auto sent_frame = _frame_counter + GetCurrentQueueDelay(); + _command_callbacks[hash].first = sent_frame; + _command_callbacks[hash].second.push_back(callback); + _command_sent.push(std::make_pair(hash, sent_frame)); +} + +bool DoCommandWithCallback(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, ::CommandCallback *callback, const std::string &text, CommandCallback cm_callback) { + if (_networking) { + AddCommandCallback(tile, p1, p2, cmd, text, cm_callback); + DoCommandP(tile, p1, p2, cmd, callback, text); + return true; + } + auto res = DoCommandP(tile, p1, p2, cmd, callback, text); + cm_callback(res); + return res; +} + +bool DoCommandWithCallback(const CommandContainer &cc, CommandCallback callback) { + return DoCommandWithCallback(cc.tile, cc.p1, cc.p2, cc.cmd, cc.callback, cc.text, callback); +} + +void HandleCommandExecution(bool res, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, const std::string &text) { + auto hash = GetCommandHash(tile, p1, p2, cmd & CMD_ID_MASK, text); + auto p = _command_callbacks.find(hash); + if (p == _command_callbacks.end()) return; + for (auto &cb : p->second.second) + cb(res); + _command_callbacks.erase(p); +} + +void ClearOldCallbacks() { + while(!_command_sent.empty() && _command_sent.front().second + MAX_CALLBACK_LIFETIME < _frame_counter) { + auto hash = _command_sent.front().first; + _command_sent.pop(); + auto p = _command_callbacks.find(hash); + if (p != _command_callbacks.end() && p->first + MAX_CALLBACK_LIFETIME < _frame_counter) { + _command_callbacks.erase(p); + } + } +} + +std::queue _outgoing_queue; +uint _commands_this_frame; + +void InitCommandQueue() { + _commands_this_frame = 0; + std::queue().swap(_outgoing_queue); // clear queue + _command_callbacks.clear(); +} + +bool CanSendCommand() { + return _commands_this_frame < 2; +} + +uint GetCurrentQueueDelay() { + return _outgoing_queue.size() / 2; +} + +void FlushCommandQueue() { + while (!_outgoing_queue.empty() && CanSendCommand()) { + MyClient::SendCommand(&_outgoing_queue.front()); + _outgoing_queue.pop(); + _commands_this_frame++; + } +} + +void HandleNextClientFrame() { + _commands_this_frame = 0; + FlushCommandQueue(); + ClearOldCallbacks(); +} + +void SendClientCommand(const CommandPacket *cp) { + if (_outgoing_queue.empty() && CanSendCommand()) { + MyClient::SendCommand(cp); + _commands_this_frame++; + return; + } + _outgoing_queue.push(*cp); +} + + +} // namespace citymania diff --git a/src/citymania/cm_commands.hpp b/src/citymania/cm_commands.hpp new file mode 100644 index 0000000000..8ba8d2ec05 --- /dev/null +++ b/src/citymania/cm_commands.hpp @@ -0,0 +1,22 @@ +#ifndef CM_COMMANDS_HPP +#define CM_COMMANDS_HPP + +#include "../command_type.h" +#include "../tile_type.h" +#include "../network/network_internal.h" + +namespace citymania { + +typedef std::function CommandCallback; +void AddCommandCallback(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, const std::string &text, CommandCallback callback); +bool DoCommandWithCallback(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, ::CommandCallback *callback, const std::string &text, CommandCallback cm_callback); +bool DoCommandWithCallback(const CommandContainer &cc, CommandCallback callback); +void HandleCommandExecution(bool res, TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, const std::string &text); + +void InitCommandQueue(); +void HandleNextClientFrame(); +void SendClientCommand(const CommandPacket *cp); + +} // namespace citymania + +#endif diff --git a/src/command.cpp b/src/command.cpp index 6674ac622d..8e916504d4 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -30,7 +30,7 @@ #include "table/strings.h" -#include "citymania/cm_blueprint.hpp" +#include "citymania/cm_commands.hpp" #include "citymania/cm_hotkeys.hpp" #include "citymania/cm_watch_gui.hpp" @@ -617,7 +617,7 @@ bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallbac callback(res, tile, p1, p2, cmd); } if (!estimate_only && !only_sending) - citymania::CommandExecuted(res.Succeeded(), tile, p1, p2, cmd); + citymania::HandleCommandExecution(res.Succeeded(), tile, p1, p2, cmd, text); return res.Succeeded(); } diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index c890e0961b..33d80e06d4 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -34,8 +34,9 @@ #include "../town.h" #include "network_func.h" -#include "../citymania/cm_newgrf_revisions.hpp" #include "../citymania/cm_client_list_gui.hpp" +#include "../citymania/cm_commands.hpp" +#include "../citymania/cm_newgrf_revisions.hpp" #include "../safeguards.h" @@ -276,6 +277,8 @@ void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res) extern void StateGameLoop(); StateGameLoop(); + citymania::HandleNextClientFrame(); + /* Check if we are in sync! */ if (_sync_frame != 0) { if (_sync_frame == _frame_counter) { @@ -776,6 +779,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_MAP_BEGIN(Packe this->savegame = new PacketReader(); _frame_counter = _frame_counter_server = _frame_counter_max = p->Recv_uint32(); + citymania::InitCommandQueue(); _network_join_bytes = 0; _network_join_bytes_total = 0; diff --git a/src/network/network_command.cpp b/src/network/network_command.cpp index b07921c6b4..52bef4c85a 100644 --- a/src/network/network_command.cpp +++ b/src/network/network_command.cpp @@ -15,6 +15,8 @@ #include "../company_func.h" #include "../settings_type.h" +#include "../citymania/cm_commands.hpp" + #include "../safeguards.h" /** Table with all the callbacks we'll use for conversion*/ @@ -163,7 +165,8 @@ void NetworkSendCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, Comman c.frame = 0; // The client can't tell which frame, so just make it 0 /* Clients send their command to the server and forget all about the packet */ - MyClient::SendCommand(&c); + // MyClient::SendCommand(&c); + citymania::SendClientCommand(&c); } /** diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index 1434a99d11..e57e05851d 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -42,8 +42,8 @@ #include "widgets/rail_widget.h" -#include "network/network.h" #include "citymania/cm_blueprint.hpp" +#include "citymania/cm_commands.hpp" #include "citymania/cm_hotkeys.hpp" #include "citymania/cm_highlight.hpp" #include "citymania/cm_station_gui.hpp" @@ -408,16 +408,12 @@ static bool DoAutodirTerraform(bool diagonal, TileIndex start_tile, TileIndex en int diag_flag = (int)diagonal; DoCommandP(e1, s1, ((h1 < h2 ? LM_RAISE : LM_LEVEL) << 1) | diag_flag, CMD_LEVEL_LAND, CcTerraform); uint32 p2 = ((h2 < h1 ? LM_RAISE : LM_LEVEL) << 1) | diag_flag; - DoCommandP(e2, s2, p2, CMD_LEVEL_LAND, CcTerraform); auto rail_callback = [rail_cmd, start_tile, end_tile, track](bool res) -> bool { if (!citymania::_estimate_mod && end_tile != INVALID_TILE) StoreRailPlacementEndpoints(start_tile, end_tile, track, true); return DoCommandP(&rail_cmd); } ; - if (_networking) { - citymania::AddCommandCallback(e2, s2, p2, CMD_LEVEL_LAND, rail_callback); - return true; - } else return rail_callback(true); + return citymania::DoCommandWithCallback(e2, s2, p2, CMD_LEVEL_LAND, CcTerraform, "", rail_callback); } static bool HandleAutodirTerraform(TileIndex start_tile, TileIndex end_tile, Track track, CommandContainer &rail_cmd) {