Use turn_id_t (int32_t) for turn-count across simulation, network and replay
Some checks failed
checkrefs / lfscheck (push) Has been cancelled
checkrefs / checkrefs (push) Has been cancelled
lint / cppcheck (push) Has been cancelled
lint / copyright (push) Has been cancelled
lint / jenkinsfiles (push) Has been cancelled
pre-commit / build (push) Has been cancelled

CSimulation2 used , CSimulation2Impl mixed /, and
CTurnManager used  for the same turn-count concept, forcing a
static_cast<int64_t> at the one place these types already met
(rejoin-test comparison).

Add  (alias for std::int32_t) in SimulationCommand.h and use
it consistently for every turn counter in CSimulation2/Impl,
CTurnManager and its subclasses (CLocalTurnManager,
CReplayTurnManager), CNetServerTurnManager/CNetClientTurnManager, and
the network wire format (m_Turn in CEndCommandBatchMessage,
CSimulationMessage, CSyncCheckMessage, CSyncErrorMessage, and
m_CurrentTurn in CLoadedGameMessage).

Turn *duration* fields (m_TurnLength, m_CommandDelay,
DEFAULT_TURN_LENGTH, COMMAND_DELAY_SP/MP, SetTurnLength,
GetSavedTurnLength return type) are left as u32 — they're milliseconds,
not a counter, and out of scope here.

Remaining turn_id_t vs size_t comparisons use std::cmp_equal or
explicit casts, matching the existing pattern in Simulation2.cpp.

Fixes #8718
This commit is contained in:
vyordan 2026-06-26 22:04:27 -06:00 committed by Phosit
parent 9ab89b01c2
commit 71be79791f
17 changed files with 105 additions and 95 deletions

View file

@ -63,7 +63,7 @@ void CNetClientTurnManager::PostCommand(JS::HandleValue data)
// TODO: we should do this when the server stops sending our commands back to us // TODO: we should do this when the server stops sending our commands back to us
} }
void CNetClientTurnManager::NotifyFinishedOwnCommands(u32 turn) void CNetClientTurnManager::NotifyFinishedOwnCommands(turn_id_t turn)
{ {
NETCLIENTTURN_LOG("NotifyFinishedOwnCommands(%d)\n", turn); NETCLIENTTURN_LOG("NotifyFinishedOwnCommands(%d)\n", turn);
@ -79,7 +79,7 @@ void CNetClientTurnManager::NotifyFinishedOwnCommands(u32 turn)
m_NetClient.SendMessage(&msg); m_NetClient.SendMessage(&msg);
} }
void CNetClientTurnManager::NotifyFinishedUpdate(u32 turn, const UpdateCallback&) void CNetClientTurnManager::NotifyFinishedUpdate(turn_id_t turn, const UpdateCallback&)
{ {
bool quick = !TurnNeedsFullHash(turn); bool quick = !TurnNeedsFullHash(turn);
std::string hash; std::string hash;
@ -110,10 +110,10 @@ void CNetClientTurnManager::OnDestroyConnection()
void CNetClientTurnManager::OnSimulationMessage(CSimulationMessage* msg) void CNetClientTurnManager::OnSimulationMessage(CSimulationMessage* msg)
{ {
// Command received from the server - store it for later execution // Command received from the server - store it for later execution
AddCommand(msg->m_Client, msg->m_Player, msg->m_Data, msg->m_Turn); AddCommand(msg->m_Client, msg->m_Player, msg->m_Data, static_cast<turn_id_t>(msg->m_Turn));
} }
void CNetClientTurnManager::OnSyncError(u32 turn, const CStr& expectedHash, const std::vector<CSyncErrorMessage::S_m_PlayerNames>& playerNames) void CNetClientTurnManager::OnSyncError(turn_id_t turn, const CStr& expectedHash, const std::vector<CSyncErrorMessage::S_m_PlayerNames>& playerNames)
{ {
CStr expectedHashHex(Hexify(expectedHash)); CStr expectedHashHex(Hexify(expectedHash));
NETCLIENTTURN_LOG("OnSyncError(%d, %hs)\n", turn, expectedHashHex.c_str()); NETCLIENTTURN_LOG("OnSyncError(%d, %hs)\n", turn, expectedHashHex.c_str());

View file

@ -49,12 +49,12 @@ public:
*/ */
void OnDestroyConnection(); void OnDestroyConnection();
void OnSyncError(u32 turn, const CStr& expectedHash, const std::vector<CSyncErrorMessage::S_m_PlayerNames>& playerNames); void OnSyncError(turn_id_t turn, const CStr& expectedHash, const std::vector<CSyncErrorMessage::S_m_PlayerNames>& playerNames);
private: private:
void NotifyFinishedOwnCommands(u32 turn) override; void NotifyFinishedOwnCommands(turn_id_t turn) override;
void NotifyFinishedUpdate(u32 turn, const UpdateCallback& sendEventToAll) override; void NotifyFinishedUpdate(turn_id_t turn, const UpdateCallback& sendEventToAll) override;
CNetClient& m_NetClient; CNetClient& m_NetClient;
}; };

View file

@ -139,7 +139,7 @@ public:
u32 m_Client; u32 m_Client;
i32 m_Player; i32 m_Player;
u32 m_Turn; i32 m_Turn;
JS::PersistentRooted<JS::Value> m_Data; JS::PersistentRooted<JS::Value> m_Data;
private: private:
const Script::Interface& m_ScriptInterface; const Script::Interface& m_ScriptInterface;

View file

@ -150,7 +150,7 @@ u8* CSimulationMessage::Serialize(u8* pBuffer) const
CBufferBinarySerializer serializer(m_ScriptInterface, pos); CBufferBinarySerializer serializer(m_ScriptInterface, pos);
serializer.NumberU32_Unbounded("client", m_Client); serializer.NumberU32_Unbounded("client", m_Client);
serializer.NumberI32_Unbounded("player", m_Player); serializer.NumberI32_Unbounded("player", m_Player);
serializer.NumberU32_Unbounded("turn", m_Turn); serializer.NumberI32_Unbounded("turn", m_Turn);
serializer.ScriptVal("command", const_cast<JS::PersistentRootedValue*>(&m_Data)); serializer.ScriptVal("command", const_cast<JS::PersistentRootedValue*>(&m_Data));
return serializer.GetBuffer(); return serializer.GetBuffer();
@ -165,7 +165,7 @@ const u8* CSimulationMessage::Deserialize(const u8* pStart, const u8* pEnd)
CStdDeserializer deserializer(m_ScriptInterface, stream); CStdDeserializer deserializer(m_ScriptInterface, stream);
deserializer.NumberU32_Unbounded("client", m_Client); deserializer.NumberU32_Unbounded("client", m_Client);
deserializer.NumberI32_Unbounded("player", m_Player); deserializer.NumberI32_Unbounded("player", m_Player);
deserializer.NumberU32_Unbounded("turn", m_Turn); deserializer.NumberI32_Unbounded("turn", m_Turn);
deserializer.ScriptVal("command", &m_Data); deserializer.ScriptVal("command", &m_Data);
return pEnd; return pEnd;
} }
@ -177,7 +177,7 @@ size_t CSimulationMessage::GetSerializedLength() const
CLengthBinarySerializer serializer(m_ScriptInterface); CLengthBinarySerializer serializer(m_ScriptInterface);
serializer.NumberU32_Unbounded("client", m_Client); serializer.NumberU32_Unbounded("client", m_Client);
serializer.NumberI32_Unbounded("player", m_Player); serializer.NumberI32_Unbounded("player", m_Player);
serializer.NumberU32_Unbounded("turn", m_Turn); serializer.NumberI32_Unbounded("turn", m_Turn);
// TODO: The cast can probably be removed if and when ScriptVal can take a JS::HandleValue instead of // TODO: The cast can probably be removed if and when ScriptVal can take a JS::HandleValue instead of
// a JS::MutableHandleValue (relies on JSAPI change). Also search for other casts like this one in that case. // a JS::MutableHandleValue (relies on JSAPI change). Also search for other casts like this one in that case.

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games. /* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -27,7 +27,7 @@
#define PS_PROTOCOL_MAGIC 0x5073013f // 'P', 's', 0x01, '?' #define PS_PROTOCOL_MAGIC 0x5073013f // 'P', 's', 0x01, '?'
#define PS_PROTOCOL_MAGIC_RESPONSE 0x50630121 // 'P', 'c', 0x01, '!' #define PS_PROTOCOL_MAGIC_RESPONSE 0x50630121 // 'P', 'c', 0x01, '!'
#define PS_PROTOCOL_VERSION 0x01010019 // Arbitrary protocol #define PS_PROTOCOL_VERSION 0x0101001A // Arbitrary protocol
#define PS_DEFAULT_PORT 0x5073 // 'P', 's' #define PS_DEFAULT_PORT 0x5073 // 'P', 's'
// Set when lobby authentication is required. Used in the SrvHandshakeResponseMessage. // Set when lobby authentication is required. Used in the SrvHandshakeResponseMessage.
@ -231,7 +231,7 @@ START_NMT_CLASS_(ClientPaused, NMT_CLIENT_PAUSED)
END_NMT_CLASS() END_NMT_CLASS()
START_NMT_CLASS_(LoadedGame, NMT_LOADED_GAME) START_NMT_CLASS_(LoadedGame, NMT_LOADED_GAME)
NMT_FIELD_INT(m_CurrentTurn, u32, 4) NMT_FIELD_INT(m_CurrentTurn, i32, 4)
END_NMT_CLASS() END_NMT_CLASS()
START_NMT_CLASS_(GameStart, NMT_GAME_START) START_NMT_CLASS_(GameStart, NMT_GAME_START)
@ -243,17 +243,17 @@ START_NMT_CLASS_(GameSavedStart, NMT_SAVED_GAME_START)
END_NMT_CLASS() END_NMT_CLASS()
START_NMT_CLASS_(EndCommandBatch, NMT_END_COMMAND_BATCH) START_NMT_CLASS_(EndCommandBatch, NMT_END_COMMAND_BATCH)
NMT_FIELD_INT(m_Turn, u32, 4) NMT_FIELD_INT(m_Turn, i32, 4)
NMT_FIELD_INT(m_TurnLength, u32, 2) NMT_FIELD_INT(m_TurnLength, u32, 2)
END_NMT_CLASS() END_NMT_CLASS()
START_NMT_CLASS_(SyncCheck, NMT_SYNC_CHECK) START_NMT_CLASS_(SyncCheck, NMT_SYNC_CHECK)
NMT_FIELD_INT(m_Turn, u32, 4) NMT_FIELD_INT(m_Turn, i32, 4)
NMT_FIELD(CStr, m_Hash) NMT_FIELD(CStr, m_Hash)
END_NMT_CLASS() END_NMT_CLASS()
START_NMT_CLASS_(SyncError, NMT_SYNC_ERROR) START_NMT_CLASS_(SyncError, NMT_SYNC_ERROR)
NMT_FIELD_INT(m_Turn, u32, 4) NMT_FIELD_INT(m_Turn, i32, 4)
NMT_FIELD(CStr, m_HashExpected) NMT_FIELD(CStr, m_HashExpected)
NMT_START_ARRAY(m_PlayerNames) NMT_START_ARRAY(m_PlayerNames)
NMT_FIELD(CStrW, m_Name) NMT_FIELD(CStrW, m_Name)

View file

@ -49,6 +49,7 @@
#include "scriptinterface/Context.h" #include "scriptinterface/Context.h"
#include "scriptinterface/Interface.h" #include "scriptinterface/Interface.h"
#include "scriptinterface/Request.h" #include "scriptinterface/Request.h"
#include "simulation2/helpers/SimulationCommand.h"
#include "simulation2/system/TurnManager.h" #include "simulation2/system/TurnManager.h"
#include <algorithm> #include <algorithm>
@ -1172,9 +1173,9 @@ bool CNetServerWorker::OnSimulationCommand(CNetServerSession* session, CFsmEvent
server.Multicast(message, { NSS_INGAME }); server.Multicast(message, { NSS_INGAME });
// Save all the received commands // Save all the received commands
if (server.m_SavedCommands.size() < message->m_Turn + 1) if (server.m_SavedCommands.size() < static_cast<size_t>(message->m_Turn) + 1)
server.m_SavedCommands.resize(message->m_Turn + 1); server.m_SavedCommands.resize(static_cast<size_t>(message->m_Turn) + 1);
server.m_SavedCommands[message->m_Turn].push_back(*message); server.m_SavedCommands[static_cast<size_t>(message->m_Turn)].push_back(*message);
// TODO: we shouldn't send the message back to the client that first sent it // TODO: we shouldn't send the message back to the client that first sent it
return true; return true;
@ -1396,14 +1397,14 @@ bool CNetServerWorker::OnJoinSyncingLoadedGame(CNetServerSession* session, CFsmE
CLoadedGameMessage* message = (CLoadedGameMessage*)event->GetParamRef(); CLoadedGameMessage* message = (CLoadedGameMessage*)event->GetParamRef();
u32 turn = message->m_CurrentTurn; turn_id_t turn = message->m_CurrentTurn;
u32 readyTurn = server.m_ServerTurnManager->GetReadyTurn(); turn_id_t readyTurn = server.m_ServerTurnManager->GetReadyTurn();
// Send them all commands received since their saved state, // Send them all commands received since their saved state,
// and turn-ended messages for any turns that have already been processed // and turn-ended messages for any turns that have already been processed
for (size_t i = turn + 1; i < std::max(readyTurn+1, (u32)server.m_SavedCommands.size()); ++i) for (turn_id_t i = turn + 1; i < std::max(readyTurn + 1, static_cast<turn_id_t>(server.m_SavedCommands.size())); ++i)
{ {
if (i < server.m_SavedCommands.size()) if (static_cast<size_t>(i) < server.m_SavedCommands.size())
for (size_t j = 0; j < server.m_SavedCommands[i].size(); ++j) for (size_t j = 0; j < server.m_SavedCommands[i].size(); ++j)
session->SendMessage(&server.m_SavedCommands[i][j]); session->SendMessage(&server.m_SavedCommands[i][j]);

View file

@ -27,6 +27,7 @@
#include "network/NetServerSession.h" #include "network/NetServerSession.h"
#include "ps/CLogger.h" #include "ps/CLogger.h"
#include "ps/ConfigDB.h" #include "ps/ConfigDB.h"
#include "simulation2/helpers/SimulationCommand.h"
#include "simulation2/system/TurnManager.h" #include "simulation2/system/TurnManager.h"
#include <limits> #include <limits>
@ -50,7 +51,7 @@ CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server)
m_SavedTurnLengths.push_back(m_TurnLength); m_SavedTurnLengths.push_back(m_TurnLength);
} }
void CNetServerTurnManager::NotifyFinishedClientCommands(CNetServerSession& session, u32 turn) void CNetServerTurnManager::NotifyFinishedClientCommands(CNetServerSession& session, turn_id_t turn)
{ {
int client = session.GetHostID(); int client = session.GetHostID();
@ -105,11 +106,11 @@ void CNetServerTurnManager::CheckClientsReady()
msg.m_Turn = m_ReadyTurn; msg.m_Turn = m_ReadyTurn;
m_NetServer.Multicast(&msg, { NSS_INGAME }); m_NetServer.Multicast(&msg, { NSS_INGAME });
ENSURE(m_SavedTurnLengths.size() == m_ReadyTurn); ENSURE(std::cmp_equal(m_SavedTurnLengths.size(), m_ReadyTurn));
m_SavedTurnLengths.push_back(m_TurnLength); m_SavedTurnLengths.push_back(m_TurnLength);
} }
void CNetServerTurnManager::NotifyFinishedClientUpdate(CNetServerSession& session, u32 turn, const CStr& hash) void CNetServerTurnManager::NotifyFinishedClientUpdate(CNetServerSession& session, turn_id_t turn, const CStr& hash)
{ {
int client = session.GetHostID(); int client = session.GetHostID();
@ -138,13 +139,13 @@ void CNetServerTurnManager::NotifyFinishedClientUpdate(CNetServerSession& sessio
m_ClientStateHashes[turn][client] = hash; m_ClientStateHashes[turn][client] = hash;
// Find the newest turn which we know all clients have simulated // Find the newest turn which we know all clients have simulated
u32 newest = std::numeric_limits<u32>::max(); turn_id_t newest = std::numeric_limits<turn_id_t>::max();
for (const std::pair<const int, Client>& clientData : m_ClientsData) for (const std::pair<const int, Client>& clientData : m_ClientsData)
if (clientData.second.simulatedTurn < newest) if (clientData.second.simulatedTurn < newest)
newest = clientData.second.simulatedTurn; newest = clientData.second.simulatedTurn;
// For every set of state hashes that all clients have simulated, check for OOS // For every set of state hashes that all clients have simulated, check for OOS
for (const std::pair<const u32, std::map<int, std::string>>& clientStateHash : m_ClientStateHashes) for (const std::pair<const turn_id_t, std::map<int, std::string>>& clientStateHash : m_ClientStateHashes)
{ {
if (clientStateHash.first > newest) if (clientStateHash.first > newest)
break; break;
@ -187,7 +188,7 @@ void CNetServerTurnManager::NotifyFinishedClientUpdate(CNetServerSession& sessio
m_ClientStateHashes.erase(m_ClientStateHashes.begin(), m_ClientStateHashes.lower_bound(newest+1)); m_ClientStateHashes.erase(m_ClientStateHashes.begin(), m_ClientStateHashes.lower_bound(newest+1));
} }
void CNetServerTurnManager::InitialiseClient(int client, u32 turn, bool observer) void CNetServerTurnManager::InitialiseClient(int client, turn_id_t turn, bool observer)
{ {
NETSERVERTURN_LOG("InitialiseClient(client=%d, turn=%d)\n", client, turn); NETSERVERTURN_LOG("InitialiseClient(client=%d, turn=%d)\n", client, turn);
@ -206,7 +207,7 @@ void CNetServerTurnManager::UninitialiseClient(int client)
bool checkOOS = m_ClientsData[client].isOOS; bool checkOOS = m_ClientsData[client].isOOS;
m_ClientsData.erase(client); m_ClientsData.erase(client);
for (std::pair<const u32, std::map<int, std::string>>& clientStateHash : m_ClientStateHashes) for (std::pair<const turn_id_t, std::map<int, std::string>>& clientStateHash : m_ClientStateHashes)
clientStateHash.second.erase(client); clientStateHash.second.erase(client);
// Check whether we're ready for the next turn now that we're not // Check whether we're ready for the next turn now that we're not
@ -228,8 +229,8 @@ void CNetServerTurnManager::SetTurnLength(u32 msecs)
m_TurnLength = msecs; m_TurnLength = msecs;
} }
u32 CNetServerTurnManager::GetSavedTurnLength(u32 turn) u32 CNetServerTurnManager::GetSavedTurnLength(turn_id_t turn)
{ {
ENSURE(turn <= m_ReadyTurn); ENSURE(turn <= m_ReadyTurn);
return m_SavedTurnLengths.at(turn); return m_SavedTurnLengths.at(static_cast<size_t>(turn));
} }

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games. /* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -21,6 +21,7 @@
#include "lib/code_annotation.h" #include "lib/code_annotation.h"
#include "lib/types.h" #include "lib/types.h"
#include "ps/CStr.h" #include "ps/CStr.h"
#include "simulation2/helpers/SimulationCommand.h"
#include <map> #include <map>
#include <string> #include <string>
@ -44,15 +45,15 @@ class CNetServerTurnManager
public: public:
CNetServerTurnManager(CNetServerWorker& server); CNetServerTurnManager(CNetServerWorker& server);
void NotifyFinishedClientCommands(CNetServerSession& session, u32 turn); void NotifyFinishedClientCommands(CNetServerSession& session, turn_id_t turn);
void NotifyFinishedClientUpdate(CNetServerSession& session, u32 turn, const CStr& hash); void NotifyFinishedClientUpdate(CNetServerSession& session, turn_id_t turn, const CStr& hash);
/** /**
* Inform the turn manager of a new client * Inform the turn manager of a new client
* @param observer - whether this client is an observer. * @param observer - whether this client is an observer.
*/ */
void InitialiseClient(int client, u32 turn, bool observer); void InitialiseClient(int client, turn_id_t turn, bool observer);
/** /**
* Inform the turn manager that a previously-initialised client has left the game. * Inform the turn manager that a previously-initialised client has left the game.
@ -65,13 +66,13 @@ public:
* Returns the latest turn for which all clients are ready; * Returns the latest turn for which all clients are ready;
* they will have already been told to execute this turn. * they will have already been told to execute this turn.
*/ */
u32 GetReadyTurn() { return m_ReadyTurn; } turn_id_t GetReadyTurn() { return m_ReadyTurn; }
/** /**
* Returns the turn length that was used for the given turn. * Returns the turn length that was used for the given turn.
* Requires turn <= GetReadyTurn(). * Requires turn <= GetReadyTurn().
*/ */
u32 GetSavedTurnLength(u32 turn); u32 GetSavedTurnLength(turn_id_t turn);
private: private:
void CheckClientsReady(); void CheckClientsReady();
@ -80,9 +81,9 @@ private:
{ {
CStrW playerName; CStrW playerName;
// Latest turn for which all commands have been received. // Latest turn for which all commands have been received.
u32 readyTurn; turn_id_t readyTurn;
// Last known simulated turn. // Last known simulated turn.
u32 simulatedTurn; turn_id_t simulatedTurn;
bool isObserver; bool isObserver;
bool isOOS = false; bool isOOS = false;
}; };
@ -93,10 +94,10 @@ private:
bool m_HasSyncError = false; bool m_HasSyncError = false;
// Map of turn -> {Client ID -> state hash}; old indexes <= min(m_ClientsSimulated) are deleted // Map of turn -> {Client ID -> state hash}; old indexes <= min(m_ClientsSimulated) are deleted
std::map<u32, std::map<int, std::string>> m_ClientStateHashes; std::map<turn_id_t, std::map<int, std::string>> m_ClientStateHashes;
/// The latest turn for which we have received all commands from all clients /// The latest turn for which we have received all commands from all clients
u32 m_ReadyTurn; turn_id_t m_ReadyTurn;
// Current turn length // Current turn length
u32 m_TurnLength; u32 m_TurnLength;

View file

@ -92,13 +92,13 @@ public:
return serializationTestOption ? serializationTestOption->turn : return serializationTestOption ? serializationTestOption->turn :
std::max(CConfigDB::GetIfInitialised("serializationtest", -1), -1); std::max(CConfigDB::GetIfInitialised("serializationtest", -1), -1);
}()}, }()},
m_RejoinTestTurn{[&]() -> std::optional<int> m_RejoinTestTurn{[&]() -> std::optional<turn_id_t>
{ {
const auto* rejoinTestOption{ const auto* rejoinTestOption{
std::get_if<SimulationDebugOptions::RejoinTest>(&debugOptions.test)}; std::get_if<SimulationDebugOptions::RejoinTest>(&debugOptions.test)};
if (rejoinTestOption) if (rejoinTestOption)
return rejoinTestOption->turn; return rejoinTestOption->turn;
const int configVal{CConfigDB::GetIfInitialised("rejointest", -1)}; const turn_id_t configVal{CConfigDB::GetIfInitialised("rejointest", -1)};
if (configVal >= 0) if (configVal >= 0)
return configVal; return configVal;
return std::nullopt; return std::nullopt;
@ -163,17 +163,17 @@ public:
std::set<VfsPath> m_LoadedScripts; std::set<VfsPath> m_LoadedScripts;
uint32_t m_TurnNumber; turn_id_t m_TurnNumber;
bool m_EnableOOSLog{false}; bool m_EnableOOSLog{false};
OsPath m_OOSLogPath; OsPath m_OOSLogPath;
// Functions and data for the serialization test mode: (see Update() for relevant comments) // Functions and data for the serialization test mode: (see Update() for relevant comments)
std::optional<int> m_SerializationTestTurn; std::optional<turn_id_t> m_SerializationTestTurn;
bool m_TestingSerialization{false}; bool m_TestingSerialization{false};
bool m_EnableSerializationTest{false}; bool m_EnableSerializationTest{false};
std::optional<int> m_RejoinTestTurn; std::optional<turn_id_t> m_RejoinTestTurn;
bool m_TestingRejoin{false}; bool m_TestingRejoin{false};
// Secondary simulation (NB: order matters for destruction). // Secondary simulation (NB: order matters for destruction).
@ -383,7 +383,7 @@ void CSimulation2Impl::InitRNGSeedAI()
void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationCommand>& commands) void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationCommand>& commands)
{ {
PROFILE3("sim update"); PROFILE3("sim update");
PROFILE2_ATTR("turn %d", (int)m_TurnNumber); PROFILE2_ATTR("turn %d", m_TurnNumber);
fixed turnLengthFixed = fixed::FromInt(turnLength) / 1000; fixed turnLengthFixed = fixed::FromInt(turnLength) / 1000;
@ -408,11 +408,11 @@ void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationComman
const Script::Interface& scriptInterface = m_ComponentManager.GetScriptInterface(); const Script::Interface& scriptInterface = m_ComponentManager.GetScriptInterface();
const bool startSerializationTest = m_SerializationTestTurn.has_value() && const bool startSerializationTest = m_SerializationTestTurn.has_value() &&
std::cmp_equal(m_SerializationTestTurn.value(), m_TurnNumber); m_SerializationTestTurn.value() == m_TurnNumber;
if (startSerializationTest) if (startSerializationTest)
m_TestingSerialization = true; m_TestingSerialization = true;
const bool startRejoinTest = m_RejoinTestTurn.has_value() && const bool startRejoinTest = m_RejoinTestTurn.has_value() &&
static_cast<int64_t>(m_RejoinTestTurn.value()) == m_TurnNumber; m_RejoinTestTurn.value() == m_TurnNumber;
if (startRejoinTest) if (startRejoinTest)
m_TestingRejoin = true; m_TestingRejoin = true;
@ -891,11 +891,11 @@ bool CSimulation2::DeserializeState(std::istream& stream)
return m->m_ComponentManager.DeserializeState(stream); return m->m_ComponentManager.DeserializeState(stream);
} }
void CSimulation2::ActivateRejoinTest(int turn) void CSimulation2::ActivateRejoinTest(turn_id_t turn)
{ {
if (m->m_RejoinTestTurn.has_value()) if (m->m_RejoinTestTurn.has_value())
return; return;
LOGMESSAGERENDER("Rejoin test will activate in %i turns", turn - m->m_TurnNumber); LOGMESSAGERENDER("Rejoin test will activate in %d turns", turn - m->m_TurnNumber);
m->m_RejoinTestTurn = turn; m->m_RejoinTestTurn = turn;
} }

View file

@ -22,6 +22,7 @@
#include "lib/file/vfs/vfs_path.h" #include "lib/file/vfs/vfs_path.h"
#include "lib/status.h" #include "lib/status.h"
#include "ps/Loader.h" #include "ps/Loader.h"
#include "simulation2/helpers/SimulationCommand.h"
#include "simulation2/system/DebugOptions.h" #include "simulation2/system/DebugOptions.h"
#include "simulation2/system/Entity.h" #include "simulation2/system/Entity.h"
@ -240,7 +241,7 @@ public:
/** /**
* Activate the rejoin-test feature for turn @param turn. * Activate the rejoin-test feature for turn @param turn.
*/ */
void ActivateRejoinTest(int turn); void ActivateRejoinTest(turn_id_t turn);
std::string GenerateSchema(); std::string GenerateSchema();

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games. /* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -24,6 +24,12 @@
#include <js/TypeDecls.h> #include <js/TypeDecls.h>
#include <js/Value.h> #include <js/Value.h>
/**
* At 1000 turns/second, a 32-bit counter would overflow in ~49 days.
* No overflow checks are needed.
*/
using turn_id_t = std::int32_t;
struct JSContext; struct JSContext;
/** /**

View file

@ -42,12 +42,12 @@ void CLocalTurnManager::PostCommand(JS::HandleValue data)
AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + m_CommandDelay); AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + m_CommandDelay);
} }
void CLocalTurnManager::NotifyFinishedOwnCommands(u32 turn) void CLocalTurnManager::NotifyFinishedOwnCommands(turn_id_t turn)
{ {
FinishedAllCommands(turn, m_TurnLength); FinishedAllCommands(turn, m_TurnLength);
} }
void CLocalTurnManager::NotifyFinishedUpdate(u32 /*turn*/, const UpdateCallback&) void CLocalTurnManager::NotifyFinishedUpdate(turn_id_t /*turn*/, const UpdateCallback&)
{ {
#if 0 // this hurts performance and is only useful for verifying log replays #if 0 // this hurts performance and is only useful for verifying log replays
std::string hash; std::string hash;

View file

@ -41,9 +41,9 @@ public:
void PostCommand(player_id_t playerid, JS::HandleValue data); void PostCommand(player_id_t playerid, JS::HandleValue data);
protected: protected:
void NotifyFinishedOwnCommands(u32 turn) override; void NotifyFinishedOwnCommands(turn_id_t turn) override;
void NotifyFinishedUpdate(u32 turn, const UpdateCallback& sendEventToAll) override; void NotifyFinishedUpdate(turn_id_t turn, const UpdateCallback& sendEventToAll) override;
}; };
#endif // INCLUDED_LOCALTURNMANAGER #endif // INCLUDED_LOCALTURNMANAGER

View file

@ -47,18 +47,18 @@ CReplayTurnManager::CReplayTurnManager(CSimulation2& simulation, IReplayLogger&
{ {
} }
void CReplayTurnManager::StoreReplayCommand(u32 turn, int player, const std::string& command) void CReplayTurnManager::StoreReplayCommand(turn_id_t turn, int player, const std::string& command)
{ {
// Using the pair we make sure that commands per turn will be processed in the correct order // Using the pair we make sure that commands per turn will be processed in the correct order
m_ReplayCommands[turn].emplace_back(player, command); m_ReplayCommands[turn].emplace_back(player, command);
} }
void CReplayTurnManager::StoreReplayHash(u32 turn, const std::string& hash, bool quick) void CReplayTurnManager::StoreReplayHash(turn_id_t turn, const std::string& hash, bool quick)
{ {
m_ReplayHash[turn] = std::make_pair(hash, quick); m_ReplayHash[turn] = std::make_pair(hash, quick);
} }
void CReplayTurnManager::StoreReplayTurnLength(u32 turn, u32 turnLength) void CReplayTurnManager::StoreReplayTurnLength(turn_id_t turn, u32 turnLength)
{ {
m_ReplayTurnLengths[turn] = turnLength; m_ReplayTurnLengths[turn] = turnLength;
@ -67,12 +67,12 @@ void CReplayTurnManager::StoreReplayTurnLength(u32 turn, u32 turnLength)
m_TurnLength = m_ReplayTurnLengths[0]; m_TurnLength = m_ReplayTurnLengths[0];
} }
void CReplayTurnManager::StoreFinalReplayTurn(u32 turn) void CReplayTurnManager::StoreFinalReplayTurn(turn_id_t turn)
{ {
m_FinalTurn = turn; m_FinalTurn = turn;
} }
void CReplayTurnManager::NotifyFinishedUpdate(u32 turn, const UpdateCallback& sendEventToAll) void CReplayTurnManager::NotifyFinishedUpdate(turn_id_t turn, const UpdateCallback& sendEventToAll)
{ {
if (turn == 1 && m_FinalTurn == 0) if (turn == 1 && m_FinalTurn == 0)
sendEventToAll(EventNameReplayFinished, std::nullopt); sendEventToAll(EventNameReplayFinished, std::nullopt);
@ -83,7 +83,7 @@ void CReplayTurnManager::NotifyFinishedUpdate(u32 turn, const UpdateCallback& se
DoTurn(turn, sendEventToAll); DoTurn(turn, sendEventToAll);
// Compare hash if it exists in the replay and if we didn't have an OOS already // Compare hash if it exists in the replay and if we didn't have an OOS already
std::map<u32, std::pair<std::string, bool>>::iterator turnHashIt = m_ReplayHash.find(turn); std::map<turn_id_t, std::pair<std::string, bool>>::iterator turnHashIt = m_ReplayHash.find(turn);
if (m_HasSyncError || turnHashIt == m_ReplayHash.end()) if (m_HasSyncError || turnHashIt == m_ReplayHash.end())
return; return;
@ -119,9 +119,9 @@ void CReplayTurnManager::NotifyFinishedUpdate(u32 turn, const UpdateCallback& se
sendEventToAll(EventNameReplayOutOfSync, paramData); sendEventToAll(EventNameReplayOutOfSync, paramData);
} }
void CReplayTurnManager::DoTurn(u32 turn, const UpdateCallback& sendEventToAll) void CReplayTurnManager::DoTurn(turn_id_t turn, const UpdateCallback& sendEventToAll)
{ {
debug_printf("Executing turn %u of %u\n", turn, m_FinalTurn); debug_printf("Executing turn %d of %d\n", turn, m_FinalTurn);
m_TurnLength = m_ReplayTurnLengths[turn]; m_TurnLength = m_ReplayTurnLengths[turn];

View file

@ -40,18 +40,18 @@ class CReplayTurnManager : public CLocalTurnManager
public: public:
CReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay); CReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay);
void StoreReplayCommand(u32 turn, int player, const std::string& command); void StoreReplayCommand(turn_id_t turn, int player, const std::string& command);
void StoreReplayTurnLength(u32 turn, u32 turnLength); void StoreReplayTurnLength(turn_id_t turn, u32 turnLength);
void StoreReplayHash(u32 turn, const std::string& hash, bool quick); void StoreReplayHash(turn_id_t turn, const std::string& hash, bool quick);
void StoreFinalReplayTurn(u32 turn); void StoreFinalReplayTurn(turn_id_t turn);
private: private:
void NotifyFinishedUpdate(u32 turn, const UpdateCallback& sendEventToAll) override; void NotifyFinishedUpdate(turn_id_t turn, const UpdateCallback& sendEventToAll) override;
void DoTurn(u32 turn, const UpdateCallback& sendEventToAll); void DoTurn(turn_id_t turn, const UpdateCallback& sendEventToAll);
static const CStr EventNameReplayFinished; static const CStr EventNameReplayFinished;
static const CStr EventNameReplayOutOfSync; static const CStr EventNameReplayOutOfSync;
@ -59,13 +59,13 @@ private:
bool m_HasSyncError = false; bool m_HasSyncError = false;
// Contains the commands of every player on each turn // Contains the commands of every player on each turn
std::map<u32, std::vector<std::pair<player_id_t, std::string>>> m_ReplayCommands; std::map<turn_id_t, std::vector<std::pair<player_id_t, std::string>>> m_ReplayCommands;
// Contains the length of every turn // Contains the length of every turn
std::map<u32, u32> m_ReplayTurnLengths; std::map<turn_id_t, u32> m_ReplayTurnLengths;
// Contains all replay hash values and weather or not the quick hash method was used // Contains all replay hash values and weather or not the quick hash method was used
std::map<u32, std::pair<std::string, bool>> m_ReplayHash; std::map<turn_id_t, std::pair<std::string, bool>> m_ReplayHash;
}; };
#endif // INCLUDED_REPLAYTURNMANAGER #endif // INCLUDED_REPLAYTURNMANAGER

View file

@ -48,14 +48,14 @@ const CStr CTurnManager::EventNameSavegameLoaded = "SavegameLoaded";
CTurnManager::CTurnManager(CSimulation2& simulation, u32 defaultTurnLength, u32 commandDelay, int clientId, IReplayLogger& replay) CTurnManager::CTurnManager(CSimulation2& simulation, u32 defaultTurnLength, u32 commandDelay, int clientId, IReplayLogger& replay)
: m_Simulation2(simulation), m_CurrentTurn(0), m_CommandDelay(commandDelay), m_ReadyTurn(commandDelay - 1), m_TurnLength(defaultTurnLength), : m_Simulation2(simulation), m_CurrentTurn(0), m_CommandDelay(commandDelay), m_ReadyTurn(commandDelay - 1), m_TurnLength(defaultTurnLength),
m_PlayerId(-1), m_ClientId(clientId), m_DeltaSimTime(0), m_Replay(replay), m_PlayerId(-1), m_ClientId(clientId), m_DeltaSimTime(0), m_Replay(replay),
m_FinalTurn(std::numeric_limits<u32>::max()), m_TimeWarpNumTurns(0) m_FinalTurn(std::numeric_limits<turn_id_t>::max()), m_TimeWarpNumTurns(0)
{ {
Script::Request rq(m_Simulation2.GetScriptInterface()); Script::Request rq(m_Simulation2.GetScriptInterface());
m_QuickSaveMetadata.init(rq.cx); m_QuickSaveMetadata.init(rq.cx);
m_QueuedCommands.resize(1); m_QueuedCommands.resize(1);
} }
void CTurnManager::ResetState(u32 newCurrentTurn, u32 newReadyTurn) void CTurnManager::ResetState(turn_id_t newCurrentTurn, turn_id_t newReadyTurn)
{ {
m_CurrentTurn = newCurrentTurn; m_CurrentTurn = newCurrentTurn;
m_ReadyTurn = newReadyTurn; m_ReadyTurn = newReadyTurn;
@ -141,7 +141,7 @@ bool CTurnManager::Update(float simFrameLength, size_t maxTurns, const UpdateCal
// Put all the client commands into a single list, in a globally consistent order // Put all the client commands into a single list, in a globally consistent order
std::vector<SimulationCommand> commands; std::vector<SimulationCommand> commands;
for (std::pair<const u32, std::vector<SimulationCommand>>& p : m_QueuedCommands[0]) for (std::pair<const turn_id_t, std::vector<SimulationCommand>>& p : m_QueuedCommands[0])
commands.insert(commands.end(), std::make_move_iterator(p.second.begin()), std::make_move_iterator(p.second.end())); commands.insert(commands.end(), std::make_move_iterator(p.second.begin()), std::make_move_iterator(p.second.end()));
m_QueuedCommands.pop_front(); m_QueuedCommands.pop_front();
@ -184,7 +184,7 @@ bool CTurnManager::UpdateFastForward()
// Put all the client commands into a single list, in a globally consistent order // Put all the client commands into a single list, in a globally consistent order
std::vector<SimulationCommand> commands; std::vector<SimulationCommand> commands;
for (std::pair<const u32, std::vector<SimulationCommand>>& p : m_QueuedCommands[0]) for (std::pair<const turn_id_t, std::vector<SimulationCommand>>& p : m_QueuedCommands[0])
commands.insert(commands.end(), std::make_move_iterator(p.second.begin()), std::make_move_iterator(p.second.end())); commands.insert(commands.end(), std::make_move_iterator(p.second.begin()), std::make_move_iterator(p.second.end()));
m_QueuedCommands.pop_front(); m_QueuedCommands.pop_front();
@ -214,7 +214,7 @@ void CTurnManager::Interpolate(float simFrameLength, float realFrameLength)
m_Simulation2.Interpolate(simFrameLength, offset, realFrameLength); m_Simulation2.Interpolate(simFrameLength, offset, realFrameLength);
} }
void CTurnManager::AddCommand(int client, int player, JS::HandleValue data, u32 turn) void CTurnManager::AddCommand(int client, int player, JS::HandleValue data, turn_id_t turn)
{ {
NETTURN_LOG("AddCommand(client=%d player=%d turn=%d current=%d, ready=%d)\n", client, player, turn, m_CurrentTurn, m_ReadyTurn); NETTURN_LOG("AddCommand(client=%d player=%d turn=%d current=%d, ready=%d)\n", client, player, turn, m_CurrentTurn, m_ReadyTurn);
@ -239,7 +239,7 @@ void CTurnManager::AddCommand(int client, int player, JS::HandleValue data, u32
m_QueuedCommands[turn - (m_CurrentTurn+1)][client].emplace_back(player, rq.cx, data); m_QueuedCommands[turn - (m_CurrentTurn+1)][client].emplace_back(player, rq.cx, data);
} }
void CTurnManager::FinishedAllCommands(u32 turn, u32 turnLength) void CTurnManager::FinishedAllCommands(turn_id_t turn, u32 turnLength)
{ {
NETTURN_LOG("FinishedAllCommands(%d, %d)\n", turn, turnLength); NETTURN_LOG("FinishedAllCommands(%d, %d)\n", turn, turnLength);
@ -248,7 +248,7 @@ void CTurnManager::FinishedAllCommands(u32 turn, u32 turnLength)
m_TurnLength = turnLength; m_TurnLength = turnLength;
} }
bool CTurnManager::TurnNeedsFullHash(u32 turn) const bool CTurnManager::TurnNeedsFullHash(turn_id_t turn) const
{ {
// Check immediately for errors caused by e.g. inconsistent game versions // Check immediately for errors caused by e.g. inconsistent game versions
// (The hash is computed after the first sim update, so we start at turn == 1) // (The hash is computed after the first sim update, so we start at turn == 1)

View file

@ -101,7 +101,7 @@ public:
virtual ~CTurnManager() { } virtual ~CTurnManager() { }
void ResetState(u32 newCurrentTurn, u32 newReadyTurn); void ResetState(turn_id_t newCurrentTurn, turn_id_t newReadyTurn);
/** /**
* Set the current user's player ID, which will be added into command messages. * Set the current user's player ID, which will be added into command messages.
@ -146,7 +146,7 @@ public:
* Called when all commands for a given turn have been received. * Called when all commands for a given turn have been received.
* This allows Update to progress to that turn. * This allows Update to progress to that turn.
*/ */
void FinishedAllCommands(u32 turn, u32 turnLength); void FinishedAllCommands(turn_id_t turn, u32 turnLength);
/** /**
* Enables the recording of state snapshots every @p numTurns, * Enables the recording of state snapshots every @p numTurns,
@ -163,52 +163,52 @@ public:
void QuickSave(JS::HandleValue GUIMetadata); void QuickSave(JS::HandleValue GUIMetadata);
std::optional<JS::Value> TryQuickLoad(); std::optional<JS::Value> TryQuickLoad();
u32 GetCurrentTurn() const { return m_CurrentTurn; } turn_id_t GetCurrentTurn() const { return m_CurrentTurn; }
/** /**
* @return how many turns are ready to be computed. * @return how many turns are ready to be computed.
* (used to detect players/observers that fall behind the live game. * (used to detect players/observers that fall behind the live game.
*/ */
u32 GetPendingTurns() const { return m_ReadyTurn - m_CurrentTurn; } turn_id_t GetPendingTurns() const { return m_ReadyTurn - m_CurrentTurn; }
protected: protected:
/** /**
* Store a command to be executed at a given turn. * Store a command to be executed at a given turn.
*/ */
void AddCommand(int client, int player, JS::HandleValue data, u32 turn); void AddCommand(int client, int player, JS::HandleValue data, turn_id_t turn);
/** /**
* Called when this client has finished sending all its commands scheduled for the given turn. * Called when this client has finished sending all its commands scheduled for the given turn.
*/ */
virtual void NotifyFinishedOwnCommands(u32 turn) = 0; virtual void NotifyFinishedOwnCommands(turn_id_t turn) = 0;
/** /**
* Called when this client has finished a simulation update. * Called when this client has finished a simulation update.
*/ */
virtual void NotifyFinishedUpdate(u32 turn, const UpdateCallback& sendEventToAll) = 0; virtual void NotifyFinishedUpdate(turn_id_t turn, const UpdateCallback& sendEventToAll) = 0;
/** /**
* Returns whether we should compute a complete state hash for the given turn, * Returns whether we should compute a complete state hash for the given turn,
* instead of a quick less-complete hash. * instead of a quick less-complete hash.
*/ */
bool TurnNeedsFullHash(u32 turn) const; bool TurnNeedsFullHash(turn_id_t turn) const;
CSimulation2& m_Simulation2; CSimulation2& m_Simulation2;
/// The turn that we have most recently executed /// The turn that we have most recently executed
u32 m_CurrentTurn; turn_id_t m_CurrentTurn;
// Current command delay (commands are scheduled for m_CurrentTurn + m_CommandDelay) // Current command delay (commands are scheduled for m_CurrentTurn + m_CommandDelay)
u32 m_CommandDelay; u32 m_CommandDelay;
/// The latest turn for which we have received all commands from all clients /// The latest turn for which we have received all commands from all clients
u32 m_ReadyTurn; turn_id_t m_ReadyTurn;
// Current turn length // Current turn length
u32 m_TurnLength; u32 m_TurnLength;
/// Commands queued at each turn (index 0 is for m_CurrentTurn+1) /// Commands queued at each turn (index 0 is for m_CurrentTurn+1)
std::deque<std::map<u32, std::vector<SimulationCommand>>> m_QueuedCommands; std::deque<std::map<turn_id_t, std::vector<SimulationCommand>>> m_QueuedCommands;
int m_PlayerId; int m_PlayerId;
uint m_ClientId; uint m_ClientId;
@ -220,7 +220,7 @@ protected:
IReplayLogger& m_Replay; IReplayLogger& m_Replay;
// The number of the last turn that is allowed to be executed (used for replays) // The number of the last turn that is allowed to be executed (used for replays)
u32 m_FinalTurn; turn_id_t m_FinalTurn;
private: private:
size_t m_TimeWarpNumTurns; // 0 if disabled size_t m_TimeWarpNumTurns; // 0 if disabled