Overte C++ Documentation
ScriptObjectV8Proxy.h
1 //
2 // ScriptObjectV8Proxy.h
3 // libraries/script-engine/src/v8
4 //
5 // Created by Heather Anderson on 12/5/21.
6 // Modified for V8 by dr Karol Suprynowicz on 2022/10/08
7 // Copyright 2021 Vircadia contributors.
8 // Copyright 2022-2023 Overte e.V.
9 //
10 // Distributed under the Apache License, Version 2.0.
11 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
12 // SPDX-License-Identifier: Apache-2.0
13 //
14 
17 
18 #ifndef hifi_ScriptObjectV8Proxy_h
19 #define hifi_ScriptObjectV8Proxy_h
20 
21 #include <QtCore/QHash>
22 #include <QtCore/QList>
23 #include <QtCore/QPointer>
24 #include <QtCore/QString>
25 
26 #include "../ScriptEngine.h"
27 #include "../Scriptable.h"
28 #include "ScriptEngineV8.h"
29 #include "V8Types.h"
30 
31 #include <shared/ReadWriteLockable.h>
32 
33 class ScriptEngineV8;
34 class ScriptSignalV8Proxy;
35 
36 // V8TODO: Current implementation relies on weak handle callbacks for destroying objects on C++ side
37 // this is fine for avoiding memory leaks while script engine runs, but there's no guarantee that these will be called
38 // when script engine shuts down, so memory leaks may happen
39 // To avoid this handle visitor needs to be added (it's a feature of V8)
40 
43 class ScriptObjectV8Proxy final {
44 private: // implementation
45  class PropertyDef {
46  public:
47  PropertyDef(QString string, uint id) : name(string), _id(id) {};
48  QString name;
49  ScriptValue::PropertyFlags flags;
50  uint _id;
51  };
52  class MethodDef {
53  public:
54  MethodDef(QString string, uint id) : name(string), _id(id) {};
55  QString name;
56  int numMaxParams;
57  QList<QMetaMethod> methods;
58  uint _id;
59  };
60  class SignalDef {
61  public:
62  SignalDef(QString string, uint id) : name(string), _id(id) {};
63  QString name;
64  QMetaMethod signal;
65  uint _id;
66  };
67  using PropertyDefMap = QHash<uint, PropertyDef>;
68  using MethodDefMap = QHash<uint, MethodDef>;
69  using SignalDefMap = QHash<uint, SignalDef>;
70  using InstanceMap = QHash<uint, QPointer<ScriptSignalV8Proxy> >;
71  using PropertyNameMap = QHash<QString, PropertyDef*>;
72  using MethodNameMap = QHash<QString, MethodDef*>;
73  using SignalNameMap = QHash<QString, SignalDef*>;
74 
75  static constexpr uint PROPERTY_TYPE = 0x1000;
76  static constexpr uint METHOD_TYPE = 0x2000;
77  static constexpr uint SIGNAL_TYPE = 0x3000;
78  static constexpr uint TYPE_MASK = 0xF000;
79 
80 public: // construction
81  ScriptObjectV8Proxy(ScriptEngineV8* engine, QObject* object, bool ownsObject, const ScriptEngine::QObjectWrapOptions& options);
82  virtual ~ScriptObjectV8Proxy();
83 
84  static V8ScriptValue newQObject(ScriptEngineV8* engine,
85  QObject* object,
87  const ScriptEngine::QObjectWrapOptions& options = ScriptEngine::QObjectWrapOptions());
88  static ScriptObjectV8Proxy* unwrapProxy(const V8ScriptValue& val);
89  static ScriptObjectV8Proxy* unwrapProxy(v8::Isolate* isolate, v8::Local<v8::Value>& value);
90  static QObject* unwrap(const V8ScriptValue& val);
91  inline QObject* toQObject() const { return _object; }
92  inline v8::Local<v8::Object> toV8Value() const {
93  v8::EscapableHandleScope handleScope(_engine->getIsolate());
94  return handleScope.Escape(_v8Object.Get(_engine->getIsolate()));
95  }
96 
97 public:
98  enum QueryFlag
99  {
100  HandlesReadAccess = 0x00000001,
101  HandlesWriteAccess = 0x00000002,
102  };
103  Q_DECLARE_FLAGS(QueryFlags, QueryFlag);
104 
105  virtual QString name() const;
106 
107  virtual V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id);
108  virtual ScriptValue::PropertyFlags propertyFlags(const V8ScriptValue& object, const V8ScriptString& name, uint id);
109  virtual QueryFlags queryProperty(const V8ScriptValue& object, const V8ScriptString& name, QueryFlags flags, uint* id);
110  virtual void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value);
111  v8::Local<v8::Array> getPropertyNames();
112  static void v8Get(v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info);
113  static void v8Set(v8::Local<v8::Name> name, v8::Local<v8::Value> value_obj, const v8::PropertyCallbackInfo<v8::Value>& info);
114  static void v8GetPropertyNames(const v8::PropertyCallbackInfo<v8::Array>& info);
115 
116 private: // implementation
117  void investigate();
118  // This gets called when script-owned object is being garbage-collected
119  static void weakHandleCallback(const v8::WeakCallbackInfo<ScriptObjectV8Proxy> &info);
120 
121 private: // storage
122  ScriptEngineV8* _engine;
123  const ScriptEngine::QObjectWrapOptions _wrapOptions;
124  PropertyDefMap _props;
125  MethodDefMap _methods;
126  SignalDefMap _signals;
127  // These are used for property lookups from V8 callbacks
128  PropertyNameMap _propNameMap;
129  MethodNameMap _methodNameMap;
130  SignalNameMap _signalNameMap;
131  InstanceMap _signalInstances;
132  const bool _ownsObject;
133  QPointer<QObject> _object;
134  // Handle for its own object
135  v8::Persistent<v8::Object> _v8Object;
136 
137  Q_DISABLE_COPY(ScriptObjectV8Proxy)
138 };
139 
153  // V8TODO: there may be memory leaks in these, it's worth checking if the proxy actually gets garbage-collected and destroyed
154 class ScriptVariantV8Proxy final {
155 public: // construction
156  ScriptVariantV8Proxy(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue scriptProto, ScriptObjectV8Proxy* proto);
158 
159  static V8ScriptValue newVariant(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue proto);
160  static ScriptVariantV8Proxy* unwrapProxy(const V8ScriptValue& val);
161  static ScriptVariantV8Proxy* unwrapProxy(v8::Isolate* isolate, v8::Local<v8::Value> &value);
166  static QVariant* unwrapQVariantPointer(v8::Isolate* isolate, const v8::Local<v8::Value> &value);
167  static QVariant unwrap(const V8ScriptValue& val);
168  inline QVariant toQVariant() const { return _variant; }
169  inline v8::Local<v8::Object> toV8Value() const {
170  v8::EscapableHandleScope handleScope(_engine->getIsolate());
171  return handleScope.Escape(_v8Object.Get(_engine->getIsolate()));
172  }
173 
174 public: // QScriptClass implementation
175  virtual QString name() const { return _name; }
176 
177  virtual V8ScriptValue prototype() const { return _scriptProto; }
178 
179  virtual V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) {
180  return _proto->property(object, name, id);
181  }
182  virtual ScriptValue::PropertyFlags propertyFlags(const V8ScriptValue& object, const V8ScriptString& name, uint id) {
183  return _proto->propertyFlags(object, name, id);
184  }
185  virtual void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) {
186  return _proto->setProperty(object, name, id, value);
187  }
188  static void v8Get(v8::Local<v8::Name> name, const v8::PropertyCallbackInfo<v8::Value>& info);
189  static void v8Set(v8::Local<v8::Name> name, v8::Local<v8::Value> value_obj, const v8::PropertyCallbackInfo<v8::Value>& info);
190  static void v8GetPropertyNames(const v8::PropertyCallbackInfo<v8::Array>& info);
191 
192 private:
193  ScriptEngineV8* _engine;
194  QVariant _variant;
195  V8ScriptValue _scriptProto;
196  ScriptObjectV8Proxy* _proto;
197  QString _name;
198  v8::UniquePersistent<v8::Object> _v8Object;
199 
200  Q_DISABLE_COPY(ScriptVariantV8Proxy)
201 };
202 
203 class ScriptMethodV8Proxy final : public QObject {
204  Q_OBJECT
205 public: // construction
206  ScriptMethodV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime,
207  const QList<QMetaMethod>& metas, int numMaxParams);
208  virtual ~ScriptMethodV8Proxy();
209 
210 public: // QScriptClass implementation
211  virtual QString name() const { return fullName(); }
212  static void callback(const v8::FunctionCallbackInfo<v8::Value>& arguments);
213  void call(const v8::FunctionCallbackInfo<v8::Value>& arguments);
214  static V8ScriptValue newMethod(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime,
215  const QList<QMetaMethod>& metas, int numMaxParams);
216 
217 private:
218  static void weakHandleCallback(const v8::WeakCallbackInfo<ScriptMethodV8Proxy> &info);
219  QString fullName() const;
220 
221 private: // storage
222  const int _numMaxParams;
223  ScriptEngineV8* _engine;
224  QPointer<QObject> _object;
225  v8::Persistent<v8::Value> _objectLifetime;
226  //V8ScriptValue _objectLifetime;
227  const QList<QMetaMethod> _metas;
228 
229  Q_DISABLE_COPY(ScriptMethodV8Proxy)
230 };
231 
232 // This abstract base class serves solely to declare the Q_INVOKABLE methods for ScriptSignalV8Proxy
233 // as we're overriding qt_metacall later for the signal callback yet still want to support
234 // metacalls for the connect/disconnect API
235 class ScriptSignalV8ProxyBase : public QObject, protected Scriptable {
236  Q_OBJECT
237 public: // API
238  // arg1 was had Null default value, but that needs isolate pointer in V8
239  Q_INVOKABLE virtual void connect(ScriptValue arg0, ScriptValue arg1 = ScriptValue()) = 0;
240  Q_INVOKABLE virtual void disconnect(ScriptValue arg0, ScriptValue arg1 = ScriptValue()) = 0;
241 };
242 
243 class ScriptSignalV8Proxy final : public ScriptSignalV8ProxyBase, public ReadWriteLockable {
244 private: // storage
245  class Connection {
246  public:
247  V8ScriptValue thisValue;
248  V8ScriptValue callback;
249  Connection(const V8ScriptValue &v8ThisValue, const V8ScriptValue &v8Callback) :
250  thisValue(v8ThisValue), callback(v8Callback) {};
251  };
252  using ConnectionList = QList<Connection>;
253 
254 public: // construction
255  inline ScriptSignalV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, const QMetaMethod& meta);
256 
257  ~ScriptSignalV8Proxy();
258 
259 private: // implementation
260  virtual int qt_metacall(QMetaObject::Call call, int id, void** arguments) override;
261  int discoverMetaCallIdx();
262  ConnectionList::iterator findConnection(V8ScriptValue thisObject, V8ScriptValue callback);
263  //QString fullName() const;
264  static void weakHandleCallback(const v8::WeakCallbackInfo<ScriptSignalV8Proxy> &info);
265 
266 public: // API
267  // arg1 was had Null default value, but that needs isolate pointer to create Null in V8
268  virtual void connect(ScriptValue arg0, ScriptValue arg1 = ScriptValue()) override;
269  virtual void disconnect(ScriptValue arg0, ScriptValue arg1 = ScriptValue()) override;
270  //Moved to public temporarily for debugging:
271  QString fullName() const;
272 
273  // Disconnects all signals from the proxy
274  void disconnectAll() { QObject::disconnect(this, nullptr, nullptr, nullptr); };
275 
276 private: // storage
277 
278  ScriptEngineV8* _engine;
279  QPointer<QObject> _object;
280  v8::Persistent<v8::Value> _objectLifetime;
281 
282  const QMetaMethod _meta;
283  const int _metaCallId;
284  ConnectionList _connections;
285  bool _isConnected{ false };
286  // Context in which it was created
287  v8::UniquePersistent<v8::Context> _v8Context;
288  // Call counter for debugging purposes. It can be used to determine which signals are overwhelming script engine.
289  int _callCounter{0};
290  float _totalCallTime_s{ 0.0 };
291 
292  Q_DISABLE_COPY(ScriptSignalV8Proxy)
293 };
294 
295 #endif // hifi_ScriptObjectV8Proxy_h
296 
ValueOwnership
Who owns a given object.
Definition: ScriptEngine.h:109
@ QtOwnership
Object is managed by Qt.
Definition: ScriptEngine.h:114
Definition: ScriptObjectV8Proxy.h:43
[ScriptInterface] Provides an engine-independent interface for QScriptValue
Definition: ScriptValue.h:40
[V8] (re-)implements the translation layer between ScriptValue and QVariant where a prototype is set.
Definition: ScriptObjectV8Proxy.h:154
static QVariant * unwrapQVariantPointer(v8::Isolate *isolate, const v8::Local< v8::Value > &value)
Used to retrieve QVariant pointer contained inside script value. This is indirectly used by ScriptVar...
Definition: ScriptObjectV8Proxy.cpp:793
[ScriptInterface] Provides an engine-independent interface for QScriptable
Definition: Scriptable.h:29