//o------------------------------------------------------------------------------------------------o //| File - CJSEngine.cpp //| Date - 2/22/2006 //o------------------------------------------------------------------------------------------------o //| Purpose - JS Engine Handling //o------------------------------------------------------------------------------------------------o //| Changes - Version History //| //| 1.0 2/22/2006 //| Extracted JSObject handling from cScript and encapsulated it in a global class. //| Moved global JSEngine loading code into the class. //| Created CJSRuntime class to handle multiple runtimes (for threading purposes). //o------------------------------------------------------------------------------------------------o #include "uox3.h" #include "CJSEngine.h" #include "UOXJSClasses.h" #include "UOXJSMethods.h" #include "UOXJSPropertySpecs.h" #include CJSEngine *JSEngine = nullptr; //================================================================================================== auto CJSEngine::Startup() -> void { runtimeList.resize( 0 ); const UI32 maxEngineSize = 0xFFFFFFFF; // 4 gb, hard max // 16 MB minimum. Any lower and UOX3 is prone to crashes from frequent JS reloads auto maxBytesSize = std::max( static_cast( 16 ), cwmWorldState->ServerData()->GetJSEngineSize() ); // from INI // Use minimum of INI-provided value and hard-defined maximum // maxBytes definition: "Maximum nominal heap before last ditch GC" UI32 engineMaxBytes = std::min( static_cast( static_cast( maxBytesSize ) * 1024 * 1024 ), maxEngineSize ); Console.PrintSectionBegin(); Console << "Starting JavaScript Engine...." << myendl; runtimeList.push_back( new CJSRuntime( engineMaxBytes )); // Default Runtime runtimeList.push_back( new CJSRuntime( engineMaxBytes )); // Console Runtime Console << "JavaScript engine startup complete." << myendl; Console.PrintSectionBegin(); } //=================================================================== CJSEngine::~CJSEngine() { // Why? we are shutting down, the process memory will take care of this for us in theory /* for( RUNTIMELIST_ITERATOR rIter = runtimeList.begin(); rIter != runtimeList.end(); ++rIter ) { if(( *rIter)) { delete ( *rIter ); } } */ } void CJSEngine::Reload( void ) { for( RUNTIMELIST_ITERATOR rIter = runtimeList.begin(); rIter != runtimeList.end(); ++rIter ) { if(( *rIter ) != nullptr ) { ( *rIter )->Reload(); } } } void CJSEngine::CollectGarbage( void ) { for( RUNTIMELIST_ITERATOR rIter = runtimeList.begin(); rIter != runtimeList.end(); ++rIter ) { if(( *rIter ) != nullptr ) { ( *rIter )->CollectGarbage(); } } } JSRuntime *CJSEngine::GetRuntime( UI08 runTime ) const { if( runTime >= runtimeList.size() ) { runTime = 0; } return runtimeList[runTime]->GetRuntime(); } JSContext *CJSEngine::GetContext( UI08 runTime ) const { if( runTime >= runtimeList.size() ) { runTime = 0; } return runtimeList[runTime]->GetContext(); } JSObject *CJSEngine::GetObject( UI08 runTime ) const { if( runTime >= runtimeList.size() ) { runTime = 0; } return runtimeList[runTime]->GetObject(); } UI08 CJSEngine::FindActiveRuntime( JSRuntime *rT ) const { for( RUNTIMELIST_CITERATOR rIter = runtimeList.begin(); rIter != runtimeList.end(); ++rIter ) { if(( *rIter ) != nullptr ) { if( rT == ( *rIter )->GetRuntime() ) return static_cast( rIter - runtimeList.begin() ); } } return 0; } JSObject *CJSEngine::GetPrototype( UI08 runTime, JSPrototypes protoNum ) const { JSObject *retVal = nullptr; if( runTime < runtimeList.size() ) { retVal = runtimeList[runTime]->GetPrototype( protoNum ); } return retVal; } JSObject *CJSEngine::AcquireObject( IUEEntries iType, void *index, UI08 runTime ) { JSObject *retVal = nullptr; if( index != nullptr && runTime < runtimeList.size() ) { retVal = runtimeList[runTime]->AcquireObject( iType, index ); } return retVal; } void CJSEngine::ReleaseObject( IUEEntries iType, void *index ) { for( RUNTIMELIST_ITERATOR rIter = runtimeList.begin(); rIter != runtimeList.end(); ++rIter ) { if(( *rIter ) != nullptr ) { ( *rIter )->ReleaseObject( iType, index ); } } } //======================================================================================================== // CJSRuntime //====================================================================================================== CJSRuntime::CJSRuntime( UI32 engineSize ) { jsRuntime = JS_NewRuntime( engineSize ); if( jsRuntime == nullptr ) { Shutdown( FATAL_UOX3_JAVASCRIPT ); } // No need to use a large number here, it's not stack size as documentation indicated, // but "chunk size of the stack pool". Recommendations are to leave it at 8192. In // debug builds, larger values can even degrade performance drastically jsContext = JS_NewContext( jsRuntime, 8192 ); // Specify JS 1.8 as version to unlock const variables, let, etc JS_SetVersion( jsContext, JSVERSION_LATEST ); JS_SetOptions( jsContext, JS_GetOptions( jsContext ) | JSOPTION_JIT | JSOPTION_METHODJIT ); if( jsContext == nullptr ) { Shutdown( FATAL_UOX3_JAVASCRIPT ); } jsGlobal = JS_NewCompartmentAndGlobalObject(jsContext, &global_class, nullptr); if (jsGlobal == nullptr) { Shutdown(FATAL_UOX3_JAVASCRIPT); } JS_LockGCThing( jsContext, jsGlobal ); JS_InitStandardClasses( jsContext, jsGlobal ); objectList.resize( IUE_COUNT ); InitializePrototypes(); } CJSRuntime::~CJSRuntime( void ) { Cleanup(); JS_UnlockGCThing( jsContext, spellsObj ); JS_UnlockGCThing( jsContext, skillsObj ); JS_UnlockGCThing( jsContext, accountsObj ); JS_UnlockGCThing( jsContext, consoleObj ); JS_UnlockGCThing( jsContext, createEntriesObj ); JS_UnlockGCThing( jsContext, timerObj ); for( size_t i = JSP_ITEM; i < JSP_COUNT; ++i ) { JS_UnlockGCThing( jsContext, protoList[i] ); } JS_UnlockGCThing( jsContext, jsGlobal ); JS_DestroyContext( jsContext ); JS_DestroyRuntime( jsRuntime ); } void CJSRuntime::Cleanup( void ) { std::vector::iterator oIter; for( oIter = objectList.begin(); oIter != objectList.end(); ++oIter ) { JSOBJECTMAP& ourList = ( *oIter ); for( JSOBJECTMAP_ITERATOR lIter = ourList.begin(); lIter != ourList.end(); ++lIter ) { JS_UnlockGCThing( jsContext, ( *lIter ).second ); JS_SetPrivate( jsContext, ( *lIter ).second, nullptr ); } ourList.clear(); } objectList.resize( 0 ); } void CJSRuntime::Reload() { Cleanup(); objectList.resize( IUE_COUNT ); } void CJSRuntime::CollectGarbage() { JS_GC( jsContext ); } void setupMap( std::map< std::string, intN >& lkpMap, const JSPropertySpec lkpProps[], int countOfProps ) { lkpMap.clear(); for( int i = 0; i < countOfProps; ++i ) { if( lkpProps[i].name != nullptr) { lkpMap[lkpProps[i].name] = lkpProps[i].tinyid; } } } void CJSRuntime::InitializePrototypes() { protoList.resize( JSP_COUNT ); JSContext *cx = jsContext; JSObject *obj = jsGlobal; protoList[JSP_CHAR] = JS_InitClass( cx, obj, nullptr, &UOXChar_class, nullptr, 0, CCharacterProps, CChar_Methods, nullptr, nullptr ); protoList[JSP_ITEM] = JS_InitClass( cx, obj, nullptr, &UOXItem_class, nullptr, 0, CItemProps, CItem_Methods, nullptr, nullptr ); protoList[JSP_SPELL] = JS_InitClass( cx, obj, nullptr, &UOXSpell_class, nullptr, 0, CSpellProperties, nullptr, nullptr, nullptr ); protoList[JSP_SPELLS] = JS_InitClass( cx, obj, nullptr, &UOXSpells_class, nullptr, 0, nullptr, nullptr, nullptr, nullptr ); protoList[JSP_GLOBALSKILL] = JS_InitClass( cx, obj, nullptr, &UOXGlobalSkill_class, nullptr, 0, CGlobalSkillProperties, nullptr, nullptr, nullptr ); protoList[JSP_GLOBALSKILLS] = JS_InitClass( cx, obj, nullptr, &UOXGlobalSkills_class, nullptr, 0, nullptr, nullptr, nullptr, nullptr ); protoList[JSP_CREATEENTRY] = JS_InitClass( cx, obj, nullptr, &UOXCreateEntry_class, nullptr, 0, CCreateEntryProperties, nullptr, nullptr, nullptr ); protoList[JSP_CREATEENTRIES] = JS_InitClass( cx, obj, nullptr, &UOXCreateEntries_class, nullptr, 0, nullptr, nullptr, nullptr, nullptr ); protoList[JSP_TIMER] = JS_InitClass( cx, obj, nullptr, &UOXTimer_class, nullptr, 0, CTimerProperties, nullptr, nullptr, nullptr ); protoList[JSP_SOCK] = JS_InitClass( cx, obj, nullptr, &UOXSocket_class, nullptr, 0, CSocketProps, CSocket_Methods, nullptr, nullptr ); protoList[JSP_ACCOUNTS] = JS_InitClass( cx, obj, nullptr, &UOXAccount_class, nullptr, 0, CAccountProperties, CAccount_Methods, nullptr, nullptr ); protoList[JSP_CONSOLE] = JS_InitClass( cx, obj, nullptr, &UOXConsole_class, nullptr, 0, CConsoleProperties, CConsole_Methods, nullptr, nullptr ); protoList[JSP_REGION] = JS_InitClass( cx, obj, nullptr, &UOXRegion_class, nullptr, 0, CRegionProperties, CRegion_Methods, nullptr, nullptr ); protoList[JSP_SPAWNREGION]= JS_InitClass( cx, obj, nullptr, &UOXSpawnRegion_class, nullptr, 0, CSpawnRegionProperties, nullptr, nullptr, nullptr ); protoList[JSP_RESOURCE] = JS_InitClass( cx, obj, nullptr, &UOXResource_class, nullptr, 0, CResourceProperties, nullptr, nullptr, nullptr ); protoList[JSP_RACE] = JS_InitClass( cx, obj, nullptr, &UOXRace_class, nullptr, 0, CRaceProperties, CRace_Methods, nullptr, nullptr ); protoList[JSP_GUILD] = JS_InitClass( cx, obj, nullptr, &UOXGuild_class, nullptr, 0, CGuildProperties, CGuild_Methods, nullptr, nullptr ); protoList[JSP_PARTY] = JS_InitClass( cx, obj, nullptr, &UOXParty_class, nullptr, 0, CPartyProperties, CParty_Methods, nullptr, nullptr ); protoList[JSP_PACKET] = JS_InitClass( cx, obj, nullptr, &UOXPacket_class, Packet, 0, nullptr, nullptr, nullptr, nullptr ); protoList[JSP_GUMP] = JS_InitClass( cx, obj, nullptr, &UOXGump_class, Gump, 0, nullptr, nullptr, nullptr, nullptr ); protoList[JSP_FILE] = JS_InitClass( cx, obj, nullptr, &UOXFile_class, UOXCFile, 0, nullptr, nullptr, nullptr, nullptr ); protoList[JSP_SCRIPT] = JS_InitClass( cx, obj, nullptr, &uox_class, nullptr, 0, CScriptProperties, nullptr, nullptr, nullptr ); spellsObj = JS_DefineObject( cx, obj, "Spells", &UOXSpells_class, protoList[JSP_SPELLS], 0 ); skillsObj = JS_DefineObject( cx, obj, "Skills", &UOXGlobalSkills_class, protoList[JSP_GLOBALSKILLS], 0 ); accountsObj = JS_DefineObject( cx, obj, "Accounts", &UOXAccount_class, protoList[JSP_ACCOUNTS], 0 ); consoleObj = JS_DefineObject( cx, obj, "Console", &UOXConsole_class, protoList[JSP_CONSOLE], 0 ); createEntriesObj = JS_DefineObject( cx, obj, "CreateEntries", &UOXCreateEntries_class, protoList[JSP_CREATEENTRIES], 0 ); timerObj = JS_DefineObject( cx, obj, "Timer", &UOXTimer_class, protoList[JSP_TIMER], 0 ); JS_LockGCThing( cx, spellsObj ); JS_LockGCThing( cx, skillsObj ); JS_LockGCThing( cx, accountsObj ); JS_LockGCThing( cx, consoleObj ); JS_LockGCThing( cx, createEntriesObj ); JS_LockGCThing( cx, timerObj ); for( size_t i = JSP_ITEM; i < JSP_COUNT; ++i ) { JS_LockGCThing( cx, protoList[i] ); } setupMap( propLookupAccount, CAccountProperties, std::size( CAccountProperties ) ); setupMap( propLookupChar, CCharacterProps, std::size( CCharacterProps ) ); setupMap( propLookupConsole, CConsoleProperties, std::size( CConsoleProperties ) ); setupMap( propLookupGuild, CGuildProperties, std::size( CGuildProperties ) ); setupMap( propLookupItem, CItemProps, std::size( CItemProps ) ); setupMap( propLookupParty, CPartyProperties, std::size( CPartyProperties ) ); setupMap( propLookupRace, CRaceProperties, std::size( CRaceProperties ) ); setupMap( propLookupRegion, CRegionProperties, std::size( CRegionProperties ) ); setupMap( propLookupResource, CResourceProperties, std::size( CResourceProperties ) ); setupMap( propLookupSkills, CSkillsProps, std::size( CSkillsProps ) ); setupMap( propLookupSocket, CSocketProps, std::size( CSocketProps ) ); setupMap( propLookupSpawnRegion, CSpawnRegionProperties, std::size( CSpawnRegionProperties ) ); } JSRuntime *CJSRuntime::GetRuntime( void ) const { return jsRuntime; } JSContext *CJSRuntime::GetContext( void ) const { return jsContext; } JSObject *CJSRuntime::GetObject( void ) const { return jsGlobal; } JSObject *CJSRuntime::GetPrototype( JSPrototypes protoNum ) const { JSObject *retVal = nullptr; if( protoNum != JSP_COUNT ) { retVal = protoList[protoNum]; } return retVal; } JSObject *CJSRuntime::AcquireObject( IUEEntries iType, void *index ) { JSObject *retVal = nullptr; if( iType != IUE_COUNT && static_cast( iType ) < objectList.size() ) { retVal = FindAssociatedObject( iType, index ); if( retVal == nullptr ) { retVal = MakeNewObject( iType ); if( retVal != nullptr ) { objectList[iType][index] = retVal; JS_SetPrivate( jsContext, retVal, index ); } } } return retVal; } void CJSRuntime::ReleaseObject( IUEEntries iType, void *index ) { JSOBJECTMAP_ITERATOR toSearch = objectList[iType].find( index ); if( toSearch != objectList[iType].end() ) { JSObject *toRelease = ( *toSearch ).second; JS_UnlockGCThing( jsContext, toRelease ); //JS_RemoveRoot( jsContext, &toRelease ); JS_SetPrivate( jsContext, toRelease, nullptr ); objectList[iType].erase( toSearch ); } } JSObject *CJSRuntime::FindAssociatedObject( IUEEntries iType, void *index ) { JSObject *retVal = nullptr; JSOBJECTMAP_CITERATOR toSearch = objectList[iType].find( index ); if( toSearch != objectList[iType].end() ) { retVal = ( *toSearch ).second; } return retVal; } JSObject *CJSRuntime::MakeNewObject( IUEEntries iType ) { JSObject *toMake = nullptr; switch( iType ) { case IUE_RACE: toMake = JS_NewObject( jsContext, &UOXRace_class, protoList[JSP_RACE], jsGlobal ); break; case IUE_CHAR: toMake = JS_NewObject( jsContext, &UOXChar_class, protoList[JSP_CHAR], jsGlobal ); break; case IUE_ITEM: toMake = JS_NewObject( jsContext, &UOXItem_class, protoList[JSP_ITEM], jsGlobal ); break; case IUE_SOCK: toMake = JS_NewObject( jsContext, &UOXSocket_class, protoList[JSP_SOCK], jsGlobal ); break; case IUE_GUILD: toMake = JS_NewObject( jsContext, &UOXGuild_class, protoList[JSP_GUILD], jsGlobal ); break; case IUE_REGION: toMake = JS_NewObject( jsContext, &UOXRegion_class, protoList[JSP_REGION], jsGlobal ); break; case IUE_SPAWNREGION: toMake = JS_NewObject( jsContext, &UOXSpawnRegion_class, protoList[JSP_SPAWNREGION], jsGlobal ); break; case IUE_PARTY: toMake = JS_NewObject( jsContext, &UOXParty_class, protoList[JSP_PARTY], jsGlobal ); break; case IUE_ACCOUNT: toMake = JS_NewObject( jsContext, &UOXAccount_class, protoList[JSP_ACCOUNT], jsGlobal ); break; default: case IUE_COUNT: return nullptr; } if( toMake == nullptr ) return nullptr; // DAMN! Using the deprecated function it works! JS_LockGCThing( jsContext, toMake ); //JS_AddRoot( jsContext, &toMake ); return toMake; }