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