Schreiben eines benutzerdefinierten Qt-3D-Aspekts - Teil 2

Einführung

Im vorherigen Artikel haben wir den Erstellungsprozess für benutzerdefinierte Aspekte überprüft und gezeigt, wie (die meisten) Frontend-Funktionen erstellt werden. In diesem Artikel bauen wir unsere Benutzerfacette weiter auf, indem wir die entsprechenden Backend-Typen implementieren, die Typen registrieren und die Beziehung zwischen Frontend-Objekten und Backend-Objekten einrichten. Dies wird den größten Teil dieses Artikels einnehmen. Im nächsten Artikel sehen wir uns an, wie man Jobs implementiert, um die Komponenten unseres Aspekts zu verarbeiten.

Zur Erinnerung an das, was wir im Sinn haben, hier ist das Architekturdiagramm aus Teil 1:


Erstellen Sie ein Backend

Eines der schönen Dinge an Qt 3D ist, dass es zu einem sehr hohen Durchsatz fähig ist. Dies wird durch die Verwendung von Jobs erreicht, die auf einem Thread-Pool im Backend ausgeführt werden. Um dies tun zu können, ohne ein kompliziertes Netzwerk von Synchronisationspunkten einzuführen (was die Parallelität einschränken würde), erstellen wir die klassische Computersituation mit Kompromissen und Speicheropfern im Interesse der Geschwindigkeit nach. Indem jeder Aspekt an seiner eigenen Kopie der Daten arbeitet, kann er Jobs sicher planen, da er weiß, dass nichts anderes seine Daten berührt.

Es ist nicht so teuer wie es klingt. Backend-Knoten werden nicht von QObject abgeleitet. Die Basisklasse für Backend-Knoten ist Qt3DCore::QBackendNode , eine ziemlich einfache Klasse . Beachten Sie auch, dass Aspekte nur die Daten speichern, die sie im Backend benötigen. Dem Animationsaspekt ist es beispielsweise egal, welche Material -Komponente Entity hat, sodass keine Daten dieser Komponente gespeichert werden müssen. Im Gegensatz dazu betrifft der Rendering-Aspekt keine Animationsclips oder Animator -Komponenten.

In unserem kleinen benutzerdefinierten Aspekt haben wir nur einen Frontend-Komponententyp, FpsMonitor . Logischerweise haben wir nur einen entsprechenden Backend-Typ, den wir bildlich FpsMonitorBacken nennen:

fpsmonitorbackend.h

class FpsMonitorBackend : public Qt3DCore::QBackendNode
{
public:
    FpsMonitorBackend()
       : Qt3DCore::QBackendNode(Qt3DCore::QBackendNode::ReadWrite)
       , m_rollingMeanFrameCount(5)
    {}

private:
    void initializeFromPeer(const Qt3DCore::QNodeCreatedChangeBasePtr &change) override
    {
        // TODO: Implement me!
    }

    int m_rollingMeanFrameCount;
};

Die Klassendeklaration ist sehr einfach. Wir erben von Qt3DCore::QBackendNode , wie Sie es erwarten würden; fügen Sie ein Datenelement hinzu, um Informationen von der Front-End-Komponente FpsMonitor widerzuspiegeln; und überschreibe die virtuelle Funktion initializeFromPeer() . Diese Funktion wird direkt aufgerufen, nachdem Qt 3D unseren Backend-Typ instanziiert hat. Das Argument ermöglicht es uns, die vom entsprechenden Frontend-Objekt gesendeten Daten zu erhalten, wie wir gleich sehen werden.

Registrierung von Typen

Wir haben jetzt einfache Implementierungen von Frontend- und Backend-Komponenten. Der nächste Schritt besteht darin, sie beim Aspekt zu registrieren, damit dieser weiß, wie der Backend-Knoten zu instanziieren ist, wenn ein Frontend-Knoten erstellt wird. Ebenso zur Vernichtung. Wir tun dies mit einem Zwischenhelfer, der als Node-Matcher bekannt ist.

Um einen Node-Mapper zu erstellen, erben wir von Qt3DCore::QNodeMapper und überschreiben virtuelle Funktionen zum Erstellen, Finden und Zerstören von Backend-Objekten bei Bedarf. Wie Sie Objekte erstellen, speichern, finden und zerstören, liegt ganz bei Ihnen als Entwickler. Qt 3D zwingt Ihnen kein bestimmtes Steuerungsschema auf. Der Rendering-Aspekt macht einige ziemlich ausgefallene Sachen mit verwalteten Speichermanagern und Speicherausrichtung für SIMD-Typen, aber wir können hier etwas viel Einfacheres tun.

Wir speichern Zeiger auf Backend-Knoten in QHash innerhalb von CustomAspect und indizieren sie mithilfe des Qt3DCore::QNodeId -Knotens. Die Knoten-ID wird verwendet, um einen bestimmten Knoten eindeutig zu identifizieren, auch zwischen dem Frontend und allen verfügbaren Backends. In Qt3DCore::QNode ist die Kennung über die Funktion id() verfügbar, während für QBackendNode Sie greifen darauf über die Funktion peerId() zu. Für zwei entsprechende Objekte, die eine Komponente darstellen, geben die Funktionen id() und peerId() denselben QNodeId -Wert zurück.

Lassen Sie uns fortfahren und etwas Speicher für die Backend-Knoten in CustomAspect zusammen mit einigen Hilfsfunktionen hinzufügen:

customaspect.h

class CustomAspect : public Qt3DCore::QAbstractAspect
{
    Q_OBJECT
public:
    ...
    void addFpsMonitor(Qt3DCore::QNodeId id, FpsMonitorBackend *fpsMonitor)
    {
        m_fpsMonitors.insert(id, fpsMonitor);
    }

    FpsMonitorBackend *fpsMonitor(Qt3DCore::QNodeId id)
    {
        return m_fpsMonitors.value(id, nullptr);
    }

    FpsMonitorBackend *takeFpsMonitor(Qt3DCore::QNodeId id)
    {
        return m_fpsMonitors.take(id);
    }
    ...

private:
    QHash<Qt3DCore::QNodeId, FpsMonitorBackend *> m_fpsMonitors;
};

Wir können jetzt einen einfachen Node-Matcher wie folgt implementieren:

fpsmonitorbackend.h

class FpsMonitorMapper : public Qt3DCore::QBackendNodeMapper
{
public:
    explicit FpsMonitorMapper(CustomAspect *aspect);

    Qt3DCore::QBackendNode *create(const Qt3DCore::QNodeCreatedChangeBasePtr &change) const override
    {
        auto fpsMonitor = new FpsMonitorBackend;
        m_aspect->addFpsMonitor(change->subjectId(), fpsMonitor);
        return fpsMonitor;
    }

    Qt3DCore::QBackendNode *get(Qt3DCore::QNodeId id) const override
    {
        return m_aspect->fpsMonitor(id);
    }

    void destroy(Qt3DCore::QNodeId id) const override
    {
        auto fpsMonitor = m_aspect->takeFpsMonitor(id);
        delete fpsMonitor;
    }

private:
    CustomAspect *m_aspect;
};

Um dieses Puzzleteil zu vervollständigen, müssen wir darüber sprechen, wie diese Typen und Matcher miteinander in Beziehung stehen. Dazu rufen wir die Vorlagenfunktion QAbstractAspect::registerBackendType() auf und übergeben einen gemeinsam genutzten Zeiger an den zu erstellenden Matcher , finden und zerstören Sie die entsprechenden Backend-Knoten. Das Template-Argument ist der Typ des Frontend-Knotens, auf dem dieser Matcher aufgerufen werden soll. Ein geeigneter Ort dafür ist der CustomAspect-Konstruktor. In unserem Fall sieht das so aus:

customaspect.cpp

CustomAspect::CustomAspect(QObject *parent)
    : Qt3DCore::QAbstractAspect(parent)
{
    // Register the mapper to handle creation, lookup, and destruction of backend nodes
    auto mapper = QSharedPointer<FpsMonitorMapper>::create(this);
    registerBackendType<FpsMonitor>(mapper);
}

Das ist alles! Wenn die FpsMonitor -Bean mit dieser direkten Registrierung zur Frontend-Objektstruktur (Szene) hinzugefügt wird, sucht der Aspekt nach einem Node-Mapper für diesen Objekttyp. Hier findet es unser registriertes FpsMonitorMapper -Objekt und ruft seine create() -Funktion auf, um den Backend-Knoten zu erstellen und seinen Speicher zu verwalten. Die Geschichte ist ähnlich mit der Zerstörung (technisch gesehen ist dies die Entfernung von der Szene) des Front-End-Knotens. Die get() -Funktion des Resolvers wird intern verwendet, um die virtuellen Funktionen des Backend-Knotens zu geeigneten Zeiten aufrufen zu können (z. B. wenn Eigenschaften melden, dass sie geändert wurden).

Front-Back-Kommunikation

Jetzt, da wir den Backend-Knoten eines beliebigen Frontend-Knotens erstellen, darauf zugreifen und zerstören können, sehen wir uns an, wie wir sie miteinander kommunizieren lassen können. Es gibt drei Hauptpunkte, wenn Front-End- und Back-End-Knoten miteinander kommunizieren:

  1. Initialisierung – Wenn unser Backend-Knoten erstellt wird, haben wir die Möglichkeit, ihn mit den vom Frontend-Knoten gesendeten Daten zu initialisieren.
  2. Von Frontend zu Backend – Wenn sich Eigenschaften auf dem Frontend-Knoten ändern, möchten wir normalerweise den neuen Wert der Eigenschaft an den Backend-Knoten senden, damit er mit aktuellen Informationen arbeitet.
  3. Vom Backend zum Frontend - Wenn unsere Jobs Daten verarbeiten, die in Backend-Knoten gespeichert sind, kann es vorkommen, dass dies zu aktualisierten Werten führt, die an den Frontend-Knoten gesendet werden müssen.

Hier betrachten wir die ersten beiden Fälle. Der dritte Fall wird auf den nächsten Artikel verschoben, wenn wir die Aufgaben präsentieren.

Initialisieren Sie den Backend-Knoten

Die gesamte Kommunikation zwischen Frontend- und Backend-Objekten erfolgt durch Senden einer Unterklasse von Qt3DCore::QSceneChanges . Sie ähneln in Art und Konzept QEvent , aber der Änderungsarbiter, der die Änderungen handhabt, hat die Fähigkeit, sie im Falle von Konflikten aus mehreren Aspekten zu manipulieren, sie auf die Priorität neu auszurichten oder andere Manipulationen vorzunehmen, die möglicherweise erforderlich sind die Zukunft.

Um den Backend-Knoten bei der Erstellung zu initialisieren, verwenden wir Qt3DCore::QNodeCreatedChange . Dies ist eine Vorlage, mit der wir Daten eines bestimmten Typs umschließen können. Wenn Qt 3D das Backend über den Anfangszustand Ihres Frontend-Knotens informieren möchte, ruft es die private virtuelle Funktion QNode::createNodeCreationChange() auf. Diese Funktion gibt eine vom Knoten erstellte Änderung zurück, die alle Informationen enthält, auf die wir im Backend-Knoten zugreifen möchten. Wir müssen dies tun, indem wir die Daten kopieren, anstatt einfach den Zeiger auf das Frontend-Objekt zu dereferenzieren, denn bis das Backend die Anfrage verarbeitet, kann das Frontend-Objekt gelöscht sein – ein klassisches Datenrennen. Für unsere einfache Komponente sieht unsere Implementierung so aus:

fpsmonitor.h

struct FpsMonitorData
{
    int rollingMeanFrameCount;
};

fpsmonitor.cpp

Qt3DCore::QNodeCreatedChangeBasePtr FpsMonitor::createNodeCreationChange() const
{
    auto creationChange = Qt3DCore::QNodeCreatedChangePtr<FpsMonitorData>::create(this);
    auto &data = creationChange->data;
    data.rollingMeanFrameCount = m_rollingMeanFrameCount;
    return creationChange;
}

Die von unserem Frontend-Knoten erstellte Änderung wird an den Backend-Knoten (über den Änderungsarbiter) weitergegeben und von der virtuellen Funktion initializeFromPeer() verarbeitet:

fpsmonitorbackend.cpp

void FpsMonitorBackend::initializeFromPeer(const Qt3DCore::QNodeCreatedChangeBasePtr &change)
{
    const auto typedChange = qSharedPointerCast<Qt3DCore::QNodeCreatedChange<FpsMonitorData>>(change);
    const auto &data = typedChange->data;
    m_rollingMeanFrameCount = data.rollingMeanFrameCount;
}

Kommunikations-Frontend mit Backend

An diesem Punkt spiegelt der Backend-Knoten den Anfangszustand des Frontend-Knotens wider. Was aber, wenn der Benutzer eine Eigenschaft im Frontend-Knoten ändert? In diesem Fall speichert unser Backend-Knoten veraltete Daten.

Die gute Nachricht ist, dass dies einfach zu handhaben ist. Die Qt3DCore::QNode -Implementierung erledigt für uns die erste Hälfte des Problems. Intern lauscht es auf Q_PROPERTY-Benachrichtigungssignale, und wenn es sieht, dass sich eine Eigenschaft geändert hat, erstellt es eine [QPropertyUpdatedChange] für uns (http://code.qt.io/cgit/qt/qt3d.git/tree/src/ core/changes/qpropertyupdatedchange.h#n51) und sendet sie an den Änderungsarbiter, der sie wiederum an die sceneChangeEvent() -Funktion im Backend-Knoten liefert.

Alles, was wir als Autoren des Backend-Knotens tun müssen, ist diese Funktion zu überschreiben, die Daten aus dem Änderungsobjekt abzurufen und unseren internen Status zu aktualisieren. Oft möchten Sie den Backend-Knoten auf irgendeine Weise markieren, damit der Aspekt weiß, dass er im nächsten Frame verarbeitet werden muss. Hier aktualisieren wir jedoch nur den Status, um den neuesten Wert vom Frontend anzuzeigen:

fpsmonitorbackend.cpp

void FpsMonitorBackend::sceneChangeEvent(const Qt3DCore::QSceneChangePtr &e)
{
    if (e->type() == Qt3DCore::PropertyUpdated) {
        const auto change = qSharedPointerCast<Qt3DCore::QPropertyUpdatedChange>(e);
        if (change->propertyName() == QByteArrayLiteral("rollingMeanFrameCount")) {
            const auto newValue = change->value().toInt();
            if (newValue != m_rollingMeanFrameCount) {
                m_rollingMeanFrameCount = newValue;
                // TODO: Update fps calculations
            }
            return;
        }
    }
    QBackendNode::sceneChangeEvent(e);
}

Wenn Sie den integrierten automatischen Versand von Eigenschaftsänderungen von Qt3DCore::QNode nicht verwenden möchten, können Sie ihn deaktivieren, indem Sie die Ausgabe eines Eigenschaftsbenachrichtigungssignals umbrechen, wenn [QNode::blockNotifications()] aufgerufen wird (https ://doc.qt.io/qt-5/qt3dcore-qnode.html#blockNotifications). Dies funktioniert genau wie QObject::blockSignals() , außer dass es nur blockiert, dass Benachrichtigungen an den zugrunde liegenden Knoten gesendet werden, nicht das Signal selbst. Das bedeutet, dass andere Verbindungen oder Eigenschaftsbindungen, die auf Ihre Signale angewiesen sind, weiterhin funktionieren.

Wenn Sie Benachrichtigungen standardmäßig auf diese Weise blockieren, müssen Sie sie senden, um sicherzustellen, dass der zugrunde liegende Knoten über aktualisierte Informationen verfügt. Fühlen Sie sich frei, von jeder Klasse in der Qt3DCore::QSceneChange -Hierarchie zu erben und sie an Ihre Bedürfnisse anzupassen. Der allgemeine Ansatz besteht darin, Qt3DCore::QStaticPropertyUpdatedChangeBase zu erben, das den Eigenschaftsnamen und behandelt in einer Unterklasse fügt ein stark typisiertes Klassenelement zur Eigenschaft payload value hinzu. Der Vorteil gegenüber dem eingebauten Mechanismus besteht darin, dass die Verwendung von QVariant vermieden wird, das in Bezug auf die Leistung etwas unter Kontexten mit vielen Threads leidet. Normalerweise ändern sich Frontend-Eigenschaften nicht zu oft, und das ist standardmäßig in Ordnung.

Fazit

In diesem Artikel haben wir gezeigt, wie die meisten Backend-Knoten implementiert werden. wie man einen Node-Mapper registriert, um Backend-Knoten zu erstellen, zu finden und zu zerstören; wie man einen Backend-Knoten sicher von einem Frontend-Knoten aus initialisiert und wie man seine Daten mit dem Frontend synchronisiert.
Im nächsten Artikel werden wir endlich unsere Benutzerseite erledigen, tatsächlich etwas echte Arbeit leisten und lernen, wie man den Backend-Knoten dazu bringt, Aktualisierungen an den Frontend-Knoten zu senden (durchschnittliche fps). Wir stellen sicher, dass die schweren Teile im Kontext des Qt-3D-Thread-Pools ausgeführt werden, damit Sie verstehen, wie er skaliert werden kann. Bis bald.

Artikel geschrieben von: Sean Harmer | Mittwoch, 13. Dezember 2017

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Stabiles Hosting des sozialen Netzwerks EVILEG. Wir empfehlen VDS-Hosting für Django-Projekte.

Magst du es? In sozialen Netzwerken teilen!

Kommentare

Nur autorisierte Benutzer können Kommentare posten.
Bitte Anmelden oder Registrieren
Letzte Kommentare
ИМ
Игорь Максимов5. Oktober 2024 07:51
Django – Lektion 064. So schreiben Sie eine Python-Markdown-Erweiterung Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55. Juli 2024 11:02
QML - Lektion 016. SQLite-Datenbank und das Arbeiten damit in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssr8. Februar 2024 18:43
Qt Linux - Lektion 001. Autorun Qt-Anwendung unter Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lektion 007. Arbeiten mit ICMP-Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25. Dezember 2023 10:30
Boost - statisches Verknüpfen im CMake-Projekt unter Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
Jetzt im Forum diskutieren
J
JacobFib17. Oktober 2024 03:27
добавить qlineseries в функции Пользователь может получить любые разъяснения по интересующим вопросам, касающимся обработки его персональных данных, обратившись к Оператору с помощью электронной почты https://topdecorpro.ru…
JW
Jhon Wick1. Oktober 2024 15:52
Indian Food Restaurant In Columbus OH| Layla’s Kitchen Indian Restaurant If you're looking for a truly authentic https://www.laylaskitchenrestaurantohio.com/ , Layla’s Kitchen Indian Restaurant is your go-to destination. Located at 6152 Cleveland Ave, Colu…
КГ
Кирилл Гусарев27. September 2024 09:09
Не запускается программа на Qt: точка входа в процедуру не найдена в библиотеке DLL Написал программу на C++ Qt в Qt Creator, сбилдил Release с помощью MinGW 64-bit, бинарнику напихал dll-ки с помощью windeployqt.exe. При попытке запуска моей сбилженной программы выдаёт три оши…
F
Fynjy22. Juli 2024 04:15
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …

Folgen Sie uns in sozialen Netzwerken