Finish Abstraction & Refactor (Ongoing) - part 1 (#25)

* refactor: abstract connection/transport; and clearly separate qt remote obj and qt local into separate implementations

* abstract qt remote registry

* add mock implementation; these serves to further test the abstraction but also useful for testing modules later

* use LogosObject instead of QObject

* abstract provider side

* updates to use new api

* re-add async api back

---------

Co-authored-by: Logos Workspace <logos@workspace.local>
This commit is contained in:
Iuri Matias
2026-03-23 11:45:25 -04:00
committed by GitHub
co-authored by Logos Workspace
parent 4b66dac015
commit 4197ee1830
37 changed files with 2652 additions and 1317 deletions
+286 -41
View File
@@ -12,7 +12,9 @@
#include <QByteArrayList>
#include <QFile>
#include <QSet>
#include <QRegularExpression>
#include <QtGlobal>
#include "logos_provider_object.h"
static QJsonArray enumerateMethods(QObject* moduleInstance)
{
@@ -115,13 +117,12 @@ static QString makeHeader(const QString& moduleName, const QString& className, c
s << "#include <QVariant>\n";
s << "#include <QStringList>\n";
s << "#include <QJsonArray>\n";
s << "#include <QObject>\n";
s << "#include <QPointer>\n";
s << "#include <functional>\n";
s << "#include <utility>\n";
s << "#include \"logos_types.h\"\n";
s << "#include \"logos_api.h\"\n";
s << "#include \"logos_api_client.h\"\n\n";
s << "#include \"logos_api_client.h\"\n";
s << "#include \"logos_object.h\"\n\n";
s << "class " << className << " {\n";
s << "public:\n";
s << " explicit " << className << "(LogosAPI* api);\n\n";
@@ -129,17 +130,17 @@ static QString makeHeader(const QString& moduleName, const QString& className, c
s << " using EventCallback = std::function<void(const QVariantList&)>;\n\n";
s << " bool on(const QString& eventName, RawEventCallback callback);\n";
s << " bool on(const QString& eventName, EventCallback callback);\n";
s << " void setEventSource(QObject* source);\n";
s << " QObject* eventSource() const;\n";
s << " void setEventSource(LogosObject* source);\n";
s << " LogosObject* eventSource() const;\n";
s << " void trigger(const QString& eventName);\n";
s << " void trigger(const QString& eventName, const QVariantList& data);\n";
s << " template<typename... Args>\n";
s << " void trigger(const QString& eventName, Args&&... args) {\n";
s << " trigger(eventName, packVariantList(std::forward<Args>(args)...));\n";
s << " }\n";
s << " void trigger(const QString& eventName, QObject* source, const QVariantList& data);\n";
s << " void trigger(const QString& eventName, LogosObject* source, const QVariantList& data);\n";
s << " template<typename... Args>\n";
s << " void trigger(const QString& eventName, QObject* source, Args&&... args) {\n";
s << " void trigger(const QString& eventName, LogosObject* source, Args&&... args) {\n";
s << " trigger(eventName, source, packVariantList(std::forward<Args>(args)...));\n";
s << " }\n\n";
// Methods
@@ -181,7 +182,7 @@ static QString makeHeader(const QString& moduleName, const QString& className, c
s << asyncCallbackType << " callback, Timeout timeout = Timeout());\n";
}
s << "\nprivate:\n";
s << " QObject* ensureReplica();\n";
s << " LogosObject* ensureReplica();\n";
s << " template<typename... Args>\n";
s << " static QVariantList packVariantList(Args&&... args) {\n";
s << " QVariantList list;\n";
@@ -193,8 +194,8 @@ static QString makeHeader(const QString& moduleName, const QString& className, c
s << " LogosAPI* m_api;\n";
s << " LogosAPIClient* m_client;\n";
s << " QString m_moduleName;\n";
s << " QPointer<QObject> m_eventReplica;\n";
s << " QPointer<QObject> m_eventSource;\n";
s << " LogosObject* m_eventReplica = nullptr;\n";
s << " LogosObject* m_eventSource = nullptr;\n";
s << "};\n";
return h;
}
@@ -206,27 +207,27 @@ static QString makeSource(const QString& moduleName, const QString& className, c
s << "#include \"" << headerBaseName << "\"\n\n";
s << "#include <QDebug>\n\n";
s << className << "::" << className << "(LogosAPI* api) : m_api(api), m_client(api->getClient(\"" << moduleName << "\")), m_moduleName(QStringLiteral(\"" << moduleName << "\")) {}\n\n";
s << "QObject* " << className << "::ensureReplica() {\n";
s << "LogosObject* " << className << "::ensureReplica() {\n";
s << " if (!m_eventReplica) {\n";
s << " QObject* replica = m_client->requestObject(m_moduleName);\n";
s << " LogosObject* replica = m_client->requestObject(m_moduleName);\n";
s << " if (!replica) {\n";
s << " qWarning() << \"" << className << ": failed to acquire remote object for events on\" << m_moduleName;\n";
s << " return nullptr;\n";
s << " }\n";
s << " m_eventReplica = replica;\n";
s << " }\n";
s << " return m_eventReplica.data();\n";
s << " return m_eventReplica;\n";
s << "}\n\n";
s << "bool " << className << "::on(const QString& eventName, RawEventCallback callback) {\n";
s << " if (!callback) {\n";
s << " qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName;\n";
s << " return false;\n";
s << " }\n";
s << " QObject* origin = ensureReplica();\n";
s << " LogosObject* origin = ensureReplica();\n";
s << " if (!origin) {\n";
s << " return false;\n";
s << " }\n";
s << " m_client->onEvent(origin, nullptr, eventName, callback);\n";
s << " m_client->onEvent(origin, eventName, callback);\n";
s << " return true;\n";
s << "}\n\n";
s << "bool " << className << "::on(const QString& eventName, EventCallback callback) {\n";
@@ -238,11 +239,11 @@ static QString makeSource(const QString& moduleName, const QString& className, c
s << " callback(data);\n";
s << " });\n";
s << "}\n\n";
s << "void " << className << "::setEventSource(QObject* source) {\n";
s << "void " << className << "::setEventSource(LogosObject* source) {\n";
s << " m_eventSource = source;\n";
s << "}\n\n";
s << "QObject* " << className << "::eventSource() const {\n";
s << " return m_eventSource.data();\n";
s << "LogosObject* " << className << "::eventSource() const {\n";
s << " return m_eventSource;\n";
s << "}\n\n";
s << "void " << className << "::trigger(const QString& eventName) {\n";
s << " trigger(eventName, QVariantList{});\n";
@@ -252,9 +253,9 @@ static QString makeSource(const QString& moduleName, const QString& className, c
s << " qWarning() << \"" << className << ": no event source set for trigger\" << eventName;\n";
s << " return;\n";
s << " }\n";
s << " m_client->onEventResponse(m_eventSource.data(), eventName, data);\n";
s << " m_client->onEventResponse(m_eventSource, eventName, data);\n";
s << "}\n\n";
s << "void " << className << "::trigger(const QString& eventName, QObject* source, const QVariantList& data) {\n";
s << "void " << className << "::trigger(const QString& eventName, LogosObject* source, const QVariantList& data) {\n";
s << " if (!source) {\n";
s << " qWarning() << \"" << className << ": cannot trigger\" << eventName << \"with null source\";\n";
s << " return;\n";
@@ -409,12 +410,11 @@ static QString makeCoreManagerHeader()
s << "#include <QVariant>\n";
s << "#include <QStringList>\n";
s << "#include <QJsonArray>\n";
s << "#include <QObject>\n";
s << "#include <QPointer>\n";
s << "#include <functional>\n";
s << "#include <utility>\n";
s << "#include \"logos_api.h\"\n";
s << "#include \"logos_api_client.h\"\n\n";
s << "#include \"logos_api_client.h\"\n";
s << "#include \"logos_object.h\"\n\n";
s << "class CoreManager {\n";
s << "public:\n";
s << " explicit CoreManager(LogosAPI* api);\n\n";
@@ -422,17 +422,17 @@ static QString makeCoreManagerHeader()
s << " using EventCallback = std::function<void(const QVariantList&)>;\n\n";
s << " bool on(const QString& eventName, RawEventCallback callback);\n";
s << " bool on(const QString& eventName, EventCallback callback);\n";
s << " void setEventSource(QObject* source);\n";
s << " QObject* eventSource() const;\n";
s << " void setEventSource(LogosObject* source);\n";
s << " LogosObject* eventSource() const;\n";
s << " void trigger(const QString& eventName);\n";
s << " void trigger(const QString& eventName, const QVariantList& data);\n";
s << " template<typename... Args>\n";
s << " void trigger(const QString& eventName, Args&&... args) {\n";
s << " trigger(eventName, packVariantList(std::forward<Args>(args)...));\n";
s << " }\n";
s << " void trigger(const QString& eventName, QObject* source, const QVariantList& data);\n";
s << " void trigger(const QString& eventName, LogosObject* source, const QVariantList& data);\n";
s << " template<typename... Args>\n";
s << " void trigger(const QString& eventName, QObject* source, Args&&... args) {\n";
s << " void trigger(const QString& eventName, LogosObject* source, Args&&... args) {\n";
s << " trigger(eventName, source, packVariantList(std::forward<Args>(args)...));\n";
s << " }\n\n";
s << " void initialize(int argc, char* argv[]);\n";
@@ -447,7 +447,7 @@ static QString makeCoreManagerHeader()
s << " bool unloadPlugin(const QString& pluginName);\n";
s << " QString processPlugin(const QString& filePath);\n\n";
s << "private:\n";
s << " QObject* ensureReplica();\n";
s << " LogosObject* ensureReplica();\n";
s << " template<typename... Args>\n";
s << " static QVariantList packVariantList(Args&&... args) {\n";
s << " QVariantList list;\n";
@@ -459,8 +459,8 @@ static QString makeCoreManagerHeader()
s << " LogosAPI* m_api;\n";
s << " LogosAPIClient* m_client;\n";
s << " QString m_moduleName;\n";
s << " QPointer<QObject> m_eventReplica;\n";
s << " QPointer<QObject> m_eventSource;\n";
s << " LogosObject* m_eventReplica = nullptr;\n";
s << " LogosObject* m_eventSource = nullptr;\n";
s << "};\n";
return h;
}
@@ -473,27 +473,27 @@ static QString makeCoreManagerSource(const QString& headerBaseName)
s << "#include <QDebug>\n";
s << "#include <QStringList>\n\n";
s << "CoreManager::CoreManager(LogosAPI* api) : m_api(api), m_client(api->getClient(\"core_manager\")), m_moduleName(QStringLiteral(\"core_manager\")) {}\n\n";
s << "QObject* CoreManager::ensureReplica() {\n";
s << "LogosObject* CoreManager::ensureReplica() {\n";
s << " if (!m_eventReplica) {\n";
s << " QObject* replica = m_client->requestObject(m_moduleName);\n";
s << " LogosObject* replica = m_client->requestObject(m_moduleName);\n";
s << " if (!replica) {\n";
s << " qWarning() << \"CoreManager: failed to acquire remote object for events on\" << m_moduleName;\n";
s << " return nullptr;\n";
s << " }\n";
s << " m_eventReplica = replica;\n";
s << " }\n";
s << " return m_eventReplica.data();\n";
s << " return m_eventReplica;\n";
s << "}\n\n";
s << "bool CoreManager::on(const QString& eventName, RawEventCallback callback) {\n";
s << " if (!callback) {\n";
s << " qWarning() << \"CoreManager: ignoring empty event callback for\" << eventName;\n";
s << " return false;\n";
s << " }\n";
s << " QObject* origin = ensureReplica();\n";
s << " LogosObject* origin = ensureReplica();\n";
s << " if (!origin) {\n";
s << " return false;\n";
s << " }\n";
s << " m_client->onEvent(origin, nullptr, eventName, callback);\n";
s << " m_client->onEvent(origin, eventName, callback);\n";
s << " return true;\n";
s << "}\n\n";
s << "bool CoreManager::on(const QString& eventName, EventCallback callback) {\n";
@@ -505,11 +505,11 @@ static QString makeCoreManagerSource(const QString& headerBaseName)
s << " callback(data);\n";
s << " });\n";
s << "}\n\n";
s << "void CoreManager::setEventSource(QObject* source) {\n";
s << "void CoreManager::setEventSource(LogosObject* source) {\n";
s << " m_eventSource = source;\n";
s << "}\n\n";
s << "QObject* CoreManager::eventSource() const {\n";
s << " return m_eventSource.data();\n";
s << "LogosObject* CoreManager::eventSource() const {\n";
s << " return m_eventSource;\n";
s << "}\n\n";
s << "void CoreManager::trigger(const QString& eventName) {\n";
s << " trigger(eventName, QVariantList{});\n";
@@ -519,9 +519,9 @@ static QString makeCoreManagerSource(const QString& headerBaseName)
s << " qWarning() << \"CoreManager: no event source set for trigger\" << eventName;\n";
s << " return;\n";
s << " }\n";
s << " m_client->onEventResponse(m_eventSource.data(), eventName, data);\n";
s << " m_client->onEventResponse(m_eventSource, eventName, data);\n";
s << "}\n\n";
s << "void CoreManager::trigger(const QString& eventName, QObject* source, const QVariantList& data) {\n";
s << "void CoreManager::trigger(const QString& eventName, LogosObject* source, const QVariantList& data) {\n";
s << " if (!source) {\n";
s << " qWarning() << \"CoreManager: cannot trigger\" << eventName << \"with null source\";\n";
s << " return;\n";
@@ -743,6 +743,222 @@ static bool writeUmbrellaSourceFromDeps(const QString& genDirPath, const QJsonAr
return true;
}
// ── Provider-header mode: scan LOGOS_METHOD markers and generate dispatch ────
struct ParsedMethod {
QString returnType;
QString name;
QVector<QPair<QString, QString>> params; // (type, name)
};
static QVector<ParsedMethod> parseProviderHeader(const QString& headerPath, QTextStream& err)
{
QVector<ParsedMethod> methods;
QFile file(headerPath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
err << "Cannot open header file: " << headerPath << "\n";
return methods;
}
QTextStream in(&file);
QRegularExpression re(
R"(^\s*LOGOS_METHOD\s+(.+?)\s+(\w+)\s*\(([^)]*)\)\s*;)"
);
while (!in.atEnd()) {
QString line = in.readLine();
auto match = re.match(line);
if (!match.hasMatch()) continue;
ParsedMethod m;
m.returnType = normalizeType(match.captured(1));
m.name = match.captured(2);
QString paramStr = match.captured(3).trimmed();
if (!paramStr.isEmpty()) {
QStringList paramParts = paramStr.split(',');
for (const QString& part : paramParts) {
QString trimmed = part.trimmed();
int eqIdx = trimmed.indexOf('=');
if (eqIdx > 0) trimmed = trimmed.left(eqIdx).trimmed();
int lastSpace = trimmed.lastIndexOf(' ');
int lastAmp = trimmed.lastIndexOf('&');
int splitAt = qMax(lastSpace, lastAmp);
if (splitAt > 0) {
QString type = normalizeType(trimmed.left(splitAt + 1));
QString pname = trimmed.mid(splitAt + 1).trimmed();
m.params.append({type, pname});
} else {
m.params.append({normalizeType(trimmed), QString("arg%1").arg(m.params.size())});
}
}
}
methods.append(m);
}
file.close();
return methods;
}
static QString toQVariantConversion(const QString& type, const QString& argExpr)
{
if (type == "int") return argExpr + ".toInt()";
if (type == "bool") return argExpr + ".toBool()";
if (type == "double") return argExpr + ".toDouble()";
if (type == "float") return argExpr + ".toFloat()";
if (type == "QString") return argExpr + ".toString()";
if (type == "QStringList") return argExpr + ".toStringList()";
if (type == "QJsonArray") return "qvariant_cast<QJsonArray>(" + argExpr + ")";
if (type == "QVariant") return argExpr;
if (type == "LogosResult") return argExpr + ".value<LogosResult>()";
return argExpr + ".toString()";
}
static int generateProviderDispatch(const QString& headerPath, const QString& outputDir, QTextStream& out, QTextStream& err)
{
QFileInfo fi(headerPath);
if (!fi.exists()) {
err << "Header file does not exist: " << headerPath << "\n";
return 2;
}
QVector<ParsedMethod> methods = parseProviderHeader(headerPath, err);
if (methods.isEmpty()) {
err << "No LOGOS_METHOD markers found in: " << headerPath << "\n";
return 3;
}
// Derive the class name from the header: parse for ": public LogosProviderBase"
QString className;
{
QFile f(headerPath);
f.open(QIODevice::ReadOnly | QIODevice::Text);
QTextStream ts(&f);
QRegularExpression classRe(R"(class\s+(\w+)\s*:\s*public\s+LogosProviderBase)");
while (!ts.atEnd()) {
QString line = ts.readLine();
auto m = classRe.match(line);
if (m.hasMatch()) {
className = m.captured(1);
break;
}
}
f.close();
}
if (className.isEmpty()) {
err << "Could not find class inheriting LogosProviderBase in: " << headerPath << "\n";
return 4;
}
QString headerBaseName = fi.fileName();
QString genDirPath = outputDir.isEmpty() ? fi.absolutePath() : outputDir;
QDir().mkpath(genDirPath);
// Generate logos_provider_dispatch.cpp
QString content;
QTextStream s(&content);
s << "// AUTO-GENERATED by logos-cpp-generator -- do not edit\n";
s << "#include \"" << headerBaseName << "\"\n";
s << "#include <QJsonArray>\n";
s << "#include <QJsonObject>\n";
s << "#include <QVariant>\n";
s << "#include <QString>\n";
s << "#include \"logos_types.h\"\n\n";
// callMethod() — group by name to support overloaded methods
QMap<QString, QVector<const ParsedMethod*>> methodsByName;
for (const ParsedMethod& m : methods) {
methodsByName[m.name].append(&m);
}
s << "QVariant " << className << "::callMethod(const QString& methodName, const QVariantList& args)\n";
s << "{\n";
for (auto it = methodsByName.constBegin(); it != methodsByName.constEnd(); ++it) {
const QString& name = it.key();
const QVector<const ParsedMethod*>& overloads = it.value();
s << " if (methodName == \"" << name << "\") {\n";
bool needArgsSizeCheck = overloads.size() > 1;
for (const ParsedMethod* m : overloads) {
if (needArgsSizeCheck) {
s << " if (args.size() == " << m->params.size() << ") {\n";
s << " ";
}
if (m->returnType == "void" || m->returnType.isEmpty()) {
s << " " << m->name << "(";
for (int i = 0; i < m->params.size(); ++i) {
s << toQVariantConversion(m->params[i].first, QString("args.at(%1)").arg(i));
if (i + 1 < m->params.size()) s << ", ";
}
s << ");\n";
if (needArgsSizeCheck) s << " ";
s << " return QVariant(true);\n";
} else {
s << " return QVariant::fromValue(" << m->name << "(";
for (int i = 0; i < m->params.size(); ++i) {
s << toQVariantConversion(m->params[i].first, QString("args.at(%1)").arg(i));
if (i + 1 < m->params.size()) s << ", ";
}
s << "));\n";
}
if (needArgsSizeCheck) {
s << " }\n";
}
}
s << " }\n";
}
s << " qWarning() << \"" << className << "::callMethod: unknown method:\" << methodName;\n";
s << " return QVariant();\n";
s << "}\n\n";
// getMethods()
s << "QJsonArray " << className << "::getMethods()\n";
s << "{\n";
s << " QJsonArray methods;\n";
for (const ParsedMethod& m : methods) {
s << " {\n";
s << " QJsonObject obj;\n";
s << " obj[\"name\"] = QStringLiteral(\"" << m.name << "\");\n";
s << " obj[\"returnType\"] = QStringLiteral(\"" << m.returnType << "\");\n";
s << " obj[\"isInvokable\"] = true;\n";
QString sig = m.name + "(";
for (int i = 0; i < m.params.size(); ++i) {
sig += m.params[i].first;
if (i + 1 < m.params.size()) sig += ",";
}
sig += ")";
s << " obj[\"signature\"] = QStringLiteral(\"" << sig << "\");\n";
if (!m.params.isEmpty()) {
s << " QJsonArray params;\n";
for (int i = 0; i < m.params.size(); ++i) {
s << " params.append(QJsonObject{{\"type\", QStringLiteral(\"" << m.params[i].first << "\")}, {\"name\", QStringLiteral(\"" << m.params[i].second << "\")}});\n";
}
s << " obj[\"parameters\"] = params;\n";
}
s << " methods.append(obj);\n";
s << " }\n";
}
s << " return methods;\n";
s << "}\n";
QString outputPath = QDir(genDirPath).filePath("logos_provider_dispatch.cpp");
QFile outFile(outputPath);
if (!outFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
err << "Failed to write dispatch file: " << outputPath << "\n";
return 5;
}
outFile.write(content.toUtf8());
outFile.close();
out << "Generated provider dispatch: " << outputPath << " (" << methods.size() << " methods from " << className << ")\n";
out.flush();
return 0;
}
static int generateFromPlugin(const QString& pluginInputPath, const QString& outputDir, bool moduleOnly, QTextStream& out, QTextStream& err)
{
QFileInfo fi(pluginInputPath);
@@ -784,7 +1000,21 @@ static int generateFromPlugin(const QString& pluginInputPath, const QString& out
}
}
QJsonArray methods = enumerateMethods(instance);
QJsonArray methods;
LogosProviderPlugin* providerPlugin = qobject_cast<LogosProviderPlugin*>(instance);
if (providerPlugin) {
LogosProviderObject* provider = providerPlugin->createProviderObject();
if (provider) {
methods = provider->getMethods();
out << "Detected new-API plugin (LogosProviderPlugin), using getMethods() — "
<< methods.size() << " methods\n";
delete provider;
} else {
err << "LogosProviderPlugin::createProviderObject() returned null\n";
}
} else {
methods = enumerateMethods(instance);
}
QString className = toPascalCase(moduleName);
QString headerRel = QString("%1_api.h").arg(moduleName);
@@ -991,10 +1221,25 @@ int main(int argc, char* argv[])
}
}
// --provider-header mode: scan LOGOS_METHOD markers and generate dispatch code
{
const int phIdx = args.indexOf("--provider-header");
if (phIdx != -1) {
if (phIdx + 1 >= args.size()) {
err << "Usage: " << QFileInfo(app.applicationFilePath()).fileName() << " --provider-header /path/to/impl.h [--output-dir /path/to/output]\n";
return 1;
}
QString headerArg = args.at(phIdx + 1);
if (headerArg.startsWith('@')) headerArg.remove(0, 1);
return generateProviderDispatch(headerArg, outputDir, out, err);
}
}
if (args.size() < 2) {
err << "Usage: " << QFileInfo(app.applicationFilePath()).fileName() << " /absolute/path/to/plugin [--output-dir /path/to/output] [--module-only]\n";
err << " or: " << QFileInfo(app.applicationFilePath()).fileName() << " --metadata /absolute/path/to/metadata.json [--output-dir /path/to/output] [--module-only] [--general-only]\n";
err << " or: " << QFileInfo(app.applicationFilePath()).fileName() << " --metadata /absolute/path/to/metadata.json --general-only [--output-dir /path/to/output]\n";
err << " or: " << QFileInfo(app.applicationFilePath()).fileName() << " --provider-header /path/to/impl.h [--output-dir /path/to/output]\n";
return 1;
}