Add QObject.parent() and a heap of wrapper management
This commit is contained in:
parent
7bf97ef618
commit
cbb3f99dfa
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2015",
|
||||
"target": "ES2021",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"sourceMap": false,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user