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:
Filippo Cucchetto 2015-12-27 15:40:33 +01:00
parent 7328a1e24c
commit 83893b2934
6 changed files with 310 additions and 0 deletions

View File

@ -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)

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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));
}

View File

@ -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) {