Changeset View
Changeset View
Standalone View
Standalone View
source/simulation2/components/CCmpAIManager.cpp
/* Copyright (C) 2020 Wildfire Games. | /* Copyright (C) 2021 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 | ||||
* it under the terms of the GNU General Public License as published by | * it under the terms of the GNU General Public License as published by | ||||
* the Free Software Foundation, either version 2 of the License, or | * the Free Software Foundation, either version 2 of the License, or | ||||
* (at your option) any later version. | * (at your option) any later version. | ||||
* | * | ||||
* 0 A.D. is distributed in the hope that it will be useful, | * 0 A.D. is distributed in the hope that it will be useful, | ||||
Show All 40 Lines | |||||
extern void QuitEngine(); | extern void QuitEngine(); | ||||
/** | /** | ||||
* @file | * @file | ||||
* Player AI interface. | * Player AI interface. | ||||
* AI is primarily scripted, and the CCmpAIManager component defined here | * AI is primarily scripted, and the CCmpAIManager component defined here | ||||
* takes care of managing all the scripts. | * takes care of managing all the scripts. | ||||
* | * | ||||
* To avoid slow AI scripts causing jerky rendering, they are run in a background | * The original idea was to run CAIWorker in a separate thread to prevent | ||||
* thread (maintained by CAIWorker) so that it's okay if they take a whole simulation | * slow AIs from impacting framerate. However, copying the game-state every turn | ||||
* turn before returning their results (though preferably they shouldn't use nearly | * proved difficult and rather slow itself (and isn't threadable, obviously). | ||||
* that much CPU). | * For these reasons, the design was changed to a single-thread, same-compartment, different-realm design. | ||||
* The AI can therefore directly use the simulation data via the 'Sim' & 'SimEngine' globals. | |||||
* As a result, a lof of the code is still designed to be "thread-ready", but this no longer matters. | |||||
* | * | ||||
* CCmpAIManager grabs the world state after each turn (making use of AIInterface.js | * TODO: despite the above, it would still be useful to allow the AI to run tasks asynchronously (and off-thread). | ||||
* and AIProxy.js to decide what data to include) then passes it to CAIWorker. | * This could be implemented by having a separate JS runtime in a different thread, | ||||
* The AI scripts will then run asynchronously and return a list of commands to execute. | * that runs tasks and returns after a distinct # of simulation turns (to maintain determinism). | ||||
* Any attempts to read the command list (including indirectly via serialization) | |||||
* will block until it's actually completed, so the rest of the engine should avoid | |||||
* reading it for as long as possible. | |||||
* | * | ||||
* JS::Values are passed between the game and AI threads using ScriptInterface::StructuredClone. | * Note also that the RL Interface, by default, uses the 'AI representation'. | ||||
* | * This representation, alimented by the JS AIInterface/AIProxy tandem, is likely to grow smaller over time | ||||
* TODO: actually the thread isn't implemented yet, because performance hasn't been | * as the AI uses more sim data directly. | ||||
* sufficiently problematic to justify the complexity yet, but the CAIWorker interface | |||||
* is designed to hopefully support threading when we want it. | |||||
*/ | */ | ||||
/** | /** | ||||
* Implements worker thread for CCmpAIManager. | * AI computation orchestator for CCmpAIManager. | ||||
*/ | */ | ||||
class CAIWorker | class CAIWorker | ||||
{ | { | ||||
private: | private: | ||||
class CAIPlayer | class CAIPlayer | ||||
{ | { | ||||
NONCOPYABLE(CAIPlayer); | NONCOPYABLE(CAIPlayer); | ||||
public: | public: | ||||
▲ Show 20 Lines • Show All 109 Lines • ▼ Show 20 Lines | public: | ||||
std::wstring m_Behavior; | std::wstring m_Behavior; | ||||
bool m_UseSharedComponent; | bool m_UseSharedComponent; | ||||
// Take care to keep this declaration before heap rooted members. Destructors of heap rooted | // Take care to keep this declaration before heap rooted members. Destructors of heap rooted | ||||
// members have to be called before the context destructor. | // members have to be called before the context destructor. | ||||
shared_ptr<ScriptInterface> m_ScriptInterface; | shared_ptr<ScriptInterface> m_ScriptInterface; | ||||
JS::PersistentRootedValue m_Obj; | JS::PersistentRootedValue m_Obj; | ||||
std::vector<ScriptInterface::StructuredClone > m_Commands; | std::vector<ScriptInterface::StructuredClone> m_Commands; | ||||
}; | }; | ||||
public: | public: | ||||
struct SCommandSets | struct SCommandSets | ||||
{ | { | ||||
player_id_t player; | player_id_t player; | ||||
std::vector<ScriptInterface::StructuredClone > commands; | std::vector<ScriptInterface::StructuredClone> commands; | ||||
}; | }; | ||||
CAIWorker() : | CAIWorker() : | ||||
m_ScriptInterface(new ScriptInterface("Engine", "AI", g_ScriptContext)), | |||||
m_TurnNum(0), | m_TurnNum(0), | ||||
m_CommandsComputed(true), | m_CommandsComputed(true), | ||||
m_HasLoadedEntityTemplates(false), | m_HasLoadedEntityTemplates(false), | ||||
m_HasSharedComponent(false), | m_HasSharedComponent(false) | ||||
m_EntityTemplates(g_ScriptContext->GetGeneralJSContext()), | { | ||||
m_SharedAIObj(g_ScriptContext->GetGeneralJSContext()), | } | ||||
m_PassabilityMapVal(g_ScriptContext->GetGeneralJSContext()), | |||||
m_TerritoryMapVal(g_ScriptContext->GetGeneralJSContext()) | ~CAIWorker() | ||||
{ | |||||
JS_RemoveExtraGCRootsTracer(m_ScriptInterface->GetGeneralJSContext(), Trace, this); | |||||
} | |||||
void Init(const ScriptInterface& simInterface) | |||||
{ | { | ||||
// Create the script interface in the same compartment as the simulation interface. | |||||
// This will allow us to directly share data from the sim to the AI (and vice versa, should the need arise). | |||||
m_ScriptInterface = std::make_shared<ScriptInterface>("Engine", "AI", simInterface); | |||||
ScriptRequest rq(m_ScriptInterface); | |||||
m_EntityTemplates.init(rq.cx); | |||||
m_SharedAIObj.init(rq.cx); | |||||
m_PassabilityMapVal.init(rq.cx); | |||||
m_TerritoryMapVal.init(rq.cx); | |||||
m_ScriptInterface->ReplaceNondeterministicRNG(m_RNG); | m_ScriptInterface->ReplaceNondeterministicRNG(m_RNG); | ||||
m_ScriptInterface->SetCallbackData(static_cast<void*> (this)); | m_ScriptInterface->SetCallbackData(static_cast<void*> (this)); | ||||
JS_AddExtraGCRootsTracer(m_ScriptInterface->GetGeneralJSContext(), Trace, this); | JS_AddExtraGCRootsTracer(m_ScriptInterface->GetGeneralJSContext(), Trace, this); | ||||
ScriptRequest rq(m_ScriptInterface); | { | ||||
ScriptRequest simrq(simInterface); | |||||
// Register the sim globals for easy & explicit access. Mark it replaceable for hotloading. | |||||
JS::RootedValue global(rq.cx, simrq.globalValue()); | |||||
m_ScriptInterface->SetGlobal("Sim", global, true); | |||||
JS::RootedValue scope(rq.cx, JS::ObjectValue(*simrq.nativeScope.get())); | |||||
m_ScriptInterface->SetGlobal("SimEngine", scope, true); | |||||
} | |||||
#define REGISTER_FUNC_NAME(func, name) \ | #define REGISTER_FUNC_NAME(func, name) \ | ||||
ScriptFunction::Register<&CAIWorker::func, ScriptFunction::ObjectFromCBData<CAIWorker>>(rq, name); | ScriptFunction::Register<&CAIWorker::func, ScriptFunction::ObjectFromCBData<CAIWorker>>(rq, name); | ||||
REGISTER_FUNC_NAME(PostCommand, "PostCommand"); | REGISTER_FUNC_NAME(PostCommand, "PostCommand"); | ||||
REGISTER_FUNC_NAME(LoadScripts, "IncludeModule"); | REGISTER_FUNC_NAME(LoadScripts, "IncludeModule"); | ||||
ScriptFunction::Register<QuitEngine>(rq, "Exit"); | ScriptFunction::Register<QuitEngine>(rq, "Exit"); | ||||
REGISTER_FUNC_NAME(ComputePathScript, "ComputePath"); | REGISTER_FUNC_NAME(ComputePathScript, "ComputePath"); | ||||
REGISTER_FUNC_NAME(DumpImage, "DumpImage"); | REGISTER_FUNC_NAME(DumpImage, "DumpImage"); | ||||
REGISTER_FUNC_NAME(GetTemplate, "GetTemplate"); | REGISTER_FUNC_NAME(GetTemplate, "GetTemplate"); | ||||
#undef REGISTER_FUNC_NAME | #undef REGISTER_FUNC_NAME | ||||
JSI_VFS::RegisterScriptFunctions_Simulation(rq); | JSI_VFS::RegisterScriptFunctions_Simulation(rq); | ||||
// Globalscripts may use VFS script functions | // Globalscripts may use VFS script functions | ||||
m_ScriptInterface->LoadGlobalScripts(); | m_ScriptInterface->LoadGlobalScripts(); | ||||
} | |||||
~CAIWorker() | |||||
{ | |||||
JS_RemoveExtraGCRootsTracer(m_ScriptInterface->GetGeneralJSContext(), Trace, this); | |||||
} | } | ||||
bool HasLoadedEntityTemplates() const { return m_HasLoadedEntityTemplates; } | bool HasLoadedEntityTemplates() const { return m_HasLoadedEntityTemplates; } | ||||
bool LoadScripts(const std::wstring& moduleName) | bool LoadScripts(const std::wstring& moduleName) | ||||
{ | { | ||||
// Ignore modules that are already loaded | // Ignore modules that are already loaded | ||||
if (m_LoadedModules.find(moduleName) != m_LoadedModules.end()) | if (m_LoadedModules.find(moduleName) != m_LoadedModules.end()) | ||||
▲ Show 20 Lines • Show All 539 Lines • ▼ Show 20 Lines | for (size_t i = 0; i < m_Players.size(); ++i) | ||||
if (m_HasSharedComponent && m_Players[i]->m_UseSharedComponent) | if (m_HasSharedComponent && m_Players[i]->m_UseSharedComponent) | ||||
m_Players[i]->Run(state, m_Players[i]->m_Player, m_SharedAIObj); | m_Players[i]->Run(state, m_Players[i]->m_Player, m_SharedAIObj); | ||||
else | else | ||||
m_Players[i]->Run(state, m_Players[i]->m_Player); | m_Players[i]->Run(state, m_Players[i]->m_Player); | ||||
} | } | ||||
} | } | ||||
// Take care to keep this declaration before heap rooted members. Destructors of heap rooted | |||||
// members have to be called before the context destructor. | |||||
shared_ptr<ScriptContext> m_ScriptContext; | |||||
shared_ptr<ScriptInterface> m_ScriptInterface; | shared_ptr<ScriptInterface> m_ScriptInterface; | ||||
boost::rand48 m_RNG; | boost::rand48 m_RNG; | ||||
u32 m_TurnNum; | u32 m_TurnNum; | ||||
JS::PersistentRootedValue m_EntityTemplates; | JS::PersistentRootedValue m_EntityTemplates; | ||||
bool m_HasLoadedEntityTemplates; | bool m_HasLoadedEntityTemplates; | ||||
std::map<VfsPath, JS::Heap<JS::Value> > m_PlayerMetadata; | std::map<VfsPath, JS::Heap<JS::Value> > m_PlayerMetadata; | ||||
Show All 36 Lines | public: | ||||
static std::string GetSchema() | static std::string GetSchema() | ||||
{ | { | ||||
return "<a:component type='system'/><empty/>"; | return "<a:component type='system'/><empty/>"; | ||||
} | } | ||||
virtual void Init(const CParamNode& UNUSED(paramNode)) | virtual void Init(const CParamNode& UNUSED(paramNode)) | ||||
{ | { | ||||
m_Worker.Init(GetSimContext().GetScriptInterface()); | |||||
m_TerritoriesDirtyID = 0; | m_TerritoriesDirtyID = 0; | ||||
m_TerritoriesDirtyBlinkingID = 0; | m_TerritoriesDirtyBlinkingID = 0; | ||||
m_JustDeserialized = false; | m_JustDeserialized = false; | ||||
} | } | ||||
virtual void Deinit() | virtual void Deinit() | ||||
{ | { | ||||
} | } | ||||
▲ Show 20 Lines • Show All 225 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator