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/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)
|
||||
|
|
|
@ -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
|
||||
#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 <typename T>
|
||||
|
@ -84,6 +86,42 @@ private slots:
|
|||
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:
|
||||
template<typename ReturnType>
|
||||
void testSlotExecutionForType(ReturnType expectedReturnValue) {
|
||||
|
|
Loading…
Reference in New Issue