1 /* Copyright (C) 2007 The SpringLobby Team. All rights reserved. */
2 
3 #include "unitsync.h"
4 
5 #include <algorithm>
6 #include <cmath>
7 #include <stdexcept>
8 #include <clocale>
9 #include <set>
10 #include <fstream>
11 #include <boost/algorithm/string.hpp>
12 #include <boost/foreach.hpp>
13 #include <boost/format.hpp>
14 #include <boost/filesystem.hpp>
15 #include <iterator>
16 
17 #include "c_api.h"
18 #include "image.h"
19 
20 #include <lslutils/config.h>
21 #include <lslutils/debug.h>
22 #include <lslutils/conversion.h>
23 #include <lslutils/misc.h>
24 #include <lslutils/globalsmanager.h>
25 
26 #define LOCK_UNITSYNC boost::mutex::scoped_lock lock_criticalsection(m_lock)
27 
28 #define ASSERT_EXCEPTION(cond,msg) do { if (!(cond)) { LSL_THROW( unitsync, msg ); } } while (0)
29 
30 namespace LSL {
31 
Unitsync()32 Unitsync::Unitsync():
33 	m_cache_thread( new WorkerThread ),
34 	m_map_image_cache( 3, "m_map_image_cache" ),         // may take about 3M per image ( 1024x1024 24 bpp minimap )
35 	m_tiny_minimap_cache( 200, "m_tiny_minimap_cache" ), // takes at most 30k per image (   100x100 24 bpp minimap )
36 	m_mapinfo_cache( 1000000, "m_mapinfo_cache" ),       // this one is just misused as thread safe std::map ...
37 	m_sides_cache( 200, "m_sides_cache" )                // another misuse
38 {
39 }
40 
41 
42 enum ASYNC_EVENTS {
43 	ASYNC_MAP_IMAGE_EVT = 1,
44 	ASYNC_MAP_IMAGE_SCALED_EVT,
45 	ASYNC_MAP_EX_EVT,
46 	ASYNC_UNITSYNC_LOADED_EVT,
47 };
48 
~Unitsync()49 Unitsync::~Unitsync()
50 {
51 	delete m_cache_thread;
52 }
53 
CompareStringNoCase(const std::string & first,const std::string & second)54 bool CompareStringNoCase(const std::string& first, const std::string& second)
55 {
56 	static std::locale l("C");
57 	static boost::is_iless il(l);
58 	return first < second;
59 }
60 
LoadUnitSyncLib(const std::string & unitsyncloc)61 bool Unitsync::LoadUnitSyncLib( const std::string& unitsyncloc )
62 {
63 	LOCK_UNITSYNC;
64 	bool ret = _LoadUnitSyncLib( unitsyncloc );
65 	if (ret)
66 	{
67 		m_cache_path = LSL::Util::config().GetCachePath().string();
68 		PopulateArchiveList();
69 	}
70 	return ret;
71 }
72 
PopulateArchiveList()73 void Unitsync::PopulateArchiveList()
74 {
75 	m_maps_list.clear();
76 	m_mods_list.clear();
77 	m_mod_array.clear();
78 	m_map_array.clear();
79 	m_unsorted_mod_array.clear();
80 	m_unsorted_map_array.clear();
81 	m_map_image_cache.Clear();
82 	m_mapinfo_cache.Clear();
83 	m_maps_unchained_hash.clear();
84 	m_mods_unchained_hash.clear();
85 	m_shortname_to_name_map.clear();
86 
87 	int numMaps = susynclib().GetMapCount();
88 	for ( int i = 0; i < numMaps; i++ )
89 	{
90 		std::string name, hash, archivename, unchainedhash;
91 		try
92 		{
93 			name = susynclib().GetMapName( i );
94 			hash = susynclib().GetMapChecksum( i );
95 			int count = susynclib().GetMapArchiveCount( i );
96 			if ( count > 0 )
97 			{
98 				archivename =  susynclib().GetMapArchiveName( 0 );
99 				unchainedhash = susynclib().GetArchiveChecksum( archivename );
100 			}
101 			//PrefetchMap( name ); // DEBUG
102 		} catch (...) { continue; }
103 		try
104 		{
105 			m_maps_list[name] = hash;
106 			if ( !unchainedhash.empty() ) m_maps_unchained_hash[name] = unchainedhash;
107 			if ( !archivename.empty() ) m_maps_archive_name[name] = archivename;
108 			assert(!name.empty());
109 			m_map_array.push_back( name );
110 		} catch (...)
111 		{
112 			LslError( "Found map with hash collision: %s hash: %s", name.c_str(), hash.c_str() );
113 		}
114 	}
115 	int numMods = susynclib().GetPrimaryModCount();
116 	for ( int i = 0; i < numMods; i++ )
117 	{
118 		std::string name, hash, archivename, unchainedhash;
119 		try
120 		{
121 			name = susynclib().GetPrimaryModName( i );
122 			hash = susynclib().GetPrimaryModChecksum( i );
123 			int count = susynclib().GetPrimaryModArchiveCount( i );
124 			if ( count > 0 )
125 			{
126 				archivename = susynclib().GetPrimaryModArchive( i );
127 				unchainedhash = susynclib().GetArchiveChecksum( archivename );
128 			}
129 		} catch (...) { continue; }
130 		try
131 		{
132 			m_mods_list[name] = hash;
133 			if ( !unchainedhash.empty() )  m_mods_unchained_hash[name] = unchainedhash;
134 			if ( !archivename.empty() ) m_mods_archive_name[name] = archivename;
135 			m_mod_array.push_back( name );
136 			m_shortname_to_name_map[
137 					std::make_pair(susynclib().GetPrimaryModShortName( i ),
138 								   susynclib().GetPrimaryModVersion( i )) ] = name;
139 		} catch (...)
140 		{
141 			LslError( "Found game with hash collision: %s hash: %s", name.c_str(), hash.c_str() );
142 		}
143 	}
144 	m_unsorted_mod_array = m_mod_array;
145 	m_unsorted_map_array = m_map_array;
146 	std::sort( m_map_array.begin(), m_map_array.end() , &CompareStringNoCase );
147 	std::sort( m_mod_array.begin(), m_mod_array.end() , &CompareStringNoCase  );
148 }
149 
150 
151 
_LoadUnitSyncLib(const std::string & unitsyncloc)152 bool Unitsync::_LoadUnitSyncLib( const std::string& unitsyncloc )
153 {
154 	try {
155 		susynclib().Load( unitsyncloc);
156 	} catch (...) {
157 		return false;
158 	}
159 	return true;
160 }
161 
162 
FreeUnitSyncLib()163 void Unitsync::FreeUnitSyncLib()
164 {
165 	LOCK_UNITSYNC;
166 
167 	susynclib().Unload();
168 }
169 
170 
IsLoaded() const171 bool Unitsync::IsLoaded() const
172 {
173 	return susynclib().IsLoaded();
174 }
175 
176 
GetSpringVersion() const177 std::string Unitsync::GetSpringVersion() const
178 {
179 
180 	std::string ret;
181 	try
182 	{
183 		ret = susynclib().GetSpringVersion();
184 	}
185 	catch (...){}
186 	return ret;
187 }
188 
189 
VersionSupports(GameFeature feature) const190 bool Unitsync::VersionSupports( GameFeature feature ) const
191 {
192 	return susynclib().VersionSupports( feature );
193 }
194 
195 
GetNumMods() const196 int Unitsync::GetNumMods() const
197 {
198 
199 	return m_mod_array.size();
200 }
201 
202 
GetModList() const203 StringVector Unitsync::GetModList() const
204 {
205 	return m_mod_array;
206 }
207 
208 
GetModIndex(const std::string & name) const209 int Unitsync::GetModIndex( const std::string& name ) const
210 {
211 	return Util::IndexInSequence( m_mod_array, name );
212 }
213 
214 
ModExists(const std::string & modname) const215 bool Unitsync::ModExists( const std::string& modname ) const
216 {
217 	return (m_mods_list.find(modname) != m_mods_list.end());
218 }
219 
220 
ModExists(const std::string & modname,const std::string & hash) const221 bool Unitsync::ModExists( const std::string& modname, const std::string& hash ) const
222 {
223 	LocalArchivesVector::const_iterator itor = m_mods_list.find(modname);
224 	if ( itor == m_mods_list.end() ) return false;
225 	return itor->second == hash;
226 }
227 
ModExistsCheckHash(const std::string & hash) const228 bool Unitsync::ModExistsCheckHash( const std::string& hash ) const
229 {
230 	LocalArchivesVector::const_iterator itor = m_mods_list.begin();
231 	for ( ; itor != m_mods_list.end(); ++itor ) {
232 		if ( itor->second == hash )
233 			return true;
234 	}
235 	return false;
236 }
237 
GetMod(const std::string & modname)238 UnitsyncMod Unitsync::GetMod( const std::string& modname )
239 {
240 	UnitsyncMod m;
241 	m.name = modname;
242 	m.hash = m_mods_list[modname];
243 	return m;
244 }
245 
246 
GetMod(int index)247 UnitsyncMod Unitsync::GetMod( int index )
248 {
249 	UnitsyncMod m;
250 	m.name = m_mod_array[index];
251 	m.hash = m_mods_list[m.name];
252 	return m;
253 }
254 
GetNumMaps() const255 int Unitsync::GetNumMaps() const
256 {
257 	return m_map_array.size();
258 }
259 
GetMapList() const260 StringVector Unitsync::GetMapList() const
261 {
262 	return m_map_array;
263 }
264 
GetModValidMapList(const std::string & modname) const265 StringVector Unitsync::GetModValidMapList( const std::string& modname ) const
266 {
267     StringVector ret;
268 	try {
269 		unsigned int mapcount = susynclib().GetValidMapCount( modname );
270 		for ( unsigned int i = 0; i < mapcount; i++ )
271 			ret.push_back( susynclib().GetValidMapName( i ) );
272 	} catch ( Exceptions::unitsync& e ) {}
273 	return ret;
274 }
275 
MapExists(const std::string & mapname) const276 bool Unitsync::MapExists( const std::string& mapname ) const
277 {
278 	return (m_maps_list.find(mapname) != m_maps_list.end());
279 }
280 
MapExists(const std::string & mapname,const std::string & hash) const281 bool Unitsync::MapExists( const std::string& mapname, const std::string& hash ) const
282 {
283 	LocalArchivesVector::const_iterator itor = m_maps_list.find(mapname);
284 	if ( itor == m_maps_list.end() ) return false;
285 	return itor->second == hash;
286 }
287 
GetMap(const std::string & mapname)288 UnitsyncMap Unitsync::GetMap( const std::string& mapname )
289 {
290 	UnitsyncMap m;
291 	m.name = mapname;
292 	m.hash = m_maps_list[mapname];
293 	return m;
294 }
295 
GetMap(int index)296 UnitsyncMap Unitsync::GetMap( int index )
297 {
298 	UnitsyncMap m;
299 	m.name = m_map_array[index];
300 	m.hash = m_maps_list[m.name];
301 	return m;
302 }
303 
GetMapEx(int index)304 UnitsyncMap Unitsync::GetMapEx( int index )
305 {
306 	UnitsyncMap m;
307 	if ( index < 0 )
308 		return m;
309 	m.name = m_map_array[index];
310 	m.hash = m_maps_list[m.name];
311 	m.info = _GetMapInfoEx( m.name );
312 	return m;
313 }
314 
GetOptionEntry(const int i,GameOptions & ret)315 void GetOptionEntry(const int i, GameOptions& ret)
316 {
317 	//all section values for options are converted to lower case
318 	//since usync returns the key of section type keys lower case
319 	//otherwise comapring would be a real hassle
320 	const std::string key = susynclib().GetOptionKey(i);
321 	const std::string name = susynclib().GetOptionName(i);
322 	const std::string section_str = boost::algorithm::to_lower_copy( susynclib().GetOptionSection(i) );
323 	switch (susynclib().GetOptionType(i))
324 	{
325 	case Enum::opt_float:
326 	{
327 		ret.float_map[key] = mmOptionFloat( name, key,
328 											susynclib().GetOptionDesc(i), susynclib().GetOptionNumberDef(i),
329 											susynclib().GetOptionNumberStep(i),
330 											susynclib().GetOptionNumberMin(i), susynclib().GetOptionNumberMax(i),
331 											section_str, susynclib().GetOptionStyle(i) );
332 		break;
333 	}
334 	case Enum::opt_bool:
335 	{
336 		ret.bool_map[key] = mmOptionBool( name, key,
337 										  susynclib().GetOptionDesc(i), susynclib().GetOptionBoolDef(i),
338 										  section_str, susynclib().GetOptionStyle(i) );
339 		break;
340 	}
341 	case Enum::opt_string:
342 	{
343 		ret.string_map[key] = mmOptionString( name, key,
344 											  susynclib().GetOptionDesc(i), susynclib().GetOptionStringDef(i),
345 											  susynclib().GetOptionStringMaxLen(i),
346 											  section_str, susynclib().GetOptionStyle(i) );
347 		break;
348 	}
349 	case Enum::opt_list:
350 	{
351 		ret.list_map[key] = mmOptionList(name,key,
352 										 susynclib().GetOptionDesc(i),susynclib().GetOptionListDef(i),
353 										 section_str,susynclib().GetOptionStyle(i));
354 
355 		int listItemCount = susynclib().GetOptionListCount(i);
356 		for (int j = 0; j < listItemCount; ++j)
357 		{
358 			std::string descr = susynclib().GetOptionListItemDesc(i,j);
359 			ret.list_map[key].addItem(susynclib().GetOptionListItemKey(i,j),susynclib().GetOptionListItemName(i,j), descr);
360 		}
361 		break;
362 	}
363 	case Enum::opt_section:
364 	{
365 		ret.section_map[key] = mmOptionSection( name, key, susynclib().GetOptionDesc(i),
366 												section_str, susynclib().GetOptionStyle(i) );
367 	}
368 	}
369 }
370 
371 
GetMapOptions(const std::string & name)372 GameOptions Unitsync::GetMapOptions( const std::string& name )
373 {
374 	GameOptions ret;
375 	int count = susynclib().GetMapOptionCount(name);
376 	for (int i = 0; i < count; ++i)
377 	{
378 		GetOptionEntry( i, ret );
379 	}
380 	return ret;
381 }
382 
GetMapDeps(const std::string & mapname)383 StringVector Unitsync::GetMapDeps( const std::string& mapname )
384 {
385 	StringVector ret;
386 	try
387 	{
388 		ret = susynclib().GetMapDeps( Util::IndexInSequence( m_unsorted_map_array, mapname ) );
389 	}
390 	catch( Exceptions::unitsync& u ) {}
391 	return ret;
392 }
393 
GetMapEx(const std::string & mapname)394 UnitsyncMap Unitsync::GetMapEx( const std::string& mapname )
395 {
396 	const int i = GetMapIndex( mapname );
397 	if( i < 0 )
398 		LSL_THROW( unitsync, "Map does not exist");
399 	return GetMapEx( i );
400 }
401 
GetMapIndex(const std::string & name) const402 int Unitsync::GetMapIndex( const std::string& name ) const
403 {
404 	return Util::IndexInSequence( m_map_array, name );
405 }
406 
GetModOptions(const std::string & name)407 GameOptions Unitsync::GetModOptions( const std::string& name )
408 {
409 	GameOptions ret;
410 	int count = susynclib().GetModOptionCount(name);
411 	for (int i = 0; i < count; ++i)
412 	{
413 		GetOptionEntry(i, ret );
414 	}
415 	return ret;
416 }
417 
GetModCustomizations(const std::string & modname)418 GameOptions Unitsync::GetModCustomizations( const std::string& modname )
419 {
420 	GameOptions ret;
421 	int count = susynclib().GetCustomOptionCount( modname, "LobbyOptions.lua" );
422 	for (int i = 0; i < count; ++i) {
423 		GetOptionEntry(i, ret );
424 	}
425 	return ret;
426 }
427 
GetSkirmishOptions(const std::string & modname,const std::string & skirmish_name)428 GameOptions Unitsync::GetSkirmishOptions( const std::string& modname, const std::string& skirmish_name )
429 {
430 	GameOptions ret;
431 	int count = susynclib().GetCustomOptionCount( modname, skirmish_name );
432 	for (int i = 0; i < count; ++i) {
433 		GetOptionEntry(i, ret );
434 	}
435 	return ret;
436 }
437 
GetModDeps(const std::string & modname) const438 StringVector Unitsync::GetModDeps( const std::string& modname ) const
439 {
440 	StringVector ret;
441 	try
442 	{
443 		ret = susynclib().GetModDeps( Util::IndexInSequence( m_unsorted_mod_array, modname ) );
444 	}
445 	catch( Exceptions::unitsync& u ) {}
446 	return ret;
447 }
448 
GetSides(const std::string & modname)449 StringVector Unitsync::GetSides( const std::string& modname )
450 {
451 	StringVector ret;
452 	if (( ! m_sides_cache.TryGet( modname, ret) ) && (ModExists(modname))){
453 		try {
454 			ret = susynclib().GetSides( modname );
455 			m_sides_cache.Add( modname, ret );
456 		} catch( Exceptions::unitsync& u ) {}
457 	}
458 	return ret;
459 }
460 
461 
GetSidePicture(const std::string & modname,const std::string & SideName) const462 UnitsyncImage Unitsync::GetSidePicture( const std::string& modname, const std::string& SideName ) const
463 {
464 	std::string ImgName("SidePics");
465 	ImgName += "/";
466 	ImgName += boost::to_lower_copy( SideName );
467 	try {
468 		return GetImage( modname, ImgName + ".png", false );
469 	}
470 	catch ( Exceptions::unitsync& u ){}
471 	return GetImage( modname, ImgName + ".bmp", true );
472 }
473 
GetImage(const std::string & modname,const std::string & image_path,bool useWhiteAsTransparent) const474 UnitsyncImage Unitsync::GetImage( const std::string& modname, const std::string& image_path, bool useWhiteAsTransparent  ) const
475 {
476 	susynclib().SetCurrentMod( modname );
477 	int ini = susynclib().OpenFileVFS ( image_path );
478 	if( !ini )
479 		LSL_THROW( unitsync, "cannot find image");
480 	int FileSize = susynclib().FileSizeVFS(ini);
481 	if (FileSize == 0) {
482 		susynclib().CloseFileVFS(ini);
483 		LSL_THROW( unitsync, "image has size 0" );
484 	}
485 	Util::uninitialized_array<char> FileContent(FileSize);
486 	susynclib().ReadFileVFS(ini, FileContent, FileSize);
487 	return UnitsyncImage::FromVfsFileData( FileContent, FileSize, image_path, useWhiteAsTransparent );
488 }
489 
GetAIList(const std::string & modname) const490 StringVector Unitsync::GetAIList( const std::string& modname ) const
491 {
492     StringVector ret;
493 	if ( usync().VersionSupports( USYNC_GetSkirmishAI ) )
494 	{
495 		int total = susynclib().GetSkirmishAICount( modname );
496 		for ( int i = 0; i < total; i++ )
497 		{
498             StringVector infos = susynclib().GetAIInfo( i );
499 			const int namepos = Util::IndexInSequence( infos, "shortName");
500 			const int versionpos = Util::IndexInSequence( infos, "version");
501 			std::string ainame;
502 			if ( namepos != lslNotFound ) ainame += infos[namepos +1];
503 			if ( versionpos != lslNotFound ) ainame += " " + infos[versionpos +1];
504 			ret.push_back( ainame );
505 		}
506 	}
507 	else
508 	{
509 		// list dynamic link libraries
510         const StringVector dlllist = susynclib().FindFilesVFS( Util::Lib::CanonicalizeName("AI/Bot-libs/*", Util::Lib::Module ) );
511 		for( int i = 0; i < long(dlllist.size()); i++ )
512 		{
513 			if ( Util::IndexInSequence( ret, Util::BeforeLast( dlllist[i], "/" ) ) == lslNotFound )
514 				ret.push_back ( dlllist[i] ); // don't add duplicates //TODO(koshi) make ret a set instead :)
515 		}
516 		// list jar files (java AIs)
517         const StringVector jarlist = susynclib().FindFilesVFS("AI/Bot-libs/*.jar");
518 		for( int i = 0; i < long(jarlist.size()); i++ )
519 		{
520 			if ( Util::IndexInSequence( ret, Util::BeforeLast( jarlist[i], "/" ) ) == lslNotFound )
521 				ret.push_back ( jarlist[i] ); // don't add duplicates //TODO(koshi) make ret a set instead :)
522 		}
523 	}
524 	//std::sort(ret.begin(), ret.end());
525 	return ret;
526 }
527 
UnSetCurrentMod()528 void Unitsync::UnSetCurrentMod()
529 {
530 	try
531 	{
532 		susynclib().UnSetCurrentMod();
533 	} catch( std::runtime_error ) {}
534 }
535 
GetAIInfos(int index) const536 StringVector Unitsync::GetAIInfos( int index ) const
537 {
538     StringVector ret;
539 	try
540 	{
541 		ret = susynclib().GetAIInfo( index );
542 	}
543 	catch ( std::runtime_error ) {}
544 	return ret;
545 }
546 
GetAIOptions(const std::string & modname,int index)547 GameOptions Unitsync::GetAIOptions( const std::string& modname, int index )
548 {
549 	GameOptions ret;
550 	int count = susynclib().GetAIOptionCount(modname, index);
551 	for (int i = 0; i < count; ++i)
552 	{
553 		GetOptionEntry(i, ret );
554 	}
555 	return ret;
556 }
557 
GetNumUnits(const std::string & modname) const558 int Unitsync::GetNumUnits( const std::string& modname ) const
559 {
560 	susynclib().AddAllArchives( susynclib().GetPrimaryModArchive( Util::IndexInSequence( m_unsorted_mod_array, modname ) ) );
561 	susynclib().ProcessUnitsNoChecksum();
562 	return susynclib().GetUnitCount();
563 }
564 
GetUnitsList(const std::string & modname)565 StringVector Unitsync::GetUnitsList( const std::string& modname )
566 {
567 	StringVector cache = GetCacheFile( GetFileCachePath( modname, "", true ) + ".units" );
568 	if (cache.empty()) {
569 		susynclib().SetCurrentMod( modname );
570 		while ( susynclib().ProcessUnitsNoChecksum() > 0 ) {}
571 		const int unitcount = susynclib().GetUnitCount();
572 		for ( int i = 0; i < unitcount; i++ )
573 		{
574 			cache.push_back( susynclib().GetFullUnitName(i) + " (" + susynclib().GetUnitName(i) + ")" );
575 		}
576 		SetCacheFile( GetFileCachePath( modname, "", true ) + ".units", cache );
577 	}
578 	return cache;
579 }
580 
GetMinimap(const std::string & mapname)581 UnitsyncImage Unitsync::GetMinimap( const std::string& mapname )
582 {
583 	return _GetMapImage( mapname, ".minimap.png", &UnitsyncLib::GetMinimap );
584 }
585 
GetMinimap(const std::string & mapname,int width,int height)586 UnitsyncImage Unitsync::GetMinimap( const std::string& mapname, int width, int height )
587 {
588 	const bool tiny = ( width <= 100 && height <= 100 );
589 	UnitsyncImage img;
590 	if ( tiny && m_tiny_minimap_cache.TryGet( mapname, img ) )
591 	{
592 		lslSize image_size = lslSize(img.GetWidth(), img.GetHeight()).MakeFit( lslSize(width, height) );
593 		if ( image_size.GetWidth() != img.GetWidth() || image_size.GetHeight() != img.GetHeight() )
594 			img.Rescale( image_size.GetWidth(), image_size.GetHeight() );
595 
596 		return img;
597 	}
598 
599 	img = GetMinimap( mapname );
600 	// special resizing code because minimap is always square,
601 	// and we need to resize it to the correct aspect ratio.
602 	if (img.GetWidth() > 1 && img.GetHeight() > 1)
603 	{
604 		try {
605 			MapInfo mapinfo = _GetMapInfoEx( mapname );
606 
607 			lslSize image_size = lslSize(mapinfo.width, mapinfo.height).MakeFit( lslSize(width, height) );
608 			img.Rescale( image_size.GetWidth(), image_size.GetHeight() );
609 		}
610 		catch (...) {
611 			img = UnitsyncImage( 1, 1 );
612 		}
613 	}
614 	if ( tiny )
615 		m_tiny_minimap_cache.Add( mapname, img );
616 	return img;
617 }
618 
GetMetalmap(const std::string & mapname)619 UnitsyncImage Unitsync::GetMetalmap( const std::string& mapname )
620 {
621 	return _GetMapImage( mapname, ".metalmap.png", &UnitsyncLib::GetMetalmap );
622 }
623 
GetMetalmap(const std::string & mapname,int width,int height)624 UnitsyncImage Unitsync::GetMetalmap( const std::string& mapname, int width, int height )
625 {
626 	return _GetScaledMapImage( mapname, &Unitsync::GetMetalmap, width, height );
627 }
628 
GetHeightmap(const std::string & mapname)629 UnitsyncImage Unitsync::GetHeightmap( const std::string& mapname )
630 {
631 	return _GetMapImage( mapname, ".heightmap.png", &UnitsyncLib::GetHeightmap );
632 }
633 
GetHeightmap(const std::string & mapname,int width,int height)634 UnitsyncImage Unitsync::GetHeightmap( const std::string& mapname, int width, int height )
635 {
636 	return _GetScaledMapImage( mapname, &Unitsync::GetHeightmap, width, height );
637 }
638 
_GetMapImage(const std::string & mapname,const std::string & imagename,UnitsyncImage (UnitsyncLib::* loadMethod)(const std::string &))639 UnitsyncImage Unitsync::_GetMapImage( const std::string& mapname, const std::string& imagename, UnitsyncImage (UnitsyncLib::*loadMethod)(const std::string&) )
640 {
641 	UnitsyncImage img;
642 	if ( m_map_image_cache.TryGet( mapname + imagename, img ) )
643 		return img;
644 
645 	std::string originalsizepath = GetFileCachePath( mapname, m_maps_unchained_hash[mapname], false ) + imagename;
646 	if (FileExists(originalsizepath)) {
647 		img = UnitsyncImage( originalsizepath );
648 	}
649 
650 	if (!img.isValid()) { //image seems invalid, recreate
651 		try {
652 		//convert and save
653 		img = (susynclib().*loadMethod)( mapname );
654 		img.Save( originalsizepath );
655 		} catch (...) { //we failed horrible, use dummy image
656 			//dummy image
657 			img = UnitsyncImage( 1, 1 );
658 		}
659 	}
660 	m_map_image_cache.Add( mapname + imagename, img );
661 	return img;
662 }
663 
_GetScaledMapImage(const std::string & mapname,UnitsyncImage (Unitsync::* loadMethod)(const std::string &),int width,int height)664 UnitsyncImage Unitsync::_GetScaledMapImage( const std::string& mapname, UnitsyncImage (Unitsync::*loadMethod)(const std::string&), int width, int height )
665 {
666 	UnitsyncImage img = (this->*loadMethod) ( mapname );
667 	if (img.GetWidth() > 1 && img.GetHeight() > 1)
668 	{
669 		lslSize image_size = lslSize(img.GetWidth(), img.GetHeight()).MakeFit( lslSize(width, height) );
670 		img.Rescale( image_size.GetWidth(), image_size.GetHeight() );
671 	}
672 	return img;
673 }
674 
_GetMapInfoEx(const std::string & mapname)675 MapInfo Unitsync::_GetMapInfoEx( const std::string& mapname )
676 {
677 	MapInfo info;
678 	info.width = 1;
679 	info.height = 1;
680 	if ( m_mapinfo_cache.TryGet( mapname, info ) )
681 		return info;
682 
683 	StringVector cache;
684 	cache = GetCacheFile( GetFileCachePath( mapname, m_maps_unchained_hash[mapname], false ) + ".infoex" );
685 	if (cache.size()>=11) {
686 			info.author = cache[0];
687 			info.tidalStrength =  Util::FromString<long>( cache[1] );
688 			info.gravity = Util::FromString<long>( cache[2] );
689 			info.maxMetal = Util::FromString<double>( cache[3] );
690 			info.extractorRadius = Util::FromString<double>( cache[4] );
691 			info.minWind = Util::FromString<long>( cache[5] );
692 			info.maxWind = Util::FromString<long>( cache[6] );
693 			info.width = Util::FromString<long>( cache[7] );
694 			info.height = Util::FromString<long>( cache[8] );
695 			const StringVector posinfo = Util::StringTokenize( cache[9], " ");
696 			BOOST_FOREACH( const std::string pos, posinfo )
697 			{
698 				StartPos position;
699 				position.x = Util::FromString<long>( Util::BeforeFirst( pos,  "-" ) );
700 				position.y = Util::FromString<long>( Util::AfterFirst( pos, "-" ) );
701 				info.positions.push_back( position );
702 			}
703 			const unsigned int LineCount = cache.size();
704 			for ( unsigned int i = 10; i < LineCount; i++ )
705 				info.description += cache[i] + "\n";
706 	} else {
707 			const int index = Util::IndexInSequence( m_unsorted_map_array, mapname);
708 			ASSERT_EXCEPTION(index>=0, "Map not found");
709 
710 			info = susynclib().GetMapInfoEx( index, 1 );
711 
712 			cache.push_back ( info.author );
713 			cache.push_back( Util::ToString( info.tidalStrength ) );
714 			cache.push_back( Util::ToString( info.gravity ) );
715 			cache.push_back( Util::ToString( info.maxMetal ) );
716 			cache.push_back( Util::ToString( info.extractorRadius ) );
717 			cache.push_back( Util::ToString( info.minWind ) );
718 			cache.push_back( Util::ToString( info.maxWind )  );
719 			cache.push_back( Util::ToString( info.width ) );
720 			cache.push_back( Util::ToString( info.height ) );
721 
722 			std::string postring;
723 			for ( unsigned int i = 0; i < info.positions.size(); i++)
724 			{
725 				postring += Util::ToString( info.positions[i].x ) + "-" + Util::ToString( info.positions[i].y ) + " ";
726 			}
727 			cache.push_back( postring );
728 
729 			const StringVector descrtokens = Util::StringTokenize( info.description, "\n" );
730 			BOOST_FOREACH( const std::string descrtoken, descrtokens ) {
731 				cache.push_back( descrtoken );
732 			}
733 			SetCacheFile( GetFileCachePath( mapname, m_maps_unchained_hash[mapname], false ) + ".infoex", cache );
734 	}
735 
736 	m_mapinfo_cache.Add( mapname, info );
737 
738 	return info;
739 }
740 
FindFilesVFS(const std::string & pattern) const741 StringVector Unitsync::FindFilesVFS( const std::string& pattern ) const
742 {
743 	return susynclib().FindFilesVFS( pattern );
744 }
745 
ReloadUnitSyncLib()746 bool Unitsync::ReloadUnitSyncLib()
747 {
748 	//FIXME: use async call
749 	//LoadUnitSyncLibAsync(LSL::Util::config().GetCurrentUsedUnitSync().string());
750 	const std::string path = LSL::Util::config().GetCurrentUsedUnitSync().string();
751 	if (path.empty())
752 		return false;
753 	LoadUnitSyncLib(path);
754 	return true;
755 }
756 
757 
SetSpringDataPath(const std::string & path)758 void Unitsync::SetSpringDataPath( const std::string& path )
759 {
760 	susynclib().SetSpringConfigString( "SpringData", path );
761 }
762 
GetSpringDataPath(std::string & path)763 bool Unitsync::GetSpringDataPath(std::string& path)
764 {
765   path = susynclib().GetSpringDataDir();
766   return true;
767 }
768 
GetFileCachePath(const std::string & name,const std::string & hash,bool IsMod)769 std::string Unitsync::GetFileCachePath( const std::string& name, const std::string& hash, bool IsMod )
770 {
771 	std::string ret = m_cache_path;
772 	if ( !name.empty() )
773 		ret += name;
774 	else
775 		return std::string();
776 	if ( !hash.empty() )
777 		ret += "-" + hash;
778 	else
779 	{
780 		if ( IsMod )
781 			ret += "-" + m_mods_list[name];
782 		else
783 		{
784 			ret += "-" + m_maps_list[name];
785 		}
786 	}
787 	return ret;
788 }
789 
GetCacheFile(const std::string & path) const790 StringVector Unitsync::GetCacheFile( const std::string& path ) const
791 {
792 	StringVector ret;
793 	std::ifstream file( path.c_str() );
794 	if (!file.good())
795 		return ret;
796 	std::string line;
797 	while(std::getline(file,line))
798 	{
799 		ret.push_back( line );
800 	}
801 	return ret;
802 }
803 
SetCacheFile(const std::string & path,const StringVector & data)804 void Unitsync::SetCacheFile( const std::string& path, const StringVector& data )
805 {
806 	std::ofstream file( path.c_str(), std::ios::trunc );
807 	ASSERT_EXCEPTION( file.good() , (boost::format( "cache file( %s ) not found" ) % path).str() );
808 	BOOST_FOREACH( const std::string line, data )
809 	{
810 		file << line << std::endl;
811 	}
812 	file.flush();
813 	file.close();
814 }
815 
GetPlaybackList(bool ReplayType) const816 StringVector  Unitsync::GetPlaybackList( bool ReplayType ) const
817 {
818 	StringVector ret;
819 	if ( !IsLoaded() ) return ret;
820 	std::string type;
821 	std::string subpath;
822 	if ( ReplayType ) {
823 		type = ".sdf";
824 		subpath = "demos";
825 	} else {
826 		type = ".ssf";
827 		subpath = "Saves";
828 	}
829 	const int count = susynclib().GetSpringDataDirCount();
830 	for(int i=0; i<count; i++) {
831 		const std::string dir = susynclib().GetSpringDataDirByIndex(i) + subpath;
832 		if (!boost::filesystem::is_directory(dir))
833 			continue;
834 		boost::filesystem::directory_iterator enditer;
835 		for( boost::filesystem::directory_iterator dir_iter(dir) ; dir_iter != enditer; ++dir_iter) {
836 			if (!boost::filesystem::is_regular_file(dir_iter->status()))
837 				continue;
838 			const std::string filename(dir_iter->path().string());
839 			if (filename.substr(filename.length()-4) != type) // compare file ending
840 				continue;
841 			ret.push_back(filename);
842 		}
843 	}
844 	return ret;
845 }
846 
FileExists(const std::string & name) const847 bool Unitsync::FileExists( const std::string& name ) const
848 {
849 	int handle = susynclib().OpenFileVFS(name);
850 	if ( handle == 0 ) return false;
851 	susynclib().CloseFileVFS(handle);
852 	return true;
853 }
854 
GetArchivePath(const std::string & name) const855 std::string Unitsync::GetArchivePath( const std::string& name ) const
856 {
857 	return susynclib().GetArchivePath( name );
858 }
859 
GetScreenshotFilenames() const860 StringVector Unitsync::GetScreenshotFilenames() const
861 {
862 	if ( !IsLoaded() )
863         return StringVector();
864 
865     StringVector ret = susynclib().FindFilesVFS( "screenshots/*.*" );
866 	std::set<std::string> ret_set ( ret.begin(), ret.end() );
867 //	for ( int i = 0; i < long(ret.size() - 1); ++i ) {
868 //		if ( ret[i] == ret[i+1] )
869 //			ret.RemoveAt( i+1 );
870 //	}
871     ret = StringVector ( ret_set.begin(), ret_set.end() );
872 	std::sort( ret.begin(), ret.end() );
873 	return ret;
874 }
875 
GetDefaultNick()876 std::string Unitsync::GetDefaultNick()
877 {
878 	std::string name = susynclib().GetSpringConfigString( "name", "Player" );
879 	if ( name.empty() ) {
880 		susynclib().SetSpringConfigString( "name", "Player" );
881 		return "Player";
882 	}
883 	return name;
884 }
885 
SetDefaultNick(const std::string & nick)886 void Unitsync::SetDefaultNick( const std::string& nick )
887 {
888 	susynclib().SetSpringConfigString( "name", nick );
889 }
890 
891 ////////////////////////////////////////////////////////////////////////////////
892 ////////////////////////////// Unitsync prefetch/background thread code
893 
894 namespace
895 {
896 typedef UnitsyncImage (Unitsync::*LoadMethodPtr)(const std::string&);
897 typedef UnitsyncImage (Unitsync::*ScaledLoadMethodPtr)(const std::string&, int, int);
898 
899 class CacheMapWorkItem : public WorkItem
900 {
901 public:
902 	Unitsync* m_usync;
903 	std::string m_mapname;
904 	LoadMethodPtr m_loadMethod;
905 
Run()906 	void Run()
907 	{
908 		(m_usync->*m_loadMethod)( m_mapname );
909 	}
910 
CacheMapWorkItem(Unitsync * usync,const std::string & mapname,LoadMethodPtr loadMethod)911 	CacheMapWorkItem( Unitsync* usync, const std::string& mapname, LoadMethodPtr loadMethod )
912 		: m_usync(usync), m_mapname(mapname.c_str()), m_loadMethod(loadMethod) {}
913 };
914 
915 class CacheMinimapWorkItem : public WorkItem
916 {
917 public:
918 	std::string m_mapname;
919 
Run()920 	void Run()
921 	{
922 		// Fetch rescaled minimap using this specialized class instead of
923 		// CacheMapWorkItem with a pointer to Unitsync::GetMinimap,
924 		// to ensure Unitsync::_GetMapInfoEx will be called too, and
925 		// hence it's data cached.
926 
927 		// This reduces main thread blocking while waiting for WorkerThread
928 		// to release it's lock while e.g. scrolling through battle list.
929 
930 		// 98x98 because battle list map preview is 98x98
931 		usync().GetMinimap( m_mapname, 98, 98 );
932 	}
933 
CacheMinimapWorkItem(const std::string & mapname)934 	CacheMinimapWorkItem( const std::string& mapname )
935 		: m_mapname(mapname.c_str()) {}
936 };
937 
938 class GetMapImageAsyncResult : public WorkItem // TODO: rename
939 {
940 public:
Run()941 	void Run()
942 	{
943 		try
944 		{
945 			RunCore();
946 		}
947 		catch (...)
948 		{
949 			// Event without mapname means some async job failed.
950 			// This is sufficient for now, we just need symmetry between
951 			// number of initiated async jobs and number of finished/failed
952 			// async jobs.
953 			m_mapname = std::string();
954 		}
955 		PostEvent();
956 	}
957 
958 protected:
959 	Unitsync* m_usync;
960 	std::string m_mapname;
961 	int m_evtId;
962 
PostEvent()963 	void PostEvent()
964 	{
965 		m_usync->PostEvent( m_mapname );
966 	}
967 
968 	virtual void RunCore() = 0;
969 
GetMapImageAsyncResult(Unitsync * usync,const std::string & mapname,int evtId)970 	GetMapImageAsyncResult( Unitsync* usync, const std::string& mapname, int evtId ):
971 		m_usync(usync),
972 		m_mapname(mapname.c_str()),
973 		m_evtId(evtId)
974 	{}
975 };
976 
977 class GetMapImageAsyncWorkItem : public GetMapImageAsyncResult
978 {
979 public:
RunCore()980 	void RunCore()
981 	{
982 		(m_usync->*m_loadMethod)( m_mapname );
983 	}
984 
985 	LoadMethodPtr m_loadMethod;
986 
GetMapImageAsyncWorkItem(Unitsync * usync,const std::string & mapname,LoadMethodPtr loadMethod)987 	GetMapImageAsyncWorkItem( Unitsync* usync, const std::string& mapname, LoadMethodPtr loadMethod )
988 		: GetMapImageAsyncResult( usync, mapname, ASYNC_MAP_IMAGE_EVT), m_loadMethod(loadMethod) {}
989 };
990 
991 class GetScaledMapImageAsyncWorkItem : public GetMapImageAsyncResult
992 {
993 public:
RunCore()994 	void RunCore()
995 	{
996 		(m_usync->*m_loadMethod)( m_mapname, m_width, m_height );
997 	}
998 
999 	int m_width;
1000 	int m_height;
1001 	ScaledLoadMethodPtr m_loadMethod;
1002 
GetScaledMapImageAsyncWorkItem(Unitsync * usync,const std::string & mapname,int w,int h,ScaledLoadMethodPtr loadMethod)1003 	GetScaledMapImageAsyncWorkItem( Unitsync* usync, const std::string& mapname, int w, int h, ScaledLoadMethodPtr loadMethod )
1004 		: GetMapImageAsyncResult( usync, mapname, ASYNC_MAP_IMAGE_SCALED_EVT), m_width(w), m_height(h), m_loadMethod(loadMethod) {}
1005 };
1006 
1007 class GetMapExAsyncWorkItem : public GetMapImageAsyncResult
1008 {
1009 public:
RunCore()1010 	void RunCore()
1011 	{
1012 		m_usync->GetMapEx( m_mapname );
1013 	}
1014 
GetMapExAsyncWorkItem(Unitsync * usync,const std::string & mapname)1015 	GetMapExAsyncWorkItem( Unitsync* usync, const std::string& mapname )
1016 		: GetMapImageAsyncResult( usync, mapname, ASYNC_MAP_EX_EVT ) {}
1017 };
1018 }
1019 
1020 
1021 class LoadUnitSyncLibAsyncWorkItem: public WorkItem
1022 {
1023 public:
Run()1024 	void Run()
1025 	{
1026 		try
1027 		{
1028 			m_usync->LoadUnitSyncLib(m_unitsyncloc);
1029 		}
1030 		catch (...)
1031 		{
1032 			// Event without mapname means some async job failed.
1033 			// This is sufficient for now, we just need symmetry between
1034 			// number of initiated async jobs and number of finished/failed
1035 			// async jobs.
1036 
1037 		}
1038 		m_usync->PostEvent( "" );
1039 	}
1040 
1041 private:
1042 	Unitsync* m_usync;
1043 	std::string m_unitsyncloc;
1044 	int m_evtId;
1045 public:
LoadUnitSyncLibAsyncWorkItem(Unitsync * usync,const std::string & unitsyncLoc)1046 	LoadUnitSyncLibAsyncWorkItem( Unitsync* usync, const std::string& unitsyncLoc):
1047 		m_usync(usync),
1048 		m_unitsyncloc(unitsyncLoc.c_str()),
1049 		m_evtId(ASYNC_UNITSYNC_LOADED_EVT)
1050 	{}
1051 };
1052 
1053 
PrefetchMap(const std::string & mapname)1054 void Unitsync::PrefetchMap( const std::string& mapname )
1055 {
1056 	// Use a simple hash based on 3 characters from the mapname
1057 	// (without '.smf') as negative priority for the WorkItems.
1058 	// This ensures WorkItems for the same map are put together,
1059 	// which improves caching performance.
1060 
1061 	// Measured improvement: 60% more cache hits while populating replay tab.
1062 	// 50% hits without, 80% hits with this code.  (cache size 20 images)
1063 
1064 	const int length = std::max(0, int(mapname.length()) - 4);
1065 	const int hash = ( mapname[length * 1/4] << 16 )
1066 			| ( mapname[length * 2/4] << 8  )
1067 			| mapname[length * 3/4];
1068 	const int priority = -hash;
1069 
1070 	if (! m_cache_thread )
1071 	{
1072 		LslDebug( "cache thread not initialized %s", "PrefetchMap" );
1073 		return;
1074 	}
1075 	{
1076 		CacheMinimapWorkItem* work;
1077 
1078 		work = new CacheMinimapWorkItem( mapname );
1079 		m_cache_thread->DoWork( work, priority );
1080 	}
1081 	{
1082 		CacheMapWorkItem* work;
1083 
1084 		work = new CacheMapWorkItem( this, mapname, &Unitsync::GetMetalmap );
1085 		m_cache_thread->DoWork( work, priority );
1086 
1087 		work = new CacheMapWorkItem( this, mapname, &Unitsync::GetHeightmap );
1088 		m_cache_thread->DoWork( work, priority );
1089 	}
1090 }
1091 
RegisterEvtHandler(const StringSignalSlotType & handler)1092 boost::signals2::connection Unitsync::RegisterEvtHandler( const StringSignalSlotType& handler )
1093 {
1094     return m_async_ops_complete_sig.connect( handler );
1095 }
1096 
UnregisterEvtHandler(boost::signals2::connection & conn)1097 void Unitsync::UnregisterEvtHandler(boost::signals2::connection &conn )
1098 {
1099     conn.disconnect();
1100 }
1101 
PostEvent(const std::string & evt)1102 void Unitsync::PostEvent( const std::string& evt )
1103 {
1104 	m_async_ops_complete_sig( evt );
1105 }
1106 
_GetMapImageAsync(const std::string & mapname,UnitsyncImage (Unitsync::* loadMethod)(const std::string &))1107 void Unitsync::_GetMapImageAsync( const std::string& mapname, UnitsyncImage (Unitsync::*loadMethod)(const std::string&) )
1108 {
1109 	if (mapname.empty())
1110 		return;
1111 	if (! m_cache_thread )
1112 	{
1113 		LslDebug( "cache thread not initialised -- %s", mapname.c_str() );
1114 		return;
1115 	}
1116 	GetMapImageAsyncWorkItem* work;
1117 	work = new GetMapImageAsyncWorkItem( this, mapname, loadMethod );
1118 	m_cache_thread->DoWork( work, 100 );
1119 }
1120 
GetMinimapAsync(const std::string & mapname)1121 void Unitsync::GetMinimapAsync( const std::string& mapname )
1122 {
1123 	_GetMapImageAsync( mapname, &Unitsync::GetMinimap );
1124 }
1125 
GetMinimapAsync(const std::string & mapname,int width,int height)1126 void Unitsync::GetMinimapAsync( const std::string& mapname, int width, int height )
1127 {
1128 	if (mapname.empty())
1129 		return;
1130 	if (! m_cache_thread )
1131 	{
1132 		LslError( "cache thread not initialised" );
1133 		return;
1134 	}
1135 	GetScaledMapImageAsyncWorkItem* work;
1136 	work = new GetScaledMapImageAsyncWorkItem( this, mapname, width, height, &Unitsync::GetMinimap );
1137 	m_cache_thread->DoWork( work, 100 );
1138 }
1139 
GetMetalmapAsync(const std::string & mapname)1140 void Unitsync::GetMetalmapAsync( const std::string& mapname )
1141 {
1142 	_GetMapImageAsync( mapname, &Unitsync::GetMetalmap );
1143 }
1144 
GetMetalmapAsync(const std::string & mapname,int,int)1145 void Unitsync::GetMetalmapAsync( const std::string& mapname, int /*width*/, int /*height*/ )
1146 {
1147 	GetMetalmapAsync( mapname );
1148 }
1149 
GetHeightmapAsync(const std::string & mapname)1150 void Unitsync::GetHeightmapAsync( const std::string& mapname )
1151 {
1152 	_GetMapImageAsync( mapname, &Unitsync::GetHeightmap );
1153 }
1154 
GetHeightmapAsync(const std::string & mapname,int,int)1155 void Unitsync::GetHeightmapAsync( const std::string& mapname, int /*width*/, int /*height*/ )
1156 {
1157 	GetHeightmapAsync( mapname );
1158 }
1159 
GetMapExAsync(const std::string & mapname)1160 void Unitsync::GetMapExAsync( const std::string& mapname )
1161 {
1162 	if (mapname.empty())
1163 		return;
1164 
1165 	if (! m_cache_thread )
1166 	{
1167 		LslDebug( "cache thread not initialized %s", "GetMapExAsync" );
1168 		return;
1169 	}
1170 	GetMapExAsyncWorkItem* work;
1171 	work = new GetMapExAsyncWorkItem( this, mapname );
1172 	m_cache_thread->DoWork( work, 200 /* higher prio then GetMinimapAsync */ );
1173 }
1174 
GetTextfileAsString(const std::string & modname,const std::string & file_path)1175 std::string Unitsync::GetTextfileAsString( const std::string& modname, const std::string& file_path )
1176 {
1177 	susynclib().SetCurrentMod( modname );
1178 
1179 	int ini = susynclib().OpenFileVFS ( file_path );
1180 	if ( !ini )
1181 		return std::string();
1182 	int FileSize = susynclib().FileSizeVFS(ini);
1183 	if (FileSize == 0) {
1184 		susynclib().CloseFileVFS(ini);
1185 		return std::string();
1186 	}
1187 	Util::uninitialized_array<char> FileContent(FileSize);
1188 	susynclib().ReadFileVFS(ini, FileContent, FileSize);
1189 	return std::string( FileContent, size_t( FileSize ) );
1190 }
1191 
GetNameForShortname(const std::string & shortname,const std::string & version) const1192 std::string Unitsync::GetNameForShortname( const std::string& shortname, const std::string& version) const
1193 {
1194 	ShortnameVersionToNameMap::const_iterator it
1195 			=  m_shortname_to_name_map.find( std::make_pair(shortname,version) );
1196 	if ( it != m_shortname_to_name_map.end() )
1197 		return it->second;
1198 	return std::string();
1199 }
1200 
usync()1201 Unitsync& usync() {
1202     static LSL::Util::LineInfo<Unitsync> m( AT );
1203     static LSL::Util::GlobalObjectHolder<Unitsync, LSL::Util::LineInfo<Unitsync> > m_sync( m );
1204 	return m_sync;
1205 }
1206 
LoadUnitSyncLibAsync(const std::string & filename)1207 void Unitsync::LoadUnitSyncLibAsync(const std::string& filename) {
1208 	LoadUnitSyncLibAsyncWorkItem* work = new LoadUnitSyncLibAsyncWorkItem( this, filename);
1209 	m_cache_thread->DoWork( work, 500 );
1210 }
1211 
1212 } // namespace LSL
1213