2013-05-26 23:57:24 +02:00
/* Copyright (C) 2013 Wildfire Games.
2011-01-12 13:29:00 +01:00
* This file is part of 0 A . D .
*
* 0 A . D . is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 2 of the License , or
* ( at your option ) any later version .
*
* 0 A . D . is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with 0 A . D . If not , see < http : //www.gnu.org/licenses/>.
*/
# include "precompiled.h"
# include "simulation2/system/Component.h"
# include "ICmpAIManager.h"
2011-03-05 02:56:59 +01:00
# include "simulation2/MessageTypes.h"
2011-02-10 17:06:28 +01:00
# include "graphics/Terrain.h"
2011-01-12 13:29:00 +01:00
# include "lib/timer.h"
2011-02-10 17:06:28 +01:00
# include "lib/tex/tex.h"
2011-04-29 21:10:34 +02:00
# include "lib/allocators/shared_ptr.h"
2011-01-12 13:29:00 +01:00
# include "ps/CLogger.h"
# include "ps/Filesystem.h"
2011-02-10 17:06:28 +01:00
# include "ps/Util.h"
2011-01-12 13:29:00 +01:00
# include "simulation2/components/ICmpAIInterface.h"
# include "simulation2/components/ICmpCommandQueue.h"
2011-02-10 17:06:28 +01:00
# include "simulation2/components/ICmpObstructionManager.h"
2011-06-29 01:24:42 +02:00
# include "simulation2/components/ICmpRangeManager.h"
2011-01-12 13:29:00 +01:00
# include "simulation2/components/ICmpTemplateManager.h"
2013-03-05 23:52:48 +01:00
# include "simulation2/components/ICmpTechnologyTemplateManager.h"
2011-10-16 04:55:58 +02:00
# include "simulation2/components/ICmpTerritoryManager.h"
2011-02-10 17:06:28 +01:00
# include "simulation2/helpers/Grid.h"
2011-01-12 13:29:00 +01:00
# include "simulation2/serialization/DebugSerializer.h"
# include "simulation2/serialization/StdDeserializer.h"
# include "simulation2/serialization/StdSerializer.h"
# include "simulation2/serialization/SerializeTemplates.h"
/**
2012-03-01 04:55:05 +01:00
* @ file
* Player AI interface .
2011-01-12 13:29:00 +01:00
* AI is primarily scripted , and the CCmpAIManager component defined here
* takes care of managing all the scripts .
*
* To avoid slow AI scripts causing jerky rendering , they are run in a background
* thread ( maintained by CAIWorker ) so that it ' s okay if they take a whole simulation
* turn before returning their results ( though preferably they shouldn ' t use nearly
* that much CPU ) .
*
* CCmpAIManager grabs the world state after each turn ( making use of AIInterface . js
* and AIProxy . js to decide what data to include ) then passes it to CAIWorker .
* The AI scripts will then run asynchronously and return a list of commands to execute .
* Any attempts to read the command list ( including indirectly via serialization )
* will block until it ' s actually completed , so the rest of the engine should avoid
* reading it for as long as possible .
*
2011-01-16 00:35:20 +01:00
* JS values are passed between the game and AI threads using ScriptInterface : : StructuredClone .
*
2011-01-12 13:29:00 +01:00
* TODO : actually the thread isn ' t implemented yet , because performance hasn ' t been
* sufficiently problematic to justify the complexity yet , but the CAIWorker interface
* is designed to hopefully support threading when we want it .
*/
2012-03-01 04:55:05 +01:00
/**
* Implements worker thread for CCmpAIManager .
*/
2011-01-12 13:29:00 +01:00
class CAIWorker
{
private :
2011-01-16 00:35:20 +01:00
class CAIPlayer
2011-01-12 13:29:00 +01:00
{
2011-01-16 00:35:20 +01:00
NONCOPYABLE ( CAIPlayer ) ;
public :
2013-03-13 21:44:48 +01:00
CAIPlayer ( CAIWorker & worker , const std : : wstring & aiName , player_id_t player , uint8_t difficulty ,
2013-12-30 11:04:59 +01:00
shared_ptr < ScriptInterface > scriptInterface ) :
m_Worker ( worker ) , m_AIName ( aiName ) , m_Player ( player ) , m_Difficulty ( difficulty ) , m_ScriptInterface ( scriptInterface )
2011-01-16 00:35:20 +01:00
{
}
~ CAIPlayer ( )
{
// Clean up rooted objects before destroying their script context
m_Obj = CScriptValRooted ( ) ;
2011-07-17 01:24:14 +02:00
m_Commands . clear ( ) ;
2011-01-16 00:35:20 +01:00
}
2014-08-02 15:22:02 +02:00
bool Initialise ( )
2011-01-16 00:35:20 +01:00
{
2013-12-30 11:04:59 +01:00
// LoadScripts will only load each script once even though we call it for each player
if ( ! m_Worker . LoadScripts ( m_AIName ) )
2011-01-16 00:35:20 +01:00
return false ;
2014-07-27 00:33:16 +02:00
JSContext * cx = m_ScriptInterface - > GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
2011-01-16 00:35:20 +01:00
2011-03-23 14:36:20 +01:00
OsPath path = L " simulation/ai/ " + m_AIName + L " /data.json " ;
2014-08-03 00:21:50 +02:00
JS : : RootedValue metadata ( cx ) ;
m_Worker . LoadMetadata ( path , & metadata ) ;
2014-07-27 00:33:16 +02:00
if ( metadata . isUndefined ( ) )
2011-01-16 00:35:20 +01:00
{
2011-03-23 14:36:20 +01:00
LOGERROR ( L " Failed to create AI player: can't find %ls " , path . string ( ) . c_str ( ) ) ;
2011-01-16 00:35:20 +01:00
return false ;
}
// Get the constructor name from the metadata
2013-12-30 11:04:59 +01:00
// If the AI doesn't use modules, we look for the constructor in the global object
// TODO: All AIs should use modules. Remove the condition if this requirement is met.
std : : string moduleName ;
2011-01-16 00:35:20 +01:00
std : : string constructor ;
2014-07-27 00:33:16 +02:00
JS : : RootedValue objectWithConstructor ( cx ) ; // object that should contain the constructor function
JS : : RootedValue global ( cx , m_ScriptInterface - > GetGlobalObject ( ) ) ;
2014-08-02 18:30:15 +02:00
JS : : RootedValue ctor ( cx ) ;
2014-07-27 00:33:16 +02:00
if ( ! m_ScriptInterface - > HasProperty ( metadata , " moduleName " ) )
2013-12-30 11:04:59 +01:00
{
2014-07-27 00:33:16 +02:00
objectWithConstructor . set ( m_ScriptInterface - > GetGlobalObject ( ) ) ;
2013-12-30 11:04:59 +01:00
}
else
{
2014-07-27 00:33:16 +02:00
m_ScriptInterface - > GetProperty ( metadata , " moduleName " , moduleName ) ;
if ( ! m_ScriptInterface - > GetProperty ( global , moduleName . c_str ( ) , & objectWithConstructor ) | | objectWithConstructor . isUndefined ( ) )
2013-12-30 11:04:59 +01:00
{
LOGERROR ( L " Failed to create AI player: %ls: can't find the module that should contain the constructor: '%hs' " , path . string ( ) . c_str ( ) , moduleName . c_str ( ) ) ;
return false ;
}
}
2014-07-27 00:33:16 +02:00
if ( ! m_ScriptInterface - > GetProperty ( metadata , " constructor " , constructor ) )
2011-01-16 00:35:20 +01:00
{
2011-03-23 14:36:20 +01:00
LOGERROR ( L " Failed to create AI player: %ls: missing 'constructor' " , path . string ( ) . c_str ( ) ) ;
2011-01-16 00:35:20 +01:00
return false ;
}
// Get the constructor function from the loaded scripts
2014-08-02 18:30:15 +02:00
if ( ! m_ScriptInterface - > GetProperty ( objectWithConstructor , constructor . c_str ( ) , & ctor )
| | ctor . isNull ( ) )
2011-01-16 00:35:20 +01:00
{
2011-03-23 14:36:20 +01:00
LOGERROR ( L " Failed to create AI player: %ls: can't find constructor '%hs' " , path . string ( ) . c_str ( ) , constructor . c_str ( ) ) ;
2011-01-16 00:35:20 +01:00
return false ;
}
2014-07-27 00:33:16 +02:00
m_ScriptInterface - > GetProperty ( metadata , " useShared " , m_UseSharedComponent ) ;
2014-08-02 15:22:02 +02:00
2014-08-02 18:30:15 +02:00
JS : : RootedValue obj ( cx ) ;
2011-01-16 00:35:20 +01:00
2014-08-02 15:22:02 +02:00
// Set up the data to pass as the constructor argument
JS : : RootedValue settings ( cx ) ;
m_ScriptInterface - > Eval ( L " ({}) " , & settings ) ;
m_ScriptInterface - > SetProperty ( settings , " player " , m_Player , false ) ;
m_ScriptInterface - > SetProperty ( settings , " difficulty " , m_Difficulty , false ) ;
ENSURE ( m_Worker . m_HasLoadedEntityTemplates ) ;
m_ScriptInterface - > SetProperty ( settings , " templates " , m_Worker . m_EntityTemplates , false ) ;
JS : : AutoValueVector argv ( cx ) ;
argv . append ( settings . get ( ) ) ;
2014-08-02 18:30:15 +02:00
m_ScriptInterface - > CallConstructor ( ctor , argv , & obj ) ;
2011-01-16 00:35:20 +01:00
2014-08-02 18:30:15 +02:00
if ( obj . isNull ( ) )
2011-01-16 00:35:20 +01:00
{
2011-03-23 14:36:20 +01:00
LOGERROR ( L " Failed to create AI player: %ls: error calling constructor '%hs' " , path . string ( ) . c_str ( ) , constructor . c_str ( ) ) ;
2011-01-16 00:35:20 +01:00
return false ;
}
2014-07-27 00:33:16 +02:00
m_Obj = CScriptValRooted ( cx , obj ) ;
2011-01-16 00:35:20 +01:00
return true ;
}
2014-07-27 00:33:16 +02:00
void Run ( JS : : HandleValue state , int playerID )
2011-01-16 00:35:20 +01:00
{
2014-07-31 21:18:40 +02:00
JSContext * cx = m_ScriptInterface - > GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
JS : : RootedValue tmpObj ( cx , m_Obj . get ( ) ) ; // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade
2011-01-16 00:35:20 +01:00
m_Commands . clear ( ) ;
2014-07-31 21:18:40 +02:00
m_ScriptInterface - > CallFunctionVoid ( tmpObj , " HandleMessage " , state , playerID ) ;
2011-01-16 00:35:20 +01:00
}
2013-03-05 23:52:48 +01:00
// overloaded with a sharedAI part.
// javascript can handle both natively on the same function.
2014-07-27 00:33:16 +02:00
void Run ( JS : : HandleValue state , int playerID , CScriptValRooted SharedAI )
2013-03-05 23:52:48 +01:00
{
2014-07-31 21:18:40 +02:00
JSContext * cx = m_ScriptInterface - > GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
JS : : RootedValue tmpObj ( cx , m_Obj . get ( ) ) ; // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade
2013-03-05 23:52:48 +01:00
m_Commands . clear ( ) ;
2014-07-31 21:18:40 +02:00
m_ScriptInterface - > CallFunctionVoid ( tmpObj , " HandleMessage " , state , playerID , SharedAI ) ;
2013-03-05 23:52:48 +01:00
}
2014-07-27 00:33:16 +02:00
void InitAI ( JS : : HandleValue state , CScriptValRooted SharedAI )
2013-03-05 23:52:48 +01:00
{
2014-07-31 21:18:40 +02:00
JSContext * cx = m_ScriptInterface - > GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
JS : : RootedValue tmpObj ( cx , m_Obj . get ( ) ) ; // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade
2013-03-05 23:52:48 +01:00
m_Commands . clear ( ) ;
2014-07-31 21:18:40 +02:00
m_ScriptInterface - > CallFunctionVoid ( tmpObj , " Init " , state , m_Player , SharedAI ) ;
2013-03-05 23:52:48 +01:00
}
2011-01-16 00:35:20 +01:00
CAIWorker & m_Worker ;
std : : wstring m_AIName ;
player_id_t m_Player ;
2013-03-13 21:44:48 +01:00
uint8_t m_Difficulty ;
2013-03-05 23:52:48 +01:00
bool m_UseSharedComponent ;
2013-12-30 11:04:59 +01:00
shared_ptr < ScriptInterface > m_ScriptInterface ;
2011-01-16 00:35:20 +01:00
CScriptValRooted m_Obj ;
std : : vector < shared_ptr < ScriptInterface : : StructuredClone > > m_Commands ;
2011-01-12 13:29:00 +01:00
} ;
public :
2011-01-16 00:35:20 +01:00
struct SCommandSets
2011-01-12 13:29:00 +01:00
{
player_id_t player ;
2011-01-16 00:35:20 +01:00
std : : vector < shared_ptr < ScriptInterface : : StructuredClone > > commands ;
2011-01-12 13:29:00 +01:00
} ;
CAIWorker ( ) :
2014-03-28 21:26:32 +01:00
m_ScriptInterface ( new ScriptInterface ( " Engine " , " AI " , g_ScriptRuntime ) ) ,
2011-03-03 01:16:14 +01:00
m_TurnNum ( 0 ) ,
2011-12-22 15:04:32 +01:00
m_CommandsComputed ( true ) ,
2013-03-05 23:52:48 +01:00
m_HasLoadedEntityTemplates ( false ) ,
m_HasSharedComponent ( false )
2011-01-12 13:29:00 +01:00
{
// TODO: ought to seed the RNG (in a network-synchronised way) before we use it
2013-12-30 11:04:59 +01:00
m_ScriptInterface - > ReplaceNondeterministicRNG ( m_RNG ) ;
m_ScriptInterface - > LoadGlobalScripts ( ) ;
2013-03-05 23:52:48 +01:00
2013-12-30 11:04:59 +01:00
m_ScriptInterface - > SetCallbackData ( static_cast < void * > ( this ) ) ;
2013-03-05 23:52:48 +01:00
2013-12-30 11:04:59 +01:00
m_ScriptInterface - > RegisterFunction < void , int , CScriptValRooted , CAIWorker : : PostCommand > ( " PostCommand " ) ;
m_ScriptInterface - > RegisterFunction < void , std : : wstring , CAIWorker : : IncludeModule > ( " IncludeModule " ) ;
m_ScriptInterface - > RegisterFunction < void , CAIWorker : : DumpHeap > ( " DumpHeap " ) ;
m_ScriptInterface - > RegisterFunction < void , CAIWorker : : ForceGC > ( " ForceGC " ) ;
2013-07-25 10:57:07 +02:00
2013-12-30 11:04:59 +01:00
m_ScriptInterface - > RegisterFunction < void , std : : wstring , std : : vector < u32 > , u32 , u32 , u32 , CAIWorker : : DumpImage > ( " DumpImage " ) ;
2011-01-12 13:29:00 +01:00
}
~ CAIWorker ( )
{
// Clear rooted script values before destructing the script interface
m_EntityTemplates = CScriptValRooted ( ) ;
m_PlayerMetadata . clear ( ) ;
m_Players . clear ( ) ;
2011-07-17 01:24:14 +02:00
m_GameState . reset ( ) ;
2011-10-16 04:55:58 +02:00
m_PassabilityMapVal = CScriptValRooted ( ) ;
m_TerritoryMapVal = CScriptValRooted ( ) ;
2011-01-12 13:29:00 +01:00
}
2013-12-30 11:04:59 +01:00
bool LoadScripts ( const std : : wstring & moduleName )
{
// Ignore modules that are already loaded
if ( m_LoadedModules . find ( moduleName ) ! = m_LoadedModules . end ( ) )
return true ;
// Mark this as loaded, to prevent it recursively loading itself
m_LoadedModules . insert ( moduleName ) ;
// Load and execute *.js
VfsPaths pathnames ;
2014-05-31 16:55:09 +02:00
if ( vfs : : GetPathnames ( g_VFS , L " simulation/ai/ " + moduleName + L " / " , L " *.js " , pathnames ) < 0 )
{
LOGERROR ( L " Failed to load AI scripts for module %ls " , moduleName . c_str ( ) ) ;
return false ;
}
2013-12-30 11:04:59 +01:00
for ( VfsPaths : : iterator it = pathnames . begin ( ) ; it ! = pathnames . end ( ) ; + + it )
{
if ( ! m_ScriptInterface - > LoadGlobalScriptFile ( * it ) )
{
LOGERROR ( L " Failed to load script %ls " , it - > string ( ) . c_str ( ) ) ;
return false ;
}
}
return true ;
}
2014-01-04 11:14:53 +01:00
static void IncludeModule ( ScriptInterface : : CxPrivate * pCxPrivate , std : : wstring name )
2013-12-30 11:04:59 +01:00
{
2014-01-04 11:14:53 +01:00
ENSURE ( pCxPrivate - > pCBData ) ;
CAIWorker * self = static_cast < CAIWorker * > ( pCxPrivate - > pCBData ) ;
2013-12-30 11:04:59 +01:00
self - > LoadScripts ( name ) ;
}
2011-01-12 13:29:00 +01:00
2014-08-03 21:32:39 +02:00
static void PostCommand ( ScriptInterface : : CxPrivate * pCxPrivate , int playerid , CScriptValRooted cmd1 )
2013-03-05 23:52:48 +01:00
{
2014-01-04 11:14:53 +01:00
ENSURE ( pCxPrivate - > pCBData ) ;
CAIWorker * self = static_cast < CAIWorker * > ( pCxPrivate - > pCBData ) ;
2014-08-03 21:32:39 +02:00
JSContext * cx = pCxPrivate - > pScriptInterface - > GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
// TODO: Get Handle parameter directly with SpiderMonkey 31
JS : : RootedValue cmd ( cx , cmd1 . get ( ) ) ;
2013-12-30 11:04:59 +01:00
self - > PostCommand ( playerid , cmd ) ;
}
2014-08-03 21:32:39 +02:00
void PostCommand ( int playerid , JS : : HandleValue cmd )
2013-12-30 11:04:59 +01:00
{
for ( size_t i = 0 ; i < m_Players . size ( ) ; i + + )
{
if ( m_Players [ i ] - > m_Player = = playerid )
{
2014-08-03 21:32:39 +02:00
m_Players [ i ] - > m_Commands . push_back ( m_ScriptInterface - > WriteStructuredClone ( cmd ) ) ;
2013-12-30 11:04:59 +01:00
return ;
}
2013-03-05 23:52:48 +01:00
}
2013-12-30 11:04:59 +01:00
LOGERROR ( L " Invalid playerid in PostCommand! " ) ;
2013-03-05 23:52:48 +01:00
}
// The next two ought to be implmeneted someday but for now as it returns "null" it can't
2014-01-04 11:14:53 +01:00
static void DumpHeap ( ScriptInterface : : CxPrivate * pCxPrivate )
2013-03-05 23:52:48 +01:00
{
2014-01-04 11:14:53 +01:00
pCxPrivate - > pScriptInterface - > DumpHeap ( ) ;
2013-03-05 23:52:48 +01:00
}
2014-01-04 11:14:53 +01:00
static void ForceGC ( ScriptInterface : : CxPrivate * pCxPrivate )
2013-03-05 23:52:48 +01:00
{
PROFILE3 ( " AI compute GC " ) ;
2014-03-28 21:26:32 +01:00
JS_GC ( pCxPrivate - > pScriptInterface - > GetJSRuntime ( ) ) ;
2013-03-05 23:52:48 +01:00
}
2013-07-25 10:57:07 +02:00
/**
* Debug function for AI scripts to dump 2 D array data ( e . g . terrain tile weights ) .
*/
2014-01-04 11:14:53 +01:00
static void DumpImage ( ScriptInterface : : CxPrivate * UNUSED ( pCxPrivate ) , std : : wstring name , std : : vector < u32 > data , u32 w , u32 h , u32 max )
2013-07-25 10:57:07 +02:00
{
// TODO: this is totally not threadsafe.
VfsPath filename = L " screenshots/aidump/ " + name ;
if ( data . size ( ) ! = w * h )
{
debug_warn ( L " DumpImage: data size doesn't match w*h " ) ;
return ;
}
if ( max = = 0 )
{
debug_warn ( L " DumpImage: max must not be 0 " ) ;
return ;
}
const size_t bpp = 8 ;
int flags = TEX_BOTTOM_UP | TEX_GREY ;
const size_t img_size = w * h * bpp / 8 ;
const size_t hdr_size = tex_hdr_size ( filename ) ;
shared_ptr < u8 > buf ;
AllocateAligned ( buf , hdr_size + img_size , maxSectorSize ) ;
Tex t ;
2014-03-13 03:37:05 +01:00
if ( t . wrap ( w , h , bpp , flags , buf , hdr_size ) < 0 )
2013-07-25 10:57:07 +02:00
return ;
u8 * img = buf . get ( ) + hdr_size ;
for ( size_t i = 0 ; i < data . size ( ) ; + + i )
img [ i ] = ( u8 ) ( ( data [ i ] * 255 ) / max ) ;
tex_write ( & t , filename ) ;
}
2013-03-05 23:52:48 +01:00
2013-03-06 12:52:41 +01:00
bool TryLoadSharedComponent ( bool hasTechs )
2013-03-05 23:52:48 +01:00
{
2014-07-14 21:52:35 +02:00
JSContext * cx = m_ScriptInterface - > GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
2013-03-05 23:52:48 +01:00
// we don't need to load it.
if ( ! m_HasSharedComponent )
return false ;
// reset the value so it can be used to determine if we actually initialized it.
m_HasSharedComponent = false ;
2013-12-30 11:04:59 +01:00
2014-01-10 18:46:48 +01:00
if ( LoadScripts ( L " common-api " ) )
2013-03-05 23:52:48 +01:00
m_HasSharedComponent = true ;
2013-12-30 11:04:59 +01:00
else
2013-03-05 23:52:48 +01:00
return false ;
2013-12-30 11:04:59 +01:00
2013-03-05 23:52:48 +01:00
// mainly here for the error messages
OsPath path = L " simulation/ai/common-api-v2/ " ;
2013-12-30 11:04:59 +01:00
// Constructor name is SharedScript, it's in the module API3
// TODO: Hardcoding this is bad, we need a smarter way.
2014-07-27 00:33:16 +02:00
JS : : RootedValue AIModule ( cx ) ;
JS : : RootedValue global ( cx , m_ScriptInterface - > GetGlobalObject ( ) ) ;
JS : : RootedValue ctor ( cx ) ;
if ( ! m_ScriptInterface - > GetProperty ( global , " API3 " , & AIModule ) | | AIModule . isUndefined ( ) )
2013-12-30 11:04:59 +01:00
{
LOGERROR ( L " Failed to create shared AI component: %ls: can't find module '%hs' " , path . string ( ) . c_str ( ) , " API3 " ) ;
return false ;
}
2014-07-27 00:33:16 +02:00
if ( ! m_ScriptInterface - > GetProperty ( AIModule , " SharedScript " , & ctor )
| | ctor . isUndefined ( ) )
2013-03-05 23:52:48 +01:00
{
LOGERROR ( L " Failed to create shared AI component: %ls: can't find constructor '%hs' " , path . string ( ) . c_str ( ) , " SharedScript " ) ;
return false ;
}
2013-12-08 20:23:09 +01:00
// Set up the data to pass as the constructor argument
2014-07-27 00:33:16 +02:00
JS : : RootedValue settings ( cx ) ;
m_ScriptInterface - > Eval ( L " ({}) " , & settings ) ;
JS : : RootedValue playersID ( cx ) ;
m_ScriptInterface - > Eval ( L " ({}) " , & playersID ) ;
2013-12-08 20:23:09 +01:00
for ( size_t i = 0 ; i < m_Players . size ( ) ; + + i )
2013-03-05 23:52:48 +01:00
{
2014-07-14 21:52:35 +02:00
JS : : RootedValue val ( cx ) ;
m_ScriptInterface - > ToJSVal ( cx , & val , m_Players [ i ] - > m_Player ) ;
2014-07-27 00:33:16 +02:00
m_ScriptInterface - > SetPropertyInt ( playersID , i , val , true ) ;
2013-12-08 20:23:09 +01:00
}
2014-07-27 00:33:16 +02:00
m_ScriptInterface - > SetProperty ( settings , " players " , playersID ) ;
2013-12-08 20:23:09 +01:00
ENSURE ( m_HasLoadedEntityTemplates ) ;
2014-07-27 00:33:16 +02:00
m_ScriptInterface - > SetProperty ( settings , " templates " , m_EntityTemplates , false ) ;
2013-03-05 23:52:48 +01:00
2013-12-08 20:23:09 +01:00
if ( hasTechs )
{
2014-07-27 00:33:16 +02:00
m_ScriptInterface - > SetProperty ( settings , " techTemplates " , m_TechTemplates , false ) ;
2013-03-05 23:52:48 +01:00
}
else
{
2013-03-06 12:52:41 +01:00
// won't get the tech templates directly.
2014-07-27 00:33:16 +02:00
JS : : RootedValue fakeTech ( cx ) ;
m_ScriptInterface - > Eval ( " ({}) " , & fakeTech ) ;
m_ScriptInterface - > SetProperty ( settings , " techTemplates " , fakeTech , false ) ;
2013-03-05 23:52:48 +01:00
}
2014-03-28 21:26:32 +01:00
2014-07-14 21:52:35 +02:00
JS : : AutoValueVector argv ( cx ) ;
2014-07-27 00:33:16 +02:00
argv . append ( settings ) ;
2014-08-02 18:30:15 +02:00
// TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade
JS : : RootedValue tmpSharedAIObj ( cx , m_SharedAIObj . get ( ) ) ;
m_ScriptInterface - > CallConstructor ( ctor , argv , & tmpSharedAIObj ) ;
m_SharedAIObj = CScriptValRooted ( cx , tmpSharedAIObj ) ;
2013-03-05 23:52:48 +01:00
2014-08-02 18:30:15 +02:00
if ( tmpSharedAIObj . isNull ( ) )
2013-03-05 23:52:48 +01:00
{
LOGERROR ( L " Failed to create shared AI component: %ls: error calling constructor '%hs' " , path . string ( ) . c_str ( ) , " SharedScript " ) ;
return false ;
}
return true ;
}
2014-08-02 15:22:02 +02:00
bool AddPlayer ( const std : : wstring & aiName , player_id_t player , uint8_t difficulty )
2011-01-12 13:29:00 +01:00
{
2013-12-30 11:04:59 +01:00
shared_ptr < CAIPlayer > ai ( new CAIPlayer ( * this , aiName , player , difficulty , m_ScriptInterface ) ) ;
2014-08-02 15:22:02 +02:00
if ( ! ai - > Initialise ( ) )
2011-01-12 13:29:00 +01:00
return false ;
2013-03-05 23:52:48 +01:00
// this will be set to true if we need to load the shared Component.
if ( ! m_HasSharedComponent )
m_HasSharedComponent = ai - > m_UseSharedComponent ;
2011-01-12 13:29:00 +01:00
m_Players . push_back ( ai ) ;
2011-01-16 00:35:20 +01:00
2011-01-12 13:29:00 +01:00
return true ;
}
2013-03-05 23:52:48 +01:00
bool RunGamestateInit ( const shared_ptr < ScriptInterface : : StructuredClone > & gameState , const Grid < u16 > & passabilityMap , const Grid < u8 > & territoryMap )
{
// this will be run last by InitGame.Js, passing the full game representation.
// For now it will run for the shared Component.
2013-07-25 10:57:07 +02:00
// This is NOT run during deserialization.
2013-12-30 11:04:59 +01:00
JSContext * cx = m_ScriptInterface - > GetContext ( ) ;
2014-07-27 00:33:16 +02:00
JSAutoRequest rq ( cx ) ;
2014-08-01 22:55:16 +02:00
JS : : RootedValue state ( cx ) ;
m_ScriptInterface - > ReadStructuredClone ( gameState , & state ) ;
2014-07-27 00:33:16 +02:00
JS : : RootedValue tmpVal ( cx ) ; // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade
2014-07-14 21:52:35 +02:00
ScriptInterface : : ToJSVal ( cx , & tmpVal , passabilityMap ) ;
2014-03-28 21:26:32 +01:00
m_PassabilityMapVal = CScriptValRooted ( cx , tmpVal . get ( ) ) ;
2014-07-27 00:33:16 +02:00
2014-07-14 21:52:35 +02:00
ScriptInterface : : ToJSVal ( cx , & tmpVal , territoryMap ) ;
2014-03-28 21:26:32 +01:00
m_TerritoryMapVal = CScriptValRooted ( cx , tmpVal ) ;
2014-07-27 00:33:16 +02:00
2013-03-05 23:52:48 +01:00
if ( m_HasSharedComponent )
{
2014-07-27 00:33:16 +02:00
m_ScriptInterface - > SetProperty ( state , " passabilityMap " , m_PassabilityMapVal , true ) ;
m_ScriptInterface - > SetProperty ( state , " territoryMap " , m_TerritoryMapVal , true ) ;
2013-03-05 23:52:48 +01:00
2014-07-31 21:18:40 +02:00
JS : : RootedValue tmpSharedAIObj ( cx , m_SharedAIObj . get ( ) ) ; // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade
m_ScriptInterface - > CallFunctionVoid ( tmpSharedAIObj , " init " , state ) ;
2013-03-05 23:52:48 +01:00
for ( size_t i = 0 ; i < m_Players . size ( ) ; + + i )
{
if ( m_HasSharedComponent & & m_Players [ i ] - > m_UseSharedComponent )
2014-07-27 00:33:16 +02:00
m_Players [ i ] - > InitAI ( state , m_SharedAIObj ) ;
2013-03-05 23:52:48 +01:00
}
}
return true ;
}
2011-10-16 04:55:58 +02:00
void StartComputation ( const shared_ptr < ScriptInterface : : StructuredClone > & gameState , const Grid < u16 > & passabilityMap , const Grid < u8 > & territoryMap , bool territoryMapDirty )
2011-01-12 13:29:00 +01:00
{
2011-04-30 15:01:45 +02:00
ENSURE ( m_CommandsComputed ) ;
2011-01-12 13:29:00 +01:00
m_GameState = gameState ;
2014-03-28 21:26:32 +01:00
JSContext * cx = m_ScriptInterface - > GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
2011-01-12 13:29:00 +01:00
2011-10-16 04:55:58 +02:00
if ( passabilityMap . m_DirtyID ! = m_PassabilityMap . m_DirtyID )
2011-03-03 01:16:14 +01:00
{
2011-10-16 04:55:58 +02:00
m_PassabilityMap = passabilityMap ;
2014-07-27 00:33:16 +02:00
JS : : RootedValue tmpVal ( cx ) ; // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade
2014-07-14 21:52:35 +02:00
ScriptInterface : : ToJSVal ( cx , & tmpVal , m_PassabilityMap ) ;
2014-03-28 21:26:32 +01:00
m_PassabilityMapVal = CScriptValRooted ( cx , tmpVal ) ;
2011-10-16 04:55:58 +02:00
}
if ( territoryMapDirty )
{
m_TerritoryMap = territoryMap ;
2014-03-28 21:26:32 +01:00
JS : : RootedValue tmpVal ( cx ) ;
2014-07-14 21:52:35 +02:00
ScriptInterface : : ToJSVal ( cx , & tmpVal , m_TerritoryMap ) ;
2014-03-28 21:26:32 +01:00
m_TerritoryMapVal = CScriptValRooted ( cx , tmpVal ) ;
2011-03-03 01:16:14 +01:00
}
2011-01-12 13:29:00 +01:00
m_CommandsComputed = false ;
}
void WaitToFinishComputation ( )
{
if ( ! m_CommandsComputed )
{
PerformComputation ( ) ;
m_CommandsComputed = true ;
}
}
2011-01-16 00:35:20 +01:00
void GetCommands ( std : : vector < SCommandSets > & commands )
2011-01-12 13:29:00 +01:00
{
2013-03-05 23:52:48 +01:00
WaitToFinishComputation ( ) ;
2011-01-12 13:29:00 +01:00
commands . clear ( ) ;
2011-01-16 00:35:20 +01:00
commands . resize ( m_Players . size ( ) ) ;
for ( size_t i = 0 ; i < m_Players . size ( ) ; + + i )
2011-01-12 13:29:00 +01:00
{
2011-01-16 00:35:20 +01:00
commands [ i ] . player = m_Players [ i ] - > m_Player ;
commands [ i ] . commands = m_Players [ i ] - > m_Commands ;
2011-01-12 13:29:00 +01:00
}
}
2013-03-05 23:52:48 +01:00
void RegisterTechTemplates ( const shared_ptr < ScriptInterface : : StructuredClone > & techTemplates ) {
2013-12-30 11:04:59 +01:00
JSContext * cx = m_ScriptInterface - > GetContext ( ) ;
2014-08-01 22:55:16 +02:00
JSAutoRequest rq ( cx ) ;
JS : : RootedValue ret ( cx ) ; // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade
m_ScriptInterface - > ReadStructuredClone ( techTemplates , & ret ) ;
m_TechTemplates = CScriptValRooted ( cx , ret ) ;
2013-03-05 23:52:48 +01:00
}
2011-01-12 13:29:00 +01:00
void LoadEntityTemplates ( const std : : vector < std : : pair < std : : string , const CParamNode * > > & templates )
{
2014-07-27 00:33:16 +02:00
JSContext * cx = m_ScriptInterface - > GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
2011-12-22 15:04:32 +01:00
m_HasLoadedEntityTemplates = true ;
2014-07-27 00:33:16 +02:00
JS : : RootedValue tmpEntityTemplates ( cx ) ; // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade
m_ScriptInterface - > Eval ( " ({}) " , & tmpEntityTemplates ) ;
2011-01-12 13:29:00 +01:00
for ( size_t i = 0 ; i < templates . size ( ) ; + + i )
{
2014-07-27 00:33:16 +02:00
JS : : RootedValue val ( cx , templates [ i ] . second - > ToJSVal ( cx , false ) ) ;
m_ScriptInterface - > SetProperty ( tmpEntityTemplates , templates [ i ] . first . c_str ( ) , val , true ) ;
2011-01-12 13:29:00 +01:00
}
// Since the template data is shared between AI players, freeze it
// to stop any of them changing it and confusing the other players
2014-07-27 00:33:16 +02:00
m_EntityTemplates = CScriptValRooted ( cx , tmpEntityTemplates ) ;
2013-12-30 11:04:59 +01:00
m_ScriptInterface - > FreezeObject ( m_EntityTemplates . get ( ) , true ) ;
2011-01-12 13:29:00 +01:00
}
void Serialize ( std : : ostream & stream , bool isDebug )
{
WaitToFinishComputation ( ) ;
if ( isDebug )
{
2013-12-30 11:04:59 +01:00
CDebugSerializer serializer ( * m_ScriptInterface , stream ) ;
2011-01-12 13:29:00 +01:00
serializer . Indent ( 4 ) ;
SerializeState ( serializer ) ;
}
else
{
2013-12-30 11:04:59 +01:00
CStdSerializer serializer ( * m_ScriptInterface , stream ) ;
2013-05-26 23:57:24 +02:00
// TODO: see comment in Deserialize()
serializer . SetSerializablePrototypes ( m_SerializablePrototypes ) ;
2011-01-12 13:29:00 +01:00
SerializeState ( serializer ) ;
}
}
void SerializeState ( ISerializer & serializer )
{
2014-07-27 00:33:16 +02:00
JSContext * cx = m_ScriptInterface - > GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
2011-12-22 15:04:32 +01:00
std : : stringstream rngStream ;
rngStream < < m_RNG ;
serializer . StringASCII ( " rng " , rngStream . str ( ) , 0 , 32 ) ;
serializer . NumberU32_Unbounded ( " turn " , m_TurnNum ) ;
2011-08-16 13:18:32 +02:00
serializer . NumberU32_Unbounded ( " num ais " , ( u32 ) m_Players . size ( ) ) ;
2011-01-12 13:29:00 +01:00
2013-03-24 10:10:32 +01:00
serializer . Bool ( " useSharedScript " , m_HasSharedComponent ) ;
if ( m_HasSharedComponent )
{
2014-07-31 21:18:40 +02:00
JS : : RootedValue sharedData ( cx ) ;
JS : : RootedValue tmpSharedAIObj ( cx , m_SharedAIObj . get ( ) ) ; // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade
if ( ! m_ScriptInterface - > CallFunction ( tmpSharedAIObj , " Serialize " , & sharedData ) )
2013-03-24 10:10:32 +01:00
LOGERROR ( L " AI shared script Serialize call failed " ) ;
2014-08-03 19:29:49 +02:00
serializer . ScriptVal ( " sharedData " , & sharedData ) ;
2013-03-24 10:10:32 +01:00
}
2011-01-12 13:29:00 +01:00
for ( size_t i = 0 ; i < m_Players . size ( ) ; + + i )
{
2011-12-22 15:04:32 +01:00
serializer . String ( " name " , m_Players [ i ] - > m_AIName , 1 , 256 ) ;
2011-01-16 00:35:20 +01:00
serializer . NumberI32_Unbounded ( " player " , m_Players [ i ] - > m_Player ) ;
2013-03-13 21:44:48 +01:00
serializer . NumberU8_Unbounded ( " difficulty " , m_Players [ i ] - > m_Difficulty ) ;
2011-08-16 13:18:32 +02:00
serializer . NumberU32_Unbounded ( " num commands " , ( u32 ) m_Players [ i ] - > m_Commands . size ( ) ) ;
2011-01-16 00:35:20 +01:00
for ( size_t j = 0 ; j < m_Players [ i ] - > m_Commands . size ( ) ; + + j )
{
2014-08-01 22:55:16 +02:00
JS : : RootedValue val ( cx ) ;
m_ScriptInterface - > ReadStructuredClone ( m_Players [ i ] - > m_Commands [ j ] , & val ) ;
2014-08-03 19:29:49 +02:00
serializer . ScriptVal ( " command " , & val ) ;
2011-01-16 00:35:20 +01:00
}
2011-12-22 15:04:32 +01:00
2014-07-27 00:33:16 +02:00
JS : : RootedValue tmpPlayerObj ( cx , m_Players [ i ] - > m_Obj . get ( ) ) ; // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade
bool hasCustomSerialize = m_ScriptInterface - > HasProperty ( tmpPlayerObj , " Serialize " ) ;
2013-05-26 23:57:24 +02:00
if ( hasCustomSerialize )
{
2014-07-27 00:33:16 +02:00
JS : : RootedValue scriptData ( cx ) ;
if ( ! m_ScriptInterface - > CallFunction ( tmpPlayerObj , " Serialize " , & scriptData ) )
2013-05-26 23:57:24 +02:00
LOGERROR ( L " AI script Serialize call failed " ) ;
2014-08-03 19:29:49 +02:00
serializer . ScriptVal ( " data " , & scriptData ) ;
2013-05-26 23:57:24 +02:00
}
else
{
2014-08-03 19:29:49 +02:00
serializer . ScriptVal ( " data " , & tmpPlayerObj ) ;
2013-05-26 23:57:24 +02:00
}
2013-03-11 20:58:29 +01:00
}
2011-01-12 13:29:00 +01:00
}
void Deserialize ( std : : istream & stream )
{
2014-07-27 00:33:16 +02:00
JSContext * cx = m_ScriptInterface - > GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
2011-04-30 15:01:45 +02:00
ENSURE ( m_CommandsComputed ) ; // deserializing while we're still actively computing would be bad
2011-01-12 13:29:00 +01:00
2013-12-30 11:04:59 +01:00
CStdDeserializer deserializer ( * m_ScriptInterface , stream ) ;
2011-01-12 13:29:00 +01:00
m_PlayerMetadata . clear ( ) ;
m_Players . clear ( ) ;
2011-12-22 15:04:32 +01:00
std : : string rngString ;
std : : stringstream rngStream ;
deserializer . StringASCII ( " rng " , rngString , 0 , 32 ) ;
rngStream < < rngString ;
rngStream > > m_RNG ;
deserializer . NumberU32_Unbounded ( " turn " , m_TurnNum ) ;
2011-01-12 13:29:00 +01:00
uint32_t numAis ;
deserializer . NumberU32_Unbounded ( " num ais " , numAis ) ;
2013-03-24 10:10:32 +01:00
deserializer . Bool ( " useSharedScript " , m_HasSharedComponent ) ;
TryLoadSharedComponent ( false ) ;
if ( m_HasSharedComponent )
{
2014-07-31 21:18:40 +02:00
JS : : RootedValue sharedData ( cx ) ;
JS : : RootedValue tmpSharedAIObj ( cx , m_SharedAIObj . get ( ) ) ; // TODO: Check if this temporary root can be removed after SpiderMonkey 31
deserializer . ScriptVal ( " sharedData " , & sharedData ) ;
if ( ! m_ScriptInterface - > CallFunctionVoid ( tmpSharedAIObj , " Deserialize " , sharedData ) )
2013-03-24 10:10:32 +01:00
LOGERROR ( L " AI shared script Deserialize call failed " ) ;
}
2011-01-12 13:29:00 +01:00
for ( size_t i = 0 ; i < numAis ; + + i )
{
std : : wstring name ;
player_id_t player ;
2013-03-13 21:44:48 +01:00
uint8_t difficulty ;
2011-12-22 15:04:32 +01:00
deserializer . String ( " name " , name , 1 , 256 ) ;
2011-01-12 13:29:00 +01:00
deserializer . NumberI32_Unbounded ( " player " , player ) ;
2013-03-13 21:44:48 +01:00
deserializer . NumberU8_Unbounded ( " difficulty " , difficulty ) ;
2014-08-02 15:22:02 +02:00
if ( ! AddPlayer ( name , player , difficulty ) )
2011-01-12 13:29:00 +01:00
throw PSERROR_Deserialize_ScriptError ( ) ;
2011-01-16 00:35:20 +01:00
uint32_t numCommands ;
deserializer . NumberU32_Unbounded ( " num commands " , numCommands ) ;
m_Players . back ( ) - > m_Commands . reserve ( numCommands ) ;
for ( size_t j = 0 ; j < numCommands ; + + j )
{
2014-07-31 21:18:40 +02:00
JS : : RootedValue val ( cx ) ;
deserializer . ScriptVal ( " command " , & val ) ;
m_Players . back ( ) - > m_Commands . push_back ( m_ScriptInterface - > WriteStructuredClone ( val ) ) ;
2011-01-16 00:35:20 +01:00
}
2013-05-26 23:57:24 +02:00
// TODO: this is yucky but necessary while the AIs are sharing data between contexts;
// ideally a new (de)serializer instance would be created for each player
// so they would have a single, consistent script context to use and serializable
// prototypes could be stored in their ScriptInterface
deserializer . SetSerializablePrototypes ( m_DeserializablePrototypes ) ;
2014-07-27 00:33:16 +02:00
JS : : RootedValue tmpPlayerObj ( cx , m_Players . back ( ) - > m_Obj . get ( ) ) ; // TODO: Check if this temporary root can be removed after SpiderMonkey 31 upgrade
bool hasCustomDeserialize = m_ScriptInterface - > HasProperty ( tmpPlayerObj , " Deserialize " ) ;
2013-05-26 23:57:24 +02:00
if ( hasCustomDeserialize )
2013-03-24 10:10:32 +01:00
{
2014-07-31 21:18:40 +02:00
JS : : RootedValue scriptData ( cx ) ;
deserializer . ScriptVal ( " data " , & scriptData ) ;
2013-05-26 23:57:24 +02:00
if ( m_Players [ i ] - > m_UseSharedComponent )
{
2014-07-27 00:33:16 +02:00
if ( ! m_ScriptInterface - > CallFunctionVoid ( tmpPlayerObj , " Deserialize " , scriptData , m_SharedAIObj ) )
2013-05-26 23:57:24 +02:00
LOGERROR ( L " AI script Deserialize call failed " ) ;
}
2014-07-27 00:33:16 +02:00
else if ( ! m_ScriptInterface - > CallFunctionVoid ( tmpPlayerObj , " Deserialize " , scriptData ) )
2013-05-26 23:57:24 +02:00
{
LOGERROR ( L " AI script deserialize() call failed " ) ;
}
2013-03-24 10:10:32 +01:00
}
2013-05-26 23:57:24 +02:00
else
2013-03-24 10:10:32 +01:00
{
2014-07-31 21:18:40 +02:00
deserializer . ScriptVal ( " data " , & tmpPlayerObj ) ;
m_Players . back ( ) - > m_Obj = CScriptValRooted ( cx , tmpPlayerObj ) ;
2013-03-24 10:10:32 +01:00
}
2013-03-11 20:58:29 +01:00
}
2011-01-12 13:29:00 +01:00
}
2013-03-05 23:52:48 +01:00
int getPlayerSize ( )
{
return m_Players . size ( ) ;
}
2011-01-12 13:29:00 +01:00
2013-05-26 23:57:24 +02:00
void RegisterSerializablePrototype ( std : : wstring name , CScriptVal proto )
{
// Require unique prototype and name (for reverse lookup)
// TODO: this is yucky - see comment in Deserialize()
JSObject * obj = JSVAL_TO_OBJECT ( proto . get ( ) ) ;
std : : pair < std : : map < JSObject * , std : : wstring > : : iterator , bool > ret1 = m_SerializablePrototypes . insert ( std : : make_pair ( obj , name ) ) ;
std : : pair < std : : map < std : : wstring , JSObject * > : : iterator , bool > ret2 = m_DeserializablePrototypes . insert ( std : : make_pair ( name , obj ) ) ;
if ( ! ret1 . second | | ! ret2 . second )
LOGERROR ( L " RegisterSerializablePrototype called with same prototype multiple times: p=%p n='%ls' " , obj , name . c_str ( ) ) ;
}
2011-01-12 13:29:00 +01:00
private :
2014-08-03 00:21:50 +02:00
void LoadMetadata ( const VfsPath & path , JS : : MutableHandleValue out )
2011-01-12 13:29:00 +01:00
{
if ( m_PlayerMetadata . find ( path ) = = m_PlayerMetadata . end ( ) )
{
// Load and cache the AI player metadata
2014-08-03 00:21:50 +02:00
m_ScriptInterface - > ReadJSONFile ( path , out ) ;
m_PlayerMetadata [ path ] = CScriptValRooted ( m_ScriptInterface - > GetContext ( ) , out ) ;
2011-01-12 13:29:00 +01:00
}
2014-08-03 00:21:50 +02:00
out . set ( m_PlayerMetadata [ path ] . get ( ) ) ;
2011-01-12 13:29:00 +01:00
}
void PerformComputation ( )
2014-03-28 21:26:32 +01:00
{
2011-01-12 13:29:00 +01:00
// Deserialize the game state, to pass to the AI's HandleMessage
2014-07-27 00:33:16 +02:00
JSContext * cx = m_ScriptInterface - > GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
JS : : RootedValue state ( cx ) ;
2011-03-03 01:16:14 +01:00
{
2011-11-04 02:35:50 +01:00
PROFILE3 ( " AI compute read state " ) ;
2014-08-01 22:55:16 +02:00
m_ScriptInterface - > ReadStructuredClone ( m_GameState , & state ) ;
2014-07-27 00:33:16 +02:00
m_ScriptInterface - > SetProperty ( state , " passabilityMap " , m_PassabilityMapVal , true ) ;
m_ScriptInterface - > SetProperty ( state , " territoryMap " , m_TerritoryMapVal , true ) ;
2011-03-03 01:16:14 +01:00
}
2011-01-12 13:29:00 +01:00
2011-01-16 00:35:20 +01:00
// It would be nice to do
2013-12-30 11:04:59 +01:00
// m_ScriptInterface->FreezeObject(state.get(), true);
2011-01-16 00:35:20 +01:00
// to prevent AI scripts accidentally modifying the state and
// affecting other AI scripts they share it with. But the performance
// cost is far too high, so we won't do that.
2013-03-05 23:52:48 +01:00
// If there is a shared component, run it
2011-01-12 13:29:00 +01:00
2013-03-05 23:52:48 +01:00
if ( m_HasSharedComponent )
{
PROFILE3 ( " AI run shared component " ) ;
2014-07-31 21:18:40 +02:00
JS : : RootedValue tmpSharedAIObj ( cx , m_SharedAIObj . get ( ) ) ; // TODO: Check if this temporary root can be removed after SpiderMonkey 31
m_ScriptInterface - > CallFunctionVoid ( tmpSharedAIObj , " onUpdate " , state ) ;
2013-03-05 23:52:48 +01:00
}
2011-11-04 02:35:50 +01:00
for ( size_t i = 0 ; i < m_Players . size ( ) ; + + i )
2011-03-03 01:16:14 +01:00
{
2011-11-04 02:35:50 +01:00
PROFILE3 ( " AI script " ) ;
PROFILE2_ATTR ( " player: %d " , m_Players [ i ] - > m_Player ) ;
PROFILE2_ATTR ( " script: %ls " , m_Players [ i ] - > m_AIName . c_str ( ) ) ;
2013-12-30 11:04:59 +01:00
2013-03-05 23:52:48 +01:00
if ( m_HasSharedComponent & & m_Players [ i ] - > m_UseSharedComponent )
2013-12-30 11:04:59 +01:00
m_Players [ i ] - > Run ( state , m_Players [ i ] - > m_Player , m_SharedAIObj ) ;
2013-03-05 23:52:48 +01:00
else
2013-12-30 11:04:59 +01:00
m_Players [ i ] - > Run ( state , m_Players [ i ] - > m_Player ) ;
2011-03-03 01:16:14 +01:00
}
2011-01-12 13:29:00 +01:00
}
2011-01-16 00:35:20 +01:00
shared_ptr < ScriptRuntime > m_ScriptRuntime ;
2013-12-30 11:04:59 +01:00
shared_ptr < ScriptInterface > m_ScriptInterface ;
2011-01-12 13:29:00 +01:00
boost : : rand48 m_RNG ;
2011-12-22 15:04:32 +01:00
u32 m_TurnNum ;
2011-01-12 13:29:00 +01:00
CScriptValRooted m_EntityTemplates ;
2011-12-22 15:04:32 +01:00
bool m_HasLoadedEntityTemplates ;
2013-03-05 23:52:48 +01:00
CScriptValRooted m_TechTemplates ;
2011-12-22 15:04:32 +01:00
2011-03-21 23:59:00 +01:00
std : : map < VfsPath , CScriptValRooted > m_PlayerMetadata ;
2011-01-16 00:35:20 +01:00
std : : vector < shared_ptr < CAIPlayer > > m_Players ; // use shared_ptr just to avoid copying
2011-01-12 13:29:00 +01:00
2013-03-05 23:52:48 +01:00
bool m_HasSharedComponent ;
CScriptValRooted m_SharedAIObj ;
std : : vector < SCommandSets > m_Commands ;
2013-12-30 11:04:59 +01:00
std : : set < std : : wstring > m_LoadedModules ;
2011-01-16 00:35:20 +01:00
shared_ptr < ScriptInterface : : StructuredClone > m_GameState ;
2011-10-16 04:55:58 +02:00
Grid < u16 > m_PassabilityMap ;
CScriptValRooted m_PassabilityMapVal ;
Grid < u8 > m_TerritoryMap ;
CScriptValRooted m_TerritoryMapVal ;
2011-01-12 13:29:00 +01:00
bool m_CommandsComputed ;
2013-05-26 23:57:24 +02:00
std : : map < JSObject * , std : : wstring > m_SerializablePrototypes ;
std : : map < std : : wstring , JSObject * > m_DeserializablePrototypes ;
2011-01-12 13:29:00 +01:00
} ;
2012-03-01 04:55:05 +01:00
/**
* Implementation of ICmpAIManager .
*/
2011-01-12 13:29:00 +01:00
class CCmpAIManager : public ICmpAIManager
{
public :
2011-03-05 02:56:59 +01:00
static void ClassInit ( CComponentManager & componentManager )
2011-01-12 13:29:00 +01:00
{
2011-03-05 02:56:59 +01:00
componentManager . SubscribeToMessageType ( MT_ProgressiveLoad ) ;
2011-01-12 13:29:00 +01:00
}
DEFAULT_COMPONENT_ALLOCATOR ( AIManager )
static std : : string GetSchema ( )
{
return " <a:component type='system'/><empty/> " ;
}
2011-01-16 15:08:38 +01:00
virtual void Init ( const CParamNode & UNUSED ( paramNode ) )
2011-01-12 13:29:00 +01:00
{
2011-11-23 22:24:41 +01:00
m_TerritoriesDirtyID = 0 ;
2013-12-11 18:10:14 +01:00
m_JustDeserialized = false ;
2011-11-23 22:24:41 +01:00
2011-03-05 02:56:59 +01:00
StartLoadEntityTemplates ( ) ;
2011-01-12 13:29:00 +01:00
}
2011-01-16 15:08:38 +01:00
virtual void Deinit ( )
2011-01-12 13:29:00 +01:00
{
}
virtual void Serialize ( ISerializer & serialize )
{
// Because the AI worker uses its own ScriptInterface, we can't use the
// ISerializer (which was initialised with the simulation ScriptInterface)
// directly. So we'll just grab the ISerializer's stream and write to it
// with an independent serializer.
2011-12-22 15:04:32 +01:00
m_Worker . Serialize ( serialize . GetStream ( ) , serialize . IsDebug ( ) ) ;
2011-01-12 13:29:00 +01:00
}
2011-01-16 15:08:38 +01:00
virtual void Deserialize ( const CParamNode & paramNode , IDeserializer & deserialize )
2011-01-12 13:29:00 +01:00
{
2011-01-16 15:08:38 +01:00
Init ( paramNode ) ;
2011-01-12 13:29:00 +01:00
2011-12-22 15:04:32 +01:00
ForceLoadEntityTemplates ( ) ;
m_Worker . Deserialize ( deserialize . GetStream ( ) ) ;
2013-12-11 18:10:14 +01:00
m_JustDeserialized = true ;
2011-01-12 13:29:00 +01:00
}
2011-03-05 02:56:59 +01:00
virtual void HandleMessage ( const CMessage & msg , bool UNUSED ( global ) )
{
switch ( msg . GetType ( ) )
{
case MT_ProgressiveLoad :
{
const CMessageProgressiveLoad & msgData = static_cast < const CMessageProgressiveLoad & > ( msg ) ;
2011-08-16 13:18:32 +02:00
* msgData . total + = ( int ) m_TemplateNames . size ( ) ;
2011-03-05 02:56:59 +01:00
if ( * msgData . progressed )
break ;
if ( ContinueLoadEntityTemplates ( ) )
* msgData . progressed = true ;
2011-08-16 13:18:32 +02:00
* msgData . progress + = ( int ) m_TemplateLoadedIdx ;
2011-03-05 02:56:59 +01:00
break ;
}
}
}
2011-06-29 01:24:42 +02:00
2013-03-13 21:44:48 +01:00
virtual void AddPlayer ( std : : wstring id , player_id_t player , uint8_t difficulty )
2011-01-12 13:29:00 +01:00
{
2014-08-02 15:22:02 +02:00
m_Worker . AddPlayer ( id , player , difficulty ) ;
2011-06-29 01:24:42 +02:00
// AI players can cheat and see through FoW/SoD, since that greatly simplifies
// their implementation.
// (TODO: maybe cleverer AIs should be able to optionally retain FoW/SoD)
2013-09-11 22:41:53 +02:00
CmpPtr < ICmpRangeManager > cmpRangeManager ( GetSystemEntity ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( cmpRangeManager )
2011-06-29 01:24:42 +02:00
cmpRangeManager - > SetLosRevealAll ( player , true ) ;
2011-01-12 13:29:00 +01:00
}
2013-03-05 23:52:48 +01:00
virtual void TryLoadSharedComponent ( )
{
2013-03-06 12:52:41 +01:00
ScriptInterface & scriptInterface = GetSimContext ( ) . GetScriptInterface ( ) ;
2014-08-03 21:32:39 +02:00
JSContext * cx = scriptInterface . GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
2013-03-06 12:52:41 +01:00
// load the technology templates
2013-09-11 22:41:53 +02:00
CmpPtr < ICmpTechnologyTemplateManager > cmpTechTemplateManager ( GetSystemEntity ( ) ) ;
2013-03-06 12:52:41 +01:00
ENSURE ( cmpTechTemplateManager ) ;
// Get the game state from AIInterface
2014-08-03 21:32:39 +02:00
JS : : RootedValue techTemplates ( cx , cmpTechTemplateManager - > GetAllTechs ( ) . get ( ) ) ;
2013-03-06 12:52:41 +01:00
2014-08-03 21:32:39 +02:00
m_Worker . RegisterTechTemplates ( scriptInterface . WriteStructuredClone ( techTemplates ) ) ;
2013-03-05 23:52:48 +01:00
m_Worker . TryLoadSharedComponent ( true ) ;
}
virtual void RunGamestateInit ( )
{
ScriptInterface & scriptInterface = GetSimContext ( ) . GetScriptInterface ( ) ;
2014-07-27 00:33:16 +02:00
JSContext * cx = scriptInterface . GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
2013-03-05 23:52:48 +01:00
2013-09-11 22:41:53 +02:00
CmpPtr < ICmpAIInterface > cmpAIInterface ( GetSystemEntity ( ) ) ;
2013-03-05 23:52:48 +01:00
ENSURE ( cmpAIInterface ) ;
// Get the game state from AIInterface
2013-12-04 17:52:44 +01:00
// We flush events from the initialization so we get a clean state now.
2014-07-27 00:33:16 +02:00
JS : : RootedValue state ( cx , cmpAIInterface - > GetFullRepresentation ( true ) . get ( ) ) ;
2013-03-05 23:52:48 +01:00
// Get the passability data
Grid < u16 > dummyGrid ;
const Grid < u16 > * passabilityMap = & dummyGrid ;
2013-09-11 22:41:53 +02:00
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSystemEntity ( ) ) ;
2013-03-05 23:52:48 +01:00
if ( cmpPathfinder )
passabilityMap = & cmpPathfinder - > GetPassabilityGrid ( ) ;
// Get the territory data
// Since getting the territory grid can trigger a recalculation, we check NeedUpdate first
Grid < u8 > dummyGrid2 ;
const Grid < u8 > * territoryMap = & dummyGrid2 ;
2013-09-11 22:41:53 +02:00
CmpPtr < ICmpTerritoryManager > cmpTerritoryManager ( GetSystemEntity ( ) ) ;
2013-03-05 23:52:48 +01:00
if ( cmpTerritoryManager & & cmpTerritoryManager - > NeedUpdate ( & m_TerritoriesDirtyID ) )
{
territoryMap = & cmpTerritoryManager - > GetTerritoryGrid ( ) ;
}
LoadPathfinderClasses ( state ) ;
2014-08-03 21:32:39 +02:00
m_Worker . RunGamestateInit ( scriptInterface . WriteStructuredClone ( state ) , * passabilityMap , * territoryMap ) ;
2013-03-05 23:52:48 +01:00
}
2011-01-12 13:29:00 +01:00
virtual void StartComputation ( )
{
PROFILE ( " AI setup " ) ;
2011-03-05 02:56:59 +01:00
ForceLoadEntityTemplates ( ) ;
2011-01-12 13:29:00 +01:00
ScriptInterface & scriptInterface = GetSimContext ( ) . GetScriptInterface ( ) ;
2014-07-27 00:33:16 +02:00
JSContext * cx = scriptInterface . GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
2011-01-12 13:29:00 +01:00
2013-03-05 23:52:48 +01:00
if ( m_Worker . getPlayerSize ( ) = = 0 )
return ;
2013-09-11 22:41:53 +02:00
CmpPtr < ICmpAIInterface > cmpAIInterface ( GetSystemEntity ( ) ) ;
2012-02-08 03:46:15 +01:00
ENSURE ( cmpAIInterface ) ;
2011-01-12 13:29:00 +01:00
2011-01-16 00:35:20 +01:00
// Get the game state from AIInterface
2014-07-27 00:33:16 +02:00
JS : : RootedValue state ( cx ) ;
2013-12-11 18:10:14 +01:00
if ( m_JustDeserialized )
2014-07-27 00:33:16 +02:00
state . set ( cmpAIInterface - > GetFullRepresentation ( true ) . get ( ) ) ;
2013-12-11 18:10:14 +01:00
else
2014-07-27 00:33:16 +02:00
state . set ( cmpAIInterface - > GetRepresentation ( ) . get ( ) ) ;
2011-01-12 13:29:00 +01:00
2011-10-16 04:55:58 +02:00
// Get the passability data
2011-03-03 01:16:14 +01:00
Grid < u16 > dummyGrid ;
2011-10-16 04:55:58 +02:00
const Grid < u16 > * passabilityMap = & dummyGrid ;
2013-09-11 22:41:53 +02:00
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSystemEntity ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( cmpPathfinder )
2011-10-16 04:55:58 +02:00
passabilityMap = & cmpPathfinder - > GetPassabilityGrid ( ) ;
// Get the territory data
// Since getting the territory grid can trigger a recalculation, we check NeedUpdate first
bool territoryMapDirty = false ;
Grid < u8 > dummyGrid2 ;
const Grid < u8 > * territoryMap = & dummyGrid2 ;
2013-09-11 22:41:53 +02:00
CmpPtr < ICmpTerritoryManager > cmpTerritoryManager ( GetSystemEntity ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( cmpTerritoryManager & & cmpTerritoryManager - > NeedUpdate ( & m_TerritoriesDirtyID ) )
2011-10-16 04:55:58 +02:00
{
territoryMap = & cmpTerritoryManager - > GetTerritoryGrid ( ) ;
territoryMapDirty = true ;
}
2011-02-10 17:06:28 +01:00
LoadPathfinderClasses ( state ) ;
2014-07-27 00:33:16 +02:00
m_Worker . StartComputation ( scriptInterface . WriteStructuredClone ( state ) , * passabilityMap , * territoryMap , territoryMapDirty ) ;
2013-12-11 18:10:14 +01:00
m_JustDeserialized = false ;
2011-01-12 13:29:00 +01:00
}
virtual void PushCommands ( )
{
2011-01-16 00:35:20 +01:00
std : : vector < CAIWorker : : SCommandSets > commands ;
2011-01-12 13:29:00 +01:00
m_Worker . GetCommands ( commands ) ;
2013-09-11 22:41:53 +02:00
CmpPtr < ICmpCommandQueue > cmpCommandQueue ( GetSystemEntity ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpCommandQueue )
2011-01-12 13:29:00 +01:00
return ;
2014-08-01 22:55:16 +02:00
ScriptInterface & scriptInterface = GetSimContext ( ) . GetScriptInterface ( ) ;
JSContext * cx = scriptInterface . GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
JS : : RootedValue clonedCommandVal ( cx ) ;
2011-01-12 13:29:00 +01:00
for ( size_t i = 0 ; i < commands . size ( ) ; + + i )
{
for ( size_t j = 0 ; j < commands [ i ] . commands . size ( ) ; + + j )
{
2014-08-01 22:55:16 +02:00
scriptInterface . ReadStructuredClone ( commands [ i ] . commands [ j ] , & clonedCommandVal ) ;
cmpCommandQueue - > PushLocalCommand ( commands [ i ] . player , CScriptVal ( clonedCommandVal ) ) ;
2011-01-12 13:29:00 +01:00
}
}
}
private :
2011-03-05 02:56:59 +01:00
std : : vector < std : : string > m_TemplateNames ;
size_t m_TemplateLoadedIdx ;
std : : vector < std : : pair < std : : string , const CParamNode * > > m_Templates ;
2011-10-16 04:55:58 +02:00
size_t m_TerritoriesDirtyID ;
2011-03-05 02:56:59 +01:00
2013-12-11 18:10:14 +01:00
bool m_JustDeserialized ;
2011-03-05 02:56:59 +01:00
void StartLoadEntityTemplates ( )
2011-01-12 13:29:00 +01:00
{
2013-09-11 22:41:53 +02:00
CmpPtr < ICmpTemplateManager > cmpTemplateManager ( GetSystemEntity ( ) ) ;
2012-02-08 03:46:15 +01:00
ENSURE ( cmpTemplateManager ) ;
2011-03-05 02:56:59 +01:00
m_TemplateNames = cmpTemplateManager - > FindAllTemplates ( false ) ;
m_TemplateLoadedIdx = 0 ;
m_Templates . reserve ( m_TemplateNames . size ( ) ) ;
}
// Tries to load the next entity template. Returns true if we did some work.
bool ContinueLoadEntityTemplates ( )
{
if ( m_TemplateLoadedIdx > = m_TemplateNames . size ( ) )
return false ;
2011-01-12 13:29:00 +01:00
2013-09-11 22:41:53 +02:00
CmpPtr < ICmpTemplateManager > cmpTemplateManager ( GetSystemEntity ( ) ) ;
2012-02-08 03:46:15 +01:00
ENSURE ( cmpTemplateManager ) ;
2011-01-12 13:29:00 +01:00
2011-03-05 02:56:59 +01:00
const CParamNode * node = cmpTemplateManager - > GetTemplateWithoutValidation ( m_TemplateNames [ m_TemplateLoadedIdx ] ) ;
if ( node )
m_Templates . push_back ( std : : make_pair ( m_TemplateNames [ m_TemplateLoadedIdx ] , node ) ) ;
2011-01-12 13:29:00 +01:00
2011-03-05 02:56:59 +01:00
m_TemplateLoadedIdx + + ;
2011-01-12 13:29:00 +01:00
2011-03-05 02:56:59 +01:00
// If this was the last template, send the data to the worker
if ( m_TemplateLoadedIdx = = m_TemplateNames . size ( ) )
m_Worker . LoadEntityTemplates ( m_Templates ) ;
return true ;
}
void ForceLoadEntityTemplates ( )
{
while ( ContinueLoadEntityTemplates ( ) )
2011-01-12 13:29:00 +01:00
{
}
}
2014-07-27 00:33:16 +02:00
void LoadPathfinderClasses ( JS : : HandleValue state )
2011-02-10 17:06:28 +01:00
{
2013-09-11 22:41:53 +02:00
CmpPtr < ICmpPathfinder > cmpPathfinder ( GetSystemEntity ( ) ) ;
2012-02-08 03:46:15 +01:00
if ( ! cmpPathfinder )
2011-02-10 17:06:28 +01:00
return ;
ScriptInterface & scriptInterface = GetSimContext ( ) . GetScriptInterface ( ) ;
2014-07-27 00:33:16 +02:00
JSContext * cx = scriptInterface . GetContext ( ) ;
JSAutoRequest rq ( cx ) ;
2011-02-10 17:06:28 +01:00
2014-07-27 00:33:16 +02:00
JS : : RootedValue classesVal ( cx ) ;
scriptInterface . Eval ( " ({ pathfinderObstruction: 1, foundationObstruction: 2 }) " , & classesVal ) ;
2011-02-10 17:06:28 +01:00
std : : map < std : : string , ICmpPathfinder : : pass_class_t > classes = cmpPathfinder - > GetPassabilityClasses ( ) ;
for ( std : : map < std : : string , ICmpPathfinder : : pass_class_t > : : iterator it = classes . begin ( ) ; it ! = classes . end ( ) ; + + it )
2014-07-27 00:33:16 +02:00
scriptInterface . SetProperty ( classesVal , it - > first . c_str ( ) , it - > second , true ) ;
2011-02-10 17:06:28 +01:00
2014-07-27 00:33:16 +02:00
scriptInterface . SetProperty ( state , " passabilityClasses " , classesVal , true ) ;
2011-02-10 17:06:28 +01:00
}
2011-01-12 13:29:00 +01:00
CAIWorker m_Worker ;
} ;
REGISTER_COMPONENT_TYPE ( AIManager )