Changeset View
Changeset View
Standalone View
Standalone View
source/scriptinterface/FunctionWrapper.h
- This file was added.
/* Copyright (C) 2020 Wildfire Games. | |||||
* This file is part of 0 A.D. | |||||
Stan: year. | |||||
* | |||||
* 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/>. | |||||
*/ | |||||
#ifndef INCLUDED_FUNCTIONWRAPPER | |||||
#define INCLUDED_FUNCTIONWRAPPER | |||||
/** | |||||
* This file introduces templates to conveniently wrap C++ functions in JSNative functions, | |||||
* and to conveniently call JS functions from C++ code. | |||||
Not Done Inline ActionsPretty sure you are missing some std includes? Stan: Pretty sure you are missing some std includes? | |||||
* This _is_ rather template heavy, so compilation times beware. | |||||
* The C++ code can have arbitrary arguments and arbitrary return types, so long | |||||
* as they can be converted to/from JS using ScriptInterface::ToJSVal (FromJSVal respectively). | |||||
*/ | |||||
#include "ScriptInterface.h" | |||||
// Misconfigured template arguments might lead to code calling this | |||||
// (which obviously won't work). | |||||
// I can't use a static_assert as that would auto-fire, so don't provide | |||||
// the implementation and linking will fail. | |||||
Not Done Inline ActionsInverted. Stan: Inverted. | |||||
Done Inline ActionsIt's not, because I need the template to be defined above where they are used (and the logic of the code builds up progressively), so I don't have a choice. wraitii: It's not, because I need the template to be defined above where they are used (and the logic of… | |||||
template<> inline void* ScriptInterface::getPrivate<void>(JSContext* cx, JS::CallArgs&); | |||||
/** | |||||
* Some types can't be default-constructed, and thus can't be used in the magic-template below. | |||||
* This in particular concerns HandleValue, which is rather annoying. | |||||
* To work around that, we replace HandleValue with a "passthrough" type, and the calling code | |||||
* will simply pass the CallArgs value directly. | |||||
*/ | |||||
struct passthrough_t{ | |||||
passthrough_t() : t(std::unique_ptr<JS::HandleValue>(new JS::HandleValue(JS::NullHandleValue))) {}; | |||||
std::unique_ptr<JS::HandleValue> t; | |||||
}; | |||||
template<typename T> | |||||
using is_passthrough = typename std::is_same<T, passthrough_t>::value; | |||||
template<> inline bool ScriptInterface::FromJSVal<passthrough_t>(JSContext*, JS::HandleValue val, passthrough_t& o) { | |||||
o.t = std::unique_ptr<JS::HandleValue>(new JS::HandleValue(val)); | |||||
return true; | |||||
}; | |||||
/** | |||||
* Some functions return JS::Value. One could SFINAE this in call(), but this works too. | |||||
*/ | |||||
template<> | |||||
inline void ScriptInterface::ToJSVal<JS::Value>(JSContext*, JS::MutableHandleValue ret, const JS::Value& val) | |||||
{ | |||||
ret.set(val); | |||||
} | |||||
namespace ScriptWrapperHelper { | |||||
/** | |||||
* Convenient struct to get info on a [const] function pointer. | |||||
*/ | |||||
template<typename ptr> | |||||
struct args_info; | |||||
template<typename ...Types> | |||||
using _args_info_t = std::tuple<typename | |||||
// We can't construct const-references, so just construct plain objects. | |||||
std::remove_const<typename std::remove_reference< | |||||
typename std::conditional<std::is_same<JS::HandleValue, Types>::value, passthrough_t, Types>::type | |||||
>::type>::type...>; | |||||
// TODO: would be nice to find a way around the duplication here. | |||||
Not Done Inline ActionsHow much work? in what direction? Stan: How much work? in what direction? | |||||
Done Inline ActionsBasically need to change the signature of FromJSVal. I'll clarify. wraitii: Basically need to change the signature of FromJSVal. I'll clarify. | |||||
template<typename C, typename R, typename ...Types> | |||||
struct args_info<R(C::*)(Types ...)> | |||||
{ | |||||
static const size_t nb_args = sizeof...(Types); | |||||
using return_type = R; | |||||
using object_type = C; | |||||
using convertible_args = _args_info_t<Types...>; | |||||
}; | |||||
template<typename C, typename R, typename ...Types> | |||||
struct args_info<R(C::*)(Types ...) const> | |||||
{ | |||||
static const size_t nb_args = sizeof...(Types); | |||||
using return_type = R; | |||||
using object_type = C; | |||||
using convertible_args = _args_info_t<Types...>; | |||||
}; | |||||
template<typename R, typename ...Types> | |||||
struct args_info<R(*)(Types ...)> | |||||
{ | |||||
static const size_t nb_args = sizeof...(Types); | |||||
using return_type = R; | |||||
using object_type = void; | |||||
using convertible_args = _args_info_t<Types...>; | |||||
}; | |||||
/** | |||||
* Based on https://stackoverflow.com/a/32223343 | |||||
* make_index_sequence is not defined in C++11... Only C++14. | |||||
* TODO C++14: remove this. | |||||
*/ | |||||
template <size_t... Ints> | |||||
struct index_sequence | |||||
{ | |||||
using type = index_sequence; | |||||
using value_type = size_t; | |||||
static constexpr std::size_t size() noexcept { return sizeof...(Ints); } | |||||
}; | |||||
template <class Sequence1, class Sequence2> | |||||
struct _merge_and_renumber; | |||||
template <size_t... I1, size_t... I2> | |||||
struct _merge_and_renumber<index_sequence<I1...>, index_sequence<I2...>> | |||||
: index_sequence<I1..., (sizeof...(I1)+I2)...> { }; | |||||
template <size_t N> | |||||
struct make_index_sequence | |||||
: _merge_and_renumber<typename make_index_sequence<N/2>::type, | |||||
typename make_index_sequence<N - N/2>::type> { }; | |||||
template<> struct make_index_sequence<0> : index_sequence<> { }; | |||||
template<> struct make_index_sequence<1> : index_sequence<0> { }; | |||||
/** | |||||
* This helper is a recursive template call that converts the N-1th argument | |||||
* of the function from a JS value to its proper C++ type. | |||||
*/ | |||||
template<int index_tuple, int index_vals, int N, typename tuple> | |||||
struct convertFromJS | |||||
{ | |||||
bool operator()(ScriptInterface& interface, JSContext* cx, JS::CallArgs& val, tuple& outs) | |||||
{ | |||||
if (index_vals >= val.length()) | |||||
{ | |||||
std::get<index_tuple>(outs) = typename std::tuple_element<index_tuple, tuple>::type{}; | |||||
return true; | |||||
} | |||||
if (!interface.FromJSVal(cx, val[index_vals], std::get<index_tuple>(outs))) | |||||
return false; | |||||
return convertFromJS<index_tuple+1, index_vals+1, N, tuple>()(interface, cx, val, outs); | |||||
} | |||||
}; | |||||
// Specialization for the base case "no arguments" -> return True. | |||||
template<int index_vals, int N, typename tuple> | |||||
struct convertFromJS<N, index_vals, N, tuple> { bool operator()(ScriptInterface&, JSContext*, JS::CallArgs&, tuple&) { return true; } }; | |||||
// Wrapper around convertFromJS to handle the optional CxPrivate* first argument. | |||||
template<size_t N, typename ...Types> | |||||
static bool convertFromJS2(ScriptInterface& interface, JSContext* cx, JS::CallArgs& val, std::tuple<ScriptInterface::CxPrivate*, Types...>& outs) | |||||
{ | |||||
std::get<0>(outs) = ScriptInterface::GetScriptInterfaceAndCBData(cx); | |||||
return convertFromJS<1, 0, N, std::tuple<ScriptInterface::CxPrivate*, Types...>>()(interface, cx, val, outs); | |||||
} | |||||
template<size_t N, typename ...Types> | |||||
static bool convertFromJS2(ScriptInterface& interface, JSContext* cx, JS::CallArgs& val, std::tuple<Types...>& outs) | |||||
{ | |||||
return convertFromJS<0, 0, N, std::tuple<Types...>>()(interface, cx, val, outs); | |||||
} | |||||
template<typename T> | |||||
static auto pickVal(T arg) -> typename std::enable_if<!std::is_same<T, passthrough_t>::value, T>::type { return arg; } | |||||
template<typename T> | |||||
static auto pickVal(T arg) -> typename std::enable_if< std::is_same<T, passthrough_t>::value, JS::HandleValue>::type { return *arg.t.get(); } | |||||
/** | |||||
* These four templates take a function pointer, its arguments, call it, | |||||
* and set the return value of the CallArgs to whatever it returned, if anything. | |||||
* It's tag-dispatched for the "returns_void" and the regular return case. | |||||
*/ | |||||
template <typename funcPtr, funcPtr callable, typename T, size_t... Is, typename... types> | |||||
static void call(T* object, ScriptInterface* scriptInterface, JS::CallArgs& callArgs, std::tuple<types...>& args, std::false_type, index_sequence<Is...>) | |||||
{ | |||||
// This is perfectly readable, what are you talking about. | |||||
auto ret = ((*object).* callable)(pickVal(std::move(std::get<Is>(args)))...); | |||||
scriptInterface->ToJSVal(scriptInterface->GetContext(), callArgs.rval(), ret); | |||||
} | |||||
template <typename funcPtr, funcPtr callable, typename T, size_t... Is, typename... types> | |||||
static void call(T* object, ScriptInterface* UNUSED(scriptInterface), JS::CallArgs&, std::tuple<types...>& args, std::true_type, index_sequence<Is...>) | |||||
{ | |||||
// Void return specialization, just call the function. | |||||
((*object).* callable)(pickVal(std::move(std::get<Is>(args)))...); | |||||
} | |||||
// Global-scope variants. This mixes templates and overloading, which is a no-no, but it works. | |||||
// TODO C++17: if constexpr would work around this nicely. | |||||
template <typename funcPtr, funcPtr callable, typename T, size_t... Is, typename... types> | |||||
static void call(void*, ScriptInterface* scriptInterface, JS::CallArgs& callArgs, std::tuple<types...>& args, std::false_type, index_sequence<Is...>) | |||||
{ | |||||
auto ret = (*callable)(pickVal(std::move(std::get<Is>(args)))...); | |||||
scriptInterface->ToJSVal(scriptInterface->GetContext(), callArgs.rval(), ret); | |||||
} | |||||
template <typename funcPtr, funcPtr callable, typename T, size_t... Is, typename... types> | |||||
static void call(void*, ScriptInterface* UNUSED(scriptInterface), JS::CallArgs&, std::tuple<types...>& args, std::true_type, index_sequence<Is...>) | |||||
{ | |||||
(*callable)(pickVal(std::move(std::get<Is>(args)))...); | |||||
} | |||||
/** | |||||
* Call a c++ function (templateparam callable), on @param object (and if void, in the global scope). | |||||
*/ | |||||
template <typename funcPtr, funcPtr callable, typename T = void, size_t N = args_info<funcPtr>::nb_args, typename tuple = typename args_info<funcPtr>::convertible_args> | |||||
static bool JSToCppCall(JSContext* cx, JS::CallArgs& args, T* object = nullptr) | |||||
{ | |||||
ScriptInterface* scriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; | |||||
// This is where the magic happens: instantiate a tuple to store the converted JS arguments, | |||||
// then 'unpack' the tuple to call the C++ function, convert & store the return value. | |||||
tuple outs; | |||||
if (!convertFromJS2<N>(*scriptInterface, cx, args, outs)) | |||||
return false; | |||||
static_assert(std::is_same<typename args_info<funcPtr>::object_type, T>::value, | |||||
"C++ function called from JS with the wrong object type."); | |||||
// TODO: We have limited failure handling here. It's non trivial in a generic sense since we may return a value. | |||||
// We could either try-catch and throw exceptions, | |||||
// or come C++17 return an std::optional/maybe or some kind of [bool, val] structured binding. | |||||
// For now, rely on code calling a JS error, but that's meh for code that doesn't expect to be called from JS. | |||||
using returns_void = std::is_same<typename args_info<funcPtr>::return_type, void>; | |||||
call<funcPtr, callable, T>(object, scriptInterface, args, outs, returns_void{}, make_index_sequence<N>{}); | |||||
return !ScriptInterface::IsExceptionPending(cx); | |||||
} | |||||
/** | |||||
* Call a C++ Function from a JS Function. | |||||
* TODO C++14: the first two arguments can be <auto callable> | |||||
* TODO C++17: use if-constepxr instead of runtime-if for this. | |||||
* (runtime if is necessary as otherwise VS15 gets confused and refuses to compile). | |||||
*/ | |||||
template <typename funcPtr, funcPtr callable, | |||||
typename ObjType = typename args_info<funcPtr>::object_type, | |||||
ObjType*(*thisGetter)(JSContext* cx, JS::CallArgs& callArgs) = ScriptInterface::getPrivate<ObjType>> | |||||
static bool scriptMethod(JSContext* cx, unsigned argc, JS::Value* vp) | |||||
{ | |||||
JSAutoRequest rq(cx); | |||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); | |||||
// GCC complains that thisGetter is always true when the argument is not nullptr, which is what we want | |||||
// so de-activate this for now (see todo below). | |||||
#pragma GCC diagnostic push | |||||
#pragma GCC diagnostic ignored "-Waddress" | |||||
// TODO C++17: this should be if constexpr-ed out (I'm relying on optimisations for now). | |||||
ObjType* obj = thisGetter != nullptr ? thisGetter(cx, args) : nullptr; | |||||
if (thisGetter != nullptr && !obj) | |||||
return false; | |||||
#pragma GCC diagnostic pop | |||||
if (!JSToCppCall<funcPtr, callable>(cx, args, obj)) | |||||
return false; | |||||
return true; | |||||
} | |||||
} | |||||
/** | |||||
* This could be in the above namespace, but it's convenient to be able to friend these by only early-declaring the class. | |||||
*/ | |||||
class ScriptWrapper | |||||
{ | |||||
template <typename T> | |||||
using args_info = ScriptWrapperHelper::args_info<T>; | |||||
public: | |||||
// flags must be a constant expression so until C++17 template-arguments it is. | |||||
template <typename funcPtr, funcPtr callable, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT> | |||||
static JSFunctionSpec Wrap(const char* name) | |||||
{ | |||||
return JS_FN(name, (&ScriptWrapperHelper::scriptMethod<funcPtr, callable>), args_info<funcPtr>::nb_args, flags); | |||||
} | |||||
/** | |||||
* Wrap a function, and register it on the root function object (usually 'Engine') | |||||
* For global & static function, there is no 'this' computed to call the pointer on. | |||||
* For method functors, 'this' is by default computed using getPrivate. | |||||
* Otherwise, you can pass your own "this getter". | |||||
* This is SFINAE-d for the void case as VS15 does not seem to like ternaries in | |||||
* non-type template default arguments. | |||||
*/ | |||||
template <typename funcPtr, funcPtr callable, typename ObjType = typename args_info<funcPtr>::object_type, typename std::enable_if<!std::is_same<void, ObjType>::value, ObjType*(*)(JSContext* cx, JS::CallArgs& callArgs)>::type thisGetter = ScriptInterface::getPrivate<ObjType>> | |||||
static void WrapAndRegister(const ScriptInterface& itf, const char* name) | |||||
{ | |||||
itf.Register(name, &(ScriptWrapperHelper::scriptMethod<funcPtr, callable, ObjType, thisGetter>), args_info<funcPtr>::nb_args); | |||||
} | |||||
template <typename funcPtr, funcPtr callable, typename ObjType = typename args_info<funcPtr>::object_type, typename std::enable_if<std::is_same<void, ObjType>::value, ObjType*(*)(JSContext* cx, JS::CallArgs& callArgs)>::type thisGetter = nullptr> | |||||
static void WrapAndRegister(const ScriptInterface& itf, const char* name) | |||||
{ | |||||
itf.Register(name, &(ScriptWrapperHelper::scriptMethod<funcPtr, callable, ObjType, thisGetter>), args_info<funcPtr>::nb_args); | |||||
} | |||||
}; | |||||
#endif // INCLUDED_FUNCTIONWRAPPER |
Wildfire Games · Phabricator
year.