diff --git a/source/main.cpp b/source/main.cpp index 5f894c1d7b..45f666b097 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -73,6 +73,7 @@ that of Atlas depending on commandline parameters. #include "graphics/TextureManager.h" #include "gui/GUIManager.h" #include "renderer/Renderer.h" +#include "scriptinterface/ScriptEngine.h" #include "simulation2/Simulation2.h" #if OS_UNIX @@ -422,10 +423,10 @@ static void RunGameOrAtlas(int argc, const char* argv[]) return; } - // We need to initialise libxml2 in the main thread before - // any thread uses it. So initialise it here before we - // might run Atlas. + // We need to initialize libxml2 and SpiderMonkey in the main thread before + // any thread uses them. So initialize them here before we might run Atlas. CXeromyces::Startup(); + ScriptEngine scriptEngine; // Atlas handles the whole init/shutdown/etc sequence by itself; if (ATLAS_RunIfOnCmdLine(args, false)) diff --git a/source/scriptinterface/ScriptEngine.h b/source/scriptinterface/ScriptEngine.h new file mode 100644 index 0000000000..07c342cd71 --- /dev/null +++ b/source/scriptinterface/ScriptEngine.h @@ -0,0 +1,55 @@ +/* Copyright (C) 2016 Wildfire Games. + * 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 . + */ + +#ifndef INCLUDED_SCRIPTENGINE +#define INCLUDED_SCRIPTENGINE + +#include "ScriptTypes.h" +#include "ps/Singleton.h" + +/** + * A class using the RAII (Resource Acquisition Is Initialization) idiom to manage initialization + * and shutdown of the SpiderMonkey script engine. It also keeps a count of active script runtimes + * in order to validate the following constraints: + * 1. JS_Init must be called before any ScriptRuntimes are initialized + * 2. JS_Shutdown must be called after all ScriptRuntimes have been destroyed + */ + +class ScriptEngine : public Singleton +{ +public: + ScriptEngine() + { + ENSURE(m_Runtimes.size() == 0 && "JS_Init must be called before any runtimes are created!"); + JS_Init(); + } + + ~ScriptEngine() + { + ENSURE(m_Runtimes.size() == 0 && "All runtimes must be destroyed before calling JS_ShutDown!"); + JS_ShutDown(); + } + + void RegisterRuntime(const JSRuntime* rt) { m_Runtimes.push_back(rt); } + void UnRegisterRuntime(const JSRuntime* rt) { m_Runtimes.remove(rt); } + +private: + + std::list m_Runtimes; +}; + +#endif // INCLUDED_SCRIPTENGINE diff --git a/source/scriptinterface/ScriptInterface.cpp b/source/scriptinterface/ScriptInterface.cpp index 9bad7a38b7..5e5eba50a3 100644 --- a/source/scriptinterface/ScriptInterface.cpp +++ b/source/scriptinterface/ScriptInterface.cpp @@ -433,11 +433,6 @@ ScriptInterface::~ScriptInterface() } } -void ScriptInterface::ShutDown() -{ - JS_ShutDown(); -} - void ScriptInterface::SetCallbackData(void* pCBData) { m_CxPrivate.pCBData = pCBData; diff --git a/source/scriptinterface/ScriptInterface.h b/source/scriptinterface/ScriptInterface.h index ac4698682e..9debed2609 100644 --- a/source/scriptinterface/ScriptInterface.h +++ b/source/scriptinterface/ScriptInterface.h @@ -97,12 +97,6 @@ public: ~ScriptInterface(); - /** - * Shut down the JS system to clean up memory. Must only be called when there - * are no ScriptInterfaces alive. - */ - static void ShutDown(); - struct CxPrivate { ScriptInterface* pScriptInterface; // the ScriptInterface object the current context belongs to diff --git a/source/scriptinterface/ScriptRuntime.cpp b/source/scriptinterface/ScriptRuntime.cpp index 65f87108e1..b678f00a6c 100644 --- a/source/scriptinterface/ScriptRuntime.cpp +++ b/source/scriptinterface/ScriptRuntime.cpp @@ -21,6 +21,7 @@ #include "ps/GameSetup/Config.h" #include "ps/Profile.h" +#include "scriptinterface/ScriptEngine.h" void GCSliceCallbackHook(JSRuntime* UNUSED(rt), JS::GCProgress progress, const JS::GCDescription& UNUSED(desc)) @@ -104,19 +105,13 @@ void ScriptRuntime::AddDeferredFinalizationObject(const std::shared_ptr& o m_FinalizationListObjectIdCache.push_back(obj); } -bool ScriptRuntime::m_Initialized = false; - ScriptRuntime::ScriptRuntime(shared_ptr parentRuntime, int runtimeSize, int heapGrowthBytesGCTrigger): m_LastGCBytes(0), m_LastGCCheck(0.0f), m_HeapGrowthBytesGCTrigger(heapGrowthBytesGCTrigger), m_RuntimeSize(runtimeSize) { - if (!m_Initialized) - { - ENSURE(JS_Init()); - m_Initialized = true; - } + 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_USE_HELPER_THREADS, parentJSRuntime); @@ -135,6 +130,8 @@ ScriptRuntime::ScriptRuntime(shared_ptr parentRuntime, int runtim m_dummyContext = JS_NewContext(m_rt, STACK_CHUNK_SIZE); ENSURE(m_dummyContext); + + ScriptEngine::GetSingleton().RegisterRuntime(m_rt); } ScriptRuntime::~ScriptRuntime() @@ -143,6 +140,9 @@ ScriptRuntime::~ScriptRuntime() JS_SetGCCallback(m_rt, nullptr, nullptr); JS_DestroyRuntime(m_rt); ENSURE(m_FinalizationListObjectIdCache.empty() && "Leak: Removing callback while some objects still aren't finalized!"); + + ENSURE(ScriptEngine::IsInitialised() && "The ScriptEngine must be active (initialized and not yet shut down) when destroying a ScriptRuntime!"); + ScriptEngine::GetSingleton().UnRegisterRuntime(m_rt); } void ScriptRuntime::RegisterContext(JSContext* cx) diff --git a/source/scriptinterface/ScriptRuntime.h b/source/scriptinterface/ScriptRuntime.h index 68fe21c8f7..fe531b3794 100644 --- a/source/scriptinterface/ScriptRuntime.h +++ b/source/scriptinterface/ScriptRuntime.h @@ -79,7 +79,6 @@ private: std::list m_Contexts; std::vector > m_FinalizationListObjectIdCache; - static bool m_Initialized; int m_RuntimeSize; int m_HeapGrowthBytesGCTrigger; diff --git a/source/test_setup.cpp b/source/test_setup.cpp index 8ad8fd09ea..b015655ec1 100644 --- a/source/test_setup.cpp +++ b/source/test_setup.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Wildfire Games. +/* Copyright (C) 2016 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -36,15 +36,13 @@ #include "lib/timer.h" #include "lib/sysdep/sysdep.h" #include "ps/Profiler2.h" +#include "scriptinterface/ScriptEngine.h" #include "scriptinterface/ScriptInterface.h" class LeakReporter : public CxxTest::GlobalFixture { virtual bool tearDownWorld() { - // Shut down JS to prevent leak reports from it - ScriptInterface::ShutDown(); - // Enable leak reporting on exit. // (This is done in tearDownWorld so that it doesn't report 'leaks' // if the program is aborted before finishing cleanly.) @@ -81,6 +79,7 @@ class MiscSetup : public CxxTest::GlobalFixture ThreadUtil::SetMainThread(); g_Profiler2.Initialise(); + m_ScriptEngine = new ScriptEngine; g_ScriptRuntime = ScriptInterface::CreateRuntime(); return true; @@ -89,10 +88,17 @@ class MiscSetup : public CxxTest::GlobalFixture virtual bool tearDownWorld() { g_ScriptRuntime.reset(); + SAFE_DELETE(m_ScriptEngine); g_Profiler2.Shutdown(); return true; } + +private: + + // We're doing the initialization and shutdown of the ScriptEngine explicitly here + // to make sure it's only initialized when setUpWorld is called. + ScriptEngine* m_ScriptEngine; }; static LeakReporter leakReporter;