diff --git a/source/graphics/MapGenerator.cpp b/source/graphics/MapGenerator.cpp index 8a058a6dfb..3d472a7595 100644 --- a/source/graphics/MapGenerator.cpp +++ b/source/graphics/MapGenerator.cpp @@ -39,8 +39,8 @@ #include #include -// TODO: what's a good default? perhaps based on map size -#define RMS_RUNTIME_SIZE 96 * 1024 * 1024 +// TODO: Maybe this should be optimized depending on the map size. +constexpr int RMS_RUNTIME_SIZE = 96 * 1024 * 1024; extern bool IsQuitRequested(); @@ -89,7 +89,7 @@ void* CMapGeneratorWorker::RunThread(CMapGeneratorWorker* self) debug_SetThreadName("MapGenerator"); g_Profiler2.RegisterCurrentThread("MapGenerator"); - shared_ptr mapgenRuntime = ScriptInterface::CreateRuntime(g_ScriptRuntime, RMS_RUNTIME_SIZE); + shared_ptr mapgenRuntime = ScriptRuntime::CreateRuntime(RMS_RUNTIME_SIZE); // Enable the script to be aborted JS_SetInterruptCallback(mapgenRuntime->m_rt, MapGeneratorInterruptCallback); diff --git a/source/network/NetServer.cpp b/source/network/NetServer.cpp index e1e8ccae23..4661a28988 100644 --- a/source/network/NetServer.cpp +++ b/source/network/NetServer.cpp @@ -388,7 +388,8 @@ void CNetServerWorker::Run() // To avoid the need for JS_SetContextThread, we create and use and destroy // the script interface entirely within this network thread - m_ScriptInterface = new ScriptInterface("Engine", "Net server", ScriptInterface::CreateRuntime(g_ScriptRuntime)); + shared_ptr netServerRuntime = ScriptRuntime::CreateRuntime(); + m_ScriptInterface = new ScriptInterface("Engine", "Net server", netServerRuntime); m_GameAttributes.init(m_ScriptInterface->GetJSRuntime(), JS::UndefinedValue()); while (true) diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index 6b478e3c2b..fcaba97a21 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -109,7 +109,7 @@ bool g_DoRenderGui = true; bool g_DoRenderLogger = true; bool g_DoRenderCursor = true; -shared_ptr g_ScriptRuntime; +thread_local shared_ptr g_ScriptRuntime; static const int SANE_TEX_QUALITY_DEFAULT = 5; // keep in sync with code @@ -895,7 +895,7 @@ bool Init(const CmdLineArgs& args, int flags) // their own threads and also their own runtimes. const int runtimeSize = 384 * 1024 * 1024; const int heapGrowthBytesGCTrigger = 20 * 1024 * 1024; - g_ScriptRuntime = ScriptInterface::CreateRuntime(shared_ptr(), runtimeSize, heapGrowthBytesGCTrigger); + g_ScriptRuntime = ScriptRuntime::CreateRuntime(runtimeSize, heapGrowthBytesGCTrigger); Mod::CacheEnabledModVersions(g_ScriptRuntime); diff --git a/source/ps/Replay.cpp b/source/ps/Replay.cpp index ce6a138eb2..eac5943b8b 100644 --- a/source/ps/Replay.cpp +++ b/source/ps/Replay.cpp @@ -208,7 +208,7 @@ void CReplayPlayer::Replay(const bool serializationtest, const int rejointesttur const int runtimeSize = 384 * 1024 * 1024; const int heapGrowthBytesGCTrigger = 20 * 1024 * 1024; - g_ScriptRuntime = ScriptInterface::CreateRuntime(shared_ptr(), runtimeSize, heapGrowthBytesGCTrigger); + g_ScriptRuntime = ScriptRuntime::CreateRuntime(runtimeSize, heapGrowthBytesGCTrigger); Mod::CacheEnabledModVersions(g_ScriptRuntime); diff --git a/source/scriptinterface/NativeWrapperDefns.h b/source/scriptinterface/NativeWrapperDefns.h index 6650fb4bf8..d0503f33a2 100644 --- a/source/scriptinterface/NativeWrapperDefns.h +++ b/source/scriptinterface/NativeWrapperDefns.h @@ -183,8 +183,7 @@ bool ScriptInterface::CallFunction(JS::HandleValue val, const char* name, R& ret JS::AutoValueVector argv(cx); argv.resize(sizeof...(Ts)); AssignOrToJSValHelper<0>(cx, argv, params...); - bool ok = CallFunction_(val, name, argv, &jsRet); - if (!ok) + if (!CallFunction_(val, name, argv, &jsRet)) return false; return FromJSVal(cx, jsRet, ret); } diff --git a/source/scriptinterface/ScriptInterface.cpp b/source/scriptinterface/ScriptInterface.cpp index c0f29539e8..706925fe8b 100644 --- a/source/scriptinterface/ScriptInterface.cpp +++ b/source/scriptinterface/ScriptInterface.cpp @@ -64,7 +64,7 @@ struct ScriptInterface_impl JSContext* m_cx; JS::PersistentRootedObject m_glob; // global scope object - JSCompartment* m_comp; + JSCompartment* m_formerCompartment; boost::rand48* m_rng; JS::PersistentRootedObject m_nativeScope; // native function scope object }; @@ -333,8 +333,6 @@ bool ScriptInterface::MathRandom(double& nbr) ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const shared_ptr& runtime) : m_runtime(runtime), m_glob(runtime->m_rt), m_nativeScope(runtime->m_rt) { - bool ok; - m_cx = JS_NewContext(m_runtime->m_rt, STACK_CHUNK_SIZE); ENSURE(m_cx); @@ -362,9 +360,8 @@ ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const sh JSAutoRequest rq(m_cx); JS::RootedObject globalRootedVal(m_cx, JS_NewGlobalObject(m_cx, &global_class, NULL, JS::OnNewGlobalHookOption::FireOnNewGlobalHook, opt)); - m_comp = JS_EnterCompartment(m_cx, globalRootedVal); - ok = JS_InitStandardClasses(m_cx, globalRootedVal); - ENSURE(ok); + m_formerCompartment = JS_EnterCompartment(m_cx, globalRootedVal); + ENSURE(JS_InitStandardClasses(m_cx, globalRootedVal)); m_glob = globalRootedVal.get(); JS_DefineProperty(m_cx, m_glob, "global", globalRootedVal, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); @@ -390,7 +387,7 @@ ScriptInterface_impl::~ScriptInterface_impl() m_runtime->UnRegisterContext(m_cx); { JSAutoRequest rq(m_cx); - JS_LeaveCompartment(m_cx, m_comp); + JS_LeaveCompartment(m_cx, m_formerCompartment); } JS_DestroyContext(m_cx); } @@ -563,9 +560,7 @@ bool ScriptInterface::CallFunction_(JS::HandleValue val, const char* name, JS::H if (!JS_HasProperty(m->m_cx, obj, name, &found) || !found) return false; - bool ok = JS_CallFunctionName(m->m_cx, obj, name, argv, ret); - - return ok; + return JS_CallFunctionName(m->m_cx, obj, name, argv, ret); } bool ScriptInterface::CreateObject_(JSContext* cx, JS::MutableHandleObject object) @@ -848,11 +843,6 @@ bool ScriptInterface::LoadScript(const VfsPath& filename, const std::string& cod return JS_CallFunction(m->m_cx, nullptr, func, JS::HandleValueArray::empty(), &rval); } -shared_ptr ScriptInterface::CreateRuntime(shared_ptr parentRuntime, int runtimeSize, int heapGrowthBytesGCTrigger) -{ - return shared_ptr(new ScriptRuntime(parentRuntime, runtimeSize, heapGrowthBytesGCTrigger)); -} - bool ScriptInterface::LoadGlobalScript(const VfsPath& filename, const std::wstring& code) const { JSAutoRequest rq(m->m_cx); diff --git a/source/scriptinterface/ScriptInterface.h b/source/scriptinterface/ScriptInterface.h index e44d5ae2f6..b33177eca9 100644 --- a/source/scriptinterface/ScriptInterface.h +++ b/source/scriptinterface/ScriptInterface.h @@ -48,15 +48,13 @@ ERROR_TYPE(Scripting_DefineType, CreationFailed); // but as large as necessary for all wrapped functions) #define SCRIPT_INTERFACE_MAX_ARGS 8 -// TODO: what's a good default? -#define DEFAULT_RUNTIME_SIZE 16 * 1024 * 1024 -#define DEFAULT_HEAP_GROWTH_BYTES_GCTRIGGER 2 * 1024 *1024 - struct ScriptInterface_impl; class ScriptRuntime; -extern shared_ptr g_ScriptRuntime; +// Using a global object for the runtime is a workaround until Simulation, AI, etc, +// use their own threads and also their own runtimes. +extern thread_local shared_ptr g_ScriptRuntime; /** @@ -73,17 +71,6 @@ class ScriptInterface public: - /** - * Returns a runtime, which can used to initialise any number of - * ScriptInterfaces contexts. Values created in one context may be used - * in any other context from the same runtime (but not any other runtime). - * Each runtime should only ever be used on a single thread. - * @param runtimeSize Maximum size in bytes of the new runtime - */ - static shared_ptr CreateRuntime(shared_ptr parentRuntime = shared_ptr(), int runtimeSize = DEFAULT_RUNTIME_SIZE, - int heapGrowthBytesGCTrigger = DEFAULT_HEAP_GROWTH_BYTES_GCTRIGGER); - - /** * Constructor. * @param nativeScopeName Name of global object that functions (via RegisterFunction) will @@ -442,7 +429,7 @@ private: void Register(const char* name, JSNative fptr, size_t nargs) const; // Take care to keep this declaration before heap rooted members. Destructors of heap rooted - // members have to be called before the runtime destructor. + // members have to be called before the custom destructor of ScriptInterface_impl. std::unique_ptr m; boost::rand48* m_rng; diff --git a/source/scriptinterface/ScriptRuntime.cpp b/source/scriptinterface/ScriptRuntime.cpp index 490ce4409b..d50bec7b56 100644 --- a/source/scriptinterface/ScriptRuntime.cpp +++ b/source/scriptinterface/ScriptRuntime.cpp @@ -89,7 +89,12 @@ void GCSliceCallbackHook(JSRuntime* UNUSED(rt), JS::GCProgress progress, const J #endif } -ScriptRuntime::ScriptRuntime(shared_ptr parentRuntime, int runtimeSize, int heapGrowthBytesGCTrigger): +shared_ptr ScriptRuntime::CreateRuntime(int runtimeSize, int heapGrowthBytesGCTrigger) +{ + return shared_ptr(new ScriptRuntime(runtimeSize, heapGrowthBytesGCTrigger)); +} + +ScriptRuntime::ScriptRuntime(int runtimeSize, int heapGrowthBytesGCTrigger): m_LastGCBytes(0), m_LastGCCheck(0.0f), m_HeapGrowthBytesGCTrigger(heapGrowthBytesGCTrigger), @@ -97,8 +102,7 @@ ScriptRuntime::ScriptRuntime(shared_ptr parentRuntime, int runtim { ENSURE(ScriptEngine::IsInitialised() && "The ScriptEngine must be initialized before constructing any ScriptRuntimes!"); - JSRuntime* parentJSRuntime = parentRuntime ? parentRuntime->m_rt : nullptr; - m_rt = JS_NewRuntime(runtimeSize, JS::DefaultNurseryBytes, parentJSRuntime); + m_rt = JS_NewRuntime(runtimeSize, JS::DefaultNurseryBytes, nullptr); ENSURE(m_rt); // TODO: error handling JS::SetGCSliceCallback(m_rt, GCSliceCallbackHook); diff --git a/source/scriptinterface/ScriptRuntime.h b/source/scriptinterface/ScriptRuntime.h index a530563bb2..1e4d61e82c 100644 --- a/source/scriptinterface/ScriptRuntime.h +++ b/source/scriptinterface/ScriptRuntime.h @@ -23,7 +23,11 @@ #include -#define STACK_CHUNK_SIZE 8192 +constexpr int STACK_CHUNK_SIZE = 8192; + +// Those are minimal defaults. The runtime for the main game is larger and GCs upon a larger growth. +constexpr int DEFAULT_RUNTIME_SIZE = 16 * 1024 * 1024; +constexpr int DEFAULT_HEAP_GROWTH_BYTES_GCTRIGGER = 2 * 1024 * 1024; /** * Abstraction around a SpiderMonkey JSRuntime. @@ -38,9 +42,21 @@ class ScriptRuntime { public: - ScriptRuntime(shared_ptr parentRuntime, int runtimeSize, int heapGrowthBytesGCTrigger); + ScriptRuntime(int runtimeSize, int heapGrowthBytesGCTrigger); ~ScriptRuntime(); + /** + * Returns a runtime, which can used to initialise any number of + * ScriptInterfaces contexts. Values created in one context may be used + * in any other context from the same runtime (but not any other runtime). + * Each runtime should only ever be used on a single thread. + * @param runtimeSize Maximum size in bytes of the new runtime + * @param heapGrowthBytesGCTrigger Size in bytes of cumulated allocations after which a GC will be triggered + */ + static shared_ptr CreateRuntime( + int runtimeSize = DEFAULT_RUNTIME_SIZE, + int heapGrowthBytesGCTrigger = DEFAULT_HEAP_GROWTH_BYTES_GCTRIGGER); + /** * MaybeIncrementalRuntimeGC tries to determine whether a runtime-wide garbage collection would free up enough memory to * be worth the amount of time it would take. It does this with our own logic and NOT some predefined JSAPI logic because diff --git a/source/test_setup.cpp b/source/test_setup.cpp index 8b8c40b581..d5b137c9c9 100644 --- a/source/test_setup.cpp +++ b/source/test_setup.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -37,7 +37,7 @@ #include "lib/sysdep/sysdep.h" #include "ps/Profiler2.h" #include "scriptinterface/ScriptEngine.h" -#include "scriptinterface/ScriptInterface.h" +#include "scriptinterface/ScriptRuntime.h" class LeakReporter : public CxxTest::GlobalFixture { @@ -80,7 +80,7 @@ class MiscSetup : public CxxTest::GlobalFixture g_Profiler2.Initialise(); m_ScriptEngine = new ScriptEngine; - g_ScriptRuntime = ScriptInterface::CreateRuntime(); + g_ScriptRuntime = ScriptRuntime::CreateRuntime(); return true; }