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