Add QObject.parent() and a heap of wrapper management

This commit is contained in:
Simon Edwards 2022-05-01 11:08:37 +02:00
parent 7bf97ef618
commit cbb3f99dfa
9 changed files with 145 additions and 14 deletions

View File

@ -35,6 +35,10 @@ class DLL_EXPORT NAbstractItemModel : public QAbstractItemModel,
return *newIndex; return *newIndex;
} }
QObject *parent() const {
return nullptr;
}
QModelIndex parent(const QModelIndex& child) const override { QModelIndex parent(const QModelIndex& child) const override {
Napi::Env env = this->dispatchOnNode.Env(); Napi::Env env = this->dispatchOnNode.Env();
Napi::HandleScope scope(env); Napi::HandleScope scope(env);

View File

@ -5,6 +5,8 @@
#include "Extras/Utils/nutils.h" #include "Extras/Utils/nutils.h"
#include "QtCore/QVariant/qvariant_wrap.h" #include "QtCore/QVariant/qvariant_wrap.h"
#include "core/Events/eventwidget_macro.h" #include "core/Events/eventwidget_macro.h"
#include "core/WrapperCache/wrappercache.h"
/* /*
This macro adds common QObject exported methods This macro adds common QObject exported methods
@ -88,6 +90,15 @@
int id = info[0].As<Napi::Number>().Int32Value(); \ int id = info[0].As<Napi::Number>().Int32Value(); \
this->instance->killTimer(id); \ this->instance->killTimer(id); \
return env.Null(); \ return env.Null(); \
} \
Napi::Value parent(const Napi::CallbackInfo& info) { \
Napi::Env env = info.Env(); \
QObject *parent = this->instance->parent(); \
if (parent) { \
return WrapperCache::instance.getWrapper(env, parent); \
} else { \
return env.Null(); \
} \
} }
// Ideally this macro below should go in // Ideally this macro below should go in
@ -131,7 +142,8 @@
InstanceMethod("dumpObjectInfo", &ComponentWrapName::dumpObjectInfo), \ InstanceMethod("dumpObjectInfo", &ComponentWrapName::dumpObjectInfo), \
InstanceMethod("setParent", &ComponentWrapName::setParent), \ InstanceMethod("setParent", &ComponentWrapName::setParent), \
InstanceMethod("startTimer", &ComponentWrapName::startTimer), \ InstanceMethod("startTimer", &ComponentWrapName::startTimer), \
InstanceMethod("killTimer", &ComponentWrapName::killTimer), InstanceMethod("killTimer", &ComponentWrapName::killTimer), \
InstanceMethod("parent", &ComponentWrapName::parent),
#endif // QOBJECT_WRAPPED_METHODS_EXPORT_DEFINE #endif // QOBJECT_WRAPPED_METHODS_EXPORT_DEFINE

View File

@ -20,5 +20,6 @@ class DLL_EXPORT QObjectWrap : public Napi::ObjectWrap<QObjectWrap> {
NObject* getInternalInstance(); NObject* getInternalInstance();
// class constructor // class constructor
static Napi::FunctionReference constructor; static Napi::FunctionReference constructor;
static Napi::Object wrapFunc(Napi::Env env, QObject* qobject);
// wrapped methods // wrapped methods
}; };

View File

@ -14,6 +14,8 @@ struct CachedObject {
napi_env env; napi_env env;
}; };
typedef Napi::Object (*WrapFunc)(Napi::Env, QObject *);
/** /**
* C++ side cache for wrapper objects. * C++ side cache for wrapper objects.
* *
@ -26,6 +28,7 @@ class DLL_EXPORT WrapperCache : public QObject {
private: private:
QMap<uint64_t, CachedObject> cache; QMap<uint64_t, CachedObject> cache;
QMap<QString, WrapFunc> wrapperRegistry;
public: public:
/** /**
@ -64,6 +67,35 @@ class DLL_EXPORT WrapperCache : public QObject {
return wrapper; return wrapper;
} }
void registerWrapper(QString typeName, WrapFunc wrapFunc) {
this->wrapperRegistry[typeName] = wrapFunc;
}
Napi::Value getWrapper(Napi::Env env, QObject* qobject) {
if (qobject == nullptr) {
return env.Null();
}
uint64_t ptrHash = extrautils::hashPointerTo53bit(qobject);
if (this->cache.contains(ptrHash)) {
napi_value result = nullptr;
napi_get_reference_value(env, this->cache[ptrHash].ref, &result);
napi_valuetype valuetype;
napi_typeof(env, result, &valuetype);
if (valuetype != napi_null) {
return Napi::Object(env, result);
}
}
// QString className(object->metaObject()->className());
// if (this->wrapperRegistry.contains(className)) {
// this->wrapperRegistry[className]
// }
return env.Null();
}
/** /**
* Store a mapping from Qt Object to wrapper * Store a mapping from Qt Object to wrapper
* *
@ -89,8 +121,8 @@ class DLL_EXPORT WrapperCache : public QObject {
static Napi::Object init(Napi::Env env, Napi::Object exports) { static Napi::Object init(Napi::Env env, Napi::Object exports) {
exports.Set("WrapperCache_injectCallback", exports.Set("WrapperCache_injectCallback",
Napi::Function::New<injectDestroyCallback>(env)); Napi::Function::New<injectDestroyCallback>(env));
// exports.Set("WrapperCache_storeJS", exports.Set("WrapperCache_store",
// Napi::Function::New<storeJS>(env)); Napi::Function::New<storeJS>(env));
return exports; return exports;
} }
@ -101,6 +133,17 @@ class DLL_EXPORT WrapperCache : public QObject {
return env.Null(); return env.Null();
} }
static Napi::Value storeJS(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::Object objectWrapper = info[0].As<Napi::Object>();
QObject* qobject = info[1].As<Napi::External<QObject>>().Data();
uint64_t ptrHash = extrautils::hashPointerTo53bit(qobject);
instance.store(env, ptrHash, qobject, objectWrapper, false);
return env.Null();
}
static Napi::FunctionReference destroyedCallback; static Napi::FunctionReference destroyedCallback;
public Q_SLOTS: public Q_SLOTS:

View File

@ -1,6 +1,7 @@
#include "QtCore/QObject/qobject_wrap.h" #include "QtCore/QObject/qobject_wrap.h"
#include "Extras/Utils/nutils.h" #include "Extras/Utils/nutils.h"
#include "core/WrapperCache/wrappercache.h"
Napi::FunctionReference QObjectWrap::constructor; Napi::FunctionReference QObjectWrap::constructor;
@ -11,6 +12,7 @@ Napi::Object QObjectWrap::init(Napi::Env env, Napi::Object exports) {
env, CLASSNAME, {QOBJECT_WRAPPED_METHODS_EXPORT_DEFINE(QObjectWrap)}); env, CLASSNAME, {QOBJECT_WRAPPED_METHODS_EXPORT_DEFINE(QObjectWrap)});
constructor = Napi::Persistent(func); constructor = Napi::Persistent(func);
exports.Set(CLASSNAME, func); exports.Set(CLASSNAME, func);
WrapperCache::instance.registerWrapper(QString("NObject"), QObjectWrap::wrapFunc);
return exports; return exports;
} }
@ -37,4 +39,11 @@ QObjectWrap::QObjectWrap(const Napi::CallbackInfo& info)
.ThrowAsJavaScriptException(); .ThrowAsJavaScriptException();
} }
this->rawData = extrautils::configureQObject(this->getInternalInstance()); this->rawData = extrautils::configureQObject(this->getInternalInstance());
// WrapperCache::instance.store<QObject, QObjectWrap>(env, this->getInternalInstance(), this);
}
Napi::Object QObjectWrap::wrapFunc(Napi::Env env, QObject *qobject) {
// Qtype *exactQObject = dynamic_cast<Qtype*>(qobject)
Napi::Object wrapper = QObjectWrap::constructor.New({Napi::External<QObject>::New(env, qobject)});
return wrapper;
} }

View File

@ -4,6 +4,7 @@ import { checkIfNativeElement } from '../utils/helpers';
import addon from '../utils/addon'; import addon from '../utils/addon';
import { QVariant, QVariantType } from './QVariant'; import { QVariant, QVariantType } from './QVariant';
import { TimerType } from '../QtEnums/TimerType'; import { TimerType } from '../QtEnums/TimerType';
import { wrapperCache } from '../core/WrapperCache';
export class QObject<Signals extends QObjectSignals = QObjectSignals> extends EventWidget<Signals> { export class QObject<Signals extends QObjectSignals = QObjectSignals> extends EventWidget<Signals> {
constructor(nativeElementOrParent?: NativeElement | QObject) { constructor(nativeElementOrParent?: NativeElement | QObject) {
@ -18,6 +19,9 @@ export class QObject<Signals extends QObjectSignals = QObjectSignals> extends Ev
native = new addon.QObject(); native = new addon.QObject();
} }
super(native); super(native);
wrapperCache.store(this);
this.setNodeParent(parent); this.setNodeParent(parent);
} }
@ -51,6 +55,9 @@ export class QObject<Signals extends QObjectSignals = QObjectSignals> extends Ev
this.native.setParent(null); this.native.setParent(null);
} }
} }
parent(): QObject {
return wrapperCache.getWrapper(this.native.parent());
}
startTimer(intervalMS: number, timerType = TimerType.CoarseTimer): number { startTimer(intervalMS: number, timerType = TimerType.CoarseTimer): number {
return this.native.startTimer(intervalMS, timerType); return this.native.startTimer(intervalMS, timerType);
} }

View File

@ -13,29 +13,68 @@ import { NativeElement } from './Component';
* wrapper automatically and unexpectedly garbage collected. * wrapper automatically and unexpectedly garbage collected.
*/ */
export class WrapperCache { export class WrapperCache {
private _cache = new Map<number, any>(); private _strongCache = new Map<number, QObject>();
private _weakCache = new Map<number, WeakRef<QObject>>();
constructor() { constructor() {
addon.WrapperCache_injectCallback(this._objectDestroyedCallback.bind(this)); addon.WrapperCache_injectCallback(this._objectDestroyedCallback.bind(this));
} }
private _objectDestroyedCallback(objectId: number): void { private _objectDestroyedCallback(objectId: number): void {
if (!this._cache.has(objectId)) { if (this._strongCache.has(objectId)) {
return; const wrapper = this._strongCache.get(objectId);
wrapper.native = null;
this._strongCache.delete(objectId);
}
const wrapperRef = this._weakCache.get(objectId);
if (wrapperRef != null) {
const wrapper = wrapperRef.deref();
if (wrapper != null) {
wrapper.native = null;
this._weakCache.delete(objectId);
}
} }
const wrapper = this._cache.get(objectId);
wrapper.native = null;
this._cache.delete(objectId);
} }
get<T>(wrapperConstructor: { new (native: any): T }, native: NativeElement): T { get<T extends QObject>(wrapperConstructor: { new (native: any): T }, native: NativeElement): T {
const id = native.__id__(); const id = native.__id__();
if (this._cache.has(id)) { if (this._strongCache.has(id)) {
return this._cache.get(id) as T; return this._strongCache.get(id) as T;
} }
const wrapper = new wrapperConstructor(native); const wrapper = new wrapperConstructor(native);
this._cache.set(id, wrapper); this._strongCache.set(id, wrapper);
return wrapper; return wrapper;
} }
getWrapper(native: any): QObject | null {
if (native == null) {
return null;
}
const id = native.__id__();
if (this._strongCache.has(id)) {
return this._strongCache.get(id);
}
const ref = this._weakCache.get(id);
if (ref != null) {
const wrapper = ref.deref();
if (wrapper != null) {
return wrapper;
}
}
return null; // FIXME: Create new wrapper on demand.
}
store(wrapper: QObject): void {
if (wrapper.native != null) {
const id = wrapper.native.__id__();
this._weakCache.set(id, new WeakRef<QObject>(wrapper));
addon.WrapperCache_store(wrapper.native, wrapper.native.__external_qobject__());
}
}
} }
export const wrapperCache = new WrapperCache(); export const wrapperCache = new WrapperCache();

View File

@ -1,3 +1,4 @@
import { QObject } from '../../QtCore/QObject';
import { QApplication } from '../../QtGui/QApplication'; import { QApplication } from '../../QtGui/QApplication';
import { CacheTestQObject } from './CacheTestQObject'; import { CacheTestQObject } from './CacheTestQObject';
@ -43,5 +44,20 @@ describe('WrapperCache using CacheTestQObject', () => {
expect(foo).not.toEqual(bar); expect(foo).not.toEqual(bar);
expect(foo.native.__id__()).not.toEqual(bar.native.__id__()); expect(foo.native.__id__()).not.toEqual(bar.native.__id__());
}); });
it('QObject.parent() can be null', () => {
const a = new QObject();
expect(a.parent()).toBeNull();
});
it('QObject.parent() === QObject.parent()', () => {
const a = new QObject();
const b = new QObject(a);
expect(a.native.__id__()).toEqual(b.parent().native.__id__());
expect(a).toEqual(b.parent());
(<any>a)['magic'] = true;
expect((<any>b.parent())['magic']).toBe(true);
});
qApp.quit(); qApp.quit();
}); });

View File

@ -1,6 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2015", "target": "ES2021",
"module": "commonjs", "module": "commonjs",
"declaration": true, "declaration": true,
"sourceMap": false, "sourceMap": false,