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;
}
QObject *parent() const {
return nullptr;
}
QModelIndex parent(const QModelIndex& child) const override {
Napi::Env env = this->dispatchOnNode.Env();
Napi::HandleScope scope(env);

View File

@ -5,6 +5,8 @@
#include "Extras/Utils/nutils.h"
#include "QtCore/QVariant/qvariant_wrap.h"
#include "core/Events/eventwidget_macro.h"
#include "core/WrapperCache/wrappercache.h"
/*
This macro adds common QObject exported methods
@ -88,6 +90,15 @@
int id = info[0].As<Napi::Number>().Int32Value(); \
this->instance->killTimer(id); \
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
@ -131,7 +142,8 @@
InstanceMethod("dumpObjectInfo", &ComponentWrapName::dumpObjectInfo), \
InstanceMethod("setParent", &ComponentWrapName::setParent), \
InstanceMethod("startTimer", &ComponentWrapName::startTimer), \
InstanceMethod("killTimer", &ComponentWrapName::killTimer),
InstanceMethod("killTimer", &ComponentWrapName::killTimer), \
InstanceMethod("parent", &ComponentWrapName::parent),
#endif // QOBJECT_WRAPPED_METHODS_EXPORT_DEFINE

View File

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

View File

@ -14,6 +14,8 @@ struct CachedObject {
napi_env env;
};
typedef Napi::Object (*WrapFunc)(Napi::Env, QObject *);
/**
* C++ side cache for wrapper objects.
*
@ -26,6 +28,7 @@ class DLL_EXPORT WrapperCache : public QObject {
private:
QMap<uint64_t, CachedObject> cache;
QMap<QString, WrapFunc> wrapperRegistry;
public:
/**
@ -64,6 +67,35 @@ class DLL_EXPORT WrapperCache : public QObject {
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
*
@ -89,8 +121,8 @@ class DLL_EXPORT WrapperCache : public QObject {
static Napi::Object init(Napi::Env env, Napi::Object exports) {
exports.Set("WrapperCache_injectCallback",
Napi::Function::New<injectDestroyCallback>(env));
// exports.Set("WrapperCache_storeJS",
// Napi::Function::New<storeJS>(env));
exports.Set("WrapperCache_store",
Napi::Function::New<storeJS>(env));
return exports;
}
@ -101,6 +133,17 @@ class DLL_EXPORT WrapperCache : public QObject {
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;
public Q_SLOTS:

View File

@ -1,6 +1,7 @@
#include "QtCore/QObject/qobject_wrap.h"
#include "Extras/Utils/nutils.h"
#include "core/WrapperCache/wrappercache.h"
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)});
constructor = Napi::Persistent(func);
exports.Set(CLASSNAME, func);
WrapperCache::instance.registerWrapper(QString("NObject"), QObjectWrap::wrapFunc);
return exports;
}
@ -37,4 +39,11 @@ QObjectWrap::QObjectWrap(const Napi::CallbackInfo& info)
.ThrowAsJavaScriptException();
}
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 { QVariant, QVariantType } from './QVariant';
import { TimerType } from '../QtEnums/TimerType';
import { wrapperCache } from '../core/WrapperCache';
export class QObject<Signals extends QObjectSignals = QObjectSignals> extends EventWidget<Signals> {
constructor(nativeElementOrParent?: NativeElement | QObject) {
@ -18,6 +19,9 @@ export class QObject<Signals extends QObjectSignals = QObjectSignals> extends Ev
native = new addon.QObject();
}
super(native);
wrapperCache.store(this);
this.setNodeParent(parent);
}
@ -51,6 +55,9 @@ export class QObject<Signals extends QObjectSignals = QObjectSignals> extends Ev
this.native.setParent(null);
}
}
parent(): QObject {
return wrapperCache.getWrapper(this.native.parent());
}
startTimer(intervalMS: number, timerType = TimerType.CoarseTimer): number {
return this.native.startTimer(intervalMS, timerType);
}

View File

@ -13,29 +13,68 @@ import { NativeElement } from './Component';
* wrapper automatically and unexpectedly garbage collected.
*/
export class WrapperCache {
private _cache = new Map<number, any>();
private _strongCache = new Map<number, QObject>();
private _weakCache = new Map<number, WeakRef<QObject>>();
constructor() {
addon.WrapperCache_injectCallback(this._objectDestroyedCallback.bind(this));
}
private _objectDestroyedCallback(objectId: number): void {
if (!this._cache.has(objectId)) {
return;
if (this._strongCache.has(objectId)) {
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__();
if (this._cache.has(id)) {
return this._cache.get(id) as T;
if (this._strongCache.has(id)) {
return this._strongCache.get(id) as T;
}
const wrapper = new wrapperConstructor(native);
this._cache.set(id, wrapper);
this._strongCache.set(id, 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();

View File

@ -1,3 +1,4 @@
import { QObject } from '../../QtCore/QObject';
import { QApplication } from '../../QtGui/QApplication';
import { CacheTestQObject } from './CacheTestQObject';
@ -43,5 +44,20 @@ describe('WrapperCache using CacheTestQObject', () => {
expect(foo).not.toEqual(bar);
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();
});

View File

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