Add dukglue

This commit is contained in:
Ted John 2018-03-17 23:26:38 +00:00
parent 43508e0402
commit f0de6533a0
17 changed files with 2716 additions and 0 deletions

View File

@ -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;
}
};
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
};
};
}
}

View File

@ -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);
}
};
}
}

View File

@ -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!");
}
};*/
}
}

View File

@ -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
}
};
}
}

View File

@ -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);
}
}
}

View File

@ -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));
}
//////////////////////////////////////////////////////////////////////////////////////////////
}
}

View File

@ -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_;
};
}
}

View File

@ -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"

View File

@ -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);
}
}
};

View File

@ -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"

View File

@ -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
};

View File

@ -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);
}

View File

@ -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
}

View File

@ -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);
}

View File

@ -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
}