2024-03-08 18:15:25 +01:00
|
|
|
/* Copyright (C) 2024 Wildfire Games.
|
2023-12-03 01:30:12 +01:00
|
|
|
* This file is part of 0 A.D.
|
2010-01-09 20:20:14 +01:00
|
|
|
*
|
2023-12-03 01:30:12 +01:00
|
|
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
2010-01-09 20:20:14 +01:00
|
|
|
* 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.
|
|
|
|
*
|
2023-12-03 01:30:12 +01:00
|
|
|
* 0 A.D. is distributed in the hope that it will be useful,
|
2010-01-09 20:20:14 +01:00
|
|
|
* 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
|
2023-12-03 01:30:12 +01:00
|
|
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
2010-01-09 20:20:14 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "precompiled.h"
|
|
|
|
|
|
|
|
#include "Simulation2.h"
|
|
|
|
|
2011-10-24 16:31:05 +02:00
|
|
|
#include "graphics/MapReader.h"
|
|
|
|
#include "graphics/Terrain.h"
|
2011-03-05 02:56:59 +01:00
|
|
|
#include "lib/timer.h"
|
2011-05-25 12:39:13 +02:00
|
|
|
#include "lib/file/vfs/vfs_util.h"
|
2010-01-09 20:20:14 +01:00
|
|
|
#include "maths/MathUtil.h"
|
|
|
|
#include "ps/CLogger.h"
|
2012-12-03 21:06:58 +01:00
|
|
|
#include "ps/ConfigDB.h"
|
2010-01-09 20:20:14 +01:00
|
|
|
#include "ps/Filesystem.h"
|
2011-10-24 16:31:05 +02:00
|
|
|
#include "ps/Loader.h"
|
2010-05-20 02:59:01 +02:00
|
|
|
#include "ps/Profile.h"
|
|
|
|
#include "ps/Pyrogenesis.h"
|
2016-02-15 16:57:23 +01:00
|
|
|
#include "ps/Util.h"
|
2010-05-28 01:31:03 +02:00
|
|
|
#include "ps/XML/Xeromyces.h"
|
2023-07-07 22:12:16 +02:00
|
|
|
#include "scriptinterface/FunctionWrapper.h"
|
|
|
|
#include "scriptinterface/JSON.h"
|
|
|
|
#include "scriptinterface/Object.h"
|
|
|
|
#include "scriptinterface/ScriptContext.h"
|
|
|
|
#include "scriptinterface/ScriptInterface.h"
|
|
|
|
#include "scriptinterface/StructuredClone.h"
|
|
|
|
#include "simulation2/MessageTypes.h"
|
|
|
|
#include "simulation2/system/ComponentManager.h"
|
|
|
|
#include "simulation2/system/ParamNode.h"
|
|
|
|
#include "simulation2/system/SimContext.h"
|
|
|
|
#include "simulation2/components/ICmpAIManager.h"
|
|
|
|
#include "simulation2/components/ICmpCommandQueue.h"
|
|
|
|
#include "simulation2/components/ICmpTemplateManager.h"
|
2010-05-20 02:59:01 +02:00
|
|
|
|
2021-03-03 23:38:59 +01:00
|
|
|
#include <fstream>
|
2010-05-20 02:59:01 +02:00
|
|
|
#include <iomanip>
|
2021-04-13 15:46:45 +02:00
|
|
|
#include <memory>
|
2010-01-09 20:20:14 +01:00
|
|
|
|
|
|
|
class CSimulation2Impl
|
|
|
|
{
|
|
|
|
public:
|
2024-03-08 18:15:25 +01:00
|
|
|
CSimulation2Impl(CUnitManager* unitManager, ScriptContext& cx, CTerrain* terrain) :
|
2024-07-05 18:55:33 +02:00
|
|
|
m_SimContext{terrain, unitManager},
|
2024-03-08 18:15:25 +01:00
|
|
|
m_ComponentManager{m_SimContext, cx},
|
|
|
|
m_MapSettings{cx.GetGeneralJSContext()},
|
|
|
|
m_InitAttributes{cx.GetGeneralJSContext()}
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
|
|
|
m_ComponentManager.LoadComponentTypes();
|
|
|
|
|
2010-06-03 03:29:43 +02:00
|
|
|
RegisterFileReloadFunc(ReloadChangedFileCB, this);
|
|
|
|
|
2012-12-05 19:27:42 +01:00
|
|
|
// Tests won't have config initialised
|
|
|
|
if (CConfigDB::IsInitialised())
|
|
|
|
{
|
2014-11-18 00:29:49 +01:00
|
|
|
CFG_GET_VAL("ooslog", m_EnableOOSLog);
|
|
|
|
CFG_GET_VAL("serializationtest", m_EnableSerializationTest);
|
2016-11-15 14:26:58 +01:00
|
|
|
CFG_GET_VAL("rejointest", m_RejoinTestTurn);
|
2019-09-26 14:09:19 +02:00
|
|
|
if (m_RejoinTestTurn < 0) // Handle bogus values of the arg
|
2016-11-15 14:26:58 +01:00
|
|
|
m_RejoinTestTurn = -1;
|
2012-12-05 19:27:42 +01:00
|
|
|
}
|
2016-02-15 16:57:23 +01:00
|
|
|
|
|
|
|
if (m_EnableOOSLog)
|
2016-05-16 02:56:07 +02:00
|
|
|
{
|
|
|
|
m_OOSLogPath = createDateIndexSubdirectory(psLogDir() / "oos_logs");
|
2016-02-15 16:57:23 +01:00
|
|
|
debug_printf("Writing ooslogs to %s\n", m_OOSLogPath.string8().c_str());
|
2016-05-16 02:56:07 +02:00
|
|
|
}
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
2010-06-03 03:29:43 +02:00
|
|
|
~CSimulation2Impl()
|
|
|
|
{
|
|
|
|
UnregisterFileReloadFunc(ReloadChangedFileCB, this);
|
|
|
|
}
|
|
|
|
|
2011-01-12 13:29:00 +01:00
|
|
|
void ResetState(bool skipScriptedComponents, bool skipAI)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
|
|
|
m_DeltaTime = 0.0;
|
2010-09-27 01:05:25 +02:00
|
|
|
m_LastFrameOffset = 0.0f;
|
2010-04-30 01:22:18 +02:00
|
|
|
m_TurnNumber = 0;
|
2011-10-24 16:31:05 +02:00
|
|
|
ResetComponentState(m_ComponentManager, skipScriptedComponents, skipAI);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ResetComponentState(CComponentManager& componentManager, bool skipScriptedComponents, bool skipAI)
|
|
|
|
{
|
|
|
|
componentManager.ResetState();
|
2013-09-11 22:41:53 +02:00
|
|
|
componentManager.InitSystemEntity();
|
2014-05-18 15:44:08 +02:00
|
|
|
componentManager.AddSystemComponents(skipScriptedComponents, skipAI);
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
2011-10-24 16:31:05 +02:00
|
|
|
static bool LoadDefaultScripts(CComponentManager& componentManager, std::set<VfsPath>* loadedScripts);
|
|
|
|
static bool LoadScripts(CComponentManager& componentManager, std::set<VfsPath>* loadedScripts, const VfsPath& path);
|
2015-04-16 06:30:51 +02:00
|
|
|
static bool LoadTriggerScripts(CComponentManager& componentManager, JS::HandleValue mapSettings, std::set<VfsPath>* loadedScripts);
|
2011-05-03 14:38:42 +02:00
|
|
|
Status ReloadChangedFile(const VfsPath& path);
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2011-05-03 14:38:42 +02:00
|
|
|
static Status ReloadChangedFileCB(void* param, const VfsPath& path)
|
2010-06-03 03:29:43 +02:00
|
|
|
{
|
|
|
|
return static_cast<CSimulation2Impl*>(param)->ReloadChangedFile(path);
|
|
|
|
}
|
|
|
|
|
2011-03-05 02:56:59 +01:00
|
|
|
int ProgressiveLoad();
|
2011-10-24 16:31:05 +02:00
|
|
|
void Update(int turnLength, const std::vector<SimulationCommand>& commands);
|
|
|
|
static void UpdateComponents(CSimContext& simContext, fixed turnLengthFixed, const std::vector<SimulationCommand>& commands);
|
2012-06-06 21:37:03 +02:00
|
|
|
void Interpolate(float simFrameLength, float frameOffset, float realFrameLength);
|
2010-05-20 02:59:01 +02:00
|
|
|
|
|
|
|
void DumpState();
|
2010-01-09 20:20:14 +01:00
|
|
|
|
|
|
|
CSimContext m_SimContext;
|
|
|
|
CComponentManager m_ComponentManager;
|
|
|
|
double m_DeltaTime;
|
2010-09-27 01:05:25 +02:00
|
|
|
float m_LastFrameOffset;
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2012-05-04 23:48:46 +02:00
|
|
|
std::string m_StartupScript;
|
2015-01-24 15:46:52 +01:00
|
|
|
JS::PersistentRootedValue m_InitAttributes;
|
|
|
|
JS::PersistentRootedValue m_MapSettings;
|
2010-04-30 01:22:18 +02:00
|
|
|
|
2011-03-21 18:53:13 +01:00
|
|
|
std::set<VfsPath> m_LoadedScripts;
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2010-04-30 01:22:18 +02:00
|
|
|
uint32_t m_TurnNumber;
|
|
|
|
|
2024-03-08 18:15:25 +01:00
|
|
|
bool m_EnableOOSLog{false};
|
2016-02-15 16:57:23 +01:00
|
|
|
OsPath m_OOSLogPath;
|
2011-10-24 16:31:05 +02:00
|
|
|
|
|
|
|
// Functions and data for the serialization test mode: (see Update() for relevant comments)
|
|
|
|
|
2024-03-08 18:15:25 +01:00
|
|
|
bool m_EnableSerializationTest{false};
|
|
|
|
int m_RejoinTestTurn{-1};
|
|
|
|
bool m_TestingRejoin{false};
|
2016-11-15 14:26:58 +01:00
|
|
|
|
2021-04-13 15:46:45 +02:00
|
|
|
// Secondary simulation (NB: order matters for destruction).
|
|
|
|
std::unique_ptr<CComponentManager> m_SecondaryComponentManager;
|
|
|
|
std::unique_ptr<CTerrain> m_SecondaryTerrain;
|
|
|
|
std::unique_ptr<CSimContext> m_SecondaryContext;
|
|
|
|
std::unique_ptr<std::set<VfsPath>> m_SecondaryLoadedScripts;
|
2011-10-24 16:31:05 +02:00
|
|
|
|
|
|
|
struct SerializationTestState
|
|
|
|
{
|
|
|
|
std::stringstream state;
|
|
|
|
std::stringstream debug;
|
|
|
|
std::string hash;
|
|
|
|
};
|
|
|
|
|
|
|
|
void DumpSerializationTestState(SerializationTestState& state, const OsPath& path, const OsPath::String& suffix);
|
|
|
|
|
|
|
|
void ReportSerializationFailure(
|
|
|
|
SerializationTestState* primaryStateBefore, SerializationTestState* primaryStateAfter,
|
|
|
|
SerializationTestState* secondaryStateBefore, SerializationTestState* secondaryStateAfter);
|
|
|
|
|
2017-12-27 17:51:25 +01:00
|
|
|
void InitRNGSeedSimulation();
|
|
|
|
void InitRNGSeedAI();
|
|
|
|
|
2021-05-10 13:51:32 +02:00
|
|
|
static std::vector<SimulationCommand> CloneCommandsFromOtherCompartment(const ScriptInterface& newScript, const ScriptInterface& oldScript,
|
2011-10-24 16:31:05 +02:00
|
|
|
const std::vector<SimulationCommand>& commands)
|
|
|
|
{
|
2015-01-24 15:46:52 +01:00
|
|
|
std::vector<SimulationCommand> newCommands;
|
|
|
|
newCommands.reserve(commands.size());
|
2020-11-13 14:18:22 +01:00
|
|
|
|
Improve JS Exception handling.
- Check for pending exceptions after function calls and script
executions.
- Call LOGERROR instead of JS_ReportError when there is a conversion
error in FromJSVal, since that can only be called from C++ (where JS
errors don't really make sense). Instead, C++ callers of FromJSVal
should handle the failure and, themselves, either report an error or
simply do something else.
- Wrap JS_ReportError since that makes updating it later easier.
This isn't a systematical fix since ToJSVal also ought return a boolean
for failures, and we probably should trigger errors instead of warnings
on 'implicit' conversions, rather a preparation diff.
Part of the SM52 migration, stage: SM45 compatible (actually SM52
incompatible, too).
Based on a patch by: Itms
Comments by: Vladislavbelov, Stan`
Refs #742, #4893
Differential Revision: https://code.wildfiregames.com/D3093
This was SVN commit r24187.
2020-11-15 19:29:17 +01:00
|
|
|
ScriptRequest rqNew(newScript);
|
2015-08-20 14:58:41 +02:00
|
|
|
for (const SimulationCommand& command : commands)
|
2011-10-24 16:31:05 +02:00
|
|
|
{
|
2021-05-10 13:51:32 +02:00
|
|
|
JS::RootedValue tmpCommand(rqNew.cx, Script::CloneValueFromOtherCompartment(newScript, oldScript, command.data));
|
2021-05-13 19:23:52 +02:00
|
|
|
Script::FreezeObject(rqNew, tmpCommand, true);
|
2020-11-13 14:18:22 +01:00
|
|
|
SimulationCommand cmd(command.player, rqNew.cx, tmpCommand);
|
2015-01-24 15:46:52 +01:00
|
|
|
newCommands.emplace_back(std::move(cmd));
|
2011-10-24 16:31:05 +02:00
|
|
|
}
|
|
|
|
return newCommands;
|
|
|
|
}
|
2010-01-09 20:20:14 +01:00
|
|
|
};
|
|
|
|
|
2011-10-24 16:31:05 +02:00
|
|
|
bool CSimulation2Impl::LoadDefaultScripts(CComponentManager& componentManager, std::set<VfsPath>* loadedScripts)
|
|
|
|
{
|
|
|
|
return (
|
2013-07-10 02:08:05 +02:00
|
|
|
LoadScripts(componentManager, loadedScripts, L"simulation/components/interfaces/") &&
|
2011-10-24 16:31:05 +02:00
|
|
|
LoadScripts(componentManager, loadedScripts, L"simulation/helpers/") &&
|
|
|
|
LoadScripts(componentManager, loadedScripts, L"simulation/components/")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CSimulation2Impl::LoadScripts(CComponentManager& componentManager, std::set<VfsPath>* loadedScripts, const VfsPath& path)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
|
|
|
VfsPaths pathnames;
|
2011-05-25 12:39:13 +02:00
|
|
|
if (vfs::GetPathnames(g_VFS, path, L"*.js", pathnames) < 0)
|
2010-01-09 20:20:14 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
bool ok = true;
|
2020-11-26 23:28:50 +01:00
|
|
|
for (const VfsPath& scriptPath : pathnames)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2011-10-24 16:31:05 +02:00
|
|
|
if (loadedScripts)
|
2020-11-26 23:28:50 +01:00
|
|
|
loadedScripts->insert(scriptPath);
|
|
|
|
LOGMESSAGE("Loading simulation script '%s'", scriptPath.string8());
|
|
|
|
if (!componentManager.LoadScript(scriptPath))
|
2010-01-09 20:20:14 +01:00
|
|
|
ok = false;
|
|
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
2015-04-16 06:30:51 +02:00
|
|
|
bool CSimulation2Impl::LoadTriggerScripts(CComponentManager& componentManager, JS::HandleValue mapSettings, std::set<VfsPath>* loadedScripts)
|
2014-07-23 01:30:27 +02:00
|
|
|
{
|
|
|
|
bool ok = true;
|
2021-05-13 19:23:52 +02:00
|
|
|
ScriptRequest rq(componentManager.GetScriptInterface());
|
|
|
|
if (Script::HasProperty(rq, mapSettings, "TriggerScripts"))
|
2014-07-23 01:30:27 +02:00
|
|
|
{
|
|
|
|
std::vector<std::string> scriptNames;
|
2021-05-13 19:23:52 +02:00
|
|
|
Script::GetProperty(rq, mapSettings, "TriggerScripts", scriptNames);
|
2015-08-20 14:58:41 +02:00
|
|
|
for (const std::string& triggerScript : scriptNames)
|
2014-07-23 01:30:27 +02:00
|
|
|
{
|
2015-08-20 14:58:41 +02:00
|
|
|
std::string scriptName = "maps/" + triggerScript;
|
2015-04-16 06:30:51 +02:00
|
|
|
if (loadedScripts)
|
2015-06-03 22:34:59 +02:00
|
|
|
{
|
|
|
|
if (loadedScripts->find(scriptName) != loadedScripts->end())
|
2015-08-20 14:49:43 +02:00
|
|
|
continue;
|
2015-04-16 06:30:51 +02:00
|
|
|
loadedScripts->insert(scriptName);
|
2015-06-03 22:34:59 +02:00
|
|
|
}
|
2015-01-22 21:36:24 +01:00
|
|
|
LOGMESSAGE("Loading trigger script '%s'", scriptName.c_str());
|
2014-07-23 01:30:27 +02:00
|
|
|
if (!componentManager.LoadScript(scriptName.data()))
|
|
|
|
ok = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
2011-05-03 14:38:42 +02:00
|
|
|
Status CSimulation2Impl::ReloadChangedFile(const VfsPath& path)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
|
|
|
// Ignore if this file wasn't loaded as a script
|
|
|
|
// (TODO: Maybe we ought to load in any new .js files that are created in the right directories)
|
2013-07-10 02:08:05 +02:00
|
|
|
if (m_LoadedScripts.find(path) == m_LoadedScripts.end())
|
2010-01-09 20:20:14 +01:00
|
|
|
return INFO::OK;
|
|
|
|
|
|
|
|
// If the file doesn't exist (e.g. it was deleted), don't bother loading it since that'll give an error message.
|
|
|
|
// (Also don't bother trying to 'unload' it from the component manager, because that's not possible)
|
2011-02-25 17:30:55 +01:00
|
|
|
if (!VfsFileExists(path))
|
2010-01-09 20:20:14 +01:00
|
|
|
return INFO::OK;
|
|
|
|
|
2015-01-22 21:36:24 +01:00
|
|
|
LOGMESSAGE("Reloading simulation script '%s'", path.string8());
|
2013-07-10 02:08:05 +02:00
|
|
|
if (!m_ComponentManager.LoadScript(path, true))
|
2010-01-09 20:20:14 +01:00
|
|
|
return ERR::FAIL;
|
|
|
|
|
|
|
|
return INFO::OK;
|
|
|
|
}
|
|
|
|
|
2011-03-05 02:56:59 +01:00
|
|
|
int CSimulation2Impl::ProgressiveLoad()
|
|
|
|
{
|
|
|
|
// yield after this time is reached. balances increased progress bar
|
|
|
|
// smoothness vs. slowing down loading.
|
|
|
|
const double end_time = timer_Time() + 200e-3;
|
|
|
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
bool progressed = false;
|
|
|
|
int total = 0;
|
|
|
|
int progress = 0;
|
|
|
|
|
|
|
|
CMessageProgressiveLoad msg(&progressed, &total, &progress);
|
|
|
|
|
|
|
|
m_ComponentManager.BroadcastMessage(msg);
|
|
|
|
|
|
|
|
if (!progressed || total == 0)
|
|
|
|
return 0; // we have nothing left to load
|
|
|
|
|
|
|
|
ret = Clamp(100*progress / total, 1, 100);
|
|
|
|
}
|
|
|
|
while (timer_Time() < end_time);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2011-10-24 16:31:05 +02:00
|
|
|
void CSimulation2Impl::DumpSerializationTestState(SerializationTestState& state, const OsPath& path, const OsPath::String& suffix)
|
|
|
|
{
|
|
|
|
if (!state.hash.empty())
|
|
|
|
{
|
|
|
|
std::ofstream file (OsString(path / (L"hash." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc);
|
|
|
|
file << Hexify(state.hash);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!state.debug.str().empty())
|
|
|
|
{
|
|
|
|
std::ofstream file (OsString(path / (L"debug." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc);
|
|
|
|
file << state.debug.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!state.state.str().empty())
|
|
|
|
{
|
|
|
|
std::ofstream file (OsString(path / (L"state." + suffix)).c_str(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary);
|
|
|
|
file << state.state.str();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CSimulation2Impl::ReportSerializationFailure(
|
|
|
|
SerializationTestState* primaryStateBefore, SerializationTestState* primaryStateAfter,
|
|
|
|
SerializationTestState* secondaryStateBefore, SerializationTestState* secondaryStateAfter)
|
|
|
|
{
|
2016-05-16 02:56:07 +02:00
|
|
|
const OsPath path = createDateIndexSubdirectory(psLogDir() / "serializationtest");
|
2016-02-15 16:57:23 +01:00
|
|
|
debug_printf("Writing serializationtest-data to %s\n", path.string8().c_str());
|
2011-10-24 16:31:05 +02:00
|
|
|
|
|
|
|
// Clean up obsolete files from previous runs
|
|
|
|
wunlink(path / "hash.before.a");
|
|
|
|
wunlink(path / "hash.before.b");
|
|
|
|
wunlink(path / "debug.before.a");
|
|
|
|
wunlink(path / "debug.before.b");
|
|
|
|
wunlink(path / "state.before.a");
|
|
|
|
wunlink(path / "state.before.b");
|
|
|
|
wunlink(path / "hash.after.a");
|
|
|
|
wunlink(path / "hash.after.b");
|
|
|
|
wunlink(path / "debug.after.a");
|
|
|
|
wunlink(path / "debug.after.b");
|
|
|
|
wunlink(path / "state.after.a");
|
|
|
|
wunlink(path / "state.after.b");
|
|
|
|
|
|
|
|
if (primaryStateBefore)
|
|
|
|
DumpSerializationTestState(*primaryStateBefore, path, L"before.a");
|
|
|
|
if (primaryStateAfter)
|
|
|
|
DumpSerializationTestState(*primaryStateAfter, path, L"after.a");
|
|
|
|
if (secondaryStateBefore)
|
|
|
|
DumpSerializationTestState(*secondaryStateBefore, path, L"before.b");
|
|
|
|
if (secondaryStateAfter)
|
|
|
|
DumpSerializationTestState(*secondaryStateAfter, path, L"after.b");
|
|
|
|
|
|
|
|
debug_warn(L"Serialization test failure");
|
|
|
|
}
|
|
|
|
|
2017-12-27 17:51:25 +01:00
|
|
|
void CSimulation2Impl::InitRNGSeedSimulation()
|
|
|
|
{
|
|
|
|
u32 seed = 0;
|
2021-05-13 19:23:52 +02:00
|
|
|
ScriptRequest rq(m_ComponentManager.GetScriptInterface());
|
|
|
|
if (!Script::HasProperty(rq, m_MapSettings, "Seed") ||
|
|
|
|
!Script::GetProperty(rq, m_MapSettings, "Seed", seed))
|
2017-12-27 17:51:25 +01:00
|
|
|
LOGWARNING("CSimulation2Impl::InitRNGSeedSimulation: No seed value specified - using %d", seed);
|
|
|
|
|
|
|
|
m_ComponentManager.SetRNGSeed(seed);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CSimulation2Impl::InitRNGSeedAI()
|
|
|
|
{
|
|
|
|
u32 seed = 0;
|
2021-05-13 19:23:52 +02:00
|
|
|
ScriptRequest rq(m_ComponentManager.GetScriptInterface());
|
|
|
|
if (!Script::HasProperty(rq, m_MapSettings, "AISeed") ||
|
|
|
|
!Script::GetProperty(rq, m_MapSettings, "AISeed", seed))
|
2017-12-27 17:51:25 +01:00
|
|
|
LOGWARNING("CSimulation2Impl::InitRNGSeedAI: No seed value specified - using %d", seed);
|
|
|
|
|
|
|
|
CmpPtr<ICmpAIManager> cmpAIManager(m_SimContext, SYSTEM_ENTITY);
|
|
|
|
if (cmpAIManager)
|
|
|
|
cmpAIManager->SetRNGSeed(seed);
|
|
|
|
}
|
|
|
|
|
2011-10-24 16:31:05 +02:00
|
|
|
void CSimulation2Impl::Update(int turnLength, const std::vector<SimulationCommand>& commands)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2011-11-04 02:35:50 +01:00
|
|
|
PROFILE3("sim update");
|
|
|
|
PROFILE2_ATTR("turn %d", (int)m_TurnNumber);
|
|
|
|
|
2010-05-20 02:59:01 +02:00
|
|
|
fixed turnLengthFixed = fixed::FromInt(turnLength) / 1000;
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2011-10-24 16:31:05 +02:00
|
|
|
/*
|
|
|
|
* In serialization test mode, we save the original (primary) simulation state before each turn update.
|
|
|
|
* We run the update, then load the saved state into a secondary context.
|
|
|
|
* We serialize that again and compare to the original serialization (to check that
|
|
|
|
* serialize->deserialize->serialize is equivalent to serialize).
|
|
|
|
* Then we run the update on the secondary context, and check that its new serialized
|
|
|
|
* state matches the primary context after the update (to check that the simulation doesn't depend
|
|
|
|
* on anything that's not serialized).
|
2016-11-15 14:26:58 +01:00
|
|
|
*
|
|
|
|
* In rejoin test mode, the secondary simulation is initialized from serialized data at turn N, then both
|
|
|
|
* simulations run independantly while comparing their states each turn. This is way faster than a
|
|
|
|
* complete serialization test and allows us to reproduce OOSes on rejoin.
|
2011-10-24 16:31:05 +02:00
|
|
|
*/
|
|
|
|
|
2011-10-28 15:18:16 +02:00
|
|
|
const bool serializationTestDebugDump = false; // set true to save human-readable state dumps before an error is detected, for debugging (but slow)
|
2011-10-24 16:31:05 +02:00
|
|
|
const bool serializationTestHash = true; // set true to save and compare hash of state
|
|
|
|
|
|
|
|
SerializationTestState primaryStateBefore;
|
2017-11-25 07:49:58 +01:00
|
|
|
const ScriptInterface& scriptInterface = m_ComponentManager.GetScriptInterface();
|
2015-01-24 15:46:52 +01:00
|
|
|
|
2016-11-15 14:26:58 +01:00
|
|
|
const bool startRejoinTest = (int64_t) m_RejoinTestTurn == m_TurnNumber;
|
|
|
|
if (startRejoinTest)
|
|
|
|
m_TestingRejoin = true;
|
|
|
|
|
|
|
|
if (m_EnableSerializationTest || m_TestingRejoin)
|
2011-10-24 16:31:05 +02:00
|
|
|
{
|
|
|
|
ENSURE(m_ComponentManager.SerializeState(primaryStateBefore.state));
|
|
|
|
if (serializationTestDebugDump)
|
|
|
|
ENSURE(m_ComponentManager.DumpDebugState(primaryStateBefore.debug, false));
|
|
|
|
if (serializationTestHash)
|
|
|
|
ENSURE(m_ComponentManager.ComputeStateHash(primaryStateBefore.hash, false));
|
|
|
|
}
|
|
|
|
|
|
|
|
UpdateComponents(m_SimContext, turnLengthFixed, commands);
|
|
|
|
|
2016-11-15 14:26:58 +01:00
|
|
|
if (m_EnableSerializationTest || startRejoinTest)
|
2011-10-24 16:31:05 +02:00
|
|
|
{
|
2016-11-15 14:26:58 +01:00
|
|
|
if (startRejoinTest)
|
|
|
|
debug_printf("Initializing the secondary simulation\n");
|
|
|
|
|
2021-04-13 15:46:45 +02:00
|
|
|
m_SecondaryTerrain = std::make_unique<CTerrain>();
|
2016-11-15 14:26:58 +01:00
|
|
|
|
2024-07-05 18:55:33 +02:00
|
|
|
m_SecondaryContext = std::make_unique<CSimContext>(m_SecondaryTerrain.get());
|
2016-11-15 14:26:58 +01:00
|
|
|
|
2021-04-13 15:46:45 +02:00
|
|
|
m_SecondaryComponentManager = std::make_unique<CComponentManager>(*m_SecondaryContext, scriptInterface.GetContext());
|
2016-11-15 14:26:58 +01:00
|
|
|
m_SecondaryComponentManager->LoadComponentTypes();
|
|
|
|
|
2021-04-13 15:46:45 +02:00
|
|
|
m_SecondaryLoadedScripts = std::make_unique<std::set<VfsPath>>();
|
|
|
|
ENSURE(LoadDefaultScripts(*m_SecondaryComponentManager, m_SecondaryLoadedScripts.get()));
|
2016-11-15 14:26:58 +01:00
|
|
|
ResetComponentState(*m_SecondaryComponentManager, false, false);
|
2011-10-24 16:31:05 +02:00
|
|
|
|
2021-05-13 19:23:52 +02:00
|
|
|
ScriptRequest rq(scriptInterface);
|
|
|
|
|
2014-07-23 01:30:27 +02:00
|
|
|
// Load the trigger scripts after we have loaded the simulation.
|
2014-07-26 12:58:24 +02:00
|
|
|
{
|
Improve JS Exception handling.
- Check for pending exceptions after function calls and script
executions.
- Call LOGERROR instead of JS_ReportError when there is a conversion
error in FromJSVal, since that can only be called from C++ (where JS
errors don't really make sense). Instead, C++ callers of FromJSVal
should handle the failure and, themselves, either report an error or
simply do something else.
- Wrap JS_ReportError since that makes updating it later easier.
This isn't a systematical fix since ToJSVal also ought return a boolean
for failures, and we probably should trigger errors instead of warnings
on 'implicit' conversions, rather a preparation diff.
Part of the SM52 migration, stage: SM45 compatible (actually SM52
incompatible, too).
Based on a patch by: Itms
Comments by: Vladislavbelov, Stan`
Refs #742, #4893
Differential Revision: https://code.wildfiregames.com/D3093
This was SVN commit r24187.
2020-11-15 19:29:17 +01:00
|
|
|
ScriptRequest rq2(m_SecondaryComponentManager->GetScriptInterface());
|
2021-05-10 13:51:32 +02:00
|
|
|
JS::RootedValue mapSettingsCloned(rq2.cx, Script::CloneValueFromOtherCompartment(m_SecondaryComponentManager->GetScriptInterface(), scriptInterface, m_MapSettings));
|
2021-04-13 15:46:45 +02:00
|
|
|
ENSURE(LoadTriggerScripts(*m_SecondaryComponentManager, mapSettingsCloned, m_SecondaryLoadedScripts.get()));
|
2014-07-26 12:58:24 +02:00
|
|
|
}
|
2014-07-23 01:30:27 +02:00
|
|
|
|
2011-10-24 16:31:05 +02:00
|
|
|
// Load the map into the secondary simulation
|
|
|
|
|
|
|
|
LDR_BeginRegistering();
|
2021-02-14 00:53:40 +01:00
|
|
|
std::unique_ptr<CMapReader> mapReader = std::make_unique<CMapReader>();
|
2016-01-23 02:02:57 +01:00
|
|
|
|
2015-01-24 15:46:52 +01:00
|
|
|
std::string mapType;
|
2021-05-13 19:23:52 +02:00
|
|
|
Script::GetProperty(rq, m_InitAttributes, "mapType", mapType);
|
2015-01-24 15:46:52 +01:00
|
|
|
if (mapType == "random")
|
|
|
|
{
|
|
|
|
// TODO: support random map scripts
|
|
|
|
debug_warn(L"Serialization test mode does not support random maps");
|
2011-10-24 16:31:05 +02:00
|
|
|
}
|
2015-01-24 15:46:52 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
std::wstring mapFile;
|
2021-05-13 19:23:52 +02:00
|
|
|
Script::GetProperty(rq, m_InitAttributes, "map", mapFile);
|
2015-01-24 15:46:52 +01:00
|
|
|
|
|
|
|
VfsPath mapfilename = VfsPath(mapFile).ChangeExtension(L".pmp");
|
2023-11-19 21:13:19 +01:00
|
|
|
mapReader->LoadMap(mapfilename, scriptInterface.GetContext(), JS::UndefinedHandleValue,
|
2021-04-13 15:46:45 +02:00
|
|
|
m_SecondaryTerrain.get(), NULL, NULL, NULL, NULL, NULL, NULL,
|
|
|
|
NULL, NULL, m_SecondaryContext.get(), INVALID_PLAYER, true); // throws exception on failure
|
2015-01-24 15:46:52 +01:00
|
|
|
}
|
|
|
|
|
2011-10-24 16:31:05 +02:00
|
|
|
LDR_EndRegistering();
|
|
|
|
ENSURE(LDR_NonprogressiveLoad() == INFO::OK);
|
2016-11-15 14:26:58 +01:00
|
|
|
ENSURE(m_SecondaryComponentManager->DeserializeState(primaryStateBefore.state));
|
|
|
|
}
|
2011-10-24 16:31:05 +02:00
|
|
|
|
2016-11-15 14:26:58 +01:00
|
|
|
if (m_EnableSerializationTest || m_TestingRejoin)
|
|
|
|
{
|
2011-10-24 16:31:05 +02:00
|
|
|
SerializationTestState secondaryStateBefore;
|
2016-11-15 14:26:58 +01:00
|
|
|
ENSURE(m_SecondaryComponentManager->SerializeState(secondaryStateBefore.state));
|
2011-10-24 16:31:05 +02:00
|
|
|
if (serializationTestDebugDump)
|
2016-11-15 14:26:58 +01:00
|
|
|
ENSURE(m_SecondaryComponentManager->DumpDebugState(secondaryStateBefore.debug, false));
|
2011-10-24 16:31:05 +02:00
|
|
|
if (serializationTestHash)
|
2016-11-15 14:26:58 +01:00
|
|
|
ENSURE(m_SecondaryComponentManager->ComputeStateHash(secondaryStateBefore.hash, false));
|
2011-10-24 16:31:05 +02:00
|
|
|
|
|
|
|
if (primaryStateBefore.state.str() != secondaryStateBefore.state.str() ||
|
|
|
|
primaryStateBefore.hash != secondaryStateBefore.hash)
|
|
|
|
{
|
|
|
|
ReportSerializationFailure(&primaryStateBefore, NULL, &secondaryStateBefore, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
SerializationTestState primaryStateAfter;
|
|
|
|
ENSURE(m_ComponentManager.SerializeState(primaryStateAfter.state));
|
|
|
|
if (serializationTestHash)
|
|
|
|
ENSURE(m_ComponentManager.ComputeStateHash(primaryStateAfter.hash, false));
|
|
|
|
|
2016-11-15 14:26:58 +01:00
|
|
|
UpdateComponents(*m_SecondaryContext, turnLengthFixed,
|
2021-05-10 13:51:32 +02:00
|
|
|
CloneCommandsFromOtherCompartment(m_SecondaryComponentManager->GetScriptInterface(), scriptInterface, commands));
|
2011-10-24 16:31:05 +02:00
|
|
|
SerializationTestState secondaryStateAfter;
|
2016-11-15 14:26:58 +01:00
|
|
|
ENSURE(m_SecondaryComponentManager->SerializeState(secondaryStateAfter.state));
|
2011-10-24 16:31:05 +02:00
|
|
|
if (serializationTestHash)
|
2016-11-15 14:26:58 +01:00
|
|
|
ENSURE(m_SecondaryComponentManager->ComputeStateHash(secondaryStateAfter.hash, false));
|
2011-10-24 16:31:05 +02:00
|
|
|
|
|
|
|
if (primaryStateAfter.state.str() != secondaryStateAfter.state.str() ||
|
|
|
|
primaryStateAfter.hash != secondaryStateAfter.hash)
|
|
|
|
{
|
2011-10-28 15:18:16 +02:00
|
|
|
// Only do the (slow) dumping now we know we're going to need to report it
|
|
|
|
ENSURE(m_ComponentManager.DumpDebugState(primaryStateAfter.debug, false));
|
2016-11-15 14:26:58 +01:00
|
|
|
ENSURE(m_SecondaryComponentManager->DumpDebugState(secondaryStateAfter.debug, false));
|
2011-10-28 15:18:16 +02:00
|
|
|
|
2011-10-24 16:31:05 +02:00
|
|
|
ReportSerializationFailure(&primaryStateBefore, &primaryStateAfter, &secondaryStateBefore, &secondaryStateAfter);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run the GC occasionally
|
2014-09-22 22:13:04 +02:00
|
|
|
// No delay because a lot of garbage accumulates in one turn and in non-visual replays there are
|
|
|
|
// much more turns in the same time than in normal games.
|
2015-01-24 15:46:52 +01:00
|
|
|
// Every 500 turns we run a shrinking GC, which decommits unused memory and frees all JIT code.
|
|
|
|
// Based on testing, this seems to be a good compromise between memory usage and performance.
|
|
|
|
// Also check the comment about gcPreserveCode in the ScriptInterface code and this forum topic:
|
|
|
|
// http://www.wildfiregames.com/forum/index.php?showtopic=18466&p=300323
|
|
|
|
//
|
2011-10-24 16:31:05 +02:00
|
|
|
// (TODO: we ought to schedule this for a frame where we're not
|
|
|
|
// running the sim update, to spread the load)
|
2015-01-24 15:46:52 +01:00
|
|
|
if (m_TurnNumber % 500 == 0)
|
2023-11-19 21:13:19 +01:00
|
|
|
scriptInterface.GetContext().ShrinkingGC();
|
2015-01-24 15:46:52 +01:00
|
|
|
else
|
2023-11-19 21:13:19 +01:00
|
|
|
scriptInterface.GetContext().MaybeIncrementalGC(0.0f);
|
2011-10-24 16:31:05 +02:00
|
|
|
|
|
|
|
if (m_EnableOOSLog)
|
|
|
|
DumpState();
|
|
|
|
|
|
|
|
++m_TurnNumber;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CSimulation2Impl::UpdateComponents(CSimContext& simContext, fixed turnLengthFixed, const std::vector<SimulationCommand>& commands)
|
|
|
|
{
|
2010-09-03 11:55:14 +02:00
|
|
|
// TODO: the update process is pretty ugly, with lots of messages and dependencies
|
|
|
|
// between different components. Ought to work out a nicer way to do this.
|
|
|
|
|
2011-10-24 16:31:05 +02:00
|
|
|
CComponentManager& componentManager = simContext.GetComponentManager();
|
|
|
|
|
Fix UpdateComponents logic for pathfinding following d592bf9cb6
Following d592bf9cb6, paths requested at turn N were set-up to be
computed between the end of turn N and the start of turn N+1 (which
would ultimately allow threading this computation), via calls to
'StartProcessingMoves' and 'FetchAsyncResultsAndSendMessages'.
However, the call to UpdateGrid() remained at the start of turn N+1,
between the 'start' and 'fetch' calls. Since all paths are currently
computed on the 'start' call, this means all paths are computed on a
(possibly) dirty pathfinder grid.
In particular, this leads to OOS on rejoin since the rejoiner will
recompute the grid before computing the outstanding paths.
This would also obviously be buggy in a threaded environment, since some
paths might be computed on the fresh and some on the dirty grid.
Finally, MT_TurnStart was sent before the paths were computed, which
might lead to further pathfinder grid changes (not a crashing problem
without threading, but still conceptually odd). The 'fetch' call is thus
moved before it.
This thus fixes d592bf9cb6/D1918, after 92ad6a61fa already fixed a first
issue.
Since the grid is now only updated at the end of a turn, we need to
ensure that it is correct on Turn 0, thus the pathfinder recomputes it
on InitGame.
Refs D14
Reported by: Itms
Fixes #5851
Differential Revision: https://code.wildfiregames.com/D3064
This was SVN commit r24142.
2020-11-08 09:58:19 +01:00
|
|
|
CmpPtr<ICmpPathfinder> cmpPathfinder(simContext, SYSTEM_ENTITY);
|
|
|
|
if (cmpPathfinder)
|
2021-04-14 09:23:47 +02:00
|
|
|
cmpPathfinder->SendRequestedPaths();
|
Fix UpdateComponents logic for pathfinding following d592bf9cb6
Following d592bf9cb6, paths requested at turn N were set-up to be
computed between the end of turn N and the start of turn N+1 (which
would ultimately allow threading this computation), via calls to
'StartProcessingMoves' and 'FetchAsyncResultsAndSendMessages'.
However, the call to UpdateGrid() remained at the start of turn N+1,
between the 'start' and 'fetch' calls. Since all paths are currently
computed on the 'start' call, this means all paths are computed on a
(possibly) dirty pathfinder grid.
In particular, this leads to OOS on rejoin since the rejoiner will
recompute the grid before computing the outstanding paths.
This would also obviously be buggy in a threaded environment, since some
paths might be computed on the fresh and some on the dirty grid.
Finally, MT_TurnStart was sent before the paths were computed, which
might lead to further pathfinder grid changes (not a crashing problem
without threading, but still conceptually odd). The 'fetch' call is thus
moved before it.
This thus fixes d592bf9cb6/D1918, after 92ad6a61fa already fixed a first
issue.
Since the grid is now only updated at the end of a turn, we need to
ensure that it is correct on Turn 0, thus the pathfinder recomputes it
on InitGame.
Refs D14
Reported by: Itms
Fixes #5851
Differential Revision: https://code.wildfiregames.com/D3064
This was SVN commit r24142.
2020-11-08 09:58:19 +01:00
|
|
|
|
2016-06-25 15:12:35 +02:00
|
|
|
{
|
|
|
|
PROFILE2("Sim - Update Start");
|
|
|
|
CMessageTurnStart msgTurnStart;
|
|
|
|
componentManager.BroadcastMessage(msgTurnStart);
|
|
|
|
}
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2011-01-12 13:29:00 +01:00
|
|
|
|
2011-10-24 16:31:05 +02:00
|
|
|
CmpPtr<ICmpCommandQueue> cmpCommandQueue(simContext, SYSTEM_ENTITY);
|
2012-02-08 03:46:15 +01:00
|
|
|
if (cmpCommandQueue)
|
2010-05-20 02:59:01 +02:00
|
|
|
cmpCommandQueue->FlushTurn(commands);
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2011-06-26 09:03:08 +02:00
|
|
|
// Process newly generated move commands so the UI feels snappy
|
2012-02-08 03:46:15 +01:00
|
|
|
if (cmpPathfinder)
|
2019-09-15 11:27:10 +02:00
|
|
|
{
|
|
|
|
cmpPathfinder->StartProcessingMoves(true);
|
2021-04-14 09:23:47 +02:00
|
|
|
cmpPathfinder->SendRequestedPaths();
|
2019-09-15 11:27:10 +02:00
|
|
|
}
|
2010-09-03 11:55:14 +02:00
|
|
|
// Send all the update phases
|
|
|
|
{
|
2016-06-25 15:12:35 +02:00
|
|
|
PROFILE2("Sim - Update");
|
2010-09-03 11:55:14 +02:00
|
|
|
CMessageUpdate msgUpdate(turnLengthFixed);
|
2011-10-24 16:31:05 +02:00
|
|
|
componentManager.BroadcastMessage(msgUpdate);
|
2010-09-03 11:55:14 +02:00
|
|
|
}
|
2019-09-15 11:27:10 +02:00
|
|
|
|
2010-09-03 11:55:14 +02:00
|
|
|
{
|
|
|
|
CMessageUpdate_MotionFormation msgUpdate(turnLengthFixed);
|
2011-10-24 16:31:05 +02:00
|
|
|
componentManager.BroadcastMessage(msgUpdate);
|
2010-09-03 11:55:14 +02:00
|
|
|
}
|
2011-06-26 09:03:08 +02:00
|
|
|
|
|
|
|
// Process move commands for formations (group proxy)
|
2012-02-08 03:46:15 +01:00
|
|
|
if (cmpPathfinder)
|
2019-09-15 11:27:10 +02:00
|
|
|
{
|
|
|
|
cmpPathfinder->StartProcessingMoves(true);
|
2021-04-14 09:23:47 +02:00
|
|
|
cmpPathfinder->SendRequestedPaths();
|
2019-09-15 11:27:10 +02:00
|
|
|
}
|
2011-06-26 09:03:08 +02:00
|
|
|
|
2010-09-03 11:55:14 +02:00
|
|
|
{
|
2016-06-25 15:12:35 +02:00
|
|
|
PROFILE2("Sim - Motion Unit");
|
2010-09-03 11:55:14 +02:00
|
|
|
CMessageUpdate_MotionUnit msgUpdate(turnLengthFixed);
|
2011-10-24 16:31:05 +02:00
|
|
|
componentManager.BroadcastMessage(msgUpdate);
|
2010-09-03 11:55:14 +02:00
|
|
|
}
|
|
|
|
{
|
2016-06-25 15:12:35 +02:00
|
|
|
PROFILE2("Sim - Update Final");
|
2010-09-03 11:55:14 +02:00
|
|
|
CMessageUpdate_Final msgUpdate(turnLengthFixed);
|
2011-10-24 16:31:05 +02:00
|
|
|
componentManager.BroadcastMessage(msgUpdate);
|
2010-09-03 11:55:14 +02:00
|
|
|
}
|
2010-01-22 21:03:14 +01:00
|
|
|
|
2010-05-20 02:59:01 +02:00
|
|
|
// Clean up any entities destroyed during the simulation update
|
2011-10-24 16:31:05 +02:00
|
|
|
componentManager.FlushDestroyedComponents();
|
2019-09-15 11:27:10 +02:00
|
|
|
|
Run the AI in the same Compartment as the simulation. Let the AI access Sim data.
This is a paradigm change for AI computation.
Historically, the AI was intended to be run in a separate thread from
the simulation. The idea was that slow AI wouldn't stop the renderer
from being smooth.
In that original design, the AI received a copy of the game world and
used that to run its logic. This meant the simulation could safely do
whatever it wanted in the meantime. This copy was done via AIProxy &
AIInterface.
This design ended up having significant flaws:
- The copying impacts the simulation negatively, particularly because
AIProxy subscribes to a lot of messages (sometimes sent exclusively to
it). This time cannot be threaded, and impacts MP games without AIs.
- Copying the data is increasingly difficult. Modifiers are a headache,
LOS is not implemented. Lots of logic is duplicated.
The intended benefits of the design also failed to realise somewhat:
- The AI was never threaded, and in fact, it is probably better to try
and thread Sim + AI from the renderer than just the AI, at which point
threading the AI specifically brings little benefit.
The new design is much simpler and straighforward, but this has some
side-effects:
- The AI can now change the simulation. This can be used for cheating,
or possibly for a tutorial AI.
- The AI runs in the same GC zone as the simulation, which may lead to
more frequent Sim GCs (but overall we might expect a reduction in
temporary objects).
- The AI state was essentially cached, so replacing some functions with
Engine.QueryInterface might be slower. The tradeoff should be balanced
by lower AIProxy computation times.
Future work:
- Threading some specific AI tasks could still be worthwhile, but should
be done in specific worker threads, allowed to run over several turns if
needed.
Technical note: the AI 'global' is in its own Realm, which means name
collisions with the same are not possible.
Other notes:
- The RL Interface uses the AI Interface and thus will gradually lose
some data there. Given that the RL Interface can now request data
however, this should be dine.
Refs #5962, #2370
Differential Revision: https://code.wildfiregames.com/D3769
This was SVN commit r26274.
2022-01-30 14:33:34 +01:00
|
|
|
// Compute AI immediately at turn's end.
|
|
|
|
CmpPtr<ICmpAIManager> cmpAIManager(simContext, SYSTEM_ENTITY);
|
|
|
|
if (cmpAIManager)
|
|
|
|
{
|
|
|
|
cmpAIManager->StartComputation();
|
|
|
|
cmpAIManager->PushCommands();
|
|
|
|
}
|
|
|
|
|
2019-09-15 11:27:10 +02:00
|
|
|
// Process all remaining moves
|
|
|
|
if (cmpPathfinder)
|
Fix UpdateComponents logic for pathfinding following d592bf9cb6
Following d592bf9cb6, paths requested at turn N were set-up to be
computed between the end of turn N and the start of turn N+1 (which
would ultimately allow threading this computation), via calls to
'StartProcessingMoves' and 'FetchAsyncResultsAndSendMessages'.
However, the call to UpdateGrid() remained at the start of turn N+1,
between the 'start' and 'fetch' calls. Since all paths are currently
computed on the 'start' call, this means all paths are computed on a
(possibly) dirty pathfinder grid.
In particular, this leads to OOS on rejoin since the rejoiner will
recompute the grid before computing the outstanding paths.
This would also obviously be buggy in a threaded environment, since some
paths might be computed on the fresh and some on the dirty grid.
Finally, MT_TurnStart was sent before the paths were computed, which
might lead to further pathfinder grid changes (not a crashing problem
without threading, but still conceptually odd). The 'fetch' call is thus
moved before it.
This thus fixes d592bf9cb6/D1918, after 92ad6a61fa already fixed a first
issue.
Since the grid is now only updated at the end of a turn, we need to
ensure that it is correct on Turn 0, thus the pathfinder recomputes it
on InitGame.
Refs D14
Reported by: Itms
Fixes #5851
Differential Revision: https://code.wildfiregames.com/D3064
This was SVN commit r24142.
2020-11-08 09:58:19 +01:00
|
|
|
{
|
|
|
|
cmpPathfinder->UpdateGrid();
|
2019-09-15 11:27:10 +02:00
|
|
|
cmpPathfinder->StartProcessingMoves(false);
|
Fix UpdateComponents logic for pathfinding following d592bf9cb6
Following d592bf9cb6, paths requested at turn N were set-up to be
computed between the end of turn N and the start of turn N+1 (which
would ultimately allow threading this computation), via calls to
'StartProcessingMoves' and 'FetchAsyncResultsAndSendMessages'.
However, the call to UpdateGrid() remained at the start of turn N+1,
between the 'start' and 'fetch' calls. Since all paths are currently
computed on the 'start' call, this means all paths are computed on a
(possibly) dirty pathfinder grid.
In particular, this leads to OOS on rejoin since the rejoiner will
recompute the grid before computing the outstanding paths.
This would also obviously be buggy in a threaded environment, since some
paths might be computed on the fresh and some on the dirty grid.
Finally, MT_TurnStart was sent before the paths were computed, which
might lead to further pathfinder grid changes (not a crashing problem
without threading, but still conceptually odd). The 'fetch' call is thus
moved before it.
This thus fixes d592bf9cb6/D1918, after 92ad6a61fa already fixed a first
issue.
Since the grid is now only updated at the end of a turn, we need to
ensure that it is correct on Turn 0, thus the pathfinder recomputes it
on InitGame.
Refs D14
Reported by: Itms
Fixes #5851
Differential Revision: https://code.wildfiregames.com/D3064
This was SVN commit r24142.
2020-11-08 09:58:19 +01:00
|
|
|
}
|
2024-07-08 21:07:04 +02:00
|
|
|
|
|
|
|
componentManager.GetScriptInterface().GetContext().RunJobs();
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
2012-06-06 21:37:03 +02:00
|
|
|
void CSimulation2Impl::Interpolate(float simFrameLength, float frameOffset, float realFrameLength)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2011-11-04 02:35:50 +01:00
|
|
|
PROFILE3("sim interpolate");
|
|
|
|
|
2010-09-27 01:05:25 +02:00
|
|
|
m_LastFrameOffset = frameOffset;
|
|
|
|
|
2012-06-06 21:37:03 +02:00
|
|
|
CMessageInterpolate msg(simFrameLength, frameOffset, realFrameLength);
|
2010-03-20 17:26:25 +01:00
|
|
|
m_ComponentManager.BroadcastMessage(msg);
|
2010-12-08 17:09:53 +01:00
|
|
|
|
|
|
|
// Clean up any entities destroyed during interpolate (e.g. local corpses)
|
|
|
|
m_ComponentManager.FlushDestroyedComponents();
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
2010-05-20 02:59:01 +02:00
|
|
|
void CSimulation2Impl::DumpState()
|
|
|
|
{
|
|
|
|
PROFILE("DumpState");
|
|
|
|
|
2011-10-27 16:17:39 +02:00
|
|
|
std::stringstream name;\
|
|
|
|
name << std::setw(5) << std::setfill('0') << m_TurnNumber << ".txt";
|
2016-02-15 16:57:23 +01:00
|
|
|
const OsPath path = m_OOSLogPath / name.str();
|
2011-03-23 14:36:20 +01:00
|
|
|
std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
|
2010-05-20 02:59:01 +02:00
|
|
|
|
2016-05-16 02:56:07 +02:00
|
|
|
if (!DirectoryExists(m_OOSLogPath))
|
|
|
|
{
|
|
|
|
LOGWARNING("OOS-log directory %s was deleted, creating it again.", m_OOSLogPath.string8().c_str());
|
|
|
|
CreateDirectories(m_OOSLogPath, 0700);
|
|
|
|
}
|
|
|
|
|
2010-05-20 02:59:01 +02:00
|
|
|
file << "State hash: " << std::hex;
|
|
|
|
std::string hashRaw;
|
2011-03-05 23:30:32 +01:00
|
|
|
m_ComponentManager.ComputeStateHash(hashRaw, false);
|
2010-05-20 02:59:01 +02:00
|
|
|
for (size_t i = 0; i < hashRaw.size(); ++i)
|
|
|
|
file << std::setfill('0') << std::setw(2) << (int)(unsigned char)hashRaw[i];
|
|
|
|
file << std::dec << "\n";
|
|
|
|
|
|
|
|
file << "\n";
|
|
|
|
|
2011-10-24 16:31:05 +02:00
|
|
|
m_ComponentManager.DumpDebugState(file, true);
|
2010-05-23 01:02:07 +02:00
|
|
|
|
2011-03-23 14:36:20 +01:00
|
|
|
std::ofstream binfile (OsString(path.ChangeExtension(L".dat")).c_str(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary);
|
2010-05-23 01:02:07 +02:00
|
|
|
m_ComponentManager.SerializeState(binfile);
|
2010-05-20 02:59:01 +02:00
|
|
|
}
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
|
2024-03-08 18:15:25 +01:00
|
|
|
CSimulation2::CSimulation2(CUnitManager* unitManager, ScriptContext& cx, CTerrain* terrain) :
|
2020-11-18 15:39:04 +01:00
|
|
|
m(new CSimulation2Impl(unitManager, cx, terrain))
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
CSimulation2::~CSimulation2()
|
|
|
|
{
|
|
|
|
delete m;
|
|
|
|
}
|
|
|
|
|
2010-01-22 21:03:14 +01:00
|
|
|
// Forward all method calls to the appropriate CSimulation2Impl/CComponentManager methods:
|
|
|
|
|
2016-11-15 14:26:58 +01:00
|
|
|
void CSimulation2::EnableSerializationTest()
|
|
|
|
{
|
|
|
|
m->m_EnableSerializationTest = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CSimulation2::EnableRejoinTest(int rejoinTestTurn)
|
|
|
|
{
|
|
|
|
m->m_RejoinTestTurn = rejoinTestTurn;
|
|
|
|
}
|
|
|
|
|
2010-05-20 02:59:01 +02:00
|
|
|
void CSimulation2::EnableOOSLog()
|
|
|
|
{
|
2016-03-15 05:37:41 +01:00
|
|
|
if (m->m_EnableOOSLog)
|
|
|
|
return;
|
|
|
|
|
2010-05-20 02:59:01 +02:00
|
|
|
m->m_EnableOOSLog = true;
|
2016-05-16 02:56:07 +02:00
|
|
|
m->m_OOSLogPath = createDateIndexSubdirectory(psLogDir() / "oos_logs");
|
|
|
|
|
2016-02-15 16:57:23 +01:00
|
|
|
debug_printf("Writing ooslogs to %s\n", m->m_OOSLogPath.string8().c_str());
|
2010-05-20 02:59:01 +02:00
|
|
|
}
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
entity_id_t CSimulation2::AddEntity(const std::wstring& templateName)
|
|
|
|
{
|
2010-01-22 21:03:14 +01:00
|
|
|
return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity());
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
entity_id_t CSimulation2::AddEntity(const std::wstring& templateName, entity_id_t preferredId)
|
|
|
|
{
|
2010-01-22 21:03:14 +01:00
|
|
|
return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity(preferredId));
|
2010-01-14 21:36:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
entity_id_t CSimulation2::AddLocalEntity(const std::wstring& templateName)
|
|
|
|
{
|
2010-01-22 21:03:14 +01:00
|
|
|
return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewLocalEntity());
|
2010-01-14 21:36:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CSimulation2::DestroyEntity(entity_id_t ent)
|
|
|
|
{
|
|
|
|
m->m_ComponentManager.DestroyComponentsSoon(ent);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CSimulation2::FlushDestroyedEntities()
|
|
|
|
{
|
|
|
|
m->m_ComponentManager.FlushDestroyedComponents();
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
IComponent* CSimulation2::QueryInterface(entity_id_t ent, int iid) const
|
|
|
|
{
|
|
|
|
return m->m_ComponentManager.QueryInterface(ent, iid);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CSimulation2::PostMessage(entity_id_t ent, const CMessage& msg) const
|
|
|
|
{
|
|
|
|
m->m_ComponentManager.PostMessage(ent, msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CSimulation2::BroadcastMessage(const CMessage& msg) const
|
|
|
|
{
|
|
|
|
m->m_ComponentManager.BroadcastMessage(msg);
|
|
|
|
}
|
|
|
|
|
2011-03-03 01:16:14 +01:00
|
|
|
CSimulation2::InterfaceList CSimulation2::GetEntitiesWithInterface(int iid)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
|
|
|
return m->m_ComponentManager.GetEntitiesWithInterface(iid);
|
|
|
|
}
|
|
|
|
|
2011-03-03 01:16:14 +01:00
|
|
|
const CSimulation2::InterfaceListUnordered& CSimulation2::GetEntitiesWithInterfaceUnordered(int iid)
|
|
|
|
{
|
|
|
|
return m->m_ComponentManager.GetEntitiesWithInterfaceUnordered(iid);
|
|
|
|
}
|
|
|
|
|
2016-05-19 21:42:07 +02:00
|
|
|
const CSimContext& CSimulation2::GetSimContext() const
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
|
|
|
return m->m_SimContext;
|
|
|
|
}
|
|
|
|
|
|
|
|
ScriptInterface& CSimulation2::GetScriptInterface() const
|
|
|
|
{
|
|
|
|
return m->m_ComponentManager.GetScriptInterface();
|
|
|
|
}
|
|
|
|
|
2014-11-25 23:47:02 +01:00
|
|
|
void CSimulation2::PreInitGame()
|
2013-12-04 17:52:44 +01:00
|
|
|
{
|
Improve JS Exception handling.
- Check for pending exceptions after function calls and script
executions.
- Call LOGERROR instead of JS_ReportError when there is a conversion
error in FromJSVal, since that can only be called from C++ (where JS
errors don't really make sense). Instead, C++ callers of FromJSVal
should handle the failure and, themselves, either report an error or
simply do something else.
- Wrap JS_ReportError since that makes updating it later easier.
This isn't a systematical fix since ToJSVal also ought return a boolean
for failures, and we probably should trigger errors instead of warnings
on 'implicit' conversions, rather a preparation diff.
Part of the SM52 migration, stage: SM45 compatible (actually SM52
incompatible, too).
Based on a patch by: Itms
Comments by: Vladislavbelov, Stan`
Refs #742, #4893
Differential Revision: https://code.wildfiregames.com/D3093
This was SVN commit r24187.
2020-11-15 19:29:17 +01:00
|
|
|
ScriptRequest rq(GetScriptInterface());
|
2020-11-14 09:46:32 +01:00
|
|
|
JS::RootedValue global(rq.cx, rq.globalValue());
|
2021-05-01 16:04:53 +02:00
|
|
|
ScriptFunction::CallVoid(rq, global, "PreInitGame");
|
2013-12-04 17:52:44 +01:00
|
|
|
}
|
|
|
|
|
2018-02-09 20:50:01 +01:00
|
|
|
void CSimulation2::InitGame()
|
2010-01-22 21:03:14 +01:00
|
|
|
{
|
Improve JS Exception handling.
- Check for pending exceptions after function calls and script
executions.
- Call LOGERROR instead of JS_ReportError when there is a conversion
error in FromJSVal, since that can only be called from C++ (where JS
errors don't really make sense). Instead, C++ callers of FromJSVal
should handle the failure and, themselves, either report an error or
simply do something else.
- Wrap JS_ReportError since that makes updating it later easier.
This isn't a systematical fix since ToJSVal also ought return a boolean
for failures, and we probably should trigger errors instead of warnings
on 'implicit' conversions, rather a preparation diff.
Part of the SM52 migration, stage: SM45 compatible (actually SM52
incompatible, too).
Based on a patch by: Itms
Comments by: Vladislavbelov, Stan`
Refs #742, #4893
Differential Revision: https://code.wildfiregames.com/D3093
This was SVN commit r24187.
2020-11-15 19:29:17 +01:00
|
|
|
ScriptRequest rq(GetScriptInterface());
|
2020-11-14 09:46:32 +01:00
|
|
|
JS::RootedValue global(rq.cx, rq.globalValue());
|
2018-02-09 20:50:01 +01:00
|
|
|
|
2020-11-13 14:18:22 +01:00
|
|
|
JS::RootedValue settings(rq.cx);
|
|
|
|
JS::RootedValue tmpInitAttributes(rq.cx, GetInitAttributes());
|
2021-05-13 19:23:52 +02:00
|
|
|
Script::GetProperty(rq, tmpInitAttributes, "settings", &settings);
|
2018-02-09 20:50:01 +01:00
|
|
|
|
2021-05-01 16:04:53 +02:00
|
|
|
ScriptFunction::CallVoid(rq, global, "InitGame", settings);
|
2010-01-22 21:03:14 +01:00
|
|
|
}
|
|
|
|
|
2011-10-24 16:31:05 +02:00
|
|
|
void CSimulation2::Update(int turnLength)
|
2010-05-20 02:59:01 +02:00
|
|
|
{
|
|
|
|
std::vector<SimulationCommand> commands;
|
2011-10-24 16:31:05 +02:00
|
|
|
m->Update(turnLength, commands);
|
2010-05-20 02:59:01 +02:00
|
|
|
}
|
|
|
|
|
2011-10-24 16:31:05 +02:00
|
|
|
void CSimulation2::Update(int turnLength, const std::vector<SimulationCommand>& commands)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2011-10-24 16:31:05 +02:00
|
|
|
m->Update(turnLength, commands);
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
2012-06-06 21:37:03 +02:00
|
|
|
void CSimulation2::Interpolate(float simFrameLength, float frameOffset, float realFrameLength)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2012-06-06 21:37:03 +02:00
|
|
|
m->Interpolate(simFrameLength, frameOffset, realFrameLength);
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
2010-03-20 17:26:25 +01:00
|
|
|
void CSimulation2::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
|
|
|
|
{
|
2011-11-04 02:35:50 +01:00
|
|
|
PROFILE3("sim submit");
|
|
|
|
|
2010-03-20 17:26:25 +01:00
|
|
|
CMessageRenderSubmit msg(collector, frustum, culling);
|
|
|
|
m->m_ComponentManager.BroadcastMessage(msg);
|
|
|
|
}
|
|
|
|
|
2010-09-27 01:05:25 +02:00
|
|
|
float CSimulation2::GetLastFrameOffset() const
|
|
|
|
{
|
|
|
|
return m->m_LastFrameOffset;
|
|
|
|
}
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
bool CSimulation2::LoadScripts(const VfsPath& path)
|
|
|
|
{
|
2011-10-24 16:31:05 +02:00
|
|
|
return m->LoadScripts(m->m_ComponentManager, &m->m_LoadedScripts, path);
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
2010-04-03 13:07:42 +02:00
|
|
|
bool CSimulation2::LoadDefaultScripts()
|
|
|
|
{
|
2011-10-24 16:31:05 +02:00
|
|
|
return m->LoadDefaultScripts(m->m_ComponentManager, &m->m_LoadedScripts);
|
2010-04-03 13:07:42 +02:00
|
|
|
}
|
|
|
|
|
2012-05-04 23:48:46 +02:00
|
|
|
void CSimulation2::SetStartupScript(const std::string& code)
|
2010-04-30 01:22:18 +02:00
|
|
|
{
|
|
|
|
m->m_StartupScript = code;
|
|
|
|
}
|
|
|
|
|
2012-05-04 23:48:46 +02:00
|
|
|
const std::string& CSimulation2::GetStartupScript()
|
2010-04-30 01:22:18 +02:00
|
|
|
{
|
|
|
|
return m->m_StartupScript;
|
|
|
|
}
|
|
|
|
|
2015-01-24 15:46:52 +01:00
|
|
|
void CSimulation2::SetInitAttributes(JS::HandleValue attribs)
|
2011-10-24 16:31:05 +02:00
|
|
|
{
|
|
|
|
m->m_InitAttributes = attribs;
|
|
|
|
}
|
|
|
|
|
2015-01-24 15:46:52 +01:00
|
|
|
JS::Value CSimulation2::GetInitAttributes()
|
2011-10-24 16:31:05 +02:00
|
|
|
{
|
2015-01-24 15:46:52 +01:00
|
|
|
return m->m_InitAttributes.get();
|
2011-10-24 16:31:05 +02:00
|
|
|
}
|
|
|
|
|
2016-07-12 03:49:48 +02:00
|
|
|
void CSimulation2::GetInitAttributes(JS::MutableHandleValue ret)
|
|
|
|
{
|
|
|
|
ret.set(m->m_InitAttributes);
|
|
|
|
}
|
|
|
|
|
2011-02-17 21:08:20 +01:00
|
|
|
void CSimulation2::SetMapSettings(const std::string& settings)
|
2010-08-04 23:15:41 +02:00
|
|
|
{
|
2021-05-14 12:18:03 +02:00
|
|
|
Script::ParseJSON(ScriptRequest(m->m_ComponentManager.GetScriptInterface()), settings, &m->m_MapSettings);
|
2010-08-04 23:15:41 +02:00
|
|
|
}
|
|
|
|
|
2015-01-24 15:46:52 +01:00
|
|
|
void CSimulation2::SetMapSettings(JS::HandleValue settings)
|
2010-10-30 06:02:42 +02:00
|
|
|
{
|
|
|
|
m->m_MapSettings = settings;
|
2016-08-14 18:28:54 +02:00
|
|
|
|
2017-12-27 17:51:25 +01:00
|
|
|
m->InitRNGSeedSimulation();
|
|
|
|
m->InitRNGSeedAI();
|
2010-10-30 06:02:42 +02:00
|
|
|
}
|
|
|
|
|
2011-02-19 04:14:37 +01:00
|
|
|
std::string CSimulation2::GetMapSettingsString()
|
2010-08-04 23:15:41 +02:00
|
|
|
{
|
2021-05-14 12:18:03 +02:00
|
|
|
return Script::StringifyJSON(ScriptRequest(m->m_ComponentManager.GetScriptInterface()), &m->m_MapSettings);
|
2010-08-04 23:15:41 +02:00
|
|
|
}
|
|
|
|
|
2015-01-24 15:46:52 +01:00
|
|
|
void CSimulation2::GetMapSettings(JS::MutableHandleValue ret)
|
2011-02-19 04:14:37 +01:00
|
|
|
{
|
2015-01-24 15:46:52 +01:00
|
|
|
ret.set(m->m_MapSettings);
|
2011-02-19 04:14:37 +01:00
|
|
|
}
|
|
|
|
|
2011-08-22 23:45:39 +02:00
|
|
|
void CSimulation2::LoadPlayerSettings(bool newPlayers)
|
2010-10-30 06:02:42 +02:00
|
|
|
{
|
Improve JS Exception handling.
- Check for pending exceptions after function calls and script
executions.
- Call LOGERROR instead of JS_ReportError when there is a conversion
error in FromJSVal, since that can only be called from C++ (where JS
errors don't really make sense). Instead, C++ callers of FromJSVal
should handle the failure and, themselves, either report an error or
simply do something else.
- Wrap JS_ReportError since that makes updating it later easier.
This isn't a systematical fix since ToJSVal also ought return a boolean
for failures, and we probably should trigger errors instead of warnings
on 'implicit' conversions, rather a preparation diff.
Part of the SM52 migration, stage: SM45 compatible (actually SM52
incompatible, too).
Based on a patch by: Itms
Comments by: Vladislavbelov, Stan`
Refs #742, #4893
Differential Revision: https://code.wildfiregames.com/D3093
This was SVN commit r24187.
2020-11-15 19:29:17 +01:00
|
|
|
ScriptRequest rq(GetScriptInterface());
|
2020-11-14 09:46:32 +01:00
|
|
|
JS::RootedValue global(rq.cx, rq.globalValue());
|
2021-05-01 16:04:53 +02:00
|
|
|
ScriptFunction::CallVoid(rq, global, "LoadPlayerSettings", m->m_MapSettings, newPlayers);
|
2010-10-30 06:02:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void CSimulation2::LoadMapSettings()
|
|
|
|
{
|
Improve JS Exception handling.
- Check for pending exceptions after function calls and script
executions.
- Call LOGERROR instead of JS_ReportError when there is a conversion
error in FromJSVal, since that can only be called from C++ (where JS
errors don't really make sense). Instead, C++ callers of FromJSVal
should handle the failure and, themselves, either report an error or
simply do something else.
- Wrap JS_ReportError since that makes updating it later easier.
This isn't a systematical fix since ToJSVal also ought return a boolean
for failures, and we probably should trigger errors instead of warnings
on 'implicit' conversions, rather a preparation diff.
Part of the SM52 migration, stage: SM45 compatible (actually SM52
incompatible, too).
Based on a patch by: Itms
Comments by: Vladislavbelov, Stan`
Refs #742, #4893
Differential Revision: https://code.wildfiregames.com/D3093
This was SVN commit r24187.
2020-11-15 19:29:17 +01:00
|
|
|
ScriptRequest rq(GetScriptInterface());
|
2016-01-23 02:02:57 +01:00
|
|
|
|
2020-11-14 09:46:32 +01:00
|
|
|
JS::RootedValue global(rq.cx, rq.globalValue());
|
2016-01-23 02:02:57 +01:00
|
|
|
|
2010-10-30 06:02:42 +02:00
|
|
|
// Initialize here instead of in Update()
|
2021-05-01 16:04:53 +02:00
|
|
|
ScriptFunction::CallVoid(rq, global, "LoadMapSettings", m->m_MapSettings);
|
2010-10-30 06:02:42 +02:00
|
|
|
|
2021-05-13 19:23:52 +02:00
|
|
|
Script::FreezeObject(rq, m->m_InitAttributes, true);
|
2019-09-25 12:06:12 +02:00
|
|
|
GetScriptInterface().SetGlobal("InitAttributes", m->m_InitAttributes, true, true, true);
|
|
|
|
|
2010-10-30 06:02:42 +02:00
|
|
|
if (!m->m_StartupScript.empty())
|
|
|
|
GetScriptInterface().LoadScript(L"map startup script", m->m_StartupScript);
|
2014-07-23 01:30:27 +02:00
|
|
|
|
|
|
|
// Load the trigger scripts after we have loaded the simulation and the map.
|
2015-04-16 06:30:51 +02:00
|
|
|
m->LoadTriggerScripts(m->m_ComponentManager, m->m_MapSettings, &m->m_LoadedScripts);
|
2010-10-30 06:02:42 +02:00
|
|
|
}
|
|
|
|
|
2011-03-05 02:56:59 +01:00
|
|
|
int CSimulation2::ProgressiveLoad()
|
|
|
|
{
|
|
|
|
return m->ProgressiveLoad();
|
|
|
|
}
|
|
|
|
|
2011-05-03 14:38:42 +02:00
|
|
|
Status CSimulation2::ReloadChangedFile(const VfsPath& path)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
|
|
|
return m->ReloadChangedFile(path);
|
|
|
|
}
|
|
|
|
|
2011-01-12 13:29:00 +01:00
|
|
|
void CSimulation2::ResetState(bool skipScriptedComponents, bool skipAI)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2011-01-12 13:29:00 +01:00
|
|
|
m->ResetState(skipScriptedComponents, skipAI);
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
2011-03-05 23:30:32 +01:00
|
|
|
bool CSimulation2::ComputeStateHash(std::string& outHash, bool quick)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2011-03-05 23:30:32 +01:00
|
|
|
return m->m_ComponentManager.ComputeStateHash(outHash, quick);
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool CSimulation2::DumpDebugState(std::ostream& stream)
|
|
|
|
{
|
2021-03-31 17:55:19 +02:00
|
|
|
stream << "sim turn: " << m->m_TurnNumber << std::endl;
|
2011-10-24 16:31:05 +02:00
|
|
|
return m->m_ComponentManager.DumpDebugState(stream, true);
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool CSimulation2::SerializeState(std::ostream& stream)
|
|
|
|
{
|
|
|
|
return m->m_ComponentManager.SerializeState(stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CSimulation2::DeserializeState(std::istream& stream)
|
|
|
|
{
|
|
|
|
// TODO: need to make sure the required SYSTEM_ENTITY components get constructed
|
|
|
|
return m->m_ComponentManager.DeserializeState(stream);
|
|
|
|
}
|
2010-04-09 21:02:39 +02:00
|
|
|
|
2020-12-17 18:53:13 +01:00
|
|
|
void CSimulation2::ActivateRejoinTest(int turn)
|
|
|
|
{
|
|
|
|
if (m->m_RejoinTestTurn != -1)
|
|
|
|
return;
|
|
|
|
LOGMESSAGERENDER("Rejoin test will activate in %i turns", turn - m->m_TurnNumber);
|
|
|
|
m->m_RejoinTestTurn = turn;
|
|
|
|
}
|
|
|
|
|
2010-04-09 21:02:39 +02:00
|
|
|
std::string CSimulation2::GenerateSchema()
|
|
|
|
{
|
|
|
|
return m->m_ComponentManager.GenerateSchema();
|
|
|
|
}
|
2011-04-14 06:01:59 +02:00
|
|
|
|
2014-12-30 19:21:02 +01:00
|
|
|
static std::vector<std::string> GetJSONData(const VfsPath& path)
|
2011-04-14 06:01:59 +02:00
|
|
|
{
|
|
|
|
VfsPaths pathnames;
|
2011-05-25 12:39:13 +02:00
|
|
|
Status ret = vfs::GetPathnames(g_VFS, path, L"*.json", pathnames);
|
2014-12-30 19:21:02 +01:00
|
|
|
if (ret != INFO::OK)
|
2011-04-14 06:01:59 +02:00
|
|
|
{
|
|
|
|
// Some error reading directory
|
|
|
|
wchar_t error[200];
|
2015-01-22 21:37:38 +01:00
|
|
|
LOGERROR("Error reading directory '%s': %s", path.string8(), utf8_from_wstring(StatusDescription(ret, error, ARRAY_SIZE(error))));
|
2014-12-30 19:21:02 +01:00
|
|
|
return std::vector<std::string>();
|
2011-04-14 06:01:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::string> data;
|
2015-06-21 20:46:33 +02:00
|
|
|
for (const VfsPath& p : pathnames)
|
2011-04-14 06:01:59 +02:00
|
|
|
{
|
2014-12-30 19:21:02 +01:00
|
|
|
// Load JSON file
|
|
|
|
CVFSFile file;
|
2020-11-26 23:28:50 +01:00
|
|
|
PSRETURN loadStatus = file.Load(g_VFS, p);
|
|
|
|
if (loadStatus != PSRETURN_OK)
|
2011-04-14 06:01:59 +02:00
|
|
|
{
|
2020-11-26 23:28:50 +01:00
|
|
|
LOGERROR("GetJSONData: Failed to load file '%s': %s", p.string8(), GetErrorString(loadStatus));
|
2014-12-30 19:21:02 +01:00
|
|
|
continue;
|
2011-04-14 06:01:59 +02:00
|
|
|
}
|
2014-12-30 19:21:02 +01:00
|
|
|
|
|
|
|
data.push_back(file.DecodeUTF8()); // assume it's UTF-8
|
2011-04-14 06:01:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2014-12-30 19:21:02 +01:00
|
|
|
std::vector<std::string> CSimulation2::GetRMSData()
|
2011-04-14 06:01:59 +02:00
|
|
|
{
|
2014-12-30 19:21:02 +01:00
|
|
|
return GetJSONData(L"maps/random/");
|
2011-06-10 01:47:20 +02:00
|
|
|
}
|
2011-04-14 06:01:59 +02:00
|
|
|
|
2020-07-18 19:39:59 +02:00
|
|
|
std::vector<std::string> CSimulation2::GetVictoryConditiondData()
|
|
|
|
{
|
|
|
|
return GetJSONData(L"simulation/data/settings/victory_conditions/");
|
|
|
|
}
|
|
|
|
|
2014-12-30 19:21:02 +01:00
|
|
|
static std::string ReadJSON(const VfsPath& path)
|
2011-06-10 01:47:20 +02:00
|
|
|
{
|
2011-05-25 12:39:13 +02:00
|
|
|
if (!VfsFileExists(path))
|
2011-04-14 06:01:59 +02:00
|
|
|
{
|
2015-01-22 21:36:24 +01:00
|
|
|
LOGERROR("File '%s' does not exist", path.string8());
|
2014-12-30 19:21:02 +01:00
|
|
|
return std::string();
|
2011-04-14 06:01:59 +02:00
|
|
|
}
|
2014-12-30 19:21:02 +01:00
|
|
|
|
|
|
|
// Load JSON file
|
|
|
|
CVFSFile file;
|
|
|
|
PSRETURN ret = file.Load(g_VFS, path);
|
|
|
|
if (ret != PSRETURN_OK)
|
2011-04-14 06:01:59 +02:00
|
|
|
{
|
2015-01-22 21:36:24 +01:00
|
|
|
LOGERROR("Failed to load file '%s': %s", path.string8(), GetErrorString(ret));
|
2014-12-30 19:21:02 +01:00
|
|
|
return std::string();
|
2011-04-14 06:01:59 +02:00
|
|
|
}
|
|
|
|
|
2014-12-30 19:21:02 +01:00
|
|
|
return file.DecodeUTF8(); // assume it's UTF-8
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string CSimulation2::GetPlayerDefaults()
|
|
|
|
{
|
2015-11-30 02:33:59 +01:00
|
|
|
return ReadJSON(L"simulation/data/settings/player_defaults.json");
|
2014-12-30 19:21:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string CSimulation2::GetMapSizes()
|
|
|
|
{
|
2015-12-04 17:37:30 +01:00
|
|
|
return ReadJSON(L"simulation/data/settings/map_sizes.json");
|
2011-04-14 06:01:59 +02:00
|
|
|
}
|
2011-06-14 01:32:41 +02:00
|
|
|
|
|
|
|
std::string CSimulation2::GetAIData()
|
|
|
|
{
|
2017-11-25 07:49:58 +01:00
|
|
|
const ScriptInterface& scriptInterface = GetScriptInterface();
|
Improve JS Exception handling.
- Check for pending exceptions after function calls and script
executions.
- Call LOGERROR instead of JS_ReportError when there is a conversion
error in FromJSVal, since that can only be called from C++ (where JS
errors don't really make sense). Instead, C++ callers of FromJSVal
should handle the failure and, themselves, either report an error or
simply do something else.
- Wrap JS_ReportError since that makes updating it later easier.
This isn't a systematical fix since ToJSVal also ought return a boolean
for failures, and we probably should trigger errors instead of warnings
on 'implicit' conversions, rather a preparation diff.
Part of the SM52 migration, stage: SM45 compatible (actually SM52
incompatible, too).
Based on a patch by: Itms
Comments by: Vladislavbelov, Stan`
Refs #742, #4893
Differential Revision: https://code.wildfiregames.com/D3093
This was SVN commit r24187.
2020-11-15 19:29:17 +01:00
|
|
|
ScriptRequest rq(scriptInterface);
|
2020-11-13 14:18:22 +01:00
|
|
|
JS::RootedValue aiData(rq.cx, ICmpAIManager::GetAIs(scriptInterface));
|
2016-01-23 02:02:57 +01:00
|
|
|
|
2011-06-14 01:32:41 +02:00
|
|
|
// Build single JSON string with array of AI data
|
2020-11-13 14:18:22 +01:00
|
|
|
JS::RootedValue ais(rq.cx);
|
2019-07-22 21:35:14 +02:00
|
|
|
|
2021-05-13 19:23:52 +02:00
|
|
|
if (!Script::CreateObject(rq, &ais, "AIData", aiData))
|
2011-06-14 01:32:41 +02:00
|
|
|
return std::string();
|
2016-01-23 02:02:57 +01:00
|
|
|
|
2021-05-14 12:18:03 +02:00
|
|
|
return Script::StringifyJSON(rq, &ais);
|
2011-06-14 01:32:41 +02:00
|
|
|
}
|