Encapsulate runtime creation.

- Makes it easier to change down the line (and change is coming)
- Allows making g_ScriptRuntime thread-local easily.
- Remove ParentRuntime, which is not used at the moment.

Part of the SM52 migration, stage: SM45 compatible.

Patch by: Itms
Tested By: Freagarach
Refs #4893

Differential Revision: https://code.wildfiregames.com/D3087
This was SVN commit r24171.
This commit is contained in:
wraitii 2020-11-12 09:34:40 +00:00
parent 90367cdc53
commit 66cc595c53
10 changed files with 46 additions and 49 deletions

View File

@ -39,8 +39,8 @@
#include <string>
#include <vector>
// 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<ScriptRuntime> mapgenRuntime = ScriptInterface::CreateRuntime(g_ScriptRuntime, RMS_RUNTIME_SIZE);
shared_ptr<ScriptRuntime> mapgenRuntime = ScriptRuntime::CreateRuntime(RMS_RUNTIME_SIZE);
// Enable the script to be aborted
JS_SetInterruptCallback(mapgenRuntime->m_rt, MapGeneratorInterruptCallback);

View File

@ -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<ScriptRuntime> netServerRuntime = ScriptRuntime::CreateRuntime();
m_ScriptInterface = new ScriptInterface("Engine", "Net server", netServerRuntime);
m_GameAttributes.init(m_ScriptInterface->GetJSRuntime(), JS::UndefinedValue());
while (true)

View File

@ -109,7 +109,7 @@ bool g_DoRenderGui = true;
bool g_DoRenderLogger = true;
bool g_DoRenderCursor = true;
shared_ptr<ScriptRuntime> g_ScriptRuntime;
thread_local shared_ptr<ScriptRuntime> 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<ScriptRuntime>(), runtimeSize, heapGrowthBytesGCTrigger);
g_ScriptRuntime = ScriptRuntime::CreateRuntime(runtimeSize, heapGrowthBytesGCTrigger);
Mod::CacheEnabledModVersions(g_ScriptRuntime);

View File

@ -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<ScriptRuntime>(), runtimeSize, heapGrowthBytesGCTrigger);
g_ScriptRuntime = ScriptRuntime::CreateRuntime(runtimeSize, heapGrowthBytesGCTrigger);
Mod::CacheEnabledModVersions(g_ScriptRuntime);

View File

@ -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);
}

View File

@ -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<ScriptRuntime>& 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<ScriptRuntime> ScriptInterface::CreateRuntime(shared_ptr<ScriptRuntime> parentRuntime, int runtimeSize, int heapGrowthBytesGCTrigger)
{
return shared_ptr<ScriptRuntime>(new ScriptRuntime(parentRuntime, runtimeSize, heapGrowthBytesGCTrigger));
}
bool ScriptInterface::LoadGlobalScript(const VfsPath& filename, const std::wstring& code) const
{
JSAutoRequest rq(m->m_cx);

View File

@ -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<ScriptRuntime> 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<ScriptRuntime> 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<ScriptRuntime> CreateRuntime(shared_ptr<ScriptRuntime> parentRuntime = shared_ptr<ScriptRuntime>(), 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<ScriptInterface_impl> m;
boost::rand48* m_rng;

View File

@ -89,7 +89,12 @@ void GCSliceCallbackHook(JSRuntime* UNUSED(rt), JS::GCProgress progress, const J
#endif
}
ScriptRuntime::ScriptRuntime(shared_ptr<ScriptRuntime> parentRuntime, int runtimeSize, int heapGrowthBytesGCTrigger):
shared_ptr<ScriptRuntime> ScriptRuntime::CreateRuntime(int runtimeSize, int heapGrowthBytesGCTrigger)
{
return shared_ptr<ScriptRuntime>(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<ScriptRuntime> 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);

View File

@ -23,7 +23,11 @@
#include <sstream>
#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<ScriptRuntime> 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<ScriptRuntime> 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

View File

@ -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;
}