Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/source/simulation2/components/CCmpAIManager.cpp
Show All 28 Lines | |||||
#include "ps/CLogger.h" | #include "ps/CLogger.h" | ||||
#include "ps/Filesystem.h" | #include "ps/Filesystem.h" | ||||
#include "ps/Profile.h" | #include "ps/Profile.h" | ||||
#include "ps/scripting/JSInterface_VFS.h" | #include "ps/scripting/JSInterface_VFS.h" | ||||
#include "ps/TemplateLoader.h" | #include "ps/TemplateLoader.h" | ||||
#include "ps/Util.h" | #include "ps/Util.h" | ||||
#include "scriptinterface/FunctionWrapper.h" | #include "scriptinterface/FunctionWrapper.h" | ||||
#include "scriptinterface/ScriptContext.h" | #include "scriptinterface/ScriptContext.h" | ||||
#include "scriptinterface/StructuredClone.h" | |||||
#include "simulation2/components/ICmpAIInterface.h" | #include "simulation2/components/ICmpAIInterface.h" | ||||
#include "simulation2/components/ICmpCommandQueue.h" | #include "simulation2/components/ICmpCommandQueue.h" | ||||
#include "simulation2/components/ICmpObstructionManager.h" | #include "simulation2/components/ICmpObstructionManager.h" | ||||
#include "simulation2/components/ICmpRangeManager.h" | #include "simulation2/components/ICmpRangeManager.h" | ||||
#include "simulation2/components/ICmpTemplateManager.h" | #include "simulation2/components/ICmpTemplateManager.h" | ||||
#include "simulation2/components/ICmpTerritoryManager.h" | #include "simulation2/components/ICmpTerritoryManager.h" | ||||
#include "simulation2/helpers/HierarchicalPathfinder.h" | #include "simulation2/helpers/HierarchicalPathfinder.h" | ||||
#include "simulation2/helpers/LongPathfinder.h" | #include "simulation2/helpers/LongPathfinder.h" | ||||
Show All 17 Lines | |||||
* | * | ||||
* CCmpAIManager grabs the world state after each turn (making use of AIInterface.js | * CCmpAIManager grabs the world state after each turn (making use of AIInterface.js | ||||
* and AIProxy.js to decide what data to include) then passes it to CAIWorker. | * and AIProxy.js to decide what data to include) then passes it to CAIWorker. | ||||
* The AI scripts will then run asynchronously and return a list of commands to execute. | * The AI scripts will then run asynchronously and return a list of commands to execute. | ||||
* Any attempts to read the command list (including indirectly via serialization) | * 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 | * will block until it's actually completed, so the rest of the engine should avoid | ||||
* reading it for as long as possible. | * reading it for as long as possible. | ||||
* | * | ||||
* JS::Values are passed between the game and AI threads using ScriptInterface::StructuredClone. | * JS::Values are passed between the game and AI threads using Script::StructuredClone. | ||||
* | * | ||||
* TODO: actually the thread isn't implemented yet, because performance hasn't been | * TODO: actually the thread isn't implemented yet, because performance hasn't been | ||||
* sufficiently problematic to justify the complexity yet, but the CAIWorker interface | * sufficiently problematic to justify the complexity yet, but the CAIWorker interface | ||||
* is designed to hopefully support threading when we want it. | * is designed to hopefully support threading when we want it. | ||||
*/ | */ | ||||
/** | /** | ||||
* Implements worker thread for CCmpAIManager. | * Implements worker thread for CCmpAIManager. | ||||
▲ Show 20 Lines • Show All 120 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<Script::StructuredClone > m_Commands; | ||||
}; | }; | ||||
public: | public: | ||||
struct SCommandSets | struct SCommandSets | ||||
{ | { | ||||
player_id_t player; | player_id_t player; | ||||
std::vector<ScriptInterface::StructuredClone > commands; | std::vector<Script::StructuredClone > commands; | ||||
}; | }; | ||||
CAIWorker() : | CAIWorker() : | ||||
m_ScriptInterface(new ScriptInterface("Engine", "AI", g_ScriptContext)), | 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), | ||||
▲ Show 20 Lines • Show All 63 Lines • ▼ Show 20 Lines | for (const VfsPath& path : pathnames) | ||||
} | } | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
void PostCommand(int playerid, JS::HandleValue cmd) | void PostCommand(int playerid, JS::HandleValue cmd) | ||||
{ | { | ||||
ScriptRequest rq(m_ScriptInterface); | |||||
for (size_t i=0; i<m_Players.size(); i++) | for (size_t i=0; i<m_Players.size(); i++) | ||||
{ | { | ||||
if (m_Players[i]->m_Player == playerid) | if (m_Players[i]->m_Player == playerid) | ||||
{ | { | ||||
m_Players[i]->m_Commands.push_back(m_ScriptInterface->WriteStructuredClone(cmd)); | m_Players[i]->m_Commands.push_back(Script::WriteStructuredClone(rq, cmd)); | ||||
return; | return; | ||||
} | } | ||||
} | } | ||||
LOGERROR("Invalid playerid in PostCommand!"); | LOGERROR("Invalid playerid in PostCommand!"); | ||||
} | } | ||||
JS::Value ComputePathScript(JS::HandleValue position, JS::HandleValue goal, pass_class_t passClass) | JS::Value ComputePathScript(JS::HandleValue position, JS::HandleValue goal, pass_class_t passClass) | ||||
▲ Show 20 Lines • Show All 153 Lines • ▼ Show 20 Lines | bool AddPlayer(const std::wstring& aiName, player_id_t player, u8 difficulty, const std::wstring& behavior) | ||||
if (!m_HasSharedComponent) | if (!m_HasSharedComponent) | ||||
m_HasSharedComponent = ai->m_UseSharedComponent; | m_HasSharedComponent = ai->m_UseSharedComponent; | ||||
m_Players.push_back(ai); | m_Players.push_back(ai); | ||||
return true; | return true; | ||||
} | } | ||||
bool RunGamestateInit(const ScriptInterface::StructuredClone& gameState, const Grid<NavcellData>& passabilityMap, const Grid<u8>& territoryMap, | bool RunGamestateInit(const Script::StructuredClone& gameState, const Grid<NavcellData>& passabilityMap, const Grid<u8>& territoryMap, | ||||
const std::map<std::string, pass_class_t>& nonPathfindingPassClassMasks, const std::map<std::string, pass_class_t>& pathfindingPassClassMasks) | const std::map<std::string, pass_class_t>& nonPathfindingPassClassMasks, const std::map<std::string, pass_class_t>& pathfindingPassClassMasks) | ||||
{ | { | ||||
// this will be run last by InitGame.js, passing the full game representation. | // this will be run last by InitGame.js, passing the full game representation. | ||||
// For now it will run for the shared Component. | // For now it will run for the shared Component. | ||||
// This is NOT run during deserialization. | // This is NOT run during deserialization. | ||||
ScriptRequest rq(m_ScriptInterface); | ScriptRequest rq(m_ScriptInterface); | ||||
JS::RootedValue state(rq.cx); | JS::RootedValue state(rq.cx); | ||||
m_ScriptInterface->ReadStructuredClone(gameState, &state); | Script::ReadStructuredClone(rq, gameState, &state); | ||||
ScriptInterface::ToJSVal(rq, &m_PassabilityMapVal, passabilityMap); | ScriptInterface::ToJSVal(rq, &m_PassabilityMapVal, passabilityMap); | ||||
ScriptInterface::ToJSVal(rq, &m_TerritoryMapVal, territoryMap); | ScriptInterface::ToJSVal(rq, &m_TerritoryMapVal, territoryMap); | ||||
m_PassabilityMap = passabilityMap; | m_PassabilityMap = passabilityMap; | ||||
m_NonPathfindingPassClasses = nonPathfindingPassClassMasks; | m_NonPathfindingPassClasses = nonPathfindingPassClassMasks; | ||||
m_PathfindingPassClasses = pathfindingPassClassMasks; | m_PathfindingPassClasses = pathfindingPassClassMasks; | ||||
m_LongPathfinder.Reload(&m_PassabilityMap); | m_LongPathfinder.Reload(&m_PassabilityMap); | ||||
Show All 10 Lines | if (m_HasSharedComponent) | ||||
if (m_HasSharedComponent && m_Players[i]->m_UseSharedComponent) | if (m_HasSharedComponent && m_Players[i]->m_UseSharedComponent) | ||||
m_Players[i]->InitAI(state, m_SharedAIObj); | m_Players[i]->InitAI(state, m_SharedAIObj); | ||||
} | } | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
void UpdateGameState(const ScriptInterface::StructuredClone& gameState) | void UpdateGameState(const Script::StructuredClone& gameState) | ||||
{ | { | ||||
ENSURE(m_CommandsComputed); | ENSURE(m_CommandsComputed); | ||||
m_GameState = gameState; | m_GameState = gameState; | ||||
} | } | ||||
void UpdatePathfinder(const Grid<NavcellData>& passabilityMap, bool globallyDirty, const Grid<u8>& dirtinessGrid, bool justDeserialized, | void UpdatePathfinder(const Grid<NavcellData>& passabilityMap, bool globallyDirty, const Grid<u8>& dirtinessGrid, bool justDeserialized, | ||||
const std::map<std::string, pass_class_t>& nonPathfindingPassClassMasks, const std::map<std::string, pass_class_t>& pathfindingPassClassMasks) | const std::map<std::string, pass_class_t>& nonPathfindingPassClassMasks, const std::map<std::string, pass_class_t>& pathfindingPassClassMasks) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 143 Lines • ▼ Show 20 Lines | for (size_t i = 0; i < m_Players.size(); ++i) | ||||
serializer.NumberI32_Unbounded("player", m_Players[i]->m_Player); | serializer.NumberI32_Unbounded("player", m_Players[i]->m_Player); | ||||
serializer.NumberU8_Unbounded("difficulty", m_Players[i]->m_Difficulty); | serializer.NumberU8_Unbounded("difficulty", m_Players[i]->m_Difficulty); | ||||
serializer.String("behavior", m_Players[i]->m_Behavior, 1, 256); | serializer.String("behavior", m_Players[i]->m_Behavior, 1, 256); | ||||
serializer.NumberU32_Unbounded("num commands", (u32)m_Players[i]->m_Commands.size()); | serializer.NumberU32_Unbounded("num commands", (u32)m_Players[i]->m_Commands.size()); | ||||
for (size_t j = 0; j < m_Players[i]->m_Commands.size(); ++j) | for (size_t j = 0; j < m_Players[i]->m_Commands.size(); ++j) | ||||
{ | { | ||||
JS::RootedValue val(rq.cx); | JS::RootedValue val(rq.cx); | ||||
m_ScriptInterface->ReadStructuredClone(m_Players[i]->m_Commands[j], &val); | Script::ReadStructuredClone(rq, m_Players[i]->m_Commands[j], &val); | ||||
serializer.ScriptVal("command", &val); | serializer.ScriptVal("command", &val); | ||||
} | } | ||||
serializer.ScriptVal("data", &m_Players[i]->m_Obj); | serializer.ScriptVal("data", &m_Players[i]->m_Obj); | ||||
} | } | ||||
// AI pathfinder | // AI pathfinder | ||||
Serializer(serializer, "non pathfinding pass classes", m_NonPathfindingPassClasses); | Serializer(serializer, "non pathfinding pass classes", m_NonPathfindingPassClasses); | ||||
▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | for (size_t i = 0; i < numAis; ++i) | ||||
u32 numCommands; | u32 numCommands; | ||||
deserializer.NumberU32_Unbounded("num commands", numCommands); | deserializer.NumberU32_Unbounded("num commands", numCommands); | ||||
m_Players.back()->m_Commands.reserve(numCommands); | m_Players.back()->m_Commands.reserve(numCommands); | ||||
for (size_t j = 0; j < numCommands; ++j) | for (size_t j = 0; j < numCommands; ++j) | ||||
{ | { | ||||
JS::RootedValue val(rq.cx); | JS::RootedValue val(rq.cx); | ||||
deserializer.ScriptVal("command", &val); | deserializer.ScriptVal("command", &val); | ||||
m_Players.back()->m_Commands.push_back(m_ScriptInterface->WriteStructuredClone(val)); | m_Players.back()->m_Commands.push_back(Script::WriteStructuredClone(rq, val)); | ||||
} | } | ||||
deserializer.ScriptObjectAssign("data", m_Players.back()->m_Obj); | deserializer.ScriptObjectAssign("data", m_Players.back()->m_Obj); | ||||
} | } | ||||
// AI pathfinder | // AI pathfinder | ||||
Serializer(deserializer, "non pathfinding pass classes", m_NonPathfindingPassClasses); | Serializer(deserializer, "non pathfinding pass classes", m_NonPathfindingPassClasses); | ||||
Serializer(deserializer, "pathfinding pass classes", m_PathfindingPassClasses); | Serializer(deserializer, "pathfinding pass classes", m_PathfindingPassClasses); | ||||
Show All 37 Lines | private: | ||||
void PerformComputation() | void PerformComputation() | ||||
{ | { | ||||
// Deserialize the game state, to pass to the AI's HandleMessage | // Deserialize the game state, to pass to the AI's HandleMessage | ||||
ScriptRequest rq(m_ScriptInterface); | ScriptRequest rq(m_ScriptInterface); | ||||
JS::RootedValue state(rq.cx); | JS::RootedValue state(rq.cx); | ||||
{ | { | ||||
PROFILE3("AI compute read state"); | PROFILE3("AI compute read state"); | ||||
m_ScriptInterface->ReadStructuredClone(m_GameState, &state); | Script::ReadStructuredClone(rq, m_GameState, &state); | ||||
m_ScriptInterface->SetProperty(state, "passabilityMap", m_PassabilityMapVal, true); | m_ScriptInterface->SetProperty(state, "passabilityMap", m_PassabilityMapVal, true); | ||||
m_ScriptInterface->SetProperty(state, "territoryMap", m_TerritoryMapVal, true); | m_ScriptInterface->SetProperty(state, "territoryMap", m_TerritoryMapVal, true); | ||||
} | } | ||||
// It would be nice to do | // It would be nice to do | ||||
// m_ScriptInterface->FreezeObject(state.get(), true); | // m_ScriptInterface->FreezeObject(state.get(), true); | ||||
// to prevent AI scripts accidentally modifying the state and | // to prevent AI scripts accidentally modifying the state and | ||||
// affecting other AI scripts they share it with. But the performance | // affecting other AI scripts they share it with. But the performance | ||||
Show All 34 Lines | private: | ||||
std::vector<shared_ptr<CAIPlayer> > m_Players; // use shared_ptr just to avoid copying | std::vector<shared_ptr<CAIPlayer> > m_Players; // use shared_ptr just to avoid copying | ||||
bool m_HasSharedComponent; | bool m_HasSharedComponent; | ||||
JS::PersistentRootedValue m_SharedAIObj; | JS::PersistentRootedValue m_SharedAIObj; | ||||
std::vector<SCommandSets> m_Commands; | std::vector<SCommandSets> m_Commands; | ||||
std::set<std::wstring> m_LoadedModules; | std::set<std::wstring> m_LoadedModules; | ||||
ScriptInterface::StructuredClone m_GameState; | Script::StructuredClone m_GameState; | ||||
Grid<NavcellData> m_PassabilityMap; | Grid<NavcellData> m_PassabilityMap; | ||||
JS::PersistentRootedValue m_PassabilityMapVal; | JS::PersistentRootedValue m_PassabilityMapVal; | ||||
Grid<u8> m_TerritoryMap; | Grid<u8> m_TerritoryMap; | ||||
JS::PersistentRootedValue m_TerritoryMapVal; | JS::PersistentRootedValue m_TerritoryMapVal; | ||||
std::map<std::string, pass_class_t> m_NonPathfindingPassClasses; | std::map<std::string, pass_class_t> m_NonPathfindingPassClasses; | ||||
std::map<std::string, pass_class_t> m_PathfindingPassClasses; | std::map<std::string, pass_class_t> m_PathfindingPassClasses; | ||||
HierarchicalPathfinder m_HierarchicalPathfinder; | HierarchicalPathfinder m_HierarchicalPathfinder; | ||||
▲ Show 20 Lines • Show All 111 Lines • ▼ Show 20 Lines | virtual void RunGamestateInit() | ||||
if (cmpTerritoryManager && cmpTerritoryManager->NeedUpdateAI(&m_TerritoriesDirtyID, &m_TerritoriesDirtyBlinkingID)) | if (cmpTerritoryManager && cmpTerritoryManager->NeedUpdateAI(&m_TerritoriesDirtyID, &m_TerritoriesDirtyBlinkingID)) | ||||
territoryMap = &cmpTerritoryManager->GetTerritoryGrid(); | territoryMap = &cmpTerritoryManager->GetTerritoryGrid(); | ||||
LoadPathfinderClasses(state); | LoadPathfinderClasses(state); | ||||
std::map<std::string, pass_class_t> nonPathfindingPassClassMasks, pathfindingPassClassMasks; | std::map<std::string, pass_class_t> nonPathfindingPassClassMasks, pathfindingPassClassMasks; | ||||
if (cmpPathfinder) | if (cmpPathfinder) | ||||
cmpPathfinder->GetPassabilityClasses(nonPathfindingPassClassMasks, pathfindingPassClassMasks); | cmpPathfinder->GetPassabilityClasses(nonPathfindingPassClassMasks, pathfindingPassClassMasks); | ||||
m_Worker.RunGamestateInit(scriptInterface.WriteStructuredClone(state), | m_Worker.RunGamestateInit(Script::WriteStructuredClone(rq, state), | ||||
*passabilityMap, *territoryMap, nonPathfindingPassClassMasks, pathfindingPassClassMasks); | *passabilityMap, *territoryMap, nonPathfindingPassClassMasks, pathfindingPassClassMasks); | ||||
} | } | ||||
virtual void StartComputation() | virtual void StartComputation() | ||||
{ | { | ||||
PROFILE("AI setup"); | PROFILE("AI setup"); | ||||
const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface(); | const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface(); | ||||
Show All 9 Lines | virtual void StartComputation() | ||||
JS::RootedValue state(rq.cx); | JS::RootedValue state(rq.cx); | ||||
if (m_JustDeserialized) | if (m_JustDeserialized) | ||||
cmpAIInterface->GetFullRepresentation(&state, false); | cmpAIInterface->GetFullRepresentation(&state, false); | ||||
else | else | ||||
cmpAIInterface->GetRepresentation(&state); | cmpAIInterface->GetRepresentation(&state); | ||||
LoadPathfinderClasses(state); // add the pathfinding classes to it | LoadPathfinderClasses(state); // add the pathfinding classes to it | ||||
// Update the game state | // Update the game state | ||||
m_Worker.UpdateGameState(scriptInterface.WriteStructuredClone(state)); | m_Worker.UpdateGameState(Script::WriteStructuredClone(rq, state)); | ||||
// Update the pathfinding data | // Update the pathfinding data | ||||
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | ||||
if (cmpPathfinder) | if (cmpPathfinder) | ||||
{ | { | ||||
const GridUpdateInformation& dirtinessInformations = cmpPathfinder->GetAIPathfinderDirtinessInformation(); | const GridUpdateInformation& dirtinessInformations = cmpPathfinder->GetAIPathfinderDirtinessInformation(); | ||||
if (dirtinessInformations.dirty || m_JustDeserialized) | if (dirtinessInformations.dirty || m_JustDeserialized) | ||||
Show All 37 Lines | virtual void PushCommands() | ||||
const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface(); | const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface(); | ||||
ScriptRequest rq(scriptInterface); | ScriptRequest rq(scriptInterface); | ||||
JS::RootedValue clonedCommandVal(rq.cx); | JS::RootedValue clonedCommandVal(rq.cx); | ||||
for (size_t i = 0; i < commands.size(); ++i) | for (size_t i = 0; i < commands.size(); ++i) | ||||
{ | { | ||||
for (size_t j = 0; j < commands[i].commands.size(); ++j) | for (size_t j = 0; j < commands[i].commands.size(); ++j) | ||||
{ | { | ||||
scriptInterface.ReadStructuredClone(commands[i].commands[j], &clonedCommandVal); | Script::ReadStructuredClone(rq, commands[i].commands[j], &clonedCommandVal); | ||||
cmpCommandQueue->PushLocalCommand(commands[i].player, clonedCommandVal); | cmpCommandQueue->PushLocalCommand(commands[i].player, clonedCommandVal); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
private: | private: | ||||
size_t m_TerritoriesDirtyID; | size_t m_TerritoriesDirtyID; | ||||
size_t m_TerritoriesDirtyBlinkingID; | size_t m_TerritoriesDirtyBlinkingID; | ||||
▲ Show 20 Lines • Show All 52 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator