Last active
January 14, 2016 11:11
-
-
Save xaxxon/c697f86094c56db1172f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// if this is called repeatedly | |
javascript_engine.wrap_class<World>(); | |
javascript_engine.wrap_class<World>(); | |
javascript_engine.wrap_class<World>(); | |
// wrap class is just this: | |
return V8ClassWrapper<T>::get_instance(this->isolate); | |
static V8ClassWrapper<T> & get_instance(v8::Isolate * isolate) { | |
// isoalte_to_wrapper_map is a class static member | |
// static std::map<v8::Isolate *, V8ClassWrapper<T> *> isolate_to_wrapper_map; | |
printf("isolate to wrapper map %p size: %d\n", &isolate_to_wrapper_map , (int)isolate_to_wrapper_map.size()); | |
if (isolate_to_wrapper_map.find(isolate) == isolate_to_wrapper_map.end()) { | |
auto new_object = new V8ClassWrapper<T>(isolate); | |
isolate_to_wrapper_map.insert(std::make_pair(isolate, new_object)); | |
printf("Creating instance %p for isolate: %p\n", new_object, isolate); | |
} | |
printf("(after) isoate to wrapper map size: %d\n", (int)isolate_to_wrapper_map.size()); | |
auto object = isolate_to_wrapper_map[isolate]; | |
printf("Returning v8 wrapper: %p\n", object); | |
return *object; | |
} | |
/* | |
outputs: | |
isolate to wrapper map 0x10cbb7750 size: 0 | |
Creating instance 0x7fd970605ee0 for isolate: 0x7fd971800000 | |
(after) isoate to wrapper map size: 1 | |
Returning v8 wrapper: 0x7fd970605ee0 | |
isolate to wrapper map 0x10cbb7750 size: 0 | |
Creating instance 0x7fd97060a360 for isolate: 0x7fd971800000 | |
(after) isoate to wrapper map size: 1 | |
Returning v8 wrapper: 0x7fd97060a360 | |
isolate to wrapper map 0x10cbb7750 size: 0 | |
Creating instance 0x7fd970608af0 for isolate: 0x7fd971800000 | |
(after) isoate to wrapper map size: 1 | |
Returning v8 wrapper: 0x7fd970608af0 | |
isolate to wrapper map 0x10cbb7750 size: 0 | |
Creating instance 0x7fd970607390 for isolate: 0x7fd971800000 | |
(after) isoate to wrapper map size: 1 | |
Returning v8 wrapper: 0x7fd970607390 | |
*/ | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2015 the V8 project authors. All rights reserved. | |
// Use of this source code is governed by a BSD-style license that can be | |
// found in the LICENSE file. | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <iostream> | |
#include <map> | |
#include <vector> | |
#include <utility> | |
#include <assert.h> | |
// V8 includes | |
#include "include/libplatform/libplatform.h" | |
#include "include/v8.h" | |
// Specialized types that know how to convert from a v8::Value to a primitive type | |
template<typename T> | |
struct CastToNative {}; | |
// casts from a primitive to a v8::Value | |
template<typename T> | |
struct CastToJS { | |
}; | |
// Responsible for calling the actual method and populating the return type for non-void return type methods | |
template<typename METHOD_TYPE> | |
struct RunMethod {}; | |
/** | |
* Specialization for methods that don't return void. Sets v8::FunctionCallbackInfo::GetReturnValue to value returned by the method | |
*/ | |
template<typename RETURN_TYPE, typename CLASS_TYPE, typename ... PARAMETERS> | |
struct RunMethod<RETURN_TYPE(CLASS_TYPE::*)(PARAMETERS...)>{ | |
typedef RETURN_TYPE(CLASS_TYPE::*METHOD_TYPE)(PARAMETERS...); | |
void operator()(CLASS_TYPE & object, METHOD_TYPE method, const v8::FunctionCallbackInfo<v8::Value> & info, PARAMETERS... parameters) { | |
fprintf(stderr, "Calling function with non-void return type\n"); | |
RETURN_TYPE return_value = (object.*method)(parameters...); | |
auto casted_result = CastToJS<RETURN_TYPE>()(info.GetIsolate(), return_value); | |
auto name = v8::String::NewFromUtf8(info.GetIsolate(), "get_map"); | |
bool fuck_this = true; | |
auto has_get_mapsish = casted_result->Has(info.GetIsolate()->GetCurrentContext(), name).FromMaybe(fuck_this); | |
printf("Has get_maps(ish): %s\n", has_get_mapsish ? "yes" : "no"); | |
// auto localtest = test.ToLocalChecked(); | |
// printf("get_maps-ish is function %s\n", localtest->IsFunction()?"yes":"no"); | |
// if (test.IsEmpty()) { | |
// printf("test is empty\n"); | |
// } else { | |
// printf("test not empty\n"); | |
// } | |
info.GetReturnValue().Set(casted_result); | |
} | |
}; | |
/** | |
* Specialization for methods that return void. No value set for v8::FunctionCallbackInfo::GetReturnValue | |
* The javascript call will return the javascript "undefined" value | |
*/ | |
template<typename CLASS_TYPE, typename ... PARAMETERS> | |
struct RunMethod<void(CLASS_TYPE::*)(PARAMETERS...)> { | |
typedef void(CLASS_TYPE::*METHOD_TYPE)(PARAMETERS...); | |
void operator()(CLASS_TYPE & object, METHOD_TYPE method, const v8::FunctionCallbackInfo<v8::Value> &, PARAMETERS... parameters) { | |
fprintf(stderr, "CALLING METHOD WITH VOID RETURN\n"); | |
(object.*method)(parameters...); | |
} | |
}; | |
/** | |
* Class for turning a function parameter list into a parameter pack useful for actually calling the function | |
*/ | |
template<int depth, typename T, typename U> | |
struct Caller {}; | |
/** | |
* Specialization for when there are no parameters left to process, so it is time to actually | |
* call the function | |
*/ | |
template<int depth, typename METHOD_TYPE, typename RET, typename CLASS_TYPE> | |
struct Caller<depth, METHOD_TYPE, RET(CLASS_TYPE::*)()> { | |
public: | |
// the final class in the call chain stores the actual method to be called | |
enum {DEPTH=depth, ARITY=0}; | |
// This call method actually calls the method with the specified object and the | |
// parameter pack that was built up via the chain of calls between templated types | |
template<typename ... Ts> | |
void operator()(METHOD_TYPE method, CLASS_TYPE & object, const v8::FunctionCallbackInfo<v8::Value> & info, Ts... ts) { | |
RunMethod<METHOD_TYPE>()(object, method, info, ts...); | |
} | |
}; | |
/** | |
* specialization that strips off the first remaining parameter off the method type, stores that and then | |
* inherits from another instance that either strips the next one off, or if none remaining, actually calls | |
* the method | |
* The method type is specified twice because the first is actually used by the final specialization to hold the | |
* method type while the second one has its input parameter list stripped off one at a time to determine when | |
* the inheritance chain ends | |
*/ | |
template<int depth, typename METHOD_TYPE, typename CLASS_TYPE, typename RET, typename HEAD, typename...TAIL> | |
struct Caller<depth, METHOD_TYPE, RET(CLASS_TYPE::*)(HEAD,TAIL...)> : public Caller<depth+1, METHOD_TYPE, RET(CLASS_TYPE::*)(TAIL...)> { | |
public: | |
typedef Caller<depth+1, METHOD_TYPE, RET(CLASS_TYPE::*)(TAIL...)> super; | |
enum {DEPTH = depth, ARITY=super::ARITY + 1}; | |
template<typename ... Ts> | |
void operator()(METHOD_TYPE method, CLASS_TYPE & object, const v8::FunctionCallbackInfo<v8::Value> & info, Ts... ts) { | |
CastToNative<HEAD> cast; | |
this->super::operator()(method, object, info, ts..., cast(info[depth])); | |
} | |
}; | |
// Provides a mechanism for creating javascript-ready objects from an arbitrary C++ class | |
// Can provide a JS constructor method or wrap objects created in another c++ function | |
template<class T> | |
class V8ClassWrapper { | |
private: | |
static std::map<v8::Isolate *, V8ClassWrapper<T> *> isolate_to_wrapper_map; | |
// Common tasks to do for any new js object regardless of how it is created | |
static void _initialize_new_js_object(v8::Isolate * isolate, v8::Local<v8::Object> js_object, T * cpp_object) { | |
js_object->SetInternalField(0, v8::External::New(isolate, cpp_object)); | |
// tell V8 about the memory we allocated so it knows when to do garbage collection | |
isolate->AdjustAmountOfExternalAllocatedMemory(sizeof(T)); | |
// set up a callback so we can clean up our internal object when the javascript | |
// object is garbage collected | |
auto callback_data = new SetWeakCallbackParameter(); | |
callback_data->first = cpp_object; | |
callback_data->second.Reset(isolate, js_object); | |
callback_data->second.SetWeak(callback_data, V8ClassWrapper<T>::v8_destructor); | |
} | |
// users of the library should call get_instance, not the constructor directly | |
V8ClassWrapper(v8::Isolate * isolate) : isolate(isolate) { | |
// this is a bit weird and there's probably a better way, but create an unbound functiontemplate for use | |
// when wrapping an object that is created outside javascript when there is no way to create that | |
// class type directly from javascript (no js constructor) | |
auto constructor_template = v8::FunctionTemplate::New(isolate); | |
constructor_template->InstanceTemplate()->SetInternalFieldCount(1); | |
this->constructor_templates.push_back(constructor_template); | |
} | |
protected: | |
// these are tightly tied, as a FunctionTemplate is only valid in the isolate it was created with | |
std::vector<v8::Local<v8::FunctionTemplate>> constructor_templates; | |
v8::Isolate * isolate; | |
bool member_or_method_added = false; | |
public: | |
/** | |
* Returns a "singleton-per-isolate" instance of the V8ClassWrapper for the wrapped class type. | |
* For each isolate you need to add constructors/methods/members separately. | |
*/ | |
static V8ClassWrapper<T> & get_instance(v8::Isolate * isolate) { | |
printf("isolate to wrapper map %p size: %d\n", &isolate_to_wrapper_map, (int)isolate_to_wrapper_map.size()); | |
if (isolate_to_wrapper_map.find(isolate) == isolate_to_wrapper_map.end()) { | |
auto new_object = new V8ClassWrapper<T>(isolate); | |
isolate_to_wrapper_map.insert(std::make_pair(isolate, new_object)); | |
printf("Creating instance %p for isolate: %p\n", new_object, isolate); | |
} | |
printf("(after) isoate to wrapper map size: %d\n", (int)isolate_to_wrapper_map.size()); | |
auto object = isolate_to_wrapper_map[isolate]; | |
printf("Returning v8 wrapper: %p\n", object); | |
return *object; | |
} | |
/** | |
* V8ClassWrapper objects shouldn't be deleted during the normal flow of your program unless the associated isolate | |
* is going away forever. Things will break otherwise as no additional objects will be able to be created | |
* even though V8 will still present the ability to your javascript (I think) | |
*/ | |
virtual ~V8ClassWrapper(){ | |
isolate_to_wrapper_map.erase(this->isolate); | |
} | |
/** | |
* Creates a javascript method of the specified name which, when called with the "new" keyword, will return | |
* a new object of this type | |
*/ | |
template<typename ... CONSTRUCTOR_PARAMETER_TYPES> | |
V8ClassWrapper<T> & add_constructor(std::string js_constructor_name, v8::Local<v8::ObjectTemplate> & parent_template) { | |
// if you add a constructor after adding a member or method, it will be missing on objects created with | |
// this constructor | |
assert(member_or_method_added == false); | |
// create a function template even if no javascript constructor will be used so | |
// FunctionTemplate::InstanceTemplate can be populated. That way if a javascript constructor is added | |
// later the FunctionTemplate will be ready to go | |
auto constructor_template = v8::FunctionTemplate::New(isolate, V8ClassWrapper<T>::v8_constructor<CONSTRUCTOR_PARAMETER_TYPES...>); | |
// When creating a new object, make sure they have room for a c++ object to shadow it | |
constructor_template->InstanceTemplate()->SetInternalFieldCount(1); | |
// store it so it doesn't go away | |
this->constructor_templates.push_back(constructor_template); | |
// Add the constructor function to the parent object template (often the global template) | |
parent_template->Set(v8::String::NewFromUtf8(isolate, js_constructor_name.c_str()), constructor_template); | |
this->constructor_templates.push_back(constructor_template); | |
return *this; | |
} | |
// Not sure if this properly sets the prototype of the new object like when the constructor functiontemplate is called as | |
// a constructor from javascript | |
/** | |
* Used when wanting to return an object from a c++ function call back to javascript | |
*/ | |
v8::Local<v8::Object> wrap_existing_cpp_object(v8::Local<v8::Context> context, T * existing_cpp_object) { | |
auto isolate = this->isolate; | |
fprintf(stderr, "Wrapping existing c++ object %p in v8 wrapper this: %p isolate %p\n", existing_cpp_object, this, isolate); | |
v8::Isolate::Scope is(isolate); | |
v8::Context::Scope cs(context); | |
auto new_js_object = this->constructor_templates[0]->InstanceTemplate()->NewInstance(); | |
_initialize_new_js_object(isolate, new_js_object, existing_cpp_object); | |
return new_js_object; | |
} | |
typedef std::function<void(const v8::FunctionCallbackInfo<v8::Value>& info)> StdFunctionCallbackType; | |
// takes a Data() parameter of a StdFunctionCallbackType lambda and calls it | |
// Useful because capturing lambdas don't have a traditional function pointer type | |
static void callback_helper(const v8::FunctionCallbackInfo<v8::Value>& args) { | |
StdFunctionCallbackType * callback_lambda = (StdFunctionCallbackType *)v8::External::Cast(*(args.Data()))->Value(); | |
(*callback_lambda)(args); | |
} | |
template<typename VALUE_T> | |
static void GetterHelper(v8::Local<v8::String> property, | |
const v8::PropertyCallbackInfo<v8::Value>& info) { | |
v8::Local<v8::Object> self = info.Holder(); | |
v8::Local<v8::External> wrap = v8::Local<v8::External>::Cast(self->GetInternalField(0)); | |
T * cpp_object = static_cast<T *>(wrap->Value()); | |
auto member_reference_getter = (std::function<VALUE_T&(T*)> *)v8::External::Cast(*(info.Data()))->Value(); | |
auto & member_ref = (*member_reference_getter)(cpp_object); | |
info.GetReturnValue().Set(CastToJS<VALUE_T>()(info.GetIsolate(), member_ref)); | |
} | |
template<typename VALUE_T> | |
static void SetterHelper(v8::Local<v8::String> property, v8::Local<v8::Value> value, | |
const v8::PropertyCallbackInfo<void>& info) { | |
v8::Local<v8::Object> self = info.Holder(); | |
v8::Local<v8::External> wrap = v8::Local<v8::External>::Cast(self->GetInternalField(0)); | |
T * cpp_object = static_cast<T *>(wrap->Value()); | |
auto member_reference_getter = (std::function<VALUE_T&(T*)> *)v8::External::Cast(*(info.Data()))->Value(); | |
auto & member_ref = (*member_reference_getter)(cpp_object); | |
member_ref = CastToNative<VALUE_T>()(value); | |
auto & m2 = (*member_reference_getter)(cpp_object); | |
} | |
/** | |
* Adds a getter and setter method for the specified class member | |
* add_member(&ClassName::member_name, "javascript_attribute_name"); | |
*/ | |
template<typename MEMBER_TYPE> | |
V8ClassWrapper<T> & add_member(MEMBER_TYPE T::* member, std::string member_name) { | |
// stop additional constructors from being added | |
member_or_method_added = true; | |
// typedef MEMBER_TYPE T::*MEMBER_POINTER_TYPE; | |
// this lambda is shared between the getter and the setter so it can only do work needed by both | |
static auto get_member_reference = std::function<MEMBER_TYPE&(T*)>([member](T * cpp_object)->MEMBER_TYPE&{ | |
return cpp_object->*member; | |
}); | |
for(auto constructor_template : this->constructor_templates) { | |
constructor_template->InstanceTemplate()->SetAccessor(v8::String::NewFromUtf8(isolate, | |
member_name.c_str()), | |
GetterHelper<MEMBER_TYPE>, | |
SetterHelper<MEMBER_TYPE>, | |
v8::External::New(isolate, &get_member_reference)); | |
} | |
return *this; | |
} | |
/** | |
* adds the ability to call the specified class instance method on an object of this type | |
* add_method(&ClassName::method_name, "javascript_attribute_name"); | |
*/ | |
template<typename METHOD_TYPE> | |
V8ClassWrapper<T> & add_method(METHOD_TYPE method, std::string method_name) { | |
// stop additional constructors from being added | |
member_or_method_added = true; | |
// this is leaked if this ever isn't used anymore | |
StdFunctionCallbackType * f = new StdFunctionCallbackType([method](const v8::FunctionCallbackInfo<v8::Value>& info) { | |
// get the behind-the-scenes c++ object | |
v8::Local<v8::Object> self = info.Holder(); | |
v8::Local<v8::External> wrap = v8::Local<v8::External>::Cast(self->GetInternalField(0)); | |
void* ptr = wrap->Value(); | |
auto backing_object_pointer = static_cast<T*>(ptr); | |
Caller<0, METHOD_TYPE, METHOD_TYPE>()(method, *backing_object_pointer, info); | |
}); | |
auto function_template = v8::FunctionTemplate::New(this->isolate, callback_helper, v8::External::New(this->isolate, f)); | |
for(auto constructor_template : this->constructor_templates) { | |
constructor_template->InstanceTemplate()->Set(v8::String::NewFromUtf8(isolate, method_name.c_str()), function_template); | |
} | |
return *this; | |
} | |
typedef std::pair<T*, v8::Global<v8::Object>> SetWeakCallbackParameter; | |
// Helper for creating objects when "new MyClass" is called from java | |
template<typename ... CONSTRUCTOR_PARAMETER_TYPES> | |
static void v8_constructor(const v8::FunctionCallbackInfo<v8::Value>& args) { | |
auto isolate = args.GetIsolate(); | |
T * new_cpp_object = call_cpp_constructor<CONSTRUCTOR_PARAMETER_TYPES...>(args, std::index_sequence_for<CONSTRUCTOR_PARAMETER_TYPES...>()); | |
_initialize_new_js_object(isolate, args.This(), new_cpp_object); | |
// return the object to the javascript caller | |
args.GetReturnValue().Set(args.This()); | |
} | |
template <typename ...Fs, size_t...ns> | |
static T * call_cpp_constructor(const v8::FunctionCallbackInfo<v8::Value> & args, std::index_sequence<ns...>){ | |
return new T(CastToNative<Fs>()(args[ns])...); | |
} | |
// Helper for cleaning up the underlying wrapped c++ object when the corresponding javascript object is | |
// garbage collected | |
static void v8_destructor(const v8::WeakCallbackData<v8::Object, SetWeakCallbackParameter> & data) { | |
auto isolate = data.GetIsolate(); | |
SetWeakCallbackParameter * parameter = data.GetParameter(); | |
// delete our internal c++ object and tell V8 the memory has been removed | |
delete parameter->first; | |
isolate->AdjustAmountOfExternalAllocatedMemory(-sizeof(T)); | |
// Clear out the Global<Object> handle | |
parameter->second.Reset(); | |
// Delete the heap-allocated std::pair from v8_constructor | |
delete parameter; | |
} | |
}; | |
template <class T> std::map<v8::Isolate *, V8ClassWrapper<T> *> V8ClassWrapper<T>::isolate_to_wrapper_map; | |
#include "casts.hpp" | |
// decent example: http://www.codeproject.com/Articles/29109/Using-V-Google-s-Chrome-JavaScript-Virtual-Machin | |
// code xecycle on freenode:##c++ gave me to call a function from values in a tuple | |
// std::get<i>(t)... turns into std::get<0>(t), std::get<1>(t),....,std::get<last_entry_in_i_sequence -1>(t) | |
// | |
// namespace tuple_util_impl { | |
// | |
// template <size_t... i, class Function, class Tuple> | |
// auto spread_call(std::index_sequence<i...>, Function f, Tuple&& t) | |
// -> decltype(f(std::get<i>(t)...)) | |
// { | |
// return f(std::get<i>(t)...); | |
// } | |
// | |
// } // namespace tuple_util_impl | |
// | |
// template <class Function, class Tuple> | |
// auto spread_call(Function&& f, Tuple&& t) -> decltype( | |
// tuple_util_impl::spread_call( | |
// std::make_index_sequence<std::tuple_size<typename std::decay<Tuple>::type>::value>(), | |
// f, t)) | |
// { | |
// return tuple_util_impl::spread_call( | |
// std::make_index_sequence<std::tuple_size<typename std::decay<Tuple>::type>::value>(), | |
// std::forward<Function>(f), std::forward<Tuple>(t)); | |
// } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment