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