Introduced the concept of factories
The use of a factory decreased the creation time from 500ms for 1000 DynamicQObjects to 1.6ms
This commit is contained in:
parent
7328a1e24c
commit
83893b2934
|
@ -24,6 +24,8 @@ set(HEADERS_LIST
|
||||||
include/DOtherSide/DynamicQObject.h
|
include/DOtherSide/DynamicQObject.h
|
||||||
include/DOtherSide/IDynamicQObject.h
|
include/DOtherSide/IDynamicQObject.h
|
||||||
include/DOtherSide/OnSlotExecutedHandler.h
|
include/DOtherSide/OnSlotExecutedHandler.h
|
||||||
|
include/DOtherSide/DynamicQObjectFactory.h
|
||||||
|
include/DOtherSide/DynamicQObject2.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SRC_LIST
|
set(SRC_LIST
|
||||||
|
@ -32,6 +34,8 @@ set(SRC_LIST
|
||||||
src/DynamicSlot.cpp
|
src/DynamicSlot.cpp
|
||||||
src/DynamicSignal.cpp
|
src/DynamicSignal.cpp
|
||||||
src/DynamicProperty.cpp
|
src/DynamicProperty.cpp
|
||||||
|
src/DynamicQObjectFactory.cpp
|
||||||
|
src/DynamicQObject2.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
include_directories(include include/Qt)
|
include_directories(include include/Qt)
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
class DynamicQObjectFactory;
|
||||||
|
|
||||||
|
class DynamicQObject2 : public QObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using OnSlotExecuted = std::function<QVariant(int, const QString&, const std::vector<QVariant>&)>;
|
||||||
|
|
||||||
|
DynamicQObject2(const DynamicQObjectFactory* factory,
|
||||||
|
OnSlotExecuted handler);
|
||||||
|
|
||||||
|
void emitSignal(const QString& name, const std::vector<QVariant>& 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;
|
||||||
|
};
|
|
@ -0,0 +1,78 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <tuple>
|
||||||
|
#include <QtCore/QObject>
|
||||||
|
#include <QtCore/QString>
|
||||||
|
#include <QtCore/QHash>
|
||||||
|
|
||||||
|
struct SignalDefinition
|
||||||
|
{
|
||||||
|
QString name;
|
||||||
|
std::vector<QMetaType::Type> argumentsTypes;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SlotDefinition
|
||||||
|
{
|
||||||
|
QString name;
|
||||||
|
QMetaType::Type returnType;
|
||||||
|
std::vector<QMetaType::Type> argumentsTypes;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PropertyDefinition
|
||||||
|
{
|
||||||
|
QString name;
|
||||||
|
QMetaType::Type type;
|
||||||
|
QString readSlot;
|
||||||
|
QString writeSlot;
|
||||||
|
QString notifySignal;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DynamicQObject2;
|
||||||
|
|
||||||
|
class DynamicQObjectFactory
|
||||||
|
{
|
||||||
|
using SignalDefinitions = std::vector<SignalDefinition>;
|
||||||
|
using SlotDefinitions = std::vector<SlotDefinition>;
|
||||||
|
using PropertyDefinitions = std::vector<PropertyDefinition>;
|
||||||
|
using SafeQMetaObjectPtr = std::unique_ptr<QMetaObject, void(*)(void*)>;
|
||||||
|
using OnSlotExecuted = std::function<QVariant(int, const QString&, const std::vector<QVariant>&)>;
|
||||||
|
|
||||||
|
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<QByteArray, int> m_signalIndexByName;
|
||||||
|
QHash<QByteArray, QPair<int,int>> 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;
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
#include "DOtherSide/DynamicQObject2.h"
|
||||||
|
#include "DOtherSide/DynamicQObjectFactory.h"
|
||||||
|
#include <QtCore/QMetaMethod>
|
||||||
|
#include <QtCore/QDebug>
|
||||||
|
|
||||||
|
DynamicQObject2::DynamicQObject2(const DynamicQObjectFactory *factory,
|
||||||
|
OnSlotExecuted handler)
|
||||||
|
: m_factory(factory)
|
||||||
|
, m_handler(std::move(handler))
|
||||||
|
{}
|
||||||
|
|
||||||
|
void DynamicQObject2::emitSignal(const QString &name, const std::vector<QVariant> &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<void*> 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<QVariant> 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);
|
||||||
|
}
|
|
@ -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<class T>
|
||||||
|
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<QString, int> 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));
|
||||||
|
}
|
|
@ -8,6 +8,8 @@
|
||||||
// DOtherSide
|
// DOtherSide
|
||||||
#include "DOtherSide/BaseQObject.h"
|
#include "DOtherSide/BaseQObject.h"
|
||||||
#include "DOtherSide/DynamicQObject.h"
|
#include "DOtherSide/DynamicQObject.h"
|
||||||
|
#include "DOtherSide/DynamicQObject2.h"
|
||||||
|
#include "DOtherSide/DynamicQObjectFactory.h"
|
||||||
|
|
||||||
// Templates that convers a T to a string
|
// Templates that convers a T to a string
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -84,6 +86,42 @@ private slots:
|
||||||
QCOMPARE(propertyValue, 10);
|
QCOMPARE(propertyValue, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void benchmarkDynamicQObjectPerformance() {
|
||||||
|
QBENCHMARK {
|
||||||
|
for (int i = 0; i < 1000; ++i) {
|
||||||
|
DynamicQObject<QObject> 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<DynamicQObject2> dynamicQObject(factory.create([](int, const QString&, const std::vector<QVariant>&)-> 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<DynamicQObject2> dynamicQObject(factory.create([](int, const QString&, const std::vector<QVariant>&)-> QVariant{}));
|
||||||
|
QVERIFY(dynamicQObject != nullptr);
|
||||||
|
|
||||||
|
QSignalSpy signalSpy(dynamicQObject.get(), SIGNAL(fooChanged(int)));
|
||||||
|
dynamicQObject->emitSignal("fooChanged", {10});
|
||||||
|
QCOMPARE(signalSpy.count(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<typename ReturnType>
|
template<typename ReturnType>
|
||||||
void testSlotExecutionForType(ReturnType expectedReturnValue) {
|
void testSlotExecutionForType(ReturnType expectedReturnValue) {
|
||||||
|
|
Loading…
Reference in New Issue