1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 #include "AILibraryManager.h"
4 
5 #include "Interface/aidefines.h"
6 #include "Interface/SAIInterfaceLibrary.h"
7 #include "AIInterfaceLibraryInfo.h"
8 #include "AIInterfaceLibrary.h"
9 #include "SkirmishAILibraryInfo.h"
10 #include "SkirmishAIData.h"
11 
12 #include "System/Util.h"
13 #include "System/Log/ILog.h"
14 #include "System/Platform/errorhandler.h"
15 #include "System/Platform/SharedLib.h"
16 #include "System/FileSystem/FileHandler.h"
17 #include "System/FileSystem/DataDirsAccess.h"
18 #include "System/FileSystem/FileQueryFlags.h"
19 #include "System/FileSystem/FileSystem.h"
20 #include "Sim/Misc/GlobalConstants.h"
21 #include "Sim/Misc/Team.h"
22 #include "Sim/Misc/TeamHandler.h"
23 
24 #include <string>
25 #include <set>
26 #include <sstream>
27 #include <limits.h>
28 
29 
CAILibraryManager()30 CAILibraryManager::CAILibraryManager() {
31 
32 	ClearAllInfos();
33 	GatherInterfaceLibrariesInfos();
34 	GatherSkirmishAIsLibrariesInfos();
35 }
36 
GatherInterfaceLibrariesInfos()37 void CAILibraryManager::GatherInterfaceLibrariesInfos() {
38 
39 	typedef std::vector<std::string> T_dirs;
40 
41 	// cause we use CFileHandler for searching files,
42 	// we are automatically searching in all data-dirs
43 
44 	// Read from AI Interface info files
45 	// we are looking for:
46 	// {AI_INTERFACES_DATA_DIR}/{*}/{*}/InterfaceInfo.lua
47 	T_dirs aiInterfaceDataDirs =
48 			dataDirsAccess.FindDirsInDirectSubDirs(AI_INTERFACES_DATA_DIR);
49 	typedef std::map<const AIInterfaceKey, std::set<std::string> > T_dupInt;
50 	T_dupInt duplicateInterfaceInfoCheck;
51 	for (T_dirs::iterator dir = aiInterfaceDataDirs.begin();
52 			dir != aiInterfaceDataDirs.end(); ++dir) {
53 		const std::string& possibleDataDir = *dir;
54 		T_dirs infoFiles =
55 				CFileHandler::FindFiles(possibleDataDir, "InterfaceInfo.lua");
56 		if (!infoFiles.empty()) { // interface info is available
57 			const std::string& infoFile = infoFiles.at(0);
58 
59 			// generate and store the interface info
60 			CAIInterfaceLibraryInfo* interfaceInfo =
61 					new CAIInterfaceLibraryInfo(infoFile);
62 
63 			interfaceInfo->SetDataDir(FileSystem::EnsureNoPathSepAtEnd(possibleDataDir));
64 			interfaceInfo->SetDataDirCommon(
65 					FileSystem::GetParent(possibleDataDir) + "common");
66 
67 			AIInterfaceKey interfaceKey = interfaceInfo->GetKey();
68 
69 			interfaceKeys.insert(interfaceKey);
70 			if (interfaceInfos.find(interfaceKey) == interfaceInfos.end()) {
71 				// no interface info with this key yet -> store it
72 				interfaceInfos[interfaceKey] = interfaceInfo;
73 			} else {
74 				// duplicate interface info -> free
75 				delete interfaceInfo;
76 				interfaceInfo = NULL;
77 			}
78 
79 			// for debug-info, in case one interface is specified multiple times
80 			duplicateInterfaceInfoCheck[interfaceKey].insert(infoFile);
81 		}
82 	}
83 
84 	// filter out interfaces that are specified multiple times
85 	for (T_dupInt::const_iterator info = duplicateInterfaceInfoCheck.begin();
86 			info != duplicateInterfaceInfoCheck.end(); ++info) {
87 		if (info->second.size() >= 2) {
88 			duplicateInterfaceInfos[info->first] = info->second;
89 
90 			if (LOG_IS_ENABLED(L_ERROR)) {
91 				LOG_L(L_ERROR, "Duplicate AI Interface Info found:");
92 				LOG_L(L_ERROR, "\tfor interface: %s %s",
93 						info->first.GetShortName().c_str(),
94 						info->first.GetVersion().c_str());
95 				LOG_L(L_ERROR, "\tin files:");
96 				const std::string* lastDir = NULL;
97 				std::set<std::string>::const_iterator dir;
98 				for (dir = info->second.begin(); dir != info->second.end(); ++dir) {
99 					LOG_L(L_ERROR, "\t%s", dir->c_str());
100 					lastDir = &(*dir);
101 				}
102 				LOG_L(L_ERROR, "\tusing: %s", lastDir->c_str());
103 			}
104 		}
105 	}
106 }
107 
GatherSkirmishAIsLibrariesInfos()108 void CAILibraryManager::GatherSkirmishAIsLibrariesInfos() {
109 
110 	T_dupSkirm duplicateSkirmishAIInfoCheck;
111 	GatherSkirmishAIsLibrariesInfosFromLuaFiles(duplicateSkirmishAIInfoCheck);
112 	GatherSkirmishAIsLibrariesInfosFromInterfaceLibrary(duplicateSkirmishAIInfoCheck);
113 	FilterDuplicateSkirmishAILibrariesInfos(duplicateSkirmishAIInfoCheck);
114 }
115 
StoreSkirmishAILibraryInfos(T_dupSkirm duplicateSkirmishAIInfoCheck,CSkirmishAILibraryInfo * skirmishAIInfo,const std::string & sourceDesc)116 void CAILibraryManager::StoreSkirmishAILibraryInfos(T_dupSkirm duplicateSkirmishAIInfoCheck, CSkirmishAILibraryInfo* skirmishAIInfo, const std::string& sourceDesc) {
117 
118 	skirmishAIInfo->SetLuaAI(false);
119 
120 	SkirmishAIKey aiKey = skirmishAIInfo->GetKey();
121 	AIInterfaceKey interfaceKey =
122 			FindFittingInterfaceSpecifier(
123 					skirmishAIInfo->GetInterfaceShortName(),
124 					skirmishAIInfo->GetInterfaceVersion(),
125 					interfaceKeys);
126 	if (!interfaceKey.IsUnspecified()) {
127 		SkirmishAIKey skirmishAIKey = SkirmishAIKey(aiKey, interfaceKey);
128 		skirmishAIKeys.insert(skirmishAIKey);
129 		if (skirmishAIInfos.find(skirmishAIKey) == skirmishAIInfos.end()) {
130 			// no AI info with this key yet -> store it
131 			skirmishAIInfos[skirmishAIKey] = skirmishAIInfo;
132 		} else {
133 			// duplicate AI info -> free
134 			delete skirmishAIInfo;
135 			skirmishAIInfo = NULL;
136 		}
137 
138 		// for debug-info, in case one AI is specified multiple times
139 		duplicateSkirmishAIInfoCheck[skirmishAIKey].insert(sourceDesc);
140 	} else {
141 		LOG_L(L_ERROR,
142 				"Required AI Interface for Skirmish AI %s %s not found.",
143 				skirmishAIInfo->GetShortName().c_str(),
144 				skirmishAIInfo->GetVersion().c_str());
145 		delete skirmishAIInfo;
146 		skirmishAIInfo = NULL;
147 	}
148 }
149 
GatherSkirmishAIsLibrariesInfosFromLuaFiles(T_dupSkirm duplicateSkirmishAIInfoCheck)150 void CAILibraryManager::GatherSkirmishAIsLibrariesInfosFromLuaFiles(T_dupSkirm duplicateSkirmishAIInfoCheck) {
151 
152 	typedef std::vector<std::string> T_dirs;
153 
154 	// Read from Skirmish AI info and option files
155 	// we are looking for:
156 	// {SKIRMISH_AI_DATA_DIR}/{*}/{*}/AIInfo.lua
157 	// {SKIRMISH_AI_DATA_DIR}/{*}/{*}/AIOptions.lua
158 	T_dirs skirmishAIDataDirs = dataDirsAccess.FindDirsInDirectSubDirs(SKIRMISH_AI_DATA_DIR);
159 	for (T_dirs::iterator dir = skirmishAIDataDirs.begin();
160 			dir != skirmishAIDataDirs.end(); ++dir)
161 	{
162 		const std::string& possibleDataDir = *dir;
163 		T_dirs infoFiles = CFileHandler::FindFiles(possibleDataDir,
164 				"AIInfo.lua");
165 		if (!infoFiles.empty()) { // skirmish AI info is available
166 			const std::string& infoFile = infoFiles.at(0);
167 
168 			std::string optionFileName = "";
169 			T_dirs optionFile = CFileHandler::FindFiles(possibleDataDir,
170 					"AIOptions.lua");
171 			if (!optionFile.empty()) {
172 				optionFileName = optionFile.at(0);
173 			}
174 			// generate and store the ai info
175 			CSkirmishAILibraryInfo* skirmishAIInfo =
176 					new CSkirmishAILibraryInfo(infoFile, optionFileName);
177 
178 			skirmishAIInfo->SetDataDir(
179 					FileSystem::EnsureNoPathSepAtEnd(possibleDataDir));
180 			skirmishAIInfo->SetDataDirCommon(
181 					FileSystem::GetParent(possibleDataDir) + "common");
182 
183 			StoreSkirmishAILibraryInfos(duplicateSkirmishAIInfoCheck, skirmishAIInfo, infoFile);
184 		}
185 	}
186 }
187 
GatherSkirmishAIsLibrariesInfosFromInterfaceLibrary(T_dupSkirm duplicateSkirmishAIInfoCheck)188 void CAILibraryManager::GatherSkirmishAIsLibrariesInfosFromInterfaceLibrary(T_dupSkirm duplicateSkirmishAIInfoCheck) {
189 
190 	const T_interfaceInfos& intInfs = GetInterfaceInfos();
191 	T_interfaceInfos::const_iterator intInfIt;
192 	for (intInfIt = intInfs.begin(); intInfIt != intInfs.end(); ++intInfIt) {
193 		// only try to lookup Skirmish AI infos through the Interface library
194 		// if it explicitly states support for this in InterfaceInfo.lua
195 		if (intInfIt->second->IsLookupSupported()) {
196 			const CAIInterfaceLibrary* intLib = FetchInterface(intInfIt->second->GetKey());
197 
198 			const int aiCount = intLib->GetSkirmishAICount();
199 			for (int aii = 0; aii < aiCount; ++aii) {
200 				const std::map<std::string, std::string> rawInfos = intLib->GetSkirmishAIInfos(aii);
201 				const std::string rawLuaOptions = intLib->GetSkirmishAIOptions(aii);
202 
203 				// generate and store the ai info
204 				CSkirmishAILibraryInfo* skirmishAIInfo =
205 						new CSkirmishAILibraryInfo(rawInfos, rawLuaOptions);
206 
207 				// NOTE We do not set the data-dir(s) for interface looked-up
208 				//   AIs. This is the duty of the AI Interface plugin.
209 
210 				StoreSkirmishAILibraryInfos(duplicateSkirmishAIInfoCheck, skirmishAIInfo, intInfIt->first.ToString());
211 			}
212 		}
213 	}
214 }
215 
FilterDuplicateSkirmishAILibrariesInfos(T_dupSkirm duplicateSkirmishAIInfoCheck)216 void CAILibraryManager::FilterDuplicateSkirmishAILibrariesInfos(T_dupSkirm duplicateSkirmishAIInfoCheck) {
217 
218 	// filter out skirmish AIs that are specified multiple times
219 	for (T_dupSkirm::const_iterator info = duplicateSkirmishAIInfoCheck.begin();
220 			info != duplicateSkirmishAIInfoCheck.end(); ++info) {
221 		if (info->second.size() >= 2) {
222 			duplicateSkirmishAIInfos[info->first] = info->second;
223 
224 			if (LOG_IS_ENABLED(L_WARNING)) {
225 				LOG_L(L_WARNING, "Duplicate Skirmish AI Info found:");
226 				LOG_L(L_WARNING, "\tfor Skirmish AI: %s %s", info->first.GetShortName().c_str(),
227 						info->first.GetVersion().c_str());
228 				LOG_L(L_WARNING, "\tin files:");
229 				const std::string* lastDir = NULL;
230 				std::set<std::string>::const_iterator dir;
231 				for (dir = info->second.begin(); dir != info->second.end(); ++dir) {
232 					LOG_L(L_WARNING, "\t%s", dir->c_str());
233 					lastDir = &(*dir);
234 				}
235 				LOG_L(L_WARNING, "\tusing: %s", lastDir->c_str());
236 			}
237 		}
238 	}
239 }
240 
ClearAllInfos()241 void CAILibraryManager::ClearAllInfos() {
242 
243 	IAILibraryManager::T_interfaceInfos::iterator iii;
244 	for (iii = interfaceInfos.begin(); iii != interfaceInfos.end(); ++iii) {
245 		delete iii->second;
246 		iii->second = NULL;
247 	}
248 	interfaceInfos.clear();
249 
250 	IAILibraryManager::T_skirmishAIInfos::iterator sai;
251 	for (sai = skirmishAIInfos.begin(); sai != skirmishAIInfos.end(); ++sai) {
252 		delete sai->second;
253 		sai->second = NULL;
254 	}
255 	skirmishAIInfos.clear();
256 
257 	interfaceKeys.clear();
258 	skirmishAIKeys.clear();
259 
260 	duplicateInterfaceInfos.clear();
261 	duplicateSkirmishAIInfos.clear();
262 }
263 
~CAILibraryManager()264 CAILibraryManager::~CAILibraryManager() {
265 
266 	ReleaseEverything();
267 	ClearAllInfos();
268 }
269 
GetInterfaceKeys() const270 const IAILibraryManager::T_interfaceSpecs& CAILibraryManager::GetInterfaceKeys() const {
271 	return interfaceKeys;
272 }
GetSkirmishAIKeys() const273 const IAILibraryManager::T_skirmishAIKeys& CAILibraryManager::GetSkirmishAIKeys() const {
274 	return skirmishAIKeys;
275 }
276 
GetInterfaceInfos() const277 const IAILibraryManager::T_interfaceInfos& CAILibraryManager::GetInterfaceInfos() const {
278 	return interfaceInfos;
279 }
GetSkirmishAIInfos() const280 const IAILibraryManager::T_skirmishAIInfos& CAILibraryManager::GetSkirmishAIInfos() const {
281 	return skirmishAIInfos;
282 }
283 
GetDuplicateInterfaceInfos() const284 const IAILibraryManager::T_dupInt& CAILibraryManager::GetDuplicateInterfaceInfos() const {
285 	return duplicateInterfaceInfos;
286 }
GetDuplicateSkirmishAIInfos() const287 const IAILibraryManager::T_dupSkirm& CAILibraryManager::GetDuplicateSkirmishAIInfos() const {
288 	return duplicateSkirmishAIInfos;
289 }
290 
291 
FittingSkirmishAIKeys(const SkirmishAIKey & skirmishAIKey) const292 std::vector<SkirmishAIKey> CAILibraryManager::FittingSkirmishAIKeys(
293 		const SkirmishAIKey& skirmishAIKey) const {
294 
295 	std::vector<SkirmishAIKey> applyingKeys;
296 
297 	if (skirmishAIKey.IsUnspecified()) {
298 		return applyingKeys;
299 	}
300 
301 	bool checkVersion = false;
302 	if (skirmishAIKey.GetVersion() != "") {
303 		checkVersion = true;
304 	}
305 
306 	std::set<SkirmishAIKey>::const_iterator sasi;
307 	for (sasi = skirmishAIKeys.begin(); sasi != skirmishAIKeys.end(); ++sasi) {
308 
309 		// check if the ai name fits
310 		if (skirmishAIKey.GetShortName() != sasi->GetShortName()) {
311 			continue;
312 		}
313 
314 		// check if the ai version fits (if one is specified)
315 		if (checkVersion && skirmishAIKey.GetVersion() != sasi->GetVersion()) {
316 			continue;
317 		}
318 
319 		// if the programm raches here, we know that this key fits
320 		applyingKeys.push_back(*sasi);
321 	}
322 
323 	return applyingKeys;
324 }
325 
326 
327 
FetchSkirmishAILibrary(const SkirmishAIKey & skirmishAIKey)328 const CSkirmishAILibrary* CAILibraryManager::FetchSkirmishAILibrary(const SkirmishAIKey& skirmishAIKey) {
329 
330 	const CSkirmishAILibrary* aiLib = NULL;
331 
332 	T_skirmishAIInfos::const_iterator aiInfo = skirmishAIInfos.find(skirmishAIKey);
333 	if (aiInfo == skirmishAIInfos.end()) {
334 		LOG_L(L_ERROR,
335 				"Unknown skirmish AI specified: %s %s",
336 				skirmishAIKey.GetShortName().c_str(),
337 				skirmishAIKey.GetVersion().c_str()
338 				);
339 	} else {
340 		CAIInterfaceLibrary* interfaceLib = FetchInterface(skirmishAIKey.GetInterface());
341 		if ((interfaceLib != NULL) && interfaceLib->IsInitialized()) {
342 			aiLib = interfaceLib->FetchSkirmishAILibrary(*(aiInfo->second));
343 		}
344 	}
345 
346 	return aiLib;
347 }
348 
ReleaseSkirmishAILibrary(const SkirmishAIKey & skirmishAIKey)349 void CAILibraryManager::ReleaseSkirmishAILibrary(const SkirmishAIKey& skirmishAIKey) {
350 
351 	CAIInterfaceLibrary* interfaceLib = FetchInterface(skirmishAIKey.GetInterface());
352 	if ((interfaceLib != NULL) && interfaceLib->IsInitialized()) {
353 		interfaceLib->ReleaseSkirmishAILibrary(skirmishAIKey);
354 		// only releases the library if its load count is 0
355 		ReleaseInterface(skirmishAIKey.GetInterface());
356 	} else {
357 		// Not releasing, because the AI Interface is not initialized,
358 		// and so neither was the AI.
359 	}
360 }
361 
362 
ReleaseEverything()363 void CAILibraryManager::ReleaseEverything() {
364 	T_loadedInterfaces::const_iterator lil;
365 
366 	for (lil = loadedAIInterfaceLibraries.begin(); lil != loadedAIInterfaceLibraries.end(); ++lil) {
367 		CAIInterfaceLibrary* interfaceLib = FetchInterface(lil->first);
368 		if ((interfaceLib != NULL) && interfaceLib->IsInitialized()) {
369 			interfaceLib->ReleaseAllSkirmishAILibraries();
370 			// only releases the library if its load count is 0
371 			ReleaseInterface(lil->first);
372 		}
373 	}
374 }
375 
376 
377 
FetchInterface(const AIInterfaceKey & interfaceKey)378 CAIInterfaceLibrary* CAILibraryManager::FetchInterface(const AIInterfaceKey& interfaceKey) {
379 
380 	CAIInterfaceLibrary* interfaceLib = NULL;
381 
382 	T_loadedInterfaces::const_iterator interfacePos = loadedAIInterfaceLibraries.find(interfaceKey);
383 	if (interfacePos == loadedAIInterfaceLibraries.end()) { // interface not yet loaded
384 		T_interfaceInfos::const_iterator interfaceInfo = interfaceInfos.find(interfaceKey);
385 		if (interfaceInfo != interfaceInfos.end()) {
386 			interfaceLib = new CAIInterfaceLibrary(*(interfaceInfo->second));
387 			if (!interfaceLib->IsInitialized()) {
388 				delete interfaceLib;
389 				interfaceLib = NULL;
390 			}
391 			// storing this for later use, even if it is NULL (failed to init)
392 			loadedAIInterfaceLibraries[interfaceKey] = interfaceLib;
393 		} else {
394 			// unavailable interface requested, returning NULL
395 		}
396 	} else {
397 		interfaceLib = interfacePos->second;
398 	}
399 
400 	return interfaceLib;
401 }
402 
ReleaseInterface(const AIInterfaceKey & interfaceKey)403 void CAILibraryManager::ReleaseInterface(const AIInterfaceKey& interfaceKey) {
404 
405 	T_loadedInterfaces::iterator interfacePos = loadedAIInterfaceLibraries.find(interfaceKey);
406 	if (interfacePos != loadedAIInterfaceLibraries.end()) {
407 		CAIInterfaceLibrary* interfaceLib = interfacePos->second;
408 		if (interfaceLib->GetLoadCount() == 0) {
409 			loadedAIInterfaceLibraries.erase(interfacePos);
410 			delete interfaceLib;
411 			interfaceLib = NULL;
412 		}
413 	}
414 }
415 
416 
FindFittingInterfaceSpecifier(const std::string & shortName,const std::string & minVersion,const IAILibraryManager::T_interfaceSpecs & keys)417 AIInterfaceKey CAILibraryManager::FindFittingInterfaceSpecifier(
418 		const std::string& shortName,
419 		const std::string& minVersion,
420 		const IAILibraryManager::T_interfaceSpecs& keys) {
421 
422 	std::set<AIInterfaceKey>::const_iterator key;
423 	int minDiff = INT_MAX;
424 	AIInterfaceKey fittingKey = AIInterfaceKey(); // unspecified key
425 	for (key = keys.begin(); key != keys.end(); ++key) {
426 		if (shortName == key->GetShortName()) {
427 			int diff = IAILibraryManager::VersionCompare(key->GetVersion(), minVersion);
428 			if (diff >= 0 && diff < minDiff) {
429 				fittingKey = *key;
430 				minDiff = diff;
431 			}
432 		}
433 	}
434 
435 	return fittingKey;
436 }
437