mirror of https://github.com/OpenRCT2/OpenRCT2.git
Add dukglue
This commit is contained in:
parent
43508e0402
commit
f0de6533a0
|
@ -0,0 +1,201 @@
|
|||
#pragma once
|
||||
|
||||
#include "detail_typeinfo.h"
|
||||
#include <assert.h>
|
||||
|
||||
namespace dukglue {
|
||||
namespace detail {
|
||||
|
||||
struct ProtoManager
|
||||
{
|
||||
public:
|
||||
template <typename Cls>
|
||||
static void push_prototype(duk_context* ctx)
|
||||
{
|
||||
push_prototype(ctx, TypeInfo(typeid(Cls)));
|
||||
}
|
||||
|
||||
static void push_prototype(duk_context* ctx, const TypeInfo& check_info)
|
||||
{
|
||||
if (!find_and_push_prototype(ctx, check_info)) {
|
||||
// nope, need to create our prototype object
|
||||
duk_push_object(ctx);
|
||||
|
||||
// add reference to this class' info object so we can do type checking
|
||||
// when trying to pass this object into method calls
|
||||
typedef dukglue::detail::TypeInfo TypeInfo;
|
||||
TypeInfo* info = new TypeInfo(check_info);
|
||||
|
||||
duk_push_pointer(ctx, info);
|
||||
duk_put_prop_string(ctx, -2, "\xFF" "type_info");
|
||||
|
||||
// Clean up the TypeInfo object when this prototype is destroyed.
|
||||
// We can't put a finalizer directly on this prototype, because it
|
||||
// will be run whenever the wrapper for an object of this class is
|
||||
// destroyed; instead, we make a dummy object and put the finalizer
|
||||
// on that.
|
||||
// If you're memory paranoid: this duplicates the type_info pointer
|
||||
// once per registered class. If you don't care about freeing memory
|
||||
// during shutdown, you can probably comment out this part.
|
||||
duk_push_object(ctx);
|
||||
duk_push_pointer(ctx, info);
|
||||
duk_put_prop_string(ctx, -2, "\xFF" "type_info");
|
||||
duk_push_c_function(ctx, type_info_finalizer, 1);
|
||||
duk_set_finalizer(ctx, -2);
|
||||
duk_put_prop_string(ctx, -2, "\xFF" "type_info_finalizer");
|
||||
|
||||
// register it in the stash
|
||||
register_prototype(ctx, info);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Cls>
|
||||
static void make_script_object(duk_context* ctx, Cls* obj)
|
||||
{
|
||||
assert(obj != NULL);
|
||||
|
||||
duk_push_object(ctx);
|
||||
duk_push_pointer(ctx, obj);
|
||||
duk_put_prop_string(ctx, -2, "\xFF" "obj_ptr");
|
||||
|
||||
// push the appropriate prototype
|
||||
#ifdef DUKGLUE_INFER_BASE_CLASS
|
||||
// In the "infer base class" case, we push the prototype
|
||||
// corresponding to the compile-time class if no prototype
|
||||
// for the run-time type has been defined. This allows us to
|
||||
// skip calling dukglue_set_base_class() for every derived class,
|
||||
// so long as we:
|
||||
// (1) Always use the derived class as a pointer typed as the base class
|
||||
// (2) Do not create a prototype for the derived class
|
||||
// (i.e. do not register any functions on the derived class).
|
||||
|
||||
// For big projects with hundreds of derived classes, this is preferrable
|
||||
// to registering each type's base class individually. However,
|
||||
// registering a native method on a derived class will cause the
|
||||
// base class's methods to disappear until dukglue_set_base_class() is
|
||||
// also called (because registering the native method causes a prototype
|
||||
// to be created for the run-time type). This behavior may be unexpected,
|
||||
// and for "small" projects it is reasonable to require
|
||||
// dukglue_set_base_class() to be called, so it is opt-in via an ifdef.
|
||||
|
||||
// does a prototype exist for the run-time type? if so, push it
|
||||
if (!find_and_push_prototype(ctx, TypeInfo(typeid(*obj)))) {
|
||||
// nope, find or create the prototype for the compile-time type
|
||||
// and push that
|
||||
push_prototype<Cls>(ctx);
|
||||
}
|
||||
#else
|
||||
// always use the prototype for the run-time type
|
||||
push_prototype(ctx, TypeInfo(typeid(*obj)));
|
||||
#endif
|
||||
|
||||
duk_set_prototype(ctx, -2);
|
||||
}
|
||||
|
||||
private:
|
||||
static duk_ret_t type_info_finalizer(duk_context* ctx)
|
||||
{
|
||||
duk_get_prop_string(ctx, 0, "\xFF" "type_info");
|
||||
dukglue::detail::TypeInfo* info = static_cast<dukglue::detail::TypeInfo*>(duk_require_pointer(ctx, -1));
|
||||
delete info;
|
||||
|
||||
// set pointer to NULL in case this finalizer runs again
|
||||
duk_push_pointer(ctx, NULL);
|
||||
duk_put_prop_string(ctx, 0, "\xFF" "type_info");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// puts heap_stash["dukglue_prototypes"] on the stack,
|
||||
// or creates it if it doesn't exist
|
||||
static void push_prototypes_array(duk_context* ctx)
|
||||
{
|
||||
static const char* DUKGLUE_PROTOTYPES = "dukglue_prototypes";
|
||||
|
||||
duk_push_heap_stash(ctx);
|
||||
|
||||
// does the prototype array already exist?
|
||||
if (!duk_has_prop_string(ctx, -1, DUKGLUE_PROTOTYPES)) {
|
||||
// nope, we need to create it
|
||||
duk_push_array(ctx);
|
||||
duk_put_prop_string(ctx, -2, DUKGLUE_PROTOTYPES);
|
||||
}
|
||||
|
||||
duk_get_prop_string(ctx, -1, DUKGLUE_PROTOTYPES);
|
||||
|
||||
// remove the heap stash from the stack
|
||||
duk_remove(ctx, -2);
|
||||
}
|
||||
|
||||
// Stack: ... [proto] -> ... [proto]
|
||||
static void register_prototype(duk_context* ctx, const TypeInfo* info) {
|
||||
// 1. We assume info is not in the prototype array already
|
||||
// 2. Duktape has no efficient "shift array indices" operation (at least publicly)
|
||||
// 3. This method doesn't need to be fast, it's only called during registration
|
||||
|
||||
// Work from high to low in the prototypes array, shifting as we go,
|
||||
// until we find the spot for info.
|
||||
|
||||
push_prototypes_array(ctx);
|
||||
duk_size_t i = duk_get_length(ctx, -1);
|
||||
while (i > 0) {
|
||||
duk_get_prop_index(ctx, -1, i - 1);
|
||||
|
||||
duk_get_prop_string(ctx, -1, "\xFF" "type_info");
|
||||
const TypeInfo* chk_info = static_cast<TypeInfo*>(duk_require_pointer(ctx, -1));
|
||||
duk_pop(ctx); // pop type_info
|
||||
|
||||
if (*chk_info > *info) {
|
||||
duk_put_prop_index(ctx, -2, i);
|
||||
i--;
|
||||
} else {
|
||||
duk_pop(ctx); // pop prototypes_array[i]
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//std::cout << "Registering prototype for " << typeid(Cls).name() << " at " << i << std::endl;
|
||||
|
||||
duk_dup(ctx, -2); // copy proto to top
|
||||
duk_put_prop_index(ctx, -2, i);
|
||||
duk_pop(ctx); // pop prototypes_array
|
||||
}
|
||||
|
||||
static bool find_and_push_prototype(duk_context* ctx, const TypeInfo& search_info) {
|
||||
push_prototypes_array(ctx);
|
||||
|
||||
// these are ints and not duk_size_t to deal with negative indices
|
||||
int min = 0;
|
||||
int max = duk_get_length(ctx, -1) - 1;
|
||||
|
||||
while (min <= max) {
|
||||
int mid = (max - min) / 2 + min;
|
||||
|
||||
duk_get_prop_index(ctx, -1, mid);
|
||||
|
||||
duk_get_prop_string(ctx, -1, "\xFF" "type_info");
|
||||
TypeInfo* mid_info = static_cast<TypeInfo*>(duk_require_pointer(ctx, -1));
|
||||
duk_pop(ctx); // pop type_info pointer
|
||||
|
||||
if (*mid_info == search_info) {
|
||||
// found it
|
||||
duk_remove(ctx, -2); // pop prototypes_array
|
||||
return true;
|
||||
}
|
||||
else if (*mid_info < search_info) {
|
||||
min = mid + 1;
|
||||
}
|
||||
else {
|
||||
max = mid - 1;
|
||||
}
|
||||
|
||||
duk_pop(ctx); // pop prototypes_array[mid]
|
||||
}
|
||||
|
||||
duk_pop(ctx); // pop prototypes_array
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
#pragma once
|
||||
|
||||
#include "detail_stack.h"
|
||||
#include "detail_traits.h"
|
||||
|
||||
namespace dukglue {
|
||||
namespace detail {
|
||||
|
||||
template<bool managed, typename Cls, typename... Ts>
|
||||
static duk_ret_t call_native_constructor(duk_context* ctx)
|
||||
{
|
||||
if (!duk_is_constructor_call(ctx)) {
|
||||
duk_error(ctx, DUK_RET_TYPE_ERROR, "Constructor must be called with new T().");
|
||||
return DUK_RET_TYPE_ERROR;
|
||||
}
|
||||
|
||||
// construct the new instance
|
||||
auto constructor_args = dukglue::detail::get_stack_values<Ts...>(ctx);
|
||||
Cls* obj = dukglue::detail::apply_constructor<Cls>(std::move(constructor_args));
|
||||
|
||||
duk_push_this(ctx);
|
||||
|
||||
// make the new script object keep the pointer to the new object instance
|
||||
duk_push_pointer(ctx, obj);
|
||||
duk_put_prop_string(ctx, -2, "\xFF" "obj_ptr");
|
||||
|
||||
// register it
|
||||
if (!managed)
|
||||
dukglue::detail::RefManager::register_native_object(ctx, obj);
|
||||
|
||||
duk_pop(ctx); // pop this
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename Cls>
|
||||
static duk_ret_t managed_finalizer(duk_context* ctx)
|
||||
{
|
||||
duk_get_prop_string(ctx, 0, "\xFF" "obj_ptr");
|
||||
Cls* obj = (Cls*) duk_require_pointer(ctx, -1);
|
||||
duk_pop(ctx); // pop obj_ptr
|
||||
|
||||
if (obj != NULL) {
|
||||
delete obj;
|
||||
|
||||
// for safety, set the pointer to undefined
|
||||
duk_push_undefined(ctx);
|
||||
duk_put_prop_string(ctx, 0, "\xFF" "obj_ptr");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename Cls>
|
||||
static duk_ret_t call_native_deleter(duk_context* ctx)
|
||||
{
|
||||
duk_push_this(ctx);
|
||||
duk_get_prop_string(ctx, -1, "\xFF" "obj_ptr");
|
||||
|
||||
if (!duk_is_pointer(ctx, -1)) {
|
||||
duk_error(ctx, DUK_RET_REFERENCE_ERROR, "Object has already been invalidated; cannot delete.");
|
||||
return DUK_RET_REFERENCE_ERROR;
|
||||
}
|
||||
|
||||
Cls* obj = static_cast<Cls*>(duk_require_pointer(ctx, -1));
|
||||
dukglue_invalidate_object(ctx, obj);
|
||||
delete obj;
|
||||
|
||||
duk_pop_2(ctx);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
#pragma once
|
||||
|
||||
#include "detail_stack.h"
|
||||
|
||||
namespace dukglue
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
// This struct can be used to generate a Duktape C function that
|
||||
// pulls the argument values off the stack (with type checking),
|
||||
// calls the appropriate function with them, and puts the function's
|
||||
// return value (if any) onto the stack.
|
||||
template<typename RetType, typename... Ts>
|
||||
struct FuncInfoHolder
|
||||
{
|
||||
typedef RetType(*FuncType)(Ts...);
|
||||
|
||||
template<FuncType funcToCall>
|
||||
struct FuncCompiletime
|
||||
{
|
||||
// The function to call is embedded into call_native_function at
|
||||
// compile-time through template magic.
|
||||
// Performance is so similar to run-time function calls that
|
||||
// this is not recommended due to the ugly syntax it requires.
|
||||
static duk_ret_t call_native_function(duk_context* ctx)
|
||||
{
|
||||
auto bakedArgs = dukglue::detail::get_stack_values<Ts...>(ctx);
|
||||
actually_call(ctx, bakedArgs);
|
||||
return std::is_void<RetType>::value ? 0 : 1;
|
||||
}
|
||||
|
||||
private:
|
||||
// this mess is to support functions with void return values
|
||||
|
||||
template<typename Dummy = RetType, typename... BakedTs>
|
||||
static typename std::enable_if<!std::is_void<Dummy>::value>::type actually_call(duk_context* ctx, const std::tuple<BakedTs...>& args)
|
||||
{
|
||||
// ArgStorage has some static_asserts in it that validate value types,
|
||||
// so we typedef it to force ArgStorage<RetType> to compile and run the asserts
|
||||
typedef typename dukglue::types::ArgStorage<RetType>::type ValidateReturnType;
|
||||
|
||||
RetType return_val = dukglue::detail::apply_fp(funcToCall, args);
|
||||
|
||||
using namespace dukglue::types;
|
||||
DukType<typename Bare<RetType>::type>::template push<RetType>(ctx, std::move(return_val));
|
||||
}
|
||||
|
||||
template<typename Dummy = RetType, typename... BakedTs>
|
||||
static typename std::enable_if<std::is_void<Dummy>::value>::type actually_call(duk_context* ctx, const std::tuple<BakedTs...>& args)
|
||||
{
|
||||
dukglue::detail::apply_fp(funcToCall, args);
|
||||
}
|
||||
};
|
||||
|
||||
struct FuncRuntime
|
||||
{
|
||||
// Pull the address of the function to call from the
|
||||
// Duktape function object at run time.
|
||||
static duk_ret_t call_native_function(duk_context* ctx)
|
||||
{
|
||||
duk_push_current_function(ctx);
|
||||
duk_get_prop_string(ctx, -1, "\xFF" "func_ptr");
|
||||
void* fp_void = duk_require_pointer(ctx, -1);
|
||||
if (fp_void == NULL) {
|
||||
duk_error(ctx, DUK_RET_TYPE_ERROR, "what even");
|
||||
return DUK_RET_TYPE_ERROR;
|
||||
}
|
||||
|
||||
duk_pop_2(ctx);
|
||||
|
||||
static_assert(sizeof(RetType(*)(Ts...)) == sizeof(void*), "Function pointer and data pointer are different sizes");
|
||||
RetType(*funcToCall)(Ts...) = reinterpret_cast<RetType(*)(Ts...)>(fp_void);
|
||||
|
||||
actually_call(ctx, funcToCall, dukglue::detail::get_stack_values<Ts...>(ctx));
|
||||
return std::is_void<RetType>::value ? 0 : 1;
|
||||
}
|
||||
|
||||
// this mess is to support functions with void return values
|
||||
template<typename Dummy = RetType, typename... BakedTs>
|
||||
static typename std::enable_if<!std::is_void<Dummy>::value>::type actually_call(duk_context* ctx, RetType(*funcToCall)(Ts...), const std::tuple<BakedTs...>& args)
|
||||
{
|
||||
// ArgStorage has some static_asserts in it that validate value types,
|
||||
// so we typedef it to force ArgStorage<RetType> to compile and run the asserts
|
||||
typedef typename dukglue::types::ArgStorage<RetType>::type ValidateReturnType;
|
||||
|
||||
RetType return_val = dukglue::detail::apply_fp(funcToCall, args);
|
||||
|
||||
using namespace dukglue::types;
|
||||
DukType<typename Bare<RetType>::type>::template push<RetType>(ctx, std::move(return_val));
|
||||
}
|
||||
|
||||
template<typename Dummy = RetType, typename... BakedTs>
|
||||
static typename std::enable_if<std::is_void<Dummy>::value>::type actually_call(duk_context* ctx, RetType(*funcToCall)(Ts...), const std::tuple<BakedTs...>& args)
|
||||
{
|
||||
dukglue::detail::apply_fp(funcToCall, args);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
#pragma once
|
||||
|
||||
#include "detail_stack.h"
|
||||
|
||||
namespace dukglue
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template<bool isConst, class Cls, typename RetType, typename... Ts>
|
||||
struct MethodInfo
|
||||
{
|
||||
typedef typename std::conditional<isConst, RetType(Cls::*)(Ts...) const, RetType(Cls::*)(Ts...)>::type MethodType;
|
||||
|
||||
// The size of a method pointer is not guaranteed to be the same size as a function pointer.
|
||||
// This means we can't just use duk_push_pointer(ctx, &MyClass::method) to store the method at run time.
|
||||
// To get around this, we wrap the method pointer in a MethodHolder (on the heap), and push a pointer to
|
||||
// that. The MethodHolder is cleaned up by the finalizer.
|
||||
struct MethodHolder
|
||||
{
|
||||
MethodType method;
|
||||
};
|
||||
|
||||
template<MethodType methodToCall>
|
||||
struct MethodCompiletime
|
||||
{
|
||||
static duk_ret_t call_native_method(duk_context* ctx)
|
||||
{
|
||||
// get this.obj_ptr
|
||||
duk_push_this(ctx);
|
||||
duk_get_prop_string(ctx, -1, "\xFF" "obj_ptr");
|
||||
void* obj_void = duk_require_pointer(ctx, -1);
|
||||
if (obj_void == nullptr) {
|
||||
duk_error(ctx, DUK_RET_REFERENCE_ERROR, "Native object missing.");
|
||||
return DUK_RET_REFERENCE_ERROR;
|
||||
}
|
||||
|
||||
duk_pop_2(ctx);
|
||||
|
||||
// (should always be valid unless someone is intentionally messing with this.obj_ptr...)
|
||||
Cls* obj = static_cast<Cls*>(obj_void);
|
||||
|
||||
// read arguments and call function
|
||||
auto bakedArgs = dukglue::detail::get_stack_values<Ts...>(ctx);
|
||||
actually_call(ctx, obj, bakedArgs);
|
||||
return std::is_void<RetType>::value ? 0 : 1;
|
||||
}
|
||||
|
||||
// this mess is to support functions with void return values
|
||||
template<typename Dummy = RetType, typename... BakedTs>
|
||||
static typename std::enable_if<!std::is_void<Dummy>::value>::type actually_call(duk_context* ctx, Cls* obj, const std::tuple<BakedTs...>& args)
|
||||
{
|
||||
// ArgStorage has some static_asserts in it that validate value types,
|
||||
// so we typedef it to force ArgStorage<RetType> to compile and run the asserts
|
||||
typedef typename dukglue::types::ArgStorage<RetType>::type ValidateReturnType;
|
||||
|
||||
RetType return_val = dukglue::detail::apply_method<Cls, RetType, Ts...>(methodToCall, obj, args);
|
||||
|
||||
using namespace dukglue::types;
|
||||
DukType<typename Bare<RetType>::type>::template push<RetType>(ctx, std::move(return_val));
|
||||
}
|
||||
|
||||
template<typename Dummy = RetType, typename... BakedTs>
|
||||
static typename std::enable_if<std::is_void<Dummy>::value>::type actually_call(duk_context* ctx, Cls* obj, const std::tuple<BakedTs...>& args)
|
||||
{
|
||||
dukglue::detail::apply_method(methodToCall, obj, args);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct MethodRuntime
|
||||
{
|
||||
static duk_ret_t finalize_method(duk_context* ctx)
|
||||
{
|
||||
// clean up the MethodHolder reference
|
||||
duk_get_prop_string(ctx, 0, "\xFF" "method_holder");
|
||||
|
||||
void* method_holder_void = duk_require_pointer(ctx, -1);
|
||||
MethodHolder* method_holder = static_cast<MethodHolder*>(method_holder_void);
|
||||
delete method_holder;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static duk_ret_t call_native_method(duk_context* ctx)
|
||||
{
|
||||
// get this.obj_ptr
|
||||
duk_push_this(ctx);
|
||||
duk_get_prop_string(ctx, -1, "\xFF" "obj_ptr");
|
||||
void* obj_void = duk_get_pointer(ctx, -1);
|
||||
if (obj_void == nullptr) {
|
||||
duk_error(ctx, DUK_RET_REFERENCE_ERROR, "Invalid native object for 'this'");
|
||||
return DUK_RET_REFERENCE_ERROR;
|
||||
}
|
||||
|
||||
duk_pop_2(ctx); // pop this.obj_ptr and this
|
||||
|
||||
// get current_function.method_info
|
||||
duk_push_current_function(ctx);
|
||||
duk_get_prop_string(ctx, -1, "\xFF" "method_holder");
|
||||
void* method_holder_void = duk_require_pointer(ctx, -1);
|
||||
if (method_holder_void == nullptr) {
|
||||
duk_error(ctx, DUK_RET_TYPE_ERROR, "Method pointer missing?!");
|
||||
return DUK_RET_TYPE_ERROR;
|
||||
}
|
||||
|
||||
duk_pop_2(ctx);
|
||||
|
||||
// (should always be valid unless someone is intentionally messing with this.obj_ptr...)
|
||||
Cls* obj = static_cast<Cls*>(obj_void);
|
||||
MethodHolder* method_holder = static_cast<MethodHolder*>(method_holder_void);
|
||||
|
||||
// read arguments and call method
|
||||
auto bakedArgs = dukglue::detail::get_stack_values<Ts...>(ctx);
|
||||
actually_call(ctx, method_holder->method, obj, bakedArgs);
|
||||
return std::is_void<RetType>::value ? 0 : 1;
|
||||
}
|
||||
|
||||
// this mess is to support functions with void return values
|
||||
template<typename Dummy = RetType, typename... BakedTs>
|
||||
static typename std::enable_if<!std::is_void<Dummy>::value>::type actually_call(duk_context* ctx, MethodType method, Cls* obj, const std::tuple<BakedTs...>& args)
|
||||
{
|
||||
// ArgStorage has some static_asserts in it that validate value types,
|
||||
// so we typedef it to force ArgStorage<RetType> to compile and run the asserts
|
||||
typedef typename dukglue::types::ArgStorage<RetType>::type ValidateReturnType;
|
||||
|
||||
RetType return_val = dukglue::detail::apply_method<Cls, RetType, Ts...>(method, obj, args);
|
||||
|
||||
using namespace dukglue::types;
|
||||
DukType<typename Bare<RetType>::type>::template push<RetType>(ctx, std::move(return_val));
|
||||
}
|
||||
|
||||
template<typename Dummy = RetType, typename... BakedTs>
|
||||
static typename std::enable_if<std::is_void<Dummy>::value>::type actually_call(duk_context* ctx, MethodType method, Cls* obj, const std::tuple<BakedTs...>& args)
|
||||
{
|
||||
dukglue::detail::apply_method(method, obj, args);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
template <bool isConst, typename Cls>
|
||||
struct MethodVariadicRuntime
|
||||
{
|
||||
typedef MethodInfo<isConst, Cls, duk_ret_t, duk_context*> MethodInfoVariadic;
|
||||
typedef typename MethodInfoVariadic::MethodHolder MethodHolderVariadic;
|
||||
|
||||
static duk_ret_t finalize_method(duk_context* ctx)
|
||||
{
|
||||
return MethodInfoVariadic::MethodRuntime::finalize_method(ctx);
|
||||
}
|
||||
|
||||
static duk_ret_t call_native_method(duk_context* ctx)
|
||||
{
|
||||
// get this.obj_ptr
|
||||
duk_push_this(ctx);
|
||||
duk_get_prop_string(ctx, -1, "\xFF" "obj_ptr");
|
||||
void* obj_void = duk_get_pointer(ctx, -1);
|
||||
if (obj_void == nullptr) {
|
||||
duk_error(ctx, DUK_RET_REFERENCE_ERROR, "Invalid native object for 'this'");
|
||||
return DUK_RET_REFERENCE_ERROR;
|
||||
}
|
||||
|
||||
duk_pop_2(ctx); // pop this.obj_ptr and this
|
||||
|
||||
// get current_function.method_info
|
||||
duk_push_current_function(ctx);
|
||||
duk_get_prop_string(ctx, -1, "\xFF" "method_holder");
|
||||
void* method_holder_void = duk_require_pointer(ctx, -1);
|
||||
if (method_holder_void == nullptr) {
|
||||
duk_error(ctx, DUK_RET_TYPE_ERROR, "Method pointer missing?!");
|
||||
return DUK_RET_TYPE_ERROR;
|
||||
}
|
||||
|
||||
duk_pop_2(ctx);
|
||||
|
||||
// (should always be valid unless someone is intentionally messing with this.obj_ptr...)
|
||||
Cls* obj = static_cast<Cls*>(obj_void);
|
||||
MethodHolderVariadic* method_holder = static_cast<MethodHolderVariadic*>(method_holder_void);
|
||||
|
||||
return (*obj.*method_holder->method)(ctx);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
#pragma once
|
||||
|
||||
#include "detail_types.h"
|
||||
#include "detail_typeinfo.h"
|
||||
#include "dukvalue.h"
|
||||
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
#include <memory> // for std::shared_ptr
|
||||
|
||||
namespace dukglue {
|
||||
namespace types {
|
||||
|
||||
#define DUKGLUE_SIMPLE_VALUE_TYPE(TYPE, DUK_IS_FUNC, DUK_GET_FUNC, DUK_PUSH_FUNC, PUSH_VALUE) \
|
||||
template<> \
|
||||
struct DukType<TYPE> { \
|
||||
typedef std::true_type IsValueType; \
|
||||
\
|
||||
template<typename FullT> \
|
||||
static TYPE read(duk_context* ctx, duk_idx_t arg_idx) { \
|
||||
if (DUK_IS_FUNC(ctx, arg_idx)) { \
|
||||
return static_cast<TYPE>(DUK_GET_FUNC(ctx, arg_idx)); \
|
||||
} else { \
|
||||
duk_int_t type_idx = duk_get_type(ctx, arg_idx); \
|
||||
duk_error(ctx, DUK_RET_TYPE_ERROR, "Argument %d: expected " #TYPE ", got %s", arg_idx, detail::get_type_name(type_idx)); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
template<typename FullT> \
|
||||
static void push(duk_context* ctx, TYPE value) { \
|
||||
DUK_PUSH_FUNC(ctx, PUSH_VALUE); \
|
||||
} \
|
||||
};
|
||||
|
||||
DUKGLUE_SIMPLE_VALUE_TYPE(bool, duk_is_boolean, 0 != duk_get_boolean, duk_push_boolean, value)
|
||||
|
||||
DUKGLUE_SIMPLE_VALUE_TYPE(uint8_t, duk_is_number, duk_get_uint, duk_push_uint, value)
|
||||
DUKGLUE_SIMPLE_VALUE_TYPE(uint16_t, duk_is_number, duk_get_uint, duk_push_uint, value)
|
||||
DUKGLUE_SIMPLE_VALUE_TYPE(uint32_t, duk_is_number, duk_get_uint, duk_push_uint, value)
|
||||
DUKGLUE_SIMPLE_VALUE_TYPE(uint64_t, duk_is_number, duk_get_number, duk_push_number, value) // have to cast to double
|
||||
|
||||
DUKGLUE_SIMPLE_VALUE_TYPE(int8_t, duk_is_number, duk_get_int, duk_push_int, value)
|
||||
DUKGLUE_SIMPLE_VALUE_TYPE(int16_t, duk_is_number, duk_get_int, duk_push_int, value)
|
||||
DUKGLUE_SIMPLE_VALUE_TYPE(int32_t, duk_is_number, duk_get_int, duk_push_int, value)
|
||||
DUKGLUE_SIMPLE_VALUE_TYPE(int64_t, duk_is_number, duk_get_number, duk_push_number, value) // have to cast to double
|
||||
|
||||
// signed char and unsigned char are surprisingly *both* different from char, at least in MSVC
|
||||
DUKGLUE_SIMPLE_VALUE_TYPE(char, duk_is_number, duk_get_int, duk_push_int, value)
|
||||
|
||||
DUKGLUE_SIMPLE_VALUE_TYPE(float, duk_is_number, duk_get_number, duk_push_number, value)
|
||||
DUKGLUE_SIMPLE_VALUE_TYPE(double, duk_is_number, duk_get_number, duk_push_number, value)
|
||||
|
||||
DUKGLUE_SIMPLE_VALUE_TYPE(std::string, duk_is_string, duk_get_string, duk_push_string, value.c_str())
|
||||
|
||||
// We have to do some magic for const char* to work correctly.
|
||||
// We override the "bare type" and "storage type" to both be const char*.
|
||||
// char* is a bit tricky because its "bare type" should still be const char*, to differentiate it from just char
|
||||
template<>
|
||||
struct Bare<char*> {
|
||||
typedef const char* type;
|
||||
};
|
||||
template<>
|
||||
struct Bare<const char*> {
|
||||
typedef const char* type;
|
||||
};
|
||||
|
||||
// the storage type should also be const char* - if we don't do this, it will end up as just "char"
|
||||
template<>
|
||||
struct ArgStorage<const char*> {
|
||||
typedef const char* type;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct DukType<const char*> {
|
||||
typedef std::true_type IsValueType;
|
||||
|
||||
template<typename FullT>
|
||||
static const char* read(duk_context* ctx, duk_idx_t arg_idx) {
|
||||
if (duk_is_string(ctx, arg_idx)) {
|
||||
return duk_get_string(ctx, arg_idx);
|
||||
} else {
|
||||
duk_int_t type_idx = duk_get_type(ctx, arg_idx);
|
||||
duk_error(ctx, DUK_RET_TYPE_ERROR, "Argument %d: expected string, got %s", arg_idx, detail::get_type_name(type_idx));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename FullT>
|
||||
static void push(duk_context* ctx, const char* value) {
|
||||
duk_push_string(ctx, value);
|
||||
}
|
||||
};
|
||||
|
||||
// DukValue
|
||||
template<>
|
||||
struct DukType<DukValue> {
|
||||
typedef std::true_type IsValueType;
|
||||
|
||||
template <typename FullT>
|
||||
static DukValue read(duk_context* ctx, duk_idx_t arg_idx) {
|
||||
try {
|
||||
return DukValue::copy_from_stack(ctx, arg_idx);
|
||||
} catch (DukException& e) {
|
||||
// only DukException can be thrown by DukValue::copy_from_stack
|
||||
duk_error(ctx, DUK_ERR_ERROR, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename FullT>
|
||||
static void push(duk_context* ctx, const DukValue& value) {
|
||||
if (value.context() == NULL) {
|
||||
duk_error(ctx, DUK_ERR_ERROR, "DukValue is uninitialized");
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.context() != ctx) {
|
||||
duk_error(ctx, DUK_ERR_ERROR, "DukValue comes from a different context");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
value.push();
|
||||
} catch (DukException& e) {
|
||||
// only DukException can be thrown by DukValue::copy_from_stack
|
||||
duk_error(ctx, DUK_ERR_ERROR, e.what());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// std::vector (as value)
|
||||
template<typename T>
|
||||
struct DukType< std::vector<T> > {
|
||||
typedef std::true_type IsValueType;
|
||||
|
||||
template <typename FullT>
|
||||
static std::vector<T> read(duk_context* ctx, duk_idx_t arg_idx) {
|
||||
if (!duk_is_array(ctx, arg_idx)) {
|
||||
duk_int_t type_idx = duk_get_type(ctx, arg_idx);
|
||||
duk_error(ctx, DUK_ERR_TYPE_ERROR, "Argument %d: expected array, got %s", arg_idx, detail::get_type_name(type_idx));
|
||||
}
|
||||
|
||||
duk_size_t len = duk_get_length(ctx, arg_idx);
|
||||
const duk_idx_t elem_idx = duk_get_top(ctx);
|
||||
|
||||
std::vector<T> vec;
|
||||
vec.reserve(len);
|
||||
for (duk_size_t i = 0; i < len; i++) {
|
||||
duk_get_prop_index(ctx, arg_idx, i);
|
||||
vec.push_back(DukType< typename Bare<T>::type >::template read<T>(ctx, elem_idx));
|
||||
duk_pop(ctx);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename FullT>
|
||||
static void push(duk_context* ctx, const std::vector<T>& value) {
|
||||
duk_idx_t obj_idx = duk_push_array(ctx);
|
||||
|
||||
for (size_t i = 0; i < value.size(); i++) {
|
||||
DukType< typename Bare<T>::type >::template push<T>(ctx, value[i]);
|
||||
duk_put_prop_index(ctx, obj_idx, i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// std::shared_ptr (as value)
|
||||
template<typename T>
|
||||
struct DukType< std::shared_ptr<T> > {
|
||||
typedef std::true_type IsValueType;
|
||||
|
||||
static_assert(std::is_same<typename DukType<T>::IsValueType, std::false_type>::value, "Dukglue can only use std::shared_ptr to non-value types!");
|
||||
|
||||
template <typename FullT>
|
||||
static std::shared_ptr<T> read(duk_context* ctx, duk_idx_t arg_idx) {
|
||||
if (duk_is_null(ctx, arg_idx))
|
||||
return nullptr;
|
||||
|
||||
if (!duk_is_object(ctx, arg_idx)) {
|
||||
duk_int_t type_idx = duk_get_type(ctx, arg_idx);
|
||||
duk_error(ctx, DUK_RET_TYPE_ERROR, "Argument %d: expected shared_ptr object, got ", arg_idx, detail::get_type_name(type_idx));
|
||||
}
|
||||
|
||||
duk_get_prop_string(ctx, arg_idx, "\xFF" "type_info");
|
||||
if (!duk_is_pointer(ctx, -1)) // missing type_info, must not be a native object
|
||||
duk_error(ctx, DUK_RET_TYPE_ERROR, "Argument %d: expected shared_ptr object (missing type_info)", arg_idx);
|
||||
|
||||
// make sure this object can be safely returned as a T*
|
||||
dukglue::detail::TypeInfo* info = static_cast<dukglue::detail::TypeInfo*>(duk_get_pointer(ctx, -1));
|
||||
if (!info->can_cast<T>())
|
||||
duk_error(ctx, DUK_RET_TYPE_ERROR, "Argument %d: wrong type of shared_ptr object", arg_idx);
|
||||
duk_pop(ctx); // pop type_info
|
||||
|
||||
duk_get_prop_string(ctx, arg_idx, "\xFF" "shared_ptr");
|
||||
if (!duk_is_pointer(ctx, -1))
|
||||
duk_error(ctx, DUK_RET_TYPE_ERROR, "Argument %d: not a shared_ptr object (missing shared_ptr)", arg_idx);
|
||||
void* ptr = duk_get_pointer(ctx, -1);
|
||||
duk_pop(ctx); // pop pointer to shared_ptr
|
||||
|
||||
return *((std::shared_ptr<T>*) ptr);
|
||||
}
|
||||
|
||||
static duk_ret_t shared_ptr_finalizer(duk_context* ctx)
|
||||
{
|
||||
duk_get_prop_string(ctx, 0, "\xFF" "shared_ptr");
|
||||
std::shared_ptr<T>* ptr = (std::shared_ptr<T>*) duk_require_pointer(ctx, -1);
|
||||
duk_pop(ctx); // pop shared_ptr ptr
|
||||
|
||||
if (ptr != NULL) {
|
||||
delete ptr;
|
||||
|
||||
// for safety, set the pointer to undefined
|
||||
// (finalizers can run multiple times)
|
||||
duk_push_undefined(ctx);
|
||||
duk_put_prop_string(ctx, 0, "\xFF" "shared_ptr");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename FullT>
|
||||
static void push(duk_context* ctx, const std::shared_ptr<T>& value) {
|
||||
dukglue::detail::ProtoManager::make_script_object(ctx, value.get());
|
||||
|
||||
// create + set shared_ptr
|
||||
duk_push_pointer(ctx, new std::shared_ptr<T>(value));
|
||||
duk_put_prop_string(ctx, -2, "\xFF" "shared_ptr");
|
||||
|
||||
// set shared_ptr finalizer
|
||||
duk_push_c_function(ctx, &shared_ptr_finalizer, 1);
|
||||
duk_set_finalizer(ctx, -2);
|
||||
}
|
||||
};
|
||||
|
||||
// std::function
|
||||
/*template <typename RetT, typename... ArgTs>
|
||||
struct DukType< std::function<RetT(ArgTs...)> > {
|
||||
typedef std::true_type IsValueType;
|
||||
|
||||
template<typename FullT>
|
||||
static std::function<RetT(ArgTs...)> read(duk_context* ctx, duk_idx_t arg_idx) {
|
||||
DukValue callable = DukValue::copy_from_stack(ctx, -1, DUK_TYPE_MASK_OBJECT);
|
||||
return [ctx, callable] (ArgTs... args) -> RetT {
|
||||
dukglue_call<RetT>(ctx, callable, args...);
|
||||
};
|
||||
}
|
||||
|
||||
template<typename FullT>
|
||||
static void push(duk_context* ctx, std::function<RetT(ArgTs...)> value) {
|
||||
static_assert(false, "Pushing an std::function has not been implemented yet. Sorry!");
|
||||
}
|
||||
};*/
|
||||
}
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
#pragma once
|
||||
|
||||
#include <duktape.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace dukglue
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
// This class handles keeping a map of void* -> script object.
|
||||
// It also prevents script objects from being GC'd until someone
|
||||
// explicitly frees the underlying native object.
|
||||
|
||||
// Implemented by keeping an array of script objects in the heap stash.
|
||||
// An std::unordered_map maps pointer -> array index.
|
||||
// Thanks to std::unordered_map, lookup time is O(1) on average.
|
||||
|
||||
// Using std::unordered_map has some memory overhead (~32 bytes per object),
|
||||
// which could be removed by using a different data structure:
|
||||
|
||||
// 1. Use no data structure. Blindly scan through the reference registry,
|
||||
// checking \xFFobj_ptr on every object until you find yours.
|
||||
// Performance when returning native objects from functions when a lot
|
||||
// of native objects are registered will suffer.
|
||||
|
||||
// 2. Implement a self-balancing binary tree on top of a Duktape array
|
||||
// for the registry. Still fast - O(log(N)) - and no memory overhead.
|
||||
|
||||
// 3. A sorted list would work too, though insertion speed might be worse
|
||||
// than a binary tree.
|
||||
|
||||
struct RefManager
|
||||
{
|
||||
public:
|
||||
|
||||
// Find the script object corresponding to obj_ptr and push it.
|
||||
// Returns true if successful, false if obj_ptr has not been registered.
|
||||
// Stack: ... -> ... (if object has been registered before)
|
||||
// ... -> ... [object] (if object has not been registered)
|
||||
static bool find_and_push_native_object(duk_context* ctx, void* obj_ptr)
|
||||
{
|
||||
RefMap* ref_map = get_ref_map(ctx);
|
||||
|
||||
const auto it = ref_map->find(obj_ptr);
|
||||
|
||||
if (it == ref_map->end()) {
|
||||
return false;
|
||||
} else {
|
||||
push_ref_array(ctx);
|
||||
duk_get_prop_index(ctx, -1, it->second);
|
||||
duk_remove(ctx, -2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Takes a script object and adds it to the registry, associating
|
||||
// it with obj_ptr. unregistered_object is not modified.
|
||||
// If obj_ptr has already been registered with another object,
|
||||
// the old registry entry will be overidden.
|
||||
// Does nothing if obj_ptr is NULL.
|
||||
// Stack: ... [object] -> ... [object]
|
||||
static void register_native_object(duk_context* ctx, void* obj_ptr)
|
||||
{
|
||||
if (obj_ptr == NULL)
|
||||
return;
|
||||
|
||||
RefMap* ref_map = get_ref_map(ctx);
|
||||
|
||||
push_ref_array(ctx);
|
||||
|
||||
// find next free index
|
||||
// free indices are kept in a linked list, starting at ref_array[0]
|
||||
duk_get_prop_index(ctx, -1, 0);
|
||||
duk_uarridx_t next_free_idx = duk_get_uint(ctx, -1);
|
||||
duk_pop(ctx);
|
||||
|
||||
if (next_free_idx == 0) {
|
||||
// no free spots in the array, make a new one at arr.length
|
||||
next_free_idx = duk_get_length(ctx, -1);
|
||||
} else {
|
||||
// free spot found, need to remove it from the free list
|
||||
// ref_array[0] = ref_array[next_free_idx]
|
||||
duk_get_prop_index(ctx, -1, next_free_idx);
|
||||
duk_put_prop_index(ctx, -2, 0);
|
||||
}
|
||||
|
||||
// std::cout << "putting reference at ref_array[" << next_free_idx << "]" << std::endl;
|
||||
(*ref_map)[obj_ptr] = next_free_idx;
|
||||
|
||||
duk_dup(ctx, -2); // put object on top
|
||||
|
||||
// ... [object] [ref_array] [object]
|
||||
duk_put_prop_index(ctx, -2, next_free_idx);
|
||||
|
||||
duk_pop(ctx); // pop ref_array
|
||||
}
|
||||
|
||||
// Remove the object associated with obj_ptr from the registry
|
||||
// and invalidate the object's internal native pointer (by setting it to undefined).
|
||||
// Does nothing if obj_ptr if object was never registered or obj_ptr is NULL.
|
||||
// Does not affect the stack.
|
||||
static void find_and_invalidate_native_object(duk_context* ctx, void* obj_ptr)
|
||||
{
|
||||
if (obj_ptr == NULL)
|
||||
return;
|
||||
|
||||
RefMap* ref_map = get_ref_map(ctx);
|
||||
auto it = ref_map->find(obj_ptr);
|
||||
if (it == ref_map->end()) // was never registered
|
||||
return;
|
||||
|
||||
push_ref_array(ctx);
|
||||
duk_get_prop_index(ctx, -1, it->second);
|
||||
|
||||
// invalidate internal pointer
|
||||
duk_push_undefined(ctx);
|
||||
duk_put_prop_string(ctx, -2, "\xFF" "obj_ptr");
|
||||
duk_pop(ctx); // pop object
|
||||
|
||||
// remove from references array and add the space it was in to free list
|
||||
// (refs[0] -> tail) -> (refs[0] -> old_obj_idx -> tail)
|
||||
|
||||
// refs[old_obj_idx] = refs[0]
|
||||
duk_get_prop_index(ctx, -1, 0);
|
||||
duk_put_prop_index(ctx, -2, it->second);
|
||||
|
||||
// refs[0] = old_obj_idx
|
||||
duk_push_uint(ctx, it->second);
|
||||
duk_put_prop_index(ctx, -2, 0);
|
||||
|
||||
duk_pop(ctx); // pop ref_array
|
||||
|
||||
// also remove from map
|
||||
// std::cout << "Freeing ref_array[" << it->second << "]" << std::endl;
|
||||
ref_map->erase(it);
|
||||
}
|
||||
|
||||
private:
|
||||
typedef std::unordered_map<void*, duk_uarridx_t> RefMap;
|
||||
|
||||
static RefMap* get_ref_map(duk_context* ctx)
|
||||
{
|
||||
static const char* DUKGLUE_REF_MAP = "dukglue_ref_map";
|
||||
static const char* PTR = "ptr";
|
||||
|
||||
duk_push_heap_stash(ctx);
|
||||
|
||||
if (!duk_has_prop_string(ctx, -1, DUKGLUE_REF_MAP)) {
|
||||
// doesn't exist yet, need to create it
|
||||
duk_push_object(ctx);
|
||||
|
||||
duk_push_pointer(ctx, new RefMap());
|
||||
duk_put_prop_string(ctx, -2, PTR);
|
||||
|
||||
duk_push_c_function(ctx, ref_map_finalizer, 1);
|
||||
duk_set_finalizer(ctx, -2);
|
||||
|
||||
duk_put_prop_string(ctx, -2, DUKGLUE_REF_MAP);
|
||||
}
|
||||
|
||||
duk_get_prop_string(ctx, -1, DUKGLUE_REF_MAP);
|
||||
duk_get_prop_string(ctx, -1, PTR);
|
||||
RefMap* map = static_cast<RefMap*>(duk_require_pointer(ctx, -1));
|
||||
duk_pop_3(ctx);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
static duk_ret_t ref_map_finalizer(duk_context* ctx)
|
||||
{
|
||||
duk_get_prop_string(ctx, 0, "ptr");
|
||||
RefMap* map = static_cast<RefMap*>(duk_require_pointer(ctx, -1));
|
||||
delete map;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void push_ref_array(duk_context* ctx)
|
||||
{
|
||||
static const char* DUKGLUE_REF_ARRAY = "dukglue_ref_array";
|
||||
duk_push_heap_stash(ctx);
|
||||
|
||||
if (!duk_has_prop_string(ctx, -1, DUKGLUE_REF_ARRAY)) {
|
||||
duk_push_array(ctx);
|
||||
|
||||
// ref_array[0] = 0 (initialize free list as empty)
|
||||
duk_push_int(ctx, 0);
|
||||
duk_put_prop_index(ctx, -2, 0);
|
||||
|
||||
duk_put_prop_string(ctx, -2, DUKGLUE_REF_ARRAY);
|
||||
}
|
||||
|
||||
duk_get_prop_string(ctx, -1, DUKGLUE_REF_ARRAY);
|
||||
duk_remove(ctx, -2); // pop heap stash
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "detail_traits.h"
|
||||
#include "detail_types.h"
|
||||
|
||||
#include <duktape.h>
|
||||
|
||||
namespace dukglue
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
// Helper to get the argument tuple type, with correct storage types.
|
||||
template<typename... Args>
|
||||
struct ArgsTuple {
|
||||
typedef std::tuple<typename dukglue::types::ArgStorage<Args>::type...> type;
|
||||
};
|
||||
|
||||
// Helper to get argument indices.
|
||||
// Call read for every Ts[i], for matching argument index Index[i].
|
||||
// The traits::index_tuple is used for type inference.
|
||||
// A concrete example:
|
||||
// get_values<int, bool>(duktape_context)
|
||||
// get_values_helper<{int, bool}, {0, 1}>(ctx, ignored)
|
||||
// std::make_tuple<int, bool>(read<int>(ctx, 0), read<bool>(ctx, 1))
|
||||
template<typename... Args, size_t... Indexes>
|
||||
typename ArgsTuple<Args...>::type get_stack_values_helper(duk_context* ctx, dukglue::detail::index_tuple<Indexes...>)
|
||||
{
|
||||
using namespace dukglue::types;
|
||||
return std::forward_as_tuple(DukType<typename Bare<Args>::type>::template read<typename ArgStorage<Args>::type>(ctx, Indexes)...);
|
||||
}
|
||||
|
||||
// Returns an std::tuple of the values asked for in the template parameters.
|
||||
// Values will remain on the stack.
|
||||
// Values are indexed from the bottom of the stack up (0, 1, ...).
|
||||
// If a value does not exist or does not have the expected type, an error is thrown
|
||||
// through Duktape (with duk_error(...)), and the function does not return
|
||||
template<typename... Args>
|
||||
typename ArgsTuple<Args...>::type get_stack_values(duk_context* ctx)
|
||||
{
|
||||
// We need the argument indices for read_value, and we need to be able
|
||||
// to unpack them as a template argument to match Ts.
|
||||
// So, we use traits::make_indexes<Ts...>, which returns a traits::index_tuple<0, 1, 2, ...> object.
|
||||
// We pass that into a helper function so we can put a name to that <0, 1, ...> template argument.
|
||||
// Here, the type of Args isn't important, the length of it is.
|
||||
auto indices = typename dukglue::detail::make_indexes<Args...>::type();
|
||||
return get_stack_values_helper<Args...>(ctx, indices);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace dukglue
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Credit to LuaState for this code:
|
||||
// https://github.com/AdUki/LuaState/blob/master/include/Traits.h
|
||||
|
||||
template<size_t...> struct index_tuple {};
|
||||
|
||||
template<size_t I, typename IndexTuple, typename ... Types>
|
||||
struct make_indexes_impl;
|
||||
|
||||
template<size_t I, size_t... Indexes, typename T, typename ... Types>
|
||||
struct make_indexes_impl<I, index_tuple<Indexes...>, T, Types...>
|
||||
{
|
||||
typedef typename make_indexes_impl<I + 1, index_tuple<Indexes..., I>, Types...>::type type;
|
||||
};
|
||||
|
||||
template<size_t I, size_t... Indexes>
|
||||
struct make_indexes_impl<I, index_tuple<Indexes...> >
|
||||
{
|
||||
typedef index_tuple<Indexes...> type;
|
||||
};
|
||||
|
||||
template<typename ... Types>
|
||||
struct make_indexes : make_indexes_impl<0, index_tuple<>, Types...>
|
||||
{};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <std::size_t... Is>
|
||||
struct indexes {};
|
||||
|
||||
template <std::size_t N, std::size_t... Is>
|
||||
struct indexes_builder : indexes_builder<N - 1, N - 1, Is...> {};
|
||||
|
||||
template <std::size_t... Is>
|
||||
struct indexes_builder<0, Is...> {
|
||||
typedef indexes<Is...> index;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// This mess is used to use function arugments stored in an std::tuple to an
|
||||
// std::function, function pointer, or method.
|
||||
|
||||
// std::function
|
||||
template<class Ret, class... Args, size_t... Indexes >
|
||||
Ret apply_helper(std::function<Ret(Args...)> pf, index_tuple< Indexes... >, std::tuple<Args...>&& tup)
|
||||
{
|
||||
return pf(std::forward<Args>(std::get<Indexes>(tup))...);
|
||||
}
|
||||
|
||||
template<class Ret, class ... Args>
|
||||
Ret apply(std::function<Ret(Args...)> pf, const std::tuple<Args...>& tup)
|
||||
{
|
||||
return apply_helper(pf, typename make_indexes<Args...>::type(), std::tuple<Args...>(tup));
|
||||
}
|
||||
|
||||
// function pointer
|
||||
template<class Ret, class... Args, class... BakedArgs, size_t... Indexes >
|
||||
Ret apply_fp_helper(Ret(*pf)(Args...), index_tuple< Indexes... >, std::tuple<BakedArgs...>&& tup)
|
||||
{
|
||||
return pf(std::forward<Args>(std::get<Indexes>(tup))...);
|
||||
}
|
||||
|
||||
template<class Ret, class ... Args, class ... BakedArgs>
|
||||
Ret apply_fp(Ret(*pf)(Args...), const std::tuple<BakedArgs...>& tup)
|
||||
{
|
||||
return apply_fp_helper(pf, typename make_indexes<BakedArgs...>::type(), std::tuple<BakedArgs...>(tup));
|
||||
}
|
||||
|
||||
// method pointer
|
||||
template<class Cls, class Ret, class... Args, class... BakedArgs, size_t... Indexes >
|
||||
Ret apply_method_helper(Ret(Cls::*pf)(Args...), index_tuple< Indexes... >, Cls* obj, std::tuple<BakedArgs...>&& tup)
|
||||
{
|
||||
return (*obj.*pf)(std::forward<Args>(std::get<Indexes>(tup))...);
|
||||
}
|
||||
|
||||
template<class Cls, class Ret, class ... Args, class... BakedArgs>
|
||||
Ret apply_method(Ret(Cls::*pf)(Args...), Cls* obj, const std::tuple<BakedArgs...>& tup)
|
||||
{
|
||||
return apply_method_helper(pf, typename make_indexes<Args...>::type(), obj, std::tuple<BakedArgs...>(tup));
|
||||
}
|
||||
|
||||
// const method pointer
|
||||
template<class Cls, class Ret, class... Args, class... BakedArgs, size_t... Indexes >
|
||||
Ret apply_method_helper(Ret(Cls::*pf)(Args...) const, index_tuple< Indexes... >, Cls* obj, std::tuple<BakedArgs...>&& tup)
|
||||
{
|
||||
return (*obj.*pf)(std::forward<Args>(std::get<Indexes>(tup))...);
|
||||
}
|
||||
|
||||
template<class Cls, class Ret, class ... Args, class... BakedArgs>
|
||||
Ret apply_method(Ret(Cls::*pf)(Args...) const, Cls* obj, const std::tuple<BakedArgs...>& tup)
|
||||
{
|
||||
return apply_method_helper(pf, typename make_indexes<Args...>::type(), obj, std::tuple<BakedArgs...>(tup));
|
||||
}
|
||||
|
||||
// constructor
|
||||
template<class Cls, typename... Args, size_t... Indexes >
|
||||
Cls* apply_constructor_helper(index_tuple< Indexes... >, std::tuple<Args...>&& tup)
|
||||
{
|
||||
return new Cls(std::forward<Args>(std::get<Indexes>(tup))...);
|
||||
}
|
||||
|
||||
template<class Cls, typename... Args>
|
||||
Cls* apply_constructor(const std::tuple<Args...>& tup)
|
||||
{
|
||||
return apply_constructor_helper<Cls>(typename make_indexes<Args...>::type(), std::tuple<Args...>(tup));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
#pragma once
|
||||
|
||||
#include <typeindex>
|
||||
|
||||
namespace dukglue
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
// same as duk_get_type_name, which is private for some reason *shakes fist*
|
||||
static const char* get_type_name(duk_int_t type_idx) {
|
||||
static const char* names[] = {
|
||||
"none",
|
||||
"undefined",
|
||||
"null",
|
||||
"boolean",
|
||||
"number",
|
||||
"string",
|
||||
"object",
|
||||
"buffer",
|
||||
"pointer",
|
||||
"lightfunc"
|
||||
};
|
||||
|
||||
if (type_idx >= 0 && type_idx < sizeof(names) / sizeof(names[0]))
|
||||
return names[type_idx];
|
||||
else
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
class TypeInfo
|
||||
{
|
||||
public:
|
||||
TypeInfo(std::type_index&& idx) : index_(idx), base_(nullptr) {}
|
||||
TypeInfo(const TypeInfo& rhs) : index_(rhs.index_), base_(rhs.base_) {}
|
||||
|
||||
inline void set_base(TypeInfo* base) {
|
||||
base_ = base;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool can_cast() const {
|
||||
if (index_ == typeid(T))
|
||||
return true;
|
||||
|
||||
if (base_)
|
||||
return base_->can_cast<T>();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool operator<(const TypeInfo& rhs) const { return index_ < rhs.index_; }
|
||||
inline bool operator<=(const TypeInfo& rhs) const { return index_ <= rhs.index_; }
|
||||
inline bool operator>(const TypeInfo& rhs) const { return index_ > rhs.index_; }
|
||||
inline bool operator>=(const TypeInfo& rhs) const { return index_ >= rhs.index_; }
|
||||
inline bool operator==(const TypeInfo& rhs) const { return index_ == rhs.index_; }
|
||||
inline bool operator!=(const TypeInfo& rhs) const { return index_ != rhs.index_; }
|
||||
|
||||
private:
|
||||
std::type_index index_;
|
||||
TypeInfo* base_;
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "detail_refs.h"
|
||||
#include "detail_typeinfo.h"
|
||||
#include "detail_class_proto.h"
|
||||
|
||||
// TODO try adding a using namespace std in here if I can scope it to just this file
|
||||
|
||||
namespace dukglue {
|
||||
namespace types {
|
||||
|
||||
// Bare<T>::type is T stripped of reference, pointer, and const off a type, like so:
|
||||
// Bare<Dog>::type = Dog
|
||||
// Bare<const Dog>::type = Dog
|
||||
// Bare<Dog*>::type = Dog
|
||||
// Bare<const Dog*>::type = Dog
|
||||
// Bare<Dog&>::type = Dog
|
||||
// Bare<const Dog&>::type = Dog
|
||||
template<typename T>
|
||||
struct Bare {
|
||||
typedef typename std::remove_const<typename std::remove_pointer<typename std::remove_reference<T>::type>::type>::type type;
|
||||
};
|
||||
|
||||
// DukType<T> provides functions for reading and writing T from the Duktape stack.
|
||||
// T is always a "bare type," i.e. "Dog" rather than "Dog*".
|
||||
|
||||
// There are two kinds of DukTypes:
|
||||
// 1. "Native" DukTypes. This is the default.
|
||||
// These types use an underlying native object allocated on the heap.
|
||||
// A pointer to the object (of type T*) is expected at script_object.\xFFobj_ptr.
|
||||
// "Native" DukTypes can return a value (returns a copy-constructed T from the native object),
|
||||
// a pointer (just returns script_object.\xFFobj_ptr), or a reference (dereferences script_object.\xFFobj_ptr if it is not null).
|
||||
|
||||
// 2. "Value" DukTypes. These are implemented through template specialization.
|
||||
// This is how primitive types are implemented (int, float, const char*).
|
||||
// These types can only be returned by value (T) or by const reference (const T&).
|
||||
// Attempting to read a pointer (T*) or non-const reference (T&) will give a compile-time error.
|
||||
// You can also use this to implement your own lightweight types, such as a 3D vector.
|
||||
// (Strictly speaking, non-const references (T&) *could* be returned, but any changes to the reference would
|
||||
// be discarded. So, I wrote a static assert to disable the option. If you understand the implications,
|
||||
// you should be able to safely comment out the static_assert in ArgStorage.)
|
||||
template<typename T>
|
||||
struct DukType {
|
||||
static_assert(std::is_same<T, typename Bare<T>::type >::value, "Invalid base type, expected bare type");
|
||||
|
||||
typedef std::false_type IsValueType;
|
||||
|
||||
// read pointer
|
||||
template<typename FullT, typename = typename std::enable_if< std::is_pointer<FullT>::value>::type >
|
||||
static T* read(duk_context* ctx, duk_idx_t arg_idx) {
|
||||
using namespace dukglue::detail;
|
||||
|
||||
if (duk_is_null(ctx, arg_idx))
|
||||
return nullptr;
|
||||
|
||||
if (!duk_is_object(ctx, arg_idx)) {
|
||||
duk_int_t type_idx = duk_get_type(ctx, arg_idx);
|
||||
duk_error(ctx, DUK_RET_TYPE_ERROR, "Argument %d: expected native object, got %s", arg_idx, get_type_name(type_idx));
|
||||
}
|
||||
|
||||
duk_get_prop_string(ctx, arg_idx, "\xFF" "type_info");
|
||||
if (!duk_is_pointer(ctx, -1)) // missing type_info, must not be a native object
|
||||
duk_error(ctx, DUK_RET_TYPE_ERROR, "Argument %d: expected native object (missing type_info)", arg_idx);
|
||||
|
||||
// make sure this object can be safely returned as a T*
|
||||
TypeInfo* info = static_cast<TypeInfo*>(duk_get_pointer(ctx, -1));
|
||||
if (!info->can_cast<T>())
|
||||
duk_error(ctx, DUK_RET_TYPE_ERROR, "Argument %d: wrong type of native object", arg_idx);
|
||||
|
||||
duk_pop(ctx); // pop type_info
|
||||
|
||||
duk_get_prop_string(ctx, arg_idx, "\xFF" "obj_ptr");
|
||||
if (!duk_is_pointer(ctx, -1))
|
||||
duk_error(ctx, DUK_RET_TYPE_ERROR, "Argument %d: invalid native object.", arg_idx);
|
||||
|
||||
T* obj = static_cast<T*>(duk_get_pointer(ctx, -1));
|
||||
|
||||
duk_pop(ctx); // pop obj_ptr
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
// read reference
|
||||
template<typename FullT, typename = typename std::enable_if< std::is_reference<FullT>::value>::type >
|
||||
static T& read(duk_context* ctx, duk_idx_t arg_idx) {
|
||||
T* obj = read<T*>(ctx, arg_idx);
|
||||
if (obj == nullptr)
|
||||
duk_error(ctx, DUK_RET_TYPE_ERROR, "Argument %d: cannot be null (native function expects reference)", arg_idx);
|
||||
|
||||
return *obj;
|
||||
}
|
||||
|
||||
// read value
|
||||
// commented out because it breaks for abstract classes
|
||||
/*template<typename FullT, typename = typename std::enable_if< std::is_same<T, typename std::remove_const<FullT>::type >::value>::type >
|
||||
static T read(duk_context* ctx, duk_idx_t arg_idx) {
|
||||
static_assert(std::is_copy_constructible<T>::value, "Reading a value requires a copy-constructable type");
|
||||
const T& obj = read<T&>(ctx, arg_idx);
|
||||
return T(obj);
|
||||
}*/
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Writing
|
||||
|
||||
// Reference
|
||||
template<typename FullT, typename = typename std::enable_if< std::is_reference<FullT>::value>::type >
|
||||
static void push(duk_context* ctx, T& value) {
|
||||
using namespace dukglue::detail;
|
||||
|
||||
if (!RefManager::find_and_push_native_object(ctx, &value)) {
|
||||
// need to create new script object
|
||||
ProtoManager::make_script_object<T>(ctx, &value);
|
||||
RefManager::register_native_object(ctx, &value);
|
||||
}
|
||||
}
|
||||
|
||||
// Pointer
|
||||
template<typename FullT, typename = typename std::enable_if< std::is_pointer<FullT>::value>::type >
|
||||
static void push(duk_context* ctx, T* value) {
|
||||
if (value == nullptr)
|
||||
duk_push_null(ctx);
|
||||
else
|
||||
push<T&>(ctx, *value);
|
||||
}
|
||||
|
||||
// Value (create new instance on the heap)
|
||||
// commented out because this is an easy way to accidentally cause a memory leak
|
||||
/*template<typename FullT, typename = typename std::enable_if< std::is_same<T, typename std::remove_const<FullT>::type >::value>::type >
|
||||
static void push(duk_context* ctx, T value) {
|
||||
static_assert(std::is_copy_constructible<T>::value, "Cannot push value for non-copy-constructable type.");
|
||||
return push<T*>(ctx, new T(value));
|
||||
}*/
|
||||
};
|
||||
|
||||
// Figure out what the type for an argument should be inside the tuple.
|
||||
// If a function expects a reference to a value type, we need temporary storage for the value.
|
||||
// For example, a reference to a value type (const int&) will need to be temporarily
|
||||
// stored in the tuple, so ArgStorage<const int&>::type == int.
|
||||
// Native objects are already allocated on the heap, so there's no problem storing, say, const Dog& in the tuple.
|
||||
template<typename T>
|
||||
struct ArgStorage {
|
||||
private:
|
||||
typedef typename Bare<T>::type BareType;
|
||||
//typedef DukType<BareType> ThisDukType;
|
||||
typedef typename DukType<BareType>::IsValueType IsValueType;
|
||||
|
||||
static_assert(!IsValueType::value || !std::is_pointer<T>::value, "Cannot return pointer to value type.");
|
||||
static_assert(!IsValueType::value ||
|
||||
(!std::is_reference<T>::value || std::is_const<typename std::remove_reference<T>::type>::value),
|
||||
"Value types can only be returned as const references.");
|
||||
|
||||
public:
|
||||
typedef typename std::conditional<IsValueType::value, BareType, T>::type type;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#include "detail_primitive_types.h"
|
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include <sstream>
|
||||
#include <exception>
|
||||
|
||||
class DukException : public std::exception
|
||||
{
|
||||
public:
|
||||
virtual const char* what() const noexcept override
|
||||
{
|
||||
return mMsg.c_str();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
DukException& operator<<(T rhs)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << mMsg << rhs;
|
||||
mMsg = ss.str();
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string mMsg;
|
||||
};
|
||||
|
||||
class DukErrorException : public DukException
|
||||
{
|
||||
public:
|
||||
DukErrorException(duk_context* ctx, int return_code, bool pop_error = true) {
|
||||
if (return_code != 0) {
|
||||
duk_get_prop_string(ctx, -1, "stack");
|
||||
mMsg = duk_safe_to_string(ctx, -1);
|
||||
duk_pop(ctx);
|
||||
|
||||
if (pop_error)
|
||||
duk_pop(ctx);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "register_function.h"
|
||||
#include "register_class.h"
|
||||
#include "register_property.h"
|
||||
#include "public_util.h"
|
||||
#include "dukvalue.h"
|
|
@ -0,0 +1,590 @@
|
|||
#pragma once
|
||||
|
||||
#include <duktape.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "dukexception.h"
|
||||
|
||||
// A variant class for Duktape values.
|
||||
// This class is not really dependant on the rest of dukglue, but the rest of dukglue is integrated to support it.
|
||||
// Script objects are persisted by copying a reference to the object into an array in the heap stash.
|
||||
// When we need to push a reference to the object, we just look up that reference in the stash.
|
||||
|
||||
// DukValues can be copied freely. We use reference counting behind the scenes to keep track of when we need
|
||||
// to remove our reference from the heap stash. Memory for reference counting is only allocated once a DukValue
|
||||
// is copied (either by copy constructor or operator=). std::move can be used if you are trying to avoid ref counting
|
||||
// for some reason.
|
||||
|
||||
// One script object can have multiple, completely separate DukValues "pointing" to it - in this case, there will be
|
||||
// multiple entries in the "ref array" that point to the same object. This will happen if the same script object is
|
||||
// put on the stack and turned into a DukValue multiple times independently (copy-constructing/operator=-ing
|
||||
// DukValues will not do this!). This is okay, as we are only keeping track of these objects to prevent garbage
|
||||
// collection (and access them later). This could be changed to use a map structure to look up one canonical entry per
|
||||
// script object in the "ref array" (I guess it would be more like a ref map in this case), but this would require a map
|
||||
// lookup every time we construct a DukValue. The performance difference probably isn't *that* noticeable (a good map
|
||||
// would probably be amortized constant-time lookup), but I am guessing constructing many separate DukValues that point
|
||||
// to the same script object isn't a very common thing.
|
||||
class DukValue {
|
||||
public:
|
||||
enum Type : uint8_t {
|
||||
//NONE = DUK_TYPE_NONE,
|
||||
UNDEFINED = DUK_TYPE_UNDEFINED,
|
||||
NULLREF = DUK_TYPE_NULL,
|
||||
BOOLEAN = DUK_TYPE_BOOLEAN,
|
||||
NUMBER = DUK_TYPE_NUMBER,
|
||||
STRING = DUK_TYPE_STRING,
|
||||
OBJECT = DUK_TYPE_OBJECT,
|
||||
BUFFER = DUK_TYPE_BUFFER, // not implemented
|
||||
POINTER = DUK_TYPE_POINTER,
|
||||
LIGHTFUNC = DUK_TYPE_LIGHTFUNC // not implemented
|
||||
};
|
||||
|
||||
// default constructor just makes an undefined-type DukValue
|
||||
inline DukValue() : mContext(NULL), mType(UNDEFINED), mRefCount(NULL) {}
|
||||
|
||||
virtual ~DukValue() {
|
||||
// release any references we have
|
||||
release_ref_count();
|
||||
}
|
||||
|
||||
// move constructor
|
||||
inline DukValue(DukValue&& move) {
|
||||
mContext = move.mContext;
|
||||
mType = move.mType;
|
||||
mPOD = move.mPOD;
|
||||
mRefCount = move.mRefCount;
|
||||
|
||||
if (mType == STRING)
|
||||
mString = std::move(move.mString);
|
||||
|
||||
move.mType = UNDEFINED;
|
||||
move.mRefCount = NULL;
|
||||
}
|
||||
|
||||
inline DukValue& operator=(const DukValue& rhs) {
|
||||
// free whatever we had
|
||||
release_ref_count();
|
||||
|
||||
// copy things
|
||||
mContext = rhs.mContext;
|
||||
mType = rhs.mType;
|
||||
mPOD = rhs.mPOD;
|
||||
|
||||
if (mType == STRING)
|
||||
mString = rhs.mString;
|
||||
|
||||
if (mType == OBJECT)
|
||||
{
|
||||
// ref counting increment
|
||||
if (rhs.mRefCount == NULL) {
|
||||
// not ref counted before, need to allocate memory
|
||||
const_cast<DukValue&>(rhs).mRefCount = new int(2);
|
||||
mRefCount = rhs.mRefCount;
|
||||
} else {
|
||||
// already refcounting, just increment
|
||||
mRefCount = rhs.mRefCount;
|
||||
*mRefCount = *mRefCount + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
// copy constructor
|
||||
inline DukValue(const DukValue& copy) : DukValue() {
|
||||
*this = copy;
|
||||
}
|
||||
|
||||
// equality operator
|
||||
inline bool operator==(const DukValue& rhs) const
|
||||
{
|
||||
if (mType != rhs.mType || mContext != rhs.mContext)
|
||||
return false;
|
||||
|
||||
switch (mType) {
|
||||
case UNDEFINED:
|
||||
case NULLREF:
|
||||
return true;
|
||||
case BOOLEAN:
|
||||
return mPOD.boolean == rhs.mPOD.boolean;
|
||||
case NUMBER:
|
||||
return mPOD.number == rhs.mPOD.number;
|
||||
case STRING:
|
||||
return mString == rhs.mString;
|
||||
|
||||
case OBJECT:
|
||||
{
|
||||
// this could be optimized to only push ref_array once...
|
||||
this->push();
|
||||
rhs.push();
|
||||
bool equal = duk_equals(mContext, -1, -2) ? true : false;
|
||||
duk_pop_2(mContext);
|
||||
return equal;
|
||||
}
|
||||
|
||||
case POINTER:
|
||||
return mPOD.pointer == rhs.mPOD.pointer;
|
||||
|
||||
case BUFFER:
|
||||
case LIGHTFUNC:
|
||||
default:
|
||||
throw DukException() << "operator== not implemented (" << type_name() << ")";
|
||||
}
|
||||
}
|
||||
|
||||
inline bool operator!=(const DukValue& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
// copies the object at idx on the stack into a new DukValue and returns it
|
||||
static DukValue copy_from_stack(duk_context* ctx, duk_idx_t idx = -1) {
|
||||
DukValue value;
|
||||
value.mContext = ctx;
|
||||
value.mType = (Type) duk_get_type(ctx, idx);
|
||||
switch (value.mType) {
|
||||
case UNDEFINED:
|
||||
break;
|
||||
|
||||
case NULLREF:
|
||||
value.mPOD.pointer = NULL;
|
||||
break;
|
||||
|
||||
case BOOLEAN:
|
||||
value.mPOD.boolean = duk_require_boolean(ctx, idx) ? true : false;
|
||||
break;
|
||||
|
||||
case NUMBER:
|
||||
value.mPOD.number = duk_require_number(ctx, idx);
|
||||
break;
|
||||
|
||||
case STRING:
|
||||
{
|
||||
duk_size_t len;
|
||||
const char* data = duk_get_lstring(ctx, idx, &len);
|
||||
value.mString.assign(data, len);
|
||||
break;
|
||||
}
|
||||
|
||||
case OBJECT:
|
||||
value.mPOD.ref_array_idx = stash_ref(ctx, idx);
|
||||
break;
|
||||
|
||||
case POINTER:
|
||||
value.mPOD.pointer = duk_require_pointer(ctx, idx);
|
||||
break;
|
||||
|
||||
case BUFFER:
|
||||
case LIGHTFUNC:
|
||||
default:
|
||||
throw DukException() << "Cannot turn type into DukValue (" << value.type_name() << ")";
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
protected:
|
||||
static duk_ret_t json_decode_safe(duk_context* ctx, void* user_data)
|
||||
{
|
||||
duk_json_decode(ctx, -1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
public:
|
||||
static_assert(sizeof(char) == 1, "Serialization probably broke");
|
||||
static DukValue deserialize(duk_context* ctx, const char* data, size_t data_len) {
|
||||
DukValue v;
|
||||
v.mContext = ctx;
|
||||
v.mType = *((Type*)data);
|
||||
|
||||
const char* data_ptr = data + sizeof(Type);
|
||||
data_len -= sizeof(Type);
|
||||
|
||||
switch (v.mType) {
|
||||
case UNDEFINED:
|
||||
case NULLREF:
|
||||
break;
|
||||
|
||||
case BOOLEAN:
|
||||
{
|
||||
if (data_len < 1)
|
||||
throw DukException() << "Malformed boolean data";
|
||||
|
||||
v.mPOD.boolean = data[1] == 1 ? true : false;
|
||||
break;
|
||||
}
|
||||
|
||||
case NUMBER:
|
||||
{
|
||||
if (data_len < sizeof(double))
|
||||
throw DukException() << "Malformed number data";
|
||||
|
||||
v.mPOD.number = *((double*)data_ptr);
|
||||
break;
|
||||
}
|
||||
|
||||
case STRING:
|
||||
{
|
||||
if (data_len < sizeof(uint32_t))
|
||||
throw DukException() << "Malformed string data (no length)";
|
||||
uint32_t str_len = *((uint32_t*)data_ptr);
|
||||
|
||||
if (data_len < sizeof(uint32_t) + str_len)
|
||||
throw DukException() << "Malformed string data (appears truncated)";
|
||||
|
||||
const char* str_data = (data_ptr + sizeof(uint32_t));
|
||||
v.mString.assign(str_data, str_len);
|
||||
break;
|
||||
}
|
||||
|
||||
case OBJECT:
|
||||
{
|
||||
if (data_len < sizeof(uint32_t))
|
||||
throw DukException() << "Malformed object JSON data (no length)";
|
||||
uint32_t json_len = *((uint32_t*)data_ptr);
|
||||
|
||||
if (data_len < sizeof(uint32_t) + json_len)
|
||||
throw DukException() << "Malformed object JSON data (appears truncated)";
|
||||
|
||||
const char* json_data = (data_ptr + sizeof(uint32_t));
|
||||
duk_push_lstring(ctx, json_data, json_len);
|
||||
int rc = duk_safe_call(ctx, &json_decode_safe, NULL, 1, 1);
|
||||
if (rc) {
|
||||
throw DukErrorException(ctx, rc) << "Could not decode JSON";
|
||||
} else {
|
||||
v.mPOD.ref_array_idx = stash_ref(ctx, -1);
|
||||
duk_pop(ctx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw DukException() << "not implemented";
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
// same as above (copy_from_stack), but also removes the value we copied from the stack
|
||||
static DukValue take_from_stack(duk_context* ctx, duk_idx_t idx = -1) {
|
||||
DukValue val = copy_from_stack(ctx, idx);
|
||||
duk_remove(ctx, idx);
|
||||
return val;
|
||||
}
|
||||
|
||||
// push the value we hold onto the stack
|
||||
inline void push() const {
|
||||
duk_context* ctx = mContext;
|
||||
|
||||
switch (mType) {
|
||||
case UNDEFINED:
|
||||
duk_push_undefined(ctx);
|
||||
break;
|
||||
case NULLREF:
|
||||
duk_push_null(ctx);
|
||||
break;
|
||||
|
||||
case BOOLEAN:
|
||||
duk_push_boolean(ctx, mPOD.boolean);
|
||||
break;
|
||||
|
||||
case NUMBER:
|
||||
duk_push_number(ctx, mPOD.number);
|
||||
break;
|
||||
|
||||
case STRING:
|
||||
duk_push_lstring(ctx, mString.data(), mString.size());
|
||||
break;
|
||||
|
||||
case OBJECT:
|
||||
push_ref_array(ctx);
|
||||
duk_get_prop_index(ctx, -1, mPOD.ref_array_idx);
|
||||
duk_remove(ctx, -2);
|
||||
break;
|
||||
|
||||
case POINTER:
|
||||
duk_push_pointer(ctx, mPOD.pointer);
|
||||
break;
|
||||
|
||||
case BUFFER:
|
||||
case LIGHTFUNC:
|
||||
default:
|
||||
throw DukException() << "DukValue.push() not implemented for type (" << type_name() << ")";
|
||||
}
|
||||
}
|
||||
|
||||
// various (type-safe) getters
|
||||
inline bool as_bool() const {
|
||||
if (mType != BOOLEAN)
|
||||
throw DukException() << "Expected boolean, got " << type_name();
|
||||
return mPOD.boolean;
|
||||
}
|
||||
|
||||
inline double as_double() const {
|
||||
if (mType != NUMBER)
|
||||
throw DukException() << "Expected number, got " << type_name();
|
||||
return mPOD.number;
|
||||
}
|
||||
|
||||
inline float as_float() const {
|
||||
if (mType != NUMBER)
|
||||
throw DukException() << "Expected number, got " << type_name();
|
||||
return static_cast<float>(mPOD.number);
|
||||
}
|
||||
|
||||
inline duk_int_t as_int() const {
|
||||
if (mType != NUMBER)
|
||||
throw DukException() << "Expected number, got " << type_name();
|
||||
return static_cast<uint32_t>(mPOD.number);
|
||||
}
|
||||
|
||||
inline duk_uint_t as_uint() const {
|
||||
if (mType != NUMBER)
|
||||
throw DukException() << "Expected number, got " << type_name();
|
||||
return static_cast<uint32_t>(mPOD.number);
|
||||
}
|
||||
|
||||
inline void* as_pointer() const {
|
||||
if (mType != POINTER && mType != NULLREF)
|
||||
throw DukException() << "Expected pointer or null, got " << type_name();
|
||||
return mPOD.pointer;
|
||||
}
|
||||
|
||||
inline const std::string& as_string() const {
|
||||
if (mType != STRING)
|
||||
throw DukException() << "Expected string, got " << type_name();
|
||||
return mString;
|
||||
}
|
||||
|
||||
inline const char* as_c_string() const {
|
||||
if (mType != STRING)
|
||||
throw DukException() << "Expected string, got " << type_name();
|
||||
return mString.data();
|
||||
}
|
||||
|
||||
inline Type type() const {
|
||||
return mType;
|
||||
}
|
||||
|
||||
// same as duk_get_type_name(), but that's internal to Duktape, so we shouldn't use it
|
||||
inline const char* type_name() const {
|
||||
switch (mType) {
|
||||
case UNDEFINED: return "undefined";
|
||||
case NULLREF: return "null";
|
||||
case BOOLEAN: return "boolean";
|
||||
case NUMBER: return "number";
|
||||
case STRING: return "string";
|
||||
case OBJECT: return "object";
|
||||
case BUFFER: return "buffer";
|
||||
case POINTER: return "pointer";
|
||||
case LIGHTFUNC: return "lightfunc";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
inline duk_context* context() const {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
// Important limitations:
|
||||
// - The returned value is binary and will not behave well if you treat it like a string (it will almost certainly contain '\0').
|
||||
// If you need to transport it like a string, maybe try encoding it as base64.
|
||||
// - Strings longer than 2^32 (UINT32_MAX) characters will throw an exception. You can raise this to be a uint64_t if you need
|
||||
// really long strings for some reason (be sure to change DukValue::deserialize() as well).
|
||||
// - Objects are encoded to JSON and then sent like a string. If your object can't be encoded as JSON (i.e. it's a function),
|
||||
// this will not work. This can be done, but I chose not to because it poses a security issue if you deserializing untrusted data.
|
||||
// If you require this functionality, you'll have to add it yourself with using duk_dump_function(...).
|
||||
static_assert(sizeof(char) == 1, "Serialization probably broke");
|
||||
std::vector<char> serialize() const {
|
||||
std::vector<char> buff;
|
||||
buff.resize(sizeof(Type));
|
||||
*((Type*)buff.data()) = mType;
|
||||
|
||||
switch (mType) {
|
||||
case UNDEFINED:
|
||||
case NULLREF:
|
||||
break;
|
||||
|
||||
case BOOLEAN:
|
||||
{
|
||||
buff.push_back(mPOD.boolean ? 1 : 0);
|
||||
break;
|
||||
}
|
||||
|
||||
case NUMBER:
|
||||
{
|
||||
buff.resize(buff.size() + sizeof(double));
|
||||
*((double*)(buff.data() + sizeof(Type))) = mPOD.number;
|
||||
break;
|
||||
}
|
||||
|
||||
case STRING:
|
||||
{
|
||||
if (mString.length() > static_cast<size_t>(UINT32_MAX))
|
||||
throw DukException() << "String length larger than uint32_t max";
|
||||
|
||||
uint32_t len = mString.length();
|
||||
buff.resize(buff.size() + sizeof(uint32_t) + len);
|
||||
|
||||
uint32_t* len_ptr = (uint32_t*)(buff.data() + sizeof(Type));
|
||||
*len_ptr = len;
|
||||
|
||||
char* out_ptr = (char*)(buff.data() + sizeof(Type) + sizeof(uint32_t));
|
||||
strncpy(out_ptr, mString.data(), len); // note: this will NOT be null-terminated
|
||||
break;
|
||||
}
|
||||
|
||||
case OBJECT:
|
||||
{
|
||||
push();
|
||||
if (duk_is_function(mContext, -1)) {
|
||||
duk_pop(mContext);
|
||||
throw DukException() << "Functions cannot be serialized";
|
||||
// well, technically they can...see the comments at the start of this method
|
||||
}
|
||||
|
||||
std::string json = duk_json_encode(mContext, -1);
|
||||
duk_pop(mContext);
|
||||
|
||||
if (json.length() > static_cast<size_t>(UINT32_MAX))
|
||||
throw DukException() << "JSON length larger than uint32_t max";
|
||||
|
||||
uint32_t len = json.length();
|
||||
buff.resize(buff.size() + sizeof(uint32_t) + len);
|
||||
|
||||
uint32_t* len_ptr = (uint32_t*)(buff.data() + sizeof(Type));
|
||||
*len_ptr = len;
|
||||
|
||||
char* out_ptr = (char*)(buff.data() + sizeof(Type) + sizeof(uint32_t));
|
||||
strncpy(out_ptr, json.data(), len); // note: this will NOT be null-terminated
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw DukException() << "Type not implemented for serialization.";
|
||||
}
|
||||
|
||||
return buff;
|
||||
}
|
||||
|
||||
private:
|
||||
// THIS IS COMPLETELY UNRELATED TO DETAIL_REFS.H.
|
||||
// detail_refs.h stores a mapping of native object -> script object.
|
||||
// This just stores arbitrary script objects (which likely have no native object backing them).
|
||||
// If I was smarter I might merge the two implementations, but this one is simpler
|
||||
// (since we don't need the std::map here).
|
||||
static void push_ref_array(duk_context* ctx)
|
||||
{
|
||||
static const char* DUKVALUE_REF_ARRAY = "dukglue_dukvalue_refs";
|
||||
duk_push_heap_stash(ctx);
|
||||
|
||||
if (!duk_has_prop_string(ctx, -1, DUKVALUE_REF_ARRAY)) {
|
||||
duk_push_array(ctx);
|
||||
|
||||
// ref_array[0] = 0 (initialize free list as empty)
|
||||
duk_push_int(ctx, 0);
|
||||
duk_put_prop_index(ctx, -2, 0);
|
||||
|
||||
duk_put_prop_string(ctx, -2, DUKVALUE_REF_ARRAY);
|
||||
}
|
||||
|
||||
duk_get_prop_string(ctx, -1, DUKVALUE_REF_ARRAY);
|
||||
duk_remove(ctx, -2); // pop heap stash
|
||||
}
|
||||
|
||||
// put a new reference into the ref array and return its index in the array
|
||||
static duk_uint_t stash_ref(duk_context* ctx, duk_idx_t idx)
|
||||
{
|
||||
push_ref_array(ctx);
|
||||
|
||||
// if idx is relative, we need to adjust it to deal with the array we just pushed
|
||||
if (idx < 0)
|
||||
idx--;
|
||||
|
||||
// find next free index
|
||||
// free indices are kept in a linked list, starting at ref_array[0]
|
||||
duk_get_prop_index(ctx, -1, 0);
|
||||
duk_uarridx_t next_free_idx = duk_get_uint(ctx, -1);
|
||||
duk_pop(ctx);
|
||||
|
||||
if (next_free_idx == 0) {
|
||||
// no free spots in the array, make a new one at arr.length
|
||||
next_free_idx = duk_get_length(ctx, -1);
|
||||
} else {
|
||||
// free spot found, need to remove it from the free list
|
||||
// ref_array[0] = ref_array[next_free_idx]
|
||||
duk_get_prop_index(ctx, -1, next_free_idx);
|
||||
duk_put_prop_index(ctx, -2, 0);
|
||||
}
|
||||
|
||||
duk_dup(ctx, idx); // copy value we are storing (since store consumes it)
|
||||
duk_put_prop_index(ctx, -2, next_free_idx); // store it (consumes duplicated value)
|
||||
duk_pop(ctx); // pop ref array
|
||||
|
||||
return next_free_idx;
|
||||
}
|
||||
|
||||
// remove ref_array_idx from the ref array and add its spot to the free list (at refs[0])
|
||||
static void free_ref(duk_context* ctx, duk_uarridx_t ref_array_idx)
|
||||
{
|
||||
push_ref_array(ctx);
|
||||
|
||||
// add this spot to the free list
|
||||
// refs[old_obj_idx] = refs[0] (implicitly gives up our reference)
|
||||
duk_get_prop_index(ctx, -1, 0);
|
||||
duk_put_prop_index(ctx, -2, ref_array_idx);
|
||||
|
||||
// refs[0] = old_obj_idx
|
||||
duk_push_uint(ctx, ref_array_idx);
|
||||
duk_put_prop_index(ctx, -2, 0);
|
||||
|
||||
duk_pop(ctx); // pop ref array
|
||||
}
|
||||
|
||||
// this is for reference counting - used to release our reference based on the state
|
||||
// of mRefCount. If mRefCount is NULL, we never got copy constructed, so we have ownership
|
||||
// of our reference and can free it. If it's not null and above 1, we decrement the counter
|
||||
// (someone else owns the reference). If it's not null and equal to 1, we are the last owner
|
||||
// of a previously shared reference, so we can free it.
|
||||
void release_ref_count()
|
||||
{
|
||||
if (mType == OBJECT)
|
||||
{
|
||||
if (mRefCount != NULL)
|
||||
{
|
||||
// sharing with another DukValue, are we the only one left?
|
||||
if (*mRefCount > 1) { // still someone else referencing this
|
||||
*mRefCount = *mRefCount - 1;
|
||||
} else {
|
||||
// not sharing anymore, we can free it
|
||||
free_ref(mContext, mPOD.ref_array_idx);
|
||||
delete mRefCount;
|
||||
}
|
||||
|
||||
mRefCount = NULL;
|
||||
} else {
|
||||
// not sharing with any other DukValue, free it
|
||||
free_ref(mContext, mPOD.ref_array_idx);
|
||||
}
|
||||
|
||||
mType = UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
duk_context* mContext;
|
||||
Type mType; // our type - one of the standard Duktape DUK_TYPE_* values
|
||||
|
||||
// This holds the plain-old-data types. Since this is a variant,
|
||||
// we hold only one value at a time, so this is a union to save
|
||||
// a bit of space.
|
||||
union ValueTypes {
|
||||
bool boolean;
|
||||
double number;
|
||||
void* pointer; // if mType == NULLREF, this is 0 (otherwise holds pointer value when mType == POINTER)
|
||||
duk_uarridx_t ref_array_idx;
|
||||
} mPOD;
|
||||
|
||||
std::string mString; // if it's a string, we store it with std::string
|
||||
int* mRefCount; // if mType == OBJECT and we're sharing, this will point to our ref counter
|
||||
};
|
|
@ -0,0 +1,315 @@
|
|||
#pragma once
|
||||
|
||||
#include "dukexception.h"
|
||||
#include "detail_traits.h" // for index_tuple/make_indexes
|
||||
|
||||
// This file has some useful utility functions for users.
|
||||
// Hopefully this saves you from wading through the implementation.
|
||||
|
||||
/**
|
||||
* @brief Push a value onto the duktape stack.
|
||||
*
|
||||
* WARNING: THIS IS NOT "PROTECTED." If an error occurs when pushing (unlikely, but possible),
|
||||
* the Duktape fatal error handler will be invoked (and the program will probably terminate).
|
||||
*
|
||||
* @param ctx duktape context
|
||||
* @param[in] val value to push
|
||||
*/
|
||||
template <typename FullT>
|
||||
void dukglue_push(duk_context* ctx, const FullT& val) {
|
||||
// ArgStorage has some static_asserts in it that validate value types,
|
||||
// so we typedef it to force ArgStorage<RetType> to compile and run the asserts
|
||||
typedef typename dukglue::types::ArgStorage<FullT>::type ValidateReturnType;
|
||||
|
||||
using namespace dukglue::types;
|
||||
DukType<typename Bare<FullT>::type>::template push<FullT>(ctx, std::move(val));
|
||||
}
|
||||
|
||||
template <typename T, typename... ArgTs>
|
||||
void dukglue_push(duk_context* ctx, const T& arg, ArgTs... args)
|
||||
{
|
||||
dukglue_push(ctx, arg);
|
||||
dukglue_push(ctx, args...);
|
||||
}
|
||||
|
||||
inline void dukglue_push(duk_context* ctx)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* WARNING: THIS IS NOT "PROTECTED." If an error occurs while reading (which is possible if you didn't
|
||||
* explicitly check the type), the fatal Duktape error handler will be invoked, and the program
|
||||
* will probably abort.
|
||||
*/
|
||||
template <typename RetT>
|
||||
void dukglue_read(duk_context* ctx, duk_idx_t arg_idx, RetT* out)
|
||||
{
|
||||
// ArgStorage has some static_asserts in it that validate value types,
|
||||
// so we typedef it to force ArgStorage<RetType> to compile and run the asserts
|
||||
typedef typename dukglue::types::ArgStorage<RetT>::type ValidateReturnType;
|
||||
|
||||
using namespace dukglue::types;
|
||||
*out = DukType<typename Bare<RetT>::type>::template read<RetT>(ctx, arg_idx);
|
||||
}
|
||||
|
||||
|
||||
// methods
|
||||
|
||||
// leaves return value on stack
|
||||
template <typename ObjT, typename... ArgTs>
|
||||
void dukglue_call_method(duk_context* ctx, const ObjT& obj, const char* method_name, ArgTs... args)
|
||||
{
|
||||
dukglue_push(ctx, obj);
|
||||
duk_get_prop_string(ctx, -1, method_name);
|
||||
|
||||
if (duk_check_type(ctx, -1, DUK_TYPE_UNDEFINED)) {
|
||||
duk_error(ctx, DUK_ERR_REFERENCE_ERROR, "Method does not exist", method_name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!duk_is_callable(ctx, -1)) {
|
||||
duk_error(ctx, DUK_ERR_TYPE_ERROR, "Property is not callable");
|
||||
return;
|
||||
}
|
||||
|
||||
duk_swap_top(ctx, -2);
|
||||
dukglue_push(ctx, args...);
|
||||
duk_call_method(ctx, sizeof...(args));
|
||||
}
|
||||
|
||||
namespace dukglue {
|
||||
namespace detail {
|
||||
|
||||
template <typename RetT, typename ObjT, typename... ArgTs>
|
||||
struct SafeMethodCallData {
|
||||
const ObjT* obj;
|
||||
const char* method_name;
|
||||
std::tuple<ArgTs...> args;
|
||||
RetT* out;
|
||||
};
|
||||
|
||||
template <typename ObjT, typename... ArgTs, size_t... Indexes>
|
||||
void call_method_safe_helper(duk_context* ctx, const ObjT& obj, const char* method_name, std::tuple<ArgTs...>& tup, index_tuple<Indexes...> indexes)
|
||||
{
|
||||
dukglue_call_method(ctx, obj, method_name, std::forward<ArgTs>(std::get<Indexes>(tup))...);
|
||||
}
|
||||
|
||||
template <typename RetT, typename ObjT, typename... ArgTs>
|
||||
typename std::enable_if<std::is_void<RetT>::value, duk_idx_t>::type call_method_safe(duk_context* ctx, void* udata)
|
||||
{
|
||||
typedef SafeMethodCallData<RetT, ObjT, ArgTs...> DataT;
|
||||
DataT* data = (DataT*) udata;
|
||||
call_method_safe_helper(ctx, *(data->obj), data->method_name, data->args, typename make_indexes<ArgTs...>::type());
|
||||
return 1;
|
||||
}
|
||||
|
||||
template <typename RetT, typename ObjT, typename... ArgTs>
|
||||
typename std::enable_if<!std::is_void<RetT>::value, duk_idx_t>::type call_method_safe(duk_context* ctx, void* udata)
|
||||
{
|
||||
typedef SafeMethodCallData<RetT, ObjT, ArgTs...> DataT;
|
||||
DataT* data = (DataT*)udata;
|
||||
|
||||
call_method_safe_helper(ctx, *(data->obj), data->method_name, data->args, typename make_indexes<ArgTs...>::type());
|
||||
dukglue_read(ctx, -1, data->out);
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
template <typename RetT, typename ObjT, typename... ArgTs>
|
||||
typename std::enable_if<std::is_void<RetT>::value, RetT>::type dukglue_pcall_method(duk_context* ctx, const ObjT& obj, const char* method_name, ArgTs... args)
|
||||
{
|
||||
dukglue::detail::SafeMethodCallData<RetT, ObjT, ArgTs...> data {
|
||||
&obj, method_name, std::tuple<ArgTs...>(args...), nullptr
|
||||
};
|
||||
|
||||
duk_idx_t rc = duk_safe_call(ctx, &dukglue::detail::call_method_safe<RetT, ObjT, ArgTs...>, (void*) &data, 0, 1);
|
||||
if (rc != 0)
|
||||
throw DukErrorException(ctx, rc);
|
||||
|
||||
duk_pop(ctx); // remove result from stack
|
||||
}
|
||||
|
||||
template <typename RetT, typename ObjT, typename... ArgTs>
|
||||
typename std::enable_if<!std::is_void<RetT>::value, RetT>::type dukglue_pcall_method(duk_context* ctx, const ObjT& obj, const char* method_name, ArgTs... args)
|
||||
{
|
||||
RetT out;
|
||||
dukglue::detail::SafeMethodCallData<RetT, ObjT, ArgTs...> data {
|
||||
&obj, method_name, std::tuple<ArgTs...>(args...), &out
|
||||
};
|
||||
|
||||
duk_idx_t rc = duk_safe_call(ctx, &dukglue::detail::call_method_safe<RetT, ObjT, ArgTs...>, (void*) &data, 0, 1);
|
||||
if (rc != 0)
|
||||
throw DukErrorException(ctx, rc);
|
||||
|
||||
duk_pop(ctx); // remove result from stack
|
||||
return std::move(out);
|
||||
}
|
||||
|
||||
|
||||
// calls
|
||||
|
||||
// leaves return value on the stack
|
||||
template <typename ObjT, typename... ArgTs>
|
||||
void dukglue_call(duk_context* ctx, const ObjT& func, ArgTs... args)
|
||||
{
|
||||
dukglue_push(ctx, func);
|
||||
if (!duk_is_callable(ctx, -1)) {
|
||||
duk_pop(ctx);
|
||||
duk_error(ctx, DUK_ERR_TYPE_ERROR, "Object is not callable");
|
||||
return;
|
||||
}
|
||||
|
||||
dukglue_push(ctx, args...);
|
||||
duk_call(ctx, sizeof...(args));
|
||||
}
|
||||
|
||||
|
||||
// safe call
|
||||
namespace dukglue {
|
||||
namespace detail {
|
||||
|
||||
template <typename RetT, typename ObjT, typename... ArgTs>
|
||||
struct SafeCallData {
|
||||
const ObjT* obj;
|
||||
std::tuple<ArgTs...> args;
|
||||
RetT* out;
|
||||
};
|
||||
|
||||
template <typename ObjT, typename... ArgTs, size_t... Indexes>
|
||||
void call_safe_helper(duk_context* ctx, const ObjT& obj, std::tuple<ArgTs...>& tup, index_tuple<Indexes...> indexes)
|
||||
{
|
||||
dukglue_call(ctx, obj, std::forward<ArgTs>(std::get<Indexes>(tup))...);
|
||||
}
|
||||
|
||||
// leaves result on stack
|
||||
template <typename RetT, typename ObjT, typename... ArgTs>
|
||||
typename std::enable_if<std::is_void<RetT>::value, duk_ret_t>::type call_safe(duk_context* ctx, void* udata)
|
||||
{
|
||||
typedef SafeCallData<RetT, ObjT, ArgTs...> DataT;
|
||||
DataT* data = (DataT*)udata;
|
||||
|
||||
call_safe_helper(ctx, *(data->obj), data->args, typename make_indexes<ArgTs...>::type());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// leaves result on stack
|
||||
// The result is read into RetT here because it can potentially trigger an error (with duk_error).
|
||||
// If we did it "above" this function, that error would trigger a panic instead of error handling.
|
||||
template <typename RetT, typename ObjT, typename... ArgTs>
|
||||
typename std::enable_if<!std::is_void<RetT>::value, duk_ret_t>::type call_safe(duk_context* ctx, void* udata)
|
||||
{
|
||||
typedef SafeCallData<RetT, ObjT, ArgTs...> DataT;
|
||||
DataT* data = (DataT*)udata;
|
||||
|
||||
call_safe_helper(ctx, *(data->obj), data->args, typename make_indexes<ArgTs...>::type());
|
||||
dukglue_read(ctx, -1, data->out);
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Unlike duktape, this will remove the return value from the stack!
|
||||
template <typename RetT, typename ObjT, typename... ArgTs>
|
||||
typename std::enable_if<std::is_void<RetT>::value, RetT>::type dukglue_pcall(duk_context* ctx, const ObjT& obj, ArgTs... args)
|
||||
{
|
||||
dukglue::detail::SafeCallData<RetT, ObjT, ArgTs...> data{
|
||||
&obj, std::tuple<ArgTs...>(args...), nullptr
|
||||
};
|
||||
|
||||
duk_int_t rc = duk_safe_call(ctx, &dukglue::detail::call_safe<RetT, ObjT, ArgTs...>, (void*) &data, 0, 1);
|
||||
if (rc != 0)
|
||||
throw DukErrorException(ctx, rc);
|
||||
duk_pop(ctx); // remove result from stack
|
||||
}
|
||||
|
||||
template <typename RetT, typename ObjT, typename... ArgTs>
|
||||
typename std::enable_if<!std::is_void<RetT>::value, RetT>::type dukglue_pcall(duk_context* ctx, const ObjT& obj, ArgTs... args)
|
||||
{
|
||||
RetT result;
|
||||
dukglue::detail::SafeCallData<RetT, ObjT, ArgTs...> data{
|
||||
&obj, std::tuple<ArgTs...>(args...), &result
|
||||
};
|
||||
|
||||
duk_int_t rc = duk_safe_call(ctx, &dukglue::detail::call_safe<RetT, ObjT, ArgTs...>, (void*) &data, 0, 1);
|
||||
if (rc != 0)
|
||||
throw DukErrorException(ctx, rc);
|
||||
|
||||
duk_pop(ctx); // remove result from stack
|
||||
return std::move(result);
|
||||
}
|
||||
|
||||
// same as dukglue_pcall, but leaves the result or error on the stack and returns the Duktape return code
|
||||
template <typename ObjT, typename... ArgTs>
|
||||
duk_int_t dukglue_pcall_raw(duk_context* ctx, const ObjT& obj, ArgTs... args)
|
||||
{
|
||||
dukglue::detail::SafeCallData<void, ObjT, ArgTs...> data{
|
||||
&obj, std::tuple<ArgTs...>(args...), nullptr
|
||||
};
|
||||
|
||||
return duk_safe_call(ctx, &dukglue::detail::call_safe<void, ObjT, ArgTs...>, (void*)&data, 0, 1);
|
||||
}
|
||||
|
||||
|
||||
// peval
|
||||
namespace dukglue {
|
||||
namespace detail {
|
||||
|
||||
template <typename RetT>
|
||||
struct SafeEvalData {
|
||||
const char* str;
|
||||
RetT* out;
|
||||
};
|
||||
|
||||
template <typename RetT>
|
||||
duk_ret_t eval_safe(duk_context* ctx, void* udata)
|
||||
{
|
||||
SafeEvalData<RetT>* data = (SafeEvalData<RetT>*) udata;
|
||||
|
||||
duk_eval_string(ctx, data->str);
|
||||
dukglue_read(ctx, -1, data->out);
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
template <typename RetT>
|
||||
typename std::enable_if<std::is_void<RetT>::value, RetT>::type dukglue_peval(duk_context* ctx, const char* str)
|
||||
{
|
||||
int prev_top = duk_get_top(ctx);
|
||||
int rc = duk_peval_string(ctx, str);
|
||||
if (rc != 0)
|
||||
throw DukErrorException(ctx, rc);
|
||||
|
||||
duk_pop_n(ctx, duk_get_top(ctx) - prev_top); // pop any results
|
||||
}
|
||||
|
||||
template <typename RetT>
|
||||
typename std::enable_if<!std::is_void<RetT>::value, RetT>::type dukglue_peval(duk_context* ctx, const char* str)
|
||||
{
|
||||
int prev_top = duk_get_top(ctx);
|
||||
|
||||
RetT ret;
|
||||
dukglue::detail::SafeEvalData<RetT> data{
|
||||
str, &ret
|
||||
};
|
||||
|
||||
int rc = duk_safe_call(ctx, &dukglue::detail::eval_safe<RetT>, (void*) &data, 0, 1);
|
||||
if (rc != 0)
|
||||
throw DukErrorException(ctx, rc);
|
||||
duk_pop_n(ctx, duk_get_top(ctx) - prev_top); // pop any results
|
||||
return ret;
|
||||
}
|
||||
|
||||
// register a global object (very simple helper, but very common for "Hello World"-ish applications)
|
||||
template <typename T>
|
||||
inline void dukglue_register_global(duk_context* ctx, const T& obj, const char* name)
|
||||
{
|
||||
dukglue_push(ctx, obj);
|
||||
duk_put_global_string(ctx, name);
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
#pragma once
|
||||
|
||||
#include "detail_class_proto.h"
|
||||
#include "detail_constructor.h"
|
||||
#include "detail_method.h"
|
||||
|
||||
// Set the constructor for the given type.
|
||||
template<class Cls, typename... Ts>
|
||||
void dukglue_register_constructor(duk_context* ctx, const char* name)
|
||||
{
|
||||
duk_c_function constructor_func = dukglue::detail::call_native_constructor<false, Cls, Ts...>;
|
||||
|
||||
duk_push_c_function(ctx, constructor_func, sizeof...(Ts));
|
||||
|
||||
// set constructor_func.prototype
|
||||
dukglue::detail::ProtoManager::push_prototype<Cls>(ctx);
|
||||
duk_put_prop_string(ctx, -2, "prototype");
|
||||
|
||||
// set name = constructor_func
|
||||
duk_put_global_string(ctx, name);
|
||||
}
|
||||
|
||||
template<class Cls, typename... Ts>
|
||||
void dukglue_register_constructor_managed(duk_context* ctx, const char* name)
|
||||
{
|
||||
duk_c_function constructor_func = dukglue::detail::call_native_constructor<true, Cls, Ts...>;
|
||||
duk_c_function finalizer_func = dukglue::detail::managed_finalizer<Cls>;
|
||||
|
||||
duk_push_c_function(ctx, constructor_func, sizeof...(Ts));
|
||||
|
||||
// create new prototype with finalizer
|
||||
duk_push_object(ctx);
|
||||
|
||||
// set the finalizer
|
||||
duk_push_c_function(ctx, finalizer_func, 1);
|
||||
duk_set_finalizer(ctx, -2);
|
||||
|
||||
// hook prototype with finalizer up to real class prototype
|
||||
// must use duk_set_prototype, not set the .prototype property
|
||||
dukglue::detail::ProtoManager::push_prototype<Cls>(ctx);
|
||||
duk_set_prototype(ctx, -2);
|
||||
|
||||
// set constructor_func.prototype to the prototype with the finalizer
|
||||
duk_put_prop_string(ctx, -2, "prototype");
|
||||
|
||||
// set name = constructor_func
|
||||
duk_put_global_string(ctx, name);
|
||||
}
|
||||
|
||||
template<class Base, class Derived>
|
||||
void dukglue_set_base_class(duk_context* ctx)
|
||||
{
|
||||
static_assert(!std::is_pointer<Base>::value && !std::is_pointer<Derived>::value
|
||||
&& !std::is_const<Base>::value && !std::is_const<Derived>::value, "Use bare class names.");
|
||||
static_assert(std::is_base_of<Base, Derived>::value, "Invalid class hierarchy!");
|
||||
|
||||
using namespace dukglue::detail;
|
||||
|
||||
// Derived.type_info->set_base(Base.type_info)
|
||||
ProtoManager::push_prototype<Derived>(ctx);
|
||||
duk_get_prop_string(ctx, -1, "\xFF" "type_info");
|
||||
TypeInfo* derived_type_info = static_cast<TypeInfo*>(duk_require_pointer(ctx, -1));
|
||||
duk_pop_2(ctx);
|
||||
|
||||
ProtoManager::push_prototype<Base>(ctx);
|
||||
duk_get_prop_string(ctx, -1, "\xFF" "type_info");
|
||||
TypeInfo* base_type_info = static_cast<TypeInfo*>(duk_require_pointer(ctx, -1));
|
||||
duk_pop_2(ctx);
|
||||
|
||||
derived_type_info->set_base(base_type_info);
|
||||
|
||||
// also set up the prototype chain
|
||||
ProtoManager::push_prototype<Derived>(ctx);
|
||||
ProtoManager::push_prototype<Base>(ctx);
|
||||
duk_set_prototype(ctx, -2);
|
||||
duk_pop(ctx);
|
||||
}
|
||||
|
||||
// methods
|
||||
template<typename T, T Value, class Cls, typename RetType, typename... Ts>
|
||||
void dukglue_register_method_compiletime(duk_context* ctx, RetType(Cls::*method)(Ts...), const char* name)
|
||||
{
|
||||
static_assert(std::is_same<T, RetType(Cls::*)(Ts...)>::value, "Mismatching method types.");
|
||||
dukglue_register_method_compiletime<false, T, Value, Cls, RetType, Ts...>(ctx, name);
|
||||
}
|
||||
|
||||
template<typename T, T Value, class Cls, typename RetType, typename... Ts>
|
||||
void dukglue_register_method_compiletime(duk_context* ctx, RetType(Cls::*method)(Ts...) const, const char* name)
|
||||
{
|
||||
static_assert(std::is_same<T, RetType(Cls::*)(Ts...) const>::value, "Mismatching method types.");
|
||||
dukglue_register_method_compiletime<true, T, Value, Cls, RetType, Ts...>(ctx, name);
|
||||
}
|
||||
|
||||
template<bool isConst, typename T, T Value, class Cls, typename RetType, typename... Ts>
|
||||
void dukglue_register_method_compiletime(duk_context* ctx, const char* name)
|
||||
{
|
||||
using namespace dukglue::detail;
|
||||
typedef MethodInfo<isConst, Cls, RetType, Ts...> MethodInfo;
|
||||
|
||||
duk_c_function method_func = MethodInfo::template MethodCompiletime<Value>::call_native_method;
|
||||
|
||||
ProtoManager::push_prototype<Cls>(ctx);
|
||||
|
||||
duk_push_c_function(ctx, method_func, sizeof...(Ts));
|
||||
duk_put_prop_string(ctx, -2, name); // consumes func above
|
||||
|
||||
duk_pop(ctx); // pop prototype
|
||||
}
|
||||
|
||||
template<class Cls, typename RetType, typename... Ts>
|
||||
void dukglue_register_method(duk_context* ctx, RetType(Cls::*method)(Ts...), const char* name)
|
||||
{
|
||||
dukglue_register_method<false, Cls, RetType, Ts...>(ctx, method, name);
|
||||
}
|
||||
|
||||
template<class Cls, typename RetType, typename... Ts>
|
||||
void dukglue_register_method(duk_context* ctx, RetType(Cls::*method)(Ts...) const, const char* name)
|
||||
{
|
||||
dukglue_register_method<true, Cls, RetType, Ts...>(ctx, method, name);
|
||||
}
|
||||
|
||||
// I'm sorry this signature is so long, but I figured it was better than duplicating the method,
|
||||
// once for const methods and once for non-const methods.
|
||||
template<bool isConst, typename Cls, typename RetType, typename... Ts>
|
||||
void dukglue_register_method(duk_context* ctx, typename std::conditional<isConst, RetType(Cls::*)(Ts...) const, RetType(Cls::*)(Ts...)>::type method, const char* name)
|
||||
{
|
||||
using namespace dukglue::detail;
|
||||
typedef MethodInfo<isConst, Cls, RetType, Ts...> MethodInfo;
|
||||
|
||||
duk_c_function method_func = MethodInfo::MethodRuntime::call_native_method;
|
||||
|
||||
ProtoManager::push_prototype<Cls>(ctx);
|
||||
|
||||
duk_push_c_function(ctx, method_func, sizeof...(Ts));
|
||||
|
||||
duk_push_pointer(ctx, new typename MethodInfo::MethodHolder{ method });
|
||||
duk_put_prop_string(ctx, -2, "\xFF" "method_holder"); // consumes raw method pointer
|
||||
|
||||
// make sure we free the method_holder when this function is removed
|
||||
duk_push_c_function(ctx, MethodInfo::MethodRuntime::finalize_method, 1);
|
||||
duk_set_finalizer(ctx, -2);
|
||||
|
||||
duk_put_prop_string(ctx, -2, name); // consumes method function
|
||||
|
||||
duk_pop(ctx); // pop prototype
|
||||
}
|
||||
|
||||
// methods with a variable number of (script) arguments
|
||||
template<class Cls>
|
||||
inline void dukglue_register_method_varargs(duk_context* ctx, duk_ret_t(Cls::*method)(duk_context*), const char* name)
|
||||
{
|
||||
dukglue_register_method_varargs<false, Cls>(ctx, method, name);
|
||||
}
|
||||
|
||||
template<class Cls>
|
||||
inline void dukglue_register_method_varargs(duk_context* ctx, duk_ret_t(Cls::*method)(duk_context*) const, const char* name)
|
||||
{
|
||||
dukglue_register_method_varargs<true, Cls>(ctx, method, name);
|
||||
}
|
||||
|
||||
template<bool isConst, typename Cls>
|
||||
void dukglue_register_method_varargs(duk_context* ctx,
|
||||
typename std::conditional<isConst, duk_ret_t(Cls::*)(duk_context*) const, duk_ret_t(Cls::*)(duk_context*)>::type method,
|
||||
const char* name)
|
||||
{
|
||||
using namespace dukglue::detail;
|
||||
typedef MethodVariadicRuntime<isConst, Cls> MethodVariadicInfo;
|
||||
|
||||
duk_c_function method_func = MethodVariadicInfo::call_native_method;
|
||||
|
||||
ProtoManager::push_prototype<Cls>(ctx);
|
||||
|
||||
duk_push_c_function(ctx, method_func, DUK_VARARGS);
|
||||
|
||||
duk_push_pointer(ctx, new typename MethodVariadicInfo::MethodHolderVariadic{ method });
|
||||
duk_put_prop_string(ctx, -2, "\xFF" "method_holder"); // consumes raw method pointer
|
||||
|
||||
// make sure we free the method_holder when this function is removed
|
||||
duk_push_c_function(ctx, MethodVariadicInfo::finalize_method, 1);
|
||||
duk_set_finalizer(ctx, -2);
|
||||
|
||||
duk_put_prop_string(ctx, -2, name); // consumes method function
|
||||
|
||||
duk_pop(ctx); // pop prototype
|
||||
}
|
||||
|
||||
inline void dukglue_invalidate_object(duk_context* ctx, void* obj_ptr)
|
||||
{
|
||||
dukglue::detail::RefManager::find_and_invalidate_native_object(ctx, obj_ptr);
|
||||
}
|
||||
|
||||
// register a deleter
|
||||
template<typename Cls>
|
||||
void dukglue_register_delete(duk_context* ctx)
|
||||
{
|
||||
duk_c_function delete_func = dukglue::detail::call_native_deleter<Cls>;
|
||||
|
||||
dukglue::detail::ProtoManager::push_prototype<Cls>(ctx);
|
||||
duk_push_c_function(ctx, delete_func, 0);
|
||||
duk_put_prop_string(ctx, -2, "delete");
|
||||
duk_pop(ctx); // pop prototype
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include "detail_function.h"
|
||||
|
||||
// Register a function, embedding the function address at compile time.
|
||||
// According to benchmarks, there's really not much reason to do this
|
||||
// (inconsistent 2-3% performance improvement for a 10,000 function call stress test averaged over 100 runs),
|
||||
// since it has much uglier syntax and may bloat executable size if you have many functions with identical signatures.
|
||||
template<typename T, T Value, typename RetType, typename... Ts>
|
||||
void dukglue_register_function_compiletime(duk_context* ctx, RetType(*)(Ts...), const char* name)
|
||||
{
|
||||
static_assert(std::is_same<T, RetType(Ts...)>::value,
|
||||
"Mismatching function pointer template parameter and function pointer argument types. "
|
||||
"Try: dukglue_register_function<decltype(func), func>(ctx, \"funcName\", func)");
|
||||
|
||||
duk_c_function evalFunc = dukglue::detail::FuncInfoHolder<RetType, Ts...>::template FuncActual<Value>::call_native_function;
|
||||
|
||||
duk_push_c_function(ctx, evalFunc, sizeof...(Ts));
|
||||
duk_put_global_string(ctx, name);
|
||||
}
|
||||
|
||||
// Register a function.
|
||||
template<typename RetType, typename... Ts>
|
||||
void dukglue_register_function(duk_context* ctx, RetType(*funcToCall)(Ts...), const char* name)
|
||||
{
|
||||
duk_c_function evalFunc = dukglue::detail::FuncInfoHolder<RetType, Ts...>::FuncRuntime::call_native_function;
|
||||
|
||||
duk_push_c_function(ctx, evalFunc, sizeof...(Ts));
|
||||
|
||||
static_assert(sizeof(RetType(*)(Ts...)) == sizeof(void*), "Function pointer and data pointer are different sizes");
|
||||
duk_push_pointer(ctx, reinterpret_cast<void*>(funcToCall));
|
||||
duk_put_prop_string(ctx, -2, "\xFF" "func_ptr");
|
||||
|
||||
duk_put_global_string(ctx, name);
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
#pragma once
|
||||
|
||||
#include "detail_method.h"
|
||||
|
||||
// const getter, setter
|
||||
template <typename Cls, typename RetT, typename ArgT>
|
||||
void dukglue_register_property(duk_context* ctx,
|
||||
RetT(Cls::*getter)() const,
|
||||
void(Cls::*setter)(ArgT),
|
||||
const char* name)
|
||||
{
|
||||
dukglue_register_property<true, Cls, RetT, ArgT>(ctx, getter, setter, name);
|
||||
}
|
||||
|
||||
// const getter, no setter
|
||||
template <typename Cls, typename RetT>
|
||||
void dukglue_register_property(duk_context* ctx,
|
||||
RetT(Cls::*getter)() const,
|
||||
std::nullptr_t setter,
|
||||
const char* name)
|
||||
{
|
||||
dukglue_register_property<true, Cls, RetT, RetT>(ctx, getter, setter, name);
|
||||
}
|
||||
|
||||
// non-const getter, setter
|
||||
template <typename Cls, typename RetT, typename ArgT>
|
||||
void dukglue_register_property(duk_context* ctx,
|
||||
RetT(Cls::*getter)(),
|
||||
void(Cls::*setter)(ArgT),
|
||||
const char* name)
|
||||
{
|
||||
dukglue_register_property<false, Cls, RetT, ArgT>(ctx, getter, setter, name);
|
||||
}
|
||||
|
||||
// non-const getter, no setter
|
||||
template <typename Cls, typename RetT>
|
||||
void dukglue_register_property(duk_context* ctx,
|
||||
RetT(Cls::*getter)(),
|
||||
std::nullptr_t setter,
|
||||
const char* name)
|
||||
{
|
||||
dukglue_register_property<false, Cls, RetT, RetT>(ctx, getter, setter, name);
|
||||
}
|
||||
|
||||
// no getter, setter
|
||||
template <typename Cls, typename ArgT>
|
||||
void dukglue_register_property(duk_context* ctx,
|
||||
std::nullptr_t getter,
|
||||
void(Cls::*setter)(ArgT),
|
||||
const char* name)
|
||||
{
|
||||
dukglue_register_property<false, Cls, ArgT, ArgT>(ctx, getter, setter, name);
|
||||
}
|
||||
|
||||
// no getter, no setter
|
||||
template <typename Cls, typename ValT>
|
||||
void dukglue_register_property(duk_context* ctx, std::nullptr_t getter, std::nullptr_t setter, const char* name)
|
||||
{
|
||||
// strictly speaking I think duktape can probably handle neither
|
||||
// (according to the wonderful API docs), but I don't know why you
|
||||
// would want to do this in the first place
|
||||
static_assert(std::is_void<Cls>::value, "Must have getter or setter");
|
||||
}
|
||||
|
||||
inline duk_ret_t dukglue_throw_error(duk_context* ctx)
|
||||
{
|
||||
duk_error(ctx, DUK_ERR_TYPE_ERROR, "Property does not have getter or setter.");
|
||||
}
|
||||
|
||||
template <bool isConstGetter, typename Cls, typename RetT, typename ArgT>
|
||||
void dukglue_register_property(duk_context* ctx,
|
||||
typename std::conditional<isConstGetter, RetT(Cls::*)() const, RetT(Cls::*)()>::type getter,
|
||||
void(Cls::*setter)(ArgT),
|
||||
const char* name)
|
||||
{
|
||||
using namespace dukglue::detail;
|
||||
typedef MethodInfo<isConstGetter, Cls, RetT> GetterMethodInfo;
|
||||
typedef MethodInfo<false, Cls, void, ArgT> SetterMethodInfo;
|
||||
|
||||
ProtoManager::push_prototype<Cls>(ctx);
|
||||
|
||||
// push key
|
||||
duk_push_string(ctx, name);
|
||||
|
||||
// push getter
|
||||
if (getter != nullptr) {
|
||||
duk_c_function method_func = GetterMethodInfo::MethodRuntime::call_native_method;
|
||||
|
||||
duk_push_c_function(ctx, method_func, 0);
|
||||
|
||||
duk_push_pointer(ctx, new typename GetterMethodInfo::MethodHolder{ getter });
|
||||
duk_put_prop_string(ctx, -2, "\xFF" "method_holder"); // consumes raw method pointer
|
||||
|
||||
// make sure we free the method_holder when this function is removed
|
||||
duk_push_c_function(ctx, GetterMethodInfo::MethodRuntime::finalize_method, 1);
|
||||
duk_set_finalizer(ctx, -2);
|
||||
} else {
|
||||
duk_push_c_function(ctx, dukglue_throw_error, 1);
|
||||
}
|
||||
|
||||
if (setter != nullptr) {
|
||||
duk_c_function method_func = SetterMethodInfo::MethodRuntime::call_native_method;
|
||||
|
||||
duk_push_c_function(ctx, method_func, 1);
|
||||
|
||||
duk_push_pointer(ctx, new typename SetterMethodInfo::MethodHolder{ setter });
|
||||
duk_put_prop_string(ctx, -2, "\xFF" "method_holder"); // consumes raw method pointer
|
||||
|
||||
// make sure we free the method_holder when this function is removed
|
||||
duk_push_c_function(ctx, SetterMethodInfo::MethodRuntime::finalize_method, 1);
|
||||
duk_set_finalizer(ctx, -2);
|
||||
} else {
|
||||
duk_push_c_function(ctx, dukglue_throw_error, 1);
|
||||
}
|
||||
|
||||
duk_uint_t flags = DUK_DEFPROP_HAVE_GETTER
|
||||
| DUK_DEFPROP_HAVE_SETTER
|
||||
| DUK_DEFPROP_HAVE_CONFIGURABLE /* set not configurable (from JS) */
|
||||
| DUK_DEFPROP_FORCE /* allow overriding built-ins and previously defined properties */;
|
||||
|
||||
duk_def_prop(ctx, -4, flags);
|
||||
duk_pop(ctx); // pop prototype
|
||||
}
|
Loading…
Reference in New Issue