Add wrapper caching. Try it on QScreen.
This commit is contained in:
parent
05c690dcd9
commit
710cfa3d31
@ -11,6 +11,9 @@ set(CORE_WIDGETS_ADDON "nodegui_core")
|
||||
|
||||
project(${CORE_WIDGETS_ADDON})
|
||||
|
||||
|
||||
# Note: CMake+moc also use this list when finding files which `moc` applied.
|
||||
|
||||
add_library(${CORE_WIDGETS_ADDON} SHARED
|
||||
"${CMAKE_JS_SRC}"
|
||||
"${PROJECT_SOURCE_DIR}/src/cpp/main.cpp"
|
||||
@ -24,6 +27,8 @@ add_library(${CORE_WIDGETS_ADDON} SHARED
|
||||
"${PROJECT_SOURCE_DIR}/src/cpp/lib/core/Events/eventsmap.cpp"
|
||||
"${PROJECT_SOURCE_DIR}/src/cpp/lib/core/Events/eventwidget.cpp"
|
||||
"${PROJECT_SOURCE_DIR}/src/cpp/lib/core/YogaWidget/yogawidget.cpp"
|
||||
"${PROJECT_SOURCE_DIR}/src/cpp/include/nodegui/core/WrapperCache/wrappercache.h"
|
||||
"${PROJECT_SOURCE_DIR}/src/cpp/lib/core/WrapperCache/wrappercache.cpp"
|
||||
# core deps
|
||||
"${PROJECT_SOURCE_DIR}/src/cpp/include/deps/yoga/log.cpp"
|
||||
"${PROJECT_SOURCE_DIR}/src/cpp/include/deps/yoga/Utils.cpp"
|
||||
|
||||
@ -17,6 +17,7 @@ DLL_EXPORT void* configureQWidget(QWidget* widget, YGNodeRef node,
|
||||
bool isLeafNode = false);
|
||||
DLL_EXPORT void* configureQObject(QObject* object);
|
||||
DLL_EXPORT void* configureComponent(void* component);
|
||||
DLL_EXPORT uint64_t hashPointerTo53bit(const void* input);
|
||||
|
||||
template <typename T>
|
||||
void safeDelete(QPointer<T>& component) {
|
||||
|
||||
@ -17,6 +17,12 @@
|
||||
\
|
||||
EVENTWIDGET_WRAPPED_METHODS_DECLARATION_WITH_EVENT_SOURCE(source) \
|
||||
\
|
||||
Napi::Value __id__(const Napi::CallbackInfo& info) { \
|
||||
Napi::Env env = info.Env(); \
|
||||
Napi::HandleScope scope(env); \
|
||||
return Napi::Value::From( \
|
||||
env, extrautils::hashPointerTo53bit(this->instance.data())); \
|
||||
} \
|
||||
Napi::Value inherits(const Napi::CallbackInfo& info) { \
|
||||
Napi::Env env = info.Env(); \
|
||||
Napi::HandleScope scope(env); \
|
||||
@ -85,7 +91,8 @@
|
||||
\
|
||||
EVENTWIDGET_WRAPPED_METHODS_EXPORT_DEFINE(ComponentWrapName) \
|
||||
\
|
||||
InstanceMethod("inherits", &ComponentWrapName::inherits), \
|
||||
InstanceMethod("__id__", &ComponentWrapName::__id__), \
|
||||
InstanceMethod("inherits", &ComponentWrapName::inherits), \
|
||||
InstanceMethod("setProperty", &ComponentWrapName::setProperty), \
|
||||
InstanceMethod("property", &ComponentWrapName::property), \
|
||||
InstanceMethod("setObjectName", &ComponentWrapName::setObjectName), \
|
||||
|
||||
34
src/cpp/include/nodegui/core/WrapperCache/wrappercache.h
Normal file
34
src/cpp/include/nodegui/core/WrapperCache/wrappercache.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <napi.h>
|
||||
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include "Extras/Export/export.h"
|
||||
#include "QtGui/QScreen/qscreen_wrap.h"
|
||||
|
||||
struct CachedObject {
|
||||
napi_ref ref;
|
||||
napi_env env;
|
||||
};
|
||||
|
||||
class DLL_EXPORT WrapperCache : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QMap<const QObject*, CachedObject> cache;
|
||||
|
||||
public:
|
||||
WrapperCache();
|
||||
Napi::Object get(const Napi::CallbackInfo& info, QScreen* screen);
|
||||
|
||||
static WrapperCache instance;
|
||||
static Napi::Object init(Napi::Env env, Napi::Object exports);
|
||||
static Napi::Value injectDestroyCallback(const Napi::CallbackInfo& info);
|
||||
|
||||
static Napi::FunctionReference destroyedCallback;
|
||||
|
||||
public Q_SLOTS:
|
||||
void handleDestroyed(const QObject* object);
|
||||
};
|
||||
@ -110,6 +110,20 @@ void* extrautils::configureQWidget(QWidget* widget, YGNodeRef node,
|
||||
return configureQObject(widget);
|
||||
}
|
||||
|
||||
uint64_t extrautils::hashPointerTo53bit(const void* input) {
|
||||
// Hash the address of the object down to something which will
|
||||
// fit into the JS 53bit safe integer space.
|
||||
uint64_t address = reinterpret_cast<uint64_t>(input);
|
||||
uint64_t top8Bits = address & 0xff00000000000000u;
|
||||
uint64_t foldedBits = (top8Bits >> 11) ^ address;
|
||||
|
||||
// Clear the top 8bits which we folded, now shift out the last 3 bits
|
||||
// Pointers are aligned on 64bit architectures to at least 8bytes
|
||||
// boundaries.
|
||||
uint64_t result = (foldedBits & ~0xff00000000000000u) >> 3;
|
||||
return result;
|
||||
}
|
||||
|
||||
Napi::FunctionReference NUtilsWrap::constructor;
|
||||
|
||||
Napi::Object NUtilsWrap::init(Napi::Env env, Napi::Object exports) {
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include "QtGui/QPalette/qpalette_wrap.h"
|
||||
#include "QtGui/QStyle/qstyle_wrap.h"
|
||||
#include "core/Integration/qode-api.h"
|
||||
#include "core/WrapperCache/wrappercache.h"
|
||||
|
||||
Napi::FunctionReference QApplicationWrap::constructor;
|
||||
|
||||
@ -175,8 +176,7 @@ Napi::Value StaticQApplicationWrapMethods::primaryScreen(
|
||||
Napi::HandleScope scope(env);
|
||||
auto screen = QApplication::primaryScreen();
|
||||
if (screen) {
|
||||
return QScreenWrap::constructor.New(
|
||||
{Napi::External<QScreen>::New(env, screen)});
|
||||
return WrapperCache::instance.get(info, screen);
|
||||
} else {
|
||||
return env.Null();
|
||||
}
|
||||
@ -191,8 +191,7 @@ Napi::Value StaticQApplicationWrapMethods::screens(
|
||||
Napi::Array jsArray = Napi::Array::New(env, screens.size());
|
||||
for (int i = 0; i < screens.size(); i++) {
|
||||
QScreen* screen = screens[i];
|
||||
auto instance = QScreenWrap::constructor.New(
|
||||
{Napi::External<QScreen>::New(env, screen)});
|
||||
auto instance = WrapperCache::instance.get(info, screen);
|
||||
jsArray[i] = instance;
|
||||
}
|
||||
return jsArray;
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include "Extras/Utils/nutils.h"
|
||||
#include "QtGui/QScreen/qscreen_wrap.h"
|
||||
#include "core/WrapperCache/wrappercache.h"
|
||||
|
||||
Napi::FunctionReference QWindowWrap::constructor;
|
||||
|
||||
@ -49,9 +50,7 @@ Napi::Value QWindowWrap::screen(const Napi::CallbackInfo& info) {
|
||||
|
||||
QScreen* screen = this->instance->screen();
|
||||
if (screen) {
|
||||
auto instance = QScreenWrap::constructor.New(
|
||||
{Napi::External<QScreen>::New(env, screen)});
|
||||
return instance;
|
||||
return WrapperCache::instance.get(info, screen);
|
||||
} else {
|
||||
return env.Null();
|
||||
}
|
||||
|
||||
70
src/cpp/lib/core/WrapperCache/wrappercache.cpp
Normal file
70
src/cpp/lib/core/WrapperCache/wrappercache.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
#include "core/WrapperCache/wrappercache.h"
|
||||
|
||||
#include "Extras/Utils/nutils.h"
|
||||
|
||||
DLL_EXPORT WrapperCache WrapperCache::instance;
|
||||
|
||||
Napi::FunctionReference WrapperCache::destroyedCallback;
|
||||
|
||||
WrapperCache::WrapperCache() {}
|
||||
|
||||
Napi::Object WrapperCache::init(Napi::Env env, Napi::Object exports) {
|
||||
Napi::HandleScope scope(env);
|
||||
|
||||
exports.Set("WrapperCache_injectCallback",
|
||||
Napi::Function::New<injectDestroyCallback>(env));
|
||||
return exports;
|
||||
}
|
||||
|
||||
Napi::Value WrapperCache::injectDestroyCallback(
|
||||
const Napi::CallbackInfo& info) {
|
||||
Napi::Env env = info.Env();
|
||||
Napi::HandleScope scope(env);
|
||||
|
||||
destroyedCallback = Napi::Persistent(info[0].As<Napi::Function>());
|
||||
return env.Null();
|
||||
}
|
||||
|
||||
Napi::Object WrapperCache::get(const Napi::CallbackInfo& info,
|
||||
QScreen* screen) {
|
||||
Napi::Env env = info.Env();
|
||||
|
||||
if (this->cache.contains(screen)) {
|
||||
napi_value result = nullptr;
|
||||
napi_get_reference_value(this->cache[screen].env, this->cache[screen].ref,
|
||||
&result);
|
||||
return Napi::Object(env, result);
|
||||
}
|
||||
|
||||
Napi::HandleScope scope(env);
|
||||
Napi::Object wrapper =
|
||||
QScreenWrap::constructor.New({Napi::External<QScreen>::New(env, screen)});
|
||||
|
||||
napi_ref ref = nullptr;
|
||||
napi_create_reference(env, wrapper, 1, &ref);
|
||||
this->cache[screen].env = napi_env(env);
|
||||
this->cache[screen].ref = ref;
|
||||
|
||||
QObject::connect(screen, &QObject::destroyed, this,
|
||||
&WrapperCache::handleDestroyed);
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
void WrapperCache::handleDestroyed(const QObject* object) {
|
||||
if (!this->cache.contains(object)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t result = 0;
|
||||
Napi::Env env = this->cache[object].env;
|
||||
napi_reference_unref(env, this->cache[object].ref, &result);
|
||||
this->cache.remove(object);
|
||||
|
||||
// Callback to JS with the address/ID of the destroyed object. So that it
|
||||
// can clear it out of the cache.
|
||||
if (destroyedCallback) {
|
||||
destroyedCallback.Call(
|
||||
{Napi::Value::From(env, extrautils::hashPointerTo53bit(object))});
|
||||
}
|
||||
}
|
||||
@ -114,6 +114,8 @@
|
||||
#include "QtWidgets/QWidget/qwidget_wrap.h"
|
||||
#include "core/FlexLayout/flexlayout_wrap.h"
|
||||
#include "core/Integration/integration.h"
|
||||
#include "core/WrapperCache/wrappercache.h"
|
||||
|
||||
// These cant be instantiated in JS Side
|
||||
void InitPrivateHelpers(Napi::Env env) {
|
||||
qodeIntegration::integrate();
|
||||
@ -123,6 +125,7 @@ void InitPrivateHelpers(Napi::Env env) {
|
||||
Napi::Object Main(Napi::Env env, Napi::Object exports) {
|
||||
InitPrivateHelpers(env);
|
||||
NUtilsWrap::init(env, exports);
|
||||
WrapperCache::init(env, exports);
|
||||
QApplicationWrap::init(env, exports);
|
||||
QDateWrap::init(env, exports);
|
||||
QDateTimeWrap::init(env, exports);
|
||||
|
||||
@ -8,6 +8,7 @@ import { QPalette } from './QPalette';
|
||||
import { StyleSheet } from '../core/Style/StyleSheet';
|
||||
import memoizeOne from 'memoize-one';
|
||||
import { QScreen } from './QScreen';
|
||||
import { wrapperCache } from '../core/WrapperCache';
|
||||
|
||||
/**
|
||||
|
||||
@ -82,11 +83,11 @@ export class QApplication extends NodeObject<QApplicationSignals> {
|
||||
if (screenNative == null) {
|
||||
return null;
|
||||
}
|
||||
return new QScreen(screenNative);
|
||||
return wrapperCache.get<QScreen>(QScreen, screenNative);
|
||||
}
|
||||
static screens(): QScreen[] {
|
||||
const screenNativeList = addon.QApplication.screens();
|
||||
return screenNativeList.map((screenNative: any) => new QScreen(screenNative));
|
||||
return screenNativeList.map((screenNative: any) => wrapperCache.get<QScreen>(QScreen, screenNative));
|
||||
}
|
||||
static setStyle(style: QStyle): void {
|
||||
addon.QApplication.setStyle(style.native);
|
||||
|
||||
@ -2,6 +2,7 @@ import { NativeElement } from '../core/Component';
|
||||
import { checkIfNativeElement } from '../utils/helpers';
|
||||
import { NodeObject, QObjectSignals } from '../QtCore/QObject';
|
||||
import { QScreen } from './QScreen';
|
||||
import { wrapperCache } from '../core/WrapperCache';
|
||||
|
||||
export class QWindow extends NodeObject<QWindowSignals> {
|
||||
native: NativeElement;
|
||||
@ -16,8 +17,7 @@ export class QWindow extends NodeObject<QWindowSignals> {
|
||||
}
|
||||
|
||||
screen(): QScreen {
|
||||
const screenNative = this.native.screen();
|
||||
return new QScreen(screenNative);
|
||||
return wrapperCache.get<QScreen>(QScreen, this.native.screen());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
44
src/lib/core/WrapperCache.ts
Normal file
44
src/lib/core/WrapperCache.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import addon from '../utils/addon';
|
||||
|
||||
/**
|
||||
* JS side cache for wrapper objects.
|
||||
*
|
||||
* This is mainly used for caching wrappers of Qt objects which are not
|
||||
* directly created by our Nodejs application. The purpose of the cache
|
||||
* is to keep "alive" wrapper objects and their underlying C++ wrappers
|
||||
* which may be connected to Qt signals from the real Qt object.
|
||||
* This makes it easier for application to grab one of these objects,
|
||||
* set up event handlers, and then let the object go and *not* have the
|
||||
* wrapper automatically and unexpectedly garbage collected.
|
||||
*/
|
||||
export class WrapperCache {
|
||||
private _cache = new Map<number, any>();
|
||||
|
||||
constructor() {
|
||||
addon.WrapperCache_injectCallback(this._objectDestroyedCallback.bind(this));
|
||||
}
|
||||
|
||||
private _objectDestroyedCallback(objectId: number): void {
|
||||
console.log(`_objectDestroyedCallback() id: ${objectId}`);
|
||||
if (!this._cache.has(objectId)) {
|
||||
return;
|
||||
}
|
||||
const wrapper = this._cache.get(objectId);
|
||||
wrapper.native = null;
|
||||
this._cache.delete(objectId);
|
||||
}
|
||||
|
||||
get<T>(wrapperConstructor: { new (native: any): T }, native: any): T {
|
||||
const id = native.__id__();
|
||||
|
||||
console.log(`WrapperCache.get() id: ${id}`);
|
||||
|
||||
if (this._cache.has(id)) {
|
||||
return this._cache.get(id) as T;
|
||||
}
|
||||
const wrapper = new wrapperConstructor(native);
|
||||
this._cache.set(id, wrapper);
|
||||
return wrapper;
|
||||
}
|
||||
}
|
||||
export const wrapperCache = new WrapperCache();
|
||||
@ -144,5 +144,5 @@ We need to run Qt's MOC (Meta Object Compiler) on the file whenever we use Q_OBJ
|
||||
# How does it work ?
|
||||
|
||||
1. On JS side for each widget instance we create an instance of NodeJS's Event Emitter. This is done by the class `EventWidget` from which `NodeWidget` inherits
|
||||
2. We send this event emiiter's `emit` function to the C++ side by calling `initNodeEventEmitter` method and store a pointer to the event emitter's emit function using `emitOnNode`. initNodeEventEmitter function is added by a macro from EventWidget (c++). You can find the initNodeEventEmitter method with the event widget macros.
|
||||
2. We send this event emitter's `emit` function to the C++ side by calling `initNodeEventEmitter` method and store a pointer to the event emitter's emit function using `emitOnNode`. initNodeEventEmitter function is added by a macro from EventWidget (c++). You can find the initNodeEventEmitter method with the event widget macros.
|
||||
3. We setup Qt's connect method for all the signals that we want to listen to and call the emitOnNode (which is actually emit from Event emitter) whenever a signal arrives. This is done manually on every widget by overriding the method `connectSignalsToEventEmitter`. Check `npushbutton.h` for details. This takes care of all the signals of the widgets. Now to export all qt events of the widget, we had overriden the widgets `event(Event*)` method to listen to events received by the widget and send it to the event emitter. This is done inside the EVENTWIDGET_IMPLEMENTATIONS macro
|
||||
|
||||
Loading…
Reference in New Issue
Block a user