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