Index: ps/trunk/source/gui/GUIManager.cpp =================================================================== --- ps/trunk/source/gui/GUIManager.cpp (revision 19182) +++ ps/trunk/source/gui/GUIManager.cpp (revision 19183) @@ -1,452 +1,452 @@ -/* Copyright (C) 2016 Wildfire Games. +/* Copyright (C) 2017 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 . */ #include "precompiled.h" #include "GUIManager.h" #include "CGUI.h" #include "lib/timer.h" #include "ps/Filesystem.h" #include "ps/CLogger.h" #include "ps/Profile.h" #include "ps/XML/Xeromyces.h" #include "ps/GameSetup/Config.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptRuntime.h" CGUIManager* g_GUI = NULL; // General TODOs: // // A lot of the CGUI data could (and should) be shared between // multiple pages, instead of treating them as completely independent, to save // memory and loading time. // called from main loop when (input) events are received. // event is passed to other handlers if false is returned. // trampoline: we don't want to make the HandleEvent implementation static InReaction gui_handler(const SDL_Event_* ev) { PROFILE("GUI event handler"); return g_GUI->HandleEvent(ev); } static Status ReloadChangedFileCB(void* param, const VfsPath& path) { return static_cast(param)->ReloadChangedFile(path); } CGUIManager::CGUIManager() { m_ScriptRuntime = g_ScriptRuntime; m_ScriptInterface.reset(new ScriptInterface("Engine", "GUIManager", m_ScriptRuntime)); m_ScriptInterface->SetCallbackData(this); m_ScriptInterface->LoadGlobalScripts(); if (!CXeromyces::AddValidator(g_VFS, "gui_page", "gui/gui_page.rng")) LOGERROR("CGUIManager: failed to load GUI page grammar file 'gui/gui_page.rng'"); if (!CXeromyces::AddValidator(g_VFS, "gui", "gui/gui.rng")) LOGERROR("CGUIManager: failed to load GUI XML grammar file 'gui/gui.rng'"); RegisterFileReloadFunc(ReloadChangedFileCB, this); } CGUIManager::~CGUIManager() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); } bool CGUIManager::HasPages() { return !m_PageStack.empty(); } void CGUIManager::SwitchPage(const CStrW& pageName, ScriptInterface* srcScriptInterface, JS::HandleValue initData) { // The page stack is cleared (including the script context where initData came from), // therefore we have to clone initData. shared_ptr initDataClone; if (!initData.isUndefined()) { initDataClone = srcScriptInterface->WriteStructuredClone(initData); } m_PageStack.clear(); PushPage(pageName, initDataClone); } void CGUIManager::PushPage(const CStrW& pageName, shared_ptr initData) { m_PageStack.push_back(SGUIPage()); m_PageStack.back().name = pageName; m_PageStack.back().initData = initData; LoadPage(m_PageStack.back()); ResetCursor(); } void CGUIManager::PopPage() { if (m_PageStack.size() < 2) { debug_warn(L"Tried to pop GUI page when there's < 2 in the stack"); return; } m_PageStack.pop_back(); } void CGUIManager::PopPageCB(shared_ptr args) { shared_ptr initDataClone = m_PageStack.back().initData; PopPage(); shared_ptr scriptInterface = m_PageStack.back().gui->GetScriptInterface(); JSContext* cx = scriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue initDataVal(cx); if (!initDataClone) { LOGERROR("Called PopPageCB when initData (which should contain the callback function name) isn't set!"); return; } scriptInterface->ReadStructuredClone(initDataClone, &initDataVal); if (!scriptInterface->HasProperty(initDataVal, "callback")) { LOGERROR("Called PopPageCB when the callback function name isn't set!"); return; } std::string callback; if (!scriptInterface->GetProperty(initDataVal, "callback", callback)) { LOGERROR("Failed to get the callback property as a string from initData in PopPageCB!"); return; } JS::RootedValue global(cx, scriptInterface->GetGlobalObject()); if (!scriptInterface->HasProperty(global, callback.c_str())) { LOGERROR("The specified callback function %s does not exist in the page %s", callback, utf8_from_wstring(m_PageStack.back().name)); return; } JS::RootedValue argVal(cx); if (args) scriptInterface->ReadStructuredClone(args, &argVal); if (!scriptInterface->CallFunctionVoid(global, callback.c_str(), argVal)) { LOGERROR("Failed to call the callback function %s in the page %s", callback, utf8_from_wstring(m_PageStack.back().name)); return; } } void CGUIManager::DisplayMessageBox(int width, int height, const CStrW& title, const CStrW& message) { JSContext* cx = m_ScriptInterface->GetContext(); JSAutoRequest rq(cx); // Set up scripted init data for the standard message box window JS::RootedValue data(cx); m_ScriptInterface->Eval("({})", &data); m_ScriptInterface->SetProperty(data, "width", width, false); m_ScriptInterface->SetProperty(data, "height", height, false); m_ScriptInterface->SetProperty(data, "mode", 2, false); m_ScriptInterface->SetProperty(data, "title", std::wstring(title), false); m_ScriptInterface->SetProperty(data, "message", std::wstring(message), false); // Display the message box PushPage(L"page_msgbox.xml", m_ScriptInterface->WriteStructuredClone(data)); } void CGUIManager::LoadPage(SGUIPage& page) { // If we're hotloading then try to grab some data from the previous page shared_ptr hotloadData; if (page.gui) { shared_ptr scriptInterface = page.gui->GetScriptInterface(); JSContext* cx = scriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue global(cx, scriptInterface->GetGlobalObject()); JS::RootedValue hotloadDataVal(cx); scriptInterface->CallFunction(global, "getHotloadData", &hotloadDataVal); hotloadData = scriptInterface->WriteStructuredClone(hotloadDataVal); } page.inputs.clear(); page.gui.reset(new CGUI(m_ScriptRuntime)); page.gui->Initialize(); VfsPath path = VfsPath("gui") / page.name; page.inputs.insert(path); CXeromyces xero; if (xero.Load(g_VFS, path, "gui_page") != PSRETURN_OK) // Fail silently (Xeromyces reported the error) return; int elmt_page = xero.GetElementID("page"); int elmt_include = xero.GetElementID("include"); XMBElement root = xero.GetRoot(); if (root.GetNodeName() != elmt_page) { LOGERROR("GUI page '%s' must have root element ", utf8_from_wstring(page.name)); return; } XERO_ITER_EL(root, node) { if (node.GetNodeName() != elmt_include) { LOGERROR("GUI page '%s' must only have elements inside ", utf8_from_wstring(page.name)); continue; } std::string name = node.GetText(); CStrW nameW (node.GetText().FromUTF8()); PROFILE2("load gui xml"); PROFILE2_ATTR("name: %s", name.c_str()); TIMER(nameW.c_str()); if (name.back() == '/') { VfsPath directory = VfsPath("gui") / nameW; VfsPaths pathnames; vfs::GetPathnames(g_VFS, directory, L"*.xml", pathnames); for (const VfsPath& path : pathnames) page.gui->LoadXmlFile(path, page.inputs); } else { VfsPath path = VfsPath("gui") / nameW; page.gui->LoadXmlFile(path, page.inputs); } } // Remember this GUI page, in case the scripts call FindObjectByName shared_ptr oldGUI = m_CurrentGUI; m_CurrentGUI = page.gui; page.gui->SendEventToAll("load"); shared_ptr scriptInterface = page.gui->GetScriptInterface(); JSContext* cx = scriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue initDataVal(cx); JS::RootedValue hotloadDataVal(cx); JS::RootedValue global(cx, scriptInterface->GetGlobalObject()); if (page.initData) scriptInterface->ReadStructuredClone(page.initData, &initDataVal); if (hotloadData) scriptInterface->ReadStructuredClone(hotloadData, &hotloadDataVal); // Call the init() function if (!scriptInterface->CallFunctionVoid( global, "init", initDataVal, hotloadDataVal) ) { LOGERROR("GUI page '%s': Failed to call init() function", utf8_from_wstring(page.name)); } m_CurrentGUI = oldGUI; } Status CGUIManager::ReloadChangedFile(const VfsPath& path) { for (SGUIPage& p : m_PageStack) if (p.inputs.count(path)) { LOGMESSAGE("GUI file '%s' changed - reloading page '%s'", path.string8(), utf8_from_wstring(p.name)); LoadPage(p); // TODO: this can crash if LoadPage runs an init script which modifies the page stack and breaks our iterators } return INFO::OK; } Status CGUIManager::ReloadAllPages() { // TODO: this can crash if LoadPage runs an init script which modifies the page stack and breaks our iterators for (SGUIPage& p : m_PageStack) LoadPage(p); return INFO::OK; } void CGUIManager::ResetCursor() { g_CursorName = g_DefaultCursor; } std::string CGUIManager::GetSavedGameData() { shared_ptr scriptInterface = top()->GetScriptInterface(); JSContext* cx = scriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue data(cx); JS::RootedValue global(cx, top()->GetGlobalObject()); scriptInterface->CallFunction(global, "getSavedGameData", &data); return scriptInterface->StringifyJSON(&data, false); } void CGUIManager::RestoreSavedGameData(std::string jsonData) { shared_ptr scriptInterface = top()->GetScriptInterface(); JSContext* cx = scriptInterface->GetContext(); JSAutoRequest rq(cx); JS::RootedValue global(cx, top()->GetGlobalObject()); JS::RootedValue dataVal(cx); scriptInterface->ParseJSON(jsonData, &dataVal); scriptInterface->CallFunctionVoid(global, "restoreSavedGameData", dataVal); } InReaction CGUIManager::HandleEvent(const SDL_Event_* ev) { // We want scripts to have access to the raw input events, so they can do complex // processing when necessary (e.g. for unit selection and camera movement). // Sometimes they'll want to be layered behind the GUI widgets (e.g. to detect mousedowns on the // visible game area), sometimes they'll want to intercepts events before the GUI (e.g. // to capture all mouse events until a mouseup after dragging). // So we call two separate handler functions: bool handled; { PROFILE("handleInputBeforeGui"); JSContext* cx = top()->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); JS::RootedValue global(cx, top()->GetGlobalObject()); - if (top()->GetScriptInterface()->CallFunction(global, "handleInputBeforeGui", *ev, top()->FindObjectUnderMouse(), handled)) + if (top()->GetScriptInterface()->CallFunction(global, "handleInputBeforeGui", handled, *ev, top()->FindObjectUnderMouse())) if (handled) return IN_HANDLED; } { PROFILE("handle event in native GUI"); InReaction r = top()->HandleEvent(ev); if (r != IN_PASS) return r; } { // We can't take the following lines out of this scope because top() may be another gui page than it was when calling handleInputBeforeGui! JSContext* cx = top()->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); JS::RootedValue global(cx, top()->GetGlobalObject()); PROFILE("handleInputAfterGui"); - if (top()->GetScriptInterface()->CallFunction(global, "handleInputAfterGui", *ev, handled)) + if (top()->GetScriptInterface()->CallFunction(global, "handleInputAfterGui", handled, *ev)) if (handled) return IN_HANDLED; } return IN_PASS; } bool CGUIManager::GetPreDefinedColor(const CStr& name, CColor& output) const { return top()->GetPreDefinedColor(name, output); } IGUIObject* CGUIManager::FindObjectByName(const CStr& name) const { // This can be called from scripts run by TickObjects, // and we want to return it the same GUI page as is being ticked if (m_CurrentGUI) return m_CurrentGUI->FindObjectByName(name); else return top()->FindObjectByName(name); } void CGUIManager::SendEventToAll(const CStr& eventName) const { top()->SendEventToAll(eventName); } void CGUIManager::TickObjects() { PROFILE3("gui tick"); // We share the script runtime with everything else that runs in the same thread. // This call makes sure we trigger GC regularly even if the simulation is not running. m_ScriptInterface->GetRuntime()->MaybeIncrementalGC(1.0f); // Save an immutable copy so iterators aren't invalidated by tick handlers PageStackType pageStack = m_PageStack; for (const SGUIPage& p : pageStack) { m_CurrentGUI = p.gui; p.gui->TickObjects(); } m_CurrentGUI.reset(); } void CGUIManager::Draw() { PROFILE3_GPU("gui"); for (const SGUIPage& p : m_PageStack) p.gui->Draw(); } void CGUIManager::UpdateResolution() { for (const SGUIPage& p : m_PageStack) p.gui->UpdateResolution(); } bool CGUIManager::TemplateExists(const std::string& templateName) const { return m_TemplateLoader.TemplateExists(templateName); } const CParamNode& CGUIManager::GetTemplate(const std::string& templateName) { const CParamNode& templateRoot = m_TemplateLoader.GetTemplateFileData(templateName).GetChild("Entity"); if (!templateRoot.IsOk()) LOGERROR("Invalid template found for '%s'", templateName.c_str()); return templateRoot; } // This returns a shared_ptr to make sure the CGUI doesn't get deallocated // while we're in the middle of calling a function on it (e.g. if a GUI script // calls SwitchPage) shared_ptr CGUIManager::top() const { ENSURE(m_PageStack.size()); return m_PageStack.back().gui; } Index: ps/trunk/source/scriptinterface/NativeWrapperDecls.h =================================================================== --- ps/trunk/source/scriptinterface/NativeWrapperDecls.h (revision 19182) +++ ps/trunk/source/scriptinterface/NativeWrapperDecls.h (revision 19183) @@ -1,130 +1,111 @@ /* Copyright (C) 2017 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 . */ #include #include // MaybeRef should be private, but has to be public due to a compiler bug in clang. // TODO: Make this private when the bug is fixed in all supported versions of clang. template struct MaybeRef; // Define lots of useful macros: // Varieties of comma-separated list to fit on the head/tail/whole of another comma-separated list #define NUMBERED_LIST_HEAD(z, i, data) data##i, #define NUMBERED_LIST_TAIL(z, i, data) ,data##i #define NUMBERED_LIST_TAIL_MAYBE_REF(z, i, data) , typename MaybeRef::Type #define NUMBERED_LIST_BALANCED(z, i, data) BOOST_PP_COMMA_IF(i) data##i #define NUMBERED_LIST_BALANCED_MAYBE_REF(z, i, data) BOOST_PP_COMMA_IF(i) typename MaybeRef::Type -// Some other things -#define TYPED_ARGS(z, i, data) , T##i a##i -#define TYPED_ARGS_MAYBE_REF(z, i, data) , typename MaybeRef::Type a##i -#define TYPED_ARGS_CONST_REF(z, i, data) , const T##i& a##i // TODO: We allow optional parameters when the C++ type can be converted from JS::UndefinedValue. // FromJSVal is expected to either set a##i or return false (otherwise we could get undefined // behaviour because some types have undefined values when not being initialized). // This is not very clear and also a bit fragile. Another problem is that the error reporting lacks // a bit. SpiderMonkey will throw a JS exception and abort the execution of the current function when // we return false here (without printing a callstack or additional detail telling that an argument // conversion failed). So we have two TODOs here: // 1. On the conceptual side: How to consistently work with optional parameters (or drop them completely?) // 2. On the technical side: Improve error handling, find a better way to ensure parameters are initialized #define CONVERT_ARG(z, i, data) \ bool typeConvRet##i; \ T##i a##i = ScriptInterface::AssignOrFromJSVal( \ cx, \ i < args.length() ? args[i] : JS::UndefinedHandleValue, \ typeConvRet##i); \ if (!typeConvRet##i) return false; // List-generating macros, named roughly after their first list item #define TYPENAME_T0_HEAD(z, i) BOOST_PP_REPEAT_##z (i, NUMBERED_LIST_HEAD, typename T) // "typename T0, typename T1, " -#define TYPENAME_T0_TAIL(z, i) BOOST_PP_REPEAT_##z (i, NUMBERED_LIST_TAIL, typename T) // ", typename T0, typename T1" #define T0(z, i) BOOST_PP_REPEAT_##z (i, NUMBERED_LIST_BALANCED, T) // "T0, T1" #define T0_MAYBE_REF(z, i) BOOST_PP_REPEAT_##z (i, NUMBERED_LIST_BALANCED_MAYBE_REF, T) // "const T0&, T1" -#define T0_HEAD(z, i) BOOST_PP_REPEAT_##z (i, NUMBERED_LIST_HEAD, T) // "T0, T1, " #define T0_TAIL(z, i) BOOST_PP_REPEAT_##z (i, NUMBERED_LIST_TAIL, T) // ", T0, T1" #define T0_TAIL_MAYBE_REF(z, i) BOOST_PP_REPEAT_##z (i, NUMBERED_LIST_TAIL_MAYBE_REF, T) // ", const T0&, T1" -#define T0_A0(z, i) BOOST_PP_REPEAT_##z (i, TYPED_ARGS, ~) // ",T0 a0, T1 a1" -#define T0_A0_MAYBE_REF(z, i) BOOST_PP_REPEAT_##z (i, TYPED_ARGS_MAYBE_REF, ~) // ",const T0& a0, T1 a1" -#define T0_A0_TAIL_CONST_REF(z, i) BOOST_PP_REPEAT_##z (i, TYPED_ARGS_CONST_REF, ~) // ", const T0& a0, const T1& a1" -#define A0(z, i) BOOST_PP_REPEAT_##z (i, NUMBERED_LIST_BALANCED, a) // "a0, a1" #define A0_TAIL(z, i) BOOST_PP_REPEAT_##z (i, NUMBERED_LIST_TAIL, a) // ", a0, a1" // Define RegisterFunction #define OVERLOADS(z, i, data) \ template \ void RegisterFunction(const char* name) { \ - Register(name, call, nargs<0 T0_TAIL(z,i)>()); \ + Register(name, call, nargs()); \ } BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) #undef OVERLOADS // JSFastNative-compatible function that wraps the function identified in the template argument list // (Definition comes later, since it depends on some things we haven't defined yet) #define OVERLOADS(z, i, data) \ template \ static bool call(JSContext* cx, uint argc, jsval* vp); BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) #undef OVERLOADS // Similar, for class methods #define OVERLOADS(z, i, data) \ template \ static bool callMethod(JSContext* cx, uint argc, jsval* vp); BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) #undef OVERLOADS // const methods #define OVERLOADS(z, i, data) \ template \ static bool callMethodConst(JSContext* cx, uint argc, jsval* vp); BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) #undef OVERLOADS // Argument-number counter -#define OVERLOADS(z, i, data) \ - template /* add a dummy parameter so we still compile with 0 template args */ \ - static size_t nargs() { return i; } -BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) -#undef OVERLOADS +template +static size_t nargs() { return sizeof...(Ts); } // Call the named property on the given object -#define OVERLOADS(z, i, data) \ - template \ - bool CallFunction(JS::HandleValue val, const char* name T0_A0_TAIL_CONST_REF(z,i), R& ret) const; -BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) -#undef OVERLOADS +template +bool CallFunction(JS::HandleValue val, const char* name, R& ret, const Ts&... params) const; // Implicit conversion from JS::Rooted* to JS::MutableHandle does not work with template argument deduction // (only exact type matches allowed). We need this overload to allow passing Rooted* using the & operator // (as people would expect it to work based on the SpiderMonkey rooting guide). -#define OVERLOADS(z, i, data) \ - template \ - bool CallFunction(JS::HandleValue val, const char* name T0_A0_TAIL_CONST_REF(z,i), JS::Rooted* ret) const; -BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) -#undef OVERLOADS +template +bool CallFunction(JS::HandleValue val, const char* name, JS::Rooted* ret, const Ts&... params) const; // This overload is for the case when a JS::MutableHandle type gets passed into CallFunction directly and // without requiring implicit conversion. -#define OVERLOADS(z, i, data) \ - template \ - bool CallFunction(JS::HandleValue val, const char* name T0_A0_TAIL_CONST_REF(z,i), JS::MutableHandle ret) const; -BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) -#undef OVERLOADS +template +bool CallFunction(JS::HandleValue val, const char* name, JS::MutableHandle ret, const Ts&... params) const; +// Call the named property on the given object, with void return type +template \ +bool CallFunctionVoid(JS::HandleValue val, const char* name, const Ts&... params) const; Index: ps/trunk/source/scriptinterface/NativeWrapperDefns.h =================================================================== --- ps/trunk/source/scriptinterface/NativeWrapperDefns.h (revision 19182) +++ ps/trunk/source/scriptinterface/NativeWrapperDefns.h (revision 19183) @@ -1,250 +1,245 @@ /* Copyright (C) 2017 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 . */ #include "ps/Profile.h" // Use the macro below to define types that will be passed by value to C++ functions. // NOTE: References are used just to avoid superfluous copy constructor calls // in the script wrapper code. They cannot be used as out-parameters. // They are const T& by default to avoid confusion about this, especially // because sometimes the function is not just exposed to scripts, but also // called from C++ code. template struct ScriptInterface::MaybeRef { typedef const T& Type; }; #define PASS_BY_VALUE_IN_NATIVE_WRAPPER(T) \ template <> struct ScriptInterface::MaybeRef \ { \ typedef T Type; \ }; \ PASS_BY_VALUE_IN_NATIVE_WRAPPER(JS::HandleValue) PASS_BY_VALUE_IN_NATIVE_WRAPPER(bool) PASS_BY_VALUE_IN_NATIVE_WRAPPER(int) PASS_BY_VALUE_IN_NATIVE_WRAPPER(uint8_t) PASS_BY_VALUE_IN_NATIVE_WRAPPER(uint16_t) PASS_BY_VALUE_IN_NATIVE_WRAPPER(uint32_t) PASS_BY_VALUE_IN_NATIVE_WRAPPER(fixed) PASS_BY_VALUE_IN_NATIVE_WRAPPER(float) PASS_BY_VALUE_IN_NATIVE_WRAPPER(double) #undef PASS_BY_VALUE_IN_NATIVE_WRAPPER // This works around a bug in Visual Studio 2013 (error C2244 if ScriptInterface:: is included in the // type specifier of MaybeRef::Type for parameters inside the member function declaration). // It's probably the bug described here, but I'm not quite sure (at least the example there still // cause error C2244): // https://connect.microsoft.com/VisualStudio/feedback/details/611863/vs2010-c-fails-with-error-c2244-gcc-4-3-4-compiles-ok // // TODO: When dropping support for VS 2013, check if this bug is still present in the supported // Visual Studio versions (replace the macro definitions in NativeWrapperDecls.h with these ones, // remove them from here and check if this causes error C2244 when compiling. #undef NUMBERED_LIST_TAIL_MAYBE_REF #undef NUMBERED_LIST_BALANCED_MAYBE_REF -#undef TYPED_ARGS_MAYBE_REF #define NUMBERED_LIST_TAIL_MAYBE_REF(z, i, data) , typename ScriptInterface::MaybeRef::Type #define NUMBERED_LIST_BALANCED_MAYBE_REF(z, i, data) BOOST_PP_COMMA_IF(i) typename ScriptInterface::MaybeRef::Type -#define TYPED_ARGS_MAYBE_REF(z, i, data) , typename ScriptInterface::MaybeRef::Type a##i // (NativeWrapperDecls.h set up a lot of the macros we use here) // ScriptInterface_NativeWrapper::call(cx, rval, fptr, args...) will call fptr(cbdata, args...), // and if T != void then it will store the result in rval: // Templated on the return type so void can be handled separately template -struct ScriptInterface_NativeWrapper { - #define OVERLOADS(z, i, data) \ - template \ - static void call(JSContext* cx, JS::MutableHandleValue rval, F fptr T0_A0_MAYBE_REF(z,i)) { \ - ScriptInterface::AssignOrToJSValUnrooted(cx, rval, fptr(ScriptInterface::GetScriptInterfaceAndCBData(cx) A0_TAIL(z,i))); \ - } - - BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) - #undef OVERLOADS +struct ScriptInterface_NativeWrapper +{ + template + static void call(JSContext* cx, JS::MutableHandleValue rval, F fptr, Ts... params) + { + ScriptInterface::AssignOrToJSValUnrooted(cx, rval, fptr(ScriptInterface::GetScriptInterfaceAndCBData(cx), params...)); + } }; // Overloaded to ignore the return value from void functions template <> -struct ScriptInterface_NativeWrapper { - #define OVERLOADS(z, i, data) \ - template \ - static void call(JSContext* cx, JS::MutableHandleValue /*rval*/, F fptr T0_A0_MAYBE_REF(z,i)) { \ - fptr(ScriptInterface::GetScriptInterfaceAndCBData(cx) A0_TAIL(z,i)); \ - } - BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) - #undef OVERLOADS +struct ScriptInterface_NativeWrapper +{ + template + static void call(JSContext* cx, JS::MutableHandleValue UNUSED(rval), F fptr, Ts... params) + { + fptr(ScriptInterface::GetScriptInterfaceAndCBData(cx), params...); + } }; // Same idea but for method calls: template -struct ScriptInterface_NativeMethodWrapper { - #define OVERLOADS(z, i, data) \ - template \ - static void call(JSContext* cx, JS::MutableHandleValue rval, TC* c, F fptr T0_A0_MAYBE_REF(z,i)) { \ - ScriptInterface::AssignOrToJSValUnrooted(cx, rval, (c->*fptr)( A0(z,i) )); \ - } - - BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) - #undef OVERLOADS +struct ScriptInterface_NativeMethodWrapper +{ + template + static void call(JSContext* cx, JS::MutableHandleValue rval, TC* c, F fptr, Ts... params) + { + ScriptInterface::AssignOrToJSValUnrooted(cx, rval, (c->*fptr)(params...)); + } }; template -struct ScriptInterface_NativeMethodWrapper { - #define OVERLOADS(z, i, data) \ - template \ - static void call(JSContext* /*cx*/, JS::MutableHandleValue /*rval*/, TC* c, F fptr T0_A0_MAYBE_REF(z,i)) { \ - (c->*fptr)( A0(z,i) ); \ - } - BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) - #undef OVERLOADS +struct ScriptInterface_NativeMethodWrapper +{ + template + static void call(JSContext* UNUSED(cx), JS::MutableHandleValue UNUSED(rval), TC* c, F fptr, Ts... params) + { + (c->*fptr)(params...); + } }; // JSFastNative-compatible function that wraps the function identified in the template argument list #define OVERLOADS(z, i, data) \ template \ bool ScriptInterface::call(JSContext* cx, uint argc, jsval* vp) \ { \ JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \ JSAutoRequest rq(cx); \ BOOST_PP_REPEAT_##z (i, CONVERT_ARG, ~) \ JS::RootedValue rval(cx); \ - ScriptInterface_NativeWrapper::template call(cx, &rval, fptr A0_TAIL(z,i)); \ + ScriptInterface_NativeWrapper::template call(cx, &rval, fptr A0_TAIL(z,i)); \ args.rval().set(rval); \ return !ScriptInterface::IsExceptionPending(cx); \ } BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) #undef OVERLOADS // Same idea but for methods #define OVERLOADS(z, i, data) \ template \ bool ScriptInterface::callMethod(JSContext* cx, uint argc, jsval* vp) \ { \ JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \ JSAutoRequest rq(cx); \ JS::RootedObject thisObj(cx, JS_THIS_OBJECT(cx, vp)); \ if (ScriptInterface::GetClass(thisObj) != CLS) return false; \ TC* c = static_cast(ScriptInterface::GetPrivate(thisObj)); \ if (! c) return false; \ BOOST_PP_REPEAT_##z (i, CONVERT_ARG, ~) \ JS::RootedValue rval(cx); \ - ScriptInterface_NativeMethodWrapper::template call(cx, &rval, c, fptr A0_TAIL(z,i)); \ + ScriptInterface_NativeMethodWrapper::template call(cx, &rval, c, fptr A0_TAIL(z,i)); \ args.rval().set(rval); \ return !ScriptInterface::IsExceptionPending(cx); \ } BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) #undef OVERLOADS // const methods #define OVERLOADS(z, i, data) \ template \ bool ScriptInterface::callMethodConst(JSContext* cx, uint argc, jsval* vp) \ { \ JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \ JSAutoRequest rq(cx); \ JS::RootedObject thisObj(cx, JS_THIS_OBJECT(cx, vp)); \ if (ScriptInterface::GetClass(thisObj) != CLS) return false; \ TC* c = static_cast(ScriptInterface::GetPrivate(thisObj)); \ if (! c) return false; \ BOOST_PP_REPEAT_##z (i, CONVERT_ARG, ~) \ JS::RootedValue rval(cx); \ - ScriptInterface_NativeMethodWrapper::template call(cx, &rval, c, fptr A0_TAIL(z,i)); \ + ScriptInterface_NativeMethodWrapper::template call(cx, &rval, c, fptr A0_TAIL(z,i)); \ args.rval().set(rval); \ return !ScriptInterface::IsExceptionPending(cx); \ } BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) #undef OVERLOADS -#define ASSIGN_OR_TO_JS_VAL(z, i, data) AssignOrToJSVal(cx, argv[i], a##i); +template +static void AssignOrToJSValHelper(JSContext* cx, JS::AutoValueVector& argv, const T& a, const Ts&... params) +{ + ScriptInterface::AssignOrToJSVal(cx, argv[i], a); + AssignOrToJSValHelper(cx, argv, params...); +} -#define OVERLOADS(z, i, data) \ -template \ -bool ScriptInterface::CallFunction(JS::HandleValue val, const char* name T0_A0_TAIL_CONST_REF(z,i), R& ret) const \ -{ \ - JSContext* cx = GetContext(); \ - JSAutoRequest rq(cx); \ - JS::RootedValue jsRet(cx); \ - JS::AutoValueVector argv(cx); \ - argv.resize(i); \ - BOOST_PP_REPEAT_##z (i, ASSIGN_OR_TO_JS_VAL, ~) \ - bool ok = CallFunction_(val, name, argv, &jsRet); \ - if (!ok) \ - return false; \ - return FromJSVal(cx, jsRet, ret); \ +template +static void AssignOrToJSValHelper(JSContext* UNUSED(cx), JS::AutoValueVector& UNUSED(argv)) +{ + cassert(sizeof...(Ts) == 0); + // Nop, for terminating the template recursion. } -BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) -#undef OVERLOADS -#define OVERLOADS(z, i, data) \ -template \ -bool ScriptInterface::CallFunction(JS::HandleValue val, const char* name T0_A0_TAIL_CONST_REF(z,i), JS::Rooted* ret) const \ -{ \ - JSContext* cx = GetContext(); \ - JSAutoRequest rq(cx); \ - JS::MutableHandle jsRet(ret); \ - JS::AutoValueVector argv(cx); \ - argv.resize(i); \ - BOOST_PP_REPEAT_##z (i, ASSIGN_OR_TO_JS_VAL, ~) \ - return CallFunction_(val, name, argv, jsRet); \ +template +bool ScriptInterface::CallFunction(JS::HandleValue val, const char* name, R& ret, const Ts&... params) const +{ + JSContext* cx = GetContext(); + JSAutoRequest rq(cx); + JS::RootedValue jsRet(cx); + JS::AutoValueVector argv(cx); + argv.resize(sizeof...(Ts)); + AssignOrToJSValHelper<0>(cx, argv, params...); + bool ok = CallFunction_(val, name, argv, &jsRet); + if (!ok) + return false; + return FromJSVal(cx, jsRet, ret); } -BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) -#undef OVERLOADS -#define OVERLOADS(z, i, data) \ -template \ -bool ScriptInterface::CallFunction(JS::HandleValue val, const char* name T0_A0_TAIL_CONST_REF(z,i), JS::MutableHandle ret) const \ -{ \ - JSContext* cx = GetContext(); \ - JSAutoRequest rq(cx); \ - JS::AutoValueVector argv(cx); \ - argv.resize(i); \ - BOOST_PP_REPEAT_##z (i, ASSIGN_OR_TO_JS_VAL, ~) \ - bool ok = CallFunction_(val, name, argv, ret); \ - if (!ok) \ - return false; \ - return true; \ +template +bool ScriptInterface::CallFunction(JS::HandleValue val, const char* name, JS::Rooted* ret, const Ts&... params) const +{ + JSContext* cx = GetContext(); + JSAutoRequest rq(cx); + JS::MutableHandle jsRet(ret); + JS::AutoValueVector argv(cx); + argv.resize(sizeof...(Ts)); + AssignOrToJSValHelper<0>(cx, argv, params...); + return CallFunction_(val, name, argv, jsRet); +} + +template +bool ScriptInterface::CallFunction(JS::HandleValue val, const char* name, JS::MutableHandle ret, const Ts&... params) const +{ + JSContext* cx = GetContext(); + JSAutoRequest rq(cx); + JS::AutoValueVector argv(cx); + argv.resize(sizeof...(Ts)); + AssignOrToJSValHelper<0>(cx, argv, params...); + return CallFunction_(val, name, argv, ret); +} + +// Call the named property on the given object, with void return type +template +bool ScriptInterface::CallFunctionVoid(JS::HandleValue val, const char* name, const Ts&... params) const +{ + JSContext* cx = GetContext(); + JSAutoRequest rq(cx); + JS::RootedValue jsRet(cx); + JS::AutoValueVector argv(cx); + argv.resize(sizeof...(Ts)); + AssignOrToJSValHelper<0>(cx, argv, params...); + return CallFunction_(val, name, argv, &jsRet); } -BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) -#undef OVERLOADS -#undef ASSIGN_OR_TO_JS_VAL // Clean up our mess #undef NUMBERED_LIST_HEAD #undef NUMBERED_LIST_TAIL #undef NUMBERED_LIST_TAIL_MAYBE_REF #undef NUMBERED_LIST_BALANCED #undef NUMBERED_LIST_BALANCED_MAYBE_REF -#undef TYPED_ARGS -#undef TYPED_ARGS_MAYBE_REF -#undef TYPED_ARGS_CONST_REF #undef CONVERT_ARG #undef TYPENAME_T0_HEAD -#undef TYPENAME_T0_TAIL #undef T0 #undef T0_MAYBE_REF -#undef T0_HEAD #undef T0_TAIL #undef T0_TAIL_MAYBE_REF -#undef T0_A0 -#undef T0_A0_MAYBE_REF -#undef T0_A0_TAIL_CONST_REF -#undef A0 #undef A0_TAIL Index: ps/trunk/source/scriptinterface/ScriptInterface.cpp =================================================================== --- ps/trunk/source/scriptinterface/ScriptInterface.cpp (revision 19182) +++ ps/trunk/source/scriptinterface/ScriptInterface.cpp (revision 19183) @@ -1,1157 +1,1149 @@ -/* Copyright (C) 2016 Wildfire Games. +/* Copyright (C) 2017 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 . */ #include "precompiled.h" #include "ScriptInterface.h" #include "ScriptRuntime.h" #include "ScriptStats.h" #include "lib/debug.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Profile.h" #include "ps/utf16string.h" #include #include #define BOOST_MULTI_INDEX_DISABLE_SERIALIZATION #include #include #include #include #include #include #include #include "valgrind.h" #include "scriptinterface/ScriptExtraHeaders.h" /** * @file * Abstractions of various SpiderMonkey features. * Engine code should be using functions of these interfaces rather than * directly accessing the underlying JS api. */ struct ScriptInterface_impl { ScriptInterface_impl(const char* nativeScopeName, const shared_ptr& runtime); ~ScriptInterface_impl(); void Register(const char* name, JSNative fptr, uint nargs); // Take care to keep this declaration before heap rooted members. Destructors of heap rooted // members have to be called before the runtime destructor. shared_ptr m_runtime; JSContext* m_cx; JS::PersistentRootedObject m_glob; // global scope object JSCompartment* m_comp; boost::rand48* m_rng; JS::PersistentRootedObject m_nativeScope; // native function scope object typedef std::map ScriptValCache; ScriptValCache m_ScriptValCache; }; namespace { JSClass global_class = { "global", JSCLASS_GLOBAL_FLAGS, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook }; void ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report) { std::stringstream msg; bool isWarning = JSREPORT_IS_WARNING(report->flags); msg << (isWarning ? "JavaScript warning: " : "JavaScript error: "); if (report->filename) { msg << report->filename; msg << " line " << report->lineno << "\n"; } msg << message; // If there is an exception, then print its stack trace JS::RootedValue excn(cx); if (JS_GetPendingException(cx, &excn) && excn.isObject()) { JS::RootedObject excnObj(cx, &excn.toObject()); // TODO: this violates the docs ("The error reporter callback must not reenter the JSAPI.") // Hide the exception from EvaluateScript JSExceptionState* excnState = JS_SaveExceptionState(cx); JS_ClearPendingException(cx); JS::RootedValue rval(cx); const char dumpStack[] = "this.stack.trimRight().replace(/^/mg, ' ')"; // indent each line JS::CompileOptions opts(cx); if (JS::Evaluate(cx, excnObj, opts.setFileAndLine("(eval)", 1), dumpStack, ARRAY_SIZE(dumpStack)-1, &rval)) { std::string stackTrace; if (ScriptInterface::FromJSVal(cx, rval, stackTrace)) msg << "\n" << stackTrace; JS_RestoreExceptionState(cx, excnState); } else { // Error got replaced by new exception from EvaluateScript JS_DropExceptionState(cx, excnState); } } if (isWarning) LOGWARNING("%s", msg.str().c_str()); else LOGERROR("%s", msg.str().c_str()); // When running under Valgrind, print more information in the error message // VALGRIND_PRINTF_BACKTRACE("->"); } // Functions in the global namespace: bool print(JSContext* cx, uint argc, jsval* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); for (uint i = 0; i < args.length(); ++i) { std::wstring str; if (!ScriptInterface::FromJSVal(cx, args[i], str)) return false; debug_printf("%s", utf8_from_wstring(str).c_str()); } fflush(stdout); args.rval().setUndefined(); return true; } bool logmsg(JSContext* cx, uint argc, jsval* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() < 1) { args.rval().setUndefined(); return true; } std::wstring str; if (!ScriptInterface::FromJSVal(cx, args[0], str)) return false; LOGMESSAGE("%s", utf8_from_wstring(str)); args.rval().setUndefined(); return true; } bool warn(JSContext* cx, uint argc, jsval* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() < 1) { args.rval().setUndefined(); return true; } std::wstring str; if (!ScriptInterface::FromJSVal(cx, args[0], str)) return false; LOGWARNING("%s", utf8_from_wstring(str)); args.rval().setUndefined(); return true; } bool error(JSContext* cx, uint argc, jsval* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() < 1) { args.rval().setUndefined(); return true; } std::wstring str; if (!ScriptInterface::FromJSVal(cx, args[0], str)) return false; LOGERROR("%s", utf8_from_wstring(str)); args.rval().setUndefined(); return true; } bool deepcopy(JSContext* cx, uint argc, jsval* vp) { JSAutoRequest rq(cx); JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() < 1) { args.rval().setUndefined(); return true; } JS::RootedValue ret(cx); if (!JS_StructuredClone(cx, args[0], &ret, NULL, NULL)) return false; args.rval().set(ret); return true; } bool ProfileStart(JSContext* cx, uint argc, jsval* vp) { const char* name = "(ProfileStart)"; JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() >= 1) { std::string str; if (!ScriptInterface::FromJSVal(cx, args[0], str)) return false; typedef boost::flyweight< std::string, boost::flyweights::no_tracking, boost::flyweights::no_locking > StringFlyweight; name = StringFlyweight(str).get().c_str(); } if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread()) g_Profiler.StartScript(name); g_Profiler2.RecordRegionEnter(name); args.rval().setUndefined(); return true; } bool ProfileStop(JSContext* UNUSED(cx), uint UNUSED(argc), jsval* vp) { JS::CallReceiver rec = JS::CallReceiverFromVp(vp); if (CProfileManager::IsInitialised() && ThreadUtil::IsMainThread()) g_Profiler.Stop(); g_Profiler2.RecordRegionLeave(); rec.rval().setUndefined(); return true; } bool ProfileAttribute(JSContext* cx, uint argc, jsval* vp) { const char* name = "(ProfileAttribute)"; JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (args.length() >= 1) { std::string str; if (!ScriptInterface::FromJSVal(cx, args[0], str)) return false; typedef boost::flyweight< std::string, boost::flyweights::no_tracking, boost::flyweights::no_locking > StringFlyweight; name = StringFlyweight(str).get().c_str(); } g_Profiler2.RecordAttribute("%s", name); args.rval().setUndefined(); return true; } // Math override functions: // boost::uniform_real is apparently buggy in Boost pre-1.47 - for integer generators // it returns [min,max], not [min,max). The bug was fixed in 1.47. // We need consistent behaviour, so manually implement the correct version: static double generate_uniform_real(boost::rand48& rng, double min, double max) { while (true) { double n = (double)(rng() - rng.min()); double d = (double)(rng.max() - rng.min()) + 1.0; ENSURE(d > 0 && n >= 0 && n <= d); double r = n / d * (max - min) + min; if (r < max) return r; } } bool Math_random(JSContext* cx, uint UNUSED(argc), jsval* vp) { JS::CallReceiver rec = JS::CallReceiverFromVp(vp); double r; if(!ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface->MathRandom(r)) return false; rec.rval().setNumber(r); return true; } } // anonymous namespace bool ScriptInterface::MathRandom(double& nbr) { if (m->m_rng == NULL) return false; nbr = generate_uniform_real(*(m->m_rng), 0.0, 1.0); return true; } 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); JS_SetOffthreadIonCompilationEnabled(m_runtime->m_rt, true); // For GC debugging: // JS_SetGCZeal(m_cx, 2, JS_DEFAULT_ZEAL_FREQ); JS_SetContextPrivate(m_cx, NULL); JS_SetErrorReporter(m_runtime->m_rt, ErrorReporter); JS_SetGlobalJitCompilerOption(m_runtime->m_rt, JSJITCOMPILER_ION_ENABLE, 1); JS_SetGlobalJitCompilerOption(m_runtime->m_rt, JSJITCOMPILER_BASELINE_ENABLE, 1); JS::RuntimeOptionsRef(m_cx).setExtraWarnings(1) .setWerror(0) .setVarObjFix(1) .setStrictMode(1); JS::CompartmentOptions opt; opt.setVersion(JSVERSION_LATEST); // Keep JIT code during non-shrinking GCs. This brings a quite big performance improvement. opt.setPreserveJitCode(true); 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_glob = globalRootedVal.get(); JS_DefineProperty(m_cx, m_glob, "global", globalRootedVal, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); m_nativeScope = JS_DefineObject(m_cx, m_glob, nativeScopeName, nullptr, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(m_cx, globalRootedVal, "print", ::print, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(m_cx, globalRootedVal, "log", ::logmsg, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(m_cx, globalRootedVal, "warn", ::warn, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(m_cx, globalRootedVal, "error", ::error, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(m_cx, globalRootedVal, "deepcopy", ::deepcopy, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); Register("ProfileStart", ::ProfileStart, 1); Register("ProfileStop", ::ProfileStop, 0); Register("ProfileAttribute", ::ProfileAttribute, 1); runtime->RegisterContext(m_cx); } ScriptInterface_impl::~ScriptInterface_impl() { m_runtime->UnRegisterContext(m_cx); { JSAutoRequest rq(m_cx); JS_LeaveCompartment(m_cx, m_comp); } JS_DestroyContext(m_cx); } void ScriptInterface_impl::Register(const char* name, JSNative fptr, uint nargs) { JSAutoRequest rq(m_cx); JS::RootedObject nativeScope(m_cx, m_nativeScope); JS::RootedFunction func(m_cx, JS_DefineFunction(m_cx, nativeScope, name, fptr, nargs, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)); } ScriptInterface::ScriptInterface(const char* nativeScopeName, const char* debugName, const shared_ptr& runtime) : m(new ScriptInterface_impl(nativeScopeName, runtime)) { // Profiler stats table isn't thread-safe, so only enable this on the main thread if (ThreadUtil::IsMainThread()) { if (g_ScriptStatsTable) g_ScriptStatsTable->Add(this, debugName); } m_CxPrivate.pScriptInterface = this; JS_SetContextPrivate(m->m_cx, (void*)&m_CxPrivate); } ScriptInterface::~ScriptInterface() { if (ThreadUtil::IsMainThread()) { if (g_ScriptStatsTable) g_ScriptStatsTable->Remove(this); } } void ScriptInterface::SetCallbackData(void* pCBData) { m_CxPrivate.pCBData = pCBData; } ScriptInterface::CxPrivate* ScriptInterface::GetScriptInterfaceAndCBData(JSContext* cx) { CxPrivate* pCxPrivate = (CxPrivate*)JS_GetContextPrivate(cx); return pCxPrivate; } JS::Value ScriptInterface::GetCachedValue(CACHED_VAL valueIdentifier) { std::map::iterator it = m->m_ScriptValCache.find(valueIdentifier); ENSURE(it != m->m_ScriptValCache.end()); return it->second.get(); } bool ScriptInterface::LoadGlobalScripts() { // Ignore this failure in tests if (!g_VFS) return false; // Load and execute *.js in the global scripts directory VfsPaths pathnames; vfs::GetPathnames(g_VFS, L"globalscripts/", L"*.js", pathnames); for (const VfsPath& path : pathnames) if (!LoadGlobalScriptFile(path)) { LOGERROR("LoadGlobalScripts: Failed to load script %s", path.string8()); return false; } JSAutoRequest rq(m->m_cx); JS::RootedValue proto(m->m_cx); JS::RootedObject global(m->m_cx, m->m_glob); if (JS_GetProperty(m->m_cx, global, "Vector2Dprototype", &proto)) m->m_ScriptValCache[CACHE_VECTOR2DPROTO].init(GetJSRuntime(), proto); if (JS_GetProperty(m->m_cx, global, "Vector3Dprototype", &proto)) m->m_ScriptValCache[CACHE_VECTOR3DPROTO].init(GetJSRuntime(), proto); return true; } bool ScriptInterface::ReplaceNondeterministicRNG(boost::rand48& rng) { JSAutoRequest rq(m->m_cx); JS::RootedValue math(m->m_cx); JS::RootedObject global(m->m_cx, m->m_glob); if (JS_GetProperty(m->m_cx, global, "Math", &math) && math.isObject()) { JS::RootedObject mathObj(m->m_cx, &math.toObject()); JS::RootedFunction random(m->m_cx, JS_DefineFunction(m->m_cx, mathObj, "random", Math_random, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)); if (random) { m->m_rng = &rng; return true; } } LOGERROR("ReplaceNondeterministicRNG: failed to replace Math.random"); return false; } void ScriptInterface::Register(const char* name, JSNative fptr, size_t nargs) { m->Register(name, fptr, (uint)nargs); } JSContext* ScriptInterface::GetContext() const { return m->m_cx; } JSRuntime* ScriptInterface::GetJSRuntime() const { return m->m_runtime->m_rt; } shared_ptr ScriptInterface::GetRuntime() const { return m->m_runtime; } void ScriptInterface::CallConstructor(JS::HandleValue ctor, JS::HandleValueArray argv, JS::MutableHandleValue out) { JSAutoRequest rq(m->m_cx); if (!ctor.isObject()) { LOGERROR("CallConstructor: ctor is not an object"); out.setNull(); return; } JS::RootedObject ctorObj(m->m_cx, &ctor.toObject()); out.setObjectOrNull(JS_New(m->m_cx, ctorObj, argv)); } void ScriptInterface::DefineCustomObjectType(JSClass *clasp, JSNative constructor, uint minArgs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs) { JSAutoRequest rq(m->m_cx); std::string typeName = clasp->name; if (m_CustomObjectTypes.find(typeName) != m_CustomObjectTypes.end()) { // This type already exists throw PSERROR_Scripting_DefineType_AlreadyExists(); } JS::RootedObject global(m->m_cx, m->m_glob); JS::RootedObject obj(m->m_cx, JS_InitClass(m->m_cx, global, JS::NullPtr(), clasp, constructor, minArgs, // Constructor, min args ps, fs, // Properties, methods static_ps, static_fs)); // Constructor properties, methods if (obj == NULL) throw PSERROR_Scripting_DefineType_CreationFailed(); CustomType& type = m_CustomObjectTypes[typeName]; type.m_Prototype.init(m->m_cx, obj); type.m_Class = clasp; type.m_Constructor = constructor; } JSObject* ScriptInterface::CreateCustomObject(const std::string& typeName) const { std::map::const_iterator it = m_CustomObjectTypes.find(typeName); if (it == m_CustomObjectTypes.end()) throw PSERROR_Scripting_TypeDoesNotExist(); JS::RootedObject prototype(m->m_cx, it->second.m_Prototype.get()); return JS_NewObjectWithGivenProto(m->m_cx, it->second.m_Class, prototype); } -bool ScriptInterface::CallFunctionVoid(JS::HandleValue val, const char* name) -{ - JSAutoRequest rq(m->m_cx); - JS::RootedValue jsRet(m->m_cx); - return CallFunction_(val, name, JS::HandleValueArray::empty(), &jsRet); -} - - bool ScriptInterface::CallFunction_(JS::HandleValue val, const char* name, JS::HandleValueArray argv, JS::MutableHandleValue ret) const { JSAutoRequest rq(m->m_cx); JS::RootedObject obj(m->m_cx); if (!JS_ValueToObject(m->m_cx, val, &obj) || !obj) return false; // Check that the named function actually exists, to avoid ugly JS error reports // when calling an undefined value bool found; 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; } jsval ScriptInterface::GetGlobalObject() { JSAutoRequest rq(m->m_cx); return JS::ObjectValue(*JS::CurrentGlobalOrNull(m->m_cx)); } JSClass* ScriptInterface::GetGlobalClass() { return &global_class; } bool ScriptInterface::SetGlobal_(const char* name, JS::HandleValue value, bool replace) { JSAutoRequest rq(m->m_cx); JS::RootedObject global(m->m_cx, m->m_glob); if (!replace) { bool found; if (!JS_HasProperty(m->m_cx, global, name, &found)) return false; if (found) { JS_ReportError(m->m_cx, "SetGlobal \"%s\" called multiple times", name); return false; } } bool ok = JS_DefineProperty(m->m_cx, global, name, value, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); return ok; } bool ScriptInterface::SetProperty_(JS::HandleValue obj, const char* name, JS::HandleValue value, bool constant, bool enumerate) { JSAutoRequest rq(m->m_cx); uint attrs = 0; if (constant) attrs |= JSPROP_READONLY | JSPROP_PERMANENT; if (enumerate) attrs |= JSPROP_ENUMERATE; if (!obj.isObject()) return false; JS::RootedObject object(m->m_cx, &obj.toObject()); if (!JS_DefineProperty(m->m_cx, object, name, value, attrs)) return false; return true; } bool ScriptInterface::SetProperty_(JS::HandleValue obj, const wchar_t* name, JS::HandleValue value, bool constant, bool enumerate) { JSAutoRequest rq(m->m_cx); uint attrs = 0; if (constant) attrs |= JSPROP_READONLY | JSPROP_PERMANENT; if (enumerate) attrs |= JSPROP_ENUMERATE; if (!obj.isObject()) return false; JS::RootedObject object(m->m_cx, &obj.toObject()); utf16string name16(name, name + wcslen(name)); if (!JS_DefineUCProperty(m->m_cx, object, reinterpret_cast(name16.c_str()), name16.length(), value, attrs)) return false; return true; } bool ScriptInterface::SetPropertyInt_(JS::HandleValue obj, int name, JS::HandleValue value, bool constant, bool enumerate) { JSAutoRequest rq(m->m_cx); uint attrs = 0; if (constant) attrs |= JSPROP_READONLY | JSPROP_PERMANENT; if (enumerate) attrs |= JSPROP_ENUMERATE; if (!obj.isObject()) return false; JS::RootedObject object(m->m_cx, &obj.toObject()); JS::RootedId id(m->m_cx, INT_TO_JSID(name)); if (!JS_DefinePropertyById(m->m_cx, object, id, value, attrs)) return false; return true; } bool ScriptInterface::GetProperty(JS::HandleValue obj, const char* name, JS::MutableHandleValue out) { return GetProperty_(obj, name, out); } bool ScriptInterface::GetProperty(JS::HandleValue obj, const char* name, JS::MutableHandleObject out) { JSContext* cx = GetContext(); JSAutoRequest rq(cx); JS::RootedValue val(cx); if (!GetProperty_(obj, name, &val)) return false; if (!val.isObject()) { LOGERROR("GetProperty failed: trying to get an object, but the property is not an object!"); return false; } out.set(&val.toObject()); return true; } bool ScriptInterface::GetPropertyInt(JS::HandleValue obj, int name, JS::MutableHandleValue out) { return GetPropertyInt_(obj, name, out); } bool ScriptInterface::GetProperty_(JS::HandleValue obj, const char* name, JS::MutableHandleValue out) { JSAutoRequest rq(m->m_cx); if (!obj.isObject()) return false; JS::RootedObject object(m->m_cx, &obj.toObject()); if (!JS_GetProperty(m->m_cx, object, name, out)) return false; return true; } bool ScriptInterface::GetPropertyInt_(JS::HandleValue obj, int name, JS::MutableHandleValue out) { JSAutoRequest rq(m->m_cx); JS::RootedId nameId(m->m_cx, INT_TO_JSID(name)); if (!obj.isObject()) return false; JS::RootedObject object(m->m_cx, &obj.toObject()); if (!JS_GetPropertyById(m->m_cx, object, nameId, out)) return false; return true; } bool ScriptInterface::HasProperty(JS::HandleValue obj, const char* name) { // TODO: proper errorhandling JSAutoRequest rq(m->m_cx); if (!obj.isObject()) return false; JS::RootedObject object(m->m_cx, &obj.toObject()); bool found; if (!JS_HasProperty(m->m_cx, object, name, &found)) return false; return found; } bool ScriptInterface::EnumeratePropertyNamesWithPrefix(JS::HandleValue objVal, const char* prefix, std::vector& out) { JSAutoRequest rq(m->m_cx); if (!objVal.isObjectOrNull()) { LOGERROR("EnumeratePropertyNamesWithPrefix expected object type!"); return false; } if (objVal.isNull()) return true; // reached the end of the prototype chain JS::RootedObject obj(m->m_cx, &objVal.toObject()); JS::AutoIdArray props(m->m_cx, JS_Enumerate(m->m_cx, obj)); if (!props) return false; for (size_t i = 0; i < props.length(); ++i) { JS::RootedId id(m->m_cx, props[i]); JS::RootedValue val(m->m_cx); if (!JS_IdToValue(m->m_cx, id, &val)) return false; if (!val.isString()) continue; // ignore integer properties JS::RootedString name(m->m_cx, val.toString()); size_t len = strlen(prefix)+1; std::vector buf(len); size_t prefixLen = strlen(prefix) * sizeof(char); JS_EncodeStringToBuffer(m->m_cx, name, &buf[0], prefixLen); buf[len-1]= '\0'; if (0 == strcmp(&buf[0], prefix)) { if (JS_StringHasLatin1Chars(name)) { size_t length; JS::AutoCheckCannotGC nogc; const JS::Latin1Char* chars = JS_GetLatin1StringCharsAndLength(m->m_cx, nogc, name, &length); if (chars) out.push_back(std::string(chars, chars+length)); } else { size_t length; JS::AutoCheckCannotGC nogc; const char16_t* chars = JS_GetTwoByteStringCharsAndLength(m->m_cx, nogc, name, &length); if (chars) out.push_back(std::string(chars, chars+length)); } } } // Recurse up the prototype chain JS::RootedObject prototype(m->m_cx); if (JS_GetPrototype(m->m_cx, obj, &prototype)) { JS::RootedValue prototypeVal(m->m_cx, JS::ObjectOrNullValue(prototype)); if (!EnumeratePropertyNamesWithPrefix(prototypeVal, prefix, out)) return false; } return true; } bool ScriptInterface::SetPrototype(JS::HandleValue objVal, JS::HandleValue protoVal) { JSAutoRequest rq(m->m_cx); if (!objVal.isObject() || !protoVal.isObject()) return false; JS::RootedObject obj(m->m_cx, &objVal.toObject()); JS::RootedObject proto(m->m_cx, &protoVal.toObject()); return JS_SetPrototype(m->m_cx, obj, proto); } bool ScriptInterface::FreezeObject(JS::HandleValue objVal, bool deep) { JSAutoRequest rq(m->m_cx); if (!objVal.isObject()) return false; JS::RootedObject obj(m->m_cx, &objVal.toObject()); if (deep) return JS_DeepFreezeObject(m->m_cx, obj); else return JS_FreezeObject(m->m_cx, obj); } bool ScriptInterface::LoadScript(const VfsPath& filename, const std::string& code) { JSAutoRequest rq(m->m_cx); JS::RootedObject global(m->m_cx, m->m_glob); utf16string codeUtf16(code.begin(), code.end()); uint lineNo = 1; // CompileOptions does not copy the contents of the filename string pointer. // Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary. std::string filenameStr = filename.string8(); JS::CompileOptions options(m->m_cx); options.setFileAndLine(filenameStr.c_str(), lineNo); options.setCompileAndGo(true); JS::RootedFunction func(m->m_cx); JS::AutoObjectVector emptyScopeChain(m->m_cx); if (!JS::CompileFunction(m->m_cx, emptyScopeChain, options, NULL, 0, NULL, reinterpret_cast(codeUtf16.c_str()), (uint)(codeUtf16.length()), &func)) return false; JS::RootedValue rval(m->m_cx); return JS_CallFunction(m->m_cx, JS::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) { JSAutoRequest rq(m->m_cx); JS::RootedObject global(m->m_cx, m->m_glob); utf16string codeUtf16(code.begin(), code.end()); uint lineNo = 1; // CompileOptions does not copy the contents of the filename string pointer. // Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary. std::string filenameStr = filename.string8(); JS::RootedValue rval(m->m_cx); JS::CompileOptions opts(m->m_cx); opts.setFileAndLine(filenameStr.c_str(), lineNo); return JS::Evaluate(m->m_cx, global, opts, reinterpret_cast(codeUtf16.c_str()), (uint)(codeUtf16.length()), &rval); } bool ScriptInterface::LoadGlobalScriptFile(const VfsPath& path) { JSAutoRequest rq(m->m_cx); JS::RootedObject global(m->m_cx, m->m_glob); if (!VfsFileExists(path)) { LOGERROR("File '%s' does not exist", path.string8()); return false; } CVFSFile file; PSRETURN ret = file.Load(g_VFS, path); if (ret != PSRETURN_OK) { LOGERROR("Failed to load file '%s': %s", path.string8(), GetErrorString(ret)); return false; } std::wstring code = wstring_from_utf8(file.DecodeUTF8()); // assume it's UTF-8 utf16string codeUtf16(code.begin(), code.end()); uint lineNo = 1; // CompileOptions does not copy the contents of the filename string pointer. // Passing a temporary string there will cause undefined behaviour, so we create a separate string to avoid the temporary. std::string filenameStr = path.string8(); JS::RootedValue rval(m->m_cx); JS::CompileOptions opts(m->m_cx); opts.setFileAndLine(filenameStr.c_str(), lineNo); return JS::Evaluate(m->m_cx, global, opts, reinterpret_cast(codeUtf16.c_str()), (uint)(codeUtf16.length()), &rval); } bool ScriptInterface::Eval(const char* code) { JSAutoRequest rq(m->m_cx); JS::RootedValue rval(m->m_cx); return Eval_(code, &rval); } bool ScriptInterface::Eval_(const char* code, JS::MutableHandleValue rval) { JSAutoRequest rq(m->m_cx); JS::RootedObject global(m->m_cx, m->m_glob); utf16string codeUtf16(code, code+strlen(code)); JS::CompileOptions opts(m->m_cx); opts.setFileAndLine("(eval)", 1); return JS::Evaluate(m->m_cx, global, opts, reinterpret_cast(codeUtf16.c_str()), (uint)codeUtf16.length(), rval); } bool ScriptInterface::Eval_(const wchar_t* code, JS::MutableHandleValue rval) { JSAutoRequest rq(m->m_cx); JS::RootedObject global(m->m_cx, m->m_glob); utf16string codeUtf16(code, code+wcslen(code)); JS::CompileOptions opts(m->m_cx); opts.setFileAndLine("(eval)", 1); return JS::Evaluate(m->m_cx, global, opts, reinterpret_cast(codeUtf16.c_str()), (uint)codeUtf16.length(), rval); } bool ScriptInterface::ParseJSON(const std::string& string_utf8, JS::MutableHandleValue out) { JSAutoRequest rq(m->m_cx); std::wstring attrsW = wstring_from_utf8(string_utf8); utf16string string(attrsW.begin(), attrsW.end()); if (JS_ParseJSON(m->m_cx, reinterpret_cast(string.c_str()), (u32)string.size(), out)) return true; LOGERROR("JS_ParseJSON failed!"); if (!JS_IsExceptionPending(m->m_cx)) return false; JS::RootedValue exc(m->m_cx); if (!JS_GetPendingException(m->m_cx, &exc)) return false; JS_ClearPendingException(m->m_cx); // We expect an object of type SyntaxError if (!exc.isObject()) return false; JS::RootedValue rval(m->m_cx); JS::RootedObject excObj(m->m_cx, &exc.toObject()); if (!JS_CallFunctionName(m->m_cx, excObj, "toString", JS::HandleValueArray::empty(), &rval)) return false; std::wstring error; ScriptInterface::FromJSVal(m->m_cx, rval, error); LOGERROR("%s", utf8_from_wstring(error)); return false; } void ScriptInterface::ReadJSONFile(const VfsPath& path, JS::MutableHandleValue out) { if (!VfsFileExists(path)) { LOGERROR("File '%s' does not exist", path.string8()); return; } CVFSFile file; PSRETURN ret = file.Load(g_VFS, path); if (ret != PSRETURN_OK) { LOGERROR("Failed to load file '%s': %s", path.string8(), GetErrorString(ret)); return; } std::string content(file.DecodeUTF8()); // assume it's UTF-8 if (!ParseJSON(content, out)) LOGERROR("Failed to parse '%s'", path.string8()); } struct Stringifier { static bool callback(const char16_t* buf, u32 len, void* data) { utf16string str(buf, buf+len); std::wstring strw(str.begin(), str.end()); Status err; // ignore Unicode errors static_cast(data)->stream << utf8_from_wstring(strw, &err); return true; } std::stringstream stream; }; // TODO: It's not quite clear why JS_Stringify needs JS::MutableHandleValue. |obj| should not get modified. // It probably has historical reasons and could be changed by SpiderMonkey in the future. std::string ScriptInterface::StringifyJSON(JS::MutableHandleValue obj, bool indent) { JSAutoRequest rq(m->m_cx); Stringifier str; JS::RootedValue indentVal(m->m_cx, indent ? JS::Int32Value(2) : JS::UndefinedValue()); if (!JS_Stringify(m->m_cx, obj, JS::NullPtr(), indentVal, &Stringifier::callback, &str)) { JS_ClearPendingException(m->m_cx); LOGERROR("StringifyJSON failed"); return std::string(); } return str.stream.str(); } std::string ScriptInterface::ToString(JS::MutableHandleValue obj, bool pretty) { JSAutoRequest rq(m->m_cx); if (obj.isUndefined()) return "(void 0)"; // Try to stringify as JSON if possible // (TODO: this is maybe a bad idea since it'll drop 'undefined' values silently) if (pretty) { Stringifier str; JS::RootedValue indentVal(m->m_cx, JS::Int32Value(2)); // Temporary disable the error reporter, so we don't print complaints about cyclic values JSErrorReporter er = JS_SetErrorReporter(m->m_runtime->m_rt, NULL); bool ok = JS_Stringify(m->m_cx, obj, JS::NullPtr(), indentVal, &Stringifier::callback, &str); // Restore error reporter JS_SetErrorReporter(m->m_runtime->m_rt, er); if (ok) return str.stream.str(); // Clear the exception set when Stringify failed JS_ClearPendingException(m->m_cx); } // Caller didn't want pretty output, or JSON conversion failed (e.g. due to cycles), // so fall back to obj.toSource() std::wstring source = L"(error)"; CallFunction(obj, "toSource", source); return utf8_from_wstring(source); } void ScriptInterface::ReportError(const char* msg) { JSAutoRequest rq(m->m_cx); // JS_ReportError by itself doesn't seem to set a JS-style exception, and so // script callers will be unable to catch anything. So use JS_SetPendingException // to make sure there really is a script-level exception. But just set it to undefined // because there's not much value yet in throwing a real exception object. JS_SetPendingException(m->m_cx, JS::UndefinedHandleValue); // And report the actual error JS_ReportError(m->m_cx, "%s", msg); // TODO: Why doesn't JS_ReportPendingException(m->m_cx); work? } bool ScriptInterface::IsExceptionPending(JSContext* cx) { JSAutoRequest rq(cx); return JS_IsExceptionPending(cx) ? true : false; } const JSClass* ScriptInterface::GetClass(JS::HandleObject obj) { return JS_GetClass(obj); } void* ScriptInterface::GetPrivate(JS::HandleObject obj) { // TODO: use JS_GetInstancePrivate return JS_GetPrivate(obj); } void ScriptInterface::MaybeGC() { JS_MaybeGC(m->m_cx); } void ScriptInterface::ForceGC() { PROFILE2("JS_GC"); JS_GC(this->GetJSRuntime()); } JS::Value ScriptInterface::CloneValueFromOtherContext(ScriptInterface& otherContext, JS::HandleValue val) { PROFILE("CloneValueFromOtherContext"); JSAutoRequest rq(m->m_cx); JS::RootedValue out(m->m_cx); shared_ptr structuredClone = otherContext.WriteStructuredClone(val); ReadStructuredClone(structuredClone, &out); return out.get(); } ScriptInterface::StructuredClone::StructuredClone() : m_Data(NULL), m_Size(0) { } ScriptInterface::StructuredClone::~StructuredClone() { if (m_Data) JS_ClearStructuredClone(m_Data, m_Size, NULL, NULL); } shared_ptr ScriptInterface::WriteStructuredClone(JS::HandleValue v) { JSAutoRequest rq(m->m_cx); u64* data = NULL; size_t nbytes = 0; if (!JS_WriteStructuredClone(m->m_cx, v, &data, &nbytes, NULL, NULL, JS::UndefinedHandleValue)) { debug_warn(L"Writing a structured clone with JS_WriteStructuredClone failed!"); return shared_ptr(); } shared_ptr ret (new StructuredClone); ret->m_Data = data; ret->m_Size = nbytes; return ret; } void ScriptInterface::ReadStructuredClone(const shared_ptr& ptr, JS::MutableHandleValue ret) { JSAutoRequest rq(m->m_cx); JS_ReadStructuredClone(m->m_cx, ptr->m_Data, ptr->m_Size, JS_STRUCTURED_CLONE_VERSION, ret, NULL, NULL); } Index: ps/trunk/source/scriptinterface/ScriptInterface.h =================================================================== --- ps/trunk/source/scriptinterface/ScriptInterface.h (revision 19182) +++ ps/trunk/source/scriptinterface/ScriptInterface.h (revision 19183) @@ -1,612 +1,562 @@ /* Copyright (C) 2017 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_SCRIPTINTERFACE #define INCLUDED_SCRIPTINTERFACE #include #include "lib/file/vfs/vfs_path.h" #include "maths/Fixed.h" #include "ScriptTypes.h" #include "ps/Errors.h" ERROR_GROUP(Scripting); ERROR_TYPE(Scripting, SetupFailed); ERROR_SUBGROUP(Scripting, LoadFile); ERROR_TYPE(Scripting_LoadFile, OpenFailed); ERROR_TYPE(Scripting_LoadFile, EvalErrors); ERROR_TYPE(Scripting, ConversionFailed); ERROR_TYPE(Scripting, CallFunctionFailed); ERROR_TYPE(Scripting, RegisterFunctionFailed); ERROR_TYPE(Scripting, DefineConstantFailed); ERROR_TYPE(Scripting, CreateObjectFailed); ERROR_TYPE(Scripting, TypeDoesNotExist); ERROR_SUBGROUP(Scripting, DefineType); ERROR_TYPE(Scripting_DefineType, AlreadyExists); ERROR_TYPE(Scripting_DefineType, CreationFailed); // Set the maximum number of function arguments that can be handled // (This should be as small as possible (for compiler efficiency), // 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; /** * Abstraction around a SpiderMonkey JSContext. * * Thread-safety: * - May be used in non-main threads. * - Each ScriptInterface must be created, used, and destroyed, all in a single thread * (it must never be shared between threads). */ class ScriptInterface { NONCOPYABLE(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 * be placed into, as a scoping mechanism; typically "Engine" * @param debugName Name of this interface for CScriptStats purposes. * @param runtime ScriptRuntime to use when initializing this interface. */ ScriptInterface(const char* nativeScopeName, const char* debugName, const shared_ptr& runtime); ~ScriptInterface(); struct CxPrivate { ScriptInterface* pScriptInterface; // the ScriptInterface object the current context belongs to void* pCBData; // meant to be used as the "this" object for callback functions } m_CxPrivate; void SetCallbackData(void* pCBData); static CxPrivate* GetScriptInterfaceAndCBData(JSContext* cx); JSContext* GetContext() const; JSRuntime* GetJSRuntime() const; shared_ptr GetRuntime() const; /** * Load global scripts that most script contexts need, * located in the /globalscripts directory. VFS must be initialized. */ bool LoadGlobalScripts(); enum CACHED_VAL { CACHE_VECTOR2DPROTO, CACHE_VECTOR3DPROTO }; JS::Value GetCachedValue(CACHED_VAL valueIdentifier); /** * Replace the default JS random number geenrator with a seeded, network-sync'd one. */ bool ReplaceNondeterministicRNG(boost::rand48& rng); /** * Call a constructor function, equivalent to JS "new ctor(arg)". * @param ctor An object that can be used as constructor * @param argv Constructor arguments * @param out The new object; On error an error message gets logged and out is Null (out.isNull() == true). */ void CallConstructor(JS::HandleValue ctor, JS::HandleValueArray argv, JS::MutableHandleValue out); - /** - * Call the named property on the given object, with void return type and 0 arguments - */ - bool CallFunctionVoid(JS::HandleValue val, const char* name); - - /** - * Call the named property on the given object, with void return type and 1 argument - */ - template - bool CallFunctionVoid(JS::HandleValue val, const char* name, const T0& a0); - - /** - * Call the named property on the given object, with void return type and 2 arguments - */ - template - bool CallFunctionVoid(JS::HandleValue val, const char* name, const T0& a0, const T1& a1); - - /** - * Call the named property on the given object, with void return type and 3 arguments - */ - template - bool CallFunctionVoid(JS::HandleValue val, const char* name, const T0& a0, const T1& a1, const T2& a2); - JSObject* CreateCustomObject(const std::string & typeName) const; void DefineCustomObjectType(JSClass *clasp, JSNative constructor, uint minArgs, JSPropertySpec *ps, JSFunctionSpec *fs, JSPropertySpec *static_ps, JSFunctionSpec *static_fs); jsval GetGlobalObject(); JSClass* GetGlobalClass(); /** * Set the named property on the global object. * If @p replace is true, an existing property will be overwritten; otherwise attempts * to set an already-defined value will fail. */ template bool SetGlobal(const char* name, const T& value, bool replace = false); /** * Set the named property on the given object. * Optionally makes it {ReadOnly, DontDelete, DontEnum}. */ template bool SetProperty(JS::HandleValue obj, const char* name, const T& value, bool constant = false, bool enumerate = true); /** * Set the named property on the given object. * Optionally makes it {ReadOnly, DontDelete, DontEnum}. */ template bool SetProperty(JS::HandleValue obj, const wchar_t* name, const T& value, bool constant = false, bool enumerate = true); /** * Set the integer-named property on the given object. * Optionally makes it {ReadOnly, DontDelete, DontEnum}. */ template bool SetPropertyInt(JS::HandleValue obj, int name, const T& value, bool constant = false, bool enumerate = true); /** * Get the named property on the given object. */ template bool GetProperty(JS::HandleValue obj, const char* name, T& out); /** * Get the named property of the given object. */ bool GetProperty(JS::HandleValue obj, const char* name, JS::MutableHandleValue out); bool GetProperty(JS::HandleValue obj, const char* name, JS::MutableHandleObject out); /** * Get the integer-named property on the given object. */ template bool GetPropertyInt(JS::HandleValue obj, int name, T& out); /** * Get the named property of the given object. */ bool GetPropertyInt(JS::HandleValue obj, int name, JS::MutableHandleValue out); /** * Check the named property has been defined on the given object. */ bool HasProperty(JS::HandleValue obj, const char* name); bool EnumeratePropertyNamesWithPrefix(JS::HandleValue objVal, const char* prefix, std::vector& out); bool SetPrototype(JS::HandleValue obj, JS::HandleValue proto); bool FreezeObject(JS::HandleValue objVal, bool deep); bool Eval(const char* code); template bool Eval(const CHAR* code, JS::MutableHandleValue out); template bool Eval(const CHAR* code, T& out); /** * Convert an object to a UTF-8 encoded string, either with JSON * (if pretty == true and there is no JSON error) or with toSource(). * * We have to use a mutable handle because JS_Stringify requires that for unknown reasons. */ std::string ToString(JS::MutableHandleValue obj, bool pretty = false); /** * Parse a UTF-8-encoded JSON string. Returns the unmodified value on error * and prints an error message. * @return true on success; false otherwise */ bool ParseJSON(const std::string& string_utf8, JS::MutableHandleValue out); /** * Read a JSON file. Returns the unmodified value on error and prints an error message. */ void ReadJSONFile(const VfsPath& path, JS::MutableHandleValue out); /** * Stringify to a JSON string, UTF-8 encoded. Returns an empty string on error. */ std::string StringifyJSON(JS::MutableHandleValue obj, bool indent = true); /** * Report the given error message through the JS error reporting mechanism, * and throw a JS exception. (Callers can check IsPendingException, and must * return false in that case to propagate the exception.) */ void ReportError(const char* msg); /** * Load and execute the given script in a new function scope. * @param filename Name for debugging purposes (not used to load the file) * @param code JS code to execute * @return true on successful compilation and execution; false otherwise */ bool LoadScript(const VfsPath& filename, const std::string& code); /** * Load and execute the given script in the global scope. * @param filename Name for debugging purposes (not used to load the file) * @param code JS code to execute * @return true on successful compilation and execution; false otherwise */ bool LoadGlobalScript(const VfsPath& filename, const std::wstring& code); /** * Load and execute the given script in the global scope. * @return true on successful compilation and execution; false otherwise */ bool LoadGlobalScriptFile(const VfsPath& path); /** * Construct a new value (usable in this ScriptInterface's context) by cloning * a value from a different context. * Complex values (functions, XML, etc) won't be cloned correctly, but basic * types and cyclic references should be fine. */ JS::Value CloneValueFromOtherContext(ScriptInterface& otherContext, JS::HandleValue val); /** * Convert a jsval to a C++ type. (This might trigger GC.) */ template static bool FromJSVal(JSContext* cx, const JS::HandleValue val, T& ret); /** * Convert a C++ type to a jsval. (This might trigger GC. The return * value must be rooted if you don't want it to be collected.) * NOTE: We are passing the JS::Value by reference instead of returning it by value. * The reason is a memory corruption problem that appears to be caused by a bug in Visual Studio. * Details here: http://www.wildfiregames.com/forum/index.php?showtopic=17289&p=285921 */ template static void ToJSVal(JSContext* cx, JS::MutableHandleValue ret, T const& val); /** * MaybeGC tries to determine whether garbage collection in cx's runtime would free up enough memory to be worth the amount of time it would take. * This calls JS_MaybeGC directly, which does not do incremental GC. Usually you should prefer MaybeIncrementalRuntimeGC. */ void MaybeGC(); /** * Triggers a full non-incremental garbage collection immediately. That should only be required in special cases and normally * you should try to use MaybeIncrementalRuntimeGC instead. */ void ForceGC(); /** * MathRandom (this function) calls the random number generator assigned to this ScriptInterface instance and * returns the generated number. * Math_random (with underscore, not this function) is a global function, but different random number generators can be * stored per ScriptInterface. It calls MathRandom of the current ScriptInterface instance. */ bool MathRandom(double& nbr); /** * Structured clones are a way to serialize 'simple' JS values into a buffer * that can safely be passed between contexts and runtimes and threads. * A StructuredClone can be stored and read multiple times if desired. * We wrap them in shared_ptr so memory management is automatic and * thread-safe. */ class StructuredClone { NONCOPYABLE(StructuredClone); public: StructuredClone(); ~StructuredClone(); u64* m_Data; size_t m_Size; }; shared_ptr WriteStructuredClone(JS::HandleValue v); void ReadStructuredClone(const shared_ptr& ptr, JS::MutableHandleValue ret); /** * Converts |a| if needed and assigns it to |handle|. * This is meant for use in other templates where we want to use the same code for JS::RootedValue&/JS::HandleValue and * other types. Note that functions are meant to take JS::HandleValue instead of JS::RootedValue&, but this implicit * conversion does not work for templates (exact type matches required for type deduction). * A similar functionality could also be implemented as a ToJSVal specialization. The current approach was preferred * because "conversions" from JS::HandleValue to JS::MutableHandleValue are unusual and should not happen "by accident". */ template static void AssignOrToJSVal(JSContext* cx, JS::MutableHandleValue handle, const T& a); /** * The same as AssignOrToJSVal, but also allows JS::Value for T. * In most cases it's not safe to use the plain (unrooted) JS::Value type, but this can happen quite * easily with template functions. The idea is that the linker prints an error if AssignOrToJSVal is * used with JS::Value. If the specialization for JS::Value should be allowed, you can use this * "unrooted" version of AssignOrToJSVal. */ template static void AssignOrToJSValUnrooted(JSContext* cx, JS::MutableHandleValue handle, const T& a) { AssignOrToJSVal(cx, handle, a); } /** * Converts |val| to T if needed or just returns it if it's a handle. * This is meant for use in other templates where we want to use the same code for JS::HandleValue and * other types. */ template static T AssignOrFromJSVal(JSContext* cx, const JS::HandleValue& val, bool& ret); private: bool CallFunction_(JS::HandleValue val, const char* name, JS::HandleValueArray argv, JS::MutableHandleValue ret) const; bool Eval_(const char* code, JS::MutableHandleValue ret); bool Eval_(const wchar_t* code, JS::MutableHandleValue ret); bool SetGlobal_(const char* name, JS::HandleValue value, bool replace); bool SetProperty_(JS::HandleValue obj, const char* name, JS::HandleValue value, bool readonly, bool enumerate); bool SetProperty_(JS::HandleValue obj, const wchar_t* name, JS::HandleValue value, bool readonly, bool enumerate); bool SetPropertyInt_(JS::HandleValue obj, int name, JS::HandleValue value, bool readonly, bool enumerate); bool GetProperty_(JS::HandleValue obj, const char* name, JS::MutableHandleValue out); bool GetPropertyInt_(JS::HandleValue obj, int name, JS::MutableHandleValue value); static bool IsExceptionPending(JSContext* cx); static const JSClass* GetClass(JS::HandleObject obj); static void* GetPrivate(JS::HandleObject obj); struct CustomType { // TODO: Move assignment operator and move constructor only have to be // explicitly defined for Visual Studio. VS2013 is still behind on C++11 support // What's missing is what they call "Rvalue references v3.0", see // https://msdn.microsoft.com/en-us/library/hh567368.aspx#rvref CustomType() {} CustomType& operator=(CustomType&& other) { m_Prototype = std::move(other.m_Prototype); m_Class = std::move(other.m_Class); m_Constructor = std::move(other.m_Constructor); return *this; } CustomType(CustomType&& other) { m_Prototype = std::move(other.m_Prototype); m_Class = std::move(other.m_Class); m_Constructor = std::move(other.m_Constructor); } JS::PersistentRootedObject m_Prototype; JSClass* m_Class; JSNative m_Constructor; }; void Register(const char* name, JSNative fptr, size_t nargs); // Take care to keep this declaration before heap rooted members. Destructors of heap rooted // members have to be called before the runtime destructor. std::unique_ptr m; boost::rand48* m_rng; std::map m_CustomObjectTypes; // The nasty macro/template bits are split into a separate file so you don't have to look at them public: #include "NativeWrapperDecls.h" // This declares: // // template // void RegisterFunction(const char* functionName); // // template // static JSNative call; // // template // static JSNative callMethod; // // template // static JSNative callMethodConst; // - // template + // template // static size_t nargs(); + // + // template + // bool CallFunction(JS::HandleValue val, const char* name, R& ret, const T0&...) const; + // + // template + // bool CallFunction(JS::HandleValue val, const char* name, JS::Rooted* ret, const T0&...) const; + // + // template + // bool CallFunction(JS::HandleValue val, const char* name, JS::MutableHandle ret, const T0&...) const; + // + // template + // bool CallFunctionVoid(JS::HandleValue val, const char* name, const T0&...) const; }; // Implement those declared functions #include "NativeWrapperDefns.h" template inline void ScriptInterface::AssignOrToJSVal(JSContext* cx, JS::MutableHandleValue handle, const T& a) { ToJSVal(cx, handle, a); } template<> inline void ScriptInterface::AssignOrToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue handle, const JS::PersistentRootedValue& a) { handle.set(a); } template<> inline void ScriptInterface::AssignOrToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue handle, const JS::RootedValue& a) { handle.set(a); } template <> inline void ScriptInterface::AssignOrToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue handle, const JS::HandleValue& a) { handle.set(a); } template <> inline void ScriptInterface::AssignOrToJSValUnrooted(JSContext* UNUSED(cx), JS::MutableHandleValue handle, const JS::Value& a) { handle.set(a); } template inline T ScriptInterface::AssignOrFromJSVal(JSContext* cx, const JS::HandleValue& val, bool& ret) { T retVal; ret = FromJSVal(cx, val, retVal); return retVal; } template<> inline JS::HandleValue ScriptInterface::AssignOrFromJSVal(JSContext* UNUSED(cx), const JS::HandleValue& val, bool& ret) { ret = true; return val; } -template -bool ScriptInterface::CallFunctionVoid(JS::HandleValue val, const char* name, const T0& a0) -{ - JSContext* cx = GetContext(); - JSAutoRequest rq(cx); - JS::RootedValue jsRet(cx); - JS::AutoValueVector argv(cx); - argv.resize(1); - AssignOrToJSVal(cx, argv[0], a0); - return CallFunction_(val, name, argv, &jsRet); -} - -template -bool ScriptInterface::CallFunctionVoid(JS::HandleValue val, const char* name, const T0& a0, const T1& a1) -{ - JSContext* cx = GetContext(); - JSAutoRequest rq(cx); - JS::RootedValue jsRet(cx); - JS::AutoValueVector argv(cx); - argv.resize(2); - AssignOrToJSVal(cx, argv[0], a0); - AssignOrToJSVal(cx, argv[1], a1); - return CallFunction_(val, name, argv, &jsRet); -} - -template -bool ScriptInterface::CallFunctionVoid(JS::HandleValue val, const char* name, const T0& a0, const T1& a1, const T2& a2) -{ - JSContext* cx = GetContext(); - JSAutoRequest rq(cx); - JS::RootedValue jsRet(cx); - JS::AutoValueVector argv(cx); - argv.resize(3); - AssignOrToJSVal(cx, argv[0], a0); - AssignOrToJSVal(cx, argv[1], a1); - AssignOrToJSVal(cx, argv[2], a2); - return CallFunction_(val, name, argv, &jsRet); -} - template bool ScriptInterface::SetGlobal(const char* name, const T& value, bool replace) { JSAutoRequest rq(GetContext()); JS::RootedValue val(GetContext()); AssignOrToJSVal(GetContext(), &val, value); return SetGlobal_(name, val, replace); } template bool ScriptInterface::SetProperty(JS::HandleValue obj, const char* name, const T& value, bool readonly, bool enumerate) { JSAutoRequest rq(GetContext()); JS::RootedValue val(GetContext()); AssignOrToJSVal(GetContext(), &val, value); return SetProperty_(obj, name, val, readonly, enumerate); } template bool ScriptInterface::SetProperty(JS::HandleValue obj, const wchar_t* name, const T& value, bool readonly, bool enumerate) { JSAutoRequest rq(GetContext()); JS::RootedValue val(GetContext()); AssignOrToJSVal(GetContext(), &val, value); return SetProperty_(obj, name, val, readonly, enumerate); } template bool ScriptInterface::SetPropertyInt(JS::HandleValue obj, int name, const T& value, bool readonly, bool enumerate) { JSAutoRequest rq(GetContext()); JS::RootedValue val(GetContext()); AssignOrToJSVal(GetContext(), &val, value); return SetPropertyInt_(obj, name, val, readonly, enumerate); } template bool ScriptInterface::GetProperty(JS::HandleValue obj, const char* name, T& out) { JSContext* cx = GetContext(); JSAutoRequest rq(cx); JS::RootedValue val(cx); if (! GetProperty_(obj, name, &val)) return false; return FromJSVal(cx, val, out); } template bool ScriptInterface::GetPropertyInt(JS::HandleValue obj, int name, T& out) { JSAutoRequest rq(GetContext()); JS::RootedValue val(GetContext()); if (! GetPropertyInt_(obj, name, &val)) return false; return FromJSVal(GetContext(), val, out); } template bool ScriptInterface::Eval(const CHAR* code, JS::MutableHandleValue ret) { if (! Eval_(code, ret)) return false; return true; } template bool ScriptInterface::Eval(const CHAR* code, T& ret) { JSAutoRequest rq(GetContext()); JS::RootedValue rval(GetContext()); if (! Eval_(code, &rval)) return false; return FromJSVal(GetContext(), rval, ret); } #endif // INCLUDED_SCRIPTINTERFACE Index: ps/trunk/source/scriptinterface/tests/test_ScriptConversions.h =================================================================== --- ps/trunk/source/scriptinterface/tests/test_ScriptConversions.h (revision 19182) +++ ps/trunk/source/scriptinterface/tests/test_ScriptConversions.h (revision 19183) @@ -1,260 +1,260 @@ -/* Copyright (C) 2016 Wildfire Games. +/* Copyright (C) 2017 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 . */ #include "lib/self_test.h" #include "scriptinterface/ScriptInterface.h" #include "maths/Fixed.h" #include "maths/FixedVector2D.h" #include "maths/FixedVector3D.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "jsapi.h" class TestScriptConversions : public CxxTest::TestSuite { template void convert_to(const T& value, const std::string& expected) { ScriptInterface script("Test", "Test", g_ScriptRuntime); TS_ASSERT(script.LoadGlobalScripts()); JSContext* cx = script.GetContext(); JSAutoRequest rq(cx); JS::RootedValue v1(cx); ScriptInterface::ToJSVal(cx, &v1, value); // We want to convert values to strings, but can't just call toSource() on them // since they might not be objects. So just use uneval. std::string source; JS::RootedValue global(cx, script.GetGlobalObject()); - TS_ASSERT(script.CallFunction(global, "uneval", v1, source)); + TS_ASSERT(script.CallFunction(global, "uneval", source, v1)); TS_ASSERT_STR_EQUALS(source, expected); } template void roundtrip(const T& value, const char* expected) { ScriptInterface script("Test", "Test", g_ScriptRuntime); TS_ASSERT(script.LoadGlobalScripts()); JSContext* cx = script.GetContext(); JSAutoRequest rq(cx); JS::RootedValue v1(cx); ScriptInterface::ToJSVal(cx, &v1, value); std::string source; JS::RootedValue global(cx, script.GetGlobalObject()); - TS_ASSERT(script.CallFunction(global, "uneval", v1, source)); + TS_ASSERT(script.CallFunction(global, "uneval", source, v1)); if (expected) TS_ASSERT_STR_EQUALS(source, expected); T v2 = T(); TS_ASSERT(ScriptInterface::FromJSVal(cx, v1, v2)); TS_ASSERT_EQUALS(value, v2); } template void call_prototype_function(const T& u, const T& v, const std::string& func, const std::string& expected) { ScriptInterface script("Test", "Test", g_ScriptRuntime); TS_ASSERT(script.LoadGlobalScripts()); JSContext* cx = script.GetContext(); JSAutoRequest rq(cx); JS::RootedValue v1(cx); ScriptInterface::ToJSVal(cx, &v1, v); JS::RootedValue u1(cx); ScriptInterface::ToJSVal(cx, &u1, u); T r; JS::RootedValue r1(cx); - TS_ASSERT(script.CallFunction(u1, func.c_str(), v1, r)); + TS_ASSERT(script.CallFunction(u1, func.c_str(), r, v1)); ScriptInterface::ToJSVal(cx, &r1, r); std::string source; JS::RootedValue global(cx, script.GetGlobalObject()); - TS_ASSERT(script.CallFunction(global, "uneval", r1, source)); + TS_ASSERT(script.CallFunction(global, "uneval", source, r1)); TS_ASSERT_STR_EQUALS(source, expected); } public: void setUp() { g_VFS = CreateVfs(20 * MiB); TS_ASSERT_OK(g_VFS->Mount(L"", DataDir()/"mods"/"_test.sim", VFS_MOUNT_MUST_EXIST)); } void tearDown() { g_VFS.reset(); } void test_roundtrip() { roundtrip(true, "true"); roundtrip(false, "false"); roundtrip(0, "0"); roundtrip(0.5, "0.5"); roundtrip(1e9f, "1000000000"); roundtrip(1e30f, "1.0000000150474662e+30"); roundtrip(0, "0"); roundtrip(123, "123"); roundtrip(-123, "-123"); roundtrip(1073741822, "1073741822"); // JSVAL_INT_MAX-1 roundtrip(1073741823, "1073741823"); // JSVAL_INT_MAX roundtrip(-1073741823, "-1073741823"); // JSVAL_INT_MIN+1 roundtrip(-1073741824, "-1073741824"); // JSVAL_INT_MIN roundtrip(0, "0"); roundtrip(123, "123"); roundtrip(1073741822, "1073741822"); // JSVAL_INT_MAX-1 roundtrip(1073741823, "1073741823"); // JSVAL_INT_MAX { TestLogger log; // swallow warnings about values not being stored as INT jsvals roundtrip(1073741824, "1073741824"); // JSVAL_INT_MAX+1 roundtrip(-1073741825, "-1073741825"); // JSVAL_INT_MIN-1 roundtrip(1073741824, "1073741824"); // JSVAL_INT_MAX+1 } std::string s1 = "test"; s1[1] = '\0'; std::string s2 = "тест"; s2[2] = s2[3] = '\0'; std::wstring w1 = L"test"; w1[1] = '\0'; std::wstring w2 = L"тест"; w2[1] = '\0'; roundtrip("", "\"\""); roundtrip("test", "\"test\""); roundtrip("тест", "\"\\xD1\\x82\\xD0\\xB5\\xD1\\x81\\xD1\\x82\""); roundtrip(s1, "\"t\\x00st\""); roundtrip(s2, "\"\\xD1\\x82\\x00\\x00\\xD1\\x81\\xD1\\x82\""); roundtrip(L"", "\"\""); roundtrip(L"test", "\"test\""); // Windows has two byte wchar_t. We test for this explicitly since we can catch more possible issues this way. roundtrip(L"тест", sizeof(wchar_t) == 2 ? "\"\\xD1\\u201A\\xD0\\xB5\\xD1\\x81\\xD1\\u201A\"" : "\"\\u0442\\u0435\\u0441\\u0442\""); roundtrip(w1, "\"t\\x00st\""); roundtrip(w2, sizeof(wchar_t) == 2 ? "\"\\xD1\\x00\\xD0\\xB5\\xD1\\x81\\xD1\\u201A\"" : "\"\\u0442\\x00\\u0441\\u0442\""); convert_to("", "\"\""); convert_to("test", "\"test\""); convert_to(s1.c_str(), "\"t\""); convert_to(s2.c_str(), "\"\\xD1\\x82\""); roundtrip(fixed::FromInt(0), "0"); roundtrip(fixed::FromInt(123), "123"); roundtrip(fixed::FromInt(-123), "-123"); roundtrip(fixed::FromDouble(123.25), "123.25"); } void test_integers() { ScriptInterface script("Test", "Test", g_ScriptRuntime); JSContext* cx = script.GetContext(); JSAutoRequest rq(cx); // using new uninitialized variables each time to be sure the test doesn't succeeed if ToJSVal doesn't touch the value at all. JS::RootedValue val0(cx), val1(cx), val2(cx), val3(cx), val4(cx), val5(cx), val6(cx), val7(cx), val8(cx); ScriptInterface::ToJSVal(cx, &val0, 0); ScriptInterface::ToJSVal(cx, &val1, 2147483646); // JSVAL_INT_MAX-1 ScriptInterface::ToJSVal(cx, &val2, 2147483647); // JSVAL_INT_MAX ScriptInterface::ToJSVal(cx, &val3, -2147483647); // JSVAL_INT_MIN+1 ScriptInterface::ToJSVal(cx, &val4, -(i64)2147483648u); // JSVAL_INT_MIN TS_ASSERT(val0.isInt32()); TS_ASSERT(val1.isInt32()); TS_ASSERT(val2.isInt32()); TS_ASSERT(val3.isInt32()); TS_ASSERT(val4.isInt32()); ScriptInterface::ToJSVal(cx, &val5, 0); ScriptInterface::ToJSVal(cx, &val6, 2147483646u); // JSVAL_INT_MAX-1 ScriptInterface::ToJSVal(cx, &val7, 2147483647u); // JSVAL_INT_MAX ScriptInterface::ToJSVal(cx, &val8, 2147483648u); // JSVAL_INT_MAX+1 TS_ASSERT(val5.isInt32()); TS_ASSERT(val6.isInt32()); TS_ASSERT(val7.isInt32()); TS_ASSERT(val8.isDouble()); } void test_nonfinite() { roundtrip(std::numeric_limits::infinity(), "Infinity"); roundtrip(-std::numeric_limits::infinity(), "-Infinity"); convert_to(std::numeric_limits::quiet_NaN(), "NaN"); // can't use roundtrip since nan != nan ScriptInterface script("Test", "Test", g_ScriptRuntime); JSContext* cx = script.GetContext(); JSAutoRequest rq(cx); float f = 0; JS::RootedValue testNANVal(cx); ScriptInterface::ToJSVal(cx, &testNANVal, NAN); TS_ASSERT(ScriptInterface::FromJSVal(cx, testNANVal, f)); TS_ASSERT(isnan(f)); } // NOTE: fixed and vector conversions are defined in simulation2/scripting/EngineScriptConversions.cpp void test_fixed() { fixed f; f.SetInternalValue(10590283); roundtrip(f, "161.5948944091797"); f.SetInternalValue(-10590283); roundtrip(f, "-161.5948944091797"); f.SetInternalValue(2000000000); roundtrip(f, "30517.578125"); f.SetInternalValue(2000000001); roundtrip(f, "30517.57814025879"); } void test_vector2d() { CFixedVector2D v(fixed::Zero(), fixed::Pi()); roundtrip(v, "({x:0, y:3.1415863037109375})"); CFixedVector2D u(fixed::FromInt(1), fixed::Zero()); call_prototype_function(u, v, "add", "({x:1, y:3.1415863037109375})"); } void test_vector3d() { CFixedVector3D v(fixed::Zero(), fixed::Pi(), fixed::FromInt(1)); roundtrip(v, "({x:0, y:3.1415863037109375, z:1})"); CFixedVector3D u(fixed::Pi(), fixed::Zero(), fixed::FromInt(2)); call_prototype_function(u, v, "add", "({x:3.1415863037109375, y:3.1415863037109375, z:3})"); } }; Index: ps/trunk/source/scriptinterface/tests/test_ScriptInterface.h =================================================================== --- ps/trunk/source/scriptinterface/tests/test_ScriptInterface.h (revision 19182) +++ ps/trunk/source/scriptinterface/tests/test_ScriptInterface.h (revision 19183) @@ -1,257 +1,257 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2017 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 . */ #include "lib/self_test.h" #include "scriptinterface/ScriptInterface.h" #include "ps/CLogger.h" #include class TestScriptInterface : public CxxTest::TestSuite { public: void test_loadscript_basic() { ScriptInterface script("Test", "Test", g_ScriptRuntime); TestLogger logger; TS_ASSERT(script.LoadScript(L"test.js", "var x = 1+1;")); TS_ASSERT_STR_NOT_CONTAINS(logger.GetOutput(), "JavaScript error"); TS_ASSERT_STR_NOT_CONTAINS(logger.GetOutput(), "JavaScript warning"); } void test_loadscript_error() { ScriptInterface script("Test", "Test", g_ScriptRuntime); TestLogger logger; TS_ASSERT(!script.LoadScript(L"test.js", "1+")); TS_ASSERT_STR_CONTAINS(logger.GetOutput(), "JavaScript error: test.js line 1\nSyntaxError: expected expression, got end of script"); } void test_loadscript_strict_warning() { ScriptInterface script("Test", "Test", g_ScriptRuntime); TestLogger logger; // in strict mode, this inside a function doesn't point to the global object TS_ASSERT(script.LoadScript(L"test.js", "var isStrict = (function() { return !this; })();warn('isStrict is '+isStrict);")); TS_ASSERT_STR_CONTAINS(logger.GetOutput(), "WARNING: isStrict is true"); } void test_loadscript_strict_error() { ScriptInterface script("Test", "Test", g_ScriptRuntime); TestLogger logger; TS_ASSERT(!script.LoadScript(L"test.js", "with(1){}")); TS_ASSERT_STR_CONTAINS(logger.GetOutput(), "JavaScript error: test.js line 1\nSyntaxError: strict mode code may not contain \'with\' statements"); } void test_clone_basic() { ScriptInterface script1("Test", "Test", g_ScriptRuntime); ScriptInterface script2("Test", "Test", g_ScriptRuntime); JSContext* cx1 = script1.GetContext(); JSAutoRequest rq1(cx1); JS::RootedValue obj1(cx1); TS_ASSERT(script1.Eval("({'x': 123, 'y': [1, 1.5, '2', 'test', undefined, null, true, false]})", &obj1)); { JSContext* cx2 = script2.GetContext(); JSAutoRequest rq2(cx2); JS::RootedValue obj2(cx2, script2.CloneValueFromOtherContext(script1, obj1)); std::string source; TS_ASSERT(script2.CallFunction(obj2, "toSource", source)); TS_ASSERT_STR_EQUALS(source, "({x:123, y:[1, 1.5, \"2\", \"test\", (void 0), null, true, false]})"); } } void test_clone_getters() { // The tests should be run with JS_SetGCZeal so this can try to find GC bugs ScriptInterface script1("Test", "Test", g_ScriptRuntime); ScriptInterface script2("Test", "Test", g_ScriptRuntime); JSContext* cx1 = script1.GetContext(); JSAutoRequest rq1(cx1); JS::RootedValue obj1(cx1); TS_ASSERT(script1.Eval("var s = '?'; var v = ({get x() { return 123 }, 'y': {'w':{get z() { delete v.y; delete v.n; v = null; s += s; return 4 }}}, 'n': 100}); v", &obj1)); { JSContext* cx2 = script2.GetContext(); JSAutoRequest rq2(cx2); JS::RootedValue obj2(cx2, script2.CloneValueFromOtherContext(script1, obj1)); std::string source; TS_ASSERT(script2.CallFunction(obj2, "toSource", source)); TS_ASSERT_STR_EQUALS(source, "({x:123, y:{w:{z:4}}})"); } } void test_clone_cyclic() { ScriptInterface script1("Test", "Test", g_ScriptRuntime); ScriptInterface script2("Test", "Test", g_ScriptRuntime); JSContext* cx1 = script1.GetContext(); JSAutoRequest rq1(cx1); JS::RootedValue obj1(cx1); TS_ASSERT(script1.Eval("var x = []; x[0] = x; ({'a': x, 'b': x})", &obj1)); { JSContext* cx2 = script2.GetContext(); JSAutoRequest rq(cx2); JS::RootedValue obj2(cx2, script2.CloneValueFromOtherContext(script1, obj1)); // Use JSAPI function to check if the values of the properties "a", "b" are equals a.x[0] JS::RootedValue prop_a(cx2); JS::RootedValue prop_b(cx2); JS::RootedValue prop_x1(cx2); TS_ASSERT(script2.GetProperty(obj2, "a", &prop_a)); TS_ASSERT(script2.GetProperty(obj2, "b", &prop_b)); TS_ASSERT(prop_a.isObject()); TS_ASSERT(prop_b.isObject()); TS_ASSERT(script2.GetProperty(prop_a, "0", &prop_x1)); TS_ASSERT_EQUALS(prop_x1.get(), prop_a.get()); TS_ASSERT_EQUALS(prop_x1.get(), prop_b.get()); } } /** * This test is mainly to make sure that all required template overloads get instantiated at least once so that compiler errors * in these functions are revealed instantly (but it also tests the basic functionality of these functions). */ void test_rooted_templates() { ScriptInterface script("Test", "Test", g_ScriptRuntime); JSContext* cx = script.GetContext(); JSAutoRequest rq(cx); JS::RootedValue val(cx); JS::RootedValue out(cx); TS_ASSERT(script.Eval("({ " "'0':0," "inc:function() { this[0]++; return this[0]; }, " "setTo:function(nbr) { this[0] = nbr; }, " "add:function(nbr) { this[0] += nbr; return this[0]; } " "})" , &val)); JS::RootedValue nbrVal(cx, JS::NumberValue(3)); int nbr = 0; // CallFunctionVoid JS::RootedValue& parameter overload script.CallFunctionVoid(val, "setTo", nbrVal); // CallFunction JS::RootedValue* out parameter overload script.CallFunction(val, "inc", &out); ScriptInterface::FromJSVal(cx, out, nbr); TS_ASSERT_EQUALS(4, nbr); // CallFunction const JS::RootedValue& parameter overload - script.CallFunction(val, "add", nbrVal, nbr); + script.CallFunction(val, "add", nbr, nbrVal); TS_ASSERT_EQUALS(7, nbr); // GetProperty JS::RootedValue* overload nbr = 0; script.GetProperty(val, "0", &out); ScriptInterface::FromJSVal(cx, out, nbr); TS_ASSERT_EQUALS(nbr, 7); // GetPropertyInt JS::RootedValue* overload nbr = 0; script.GetPropertyInt(val, 0, &out); ScriptInterface::FromJSVal(cx, out, nbr); TS_ASSERT_EQUALS(nbr, 7); handle_templates_test(script, val, &out, nbrVal); } void handle_templates_test(ScriptInterface& script, JS::HandleValue val, JS::MutableHandleValue out, JS::HandleValue nbrVal) { int nbr = 0; // CallFunctionVoid JS::HandleValue parameter overload script.CallFunctionVoid(val, "setTo", nbrVal); // CallFunction JS::MutableHandleValue out parameter overload script.CallFunction(val, "inc", out); ScriptInterface::FromJSVal(script.GetContext(), out, nbr); TS_ASSERT_EQUALS(4, nbr); // CallFunction const JS::HandleValue& parameter overload - script.CallFunction(val, "add", nbrVal, nbr); + script.CallFunction(val, "add", nbr, nbrVal); TS_ASSERT_EQUALS(7, nbr); // GetProperty JS::MutableHandleValue overload nbr = 0; script.GetProperty(val, "0", out); ScriptInterface::FromJSVal(script.GetContext(), out, nbr); TS_ASSERT_EQUALS(nbr, 7); // GetPropertyInt JS::MutableHandleValue overload nbr = 0; script.GetPropertyInt(val, 0, out); ScriptInterface::FromJSVal(script.GetContext(), out, nbr); TS_ASSERT_EQUALS(nbr, 7); } void test_random() { ScriptInterface script("Test", "Test", g_ScriptRuntime); double d1, d2; TS_ASSERT(script.Eval("Math.random()", d1)); TS_ASSERT(script.Eval("Math.random()", d2)); TS_ASSERT_DIFFERS(d1, d2); boost::rand48 rng; script.ReplaceNondeterministicRNG(rng); rng.seed((u64)0); TS_ASSERT(script.Eval("Math.random()", d1)); TS_ASSERT(script.Eval("Math.random()", d2)); TS_ASSERT_DIFFERS(d1, d2); rng.seed((u64)0); TS_ASSERT(script.Eval("Math.random()", d2)); TS_ASSERT_EQUALS(d1, d2); } void test_json() { ScriptInterface script("Test", "Test", g_ScriptRuntime); JSContext* cx = script.GetContext(); JSAutoRequest rq(cx); std::string input = "({'x':1,'z':[2,'3\\u263A\\ud800'],\"y\":true})"; JS::RootedValue val(cx); TS_ASSERT(script.Eval(input.c_str(), &val)); std::string stringified = script.StringifyJSON(&val); TS_ASSERT_STR_EQUALS(stringified, "{\n \"x\": 1,\n \"z\": [\n 2,\n \"3\xE2\x98\xBA\xEF\xBF\xBD\"\n ],\n \"y\": true\n}"); TS_ASSERT(script.ParseJSON(stringified, &val)); TS_ASSERT_STR_EQUALS(script.ToString(&val), "({x:1, z:[2, \"3\\u263A\\uFFFD\"], y:true})"); } }; Index: ps/trunk/source/simulation2/components/ICmpAIInterface.cpp =================================================================== --- ps/trunk/source/simulation2/components/ICmpAIInterface.cpp (revision 19182) +++ ps/trunk/source/simulation2/components/ICmpAIInterface.cpp (revision 19183) @@ -1,44 +1,44 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2017 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 . */ #include "precompiled.h" #include "ICmpAIInterface.h" #include "simulation2/system/InterfaceScripted.h" #include "simulation2/scripting/ScriptComponent.h" BEGIN_INTERFACE_WRAPPER(AIInterface) END_INTERFACE_WRAPPER(AIInterface) class CCmpAIInterfaceScripted : public ICmpAIInterface { public: DEFAULT_SCRIPT_WRAPPER(AIInterfaceScripted) virtual void GetRepresentation(JS::MutableHandleValue ret) { - return m_Script.CallRef("GetRepresentation", ret); + m_Script.CallRef("GetRepresentation", ret); } virtual void GetFullRepresentation(JS::MutableHandleValue ret, bool flushEvents = false) { - return m_Script.CallRef("GetFullRepresentation",flushEvents, ret); + m_Script.CallRef("GetFullRepresentation", ret, flushEvents); } }; REGISTER_COMPONENT_SCRIPT_WRAPPER(AIInterfaceScripted) Index: ps/trunk/source/simulation2/components/ICmpDataTemplateManager.cpp =================================================================== --- ps/trunk/source/simulation2/components/ICmpDataTemplateManager.cpp (revision 19182) +++ ps/trunk/source/simulation2/components/ICmpDataTemplateManager.cpp (revision 19183) @@ -1,39 +1,39 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2017 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 . */ #include "precompiled.h" #include "ICmpDataTemplateManager.h" #include "simulation2/system/InterfaceScripted.h" #include "simulation2/scripting/ScriptComponent.h" BEGIN_INTERFACE_WRAPPER(DataTemplateManager) END_INTERFACE_WRAPPER(DataTemplateManager) class CCmpDataTemplateManagerScripted : public ICmpDataTemplateManager { public: DEFAULT_SCRIPT_WRAPPER(DataTemplateManagerScripted) virtual void GetAllTechs(JS::MutableHandleValue ret) { - return m_Script.CallRef("GetAllTechs", ret); + m_Script.CallRef("GetAllTechs", ret); } }; REGISTER_COMPONENT_SCRIPT_WRAPPER(DataTemplateManagerScripted) Index: ps/trunk/source/simulation2/components/ICmpGuiInterface.cpp =================================================================== --- ps/trunk/source/simulation2/components/ICmpGuiInterface.cpp (revision 19182) +++ ps/trunk/source/simulation2/components/ICmpGuiInterface.cpp (revision 19183) @@ -1,39 +1,39 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2017 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 . */ #include "precompiled.h" #include "ICmpGuiInterface.h" #include "simulation2/system/InterfaceScripted.h" #include "simulation2/scripting/ScriptComponent.h" BEGIN_INTERFACE_WRAPPER(GuiInterface) END_INTERFACE_WRAPPER(GuiInterface) class CCmpGuiInterfaceScripted : public ICmpGuiInterface { public: DEFAULT_SCRIPT_WRAPPER(GuiInterfaceScripted) virtual void ScriptCall(int player, const std::wstring& cmd, JS::HandleValue data, JS::MutableHandleValue ret) { - m_Script.CallRef("ScriptCall", player, cmd, data, ret); + m_Script.CallRef("ScriptCall", ret, player, cmd, data); } }; REGISTER_COMPONENT_SCRIPT_WRAPPER(GuiInterfaceScripted) Index: ps/trunk/source/simulation2/scripting/ScriptComponent.h =================================================================== --- ps/trunk/source/simulation2/scripting/ScriptComponent.h (revision 19182) +++ ps/trunk/source/simulation2/scripting/ScriptComponent.h (revision 19183) @@ -1,96 +1,78 @@ /* Copyright (C) 2017 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_SCRIPTCOMPONENT #define INCLUDED_SCRIPTCOMPONENT #include "simulation2/system/Component.h" #include "ps/CLogger.h" -#include -#include -#include - class CSimContext; class CParamNode; class ISerializer; class IDeserializer; class CComponentTypeScript { + NONCOPYABLE(CComponentTypeScript); public: CComponentTypeScript(ScriptInterface& scriptInterface, JS::HandleValue instance); JS::Value GetInstance() const { return m_Instance.get(); } void Init(const CParamNode& paramNode, entity_id_t ent); void Deinit(); void HandleMessage(const CMessage& msg, bool global); void Serialize(ISerializer& serialize); void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize, entity_id_t ent); - // Use Boost.PP to define: - // template R Call(const char* funcname) const; - // template R Call(const char* funcname, const T0& a0) const; - // ... - // template void CallRef(const char* funcname, R ret) const; - // template void CallRef(const char* funcname, const T0& a0, R ret) const; - // ... - // void CallVoid(const char* funcname) const; - // template void CallVoid(const char* funcname, const T0& a0) const; - // ... - -// CallRef is mainly used for returning script values with correct stack rooting. -#define OVERLOADS(z, i, data) \ - template \ - R Call(const char* funcname BOOST_PP_ENUM_TRAILING_BINARY_PARAMS(i, const T, &a)) const \ - { \ - R ret; \ - if (m_ScriptInterface.CallFunction(m_Instance, funcname BOOST_PP_ENUM_TRAILING_PARAMS(i, a), ret)) \ - return ret; \ - LOGERROR("Error calling component script function %s", funcname); \ - return R(); \ - } \ - template \ - void CallRef(const char* funcname BOOST_PP_ENUM_TRAILING_BINARY_PARAMS(i, const T, &a), R ret) const \ - { \ - if (!m_ScriptInterface.CallFunction(m_Instance, funcname BOOST_PP_ENUM_TRAILING_PARAMS(i, a), ret)) \ - LOGERROR("Error calling component script function %s", funcname); \ - } \ - BOOST_PP_IF(i, template<, ) BOOST_PP_ENUM_PARAMS(i, typename T) BOOST_PP_IF(i, >, ) \ - void CallVoid(const char* funcname BOOST_PP_ENUM_TRAILING_BINARY_PARAMS(i, const T, &a)) const \ - { \ - if (m_ScriptInterface.CallFunctionVoid(m_Instance, funcname BOOST_PP_ENUM_TRAILING_PARAMS(i, a))) \ - return; \ - LOGERROR("Error calling component script function %s", funcname); \ + template + R Call(const char* funcname, const Ts&... params) const + { + R ret; + if (m_ScriptInterface.CallFunction(m_Instance, funcname, ret, params...)) + return ret; + LOGERROR("Error calling component script function %s", funcname); + return R(); + } + + // CallRef is mainly used for returning script values with correct stack rooting. + template + void CallRef(const char* funcname, R ret, const Ts&... params) const + { + if (!m_ScriptInterface.CallFunction(m_Instance, funcname, ret, params...)) + LOGERROR("Error calling component script function %s", funcname); + } + + template + void CallVoid(const char* funcname, const Ts&... params) const + { + if (!m_ScriptInterface.CallFunctionVoid(m_Instance, funcname, params...)) + LOGERROR("Error calling component script function %s", funcname); } -BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~) -#undef OVERLOADS private: ScriptInterface& m_ScriptInterface; JS::PersistentRootedValue m_Instance; bool m_HasCustomSerialize; bool m_HasCustomDeserialize; bool m_HasNullSerialize; - - NONCOPYABLE(CComponentTypeScript); }; #endif // INCLUDED_SCRIPTCOMPONENT