v8toolkit  0.0.1
Utility library for embedding V8 Javascript engine in a c++ program
v8_class_wrapper_impl.h
Go to the documentation of this file.
1 
2 #pragma once
3 
4 
5 
6 #include <algorithm>
7 #include <string>
8 
9 #include "v8_class_wrapper.h"
10 
11 namespace v8toolkit {
12 
13  template<class T>
14  V8ClassWrapper<T, V8TOOLKIT_V8CLASSWRAPPER_USE_REAL_TEMPLATE_SFINAE>::V8ClassWrapper(v8::Isolate * isolate) :
15  isolate(isolate)
16  {
17  wrapper_registery.add_callback(isolate, [this](){
18  this->isolate_about_to_be_destroyed(this->isolate);
19  });
20  this->isolate_to_wrapper_map.emplace(isolate, this);
21  }
22 
23  template<class T> void
24  V8ClassWrapper<T, V8TOOLKIT_V8CLASSWRAPPER_USE_REAL_TEMPLATE_SFINAE>::call_callbacks(v8::Local<v8::Object> object,
25  const std::string & property_name,
26  v8::Local<v8::Value> & value) {
27  for (auto &callback : property_changed_callbacks) {
28  callback(isolate, object, property_name, value);
29  }
30  }
31 
32 
33  template<class T> void
34  V8ClassWrapper<T, V8TOOLKIT_V8CLASSWRAPPER_USE_REAL_TEMPLATE_SFINAE>::check_if_name_used(const std::string & name) {
35 
36  if (std::find(used_attribute_name_list.begin(),
37  used_attribute_name_list.end(),
38  name) != used_attribute_name_list.end()) {
39 
40  throw DuplicateNameException(fmt::format("Cannot add method/member named '{}' to class '{}', name already in use", name, class_name));
41  }
42 
43  // hasn't been used, so add it to used list
44  used_attribute_name_list.push_back(name);
45 
46  }
47 
48  template<class T> void
49  V8ClassWrapper<T, V8TOOLKIT_V8CLASSWRAPPER_USE_REAL_TEMPLATE_SFINAE>::check_if_static_name_used(const std::string & name) {
50  if (std::find(used_static_attribute_name_list.begin(),
51  used_static_attribute_name_list.end(),
52  name) != used_static_attribute_name_list.end()) {
53 
54  throw DuplicateNameException(fmt::format("Cannot add static method named '{}' to class '{}', name already in use", name, class_name));
55  }
56  used_static_attribute_name_list.push_back(name);
57  }
58 
59  template<class T> void
60  V8ClassWrapper<T, V8TOOLKIT_V8CLASSWRAPPER_USE_REAL_TEMPLATE_SFINAE>::check_if_constructor_name_used(const std::string & name) {
61  auto used_constructor_name_list_pair = used_constructor_name_list_map.find(this->isolate);
62  if (used_constructor_name_list_pair == used_constructor_name_list_map.end()) {
64  }
65  auto & used_constructor_name_list = used_constructor_name_list_map[this->isolate];
66  if (std::find(used_constructor_name_list.begin(),
67  used_constructor_name_list.end(),
68  name) != used_constructor_name_list.end()) {
69 
70  throw DuplicateNameException(fmt::format("Cannot add constructor named '{}' to class '{}', name already in use (or built-in JavaScript type", name, class_name));
71  }
72  used_constructor_name_list.push_back(name);
73  }
74 
75 
76 
77 
78  // takes a Data() parameter of a StdFunctionCallbackType lambda and calls it
79  // Useful because capturing lambdas don't have a traditional function pointer type
80  template<class T> void
81  V8ClassWrapper<T, V8TOOLKIT_V8CLASSWRAPPER_USE_REAL_TEMPLATE_SFINAE>::callback_helper(const v8::FunctionCallbackInfo<v8::Value>& args) {
82  StdFunctionCallbackType * callback_lambda = (StdFunctionCallbackType *)v8::External::Cast(*(args.Data()))->Value();
83  (*callback_lambda)(args);
84  }
85 
86 
87  template<class T> void
89  property_changed_callbacks.push_back(callback);
90  }
91 
92 
93 
94  /**
95  * Creates a new v8::FunctionTemplate capable of creating wrapped T objects based on previously added methods and members.
96  * TODO: This needs to track all FunctionTemplates ever created so it can try to use them in GetInstanceByPrototypeChain
97  */
98  template<class T>
101  const v8::Local<v8::Value> & data) {
102 
103  assert(this->finalized == true);
104 
105  // fprintf(stderr, "Making new wrapping function template for type %s\n", demangle<T>().c_str());
106 
107  auto function_template = v8::FunctionTemplate::New(isolate, callback, data);
108  init_instance_object_template(function_template->InstanceTemplate());
109  init_prototype_object_template(function_template->PrototypeTemplate());
110  for (auto &adder : this->static_method_adders) {
111  adder(function_template);
112  }
113 
114  function_template->SetClassName(v8::String::NewFromUtf8(isolate, class_name.c_str()));
115 
116 
117 
118  // if there is a parent type set, set that as this object's prototype
119  auto parent_function_template = global_parent_function_template.Get(isolate);
120  if (!parent_function_template.IsEmpty()) {
121  //fprintf(stderr, "FOUND PARENT TYPE of %s, USING ITS PROTOTYPE AS PARENT PROTOTYPE\n", demangle<T>().c_str());
122  function_template->Inherit(parent_function_template);
123  }
124 
125  for (auto callback : this->function_template_callbacks) {
126  callback(function_template);
127  }
128 
129  // fprintf(stderr, "Adding this_class_function_template for %s\n", demangle<T>().c_str());
130  this_class_function_templates.emplace_back(v8::Global<v8::FunctionTemplate>(isolate, function_template));
131  return function_template;
132 
133  }
134 
135  /**
136  * Returns an existing constructor function template for the class/isolate OR creates one if none exist.
137  * This is to keep the number of constructor function templates as small as possible because looking up
138  * which one created an object takes linear time based on the number that exist
139  */
140  template<class T> v8::Local<v8::FunctionTemplate>
142  {
143  if (this_class_function_templates.empty()){
144 // fprintf(stderr, "Making function template because there isn't one %s\n", demangle<T>().c_str());
145  // this will store it for later use automatically
146  return make_wrapping_function_template();
147  } else {
148  // fprintf(stderr, "Not making function template because there is already one %s\n", demangle<T>().c_str());
149  // return an arbitrary one, since they're all the same when used to call .NewInstance()
150  return this_class_function_templates[0].Get(isolate);
151  }
152  }
153 
154 
155  /**
156  * Returns the embedded native object inside a javascript object or nullptr if none is present or it isn't of the
157  * correct type
158  */
159  template<class T>
161  if (object->InternalFieldCount() == 0) {
162  return nullptr;
163  } else if (object->InternalFieldCount() > 1) {
164  throw CastException(
165  fmt::format("Tried to get internal field from object with more than one internal fields - this is not supported by v8toolkit: {}", demangle<T>()));
166  }
167 
168  auto wrap = v8::Local<v8::External>::Cast(object->GetInternalField(0));
169  WrappedData<T> *wrapped_data = static_cast<WrappedData<T> *>(wrap->Value());
170  V8TOOLKIT_DEBUG("uncasted internal field: %p\n", wrapped_data->native_object);
171  if (wrapped_data == nullptr) {
172  assert(false);
173  }
174  T * result = this->cast(wrapped_data->native_object);
175 
176  return result;
177  }
178 
179 
180 
181  /**
182  * Check to see if an object can be converted to type T, else return nullptr
183  */
184  template<class T> T *
186  {
187  V8TOOLKIT_DEBUG("In ClassWrapper::cast for type %s\n", demangle<T>().c_str());
188  if (type_checker != nullptr) {
189  V8TOOLKIT_DEBUG("Explicit compatible types set, using that\n");
190  return type_checker->check(any_base);
191  }
192 
193  else if (dynamic_cast<AnyPtr<T>*>(any_base)) {
194  assert(false); // should not use this code path anymore
195  V8TOOLKIT_DEBUG("No explicit compatible types, but successfully cast to self-type\n");
196  return static_cast<AnyPtr<T>*>(any_base)->get();
197  }
198 
199  // if it's already not const, it's ok to run it again
200  else if (dynamic_cast<AnyPtr<std::remove_const_t<T>>*>(any_base)) {
201  return static_cast<AnyPtr<std::remove_const_t<T>>*>(any_base)->get();
202  }
203  throw CastException("Could not determine type of object in V8ClassWrapper::cast(). Define ANYBASE_DEBUG for more information");
204  }
205 
206 
207  template<class T> void
209  object_template->SetInternalFieldCount(1);
210 // fprintf(stderr, "Adding %d members\n", (int)this->member_adders.size());
211  for (auto &adder : this->member_adders) {
212  adder(object_template);
213  }
214  // if this is set, it allows the object returned from a 'new' call to be used as a function as well as a traditional object
215  // e.g. let my_object = new MyClass(); my_object();
216  if (callable_adder.callback) {
217  object_template->SetCallAsFunctionHandler(callback_helper,
218  v8::External::New(this->isolate, &callable_adder.callback));
219  }
220  }
221 
222 
225 // fprintf(stderr, "Adding %d methods\n", (int)this->method_adders.size());
226  for (auto &adder : this->method_adders) {
227 
228  //std::cerr << fmt::format("Class: {} adding method: {}", demangle<T>(), adder.method_name) << std::endl;
229 
230  // create a function template, set the lambda created above to be the handler
231  auto function_template = v8::FunctionTemplate::New(this->isolate);
232  function_template->SetCallHandler(callback_helper, v8::External::New(this->isolate, &adder.callback));
233 
234  // methods are put into the protype of the newly created javascript object
235  object_template->Set(v8::String::NewFromUtf8(isolate, adder.method_name.c_str()), function_template);
236  }
237  for (auto &adder : this->fake_method_adders) {
238  adder(object_template);
239  }
240 
241  if (this->indexed_property_getter) {
242  object_template->SetIndexedPropertyHandler(this->indexed_property_getter);
243  }
244  }
245 
246 
247  /**
248  * V8ClassWrapper objects shouldn't be deleted during the normal flow of your program unless the associated isolate
249  * is going away forever. Things will break otherwise as no additional objects will be able to be created
250  * even though V8 will still present the ability to your javascript (I think)
251  */
252  template<class T>
254  {
255  // this was happening when it wasn't supposed to, like when making temp copies. need to disable copying or something
256  // if this line is to be added back
257  // isolate_to_wrapper_map.erase(this->isolate);
258  }
259 
260 
261  template<class T> void
263  assert(!this->finalized);
264  this->class_name = name;
265  }
266 
267 
268 
269  /**
270  * Function to force API user to declare that all members/methods have been added before any
271  * objects of the wrapped type can be created to make sure everything stays consistent
272  * Must be called before adding any constructors or using wrap_existing_object()
273  */
274  template<class T> void
276  if (this->finalized) {
277  throw V8Exception(this->isolate, fmt::format("Called ::finalize on wrapper that was already finalized: {}", demangle<T>()));
278  }
279  if (!std::is_const<T>::value) {
280  V8ClassWrapper<std::add_const_t<T>>::get_instance(isolate).finalize(wrap_as_most_derived_flag);
281  }
282  this->wrap_as_most_derived_flag = wrap_as_most_derived_flag;
283  this->finalized = true;
284  get_function_template(); // force creation of a function template that doesn't call v8_constructor
285  }
286 
287 
288 } // end namespace v8toolkit
func::function< void(const v8::FunctionCallbackInfo< v8::Value > &info)> StdFunctionCallbackType
Definition: v8helpers.h:42
::std::string string
Definition: gtest-port.h:1097
void add_callback(v8::Isolate *isolate, func::function< void()> callback)
#define V8TOOLKIT_DEBUG(format_string,...)
std::vector< std::string > reserved_global_names
Definition: v8helpers.cpp:368
std::map< v8::Isolate *, std::vector< std::string > > used_constructor_name_list_map
Definition: v8toolkit.cpp:39
V8ClassWrapperInstanceRegistry wrapper_registery
Definition: v8toolkit.cpp:42