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