Replace ScriptInterface::Call* with new ScriptFunction functions

Finishes work started in f3aedf88a6.
This removes the boost-CPP function wrappers entirely, in favour of pure
templated code in FunctionWrapper.h
The Call* functions were already heavily templated, so there is nothing
really new here. I just use tag dispatch to reduce the number of
overloads slightly.

The new functions do not need the script interface, only the script
request.

Differential Revision: https://code.wildfiregames.com/D3912
This was SVN commit r25354.
This commit is contained in:
wraitii 2021-05-01 14:04:53 +00:00
parent d9748173c7
commit d46a417748
20 changed files with 164 additions and 446 deletions

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -26,6 +26,7 @@
#include "ps/GameSetup/Config.h"
#include "ps/Profile.h"
#include "ps/XML/Xeromyces.h"
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ScriptContext.h"
#include "scriptinterface/ScriptInterface.h"
@ -137,7 +138,7 @@ void CGUIManager::SGUIPage::LoadPage(shared_ptr<ScriptContext> scriptContext)
JS::RootedValue global(rq.cx, rq.globalValue());
JS::RootedValue hotloadDataVal(rq.cx);
scriptInterface->CallFunction(global, "getHotloadData", &hotloadDataVal);
ScriptFunction::Call(rq, global, "getHotloadData", &hotloadDataVal);
hotloadData = scriptInterface->WriteStructuredClone(hotloadDataVal);
}
@ -212,7 +213,7 @@ void CGUIManager::SGUIPage::LoadPage(shared_ptr<ScriptContext> scriptContext)
scriptInterface->ReadStructuredClone(hotloadData, &hotloadDataVal);
if (scriptInterface->HasProperty(global, "init") &&
!scriptInterface->CallFunctionVoid(global, "init", initDataVal, hotloadDataVal))
!ScriptFunction::CallVoid(rq, global, "init", initDataVal, hotloadDataVal))
LOGERROR("GUI page '%s': Failed to call init() function", utf8_from_wstring(m_Name));
}
@ -301,7 +302,7 @@ InReaction CGUIManager::HandleEvent(const SDL_Event_* ev)
ScriptRequest rq(*top()->GetScriptInterface());
JS::RootedValue global(rq.cx, rq.globalValue());
if (top()->GetScriptInterface()->CallFunction(global, "handleInputBeforeGui", handled, *ev, top()->FindObjectUnderMouse()))
if (ScriptFunction::Call(rq, global, "handleInputBeforeGui", handled, *ev, top()->FindObjectUnderMouse()))
if (handled)
return IN_HANDLED;
}
@ -319,7 +320,7 @@ InReaction CGUIManager::HandleEvent(const SDL_Event_* ev)
JS::RootedValue global(rq.cx, rq.globalValue());
PROFILE("handleInputAfterGui");
if (top()->GetScriptInterface()->CallFunction(global, "handleInputAfterGui", handled, *ev))
if (ScriptFunction::Call(rq, global, "handleInputAfterGui", handled, *ev))
if (handled)
return IN_HANDLED;
}

View File

@ -41,6 +41,7 @@
#include "renderer/Renderer.h"
#include "renderer/TimeManager.h"
#include "renderer/WaterManager.h"
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpPlayer.h"
@ -328,7 +329,7 @@ PSRETURN CGame::ReallyStartGame()
JS::RootedValue global(rq.cx, rq.globalValue());
if (scriptInterface->HasProperty(global, "reallyStartGame"))
scriptInterface->CallFunctionVoid(global, "reallyStartGame");
ScriptFunction::CallVoid(rq, global, "reallyStartGame");
}
debug_printf("GAME STARTED, ALL INIT COMPLETE\n");

View File

@ -78,6 +78,7 @@
#include "renderer/Renderer.h"
#include "renderer/VertexBufferManager.h"
#include "renderer/ModelRenderer.h"
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/ScriptStats.h"
#include "scriptinterface/ScriptContext.h"
@ -1631,7 +1632,7 @@ void CancelLoad(const CStrW& message)
if (g_GUI &&
g_GUI->GetPageCount() &&
pScriptInterface->HasProperty(global, "cancelOnLoadGameError"))
pScriptInterface->CallFunctionVoid(global, "cancelOnLoadGameError", message);
ScriptFunction::CallVoid(rq, global, "cancelOnLoadGameError", message);
}
bool InDevelopmentCopy()

View File

@ -226,7 +226,7 @@ void RunHardwareDetection()
// Run the detection script:
JS::RootedValue global(rq.cx, rq.globalValue());
scriptInterface.CallFunctionVoid(global, "RunHardwareDetection", settings);
ScriptFunction::CallVoid(rq, global, "RunHardwareDetection", settings);
}
static void ReportSDL(const ScriptInterface& scriptInterface, JS::HandleValue settings)

View File

@ -207,6 +207,71 @@ private:
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
struct IgnoreResult_t {};
static inline IgnoreResult_t IgnoreResult;
/**
* Recursive helper to call AssignOrToJSVal
*/
template<int i, typename T, typename... Ts>
static void AssignOrToJSValHelper(const ScriptRequest& rq, JS::MutableHandleValueVector argv, const T& a, const Ts&... params)
{
ScriptInterface::AssignOrToJSVal(rq, argv[i], a);
AssignOrToJSValHelper<i+1>(rq, argv, params...);
}
template<int i, typename... Ts>
static void AssignOrToJSValHelper(const ScriptRequest& UNUSED(rq), JS::MutableHandleValueVector UNUSED(argv))
{
static_assert(sizeof...(Ts) == 0);
// Nop, for terminating the template recursion.
}
/**
* Wrapper around calling a JS function from C++.
* Arguments are const& to avoid lvalue/rvalue issues, and so can't be used as out-parameters.
* In particular, the problem is that Rooted are deduced as Rooted, not Handle, and so can't be copied.
* This could be worked around with more templates, but it doesn't seem particularly worth doing.
*/
template<typename R, typename ...Args>
static bool Call_(const ScriptRequest& rq, JS::HandleValue val, const char* name, R& ret, const Args&... args)
{
JS::RootedObject obj(rq.cx);
if (!JS_ValueToObject(rq.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(rq.cx, obj, name, &found) || !found)
return false;
JS::RootedValueVector argv(rq.cx);
ignore_result(argv.resize(sizeof...(Args)));
AssignOrToJSValHelper<0>(rq, &argv, args...);
bool success;
if constexpr (std::is_same_v<R, JS::MutableHandleValue>)
success = JS_CallFunctionName(rq.cx, obj, name, argv, ret);
else
{
JS::RootedValue jsRet(rq.cx);
success = JS_CallFunctionName(rq.cx, obj, name, argv, &jsRet);
if constexpr (!std::is_same_v<R, IgnoreResult_t>)
{
if (success)
ScriptInterface::FromJSVal(rq, jsRet, ret);
}
else
UNUSED2(ret); // VS2017 complains.
}
// Even if everything succeeded, there could be pending exceptions
return !ScriptException::CatchPending(rq) && success;
}
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
public:
template <typename T>
using ObjectGetter = T*(*)(const ScriptRequest&, JS::CallArgs&);
@ -279,6 +344,34 @@ public:
return !ScriptException::IsPending(rq);
}
/**
* Call a JS function @a name, property of object @a val, with the arguments @a args.
* @a ret will be updated with the return value, if any.
* @return the success (or failure) thereof.
*/
template<typename R, typename ...Args>
static bool Call(const ScriptRequest& rq, JS::HandleValue val, const char* name, R& ret, const Args&... args)
{
return Call_(rq, val, name, ret, std::forward<const Args>(args)...);
}
// Specialisation for MutableHandleValue return.
template<typename ...Args>
static bool Call(const ScriptRequest& rq, JS::HandleValue val, const char* name, JS::MutableHandleValue ret, const Args&... args)
{
return Call_(rq, val, name, ret, std::forward<const Args>(args)...);
}
/**
* Call a JS function @a name, property of object @a val, with the arguments @a args.
* @return the success (or failure) thereof.
*/
template<typename ...Args>
static bool CallVoid(const ScriptRequest& rq, JS::HandleValue val, const char* name, const Args&... args)
{
return Call(rq, val, name, IgnoreResult, std::forward<const Args>(args)...);
}
/**
* Return a function spec from a C++ function.
*/

View File

@ -1,102 +0,0 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
#include <boost/preprocessor/punctuation/comma_if.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
// 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 <typename T> 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<data##i>::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<data##i>::Type
// 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<T##i>( \
rq, \
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 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_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 A0_TAIL(z, i) BOOST_PP_REPEAT_##z (i, NUMBERED_LIST_TAIL, a) // ", a0, a1"
// 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 <typename R, TYPENAME_T0_HEAD(z,i) R (*fptr) ( ScriptInterface::CmptPrivate* T0_TAIL_MAYBE_REF(z,i) )> \
static bool call(JSContext* cx, uint argc, JS::Value* vp);
BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~)
#undef OVERLOADS
// Similar, for class methods
#define OVERLOADS(z, i, data) \
template <typename R, TYPENAME_T0_HEAD(z,i) JSClass* CLS, typename TC, R (TC::*fptr) ( T0_MAYBE_REF(z,i) )> \
static bool callMethod(JSContext* cx, uint argc, JS::Value* vp);
BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~)
#undef OVERLOADS
// const methods
#define OVERLOADS(z, i, data) \
template <typename R, TYPENAME_T0_HEAD(z,i) JSClass* CLS, typename TC, R (TC::*fptr) ( T0_MAYBE_REF(z,i) ) const> \
static bool callMethodConst(JSContext* cx, uint argc, JS::Value* vp);
BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~)
#undef OVERLOADS
// Argument-number counter
template<typename... Ts>
static size_t nargs() { return sizeof...(Ts); }
// Call the named property on the given object
template<typename R, typename... Ts>
bool CallFunction(JS::HandleValue val, const char* name, R& ret, const Ts&... params) const;
// Implicit conversion from JS::Rooted<R>* to JS::MutableHandle<R> does not work with template argument deduction
// (only exact type matches allowed). We need this overload to allow passing Rooted<R>* using the & operator
// (as people would expect it to work based on the SpiderMonkey rooting guide).
template<typename R, typename... Ts>
bool CallFunction(JS::HandleValue val, const char* name, JS::Rooted<R>* ret, const Ts&... params) const;
// This overload is for the case when a JS::MutableHandle<R> type gets passed into CallFunction directly and
// without requiring implicit conversion.
template<typename R, typename... Ts>
bool CallFunction(JS::HandleValue val, const char* name, JS::MutableHandle<R> ret, const Ts&... params) const;
// Call the named property on the given object, with void return type
template<typename... Ts> \
bool CallFunctionVoid(JS::HandleValue val, const char* name, const Ts&... params) const;

View File

@ -1,235 +0,0 @@
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* 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 <http://www.gnu.org/licenses/>.
*/
// 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 <typename T> struct ScriptInterface::MaybeRef
{
typedef const T& Type;
};
#define PASS_BY_VALUE_IN_NATIVE_WRAPPER(T) \
template <> struct ScriptInterface::MaybeRef<T> \
{ \
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 (error C2244 if ScriptInterface:: is included in the
// type specifier of MaybeRef<T>::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 2015, 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
#define NUMBERED_LIST_TAIL_MAYBE_REF(z, i, data) , typename ScriptInterface::MaybeRef<data##i>::Type
#define NUMBERED_LIST_BALANCED_MAYBE_REF(z, i, data) BOOST_PP_COMMA_IF(i) typename ScriptInterface::MaybeRef<data##i>::Type
// (NativeWrapperDecls.h set up a lot of the macros we use here)
// ScriptInterface_NativeWrapper<T>::call(rq, 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 <typename R>
struct ScriptInterface_NativeWrapper
{
template<typename F, typename... Ts>
static void call(const ScriptRequest& rq, JS::MutableHandleValue rval, F fptr, Ts... params)
{
ScriptInterface::AssignOrToJSValUnrooted<R>(rq, rval, fptr(ScriptInterface::GetScriptInterfaceAndCBData(rq.cx), params...));
}
};
// Overloaded to ignore the return value from void functions
template <>
struct ScriptInterface_NativeWrapper<void>
{
template<typename F, typename... Ts>
static void call(const ScriptRequest& rq, JS::MutableHandleValue UNUSED(rval), F fptr, Ts... params)
{
fptr(ScriptInterface::GetScriptInterfaceAndCBData(rq.cx), params...);
}
};
// Same idea but for method calls:
template <typename R, typename TC>
struct ScriptInterface_NativeMethodWrapper
{
template<typename F, typename... Ts>
static void call(const ScriptRequest& rq, JS::MutableHandleValue rval, TC* c, F fptr, Ts... params)
{
ScriptInterface::AssignOrToJSValUnrooted<R>(rq, rval, (c->*fptr)(params...));
}
};
template <typename TC>
struct ScriptInterface_NativeMethodWrapper<void, TC>
{
template<typename F, typename... Ts>
static void call(const ScriptRequest& UNUSED(rq), 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 <typename R, TYPENAME_T0_HEAD(z,i) R (*fptr) ( ScriptInterface::CmptPrivate* T0_TAIL_MAYBE_REF(z,i) )> \
bool ScriptInterface::call(JSContext* cx, uint argc, JS::Value* vp) \
{ \
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \
ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); \
BOOST_PP_REPEAT_##z (i, CONVERT_ARG, ~) \
JS::RootedValue rval(rq.cx); \
ScriptInterface_NativeWrapper<R>::template call<R( ScriptInterface::CmptPrivate* T0_TAIL_MAYBE_REF(z,i)) T0_TAIL(z,i)>(rq, &rval, fptr A0_TAIL(z,i)); \
args.rval().set(rval); \
return !ScriptException::IsPending(rq); \
}
BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~)
#undef OVERLOADS
// Same idea but for methods
#define OVERLOADS(z, i, data) \
template <typename R, TYPENAME_T0_HEAD(z,i) JSClass* CLS, typename TC, R (TC::*fptr) ( T0_MAYBE_REF(z,i) )> \
bool ScriptInterface::callMethod(JSContext* cx, uint argc, JS::Value* vp) \
{ \
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \
ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); \
TC* c = ScriptInterface::GetPrivate<TC>(rq, args, CLS); \
if (! c) return false; \
BOOST_PP_REPEAT_##z (i, CONVERT_ARG, ~) \
JS::RootedValue rval(rq.cx); \
ScriptInterface_NativeMethodWrapper<R, TC>::template call<R (TC::*)(T0_MAYBE_REF(z,i)) T0_TAIL(z,i)>(rq, &rval, c, fptr A0_TAIL(z,i)); \
args.rval().set(rval); \
return !ScriptException::IsPending(rq); \
}
BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~)
#undef OVERLOADS
// const methods
#define OVERLOADS(z, i, data) \
template <typename R, TYPENAME_T0_HEAD(z,i) JSClass* CLS, typename TC, R (TC::*fptr) ( T0_MAYBE_REF(z,i) ) const> \
bool ScriptInterface::callMethodConst(JSContext* cx, uint argc, JS::Value* vp) \
{ \
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \
ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); \
TC* c = ScriptInterface::GetPrivate<TC>(rq, args, CLS); \
if (! c) return false; \
BOOST_PP_REPEAT_##z (i, CONVERT_ARG, ~) \
JS::RootedValue rval(rq.cx); \
ScriptInterface_NativeMethodWrapper<R, TC>::template call<R (TC::*)(T0_MAYBE_REF(z,i)) const T0_TAIL(z,i)>(rq, &rval, c, fptr A0_TAIL(z,i)); \
args.rval().set(rval); \
return !ScriptException::IsPending(rq); \
}
BOOST_PP_REPEAT(SCRIPT_INTERFACE_MAX_ARGS, OVERLOADS, ~)
#undef OVERLOADS
template<int i, typename T, typename... Ts>
static void AssignOrToJSValHelper(const ScriptRequest& rq, JS::MutableHandleValueVector argv, const T& a, const Ts&... params)
{
ScriptInterface::AssignOrToJSVal(rq, argv[i], a);
AssignOrToJSValHelper<i+1>(rq, argv, params...);
}
template<int i, typename... Ts>
static void AssignOrToJSValHelper(const ScriptRequest& UNUSED(rq), JS::MutableHandleValueVector UNUSED(argv))
{
cassert(sizeof...(Ts) == 0);
// Nop, for terminating the template recursion.
}
template<typename R, typename... Ts>
bool ScriptInterface::CallFunction(JS::HandleValue val, const char* name, R& ret, const Ts&... params) const
{
ScriptRequest rq(this);
JS::RootedValue jsRet(rq.cx);
JS::RootedValueVector argv(rq.cx);
ignore_result(argv.resize(sizeof...(Ts)));
AssignOrToJSValHelper<0>(rq, &argv, params...);
if (!CallFunction_(val, name, argv, &jsRet))
return false;
return FromJSVal(rq, jsRet, ret);
}
template<typename R, typename... Ts>
bool ScriptInterface::CallFunction(JS::HandleValue val, const char* name, JS::Rooted<R>* ret, const Ts&... params) const
{
ScriptRequest rq(this);
JS::MutableHandle<R> jsRet(ret);
JS::RootedValueVector argv(rq.cx);
ignore_result(argv.resize(sizeof...(Ts)));
AssignOrToJSValHelper<0>(rq, &argv, params...);
return CallFunction_(val, name, argv, jsRet);
}
template<typename R, typename... Ts>
bool ScriptInterface::CallFunction(JS::HandleValue val, const char* name, JS::MutableHandle<R> ret, const Ts&... params) const
{
ScriptRequest rq(this);
JS::RootedValueVector argv(rq.cx);
ignore_result(argv.resize(sizeof...(Ts)));
AssignOrToJSValHelper<0>(rq, &argv, params...);
return CallFunction_(val, name, argv, ret);
}
// Call the named property on the given object, with void return type
template<typename... Ts>
bool ScriptInterface::CallFunctionVoid(JS::HandleValue val, const char* name, const Ts&... params) const
{
ScriptRequest rq(this);
JS::RootedValue jsRet(rq.cx);
JS::RootedValueVector argv(rq.cx);
ignore_result(argv.resize(sizeof...(Ts)));
AssignOrToJSValHelper<0>(rq, &argv, params...);
return CallFunction_(val, name, argv, &jsRet);
}
// 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 CONVERT_ARG
#undef TYPENAME_T0_HEAD
#undef T0
#undef T0_MAYBE_REF
#undef T0_TAIL
#undef T0_TAIL_MAYBE_REF
#undef A0_TAIL

View File

@ -472,26 +472,6 @@ JSObject* ScriptInterface::CreateCustomObject(const std::string& typeName) const
return JS_NewObjectWithGivenProto(rq.cx, it->second.m_Class, prototype);
}
bool ScriptInterface::CallFunction_(JS::HandleValue val, const char* name, JS::HandleValueArray argv, JS::MutableHandleValue ret) const
{
ScriptRequest rq(this);
JS::RootedObject obj(rq.cx);
if (!JS_ValueToObject(rq.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(rq.cx, obj, name, &found) || !found)
return false;
if (JS_CallFunctionName(rq.cx, obj, name, argv, ret))
return true;
ScriptException::CatchPending(rq);
return false;
}
bool ScriptInterface::CreateObject_(const ScriptRequest& rq, JS::MutableHandleObject object)
{
object.set(JS_NewPlainObject(rq.cx));
@ -977,7 +957,7 @@ std::string ScriptInterface::ToString(JS::MutableHandleValue obj, bool pretty) c
// so fall back to obj.toSource()
std::wstring source = L"(error)";
CallFunction(obj, "toSource", source);
ScriptFunction::Call(rq, obj, "toSource", source);
return utf8_from_wstring(source);
}

View File

@ -442,7 +442,6 @@ private:
return CreateObject_(rq, obj, args...) && JS_DefineProperty(rq.cx, obj, propertyName, val, JSPROP_ENUMERATE);
}
bool CallFunction_(JS::HandleValue val, const char* name, JS::HandleValueArray argv, JS::MutableHandleValue ret) const;
bool SetGlobal_(const char* name, JS::HandleValue value, bool replace, bool constant, bool enumerate);
bool SetProperty_(JS::HandleValue obj, const char* name, JS::HandleValue value, bool constant, bool enumerate) const;
bool SetProperty_(JS::HandleValue obj, const wchar_t* name, JS::HandleValue value, bool constant, bool enumerate) const;
@ -461,40 +460,8 @@ private:
boost::random::rand48* m_rng;
std::map<std::string, CustomType> 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 <R, T0..., TR (*fptr) (void* cbdata, T0...)>
// static JSNative call;
//
// template <R, T0..., JSClass*, TC, TR (TC:*fptr) (T0...)>
// static JSNative callMethod;
//
// template <R, T0..., JSClass*, TC, TR (TC:*fptr) const (T0...)>
// static JSNative callMethodConst;
//
// template <T0...>
// static size_t nargs();
//
// template <R, T0...>
// bool CallFunction(JS::HandleValue val, const char* name, R& ret, const T0&...) const;
//
// template <R, T0...>
// bool CallFunction(JS::HandleValue val, const char* name, JS::Rooted<R>* ret, const T0&...) const;
//
// template <R, T0...>
// bool CallFunction(JS::HandleValue val, const char* name, JS::MutableHandle<R> ret, const T0&...) const;
//
// template <T0...>
// bool CallFunctionVoid(JS::HandleValue val, const char* name, const T0&...) const;
};
// Implement those declared functions
#include "NativeWrapperDefns.h"
template<typename T>
inline void ScriptInterface::AssignOrToJSVal(const ScriptRequest& rq, JS::MutableHandleValue handle, const T& a)
{

View File

@ -17,6 +17,7 @@
#include "lib/self_test.h"
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ScriptInterface.h"
#include "maths/Fixed.h"
@ -44,7 +45,7 @@ class TestScriptConversions : public CxxTest::TestSuite
// since they might not be objects. So just use uneval.
std::string source;
JS::RootedValue global(rq.cx, rq.globalValue());
TS_ASSERT(script.CallFunction(global, "uneval", source, v1));
TS_ASSERT(ScriptFunction::Call(rq, global, "uneval", source, v1));
TS_ASSERT_STR_EQUALS(source, expected);
}
@ -61,7 +62,7 @@ class TestScriptConversions : public CxxTest::TestSuite
std::string source;
JS::RootedValue global(rq.cx, rq.globalValue());
TS_ASSERT(script.CallFunction(global, "uneval", source, v1));
TS_ASSERT(ScriptFunction::Call(rq, global, "uneval", source, v1));
if (expected)
TS_ASSERT_STR_EQUALS(source, expected);
@ -86,12 +87,12 @@ class TestScriptConversions : public CxxTest::TestSuite
T r;
JS::RootedValue r1(rq.cx);
TS_ASSERT(script.CallFunction(u1, func.c_str(), r, v1));
TS_ASSERT(ScriptFunction::Call(rq, u1, func.c_str(), r, v1));
ScriptInterface::ToJSVal(rq, &r1, r);
std::string source;
JS::RootedValue global(rq.cx, rq.globalValue());
TS_ASSERT(script.CallFunction(global, "uneval", source, r1));
TS_ASSERT(ScriptFunction::Call(rq, global, "uneval", source, r1));
TS_ASSERT_STR_EQUALS(source, expected);
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -17,6 +17,7 @@
#include "lib/self_test.h"
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ScriptInterface.h"
#include "ps/CLogger.h"
@ -75,7 +76,7 @@ public:
JS::RootedValue obj2(rq2.cx, script2.CloneValueFromOtherCompartment(script1, obj1));
std::string source;
TS_ASSERT(script2.CallFunction(obj2, "toSource", source));
TS_ASSERT(ScriptFunction::Call(rq2, obj2, "toSource", source));
TS_ASSERT_STR_EQUALS(source, "({x:123, y:[1, 1.5, \"2\", \"test\", (void 0), null, true, false]})");
}
}
@ -97,7 +98,7 @@ public:
JS::RootedValue obj2(rq2.cx, script2.CloneValueFromOtherCompartment(script1, obj1));
std::string source;
TS_ASSERT(script2.CallFunction(obj2, "toSource", source));
TS_ASSERT(ScriptFunction::Call(rq2, obj2, "toSource", source));
TS_ASSERT_STR_EQUALS(source, "({x:123, y:{w:{z:4}}})");
}
}
@ -153,17 +154,15 @@ public:
JS::RootedValue nbrVal(rq.cx, JS::NumberValue(3));
int nbr = 0;
// CallFunctionVoid JS::RootedValue& parameter overload
script.CallFunctionVoid(val, "setTo", nbrVal);
ScriptFunction::CallVoid(rq, val, "setTo", nbrVal);
// CallFunction JS::RootedValue* out parameter overload
script.CallFunction(val, "inc", &out);
// Test that a mutable handle value as return value works.
ScriptFunction::Call(rq, val, "inc", &out);
ScriptInterface::FromJSVal(rq, out, nbr);
TS_ASSERT_EQUALS(4, nbr);
// CallFunction const JS::RootedValue& parameter overload
script.CallFunction(val, "add", nbr, nbrVal);
ScriptFunction::Call(rq, val, "add", nbr, nbrVal);
TS_ASSERT_EQUALS(7, nbr);
// GetProperty JS::RootedValue* overload
@ -187,17 +186,13 @@ public:
int nbr = 0;
// CallFunctionVoid JS::HandleValue parameter overload
script.CallFunctionVoid(val, "setTo", nbrVal);
// CallFunction JS::MutableHandleValue out parameter overload
script.CallFunction(val, "inc", out);
ScriptFunction::CallVoid(rq, val, "setTo", nbrVal);
ScriptFunction::Call(rq, val, "inc", out);
ScriptInterface::FromJSVal(rq, out, nbr);
TS_ASSERT_EQUALS(4, nbr);
// CallFunction const JS::HandleValue& parameter overload
script.CallFunction(val, "add", nbr, nbrVal);
ScriptFunction::Call(rq, val, "add", nbr, nbrVal);
TS_ASSERT_EQUALS(7, nbr);
// GetProperty JS::MutableHandleValue overload

View File

@ -19,6 +19,7 @@
#include "Simulation2.h"
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ScriptContext.h"
#include "scriptinterface/ScriptInterface.h"
@ -726,7 +727,7 @@ void CSimulation2::PreInitGame()
{
ScriptRequest rq(GetScriptInterface());
JS::RootedValue global(rq.cx, rq.globalValue());
GetScriptInterface().CallFunctionVoid(global, "PreInitGame");
ScriptFunction::CallVoid(rq, global, "PreInitGame");
}
void CSimulation2::InitGame()
@ -738,7 +739,7 @@ void CSimulation2::InitGame()
JS::RootedValue tmpInitAttributes(rq.cx, GetInitAttributes());
GetScriptInterface().GetProperty(tmpInitAttributes, "settings", &settings);
GetScriptInterface().CallFunctionVoid(global, "InitGame", settings);
ScriptFunction::CallVoid(rq, global, "InitGame", settings);
}
void CSimulation2::Update(int turnLength)
@ -832,7 +833,7 @@ void CSimulation2::LoadPlayerSettings(bool newPlayers)
{
ScriptRequest rq(GetScriptInterface());
JS::RootedValue global(rq.cx, rq.globalValue());
GetScriptInterface().CallFunctionVoid(global, "LoadPlayerSettings", m->m_MapSettings, newPlayers);
ScriptFunction::CallVoid(rq, global, "LoadPlayerSettings", m->m_MapSettings, newPlayers);
}
void CSimulation2::LoadMapSettings()
@ -842,7 +843,7 @@ void CSimulation2::LoadMapSettings()
JS::RootedValue global(rq.cx, rq.globalValue());
// Initialize here instead of in Update()
GetScriptInterface().CallFunctionVoid(global, "LoadMapSettings", m->m_MapSettings);
ScriptFunction::CallVoid(rq, global, "LoadMapSettings", m->m_MapSettings);
GetScriptInterface().FreezeObject(m->m_InitAttributes, true);
GetScriptInterface().SetGlobal("InitAttributes", m->m_InitAttributes, true, true, true);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -174,19 +174,22 @@ private:
void Run(JS::HandleValue state, int playerID)
{
m_Commands.clear();
m_ScriptInterface->CallFunctionVoid(m_Obj, "HandleMessage", state, playerID);
ScriptRequest rq(m_ScriptInterface);
ScriptFunction::CallVoid(rq, m_Obj, "HandleMessage", state, playerID);
}
// overloaded with a sharedAI part.
// javascript can handle both natively on the same function.
void Run(JS::HandleValue state, int playerID, JS::HandleValue SharedAI)
{
m_Commands.clear();
m_ScriptInterface->CallFunctionVoid(m_Obj, "HandleMessage", state, playerID, SharedAI);
ScriptRequest rq(m_ScriptInterface);
ScriptFunction::CallVoid(rq, m_Obj, "HandleMessage", state, playerID, SharedAI);
}
void InitAI(JS::HandleValue state, JS::HandleValue SharedAI)
{
m_Commands.clear();
m_ScriptInterface->CallFunctionVoid(m_Obj, "Init", state, m_Player, SharedAI);
ScriptRequest rq(m_ScriptInterface);
ScriptFunction::CallVoid(rq, m_Obj, "Init", state, m_Player, SharedAI);
}
CAIWorker& m_Worker;
@ -486,7 +489,7 @@ public:
{
m_ScriptInterface->SetProperty(state, "passabilityMap", m_PassabilityMapVal, true);
m_ScriptInterface->SetProperty(state, "territoryMap", m_TerritoryMapVal, true);
m_ScriptInterface->CallFunctionVoid(m_SharedAIObj, "init", state);
ScriptFunction::CallVoid(rq, m_SharedAIObj, "init", state);
for (size_t i = 0; i < m_Players.size(); ++i)
{
@ -792,7 +795,7 @@ private:
if (m_HasSharedComponent)
{
PROFILE3("AI run shared component");
m_ScriptInterface->CallFunctionVoid(m_SharedAIObj, "onUpdate", state);
ScriptFunction::CallVoid(rq, m_SharedAIObj, "onUpdate", state);
}
for (size_t i = 0; i < m_Players.size(); ++i)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -23,6 +23,7 @@
#include "ps/CLogger.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "scriptinterface/FunctionWrapper.h"
#include "simulation2/system/TurnManager.h"
class CCmpCommandQueue : public ICmpCommandQueue
@ -109,14 +110,14 @@ public:
for (size_t i = 0; i < localCommands.size(); ++i)
{
bool ok = scriptInterface.CallFunctionVoid(global, "ProcessCommand", localCommands[i].player, localCommands[i].data);
bool ok = ScriptFunction::CallVoid(rq, global, "ProcessCommand", localCommands[i].player, localCommands[i].data);
if (!ok)
LOGERROR("Failed to call ProcessCommand() global script function");
}
for (size_t i = 0; i < commands.size(); ++i)
{
bool ok = scriptInterface.CallFunctionVoid(global, "ProcessCommand", commands[i].player, commands[i].data);
bool ok = ScriptFunction::CallVoid(rq, global, "ProcessCommand", commands[i].player, commands[i].data);
if (!ok)
LOGERROR("Failed to call ProcessCommand() global script function");
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -19,6 +19,7 @@
#include "ScriptComponent.h"
#include "scriptinterface/FunctionWrapper.h"
#include "simulation2/serialization/ISerializer.h"
#include "simulation2/serialization/IDeserializer.h"
@ -29,14 +30,16 @@ CComponentTypeScript::CComponentTypeScript(const ScriptInterface& scriptInterfac
void CComponentTypeScript::Init(const CParamNode& paramNode, entity_id_t ent)
{
ScriptRequest rq(m_ScriptInterface);
m_ScriptInterface.SetProperty(m_Instance, "entity", (int)ent, true, false);
m_ScriptInterface.SetProperty(m_Instance, "template", paramNode, true, false);
m_ScriptInterface.CallFunctionVoid(m_Instance, "Init");
ScriptFunction::CallVoid(rq, m_Instance, "Init");
}
void CComponentTypeScript::Deinit()
{
m_ScriptInterface.CallFunctionVoid(m_Instance, "Deinit");
ScriptRequest rq(m_ScriptInterface);
ScriptFunction::CallVoid(rq, m_Instance, "Deinit");
}
void CComponentTypeScript::HandleMessage(const CMessage& msg, bool global)
@ -47,7 +50,7 @@ void CComponentTypeScript::HandleMessage(const CMessage& msg, bool global)
JS::RootedValue msgVal(rq.cx, msg.ToJSValCached(m_ScriptInterface));
if (!m_ScriptInterface.CallFunctionVoid(m_Instance, name, msgVal))
if (!ScriptFunction::CallVoid(rq, m_Instance, name, msgVal))
LOGERROR("Script message handler %s failed", name);
}

View File

@ -18,6 +18,7 @@
#ifndef INCLUDED_SCRIPTCOMPONENT
#define INCLUDED_SCRIPTCOMPONENT
#include "scriptinterface/FunctionWrapper.h"
#include "simulation2/system/Component.h"
#include "ps/CLogger.h"
@ -41,7 +42,8 @@ public:
R Call(const char* funcname, const Ts&... params) const
{
R ret;
if (m_ScriptInterface.CallFunction(m_Instance, funcname, ret, params...))
ScriptRequest rq(m_ScriptInterface);
if (ScriptFunction::Call(rq, m_Instance, funcname, ret, params...))
return ret;
LOGERROR("Error calling component script function %s", funcname);
return R();
@ -51,14 +53,16 @@ public:
template<typename R, typename... Ts>
void CallRef(const char* funcname, R ret, const Ts&... params) const
{
if (!m_ScriptInterface.CallFunction(m_Instance, funcname, ret, params...))
ScriptRequest rq(m_ScriptInterface);
if (!ScriptFunction::Call(rq, m_Instance, funcname, ret, params...))
LOGERROR("Error calling component script function %s", funcname);
}
template<typename... Ts>
void CallVoid(const char* funcname, const Ts&... params) const
{
if (!m_ScriptInterface.CallFunctionVoid(m_Instance, funcname, params...))
ScriptRequest rq(m_ScriptInterface);
if (!ScriptFunction::CallVoid(rq, m_Instance, funcname, params...))
LOGERROR("Error calling component script function %s", funcname);
}

View File

@ -22,6 +22,7 @@
#include "lib/alignment.h"
#include "ps/CLogger.h"
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/ScriptExtraHeaders.h"
#include "SerializedScriptTypes.h"
@ -246,7 +247,7 @@ void CBinarySerializerScriptImpl::HandleScriptVal(JS::HandleValue val)
if (!protoInfo.hasNullSerialize)
{
JS::RootedValue data(rq.cx);
if (!m_ScriptInterface.CallFunction(val, "Serialize", &data))
if (!ScriptFunction::Call(rq, val, "Serialize", &data))
throw PSERROR_Serialize_ScriptError("Prototype Serialize function failed");
m_Serializer.ScriptVal("data", &data);
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -19,6 +19,7 @@
#include "DebugSerializer.h"
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ScriptInterface.h"
#include "lib/secure_crt.h"
@ -156,7 +157,7 @@ void CDebugSerializer::PutScriptVal(const char* name, JS::MutableHandleValue val
{
// If the value has a Serialize property, pretty-parse that instead.
// (this gives more accurate OOS reports).
m_ScriptInterface.CallFunction(value, "Serialize", &serialize);
ScriptFunction::Call(rq, value, "Serialize", &serialize);
std::string serialized_source = m_ScriptInterface.ToString(&serialize, true);
m_Stream << INDENT << name << ": " << serialized_source << "\n";
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Wildfire Games.
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -22,6 +22,7 @@
#include "lib/byte_order.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/ScriptExtraHeaders.h" // For typed arrays and ArrayBuffer
#include "simulation2/serialization/ISerializer.h"
@ -180,7 +181,7 @@ JS::Value CStdDeserializer::ReadScriptVal(const char* UNUSED(name), JS::HandleOb
ScriptVal("data", &data);
JS::RootedValue objVal(rq.cx, JS::ObjectValue(*obj));
m_ScriptInterface.CallFunctionVoid(objVal, "Deserialize", data);
ScriptFunction::CallVoid(rq, objVal, "Deserialize", data);
return JS::ObjectValue(*obj);
}

View File

@ -21,6 +21,7 @@
#include "simulation2/serialization/HashSerializer.h"
#include "simulation2/serialization/StdSerializer.h"
#include "simulation2/serialization/StdDeserializer.h"
#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ScriptContext.h"
#include "scriptinterface/ScriptInterface.h"
@ -333,7 +334,7 @@ public:
deserialize2.ScriptVal("script2", &newobj);
std::string source;
TSM_ASSERT(msg, script.CallFunction(newobj, "toSource", source));
TSM_ASSERT(msg, ScriptFunction::Call(rq, newobj, "toSource", source));
TS_ASSERT_STR_EQUALS(source, expected);
}
@ -867,7 +868,7 @@ public:
if (i == 0)
{
std::string source;
TS_ASSERT(script.CallFunction(newobj, "toSource", source));
TS_ASSERT(ScriptFunction::Call(rq, newobj, "toSource", source));
std::cout << source << "\n";
}
}