1 /* Copyright (C) 2018 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "precompiled.h" 19 20 #include "simulation2/system/Component.h" 21 #include "ICmpAIManager.h" 22 23 #include "simulation2/MessageTypes.h" 24 25 #include "graphics/Terrain.h" 26 #include "lib/timer.h" 27 #include "lib/tex/tex.h" 28 #include "lib/allocators/shared_ptr.h" 29 #include "ps/CLogger.h" 30 #include "ps/Filesystem.h" 31 #include "ps/Profile.h" 32 #include "ps/scripting/JSInterface_VFS.h" 33 #include "ps/TemplateLoader.h" 34 #include "ps/Util.h" 35 #include "simulation2/components/ICmpAIInterface.h" 36 #include "simulation2/components/ICmpCommandQueue.h" 37 #include "simulation2/components/ICmpObstructionManager.h" 38 #include "simulation2/components/ICmpRangeManager.h" 39 #include "simulation2/components/ICmpTemplateManager.h" 40 #include "simulation2/components/ICmpTerritoryManager.h" 41 #include "simulation2/helpers/LongPathfinder.h" 42 #include "simulation2/serialization/DebugSerializer.h" 43 #include "simulation2/serialization/StdDeserializer.h" 44 #include "simulation2/serialization/StdSerializer.h" 45 #include "simulation2/serialization/SerializeTemplates.h" 46 47 extern void QuitEngine(); 48 49 /** 50 * @file 51 * Player AI interface. 52 * AI is primarily scripted, and the CCmpAIManager component defined here 53 * takes care of managing all the scripts. 54 * 55 * To avoid slow AI scripts causing jerky rendering, they are run in a background 56 * thread (maintained by CAIWorker) so that it's okay if they take a whole simulation 57 * turn before returning their results (though preferably they shouldn't use nearly 58 * that much CPU). 59 * 60 * CCmpAIManager grabs the world state after each turn (making use of AIInterface.js 61 * and AIProxy.js to decide what data to include) then passes it to CAIWorker. 62 * The AI scripts will then run asynchronously and return a list of commands to execute. 63 * Any attempts to read the command list (including indirectly via serialization) 64 * will block until it's actually completed, so the rest of the engine should avoid 65 * reading it for as long as possible. 66 * 67 * JS::Values are passed between the game and AI threads using ScriptInterface::StructuredClone. 68 * 69 * TODO: actually the thread isn't implemented yet, because performance hasn't been 70 * sufficiently problematic to justify the complexity yet, but the CAIWorker interface 71 * is designed to hopefully support threading when we want it. 72 */ 73 74 /** 75 * Implements worker thread for CCmpAIManager. 76 */ 77 class CAIWorker 78 { 79 private: 80 class CAIPlayer 81 { 82 NONCOPYABLE(CAIPlayer); 83 public: CAIPlayer(CAIWorker & worker,const std::wstring & aiName,player_id_t player,u8 difficulty,const std::wstring & behavior,shared_ptr<ScriptInterface> scriptInterface)84 CAIPlayer(CAIWorker& worker, const std::wstring& aiName, player_id_t player, u8 difficulty, const std::wstring& behavior, 85 shared_ptr<ScriptInterface> scriptInterface) : 86 m_Worker(worker), m_AIName(aiName), m_Player(player), m_Difficulty(difficulty), m_Behavior(behavior), 87 m_ScriptInterface(scriptInterface), m_Obj(scriptInterface->GetJSRuntime()) 88 { 89 } 90 Initialise()91 bool Initialise() 92 { 93 // LoadScripts will only load each script once even though we call it for each player 94 if (!m_Worker.LoadScripts(m_AIName)) 95 return false; 96 97 JSContext* cx = m_ScriptInterface->GetContext(); 98 JSAutoRequest rq(cx); 99 100 OsPath path = L"simulation/ai/" + m_AIName + L"/data.json"; 101 JS::RootedValue metadata(cx); 102 m_Worker.LoadMetadata(path, &metadata); 103 if (metadata.isUndefined()) 104 { 105 LOGERROR("Failed to create AI player: can't find %s", path.string8()); 106 return false; 107 } 108 109 // Get the constructor name from the metadata 110 std::string moduleName; 111 std::string constructor; 112 JS::RootedValue objectWithConstructor(cx); // object that should contain the constructor function 113 JS::RootedValue global(cx, m_ScriptInterface->GetGlobalObject()); 114 JS::RootedValue ctor(cx); 115 if (!m_ScriptInterface->HasProperty(metadata, "moduleName")) 116 { 117 LOGERROR("Failed to create AI player: %s: missing 'moduleName'", path.string8()); 118 return false; 119 } 120 121 m_ScriptInterface->GetProperty(metadata, "moduleName", moduleName); 122 if (!m_ScriptInterface->GetProperty(global, moduleName.c_str(), &objectWithConstructor) 123 || objectWithConstructor.isUndefined()) 124 { 125 LOGERROR("Failed to create AI player: %s: can't find the module that should contain the constructor: '%s'", path.string8(), moduleName); 126 return false; 127 } 128 129 if (!m_ScriptInterface->GetProperty(metadata, "constructor", constructor)) 130 { 131 LOGERROR("Failed to create AI player: %s: missing 'constructor'", path.string8()); 132 return false; 133 } 134 135 // Get the constructor function from the loaded scripts 136 if (!m_ScriptInterface->GetProperty(objectWithConstructor, constructor.c_str(), &ctor) 137 || ctor.isNull()) 138 { 139 LOGERROR("Failed to create AI player: %s: can't find constructor '%s'", path.string8(), constructor); 140 return false; 141 } 142 143 m_ScriptInterface->GetProperty(metadata, "useShared", m_UseSharedComponent); 144 145 // Set up the data to pass as the constructor argument 146 JS::RootedValue settings(cx); 147 m_ScriptInterface->Eval(L"({})", &settings); 148 m_ScriptInterface->SetProperty(settings, "player", m_Player, false); 149 m_ScriptInterface->SetProperty(settings, "difficulty", m_Difficulty, false); 150 m_ScriptInterface->SetProperty(settings, "behavior", m_Behavior, false); 151 152 if (!m_UseSharedComponent) 153 { 154 ENSURE(m_Worker.m_HasLoadedEntityTemplates); 155 m_ScriptInterface->SetProperty(settings, "templates", m_Worker.m_EntityTemplates, false); 156 } 157 158 JS::AutoValueVector argv(cx); 159 argv.append(settings.get()); 160 m_ScriptInterface->CallConstructor(ctor, argv, &m_Obj); 161 162 if (m_Obj.get().isNull()) 163 { 164 LOGERROR("Failed to create AI player: %s: error calling constructor '%s'", path.string8(), constructor); 165 return false; 166 } 167 return true; 168 } 169 Run(JS::HandleValue state,int playerID)170 void Run(JS::HandleValue state, int playerID) 171 { 172 m_Commands.clear(); 173 m_ScriptInterface->CallFunctionVoid(m_Obj, "HandleMessage", state, playerID); 174 } 175 // overloaded with a sharedAI part. 176 // javascript can handle both natively on the same function. Run(JS::HandleValue state,int playerID,JS::HandleValue SharedAI)177 void Run(JS::HandleValue state, int playerID, JS::HandleValue SharedAI) 178 { 179 m_Commands.clear(); 180 m_ScriptInterface->CallFunctionVoid(m_Obj, "HandleMessage", state, playerID, SharedAI); 181 } InitAI(JS::HandleValue state,JS::HandleValue SharedAI)182 void InitAI(JS::HandleValue state, JS::HandleValue SharedAI) 183 { 184 m_Commands.clear(); 185 m_ScriptInterface->CallFunctionVoid(m_Obj, "Init", state, m_Player, SharedAI); 186 } 187 188 CAIWorker& m_Worker; 189 std::wstring m_AIName; 190 player_id_t m_Player; 191 u8 m_Difficulty; 192 std::wstring m_Behavior; 193 bool m_UseSharedComponent; 194 195 // Take care to keep this declaration before heap rooted members. Destructors of heap rooted 196 // members have to be called before the runtime destructor. 197 shared_ptr<ScriptInterface> m_ScriptInterface; 198 199 JS::PersistentRootedValue m_Obj; 200 std::vector<shared_ptr<ScriptInterface::StructuredClone> > m_Commands; 201 }; 202 203 public: 204 struct SCommandSets 205 { 206 player_id_t player; 207 std::vector<shared_ptr<ScriptInterface::StructuredClone> > commands; 208 }; 209 CAIWorker()210 CAIWorker() : 211 m_ScriptInterface(new ScriptInterface("Engine", "AI", g_ScriptRuntime)), 212 m_TurnNum(0), 213 m_CommandsComputed(true), 214 m_HasLoadedEntityTemplates(false), 215 m_HasSharedComponent(false), 216 m_SerializablePrototypes(new ObjectIdCache<std::wstring>(g_ScriptRuntime)), 217 m_EntityTemplates(g_ScriptRuntime->m_rt), 218 m_SharedAIObj(g_ScriptRuntime->m_rt), 219 m_PassabilityMapVal(g_ScriptRuntime->m_rt), 220 m_TerritoryMapVal(g_ScriptRuntime->m_rt) 221 { 222 223 m_ScriptInterface->ReplaceNondeterministicRNG(m_RNG); 224 225 m_ScriptInterface->SetCallbackData(static_cast<void*> (this)); 226 227 m_SerializablePrototypes->init(); 228 JS_AddExtraGCRootsTracer(m_ScriptInterface->GetJSRuntime(), Trace, this); 229 230 m_ScriptInterface->RegisterFunction<void, int, JS::HandleValue, CAIWorker::PostCommand>("PostCommand"); 231 m_ScriptInterface->RegisterFunction<void, std::wstring, CAIWorker::IncludeModule>("IncludeModule"); 232 m_ScriptInterface->RegisterFunction<void, CAIWorker::ExitProgram>("Exit"); 233 234 m_ScriptInterface->RegisterFunction<JS::Value, JS::HandleValue, JS::HandleValue, pass_class_t, CAIWorker::ComputePath>("ComputePath"); 235 236 m_ScriptInterface->RegisterFunction<void, std::wstring, std::vector<u32>, u32, u32, u32, CAIWorker::DumpImage>("DumpImage"); 237 m_ScriptInterface->RegisterFunction<CParamNode, std::string, CAIWorker::GetTemplate>("GetTemplate"); 238 239 JSI_VFS::RegisterScriptFunctions_Simulation(*(m_ScriptInterface.get())); 240 241 // Globalscripts may use VFS script functions 242 m_ScriptInterface->LoadGlobalScripts(); 243 } 244 ~CAIWorker()245 ~CAIWorker() 246 { 247 JS_RemoveExtraGCRootsTracer(m_ScriptInterface->GetJSRuntime(), Trace, this); 248 } 249 HasLoadedEntityTemplates() const250 bool HasLoadedEntityTemplates() const { return m_HasLoadedEntityTemplates; } 251 LoadScripts(const std::wstring & moduleName)252 bool LoadScripts(const std::wstring& moduleName) 253 { 254 // Ignore modules that are already loaded 255 if (m_LoadedModules.find(moduleName) != m_LoadedModules.end()) 256 return true; 257 258 // Mark this as loaded, to prevent it recursively loading itself 259 m_LoadedModules.insert(moduleName); 260 261 // Load and execute *.js 262 VfsPaths pathnames; 263 if (vfs::GetPathnames(g_VFS, L"simulation/ai/" + moduleName + L"/", L"*.js", pathnames) < 0) 264 { 265 LOGERROR("Failed to load AI scripts for module %s", utf8_from_wstring(moduleName)); 266 return false; 267 } 268 269 for (const VfsPath& path : pathnames) 270 { 271 if (!m_ScriptInterface->LoadGlobalScriptFile(path)) 272 { 273 LOGERROR("Failed to load script %s", path.string8()); 274 return false; 275 } 276 } 277 278 return true; 279 } 280 IncludeModule(ScriptInterface::CxPrivate * pCxPrivate,const std::wstring & name)281 static void IncludeModule(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name) 282 { 283 ENSURE(pCxPrivate->pCBData); 284 CAIWorker* self = static_cast<CAIWorker*> (pCxPrivate->pCBData); 285 self->LoadScripts(name); 286 } 287 PostCommand(ScriptInterface::CxPrivate * pCxPrivate,int playerid,JS::HandleValue cmd)288 static void PostCommand(ScriptInterface::CxPrivate* pCxPrivate, int playerid, JS::HandleValue cmd) 289 { 290 ENSURE(pCxPrivate->pCBData); 291 CAIWorker* self = static_cast<CAIWorker*> (pCxPrivate->pCBData); 292 self->PostCommand(playerid, cmd); 293 } 294 PostCommand(int playerid,JS::HandleValue cmd)295 void PostCommand(int playerid, JS::HandleValue cmd) 296 { 297 for (size_t i=0; i<m_Players.size(); i++) 298 { 299 if (m_Players[i]->m_Player == playerid) 300 { 301 m_Players[i]->m_Commands.push_back(m_ScriptInterface->WriteStructuredClone(cmd)); 302 return; 303 } 304 } 305 306 LOGERROR("Invalid playerid in PostCommand!"); 307 } 308 ComputePath(ScriptInterface::CxPrivate * pCxPrivate,JS::HandleValue position,JS::HandleValue goal,pass_class_t passClass)309 static JS::Value ComputePath(ScriptInterface::CxPrivate* pCxPrivate, 310 JS::HandleValue position, JS::HandleValue goal, pass_class_t passClass) 311 { 312 ENSURE(pCxPrivate->pCBData); 313 CAIWorker* self = static_cast<CAIWorker*> (pCxPrivate->pCBData); 314 JSContext* cx(self->m_ScriptInterface->GetContext()); 315 JSAutoRequest rq(cx); 316 317 CFixedVector2D pos, goalPos; 318 std::vector<CFixedVector2D> waypoints; 319 JS::RootedValue retVal(cx); 320 321 self->m_ScriptInterface->FromJSVal<CFixedVector2D>(cx, position, pos); 322 self->m_ScriptInterface->FromJSVal<CFixedVector2D>(cx, goal, goalPos); 323 324 self->ComputePath(pos, goalPos, passClass, waypoints); 325 self->m_ScriptInterface->ToJSVal<std::vector<CFixedVector2D> >(cx, &retVal, waypoints); 326 327 return retVal; 328 } 329 ComputePath(const CFixedVector2D & pos,const CFixedVector2D & goal,pass_class_t passClass,std::vector<CFixedVector2D> & waypoints)330 void ComputePath(const CFixedVector2D& pos, const CFixedVector2D& goal, pass_class_t passClass, std::vector<CFixedVector2D>& waypoints) 331 { 332 WaypointPath ret; 333 PathGoal pathGoal = { PathGoal::POINT, goal.X, goal.Y }; 334 m_LongPathfinder.ComputePath(pos.X, pos.Y, pathGoal, passClass, ret); 335 336 for (Waypoint& wp : ret.m_Waypoints) 337 waypoints.emplace_back(wp.x, wp.z); 338 } 339 GetTemplate(ScriptInterface::CxPrivate * pCxPrivate,const std::string & name)340 static CParamNode GetTemplate(ScriptInterface::CxPrivate* pCxPrivate, const std::string& name) 341 { 342 ENSURE(pCxPrivate->pCBData); 343 CAIWorker* self = static_cast<CAIWorker*> (pCxPrivate->pCBData); 344 345 return self->GetTemplate(name); 346 } 347 GetTemplate(const std::string & name)348 CParamNode GetTemplate(const std::string& name) 349 { 350 if (!m_TemplateLoader.TemplateExists(name)) 351 return CParamNode(false); 352 return m_TemplateLoader.GetTemplateFileData(name).GetChild("Entity"); 353 } 354 ExitProgram(ScriptInterface::CxPrivate * UNUSED (pCxPrivate))355 static void ExitProgram(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) 356 { 357 QuitEngine(); 358 } 359 360 /** 361 * Debug function for AI scripts to dump 2D array data (e.g. terrain tile weights). 362 */ DumpImage(ScriptInterface::CxPrivate * UNUSED (pCxPrivate),const std::wstring & name,const std::vector<u32> & data,u32 w,u32 h,u32 max)363 static void DumpImage(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& name, const std::vector<u32>& data, u32 w, u32 h, u32 max) 364 { 365 // TODO: this is totally not threadsafe. 366 VfsPath filename = L"screenshots/aidump/" + name; 367 368 if (data.size() != w*h) 369 { 370 debug_warn(L"DumpImage: data size doesn't match w*h"); 371 return; 372 } 373 374 if (max == 0) 375 { 376 debug_warn(L"DumpImage: max must not be 0"); 377 return; 378 } 379 380 const size_t bpp = 8; 381 int flags = TEX_BOTTOM_UP|TEX_GREY; 382 383 const size_t img_size = w * h * bpp/8; 384 const size_t hdr_size = tex_hdr_size(filename); 385 shared_ptr<u8> buf; 386 AllocateAligned(buf, hdr_size+img_size, maxSectorSize); 387 Tex t; 388 if (t.wrap(w, h, bpp, flags, buf, hdr_size) < 0) 389 return; 390 391 u8* img = buf.get() + hdr_size; 392 for (size_t i = 0; i < data.size(); ++i) 393 img[i] = (u8)((data[i] * 255) / max); 394 395 tex_write(&t, filename); 396 } 397 SetRNGSeed(u32 seed)398 void SetRNGSeed(u32 seed) 399 { 400 m_RNG.seed(seed); 401 } 402 TryLoadSharedComponent()403 bool TryLoadSharedComponent() 404 { 405 JSContext* cx = m_ScriptInterface->GetContext(); 406 JSAutoRequest rq(cx); 407 408 // we don't need to load it. 409 if (!m_HasSharedComponent) 410 return false; 411 412 // reset the value so it can be used to determine if we actually initialized it. 413 m_HasSharedComponent = false; 414 415 if (LoadScripts(L"common-api")) 416 m_HasSharedComponent = true; 417 else 418 return false; 419 420 // mainly here for the error messages 421 OsPath path = L"simulation/ai/common-api/"; 422 423 // Constructor name is SharedScript, it's in the module API3 424 // TODO: Hardcoding this is bad, we need a smarter way. 425 JS::RootedValue AIModule(cx); 426 JS::RootedValue global(cx, m_ScriptInterface->GetGlobalObject()); 427 JS::RootedValue ctor(cx); 428 if (!m_ScriptInterface->GetProperty(global, "API3", &AIModule) || AIModule.isUndefined()) 429 { 430 LOGERROR("Failed to create shared AI component: %s: can't find module '%s'", path.string8(), "API3"); 431 return false; 432 } 433 434 if (!m_ScriptInterface->GetProperty(AIModule, "SharedScript", &ctor) 435 || ctor.isUndefined()) 436 { 437 LOGERROR("Failed to create shared AI component: %s: can't find constructor '%s'", path.string8(), "SharedScript"); 438 return false; 439 } 440 441 // Set up the data to pass as the constructor argument 442 JS::RootedValue settings(cx); 443 m_ScriptInterface->Eval(L"({})", &settings); 444 JS::RootedValue playersID(cx); 445 m_ScriptInterface->Eval(L"({})", &playersID); 446 447 for (size_t i = 0; i < m_Players.size(); ++i) 448 { 449 JS::RootedValue val(cx); 450 m_ScriptInterface->ToJSVal(cx, &val, m_Players[i]->m_Player); 451 m_ScriptInterface->SetPropertyInt(playersID, i, val, true); 452 } 453 454 m_ScriptInterface->SetProperty(settings, "players", playersID); 455 ENSURE(m_HasLoadedEntityTemplates); 456 m_ScriptInterface->SetProperty(settings, "templates", m_EntityTemplates, false); 457 458 JS::AutoValueVector argv(cx); 459 argv.append(settings); 460 m_ScriptInterface->CallConstructor(ctor, argv, &m_SharedAIObj); 461 462 if (m_SharedAIObj.get().isNull()) 463 { 464 LOGERROR("Failed to create shared AI component: %s: error calling constructor '%s'", path.string8(), "SharedScript"); 465 return false; 466 } 467 468 return true; 469 } 470 AddPlayer(const std::wstring & aiName,player_id_t player,u8 difficulty,const std::wstring & behavior)471 bool AddPlayer(const std::wstring& aiName, player_id_t player, u8 difficulty, const std::wstring& behavior) 472 { 473 shared_ptr<CAIPlayer> ai(new CAIPlayer(*this, aiName, player, difficulty, behavior, m_ScriptInterface)); 474 if (!ai->Initialise()) 475 return false; 476 477 // this will be set to true if we need to load the shared Component. 478 if (!m_HasSharedComponent) 479 m_HasSharedComponent = ai->m_UseSharedComponent; 480 481 m_Players.push_back(ai); 482 483 return true; 484 } 485 RunGamestateInit(const shared_ptr<ScriptInterface::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)486 bool RunGamestateInit(const shared_ptr<ScriptInterface::StructuredClone>& gameState, const Grid<NavcellData>& passabilityMap, const Grid<u8>& territoryMap, 487 const std::map<std::string, pass_class_t>& nonPathfindingPassClassMasks, const std::map<std::string, pass_class_t>& pathfindingPassClassMasks) 488 { 489 // this will be run last by InitGame.js, passing the full game representation. 490 // For now it will run for the shared Component. 491 // This is NOT run during deserialization. 492 JSContext* cx = m_ScriptInterface->GetContext(); 493 JSAutoRequest rq(cx); 494 495 JS::RootedValue state(cx); 496 m_ScriptInterface->ReadStructuredClone(gameState, &state); 497 ScriptInterface::ToJSVal(cx, &m_PassabilityMapVal, passabilityMap); 498 ScriptInterface::ToJSVal(cx, &m_TerritoryMapVal, territoryMap); 499 500 m_PassabilityMap = passabilityMap; 501 m_NonPathfindingPassClasses = nonPathfindingPassClassMasks; 502 m_PathfindingPassClasses = pathfindingPassClassMasks; 503 504 m_LongPathfinder.Reload(&m_PassabilityMap, nonPathfindingPassClassMasks, pathfindingPassClassMasks); 505 506 if (m_HasSharedComponent) 507 { 508 m_ScriptInterface->SetProperty(state, "passabilityMap", m_PassabilityMapVal, true); 509 m_ScriptInterface->SetProperty(state, "territoryMap", m_TerritoryMapVal, true); 510 m_ScriptInterface->CallFunctionVoid(m_SharedAIObj, "init", state); 511 512 for (size_t i = 0; i < m_Players.size(); ++i) 513 { 514 if (m_HasSharedComponent && m_Players[i]->m_UseSharedComponent) 515 m_Players[i]->InitAI(state, m_SharedAIObj); 516 } 517 } 518 519 return true; 520 } 521 UpdateGameState(const shared_ptr<ScriptInterface::StructuredClone> & gameState)522 void UpdateGameState(const shared_ptr<ScriptInterface::StructuredClone>& gameState) 523 { 524 ENSURE(m_CommandsComputed); 525 m_GameState = gameState; 526 } 527 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)528 void UpdatePathfinder(const Grid<NavcellData>& passabilityMap, bool globallyDirty, const Grid<u8>& dirtinessGrid, bool justDeserialized, 529 const std::map<std::string, pass_class_t>& nonPathfindingPassClassMasks, const std::map<std::string, pass_class_t>& pathfindingPassClassMasks) 530 { 531 ENSURE(m_CommandsComputed); 532 bool dimensionChange = m_PassabilityMap.m_W != passabilityMap.m_W || m_PassabilityMap.m_H != passabilityMap.m_H; 533 534 m_PassabilityMap = passabilityMap; 535 if (globallyDirty) 536 m_LongPathfinder.Reload(&m_PassabilityMap, nonPathfindingPassClassMasks, pathfindingPassClassMasks); 537 else 538 m_LongPathfinder.Update(&m_PassabilityMap, dirtinessGrid); 539 540 JSContext* cx = m_ScriptInterface->GetContext(); 541 if (dimensionChange || justDeserialized) 542 ScriptInterface::ToJSVal(cx, &m_PassabilityMapVal, m_PassabilityMap); 543 else 544 { 545 // Avoid a useless memory reallocation followed by a garbage collection. 546 JSAutoRequest rq(cx); 547 548 JS::RootedObject mapObj(cx, &m_PassabilityMapVal.toObject()); 549 JS::RootedValue mapData(cx); 550 ENSURE(JS_GetProperty(cx, mapObj, "data", &mapData)); 551 JS::RootedObject dataObj(cx, &mapData.toObject()); 552 553 u32 length = 0; 554 ENSURE(JS_GetArrayLength(cx, dataObj, &length)); 555 u32 nbytes = (u32)(length * sizeof(NavcellData)); 556 557 JS::AutoCheckCannotGC nogc; 558 memcpy((void*)JS_GetUint16ArrayData(dataObj, nogc), m_PassabilityMap.m_Data, nbytes); 559 } 560 } 561 UpdateTerritoryMap(const Grid<u8> & territoryMap)562 void UpdateTerritoryMap(const Grid<u8>& territoryMap) 563 { 564 ENSURE(m_CommandsComputed); 565 bool dimensionChange = m_TerritoryMap.m_W != territoryMap.m_W || m_TerritoryMap.m_H != territoryMap.m_H; 566 567 m_TerritoryMap = territoryMap; 568 569 JSContext* cx = m_ScriptInterface->GetContext(); 570 if (dimensionChange) 571 ScriptInterface::ToJSVal(cx, &m_TerritoryMapVal, m_TerritoryMap); 572 else 573 { 574 // Avoid a useless memory reallocation followed by a garbage collection. 575 JSAutoRequest rq(cx); 576 577 JS::RootedObject mapObj(cx, &m_TerritoryMapVal.toObject()); 578 JS::RootedValue mapData(cx); 579 ENSURE(JS_GetProperty(cx, mapObj, "data", &mapData)); 580 JS::RootedObject dataObj(cx, &mapData.toObject()); 581 582 u32 length = 0; 583 ENSURE(JS_GetArrayLength(cx, dataObj, &length)); 584 u32 nbytes = (u32)(length * sizeof(u8)); 585 586 JS::AutoCheckCannotGC nogc; 587 memcpy((void*)JS_GetUint8ArrayData(dataObj, nogc), m_TerritoryMap.m_Data, nbytes); 588 } 589 } 590 StartComputation()591 void StartComputation() 592 { 593 m_CommandsComputed = false; 594 } 595 WaitToFinishComputation()596 void WaitToFinishComputation() 597 { 598 if (!m_CommandsComputed) 599 { 600 PerformComputation(); 601 m_CommandsComputed = true; 602 } 603 } 604 GetCommands(std::vector<SCommandSets> & commands)605 void GetCommands(std::vector<SCommandSets>& commands) 606 { 607 WaitToFinishComputation(); 608 609 commands.clear(); 610 commands.resize(m_Players.size()); 611 for (size_t i = 0; i < m_Players.size(); ++i) 612 { 613 commands[i].player = m_Players[i]->m_Player; 614 commands[i].commands = m_Players[i]->m_Commands; 615 } 616 } 617 LoadEntityTemplates(const std::vector<std::pair<std::string,const CParamNode * >> & templates)618 void LoadEntityTemplates(const std::vector<std::pair<std::string, const CParamNode*> >& templates) 619 { 620 JSContext* cx = m_ScriptInterface->GetContext(); 621 JSAutoRequest rq(cx); 622 623 m_HasLoadedEntityTemplates = true; 624 625 m_ScriptInterface->Eval("({})", &m_EntityTemplates); 626 627 JS::RootedValue val(cx); 628 for (size_t i = 0; i < templates.size(); ++i) 629 { 630 templates[i].second->ToJSVal(cx, false, &val); 631 m_ScriptInterface->SetProperty(m_EntityTemplates, templates[i].first.c_str(), val, true); 632 } 633 } 634 Serialize(std::ostream & stream,bool isDebug)635 void Serialize(std::ostream& stream, bool isDebug) 636 { 637 WaitToFinishComputation(); 638 639 if (isDebug) 640 { 641 CDebugSerializer serializer(*m_ScriptInterface, stream); 642 serializer.Indent(4); 643 SerializeState(serializer); 644 } 645 else 646 { 647 CStdSerializer serializer(*m_ScriptInterface, stream); 648 // TODO: see comment in Deserialize() 649 serializer.SetSerializablePrototypes(m_SerializablePrototypes); 650 SerializeState(serializer); 651 } 652 } 653 SerializeState(ISerializer & serializer)654 void SerializeState(ISerializer& serializer) 655 { 656 if (m_Players.empty()) 657 return; 658 659 JSContext* cx = m_ScriptInterface->GetContext(); 660 JSAutoRequest rq(cx); 661 662 std::stringstream rngStream; 663 rngStream << m_RNG; 664 serializer.StringASCII("rng", rngStream.str(), 0, 32); 665 666 serializer.NumberU32_Unbounded("turn", m_TurnNum); 667 668 serializer.Bool("useSharedScript", m_HasSharedComponent); 669 if (m_HasSharedComponent) 670 { 671 JS::RootedValue sharedData(cx); 672 if (!m_ScriptInterface->CallFunction(m_SharedAIObj, "Serialize", &sharedData)) 673 LOGERROR("AI shared script Serialize call failed"); 674 serializer.ScriptVal("sharedData", &sharedData); 675 } 676 for (size_t i = 0; i < m_Players.size(); ++i) 677 { 678 serializer.String("name", m_Players[i]->m_AIName, 1, 256); 679 serializer.NumberI32_Unbounded("player", m_Players[i]->m_Player); 680 serializer.NumberU8_Unbounded("difficulty", m_Players[i]->m_Difficulty); 681 serializer.String("behavior", m_Players[i]->m_Behavior, 1, 256); 682 683 serializer.NumberU32_Unbounded("num commands", (u32)m_Players[i]->m_Commands.size()); 684 for (size_t j = 0; j < m_Players[i]->m_Commands.size(); ++j) 685 { 686 JS::RootedValue val(cx); 687 m_ScriptInterface->ReadStructuredClone(m_Players[i]->m_Commands[j], &val); 688 serializer.ScriptVal("command", &val); 689 } 690 691 bool hasCustomSerialize = m_ScriptInterface->HasProperty(m_Players[i]->m_Obj, "Serialize"); 692 if (hasCustomSerialize) 693 { 694 JS::RootedValue scriptData(cx); 695 if (!m_ScriptInterface->CallFunction(m_Players[i]->m_Obj, "Serialize", &scriptData)) 696 LOGERROR("AI script Serialize call failed"); 697 serializer.ScriptVal("data", &scriptData); 698 } 699 else 700 { 701 serializer.ScriptVal("data", &m_Players[i]->m_Obj); 702 } 703 } 704 705 // AI pathfinder 706 SerializeMap<SerializeString, SerializeU16_Unbounded>()(serializer, "non pathfinding pass classes", m_NonPathfindingPassClasses); 707 SerializeMap<SerializeString, SerializeU16_Unbounded>()(serializer, "pathfinding pass classes", m_PathfindingPassClasses); 708 serializer.NumberU16_Unbounded("pathfinder grid w", m_PassabilityMap.m_W); 709 serializer.NumberU16_Unbounded("pathfinder grid h", m_PassabilityMap.m_H); 710 serializer.RawBytes("pathfinder grid data", (const u8*)m_PassabilityMap.m_Data, 711 m_PassabilityMap.m_W*m_PassabilityMap.m_H*sizeof(NavcellData)); 712 } 713 Deserialize(std::istream & stream,u32 numAis)714 void Deserialize(std::istream& stream, u32 numAis) 715 { 716 m_PlayerMetadata.clear(); 717 m_Players.clear(); 718 719 if (numAis == 0) 720 return; 721 722 JSContext* cx = m_ScriptInterface->GetContext(); 723 JSAutoRequest rq(cx); 724 725 ENSURE(m_CommandsComputed); // deserializing while we're still actively computing would be bad 726 727 CStdDeserializer deserializer(*m_ScriptInterface, stream); 728 729 std::string rngString; 730 std::stringstream rngStream; 731 deserializer.StringASCII("rng", rngString, 0, 32); 732 rngStream << rngString; 733 rngStream >> m_RNG; 734 735 deserializer.NumberU32_Unbounded("turn", m_TurnNum); 736 737 deserializer.Bool("useSharedScript", m_HasSharedComponent); 738 if (m_HasSharedComponent) 739 { 740 TryLoadSharedComponent(); 741 JS::RootedValue sharedData(cx); 742 deserializer.ScriptVal("sharedData", &sharedData); 743 if (!m_ScriptInterface->CallFunctionVoid(m_SharedAIObj, "Deserialize", sharedData)) 744 LOGERROR("AI shared script Deserialize call failed"); 745 } 746 747 for (size_t i = 0; i < numAis; ++i) 748 { 749 std::wstring name; 750 player_id_t player; 751 u8 difficulty; 752 std::wstring behavior; 753 deserializer.String("name", name, 1, 256); 754 deserializer.NumberI32_Unbounded("player", player); 755 deserializer.NumberU8_Unbounded("difficulty",difficulty); 756 deserializer.String("behavior", behavior, 1, 256); 757 if (!AddPlayer(name, player, difficulty, behavior)) 758 throw PSERROR_Deserialize_ScriptError(); 759 760 u32 numCommands; 761 deserializer.NumberU32_Unbounded("num commands", numCommands); 762 m_Players.back()->m_Commands.reserve(numCommands); 763 for (size_t j = 0; j < numCommands; ++j) 764 { 765 JS::RootedValue val(cx); 766 deserializer.ScriptVal("command", &val); 767 m_Players.back()->m_Commands.push_back(m_ScriptInterface->WriteStructuredClone(val)); 768 } 769 770 // TODO: this is yucky but necessary while the AIs are sharing data between contexts; 771 // ideally a new (de)serializer instance would be created for each player 772 // so they would have a single, consistent script context to use and serializable 773 // prototypes could be stored in their ScriptInterface 774 deserializer.SetSerializablePrototypes(m_DeserializablePrototypes); 775 776 bool hasCustomDeserialize = m_ScriptInterface->HasProperty(m_Players.back()->m_Obj, "Deserialize"); 777 if (hasCustomDeserialize) 778 { 779 JS::RootedValue scriptData(cx); 780 deserializer.ScriptVal("data", &scriptData); 781 if (m_Players[i]->m_UseSharedComponent) 782 { 783 if (!m_ScriptInterface->CallFunctionVoid(m_Players.back()->m_Obj, "Deserialize", scriptData, m_SharedAIObj)) 784 LOGERROR("AI script Deserialize call failed"); 785 } 786 else if (!m_ScriptInterface->CallFunctionVoid(m_Players.back()->m_Obj, "Deserialize", scriptData)) 787 { 788 LOGERROR("AI script deserialize() call failed"); 789 } 790 } 791 else 792 { 793 deserializer.ScriptVal("data", &m_Players.back()->m_Obj); 794 } 795 } 796 797 // AI pathfinder 798 SerializeMap<SerializeString, SerializeU16_Unbounded>()(deserializer, "non pathfinding pass classes", m_NonPathfindingPassClasses); 799 SerializeMap<SerializeString, SerializeU16_Unbounded>()(deserializer, "pathfinding pass classes", m_PathfindingPassClasses); 800 u16 mapW, mapH; 801 deserializer.NumberU16_Unbounded("pathfinder grid w", mapW); 802 deserializer.NumberU16_Unbounded("pathfinder grid h", mapH); 803 m_PassabilityMap = Grid<NavcellData>(mapW, mapH); 804 deserializer.RawBytes("pathfinder grid data", (u8*)m_PassabilityMap.m_Data, mapW*mapH*sizeof(NavcellData)); 805 m_LongPathfinder.Reload(&m_PassabilityMap, m_NonPathfindingPassClasses, m_PathfindingPassClasses); 806 } 807 getPlayerSize()808 int getPlayerSize() 809 { 810 return m_Players.size(); 811 } 812 RegisterSerializablePrototype(std::wstring name,JS::HandleValue proto)813 void RegisterSerializablePrototype(std::wstring name, JS::HandleValue proto) 814 { 815 // Require unique prototype and name (for reverse lookup) 816 // TODO: this is yucky - see comment in Deserialize() 817 ENSURE(proto.isObject() && "A serializable prototype has to be an object!"); 818 819 JSContext* cx = m_ScriptInterface->GetContext(); 820 JSAutoRequest rq(cx); 821 822 JS::RootedObject obj(cx, &proto.toObject()); 823 if (m_SerializablePrototypes->has(obj) || m_DeserializablePrototypes.find(name) != m_DeserializablePrototypes.end()) 824 { 825 LOGERROR("RegisterSerializablePrototype called with same prototype multiple times: p=%p n='%s'", (void *)obj.get(), utf8_from_wstring(name)); 826 return; 827 } 828 m_SerializablePrototypes->add(cx, obj, name); 829 m_DeserializablePrototypes[name] = JS::Heap<JSObject*>(obj); 830 } 831 832 private: Trace(JSTracer * trc,void * data)833 static void Trace(JSTracer *trc, void *data) 834 { 835 reinterpret_cast<CAIWorker*>(data)->TraceMember(trc); 836 } 837 TraceMember(JSTracer * trc)838 void TraceMember(JSTracer *trc) 839 { 840 for (std::pair<const std::wstring, JS::Heap<JSObject*>>& prototype : m_DeserializablePrototypes) 841 JS_CallObjectTracer(trc, &prototype.second, "CAIWorker::m_DeserializablePrototypes"); 842 for (std::pair<const VfsPath, JS::Heap<JS::Value>>& metadata : m_PlayerMetadata) 843 JS_CallValueTracer(trc, &metadata.second, "CAIWorker::m_PlayerMetadata"); 844 } 845 LoadMetadata(const VfsPath & path,JS::MutableHandleValue out)846 void LoadMetadata(const VfsPath& path, JS::MutableHandleValue out) 847 { 848 if (m_PlayerMetadata.find(path) == m_PlayerMetadata.end()) 849 { 850 // Load and cache the AI player metadata 851 m_ScriptInterface->ReadJSONFile(path, out); 852 m_PlayerMetadata[path] = JS::Heap<JS::Value>(out); 853 return; 854 } 855 out.set(m_PlayerMetadata[path].get()); 856 } 857 PerformComputation()858 void PerformComputation() 859 { 860 // Deserialize the game state, to pass to the AI's HandleMessage 861 JSContext* cx = m_ScriptInterface->GetContext(); 862 JSAutoRequest rq(cx); 863 JS::RootedValue state(cx); 864 { 865 PROFILE3("AI compute read state"); 866 m_ScriptInterface->ReadStructuredClone(m_GameState, &state); 867 m_ScriptInterface->SetProperty(state, "passabilityMap", m_PassabilityMapVal, true); 868 m_ScriptInterface->SetProperty(state, "territoryMap", m_TerritoryMapVal, true); 869 } 870 871 // It would be nice to do 872 // m_ScriptInterface->FreezeObject(state.get(), true); 873 // to prevent AI scripts accidentally modifying the state and 874 // affecting other AI scripts they share it with. But the performance 875 // cost is far too high, so we won't do that. 876 // If there is a shared component, run it 877 878 if (m_HasSharedComponent) 879 { 880 PROFILE3("AI run shared component"); 881 m_ScriptInterface->CallFunctionVoid(m_SharedAIObj, "onUpdate", state); 882 } 883 884 for (size_t i = 0; i < m_Players.size(); ++i) 885 { 886 PROFILE3("AI script"); 887 PROFILE2_ATTR("player: %d", m_Players[i]->m_Player); 888 PROFILE2_ATTR("script: %ls", m_Players[i]->m_AIName.c_str()); 889 890 if (m_HasSharedComponent && m_Players[i]->m_UseSharedComponent) 891 m_Players[i]->Run(state, m_Players[i]->m_Player, m_SharedAIObj); 892 else 893 m_Players[i]->Run(state, m_Players[i]->m_Player); 894 } 895 } 896 897 // Take care to keep this declaration before heap rooted members. Destructors of heap rooted 898 // members have to be called before the runtime destructor. 899 shared_ptr<ScriptRuntime> m_ScriptRuntime; 900 901 shared_ptr<ScriptInterface> m_ScriptInterface; 902 boost::rand48 m_RNG; 903 u32 m_TurnNum; 904 905 JS::PersistentRootedValue m_EntityTemplates; 906 bool m_HasLoadedEntityTemplates; 907 908 std::map<VfsPath, JS::Heap<JS::Value> > m_PlayerMetadata; 909 std::vector<shared_ptr<CAIPlayer> > m_Players; // use shared_ptr just to avoid copying 910 911 bool m_HasSharedComponent; 912 JS::PersistentRootedValue m_SharedAIObj; 913 std::vector<SCommandSets> m_Commands; 914 915 std::set<std::wstring> m_LoadedModules; 916 917 shared_ptr<ScriptInterface::StructuredClone> m_GameState; 918 Grid<NavcellData> m_PassabilityMap; 919 JS::PersistentRootedValue m_PassabilityMapVal; 920 Grid<u8> m_TerritoryMap; 921 JS::PersistentRootedValue m_TerritoryMapVal; 922 923 std::map<std::string, pass_class_t> m_NonPathfindingPassClasses; 924 std::map<std::string, pass_class_t> m_PathfindingPassClasses; 925 LongPathfinder m_LongPathfinder; 926 927 bool m_CommandsComputed; 928 929 shared_ptr<ObjectIdCache<std::wstring> > m_SerializablePrototypes; 930 std::map<std::wstring, JS::Heap<JSObject*> > m_DeserializablePrototypes; 931 CTemplateLoader m_TemplateLoader; 932 }; 933 934 935 /** 936 * Implementation of ICmpAIManager. 937 */ 938 class CCmpAIManager : public ICmpAIManager 939 { 940 public: ClassInit(CComponentManager & UNUSED (componentManager))941 static void ClassInit(CComponentManager& UNUSED(componentManager)) 942 { 943 } 944 DEFAULT_COMPONENT_ALLOCATOR(AIManager)945 DEFAULT_COMPONENT_ALLOCATOR(AIManager) 946 947 static std::string GetSchema() 948 { 949 return "<a:component type='system'/><empty/>"; 950 } 951 Init(const CParamNode & UNUSED (paramNode))952 virtual void Init(const CParamNode& UNUSED(paramNode)) 953 { 954 m_TerritoriesDirtyID = 0; 955 m_TerritoriesDirtyBlinkingID = 0; 956 m_JustDeserialized = false; 957 } 958 Deinit()959 virtual void Deinit() 960 { 961 } 962 Serialize(ISerializer & serialize)963 virtual void Serialize(ISerializer& serialize) 964 { 965 serialize.NumberU32_Unbounded("num ais", m_Worker.getPlayerSize()); 966 967 // Because the AI worker uses its own ScriptInterface, we can't use the 968 // ISerializer (which was initialised with the simulation ScriptInterface) 969 // directly. So we'll just grab the ISerializer's stream and write to it 970 // with an independent serializer. 971 972 m_Worker.Serialize(serialize.GetStream(), serialize.IsDebug()); 973 } 974 Deserialize(const CParamNode & paramNode,IDeserializer & deserialize)975 virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) 976 { 977 Init(paramNode); 978 979 u32 numAis; 980 deserialize.NumberU32_Unbounded("num ais", numAis); 981 if (numAis > 0) 982 LoadUsedEntityTemplates(); 983 984 m_Worker.Deserialize(deserialize.GetStream(), numAis); 985 986 m_JustDeserialized = true; 987 } 988 AddPlayer(const std::wstring & id,player_id_t player,u8 difficulty,const std::wstring & behavior)989 virtual void AddPlayer(const std::wstring& id, player_id_t player, u8 difficulty, const std::wstring& behavior) 990 { 991 LoadUsedEntityTemplates(); 992 993 m_Worker.AddPlayer(id, player, difficulty, behavior); 994 995 // AI players can cheat and see through FoW/SoD, since that greatly simplifies 996 // their implementation. 997 // (TODO: maybe cleverer AIs should be able to optionally retain FoW/SoD) 998 CmpPtr<ICmpRangeManager> cmpRangeManager(GetSystemEntity()); 999 if (cmpRangeManager) 1000 cmpRangeManager->SetLosRevealAll(player, true); 1001 } 1002 SetRNGSeed(u32 seed)1003 virtual void SetRNGSeed(u32 seed) 1004 { 1005 m_Worker.SetRNGSeed(seed); 1006 } 1007 TryLoadSharedComponent()1008 virtual void TryLoadSharedComponent() 1009 { 1010 m_Worker.TryLoadSharedComponent(); 1011 } 1012 RunGamestateInit()1013 virtual void RunGamestateInit() 1014 { 1015 const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface(); 1016 JSContext* cx = scriptInterface.GetContext(); 1017 JSAutoRequest rq(cx); 1018 1019 CmpPtr<ICmpAIInterface> cmpAIInterface(GetSystemEntity()); 1020 ENSURE(cmpAIInterface); 1021 1022 // Get the game state from AIInterface 1023 // We flush events from the initialization so we get a clean state now. 1024 JS::RootedValue state(cx); 1025 cmpAIInterface->GetFullRepresentation(&state, true); 1026 1027 // Get the passability data 1028 Grid<NavcellData> dummyGrid; 1029 const Grid<NavcellData>* passabilityMap = &dummyGrid; 1030 CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); 1031 if (cmpPathfinder) 1032 passabilityMap = &cmpPathfinder->GetPassabilityGrid(); 1033 1034 // Get the territory data 1035 // Since getting the territory grid can trigger a recalculation, we check NeedUpdateAI first 1036 Grid<u8> dummyGrid2; 1037 const Grid<u8>* territoryMap = &dummyGrid2; 1038 CmpPtr<ICmpTerritoryManager> cmpTerritoryManager(GetSystemEntity()); 1039 if (cmpTerritoryManager && cmpTerritoryManager->NeedUpdateAI(&m_TerritoriesDirtyID, &m_TerritoriesDirtyBlinkingID)) 1040 territoryMap = &cmpTerritoryManager->GetTerritoryGrid(); 1041 1042 LoadPathfinderClasses(state); 1043 std::map<std::string, pass_class_t> nonPathfindingPassClassMasks, pathfindingPassClassMasks; 1044 if (cmpPathfinder) 1045 cmpPathfinder->GetPassabilityClasses(nonPathfindingPassClassMasks, pathfindingPassClassMasks); 1046 1047 m_Worker.RunGamestateInit(scriptInterface.WriteStructuredClone(state), *passabilityMap, *territoryMap, nonPathfindingPassClassMasks, pathfindingPassClassMasks); 1048 } 1049 StartComputation()1050 virtual void StartComputation() 1051 { 1052 PROFILE("AI setup"); 1053 1054 const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface(); 1055 JSContext* cx = scriptInterface.GetContext(); 1056 JSAutoRequest rq(cx); 1057 1058 if (m_Worker.getPlayerSize() == 0) 1059 return; 1060 1061 CmpPtr<ICmpAIInterface> cmpAIInterface(GetSystemEntity()); 1062 ENSURE(cmpAIInterface); 1063 1064 // Get the game state from AIInterface 1065 JS::RootedValue state(cx); 1066 if (m_JustDeserialized) 1067 cmpAIInterface->GetFullRepresentation(&state, false); 1068 else 1069 cmpAIInterface->GetRepresentation(&state); 1070 LoadPathfinderClasses(state); // add the pathfinding classes to it 1071 1072 // Update the game state 1073 m_Worker.UpdateGameState(scriptInterface.WriteStructuredClone(state)); 1074 1075 // Update the pathfinding data 1076 CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); 1077 if (cmpPathfinder) 1078 { 1079 const GridUpdateInformation& dirtinessInformations = cmpPathfinder->GetAIPathfinderDirtinessInformation(); 1080 1081 if (dirtinessInformations.dirty || m_JustDeserialized) 1082 { 1083 const Grid<NavcellData>& passabilityMap = cmpPathfinder->GetPassabilityGrid(); 1084 1085 std::map<std::string, pass_class_t> nonPathfindingPassClassMasks, pathfindingPassClassMasks; 1086 cmpPathfinder->GetPassabilityClasses(nonPathfindingPassClassMasks, pathfindingPassClassMasks); 1087 1088 m_Worker.UpdatePathfinder(passabilityMap, 1089 dirtinessInformations.globallyDirty, dirtinessInformations.dirtinessGrid, m_JustDeserialized, 1090 nonPathfindingPassClassMasks, pathfindingPassClassMasks); 1091 } 1092 1093 cmpPathfinder->FlushAIPathfinderDirtinessInformation(); 1094 } 1095 1096 // Update the territory data 1097 // Since getting the territory grid can trigger a recalculation, we check NeedUpdateAI first 1098 CmpPtr<ICmpTerritoryManager> cmpTerritoryManager(GetSystemEntity()); 1099 if (cmpTerritoryManager && (cmpTerritoryManager->NeedUpdateAI(&m_TerritoriesDirtyID, &m_TerritoriesDirtyBlinkingID) || m_JustDeserialized)) 1100 { 1101 const Grid<u8>& territoryMap = cmpTerritoryManager->GetTerritoryGrid(); 1102 m_Worker.UpdateTerritoryMap(territoryMap); 1103 } 1104 1105 m_Worker.StartComputation(); 1106 1107 m_JustDeserialized = false; 1108 } 1109 PushCommands()1110 virtual void PushCommands() 1111 { 1112 std::vector<CAIWorker::SCommandSets> commands; 1113 m_Worker.GetCommands(commands); 1114 1115 CmpPtr<ICmpCommandQueue> cmpCommandQueue(GetSystemEntity()); 1116 if (!cmpCommandQueue) 1117 return; 1118 1119 const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface(); 1120 JSContext* cx = scriptInterface.GetContext(); 1121 JSAutoRequest rq(cx); 1122 JS::RootedValue clonedCommandVal(cx); 1123 1124 for (size_t i = 0; i < commands.size(); ++i) 1125 { 1126 for (size_t j = 0; j < commands[i].commands.size(); ++j) 1127 { 1128 scriptInterface.ReadStructuredClone(commands[i].commands[j], &clonedCommandVal); 1129 cmpCommandQueue->PushLocalCommand(commands[i].player, clonedCommandVal); 1130 } 1131 } 1132 } 1133 1134 private: 1135 size_t m_TerritoriesDirtyID; 1136 size_t m_TerritoriesDirtyBlinkingID; 1137 1138 bool m_JustDeserialized; 1139 1140 /** 1141 * Load the templates of all entities on the map (called when adding a new AI player for a new game 1142 * or when deserializing) 1143 */ LoadUsedEntityTemplates()1144 void LoadUsedEntityTemplates() 1145 { 1146 if (m_Worker.HasLoadedEntityTemplates()) 1147 return; 1148 1149 CmpPtr<ICmpTemplateManager> cmpTemplateManager(GetSystemEntity()); 1150 ENSURE(cmpTemplateManager); 1151 1152 std::vector<std::string> templateNames = cmpTemplateManager->FindUsedTemplates(); 1153 std::vector<std::pair<std::string, const CParamNode*> > usedTemplates; 1154 usedTemplates.reserve(templateNames.size()); 1155 for (const std::string& name : templateNames) 1156 { 1157 const CParamNode* node = cmpTemplateManager->GetTemplateWithoutValidation(name); 1158 if (node) 1159 usedTemplates.emplace_back(name, node); 1160 } 1161 // Send the data to the worker 1162 m_Worker.LoadEntityTemplates(usedTemplates); 1163 } 1164 LoadPathfinderClasses(JS::HandleValue state)1165 void LoadPathfinderClasses(JS::HandleValue state) 1166 { 1167 CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); 1168 if (!cmpPathfinder) 1169 return; 1170 1171 const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface(); 1172 JSContext* cx = scriptInterface.GetContext(); 1173 JSAutoRequest rq(cx); 1174 1175 JS::RootedValue classesVal(cx); 1176 scriptInterface.Eval("({})", &classesVal); 1177 1178 std::map<std::string, pass_class_t> classes; 1179 cmpPathfinder->GetPassabilityClasses(classes); 1180 for (std::map<std::string, pass_class_t>::iterator it = classes.begin(); it != classes.end(); ++it) 1181 scriptInterface.SetProperty(classesVal, it->first.c_str(), it->second, true); 1182 1183 scriptInterface.SetProperty(state, "passabilityClasses", classesVal, true); 1184 } 1185 1186 CAIWorker m_Worker; 1187 }; 1188 1189 REGISTER_COMPONENT_TYPE(AIManager) 1190