uox3/source/CJSEngine.cpp
Charles Kerr 87af0d8cbb destructor cleanup
speed up shutdown.  Process termination will free up the memory (in fact default on OS x.
2022-06-17 21:38:30 -04:00

404 lines
15 KiB
C++

//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 <algorithm>
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<UI16>(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<UI32>( static_cast<UI32>(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<UI08>(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.7 as version to unlock const variables, let, etc
JS_SetVersion( jsContext, JSVERSION_1_7 );
if( jsContext == nullptr )
Shutdown( FATAL_UOX3_JAVASCRIPT );
jsGlobal = JS_NewObject( jsContext, &global_class, nullptr, nullptr );
if( jsGlobal == nullptr )
Shutdown( FATAL_UOX3_JAVASCRIPT );
JS_LockGCThing( jsContext, jsGlobal );
//JS_AddRoot( jsContext, &jsGlobal );
JS_InitStandardClasses( jsContext, jsGlobal );
objectList.resize( IUE_COUNT );
InitializePrototypes();
}
CJSRuntime::~CJSRuntime( void )
{
Cleanup();
JS_UnlockGCThing( jsContext, spellsObj );
//JS_RemoveRoot( jsContext, &spellsObj );
JS_UnlockGCThing( jsContext, skillsObj );
//JS_RemoveRoot( jsContext, &skillsObj );
JS_UnlockGCThing( jsContext, accountsObj );
//JS_RemoveRoot( jsContext, &accountsObj );
JS_UnlockGCThing( jsContext, consoleObj );
//JS_RemoveRoot( jsContext, &consoleObj );
JS_UnlockGCThing( jsContext, createEntriesObj );
//JS_RemoveRoot( jsContext, &createEntriesObj );
JS_UnlockGCThing( jsContext, timerObj );
//JS_RemoveRoot( jsContext, &timersObj );
for( size_t i = JSP_ITEM; i < JSP_COUNT; ++i )
{
JS_UnlockGCThing( jsContext, protoList[i] );
//JS_RemoveRoot( jsContext, &protoList[i] );
}
JS_UnlockGCThing( jsContext, jsGlobal );
//JS_RemoveRoot( jsContext, &jsGlobal );
JS_DestroyContext( jsContext );
JS_DestroyRuntime( jsRuntime );
}
void CJSRuntime::Cleanup( void )
{
std::vector< JSOBJECTMAP >::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_RemoveRoot( 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 CJSRuntime::InitializePrototypes()
{
protoList.resize( JSP_COUNT );
JSContext *cx = jsContext;
JSObject *obj = jsGlobal;
protoList[JSP_CHAR] = JS_InitClass( cx, obj, nullptr, &UOXChar_class.base, nullptr, 0, CCharacterProps, CChar_Methods, nullptr, nullptr );
protoList[JSP_ITEM] = JS_InitClass( cx, obj, nullptr, &UOXItem_class.base, 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.base, 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.base, 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 );
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_AddRoot( cx, &spellsObj );
JS_LockGCThing( cx, skillsObj );
//JS_AddRoot( cx, &skillsObj );
JS_LockGCThing( cx, accountsObj );
//JS_AddRoot( cx, &accountsObj );
JS_LockGCThing( cx, consoleObj );
//JS_AddRoot( cx, &consoleObj );
JS_LockGCThing( cx, createEntriesObj );
//JS_AddRoot( cx, &createEntriesObj );
JS_LockGCThing( cx, timerObj );
//JS_AddRoot( cx, &timersObj );
for( size_t i = JSP_ITEM; i < JSP_COUNT; ++i ) {
JS_LockGCThing( cx, protoList[i] );
//JS_AddRoot( cx, &protoList[i] );
}
}
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<size_t>(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 );
if( toMake == nullptr )
return nullptr;
//JS_DefineFunctions( jsContext, toMake, CRace_Methods );
//JS_DefineProperties( jsContext, toMake, CRaceProperties );
break;
case IUE_CHAR:
toMake = JS_NewObject( jsContext, &UOXChar_class.base, protoList[JSP_CHAR], jsGlobal );
if( toMake == nullptr )
return nullptr;
//JS_DefineProperties( jsContext, toMake, CCharacterProps );
//JS_DefineFunctions( jsContext, toMake, CChar_Methods );
break;
case IUE_ITEM:
toMake = JS_NewObject( jsContext, &UOXItem_class.base, protoList[JSP_ITEM], jsGlobal );
if( toMake == nullptr )
return nullptr;
//JS_DefineFunctions( jsContext, toMake, CItem_Methods );
//JS_DefineProperties( jsContext, toMake, CItemProps );
break;
case IUE_SOCK:
toMake = JS_NewObject( jsContext, &UOXSocket_class.base, protoList[JSP_SOCK], jsGlobal );
if( toMake == nullptr )
return nullptr;
//JS_DefineFunctions( jsContext, toMake, CSocket_Methods );
//JS_DefineProperties( jsContext, toMake, CSocketProps );
break;
case IUE_GUILD:
toMake = JS_NewObject( jsContext, &UOXGuild_class, protoList[JSP_GUILD], jsGlobal );
if( toMake == nullptr )
return nullptr;
//JS_DefineFunctions( jsContext, toMake, CGuild_Methods );
//JS_DefineProperties( jsContext, toMake, CGuildProperties );
break;
case IUE_REGION:
toMake = JS_NewObject( jsContext, &UOXRegion_class, protoList[JSP_REGION], jsGlobal );
if( toMake == nullptr )
return nullptr;
//JS_DefineFunctions( jsContext, toMake, CRegion_Methods );
//JS_DefineProperties( jsContext, toMake, CRegionProperties );
break;
case IUE_SPAWNREGION:
toMake = JS_NewObject( jsContext, &UOXSpawnRegion_class, protoList[JSP_SPAWNREGION], jsGlobal );
if( toMake == nullptr )
return nullptr;
//JS_DefineFunctions( jsContext, toMake, CSpawnRegion_Methods );
//JS_DefineProperties( jsContext, toMake, CSpawnRegionProperties );
break;
case IUE_PARTY:
toMake = JS_NewObject( jsContext, &UOXParty_class.base, protoList[JSP_PARTY], jsGlobal );
if( toMake == nullptr )
return nullptr;
//JS_DefineFunctions( jsContext, toMake, CParty_Methods );
//JS_DefineProperties( jsContext, toMake, CPartyProperties );
break;
case IUE_ACCOUNT:
toMake = JS_NewObject( jsContext, &UOXAccount_class, protoList[JSP_ACCOUNT], jsGlobal );
if( toMake == nullptr )
return nullptr;
//JS_DefineFunctions( jsContext, toMake, CAccount_Methods );
//JS_DefineProperties( jsContext, toMake, CAccountProperties );
break;
default:
case IUE_COUNT:
return nullptr;
}
// DAMN! Using the deprecated function it works!
JS_LockGCThing( jsContext, toMake );
//JS_AddRoot( jsContext, &toMake );
return toMake;
}