From 83893b293452f6a52f87a02bfa3ef8c6a0dc2ad3 Mon Sep 17 00:00:00 2001 From: Filippo Cucchetto Date: Sun, 27 Dec 2015 15:40:33 +0100 Subject: [PATCH] Introduced the concept of factories The use of a factory decreased the creation time from 500ms for 1000 DynamicQObjects to 1.6ms --- lib/CMakeLists.txt | 4 + lib/include/DOtherSide/DynamicQObject2.h | 27 ++++++ .../DOtherSide/DynamicQObjectFactory.h | 78 ++++++++++++++++ lib/src/DynamicQObject2.cpp | 93 +++++++++++++++++++ lib/src/DynamicQObjectFactory.cpp | 70 ++++++++++++++ test/test_dynamicqobject.cpp | 38 ++++++++ 6 files changed, 310 insertions(+) create mode 100644 lib/include/DOtherSide/DynamicQObject2.h create mode 100644 lib/include/DOtherSide/DynamicQObjectFactory.h create mode 100644 lib/src/DynamicQObject2.cpp create mode 100644 lib/src/DynamicQObjectFactory.cpp diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index cc4a1a7..14887a1 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -24,6 +24,8 @@ set(HEADERS_LIST include/DOtherSide/DynamicQObject.h include/DOtherSide/IDynamicQObject.h include/DOtherSide/OnSlotExecutedHandler.h + include/DOtherSide/DynamicQObjectFactory.h + include/DOtherSide/DynamicQObject2.h ) set(SRC_LIST @@ -32,6 +34,8 @@ set(SRC_LIST src/DynamicSlot.cpp src/DynamicSignal.cpp src/DynamicProperty.cpp + src/DynamicQObjectFactory.cpp + src/DynamicQObject2.cpp ) include_directories(include include/Qt) diff --git a/lib/include/DOtherSide/DynamicQObject2.h b/lib/include/DOtherSide/DynamicQObject2.h new file mode 100644 index 0000000..2075c79 --- /dev/null +++ b/lib/include/DOtherSide/DynamicQObject2.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +class DynamicQObjectFactory; + +class DynamicQObject2 : public QObject +{ +public: + using OnSlotExecuted = std::function&)>; + + DynamicQObject2(const DynamicQObjectFactory* factory, + OnSlotExecuted handler); + + void emitSignal(const QString& name, const std::vector& arguments); + const QMetaObject* metaObject() const override; + int qt_metacall(QMetaObject::Call callType, int index, void**args) override; + +private: + bool executeSlot(int index, void** args); + bool readProperty(int index, void** args); + bool writeProperty(int index, void** args); + + const DynamicQObjectFactory* const m_factory; + const OnSlotExecuted m_handler; +}; diff --git a/lib/include/DOtherSide/DynamicQObjectFactory.h b/lib/include/DOtherSide/DynamicQObjectFactory.h new file mode 100644 index 0000000..31efc66 --- /dev/null +++ b/lib/include/DOtherSide/DynamicQObjectFactory.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +struct SignalDefinition +{ + QString name; + std::vector argumentsTypes; +}; + +struct SlotDefinition +{ + QString name; + QMetaType::Type returnType; + std::vector argumentsTypes; +}; + +struct PropertyDefinition +{ + QString name; + QMetaType::Type type; + QString readSlot; + QString writeSlot; + QString notifySignal; +}; + +class DynamicQObject2; + +class DynamicQObjectFactory +{ + using SignalDefinitions = std::vector; + using SlotDefinitions = std::vector; + using PropertyDefinitions = std::vector; + using SafeQMetaObjectPtr = std::unique_ptr; + using OnSlotExecuted = std::function&)>; + +public: + DynamicQObjectFactory(SignalDefinitions signalDefinitions, + SlotDefinitions slotDefinitions, + PropertyDefinitions propertyDefinitions); + + + DynamicQObject2* create(OnSlotExecuted handler) const; + inline const QMetaObject* metaObject() const; + inline int signalSlotIndex(const QString& signalName) const; + inline int readSlotIndex(const char* propertyName) const; + inline int writeSlotIndex(const char* propertyName) const; + +private: + SafeQMetaObjectPtr m_metaObject; + QHash m_signalIndexByName; + QHash> m_propertySlots; +}; + +const QMetaObject *DynamicQObjectFactory::metaObject() const +{ + return m_metaObject.get(); +} + +inline int DynamicQObjectFactory::signalSlotIndex(const QString &signalName) const +{ + return m_signalIndexByName.value(signalName.toUtf8(), -1); +} + +inline int DynamicQObjectFactory::readSlotIndex(const char *propertyName) const +{ + return m_propertySlots.value(propertyName, {-1,-1}).first; +} + +inline int DynamicQObjectFactory::writeSlotIndex(const char *propertyName) const +{ + return m_propertySlots.value(propertyName, {-1,-1}).second; +} diff --git a/lib/src/DynamicQObject2.cpp b/lib/src/DynamicQObject2.cpp new file mode 100644 index 0000000..088a1cf --- /dev/null +++ b/lib/src/DynamicQObject2.cpp @@ -0,0 +1,93 @@ +#include "DOtherSide/DynamicQObject2.h" +#include "DOtherSide/DynamicQObjectFactory.h" +#include +#include + +DynamicQObject2::DynamicQObject2(const DynamicQObjectFactory *factory, + OnSlotExecuted handler) + : m_factory(factory) + , m_handler(std::move(handler)) +{} + +void DynamicQObject2::emitSignal(const QString &name, const std::vector &args) +{ + const int index = metaObject()->methodOffset() + m_factory->signalSlotIndex(name); + const QMetaMethod method = metaObject()->method(index); + if (!method.isValid()) { + qDebug() << "Cannot emit signal from invalid method"; + return; + } + Q_ASSERT(name.toUtf8() == method.name()); + + std::vector arguments(args.size() + 1, nullptr); + arguments.front() = nullptr; + auto func = [](const QVariant& arg) -> void* { return (void*)(&arg); }; + std::transform(args.begin(), args.end(), arguments.begin() + 1, func); + QMetaObject::activate(this, method.methodIndex(), arguments.data()); +} + +const QMetaObject *DynamicQObject2::metaObject() const +{ + return m_factory->metaObject(); +} + +int DynamicQObject2::qt_metacall(QMetaObject::Call callType, int index, void** args) +{ + switch (callType) + { + case QMetaObject::InvokeMetaMethod: + return executeSlot(index, args) ? 1 : -1; + case QMetaObject::ReadProperty: + return readProperty(index, args) ? 1 : -1; + case QMetaObject::WriteProperty: + return writeProperty(index, args) ? 1 : -1; + default: + return QObject::qt_metacall(callType, index, args); + } + + return -1; +} + +bool DynamicQObject2::executeSlot(int index, void **args) +{ + const QMetaMethod method = metaObject()->method(index); + if (!method.isValid()) { + qDebug() << "Cannot execute invalid method"; + return false; + } + + std::vector arguments; + arguments.reserve(method.parameterCount()); + for (int i = 0; i < method.parameterCount(); ++i) { + QVariant argument(method.parameterType(i), args[i + 1]); + arguments.emplace_back(std::move(argument)); + } + + QVariant result = m_handler(index, method.name(), arguments); // Execute method + + if (method.returnType() != QMetaType::Void && result.isValid()) { + QMetaType::construct(method.returnType(), args[0], result.constData()); + } + + return true; +} + +bool DynamicQObject2::readProperty(int index, void **args) +{ + const QMetaProperty property = metaObject()->property(index); + if (!property.isValid() || !property.isReadable()) { + qDebug() << "Cannot read invalid or unreadable property "; + return false; + } + return executeSlot(m_factory->readSlotIndex(property.name()), args); +} + +bool DynamicQObject2::writeProperty(int index, void **args) +{ + const QMetaProperty property = metaObject()->property(index); + if (!property.isValid() || !property.isWritable()) { + qDebug() << "Cannot write invalid or unwritable property "; + return false; + } + return executeSlot(m_factory->writeSlotIndex(property.name()), args); +} diff --git a/lib/src/DynamicQObjectFactory.cpp b/lib/src/DynamicQObjectFactory.cpp new file mode 100644 index 0000000..c749fad --- /dev/null +++ b/lib/src/DynamicQObjectFactory.cpp @@ -0,0 +1,70 @@ +#include "DOtherSide/DynamicQObjectFactory.h" +#include "DOtherSide/DynamicQObject2.h" +#include "private/qmetaobjectbuilder_p.h" +#include "private/qmetaobject_p.h" +#include "private/qobject_p.h" + +namespace +{ + template + QByteArray createSignature(const T& functionDefinition) + { + QString signature("%1(%2)"); + QString arguments; + + for (QMetaType::Type type : functionDefinition.argumentsTypes) { + if (type != functionDefinition.argumentsTypes.front()) + arguments += QLatin1Char(','); + arguments += QMetaType::typeName(type); + } + + return signature.arg(functionDefinition.name, arguments).toUtf8(); + } +} + +DynamicQObjectFactory::DynamicQObjectFactory(DynamicQObjectFactory::SignalDefinitions signalDefinitions, DynamicQObjectFactory::SlotDefinitions slotDefinitions, DynamicQObjectFactory::PropertyDefinitions propertyDefinitions) + : m_metaObject(nullptr, ::free) +{ + QMetaObjectBuilder builder; + builder.setFlags(QMetaObjectBuilder::DynamicMetaObject); + builder.setClassName("DynamicQObject"); + builder.setSuperClass(&QObject::staticMetaObject); + + m_signalIndexByName.reserve(signalDefinitions.size()); + + for (const SignalDefinition& signal : signalDefinitions) + { + QMetaMethodBuilder signalBuilder = builder.addSignal(createSignature(signal)); + signalBuilder.setReturnType(QMetaType::typeName(QMetaType::Void)); + signalBuilder.setAccess(QMetaMethod::Public); + m_signalIndexByName[signal.name.toUtf8()] = signalBuilder.index(); + } + + QHash methodIndexByName; + for (const SlotDefinition& slot : slotDefinitions) + { + QMetaMethodBuilder methodBuilder = builder.addSlot(createSignature(slot)); + methodBuilder.setReturnType(QMetaType::typeName(slot.returnType)); + methodBuilder.setAttributes(QMetaMethod::Scriptable); + methodIndexByName[slot.name] = methodBuilder.index(); + } + + for (const PropertyDefinition& property : propertyDefinitions) + { + const int notifier = m_signalIndexByName.value(property.notifySignal.toUtf8(), -1); + const QByteArray name = property.name.toUtf8(); + const QByteArray typeName = QMetaObject::normalizedType(QMetaType::typeName(property.type)); + QMetaPropertyBuilder propertyBuilder = builder.addProperty(name, typeName, notifier); + if (notifier == -1) + propertyBuilder.setConstant(true); + m_propertySlots[propertyBuilder.name()] = { methodIndexByName.value(property.readSlot, -1) + , methodIndexByName.value(property.writeSlot, -1)}; + } + + m_metaObject.reset(builder.toMetaObject()); +} + +DynamicQObject2* DynamicQObjectFactory::create(OnSlotExecuted handler) const +{ + return new DynamicQObject2(this, std::move(handler)); +} diff --git a/test/test_dynamicqobject.cpp b/test/test_dynamicqobject.cpp index 0b91d21..01b34f1 100644 --- a/test/test_dynamicqobject.cpp +++ b/test/test_dynamicqobject.cpp @@ -8,6 +8,8 @@ // DOtherSide #include "DOtherSide/BaseQObject.h" #include "DOtherSide/DynamicQObject.h" +#include "DOtherSide/DynamicQObject2.h" +#include "DOtherSide/DynamicQObjectFactory.h" // Templates that convers a T to a string template @@ -84,6 +86,42 @@ private slots: QCOMPARE(propertyValue, 10); } + void benchmarkDynamicQObjectPerformance() { + QBENCHMARK { + for (int i = 0; i < 1000; ++i) { + DynamicQObject dynamicQObject; + int index = -1; + dynamicQObject.registerSlot("foo", QMetaType::Int, {}, index); + dynamicQObject.registerSlot("setFoo", QMetaType::Void, {QMetaType::Int}, index); + dynamicQObject.registerSignal("fooChanged", {QMetaType::Int}, index); + dynamicQObject.registerProperty("foo", QMetaType::Int, "foo", "setFoo", "fooChanged"); + } + } + } + + void benchmarkDynamicQObject2Performance() { + QBENCHMARK { + DynamicQObjectFactory factory {{SignalDefinition{"fooChanged", {QMetaType::Int}}}, + {SlotDefinition{"foo", QMetaType::Int, {}}, SlotDefinition{"setFoo", QMetaType::Void, {QMetaType::Int}}}, + {PropertyDefinition{"foo", QMetaType::Int, "foo", "setFoo", "fooChanged"}}}; + for (int i = 0; i < 1000; ++i) { + std::unique_ptr dynamicQObject(factory.create([](int, const QString&, const std::vector&)-> QVariant{})); + } + } + } + + void testDynamicQObject2() { + DynamicQObjectFactory factory {{SignalDefinition{"fooChanged", {QMetaType::Int}}}, + {SlotDefinition{"foo", QMetaType::Int, {}}, SlotDefinition{"setFoo", QMetaType::Void, {QMetaType::Int}}}, + {PropertyDefinition{"foo", QMetaType::Int, "foo", "setFoo", "fooChanged"}}}; + std::unique_ptr dynamicQObject(factory.create([](int, const QString&, const std::vector&)-> QVariant{})); + QVERIFY(dynamicQObject != nullptr); + + QSignalSpy signalSpy(dynamicQObject.get(), SIGNAL(fooChanged(int))); + dynamicQObject->emitSignal("fooChanged", {10}); + QCOMPARE(signalSpy.count(), 1); + } + private: template void testSlotExecutionForType(ReturnType expectedReturnValue) {