Changeset View
Changeset View
Standalone View
Standalone View
source/scriptinterface/FunctionWrapper.h
/* Copyright (C) 2021 Wildfire Games. | /* Copyright (C) 2021 Wildfire Games. | ||||
Lint: Inaccurate Copyright Year: Inaccurate Copyright Year | |||||
* This file is part of 0 A.D. | * This file is part of 0 A.D. | ||||
* | * | ||||
* 0 A.D. is free software: you can redistribute it and/or modify | * 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 | * it under the terms of the GNU General Public License as published by | ||||
* the Free Software Foundation, either version 2 of the License, or | * the Free Software Foundation, either version 2 of the License, or | ||||
* (at your option) any later version. | * (at your option) any later version. | ||||
* | * | ||||
* 0 A.D. is distributed in the hope that it will be useful, | * 0 A.D. is distributed in the hope that it will be useful, | ||||
▲ Show 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | private: | ||||
using type_transform = std::conditional_t< | using type_transform = std::conditional_t< | ||||
std::is_same_v<const ScriptRequest&, T> || std::is_same_v<const ScriptInterface&, T>, | std::is_same_v<const ScriptRequest&, T> || std::is_same_v<const ScriptInterface&, T>, | ||||
T, | T, | ||||
std::remove_const_t<typename std::remove_reference_t<T>> | std::remove_const_t<typename std::remove_reference_t<T>> | ||||
>; | >; | ||||
/** | /** | ||||
* Convenient struct to get info on a [class] [const] function pointer. | * Convenient struct to get info on a [class] [const] function pointer. | ||||
* TODO VS19: I ran into a really weird bug with an auto specialisation on this taking function pointers. | * Prefer the args_info variant which avoids decltype. | ||||
* It'd be good to add it back once we upgrade. | |||||
*/ | */ | ||||
template <class T> struct args_info; | template <class T> struct args_info_t; | ||||
template <auto T> struct args_info : public args_info_t<decltype(T)> {}; | |||||
template<typename R, typename ...Types> | template<typename R, typename ...Types> | ||||
struct args_info<R(*)(Types ...)> | struct args_info_t<R(*)(Types ...)> | ||||
{ | { | ||||
static constexpr const size_t nb_args = sizeof...(Types); | static constexpr const size_t nb_args = sizeof...(Types); | ||||
using return_type = R; | using return_type = R; | ||||
using object_type = void; | using object_type = void; | ||||
using arg_types = std::tuple<type_transform<Types>...>; | using arg_types = std::tuple<type_transform<Types>...>; | ||||
}; | }; | ||||
template<typename C, typename R, typename ...Types> | template<typename C, typename R, typename ...Types> | ||||
struct args_info<R(C::*)(Types ...)> : public args_info<R(*)(Types ...)> { using object_type = C; }; | struct args_info_t<R(C::*)(Types ...)> : public args_info_t<R(*)(Types ...)> { using object_type = C; }; | ||||
template<typename C, typename R, typename ...Types> | template<typename C, typename R, typename ...Types> | ||||
struct args_info<R(C::*)(Types ...) const> : public args_info<R(C::*)(Types ...)> {}; | struct args_info_t<R(C::*)(Types ...) const> : public args_info_t<R(C::*)(Types ...)> {}; | ||||
/////////////////////////////////////////////////////////////////////////// | /////////////////////////////////////////////////////////////////////////// | ||||
/////////////////////////////////////////////////////////////////////////// | /////////////////////////////////////////////////////////////////////////// | ||||
/** | /** | ||||
* DoConvertFromJS takes a type, a JS argument, and converts. | * DoConvertFromJS takes a type, a JS argument, and converts. | ||||
* The type T must be default constructible (except for HandleValue, which is handled specially). | * The type T must be default constructible (except for HandleValue, which is handled specially). | ||||
* (possible) TODO: this could probably be changed if FromJSVal had a different signature. | * (possible) TODO: this could probably be changed if FromJSVal had a different signature. | ||||
▲ Show 20 Lines • Show All 92 Lines • ▼ Show 20 Lines | private: | ||||
/////////////////////////////////////////////////////////////////////////// | /////////////////////////////////////////////////////////////////////////// | ||||
/////////////////////////////////////////////////////////////////////////// | /////////////////////////////////////////////////////////////////////////// | ||||
/** | /** | ||||
* Wrap std::apply for the case where we have an object method or a regular function. | * Wrap std::apply for the case where we have an object method or a regular function. | ||||
*/ | */ | ||||
template <auto callable, typename T, typename tuple> | template <auto callable, typename T, typename tuple> | ||||
static typename args_info<decltype(callable)>::return_type call(T* object, tuple& args) | static typename args_info<callable>::return_type call(T* object, tuple& args) | ||||
{ | { | ||||
if constexpr(std::is_same_v<T, void>) | if constexpr(std::is_same_v<T, void>) | ||||
{ | { | ||||
// GCC (at least < 9) & VS17 prints warnings if arguments are not used in some constexpr branch. | // GCC (at least < 9) & VS17 prints warnings if arguments are not used in some constexpr branch. | ||||
UNUSED2(object); | UNUSED2(object); | ||||
return std::apply(callable, args); | return std::apply(callable, args); | ||||
} | } | ||||
else | else | ||||
▲ Show 20 Lines • Show All 66 Lines • ▼ Show 20 Lines | private: | ||||
} | } | ||||
/////////////////////////////////////////////////////////////////////////// | /////////////////////////////////////////////////////////////////////////// | ||||
/////////////////////////////////////////////////////////////////////////// | /////////////////////////////////////////////////////////////////////////// | ||||
public: | public: | ||||
template <typename T> | template <typename T> | ||||
using ObjectGetter = T*(*)(const ScriptRequest&, JS::CallArgs&); | using ObjectGetter = T*(*)(const ScriptRequest&, JS::CallArgs&); | ||||
// TODO: the fact that this takes class and not auto is to work around an odd VS17 bug. | template <auto callable> | ||||
// It can be removed with VS19. | using GetterFor = ObjectGetter<typename args_info<callable>::object_type>; | ||||
template <class callableType> | |||||
using GetterFor = ObjectGetter<typename args_info<callableType>::object_type>; | |||||
/** | /** | ||||
* The meat of this file. This wraps a C++ function into a JSNative, | * The meat of this file. This wraps a C++ function into a JSNative, | ||||
* so that it can be called from JS and manipulated in Spidermonkey. | * so that it can be called from JS and manipulated in Spidermonkey. | ||||
* Most C++ functions can be directly wrapped, so long as their arguments are | * Most C++ functions can be directly wrapped, so long as their arguments are | ||||
* convertible from JS::Value and their return value is convertible to JS::Value (or void) | * convertible from JS::Value and their return value is convertible to JS::Value (or void) | ||||
* The C++ function may optionally take const ScriptRequest& or ScriptInterface& as its first argument. | * The C++ function may optionally take const ScriptRequest& or ScriptInterface& as its first argument. | ||||
* The function may be an object method, in which case you need to pass an appropriate getter | * The function may be an object method, in which case you need to pass an appropriate getter | ||||
* | * | ||||
* Optimisation note: the ScriptRequest object is created even without arguments, | * Optimisation note: the ScriptRequest object is created even without arguments, | ||||
* as it's necessary for IsExceptionPending. | * as it's necessary for IsExceptionPending. | ||||
* | * | ||||
* @param thisGetter to get the object, if necessary. | * @param thisGetter to get the object, if necessary. | ||||
*/ | */ | ||||
template <auto callable, GetterFor<decltype(callable)> thisGetter = nullptr> | template <auto callable, GetterFor<callable> thisGetter = nullptr> | ||||
static bool ToJSNative(JSContext* cx, unsigned argc, JS::Value* vp) | static bool ToJSNative(JSContext* cx, unsigned argc, JS::Value* vp) | ||||
{ | { | ||||
using ObjType = typename args_info<decltype(callable)>::object_type; | using ObjType = typename args_info<callable>::object_type; | ||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | ||||
ScriptRequest rq(cx); | ScriptRequest rq(cx); | ||||
// If the callable is an object method, we must specify how to fetch the object. | // If the callable is an object method, we must specify how to fetch the object. | ||||
static_assert(std::is_same_v<typename args_info<decltype(callable)>::object_type, void> || thisGetter != nullptr, | static_assert(std::is_same_v<typename args_info<callable>::object_type, void> || thisGetter != nullptr, | ||||
"ScriptFunction::Register - No getter specified for object method"); | "ScriptFunction::Register - No getter specified for object method"); | ||||
// GCC 7 triggers spurious warnings | // GCC 7 triggers spurious warnings | ||||
#ifdef __GNUC__ | #ifdef __GNUC__ | ||||
#pragma GCC diagnostic push | #pragma GCC diagnostic push | ||||
#pragma GCC diagnostic ignored "-Waddress" | #pragma GCC diagnostic ignored "-Waddress" | ||||
#endif | #endif | ||||
ObjType* obj = nullptr; | ObjType* obj = nullptr; | ||||
if constexpr (thisGetter != nullptr) | if constexpr (thisGetter != nullptr) | ||||
{ | { | ||||
obj = thisGetter(rq, args); | obj = thisGetter(rq, args); | ||||
if (!obj) | if (!obj) | ||||
return false; | return false; | ||||
} | } | ||||
#ifdef __GNUC__ | #ifdef __GNUC__ | ||||
#pragma GCC diagnostic pop | #pragma GCC diagnostic pop | ||||
#endif | #endif | ||||
bool went_ok = true; | bool went_ok = true; | ||||
typename args_info<decltype(callable)>::arg_types outs = ConvertFromJS(rq, args, went_ok, static_cast<typename args_info<decltype(callable)>::arg_types*>(nullptr)); | typename args_info<callable>::arg_types outs = ConvertFromJS(rq, args, went_ok, static_cast<typename args_info<callable>::arg_types*>(nullptr)); | ||||
if (!went_ok) | if (!went_ok) | ||||
return false; | return false; | ||||
/** | /** | ||||
* TODO: error handling isn't standard, and since this can call any C++ function, | * TODO: error handling isn't standard, and since this can call any C++ function, | ||||
* there's no simple obvious way to deal with it. | * there's no simple obvious way to deal with it. | ||||
* For now we check for pending JS exceptions, but it would probably be nicer | * For now we check for pending JS exceptions, but it would probably be nicer | ||||
* to standardise on something, or perhaps provide an "errorHandler" here. | * to standardise on something, or perhaps provide an "errorHandler" here. | ||||
*/ | */ | ||||
if constexpr (std::is_same_v<void, typename args_info<decltype(callable)>::return_type>) | if constexpr (std::is_same_v<void, typename args_info<callable>::return_type>) | ||||
call<callable>(obj, outs); | call<callable>(obj, outs); | ||||
else if constexpr (std::is_same_v<JS::Value, typename args_info<decltype(callable)>::return_type>) | else if constexpr (std::is_same_v<JS::Value, typename args_info<callable>::return_type>) | ||||
args.rval().set(call<callable>(obj, outs)); | args.rval().set(call<callable>(obj, outs)); | ||||
else | else | ||||
Script::ToJSVal(rq, args.rval(), call<callable>(obj, outs)); | Script::ToJSVal(rq, args.rval(), call<callable>(obj, outs)); | ||||
return !ScriptException::IsPending(rq); | return !ScriptException::IsPending(rq); | ||||
} | } | ||||
/** | /** | ||||
Show All 22 Lines | #endif | ||||
static bool CallVoid(const ScriptRequest& rq, JS::HandleValue val, const char* name, const Args&... 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 Call(rq, val, name, IgnoreResult, std::forward<const Args>(args)...); | ||||
} | } | ||||
/** | /** | ||||
* Return a function spec from a C++ function. | * Return a function spec from a C++ function. | ||||
*/ | */ | ||||
template <auto callable, GetterFor<decltype(callable)> thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT> | template <auto callable, GetterFor<callable> thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT> | ||||
static JSFunctionSpec Wrap(const char* name) | static JSFunctionSpec Wrap(const char* name) | ||||
{ | { | ||||
return JS_FN(name, (&ToJSNative<callable, thisGetter>), args_info<decltype(callable)>::nb_args, flags); | return JS_FN(name, (&ToJSNative<callable, thisGetter>), args_info<callable>::nb_args, flags); | ||||
} | } | ||||
/** | /** | ||||
* Return a JSFunction from a C++ function. | * Return a JSFunction from a C++ function. | ||||
*/ | */ | ||||
template <auto callable, GetterFor<decltype(callable)> thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT> | template <auto callable, GetterFor<callable> thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT> | ||||
static JSFunction* Create(const ScriptRequest& rq, const char* name) | static JSFunction* Create(const ScriptRequest& rq, const char* name) | ||||
{ | { | ||||
return JS_NewFunction(rq.cx, &ToJSNative<callable, thisGetter>, args_info<decltype(callable)>::nb_args, flags, name); | return JS_NewFunction(rq.cx, &ToJSNative<callable, thisGetter>, args_info<callable>::nb_args, flags, name); | ||||
} | } | ||||
/** | /** | ||||
* Register a function on the native scope (usually 'Engine'). | * Register a function on the native scope (usually 'Engine'). | ||||
*/ | */ | ||||
template <auto callable, GetterFor<decltype(callable)> thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT> | template <auto callable, GetterFor<callable> thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT> | ||||
static void Register(const ScriptRequest& rq, const char* name) | static void Register(const ScriptRequest& rq, const char* name) | ||||
{ | { | ||||
JS_DefineFunction(rq.cx, rq.nativeScope, name, &ToJSNative<callable, thisGetter>, args_info<decltype(callable)>::nb_args, flags); | JS_DefineFunction(rq.cx, rq.nativeScope, name, &ToJSNative<callable, thisGetter>, args_info<callable>::nb_args, flags); | ||||
} | } | ||||
/** | /** | ||||
* Register a function on @param scope. | * Register a function on @param scope. | ||||
* Prefer the version taking ScriptRequest unless you have a good reason not to. | * Prefer the version taking ScriptRequest unless you have a good reason not to. | ||||
* @see Register | * @see Register | ||||
*/ | */ | ||||
template <auto callable, GetterFor<decltype(callable)> thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT> | template <auto callable, GetterFor<callable> thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT> | ||||
static void Register(JSContext* cx, JS::HandleObject scope, const char* name) | static void Register(JSContext* cx, JS::HandleObject scope, const char* name) | ||||
{ | { | ||||
JS_DefineFunction(cx, scope, name, &ToJSNative<callable, thisGetter>, args_info<decltype(callable)>::nb_args, flags); | JS_DefineFunction(cx, scope, name, &ToJSNative<callable, thisGetter>, args_info<callable>::nb_args, flags); | ||||
} | } | ||||
}; | }; | ||||
#endif // INCLUDED_FUNCTIONWRAPPER | #endif // INCLUDED_FUNCTIONWRAPPER |
Wildfire Games · Phabricator
Inaccurate Copyright Year