[beast/ebeast: 14/24] V8: specialise v8pp::convert<> for all Aida::RemoteHandle derived types



commit ca8f4bc6f4bc25131b466b4b2b293f790cd6dbf9
Author: Tim Janik <timj gnu org>
Date:   Mon Feb 20 15:01:39 2017 +0100

    V8: specialise v8pp::convert<> for all Aida::RemoteHandle derived types
    
    Signed-off-by: Tim Janik <timj gnu org>

 ebeast/v8bse/V8Stub.py     |   20 +++++++-
 ebeast/v8bse/nodemodule.cc |  108 +++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 124 insertions(+), 4 deletions(-)
---
diff --git a/ebeast/v8bse/V8Stub.py b/ebeast/v8bse/V8Stub.py
index 265a113..d71aa45 100644
--- a/ebeast/v8bse/V8Stub.py
+++ b/ebeast/v8bse/V8Stub.py
@@ -95,10 +95,19 @@ class Generator:
         continue
       if tp.storage in (Decls.SEQUENCE, Decls.RECORD, Decls.INTERFACE):
         v8pp_class_types += [ tp ]
-    # C++ Classes
-    s += '\n// C++ Classes\n'
+    # C++ class type aliases for v8pp::class_
+    s += '\n// v8pp::class_ aliases\n'
     for tp in v8pp_class_types:
       s += 'typedef %-40s %s;\n' % ('v8pp::class_<%s>' % colon_typename (tp), v8ppclass_type (tp))
+    # C++ class specialisations for v8pp::convert
+    s += '\n// v8pp::convert<> specialisations\n'
+    s += 'namespace v8pp {\n'
+    for tp in v8pp_class_types:
+      if tp.storage == Decls.INTERFACE:
+        cn = colon_typename (tp)
+        s += 'template<> struct convert%-40s : convert_AidaRemoteHandle<%s>  {};\n' % ('<%s>' % cn, cn)
+        s += 'template<> struct convert%-40s : convert_AidaRemoteHandle<%s*> {};\n' % ('<%s*>' % cn, cn)
+    s += '} // v8pp\n'
     # V8stub - main binding stub
     s += '\n// Main binding stub\n'
     s += 'struct V8stub final {\n'
@@ -116,6 +125,13 @@ class Generator:
       s += '  %s (isolate),\n' % v8ppclass (tp)
     s += '  module_ (isolate)\n'
     s += '{\n'
+    # Wrapper registration
+    for tp in v8pp_class_types:
+      cn = colon_typename (tp)
+      if tp.storage == Decls.INTERFACE:
+        s += '  aida_remote_handle_wrapper_map (Aida::TypeHash '
+        s += '(%s), ' % class_digest (tp)
+        s += 'aida_remote_handle_wrapper_impl<%s>);\n' % cn
     # Class bindings
     for tp in v8pp_class_types:
       cn = colon_typename (tp)
diff --git a/ebeast/v8bse/nodemodule.cc b/ebeast/v8bse/nodemodule.cc
index dd9c3ab..2a9bb90 100644
--- a/ebeast/v8bse/nodemodule.cc
+++ b/ebeast/v8bse/nodemodule.cc
@@ -1,8 +1,112 @@
 // Licensed GNU LGPL v2.1 or later: http://www.gnu.org/licenses/lgpl.html
-#include "../bse.hh"
-#include "v8bse.cc"
+#include "../../bse/bse.hh"
 #include <node.h>
 #include <uv.h>
+#include <v8pp/class.hpp>
+#include <v8pp/convert.hpp>
+
+namespace Aida = Rapicorn::Aida;
+
+// == RemoteHandle Wrapping ==
+/* NOTE: A RemoteHandle is a smart pointer to a complex C++ object (possibly behind a thread boundary),
+ * so multiple RemoteHandle objects can point to the same C++ object. There are a couple ways that a
+ * RemoteHandle can be mapped onto Javascript:
+ * 1) A v8::Object contains a RemoteHandle, i.e. every function that returns a new RemoteHandle also
+ *    returns a new Object in JS, even if they all point to the same C++ impl. In this case it is
+ *    desirable that the JS Object cannot gain new properties (i.e. isSealed() === true).
+ *    A major downside here is that two JS Objects that point to the same C++ impl are not === equal.
+ * 2) All RemoteHandles that point to the same C++ impl are mapped onto a single JS Object of the
+ *    appropriate down-cast type. This correctly provides === equality and new properties added to
+ *    a JS Object are preseved whenever another RemoteHandles is mapped into a JS Object that points
+ *    to the same C++ impl.
+ *    Here, an extra map must be maintained to achieve the (n RemoteHandle) => (1 JS Object) mapping
+ *    by storing and looking up the orbid_ that defines the object identity each RemoteHandle points to.
+ *    The downside here is resource lockup. Once created, a JS Object must keep its RemoteHandle around
+ *    which forces the C++ impl to stay alive. And the v8::Persistent holding the JS Object map entry
+ *    must not be weak to prevent GC cycles from "forgetting" the properties stored on the JS Object.
+ * 3) A viable middle ground might be using the map from (2) so a JS Object provides correct equality
+ *    via ===, but the JS Objects are sealed as in (1) so we can use SetWeak persistents for the map
+ *    to avoid resource leaks.
+ */
+
+/// Function to wrap a RemoteHandle derived type via v8pp::class_<>::import_external().
+typedef v8::Local<v8::Object> (*AidaRemoteHandleWrapper) (v8::Isolate *const, Rapicorn::Aida::RemoteHandle);
+
+/// Default implementation to wrap a RemoteHandle derived type via v8pp::class_<>::import_external().
+template<class Native> static v8::Local<v8::Object>
+aida_remote_handle_wrapper_impl (v8::Isolate *const isolate, Aida::RemoteHandle rhandle)
+{
+  Native target = Native::down_cast (rhandle);
+  if (target != NULL)
+    return v8pp::class_<Native>::import_external (isolate, new Native (target));
+  return v8::Local<v8::Object>();
+}
+
+/// Map Aida type ids to the corresponding AidaRemoteHandleWrapper.
+static AidaRemoteHandleWrapper
+aida_remote_handle_wrapper_map (const Aida::TypeHash &thash, AidaRemoteHandleWrapper newfunc)
+{
+  static std::map<Aida::TypeHash, AidaRemoteHandleWrapper> wmap;
+  if (!newfunc)
+    return wmap[thash];
+  wmap[thash] = newfunc;
+  return NULL;
+}
+
+/// Create (or find) the corresponding down_cast() JS Object for a RemoteHandle.
+static v8::Local<v8::Object>
+aida_remote_handle_wrap_native (v8::Isolate *const isolate, Aida::RemoteHandle const &rhandle)
+{
+  v8::EscapableHandleScope scope (isolate);
+  if (NULL != rhandle)
+    {
+      Aida::TypeHashList thl = rhandle.__aida_typelist__();
+      for (const auto &th : thl)
+        {
+          AidaRemoteHandleWrapper wrapper = aida_remote_handle_wrapper_map (th, AidaRemoteHandleWrapper 
(NULL));
+          if (wrapper)
+            return scope.Escape (wrapper (isolate, rhandle));
+        }
+    }
+  return scope.Escape (v8::Local<v8::Object>());
+}
+
+/// Retrieve the native RemoteHandle from a JS Object.
+template<class NativeClass> static NativeClass&
+aida_remote_handle_unwrap_native (v8::Isolate *const isolate, v8::Local<v8::Value> value)
+{
+  v8::HandleScope scope (isolate);
+  NativeClass *nobject = NULL;
+  if (!value.IsEmpty() && value->IsObject())
+    nobject = v8pp::class_<NativeClass>::unwrap_object (isolate, value);
+  if (!nobject)
+    throw std::runtime_error ("failed to unwrap C++ Aida::RemoteHandle");
+  return *nobject;
+}
+
+/// Helper to specialize v8pp::convert<> for all RemoteHandle types.
+template<class DerivedHandle>
+struct convert_AidaRemoteHandle
+{
+  using N = DerivedHandle;              // native type, derived from Aida::RemoteHandle
+  using J = v8::Local<v8::Object>;      // Javascript type
+  static bool is_valid (v8::Isolate *const isolate, v8::Local<v8::Value> v) { return !v.IsEmpty() && 
v->IsObject(); }
+  static N&   from_v8  (v8::Isolate *const isolate, v8::Local<v8::Value> v) { return 
aida_remote_handle_unwrap_native<N> (isolate, v); }
+  static J    to_v8    (v8::Isolate *const isolate, const N &rhandle)       { return 
aida_remote_handle_wrap_native (isolate, rhandle); }
+};
+
+/// Helper for convert_AidaRemoteHandle pointer types.
+template<class DerivedHandle>
+struct convert_AidaRemoteHandle<DerivedHandle*>
+{
+  using N = DerivedHandle;              // native type, derived from Aida::RemoteHandle
+  using J = v8::Local<v8::Object>;      // Javascript type
+  static bool is_valid (v8::Isolate *const isolate, v8::Local<v8::Value> v) { return 
v8pp::convert<N>::is_valid (isolate, v); }
+  static N*   from_v8  (v8::Isolate *const isolate, v8::Local<v8::Value> v) { return 
&v8pp::convert<N>::from_v8 (isolate, v); }
+  static J    to_v8    (v8::Isolate *const isolate, const N *n)             { return !n ? J() : 
v8pp::convert<N>::to_v8 (isolate, *n); }
+};
+
+#include "v8bse.cc"
 
 // v8pp binding for Bse
 static V8stub *bse_v8stub = NULL;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]