Skip to content

Commit cbb3f99

Browse files
committed
Add QObject.parent() and a heap of wrapper management
1 parent 7bf97ef commit cbb3f99

9 files changed

Lines changed: 145 additions & 14 deletions

File tree

src/cpp/include/nodegui/QtCore/QAbstractItemModel/nabstractitemmodel.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ class DLL_EXPORT NAbstractItemModel : public QAbstractItemModel,
3535
return *newIndex;
3636
}
3737

38+
QObject *parent() const {
39+
return nullptr;
40+
}
41+
3842
QModelIndex parent(const QModelIndex& child) const override {
3943
Napi::Env env = this->dispatchOnNode.Env();
4044
Napi::HandleScope scope(env);

src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#include "Extras/Utils/nutils.h"
66
#include "QtCore/QVariant/qvariant_wrap.h"
77
#include "core/Events/eventwidget_macro.h"
8+
#include "core/WrapperCache/wrappercache.h"
9+
810
/*
911
1012
This macro adds common QObject exported methods
@@ -88,6 +90,15 @@
8890
int id = info[0].As<Napi::Number>().Int32Value(); \
8991
this->instance->killTimer(id); \
9092
return env.Null(); \
93+
} \
94+
Napi::Value parent(const Napi::CallbackInfo& info) { \
95+
Napi::Env env = info.Env(); \
96+
QObject *parent = this->instance->parent(); \
97+
if (parent) { \
98+
return WrapperCache::instance.getWrapper(env, parent); \
99+
} else { \
100+
return env.Null(); \
101+
} \
91102
}
92103

93104
// Ideally this macro below should go in
@@ -131,7 +142,8 @@
131142
InstanceMethod("dumpObjectInfo", &ComponentWrapName::dumpObjectInfo), \
132143
InstanceMethod("setParent", &ComponentWrapName::setParent), \
133144
InstanceMethod("startTimer", &ComponentWrapName::startTimer), \
134-
InstanceMethod("killTimer", &ComponentWrapName::killTimer),
145+
InstanceMethod("killTimer", &ComponentWrapName::killTimer), \
146+
InstanceMethod("parent", &ComponentWrapName::parent),
135147

136148
#endif // QOBJECT_WRAPPED_METHODS_EXPORT_DEFINE
137149

src/cpp/include/nodegui/QtCore/QObject/qobject_wrap.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ class DLL_EXPORT QObjectWrap : public Napi::ObjectWrap<QObjectWrap> {
2020
NObject* getInternalInstance();
2121
// class constructor
2222
static Napi::FunctionReference constructor;
23+
static Napi::Object wrapFunc(Napi::Env env, QObject* qobject);
2324
// wrapped methods
2425
};

src/cpp/include/nodegui/core/WrapperCache/wrappercache.h

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ struct CachedObject {
1414
napi_env env;
1515
};
1616

17+
typedef Napi::Object (*WrapFunc)(Napi::Env, QObject *);
18+
1719
/**
1820
* C++ side cache for wrapper objects.
1921
*
@@ -26,6 +28,7 @@ class DLL_EXPORT WrapperCache : public QObject {
2628

2729
private:
2830
QMap<uint64_t, CachedObject> cache;
31+
QMap<QString, WrapFunc> wrapperRegistry;
2932

3033
public:
3134
/**
@@ -64,6 +67,35 @@ class DLL_EXPORT WrapperCache : public QObject {
6467
return wrapper;
6568
}
6669

70+
void registerWrapper(QString typeName, WrapFunc wrapFunc) {
71+
this->wrapperRegistry[typeName] = wrapFunc;
72+
}
73+
74+
Napi::Value getWrapper(Napi::Env env, QObject* qobject) {
75+
if (qobject == nullptr) {
76+
return env.Null();
77+
}
78+
79+
uint64_t ptrHash = extrautils::hashPointerTo53bit(qobject);
80+
if (this->cache.contains(ptrHash)) {
81+
napi_value result = nullptr;
82+
napi_get_reference_value(env, this->cache[ptrHash].ref, &result);
83+
84+
napi_valuetype valuetype;
85+
napi_typeof(env, result, &valuetype);
86+
if (valuetype != napi_null) {
87+
return Napi::Object(env, result);
88+
}
89+
}
90+
91+
// QString className(object->metaObject()->className());
92+
// if (this->wrapperRegistry.contains(className)) {
93+
// this->wrapperRegistry[className]
94+
// }
95+
96+
return env.Null();
97+
}
98+
6799
/**
68100
* Store a mapping from Qt Object to wrapper
69101
*
@@ -89,8 +121,8 @@ class DLL_EXPORT WrapperCache : public QObject {
89121
static Napi::Object init(Napi::Env env, Napi::Object exports) {
90122
exports.Set("WrapperCache_injectCallback",
91123
Napi::Function::New<injectDestroyCallback>(env));
92-
// exports.Set("WrapperCache_storeJS",
93-
// Napi::Function::New<storeJS>(env));
124+
exports.Set("WrapperCache_store",
125+
Napi::Function::New<storeJS>(env));
94126
return exports;
95127
}
96128

@@ -101,6 +133,17 @@ class DLL_EXPORT WrapperCache : public QObject {
101133
return env.Null();
102134
}
103135

136+
static Napi::Value storeJS(const Napi::CallbackInfo& info) {
137+
Napi::Env env = info.Env();
138+
139+
Napi::Object objectWrapper = info[0].As<Napi::Object>();
140+
QObject* qobject = info[1].As<Napi::External<QObject>>().Data();
141+
142+
uint64_t ptrHash = extrautils::hashPointerTo53bit(qobject);
143+
instance.store(env, ptrHash, qobject, objectWrapper, false);
144+
return env.Null();
145+
}
146+
104147
static Napi::FunctionReference destroyedCallback;
105148

106149
public Q_SLOTS:

src/cpp/lib/QtCore/QObject/qobject_wrap.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "QtCore/QObject/qobject_wrap.h"
22

33
#include "Extras/Utils/nutils.h"
4+
#include "core/WrapperCache/wrappercache.h"
45

56
Napi::FunctionReference QObjectWrap::constructor;
67

@@ -11,6 +12,7 @@ Napi::Object QObjectWrap::init(Napi::Env env, Napi::Object exports) {
1112
env, CLASSNAME, {QOBJECT_WRAPPED_METHODS_EXPORT_DEFINE(QObjectWrap)});
1213
constructor = Napi::Persistent(func);
1314
exports.Set(CLASSNAME, func);
15+
WrapperCache::instance.registerWrapper(QString("NObject"), QObjectWrap::wrapFunc);
1416
return exports;
1517
}
1618

@@ -37,4 +39,11 @@ QObjectWrap::QObjectWrap(const Napi::CallbackInfo& info)
3739
.ThrowAsJavaScriptException();
3840
}
3941
this->rawData = extrautils::configureQObject(this->getInternalInstance());
42+
// WrapperCache::instance.store<QObject, QObjectWrap>(env, this->getInternalInstance(), this);
43+
}
44+
45+
Napi::Object QObjectWrap::wrapFunc(Napi::Env env, QObject *qobject) {
46+
// Qtype *exactQObject = dynamic_cast<Qtype*>(qobject)
47+
Napi::Object wrapper = QObjectWrap::constructor.New({Napi::External<QObject>::New(env, qobject)});
48+
return wrapper;
4049
}

src/lib/QtCore/QObject.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { checkIfNativeElement } from '../utils/helpers';
44
import addon from '../utils/addon';
55
import { QVariant, QVariantType } from './QVariant';
66
import { TimerType } from '../QtEnums/TimerType';
7+
import { wrapperCache } from '../core/WrapperCache';
78

89
export class QObject<Signals extends QObjectSignals = QObjectSignals> extends EventWidget<Signals> {
910
constructor(nativeElementOrParent?: NativeElement | QObject) {
@@ -18,6 +19,9 @@ export class QObject<Signals extends QObjectSignals = QObjectSignals> extends Ev
1819
native = new addon.QObject();
1920
}
2021
super(native);
22+
23+
wrapperCache.store(this);
24+
2125
this.setNodeParent(parent);
2226
}
2327

@@ -51,6 +55,9 @@ export class QObject<Signals extends QObjectSignals = QObjectSignals> extends Ev
5155
this.native.setParent(null);
5256
}
5357
}
58+
parent(): QObject {
59+
return wrapperCache.getWrapper(this.native.parent());
60+
}
5461
startTimer(intervalMS: number, timerType = TimerType.CoarseTimer): number {
5562
return this.native.startTimer(intervalMS, timerType);
5663
}

src/lib/core/WrapperCache.ts

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,68 @@ import { NativeElement } from './Component';
1313
* wrapper automatically and unexpectedly garbage collected.
1414
*/
1515
export class WrapperCache {
16-
private _cache = new Map<number, any>();
16+
private _strongCache = new Map<number, QObject>();
17+
private _weakCache = new Map<number, WeakRef<QObject>>();
1718

1819
constructor() {
1920
addon.WrapperCache_injectCallback(this._objectDestroyedCallback.bind(this));
2021
}
2122

2223
private _objectDestroyedCallback(objectId: number): void {
23-
if (!this._cache.has(objectId)) {
24-
return;
24+
if (this._strongCache.has(objectId)) {
25+
const wrapper = this._strongCache.get(objectId);
26+
wrapper.native = null;
27+
this._strongCache.delete(objectId);
28+
}
29+
30+
const wrapperRef = this._weakCache.get(objectId);
31+
if (wrapperRef != null) {
32+
const wrapper = wrapperRef.deref();
33+
if (wrapper != null) {
34+
wrapper.native = null;
35+
this._weakCache.delete(objectId);
36+
}
2537
}
26-
const wrapper = this._cache.get(objectId);
27-
wrapper.native = null;
28-
this._cache.delete(objectId);
2938
}
3039

31-
get<T>(wrapperConstructor: { new (native: any): T }, native: NativeElement): T {
40+
get<T extends QObject>(wrapperConstructor: { new (native: any): T }, native: NativeElement): T {
3241
const id = native.__id__();
33-
if (this._cache.has(id)) {
34-
return this._cache.get(id) as T;
42+
if (this._strongCache.has(id)) {
43+
return this._strongCache.get(id) as T;
3544
}
3645
const wrapper = new wrapperConstructor(native);
37-
this._cache.set(id, wrapper);
46+
this._strongCache.set(id, wrapper);
3847
return wrapper;
3948
}
49+
50+
getWrapper(native: any): QObject | null {
51+
if (native == null) {
52+
return null;
53+
}
54+
const id = native.__id__();
55+
56+
if (this._strongCache.has(id)) {
57+
return this._strongCache.get(id);
58+
}
59+
60+
const ref = this._weakCache.get(id);
61+
if (ref != null) {
62+
const wrapper = ref.deref();
63+
if (wrapper != null) {
64+
return wrapper;
65+
}
66+
}
67+
68+
return null; // FIXME: Create new wrapper on demand.
69+
}
70+
71+
store(wrapper: QObject): void {
72+
if (wrapper.native != null) {
73+
const id = wrapper.native.__id__();
74+
this._weakCache.set(id, new WeakRef<QObject>(wrapper));
75+
76+
addon.WrapperCache_store(wrapper.native, wrapper.native.__external_qobject__());
77+
}
78+
}
4079
}
4180
export const wrapperCache = new WrapperCache();

src/lib/core/__test__/WrapperCache.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { QObject } from '../../QtCore/QObject';
12
import { QApplication } from '../../QtGui/QApplication';
23
import { CacheTestQObject } from './CacheTestQObject';
34

@@ -43,5 +44,20 @@ describe('WrapperCache using CacheTestQObject', () => {
4344
expect(foo).not.toEqual(bar);
4445
expect(foo.native.__id__()).not.toEqual(bar.native.__id__());
4546
});
47+
48+
it('QObject.parent() can be null', () => {
49+
const a = new QObject();
50+
expect(a.parent()).toBeNull();
51+
});
52+
53+
it('QObject.parent() === QObject.parent()', () => {
54+
const a = new QObject();
55+
const b = new QObject(a);
56+
expect(a.native.__id__()).toEqual(b.parent().native.__id__());
57+
expect(a).toEqual(b.parent());
58+
(<any>a)['magic'] = true;
59+
expect((<any>b.parent())['magic']).toBe(true);
60+
});
61+
4662
qApp.quit();
4763
});

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"compilerOptions": {
3-
"target": "ES2015",
3+
"target": "ES2021",
44
"module": "commonjs",
55
"declaration": true,
66
"sourceMap": false,

0 commit comments

Comments
 (0)