Empirical
JSWrap.h
Go to the documentation of this file.
1 
39 #ifndef EMP_JSWRAP_H
40 #define EMP_JSWRAP_H
41 
42 
43 #include <array>
44 #include <functional>
45 #include <tuple>
46 
47 #include "../meta/meta.h"
48 
49 #include "../base/assert.h"
50 #include "../base/vector.h"
51 
52 #include "../tools/functions.h"
53 #include "../tools/mem_track.h"
54 #include "../tools/tuple_struct.h"
55 #include "../tools/tuple_utils.h"
56 
57 #include "init.h"
58 #include "js_utils.h"
59 
60 #ifdef EMSCRIPTEN
61 extern "C" {
62  extern int EMP_GetCBArgCount(); // Get the number of arguments associated with a callback.
63 }
64 #else
65 // When NOT in Emscripten, need a stub for this function.
66 int EMP_GetCBArgCount() { return -1; }
67 #endif
68 
69 namespace emp {
70 
72  template <int ARG_ID> static void LoadArg(int16_t & arg_var) {
73  arg_var = (int16_t) EM_ASM_INT({ return emp_i.cb_args[$0]; }, ARG_ID);
74  }
75 
76  template <int ARG_ID> static void LoadArg(int32_t & arg_var) {
77  arg_var = (int32_t) EM_ASM_INT({ return emp_i.cb_args[$0]; }, ARG_ID);
78  }
79 
80  template <int ARG_ID> static void LoadArg(int64_t & arg_var) {
81  arg_var = (int64_t) EM_ASM_DOUBLE({ return emp_i.cb_args[$0]; }, ARG_ID);
82  }
83 
84  template <int ARG_ID> static void LoadArg(uint16_t & arg_var) {
85  arg_var = (uint16_t) EM_ASM_INT({ return emp_i.cb_args[$0]; }, ARG_ID);
86  }
87 
88  template <int ARG_ID> static void LoadArg(uint32_t & arg_var) {
89  arg_var = (uint32_t) EM_ASM_INT({ return emp_i.cb_args[$0]; }, ARG_ID);
90  }
91 
92  template <int ARG_ID> static void LoadArg(uint64_t & arg_var) {
93  arg_var = (uint64_t) EM_ASM_DOUBLE({ return emp_i.cb_args[$0]; }, ARG_ID);
94  }
95 
96  template <int ARG_ID> static void LoadArg(bool & arg_var) {
97  arg_var = (bool) EM_ASM_INT({ return emp_i.cb_args[$0]; }, ARG_ID);
98  }
99 
100  template <int ARG_ID> static void LoadArg(char & arg_var) {
101  arg_var = (char) EM_ASM_INT({ return emp_i.cb_args[$0]; }, ARG_ID);
102  }
103 
104  template <int ARG_ID> static void LoadArg(double & arg_var) {
105  arg_var = EM_ASM_DOUBLE({ return emp_i.cb_args[$0]; }, ARG_ID);
106  }
107 
108  template <int ARG_ID> static void LoadArg(float & arg_var) {
109  arg_var = (float) EM_ASM_DOUBLE({ return emp_i.cb_args[$0]; }, ARG_ID);
110  }
111 
112  template <int ARG_ID> static void LoadArg(std::string & arg_var) {
113  char * tmp_var = (char *) EM_ASM_INT({
114  return allocate(intArrayFromString(emp_i.cb_args[$0]), 'i8', ALLOC_STACK);
115  }, ARG_ID);
116  arg_var = tmp_var; // @CAO Do we need to free the memory in tmp_var?
117  }
118 
119  template <int ARG_ID, size_t SIZE, typename T> static void LoadArg(emp::array<T, SIZE> & arg_var){
120  EM_ASM_ARGS({emp_i.__outgoing_array = emp_i.cb_args[$0];}, ARG_ID);
121  pass_array_to_cpp(arg_var);
122  }
123 
124  template <int ARG_ID, typename T> static void LoadArg(emp::vector<T> & arg_var){
125  EM_ASM_ARGS({emp_i.__outgoing_array = emp_i.cb_args[$0];}, ARG_ID);
126  pass_vector_to_cpp(arg_var);
127  }
128 
129  //Helper functions to load arguments from inside Javascript objects by name.
130  template <int ARG_ID> static void LoadArg(int16_t & arg_var, std::string var) {
131  arg_var = (int16_t) EM_ASM_INT({
132  return emp_i.curr_obj[Pointer_stringify($0)];
133  }, var.c_str());
134  }
135 
136  template <int ARG_ID> static void LoadArg(int32_t & arg_var, std::string var) {
137  arg_var = (int32_t) EM_ASM_INT({
138  return emp_i.curr_obj[Pointer_stringify($0)];
139  }, var.c_str());
140  }
141 
142  template <int ARG_ID> static void LoadArg(int64_t & arg_var, std::string var) {
143  arg_var = (int64_t) EM_ASM_DOUBLE({
144  return emp_i.curr_obj[Pointer_stringify($0)];
145  }, var.c_str());
146  }
147 
148  template <int ARG_ID> static void LoadArg(uint16_t & arg_var, std::string var) {
149  arg_var = (uint16_t) EM_ASM_INT({
150  return emp_i.curr_obj[Pointer_stringify($0)];
151  }, var.c_str());
152  }
153 
154  template <int ARG_ID> static void LoadArg(uint32_t & arg_var, std::string var) {
155  arg_var = (uint32_t) EM_ASM_INT({
156  return emp_i.curr_obj[Pointer_stringify($0)];
157  }, var.c_str());
158  }
159 
160  template <int ARG_ID> static void LoadArg(uint64_t & arg_var, std::string var) {
161  arg_var = (uint64_t) EM_ASM_DOUBLE({
162  return emp_i.curr_obj[Pointer_stringify($0)];
163  }, var.c_str());
164  }
165 
166  template <int ARG_ID> static void LoadArg(bool & arg_var, std::string var) {
167  arg_var = (bool) EM_ASM_INT({
168  return emp_i.curr_obj[Pointer_stringify($0)];
169  }, var.c_str());
170  }
171 
172  template <int ARG_ID> static void LoadArg(char & arg_var, std::string var) {
173  arg_var = (char) EM_ASM_INT({
174  return emp_i.curr_obj[Pointer_stringify($0)];
175  }, var.c_str());
176  }
177 
178  template <int ARG_ID> static void LoadArg(double & arg_var, std::string var) {
179  arg_var = EM_ASM_DOUBLE({
180  return emp_i.curr_obj[Pointer_stringify($0)];
181  }, var.c_str());
182  }
183 
184  template <int ARG_ID> static void LoadArg(float & arg_var, std::string var) {
185  arg_var = (float) EM_ASM_DOUBLE({
186  return emp_i.curr_obj[Pointer_stringify($0)];
187  }, var.c_str());
188  }
189 
190  template <int ARG_ID> static void LoadArg(std::string & arg_var, std::string var) {
191  char * tmp_var = (char *) EM_ASM_INT({
192  if (emp_i.curr_obj[Pointer_stringify($0)] == null){
193  emp_i.curr_obj[Pointer_stringify($0)] = "undefined";
194  }
195  return allocate(intArrayFromString(emp_i.curr_obj[Pointer_stringify($0)]),
196  'i8', ALLOC_STACK);
197  }, var.c_str());
198  arg_var = tmp_var; // Free memory here?
199  }
200 
201  template <typename JSON_TYPE, int ARG_ID, int FIELD>
202  struct LoadTuple;
203 
205  template <int ARG_ID, typename JSON_TYPE> static
206  typename std::enable_if<JSON_TYPE::n_fields != -1, void>::type
207  LoadArg(JSON_TYPE & arg_var, std::string var) {
208  //std::cout << "Loading " << var << " ARGNID: " << ARG_ID << std::endl;
209  //LoadArg<ARG_ID>(std::get<ARG_ID>(arg_var.emp__tuple_body));
210  EM_ASM_ARGS({
211  emp_i.object_queue.push(emp_i.curr_obj);
212  emp_i.curr_obj = emp_i.curr_obj[Pointer_stringify($0)];
213  }, var.c_str());
214  LoadTuple<JSON_TYPE, ARG_ID, JSON_TYPE::n_fields> load_tuple = LoadTuple<JSON_TYPE, ARG_ID, JSON_TYPE::n_fields>();
215  load_tuple.LoadJSDataArg(arg_var);
216  }
217 
218  template <typename JSON_TYPE, int ARG_ID, int FIELD>
219  struct LoadTuple {
220  static void LoadJSDataArg(JSON_TYPE & arg_var) {
221  //std::cout << "LoadingJS " << arg_var.var_names[FIELD-1] << " FIeLd: " << FIELD-1 << std::endl;
222  LoadArg<ARG_ID>(std::get<FIELD-1>(arg_var.emp__tuple_body), arg_var.var_names[FIELD-1]);
223  LoadTuple<JSON_TYPE, ARG_ID, FIELD-1> load_tuple = LoadTuple<JSON_TYPE, ARG_ID, FIELD-1>();
224  load_tuple.LoadJSDataArg(arg_var);
225  }
226  };
227 
228  template <typename JSON_TYPE, int ARG_ID>
229  struct LoadTuple<JSON_TYPE, ARG_ID, 0> {
230  static void LoadJSDataArg(JSON_TYPE & arg_var) {
231  EM_ASM({emp_i.curr_obj = emp_i.object_queue.pop();});
232  }
233  };
234 
235 
236  template <int ARG_ID, typename JSON_TYPE> static
237  typename std::enable_if<JSON_TYPE::n_fields != -1, void>::type
238  LoadArg(JSON_TYPE & arg_var) {
239  //std::cout << "Loading ARGNID: " << ARG_ID << std::endl;
240  EM_ASM_ARGS({
241  emp_i.object_queue = [];
242  emp_i.curr_obj = emp_i.cb_args[$0];
243  }, ARG_ID);
244  LoadTuple<JSON_TYPE, ARG_ID, JSON_TYPE::n_fields> load_tuple = LoadTuple<JSON_TYPE, ARG_ID, JSON_TYPE::n_fields>();
245  load_tuple.LoadJSDataArg(arg_var);
246  }
247 
248  // ----- StoreReturn -----
249  // Helper functions to individually store return values to JS
250 
251  // static void StoreReturn(const bool & ret_var) {
252  // EM_ASM_ARGS({ emp_i.cb_return = $0; }, ret_var);
253  // }
254 
255  static void StoreReturn(const int & ret_var) {
256  EM_ASM_ARGS({ emp_i.cb_return = $0; }, ret_var);
257  }
258 
259  static void StoreReturn(const double & ret_var) {
260  EM_ASM_ARGS({ emp_i.cb_return = $0; }, ret_var);
261  }
262 
263  static void StoreReturn(const std::string & ret_var) {
264  EM_ASM_ARGS({ emp_i.cb_return = Pointer_stringify($0); }, ret_var.c_str());
265  }
266 
267  template <typename T, size_t N>
268  static void StoreReturn(const emp::array<T, N> & ret_var) {
269  pass_array_to_javascript(ret_var);
270  EM_ASM({ emp_i.cb_return = emp_i.__incoming_array; });
271  }
272 
274  template <typename RETURN_TYPE>
276  StoreReturn(const RETURN_TYPE & ret_var) {
277  ret_var.template StoreAsReturn();
278  }
279 
281  static void StoreReturn(const int & ret_var, std::string var) {
282  EM_ASM_ARGS({ emp_i.curr_obj[Pointer_stringify($1)] = $0; }, ret_var, var.c_str());
283  }
284 
285  static void StoreReturn(const double & ret_var, std::string var) {
286  EM_ASM_ARGS({ emp_i.curr_obj[Pointer_stringify($1)] = $0; }, ret_var, var.c_str());
287  }
288 
289  static void StoreReturn(const std::string & ret_var, std::string var) {
290  EM_ASM_ARGS({ emp_i.curr_obj[Pointer_stringify($1)] = Pointer_stringify($0); }
291  , ret_var.c_str(), var.c_str());
292  }
293 
294  template <typename T, size_t N>
295  static void StoreReturn(const emp::array<T, N> & ret_var, std::string var) {
296  pass_array_to_javascript(ret_var);
297  EM_ASM_ARGS({ emp_i.curr_obj[Pointer_stringify($0)] = emp_i.__incoming_array;}, var.c_str());
298  }
299 
300  template <typename JSON_TYPE, int FIELD>
301  struct StoreTuple;
302 
303  // Tuple struct
304  template <typename RETURN_TYPE>
305  static typename std::enable_if<RETURN_TYPE::n_fields != -1, void>::type
306  StoreReturn(const RETURN_TYPE & ret_var) {
307  EM_ASM({
308  emp_i.cb_return = {};
309  emp_i.object_queue = [];
310  emp_i.curr_obj = emp_i.cb_return;
311  });
312 
313  StoreTuple<RETURN_TYPE, RETURN_TYPE::n_fields> store_tuple = StoreTuple<RETURN_TYPE, RETURN_TYPE::n_fields>();
314  store_tuple.StoreJSDataArg(ret_var);
315  }
316 
317  // Nested tuple struct
318  template <typename RETURN_TYPE>
320  StoreReturn(const RETURN_TYPE & ret_var, std::string var) {
321  EM_ASM_ARGS({
322  emp_i.curr_obj[Pointer_stringify($0)] = {};
323  emp_i.object_queue.push(emp_i.curr_obj);
324  emp_i.curr_obj = emp_i.curr_obj[Pointer_stringify($0)];
325  }, var.c_str());
326 
327  StoreTuple<RETURN_TYPE, RETURN_TYPE::n_fields> store_tuple = StoreTuple<RETURN_TYPE, RETURN_TYPE::n_fields>();
328  store_tuple.StoreJSDataArg(ret_var);
329  }
330 
331  template <typename JSON_TYPE, int FIELD>
332  struct StoreTuple {
333  static void StoreJSDataArg(const JSON_TYPE & ret_var) {
334  StoreReturn(std::get<FIELD-1>(ret_var.emp__tuple_body), ret_var.var_names[FIELD-1]);
335  StoreTuple<JSON_TYPE, FIELD-1> store_tuple = StoreTuple<JSON_TYPE, FIELD-1>();
336  store_tuple.StoreJSDataArg(ret_var);
337  }
338  };
339 
340  template <typename JSON_TYPE>
341  struct StoreTuple<JSON_TYPE, 0> {
342  static void StoreJSDataArg(const JSON_TYPE & ret_var) {
343  EM_ASM({emp_i.curr_obj = emp_i.object_queue.pop();});
344  }
345  };
346 
347  // The following code is in the "internal" namespace since it's used only to implement the
348  // details of the JSWrap function.
349 
350  namespace internal {
351 
352  template <typename T, int ARG_ID>
353  void LoadArg_impl(emp::sfinae_decoy<bool, decltype(&T::template LoadFromArg<ARG_ID>)>,
354  T & target) {
355  target.template LoadFromArg<ARG_ID>();
356  }
357  template <typename T, int ARG_ID>
358  void LoadArg_impl(int, T & target) {
359  LoadArg<ARG_ID>(target);
360  }
361 
362  // JSWrap_Callback_Base provides a base class for the wrappers around functions.
363  // Specifically, it creates a virtual DoCallback() member function that can be called
364  // to trigger a specific wrapped function.
365 
366  class JSWrap_Callback_Base {
367  protected:
368  bool is_disposable; // Should this callback be deleted automatically after first use?
369 
370  public:
371  JSWrap_Callback_Base(bool in_disposable=false) : is_disposable(in_disposable) { ; }
372  virtual ~JSWrap_Callback_Base() { ; }
373 
374  bool IsDisposable() const { return is_disposable; }
375  void SetDisposable() { is_disposable = true; }
376 
377  // Base class to be called from Javascript (after storing args) to do a callback.
378  virtual void DoCallback() = 0;
379 
380 
381  // A pair of helper functions that systematically load ALL arguments from JS.
382  template <typename TUPLE_TYPE, int ARGS_LEFT>
383  struct Collect_impl {
384  static void CollectArgs(TUPLE_TYPE & tuple) {
385  LoadArg_impl<typename std::tuple_element<ARGS_LEFT-1,TUPLE_TYPE>::type, ARGS_LEFT-1>( true, std::get<ARGS_LEFT-1>(tuple) ); // Load an arg
386  Collect_impl<TUPLE_TYPE, ARGS_LEFT-1>::CollectArgs(tuple); // Recurse to next arg
387  }
388  };
389 
390  template <typename TUPLE_TYPE>
391  struct Collect_impl<TUPLE_TYPE, 0> {
392  static void CollectArgs(TUPLE_TYPE & tuple) { (void) tuple; } // End load recursion.
393  };
394 
395  };
396 
397 
398  // The derived form of JSWrap_Callback knows the specific argument types of the function
399  // needed, keeps track of the function poninter, and has a tuple in which the arguments
400  // can be loaded before a call is made.
401 
402  template <typename RET_TYPE, typename... ARG_TYPES>
403  class JSWrap_Callback : public JSWrap_Callback_Base {
404  private:
405  std::function<RET_TYPE(ARG_TYPES...)> fun; // Function to be wrapped
406 
407  public:
408  JSWrap_Callback(const std::function<RET_TYPE(ARG_TYPES...)> & in_fun, bool disposable=false)
409  : JSWrap_Callback_Base(disposable), fun(in_fun)
410  {
411  EMP_TRACK_CONSTRUCT(JSWrap_Callback);
412  }
413  ~JSWrap_Callback() { EMP_TRACK_DESTRUCT(JSWrap_Callback); }
414 
415  // This function is called from Javascript. Arguments should be collected and then used
416  // to call the target function.
417  void DoCallback() {
418  const int num_args = sizeof...(ARG_TYPES);
419 
420  // Make sure that we are returning the correct number of arguments. If this
421  // assert fails, it means that we've failed to set the correct number of arguments
422  // in emp.cb_args, and need to realign.
423  emp_assert(EMP_GetCBArgCount() < 0 || EMP_GetCBArgCount() >= num_args, EMP_GetCBArgCount(), num_args);
424 
425  // Collect the values of the arguments in a tuple
426  using args_t = std::tuple< typename std::decay<ARG_TYPES>::type... >;
427  args_t args;
428  Collect_impl<args_t, num_args>::CollectArgs(args);
429 
430  // And finally, do the actual callback.
431 
432  RET_TYPE return_val;
433  emp::ApplyTuple([&return_val, this](ARG_TYPES... in_args){
434  return_val = fun(in_args...);
435  }, args);
436 
437  // And save the return value for JS.
438  StoreReturn(return_val);
439  }
440  };
441 
442  // A specialized version of the class that handles functions with void returns.
443 
444  template <typename... ARG_TYPES>
445  class JSWrap_Callback<void, ARG_TYPES...> : public JSWrap_Callback_Base {
446  private:
447  std::function<void(ARG_TYPES...)> fun; // Function to be wrapped
448 
449  public:
450  JSWrap_Callback(const std::function<void(ARG_TYPES...)> & in_fun, bool disposable=false)
451  : JSWrap_Callback_Base(disposable), fun(in_fun)
452  { EMP_TRACK_CONSTRUCT(JSWrap_Callback_VOID); }
453  ~JSWrap_Callback() { EMP_TRACK_DESTRUCT(JSWrap_Callback_VOID); }
454 
455 
456  // This function is called from Javascript. Arguments should be collected and then used
457  // to call the target function.
458  void DoCallback() {
459  const int num_args = sizeof...(ARG_TYPES);
460 
461  // Make sure that we are returning the correct number of arguments. If this
462  // assert fails, it means that we've failed to set the correct number of arguments
463  // in emp.cb_args, and need to realign.
464  emp_assert(EMP_GetCBArgCount() < 0 || EMP_GetCBArgCount() >= num_args, EMP_GetCBArgCount(), num_args);
465 
466  // Collect the values of the arguments in a tuple
467  using args_t = std::tuple< typename std::decay<ARG_TYPES>::type... >;
468  args_t args;
469  Collect_impl<args_t, num_args>::CollectArgs(args);
470 
471  // And finally, do the actual callback.
472  emp::ApplyTuple(fun, args);
473 
474  // And save a return value for JS.
475  StoreReturn(0);
476  }
477  };
478 
479 
480  // The following function returns a static callback array; callback ID's all index into
481  // this array.
482  static emp::vector<JSWrap_Callback_Base *> & CallbackArray() {
483  static emp::vector<JSWrap_Callback_Base *> callback_array(1, nullptr);
484  return callback_array;
485  }
486 
487  } // End internal namespace
488 
489  // The following JSWrap functions take a target function and return an integer id that
490  // indexes into a callback array.
491 
492  // The first version assumes that we already have it enclosed in an std::function, while
493  // the second version assumes we have a raw function pointer and wraps it for us.
494 
495  template <typename RET_TYPE, typename... ARG_TYPES>
496  size_t JSWrap(const std::function<RET_TYPE(ARG_TYPES...)> & in_fun,
497  const std::string & fun_name="",
498  bool dispose_on_use=false)
499  {
500  // We should never create disposible functions with names!
501  emp_assert(fun_name == "" || dispose_on_use == false);
502 
503  auto * new_cb =
504  new emp::internal::JSWrap_Callback<RET_TYPE, ARG_TYPES...>(in_fun, dispose_on_use);
505  auto & callback_array = internal::CallbackArray();
506  size_t out_id = callback_array.size();
507  callback_array.push_back(new_cb);
508 
509  if (fun_name != "") {
510  EM_ASM_ARGS({
511  var fun_name = Pointer_stringify($1);
512  emp[fun_name] = function() {
513  emp_i.cb_args = [];
514  for (var i = 0; i < arguments.length; i++) {
515  emp_i.cb_args[i] = arguments[i];
516  }
517 
518  // Callback to the original function.
519  empCppCallback($0);
520 
521  // Return the resulting value
522  return emp_i.cb_return;
523  };
524  }, out_id, fun_name.c_str());
525  }
526 
527  return out_id;
528  }
529 
530  template <typename RETURN_TYPE, typename... ARG_TYPES>
531  size_t JSWrap( RETURN_TYPE (*in_fun) (ARG_TYPES...),
532  const std::string & fun_name="", bool dispose_on_use=false )
533  {
534  std::function<RETURN_TYPE(ARG_TYPES...)> fun_ptr(in_fun);
535  return JSWrap(fun_ptr, fun_name, dispose_on_use);
536  }
537 
539 
540  template <typename FUN_TYPE>
541  size_t JSWrap(const FUN_TYPE & in_fun, const std::string & fun_name="", bool dispose_on_use=false)
542  {
543  return JSWrap(to_function(in_fun), fun_name, dispose_on_use);
544  }
545 
546 
548  template <typename FUN_TYPE>
549  size_t JSWrapOnce(FUN_TYPE && in_fun) { return JSWrap(std::forward<FUN_TYPE>(in_fun), "", true); }
550 
551 
553  void JSDelete( size_t fun_id ) {
554  emp_assert(fun_id > 0); // Make sure this isn't a null pointer!
555  // @CAO -- Should make sure to clean up named functions on JS side if they exist.
556  auto & callback_array = internal::CallbackArray();
557  delete callback_array[fun_id];
558  callback_array[fun_id] = nullptr;
559  }
560 
562 }
563 
566 
567 extern "C" void empCppCallback(size_t cb_id)
568 {
569  // Convert the uint passed in from 32 bits to 64 and THEN convert it to a pointer.
570  auto * cb_obj = emp::internal::CallbackArray()[cb_id];
571 
572  // Run DoCallback() on the generic base class type, which is virtual and will call
573  // the correct template automatically.
574  cb_obj->DoCallback();
575 
576  // If we have indicated that this callback is single use, delete it now.
577  if (cb_obj->IsDisposable()) {
578  delete cb_obj;
579  emp::internal::CallbackArray()[cb_id] = nullptr;
580  }
581 }
582 
584 
585 #endif
Definition: array.h:42
Define Initialize() and other functions to set up Empirical to build Emscripten projects.
REAL_TYPE sfinae_decoy
Definition: meta.h:93
void pass_array_to_cpp(emp::array< T, SIZE > &arr, bool recurse=false)
Definition: js_utils.h:299
void pass_vector_to_cpp(emp::vector< T > &arr, bool recurse=false)
Same as pass_array_to_cpp, but lets you store values in a vector instead.
Definition: js_utils.h:334
Tools for passing data between C++ and Javascript.
auto ApplyTuple(const FUN_T &fun, const TUPLE_T &tup, IntPack< N... >)
Definition: tuple_utils.h:37
If we are in emscripten, make sure to include the header.
Definition: array.h:37
#define emp_assert(...)
Definition: assert.h:199
void pass_array_to_javascript(C values)
Definition: js_utils.h:212
function_traits< Function >::function to_function(Function &lambda)
Definition: meta.h:289