Behebung einer langsamen Festplatte in Windows 10

Dieser Hinweis richtet sich an alle Festplattennutzer, die nicht aufgeben.


Original (Mae Mu)

Nachdem ich den HP Pavilion Laptop 1,5 Jahre lang mit einer Dual-HDD (Windows 10) und SSD (Ubuntu) verwendet hatte, bemerkte ich sehr lange Ladezeiten für Anwendungen, eine allgemeine Nichtreaktion der Benutzeroberfläche und ein Einfrieren bei den einfachsten Vorgängen in Windows 10. Das Problem wurde soweit minimiert, dass der Laptop wieder verwendet werden konnte. Als Nächstes beschreibe ich die Schritte, die ich unternommen habe, um das Problem zu beheben.

Diagnose

Um mit der Forschung zu beginnen, müssen wir zunächst jede Art von Falschmeldung beseitigen. Lassen Sie uns die Hauptursachen für Festplattenausfälle ermitteln. Was kann beim Arbeiten mit einer Festplatte schiefgehen? Probleme können auf der physischen Ebene der Elektronik und auf der logischen Software-Datenebene auftreten.
Zu den elektronischen Problemen gehören beispielsweise: ein nicht funktionierendes Computer-/Laptop-Netzteil, Probleme mit dem Laptop-Akku; Abnutzung von Festplattenkomponenten, Probleme in den Schaltkreisen und Chips der internen Komponenten des Laufwerks, Firmware-Fehler, Folgen von Stößen/Stürzen des Laufwerks oder ähnliche Probleme mit anderen Geräten, die seinen Betrieb beeinträchtigen.
Als kritischer Verschleiß einer Festplatte gilt der Moment, in dem so viele fehlerhafte Sektoren (Bad Block) auftreten, dass ein weiterer Betrieb der Festplatte unmöglich ist. Diese Blöcke werden von der Festplatten-Firmware blockiert, die Daten werden automatisch in andere Sektoren übertragen und sollten den Betrieb der Festplatte bis zu einem bestimmten kritischen Moment nicht beeinträchtigen.
Zu den Programmlogikproblemen gehören Fehler im Dateisystem aufgrund fehlerhafter Ausführung von Anwendungen, Benutzeraktionen: Ausschalten des Geräts im heißen Zustand, Abschließen von Aufzeichnungsvorgängen ohne ordnungsgemäßes Stoppen von Anwendungen, Fehler in Treibern und Betriebssystemdiensten.
Ohne spezielle elektronische Diagnosetools können wir nur die Richtigkeit des Softwarestands überprüfen. Dabei können elektronische Probleme entdeckt werden, die normalerweise durch die Blockreparaturmethode (Austausch von Komponenten/Chips) behoben werden. Als nächstes betrachten wir Softwarediagnosemethoden mithilfe von Diagnosedienstprogrammen. Es ist zu beachten, dass alle Dienstprogramme mit höchster Priorität auf dem System gestartet werden müssen, denn Andere Anwendungen können die Leistungsmessung beeinträchtigen und die Festplatte beim Lesen/Schreiben blockieren, was zu falschen Diagnoseergebnissen führt.

SMART

S.M.A.R.T. Speichergeräte-Statusüberwachungssystem – HDD, SDD, eMMC usw. Ermöglicht Ihnen, den Verschleiß des Geräts zu bewerten, die Anzahl fehlerhafter Blöcke anzuzeigen und basierend auf den Daten weitere Maßnahmen zu ergreifen. Sie können SMART in verschiedenen Anwendungen zum Arbeiten mit Datenträgern anzeigen. Ich bevorzuge die Verwendung von Dienstprogrammen des Herstellers. Für meine Seagate-Festplatte habe ich das Dienstprogramm SeaTools verwendet, dessen Status als GUT angezeigt wurde, d. h. die Festplatten-Firmware geht davon aus, dass alles in Ordnung ist.

Dienstprogramme des Herstellers

Die Dienstprogramme des Festplattenherstellers bieten Tests zur Überprüfung der Funktionsfähigkeit an. SeaTools verfügt über mehrere Arten von Tests. Sie können sie alle verwenden, um das Problem zu lokalisieren. Schnelle und einfache Tests offenbaren möglicherweise keine Probleme, bevorzugen Sie daher lange Tests. In meinem Fall hat nur Long Test Fehler gefunden.

Slowride

Um die Richtigkeit des Lesens zu überprüfen und langsame oder tote Blöcke zu finden, habe ich eine Anwendung geschrieben slowride, es funktioniert nach einem sehr einfachen Prinzip – öffnet einen Block-Gerätedeskriptor, mit den angegebenen Benutzereinstellungen, liest die Daten des gesamten Geräts, mit Zeitmessungen, Ausgabe langsamer Blöcke. Das Programm stoppt beim ersten Fehler; in diesem Fall müssen Sie zu ernsthafteren Dienstprogrammen zum Entfernen von Daten übergehen, da es nicht möglich ist, die Festplattendaten mit einfachen Methoden zu lesen.
In meinem Fall wurde das Lesen der gesamten Festplatte korrekt durchgeführt, mit einem leichten Geschwindigkeitsabfall – 90 MB/Sek. (5400 U/min) in einer Sekunde in einigen Bereichen der Festplatte. Daraus könnte man schließen, dass es sich bei mir um ein Softwareproblem handelte.

Akustische Analyse

Diese Methode gilt nicht für Softwarediagnosemethoden, ist aber sehr wichtig, um das Problem zu beheben. Wenn beispielsweise das Netzteil teilweise funktioniert, kann es sein, dass die Festplatte einfriert/einfriert und ein lautes Klicken von sich gibt.
In meinem Fall hörte ich beim Arbeiten mit einer Festplatte in Windows 10 etwas, das allen Festplattenbesitzern bekannt ist:
lautes Knackgeräusch des Plattenkopfes, der hin und her läuft, wenn versucht wird, etwas im Betriebssystem zu tun, aber das Geräusch war fast konstant, das ließ mich denken, dass es zu viel Fragmentierung gab Festplatte, Festplattenüberlastung durch Hintergrunddienste.

Reparatur

Während der Softwarediagnose wurden keine Elektronikprobleme festgestellt; das blockweise Lesen der gesamten Festplatte wurde korrekt abgeschlossen, aber SeaTools zeigte während des Langzeittests Fehler.

Dienstprogramme des Herstellers

Zusätzlich zur Diagnose bietet die Software des Festplattenherstellers Fehlerkorrekturverfahren. In SeaTools ist hierfür die Schaltfläche „Alle reparieren“ zuständig; nach Bestätigung Ihrer Zustimmung zum möglichen Datenverlust beginnt der Korrekturvorgang. Hat dieser Fix in meinem Fall geholfen? Nein, die Festplatte arbeitete weiterhin laut und langsam, aber der Langzeittest zeigte keine Fehler mehr.

CHKDSK

CHKSDK ist ein Microsoft-Dienstprogramm zur Fehlerbehebung von Softwarefehlern für Windows-Dateisysteme. Mit der Zeit sammeln sich solche Fehler auf der Festplatte an und können die Arbeit stark beeinträchtigen, bis hin zur Unfähigkeit, überhaupt Daten zu lesen/schreiben. Anweisungen zur Verwendung des Dienstprogramms finden Sie auf der Microsoft-Website. Ich empfehle jedoch, alle möglichen Flags zu verwenden, um Fehler zu beheben (zum Zeitpunkt des Verfassens dieses Artikels ist dies /r /b /f); Sie müssen den Scan mit Administratorrechten über das Windows-Terminal (cmd) ausführen. Für die Systempartition erfolgt er beim Systemstart und kann sehr lange dauern, in meinem Fall dauerte er 12 Stunden.
Hat dieser Fix in meinem Fall geholfen? Nein.

Festplattendefragmentierung

Daten auf der Festplatte werden in Blöcken verarbeitet; große Dateien werden normalerweise in mehreren Blöcken/Fragmenten geschrieben. Im Laufe der Zeit erzeugen viele gelöschte Dateien leere Blöcke, die nicht in der Nähe sind. Aus diesem Grund füllen sie beim Schreiben von Dateien diese Lücken und der Plattenkopf muss physisch weite Strecken zurücklegen. Dieses Problem wird Fragmentierung genannt und tritt nur bei Festplattenbenutzern auf. Zum Zeitpunkt mehrerer Korrekturen betrug die Fragmentierung meiner Festplatte 41 %, optisch sah es so aus:

Das heißt, alles ist schlecht. Sie können die Fragmentierung erkennen und mit dem Defragmentierungsprogramm oder dem integrierten Defragmentierer defragmentieren. Sie können auch den Dienst „Laufwerke optimieren“ aktivieren. Planen Sie in Windows 10 die Defragmentierung in der Systemsteuerung. Nur HDD-Laufwerke benötigen eine Defragmentierung; es ist nicht ratsam, sie für SSD-Laufwerke zu aktivieren, da dies zu einem beschleunigten Festplattenverschleiß führt. Aus diesem Grund ist die Hintergrunddefragmentierung offenbar standardmäßig deaktiviert.

Eine alternative Defragmentierungsoption ist ebenfalls bekannt – Übertragen von Daten auf eine andere Festplatte, Formatieren der Festplatte und Zurückkopieren der Daten. In diesem Fall werden die Daten in völlig leere Sektoren geschrieben, während die korrekte logische Struktur für den Systembetrieb beibehalten wird. Diese Option ist mit Problemen beim Zurücksetzen potenziell kritischer Metadaten behaftet, die beim normalen Kopieren möglicherweise nicht verschoben werden.

Dienste deaktivieren

Verwenden des Dienstprogramms Process Monitor von Mark Russinovich Sie können die Prozesse verfolgen, die die Festplatte mit ihrer Arbeit belasten, indem Sie einfach die Spalten „IO Write/Read“ aktivieren. Nachdem ich diese Kolumne recherchiert hatte, habe ich den Xbox Game Bar-Dienst, den bekannten Hintergrundbeschleunigungsdienst für Superfetch-Programme unter dem neuen Namen SysMain, über das Bedienfeld „Dienste“ deaktiviert. Superfetch muss ständig die vom Benutzer verwendeten Anwendungen analysieren und deren Start durch Zwischenspeichern im RAM beschleunigen. Dies führte in meinem Fall dazu, dass die gesamte Festplatte im Hintergrund geladen wurde und nicht mehr funktionierte.

Reinigen der Festplatte

Ich habe auch alte Anwendungen und unnötige Dateien gelöscht, wodurch Sektoren für eine korrekte Fragmentierung frei wurden, die Bedienung des Betriebssystems vereinfacht und die Anzahl nutzloser, schwerer Dienste und Programme reduziert wurde.

Gesamt

Was hat am meisten geholfen? Ein spürbarer Leistungsunterschied wurde nach der Defragmentierung der Festplatte erzielt; spontane Einfrierungen wurden durch die Deaktivierung der Xbox- und Superfetch-Dienste beseitigt. Würden diese Probleme nicht auftreten, wenn ich eine SSD verwendet hätte? Probleme mit einem langsamen Betrieb durch Fragmentierung gäbe es definitiv nicht, Probleme mit Diensten müssten auf jeden Fall behoben werden und Softwarefehler seien nicht von der Art des Laufwerks abhängig. In naher Zukunft plane ich einen kompletten Umstieg auf SSD, aber vorerst gilt: „Lang lebe Pfannkuchen, Pfannkuchen für immer!“

Links

http://www.outsidethebox.ms/why-windows-8-defragments-your-ssd-and-how-you-can-avoid-this/
https://channel9.msdn.com/Shows/The-Defrag-Show
https://www.seagate.com/ru/ru/support/downloads/seatools/
https://www.ccleaner.com/defraggler/download
https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/chkdsk
https://gitlab.com/demensdeum/slowride/

Schreiben eines Backend-Servers in C++ FCGI

Eine kurze Anmerkung dazu, wie ich den Serverteil für den 3D-Editor Cube Art Project geschrieben habe: Der Server sollte die Arbeit der Benutzer der Webversion speichern und anzeigen und ihnen über die Schaltfläche „Speichern“ kurze URLs geben. Zuerst wollte ich Swift/PHP/Ruby/JS oder eine ähnliche moderne Sprache für das Backend verwenden, aber nachdem ich mir die Eigenschaften meines VPS angesehen hatte, entschied ich mich, den Server in C/C++ zu schreiben.
Zuerst müssen Sie libfcgi auf dem Server und das fcgi-Unterstützungsmodul für Ihren Webserver installieren, Beispiel für Ubuntu und Apache:

sudo apt install libfcgi libapache2-mod-fcgid

Als nächstes konfigurieren wir das Modul in der config:

FcgidMaxProcessesPerClass – Maximale Anzahl von Prozessen pro Klasse. Ich habe sie auf 1 Prozess festgelegt, da ich keine große Auslastung erwarte.
AddHandler fcgid-script .fcgi – Dateierweiterung, mit der das fcgi-Modul starten soll.
Fügen Sie der Konfiguration den Ordner hinzu, aus dem CGI-Anwendungen gestartet werden:

Als nächstes schreiben wir eine Anwendung in C/C++ mit fcgi-Unterstützung, assemblieren sie und kopieren sie in den Ordner /var/www/html/cgi-bin.
Beispiele für Code und Build-Skript:
https://gitlab.com/demensdeum/cube-art-project-server/-/blob/master/src/cubeArtProjectServer.cpp
https://gitlab.com/demensdeum/cube-art-project-server/-/blob/master/src/build.sh
Danach müssen Sie Ihren Webserver neu starten:

systemctl restart apache2

Als nächstes geben Sie die notwendigen Berechtigungen ein, um den cgi-bin-Ordner über chmod auszuführen.
Danach sollte Ihr CGI-Programm über einen Browser über den Link funktionieren, Beispiel für den Cube Art Project-Server:
http://192.243.103.70/cgi-bin/cubeArtProject/cubeArtProjectServer.fcgi
Wenn etwas nicht funktioniert, sehen Sie sich die Webserverprotokolle an oder stellen Sie eine Verbindung mit einem Debugger zum laufenden Prozess her. Der Debugging-Prozess sollte sich nicht vom Debugging-Prozess einer regulären Clientanwendung unterscheiden.

Quellen

https://habr.com/ru/post/154187/
http://chriswu.me/blog/writing-hello-world-in-fcgi-with-c-plus-plus/

Quellcode

https://gitlab.com/demensdeum/cube-art -Projektserver

Portieren einer C++-SDL-Anwendung auf Android

In diesem Beitrag beschreibe ich meine Erfahrungen mit der Portierung eines Prototyps eines 3D-Editors Cube Art Projectauf Android.
Schauen wir uns zunächst das Ergebnis an: Im Emulator läuft ein Editor mit einem roten 3D-Würfelcursor:

Für eine erfolgreiche Montage mussten Sie Folgendes tun:

  1. Installieren Sie das neueste Android SDK und NDK (je neuer die NDK-Version, desto besser).
  2. Laden Sie den SDL2-Quellcode herunter und verwenden Sie die Vorlage von dort, um die Android-Anwendung zu erstellen.
  3. SDL Image und SDL Mixer zur Baugruppe hinzufügen.
  4. Fügen Sie die Bibliotheken meiner Spiel-Engine und meines Toolkits sowie deren Abhängigkeiten hinzu (GLM, JSON für Modern C++)
  5. Assembly-Dateien für Gradle anpassen.
  6. C++-Code für Kompatibilität mit Android anpassen, betroffene plattformabhängige Komponenten ändern (OpenGL ES, Grafikkontextinitialisierung)
  7. Erstellen und testen Sie das Projekt auf dem Emulator.

Projektvorlage

Laden der Quellen SDL, SDL Image, SDL Mixer:
https://www.libsdl.org/download-2.0.php
Der Ordner „docs“ enthält detaillierte Anweisungen zum Arbeiten mit der Android-Projektvorlage. Kopieren Sie das Android-Projektverzeichnis in einen separaten Ordner, erstellen Sie einen Symlink oder kopieren Sie den SDL-Ordner nach Android-Projekt/App/jni.
Wir ersetzen das Avd-Flag durch die richtige Kennung und starten den Android-Emulator aus dem Verzeichnis Sdk:

cd ~/Android/Sdk/emulator
./emulator -avd Pixel_2_API_24

Geben Sie die Pfade im Skript an und stellen Sie das Projekt zusammen:

rm -rf app/build || true
export ANDROID_HOME=/home/demensdeum/Android/Sdk/
export ANDROID_NDK_HOME=/home/demensdeum/Android/android-ndk-r21-beta2/
./gradlew clean build
./gradlew installDebug

Die SDL-Projektvorlage mit C-Code aus der Datei sollte zusammengestellt werden

android-sdl-test-app/cube-art-project-android/app/jni/src/YourSourceHere.c

Abhängigkeiten

Laden Sie den Quellcode in den Archiven für SDL_image und SDL_mixer herunter:
https://www.libsdl.org/projects/SDL_image/
https://www.libsdl.org/projects/SDL_mixer/

Laden der Abhängigkeiten Ihres Projekts, zum Beispiel meiner gemeinsam genutzten Bibliotheken:
https://gitlab.com/demensdeum/FlameSteelCore/
https://gitlab.com/demensdeum/FlameSteelCommonTraits
https://gitlab.com/demensdeum/FlameSteelBattleHorn
https://gitlab.com/demensdeum/FlameSteelEngineGameToolkit/
https://gitlab.com/demensdeum/FlameSteelEngineGameToolkitFSGL
https://gitlab.com/demensdeum/FSGL
https://gitlab.com/demensdeum/cube-art-project

Wir laden das alles nach app/jni hoch, jedes „Modul“ in einen separaten Ordner, zum Beispiel app/jni/FSGL. Als nächstes haben Sie die Möglichkeit, funktionierende Generatoren für die Dateien Application.mk und Android.mk zu finden. Ich habe sie nicht gefunden, aber vielleicht gibt es eine einfache Lösung, die auf CMake basiert. Folgen Sie den Links und machen Sie sich mit dem Assembly-Dateiformat für Android NDK vertraut:
https://developer.android.com/ndk/guides/application_mk
https://developer.android.com/ndk/guides/android_mk

Sie sollten auch über verschiedene APP_STL-Implementierungen in NDK lesen:
https://developer.android.com/ndk/guides/cpp-support.html

Nach der Einarbeitung erstellen wir für jedes „Modul“ eine Android.mk-Datei, gefolgt von einer Beispiel-Assembly-Datei der gemeinsam genutzten Bibliothek Cube-Art-Project:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

APP_STL := c++_static
APP_CPPFLAGS := -fexceptions
LOCAL_MODULE := CubeArtProject
LOCAL_C_INCLUDES := $(LOCAL_PATH)/src $(LOCAL_PATH)/../include $(LOCAL_PATH)/../include/FlameSteelCommonTraits/src/FlameSteelCommonTraits
LOCAL_EXPORT_C_INCLUDES = $(LOCAL_PATH)/src/

define walk
$(wildcard $(1)) $(foreach e, $(wildcard $(1)/*), $(call walk, $(e)))
endef

ALLFILES = $(call walk, $(LOCAL_PATH)/src)
FILE_LIST := $(filter %.cpp, $(ALLFILES))
$(info CubeArtProject source code files list)
$(info $(FILE_LIST))
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)

LOCAL_SHARED_LIBRARIES += FlameSteelCore
LOCAL_SHARED_LIBRARIES += FlameSteelBattleHorn
LOCAL_SHARED_LIBRARIES += FlameSteelCommonTraits
LOCAL_SHARED_LIBRARIES += FlameSteelEngineGameToolkit
LOCAL_SHARED_LIBRARIES += FlameSteelEngineGameToolkitFSGL
LOCAL_SHARED_LIBRARIES += FSGL
LOCAL_SHARED_LIBRARIES += SDL2
LOCAL_SHARED_LIBRARIES += SDL2_image

LOCAL_LDFLAGS := -static-libstdc++
include $(BUILD_SHARED_LIBRARY)

Jeder erfahrene CMake-Benutzer wird diese Konfiguration von den ersten Zeilen an verstehen, die Formate sind sehr ähnlich, Android.mk fehlt GLOB_RECURSIVE, sodass Sie mit der Walk-Funktion rekursiv nach Quelldateien suchen müssen.

Wir ändern Application.mk und Android.mk, um C++ und nicht C-Code zu erstellen:

APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
APP_PLATFORM=android-16
APP_STL := c++_static
APP_CPPFLAGS := -fexceptions

Benennen Sie YourSourceHere.c -> YourSourceHere.cpp um, überprüfen Sie die Einträge, ändern Sie den Pfad in der Assembly, zum Beispiel:

app/jni/src/Android.mk:LOCAL_SRC_FILES := YourSourceHere.cpp

Versuchen Sie als Nächstes, das Projekt zu erstellen. Wenn Sie vom Compiler Fehlermeldungen über das Fehlen von Headern sehen, überprüfen Sie die Richtigkeit der Pfade in Android.mk. Wenn vom Linker Fehler wie „undefinierte Referenz“ auftreten, überprüfen Sie, ob die Quellcodedateien in den Assemblys korrekt angegeben sind, indem Sie $(info $(FILE_LIST)) in der Datei Android.mk angeben. Vergessen Sie nicht den doppelten Verknüpfungsmechanismus, die Verwendung von Modulen im Schlüssel LOCAL_SHARED_LIBRARIES und die korrekte Verknüpfung über LD, zum Beispiel für FSGL:

LOCAL_LDLIBS := -lEGL -lGLESv2

Anpassung und Einführung

Ich musste einige Dinge ändern, zum Beispiel GLEW aus den Builds für iOS und Android entfernen, einige der OpenGL-Aufrufe umbenennen, das EOS-Postfix hinzufügen (glGenVertexArrays -> glGenVertexArraysOES) und ein Makro für die fehlenden modernen Debugging-Funktionen einfügen , das Sahnehäubchen ist die implizite Einbindung von GLES2-Headern, die auf Makros hinweisen GL_GLEXT_PROTOTYPES 1:

#define GL_GLEXT_PROTOTYPES 1
#include "SDL_opengles2.h"

Ich habe bei den ersten Starts auch einen schwarzen Bildschirm mit einem Fehler wie „E/libEGL: validate_display:255 error 3008 (EGL_BAD_DISPLAY)“ beobachtet, die Initialisierung des SDL-Fensters und des OpenGL-Profils geändert und alles hat funktioniert:

SDL_DisplayMode mode;
SDL_GetDisplayMode(0,0,&mode);
int width = mode.w;
int height = mode.h;

window = SDL_CreateWindow(
            title,
            0,
            0,
            width,
            height,
            SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_RESIZABLE
        );

SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES );

Auf dem Emulator wird die Anwendung standardmäßig mit dem SDL-Symbol und dem Namen „Spiel“ installiert.

Ich muss nur die Möglichkeit erkunden, Assembly-Dateien automatisch auf Basis von CMake zu generieren oder Assemblys für alle Plattformen nach Gradle zu migrieren; CMake bleibt jedoch de facto die Wahl für die fortlaufende C++-Entwicklung.

Quellcode

https://gitlab.com/demensdeum/android- SDL-Test-App
https://gitlab.com/demensdeum/android-sdl-test-app/tree/master/cube-art-project-android

Quellen

https://developer.android.com/ ndk/guides/cpp-support.html
https://developer.android.com/ndk/guides/application_mk
https://developer.android.com/ndk/guides/android_mk
https://lazyfoo.net/tutorials/SDL/52_hello_mobile/android_windows/index.php
https://medium.com/androiddevelopers/getting-started-with-c-and-android-native-activities-2213b402ffff

Auf den Kopf gestellte Welt

Um ein neues Projekt zu entwickeln, hat Cube Art Project die Test Driven Development-Methodik übernommen. Bei diesem Ansatz wird zunächst ein Test für eine bestimmte Funktionalität der Anwendung durchgeführt und anschließend die spezifische Funktionalität implementiert. Den großen Vorteil dieses Ansatzes sehe ich darin, dass die finalen Schnittstellen möglichst unbeeinflusst von Implementierungsdetails implementiert werden, bevor mit der Entwicklung der Funktionalität begonnen wird. Bei diesem Ansatz bestimmt der Test die weitere Implementierung und bietet alle Vorteile der Vertragsprogrammierung, wenn es sich bei Schnittstellen um Verträge für eine bestimmte Implementierung handelt.
Würfelkunstprojekt – Ein 3D-Editor, in dem der Benutzer Figuren aus Würfeln baut; vor nicht allzu langer Zeit war dieses Genre sehr beliebt. Da es sich um eine grafische Anwendung handelt, habe ich beschlossen, Tests mit Screenshot-Validierung hinzuzufügen.
Um Screenshots zu validieren, müssen Sie sie aus dem OpenGL-Kontext abrufen. Dies geschieht mit der glReadPixels-Funktion. Die Beschreibung der Funktionsargumente ist einfach – Startposition, Breite, Höhe, Format (RGB/RGBA/usw.), Zeiger auf Ausgabepuffer; jeder, der mit SDL gearbeitet hat oder Erfahrung mit Datenpuffern in C hat, ersetzt einfach die notwendigen Argumente. Ich halte es jedoch für notwendig, eine interessante Funktion des glReadPixels-Ausgabepuffers zu beschreiben: Pixel werden darin von unten nach oben gespeichert, während in SDL_Surface alle grundlegenden Operationen von oben nach unten erfolgen.
Das heißt, nachdem ich einen Referenz-Screenshot aus einer PNG-Datei geladen hatte, konnte ich die beiden Puffer nicht direkt vergleichen, da einer von ihnen auf dem Kopf stand.
Um den Ausgabepuffer von OpenGL umzudrehen, müssen Sie ihn füllen, indem Sie die Screenshot-Höhe von der Y-Koordinate subtrahieren. Es ist jedoch zu bedenken, dass die Möglichkeit besteht, dass die Puffergrenzen überschritten werden, wenn Sie beim Füllen nichts subtrahieren zu Speicherbeschädigungen führen.
Da ich immer versuche, das OOP-Paradigma der „Programmierung durch Schnittstellen“ anstelle des direkten C-ähnlichen Speicherzugriffs durch Zeiger zu verwenden, hat mich das Objekt beim Versuch, Daten außerhalb des Puffers zu schreiben, dank der Grenzvalidierung in der Methode darüber informiert .
Der endgültige Code für die Methode zum Erstellen eines Screenshots im Top-Down-Stil:

    auto width = params->width;
    auto height = params->height;

    auto colorComponentsCount = 3;
    GLubyte *bytes = (GLubyte *)malloc(colorComponentsCount * width * height);
    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, bytes);

    auto screenshot = make_shared(width, height);

    for (auto y = 0; y < height; y++) {
        for (auto x = 0; x < width; x++) {
            auto byteX = x * colorComponentsCount;
            auto byteIndex = byteX + (y * (width * colorComponentsCount));
            auto redColorByte = bytes[byteIndex];
            auto greenColorByte = bytes[byteIndex + 1];
            auto blueColorByte = bytes[byteIndex + 2];
            auto color = make_shared(redColorByte, greenColorByte, blueColorByte, 255);
            screenshot->setColorAtXY(color, x, height - y - 1);
        }
    }

    free(bytes);

Quellen

https://community.khronos.org/ t/glreadpixels-fliped-image/26561
https://stackoverflow.com/questions/8346115/why-are-bmps-stored-upside-down

Quellcode

https://gitlab.com/demensdeum/cube- Kunstprojekt-Bootstrap

Längster gemeinsamer Teilstring

In diesem Beitrag beschreibe ich einen Algorithmus zur Lösung des größten häufigen Teilstringproblems. Nehmen wir an, wir versuchen, verschlüsselte Binärdaten zu entschlüsseln. Versuchen wir zunächst, gemeinsame Muster zu finden, indem wir nach der größten Teilzeichenfolge suchen.
Beispiel-Eingabezeichenfolge:
adasDATAHEADER??jpjjwerthhkjbcvkDATAHEADER??kkasdf
Wir suchen nach einer Zeichenfolge, die sich zweimal wiederholt:
DATAHEADER??

Präfixe

Schreiben wir zunächst eine Methode zum Vergleichen der Präfixe zweier Zeichenfolgen und lassen Sie sie die resultierende Zeichenfolge zurückgeben, in der die Zeichen des linken Präfixes den Zeichen des rechten Präfixes entsprechen.
Zum Beispiel für die Zeilen:

        val lhs = "asdfWUKI"
        val rhs = "asdfIKUW"

Ergebniszeichenfolge – asdf
Beispiel in Kotlin:

fun longestPrefix(lhs: String, rhs: String): String {
        val maximalLength = min(lhs.length-1, rhs.length -1)
        for (i in 0..maximalLength) {
            val xChar = lhs.take(i)
            val yChar = rhs.take(i)
                if (xChar != yChar) {
                    return lhs.substring(0, i-1)
                }
        }
        return lhs.substring(0,maximalLength)
}

Brute Force

Wenn etwas nicht klappt, sollten Sie zu roher Gewalt greifen. Mit der longestPrefix-Methode durchlaufen wir die Zeichenfolge in zwei Schleifen. Die erste Schleife nimmt die Zeichenfolge von i bis zum Ende, die zweite von i + 1 bis zum Ende und gibt sie weiter, um nach dem größten Präfix zu suchen. Die zeitliche Komplexität dieses Algorithmus beträgt ungefähr O(n^2) ~ O(n*^3).
Beispiel in Kotlin:

fun searchLongestRepeatedSubstring(searchString: String): String {
        var longestRepeatedSubstring = ""
        for (x in 0..searchString.length-1) {
            val lhs = searchString.substring(x)
            for (y in x+1..searchString.length-1) {
                val rhs = searchString.substring(y)
                val longestPrefix = longestPrefix(lhs, rhs)
                if (longestRepeatedSubstring.length < longestPrefix.length) {
                    longestRepeatedSubstring = longestPrefix
                }
            }
        }
        return longestRepeatedSubstring
}

Suffix-Array

Für eine elegantere Lösung benötigen wir ein Werkzeug – eine Datenstruktur namens „Suffix Array“. Diese Datenstruktur ist ein Array von Teilzeichenfolgen, die in einer Schleife gefüllt werden, wobei jede Teilzeichenfolge vom nächsten Zeichen der Zeile bis zum Ende beginnt.
Zum Beispiel für die Zeile:

adasDATAHEADER??

Das Suffix-Array sieht folgendermaßen aus:

adasDATAHEADER??
dasDATAHEADER??
asDATAHEADER??
sDATAHEADER??
DATAHEADER??
ATAHEADER??
TAHEADER??
AHEADER??
HEADER??
EADER??
ADER??
DER??
ER??
R??
??
?

Wir lösen durch Sortieren

Sortieren wir das Suffix-Array, gehen wir dann alle Elemente in einer Schleife durch, wobei sich das aktuelle Element in der linken Hand (links) und das nächste in der rechten Hand (rechts) befindet, und berechnen wir das längste Präfix mithilfe des longestPrefix Methode.
Beispiel in Kotlin:

fun searchLongestRepeatedSubstring(searchString: String): String {
    val suffixTree = suffixArray(searchString)
    val sortedSuffixTree = suffixTree.sorted()

    var longestRepeatedSubstring = ""
    for (i in 0..sortedSuffixTree.count() - 2) {
        val lhs = sortedSuffixTree[i]
        val rhs = sortedSuffixTree[i+1]
        val longestPrefix = longestPrefix(lhs, rhs)
        if (longestRepeatedSubstring.length < longestPrefix.length) {
            longestRepeatedSubstring = longestPrefix
        }
    }
    return longestRepeatedSubstring
}

Die zeitliche Komplexität des Algorithmus beträgt O(N log N), was viel besser ist als eine einfache Lösung.

Quellen

https://en.wikipedia.org/wiki/Longest_common_substring_problem

Quellcode

https://gitlab.com/demensdeum/algorithms

Einfügungssortierung, Zusammenführungssortierung

Einfügungssortierung

Einfügesortierung – Jedes Element wird mit den vorherigen Elementen in der Liste verglichen und das Element wird mit dem größeren ausgetauscht, falls vorhanden, andernfalls die innere Vergleichsschleife stoppt. Da die Elemente vom ersten bis zum letzten sortiert werden, wird jedes Element mit einer bereits sortierten Liste verglichen, was *möglicherweise* die Gesamtlaufzeit verkürzt. Die zeitliche Komplexität des Algorithmus beträgt O(n^2), also identisch mit der Blasenvariante.

Sortierung zusammenführen

Sortierung zusammenführen – Die Liste wird in Gruppen eines Elements unterteilt. Anschließend werden die Gruppen paarweise „zusammengeführt“ und gleichzeitig verglichen. In meiner Implementierung werden beim Zusammenführen von Paaren die Elemente auf der linken Seite mit den Elementen auf der rechten Seite verglichen und dann in die resultierende Liste verschoben. Wenn die Elemente auf der linken Seite nicht mehr vorhanden sind, werden alle Elemente auf der rechten Seite zur Ergebnisliste hinzugefügt Liste (ihr zusätzlicher Vergleich ist unnötig, da alle Elemente in den Gruppen Sortieriterationen durchlaufen)< br />Die Arbeit dieses Algorithmus lässt sich sehr einfach parallelisieren; die Phase der Zusammenführung von Paaren kann in Threads durchgeführt werden, wobei auf das Ende der Iterationen im Dispatcher gewartet wird.
Ausgabe des Algorithmus für Single-Threaded-Ausführung:

["John", "Alice", "Mike", "#1", "Артем", "20", "60", "60", "DoubleTrouble"]
[["John"], ["Alice"], ["Mike"], ["#1"], ["Артем"], ["20"], ["60"], ["60"], ["DoubleTrouble"]]
[["Alice", "John"], ["#1", "Mike"], ["20", "Артем"], ["60", "60"], ["DoubleTrouble"]]
[["#1", "Alice", "John", "Mike"], ["20", "60", "60", "Артем"], ["DoubleTrouble"]]
[["#1", "20", "60", "60", "Alice", "John", "Mike", "Артем"], ["DoubleTrouble"]]
["#1", "20", "60", "60", "Alice", "DoubleTrouble", "John", "Mike", "Артем"]

Ausgabe des Algorithmus für die Multithread-Ausführung:

["John", "Alice", "Mike", "#1", "Артем", "20", "60", "60", "DoubleTrouble"]
[["John"], ["Alice"], ["Mike"], ["#1"], ["Артем"], ["20"], ["60"], ["60"], ["DoubleTrouble"]]
[["20", "Артем"], ["Alice", "John"], ["60", "60"], ["#1", "Mike"], ["DoubleTrouble"]]
[["#1", "60", "60", "Mike"], ["20", "Alice", "John", "Артем"], ["DoubleTrouble"]]
[["DoubleTrouble"], ["#1", "20", "60", "60", "Alice", "John", "Mike", "Артем"]]
["#1", "20", "60", "60", "Alice", "DoubleTrouble", "John", "Mike", "Артем"]

Die zeitliche Komplexität des Algorithmus beträgt O(n*log(n)), was etwas besser ist als O(n^2)

Quellen

https://en.wikipedia.org/wiki/Insertion_sort
https://en.wikipedia.org/wiki/Merge_sort

Quellcode

https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/insertionSort
https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/mergeSort

Blasensortierung in Erlang

Bubble Sort ist ziemlich langweilig, aber es wird interessanter, wenn Sie versuchen, es in einer funktionalen Sprache für die Telekommunikation zu implementieren – Erlang.

Wir haben eine Liste mit Zahlen, wir müssen sie sortieren. Der Blasensortierungsalgorithmus durchläuft die gesamte Liste und iteriert und vergleicht die Zahlen paarweise. Bei der Prüfung geschieht Folgendes: Eine kleinere Zahl wird zur Ausgabeliste hinzugefügt, oder die Zahlen werden in der aktuellen Liste vertauscht, wenn rechts weniger vorhanden sind, wird die Suche mit der nächsten Zahl in der Iteration fortgesetzt. Dieser Durchlauf wird wiederholt, bis die Liste keine Ersetzungen mehr enthält.

In der Praxis lohnt sich der Einsatz aufgrund der hohen zeitlichen Komplexität des Algorithmus nicht – O(n^2); Ich habe es in Erlang im Imperativstil implementiert, aber wenn Sie interessiert sind, können Sie nach besseren Optionen suchen:

-module(bubbleSort).
-export([main/1]).

startBubbleSort([CurrentHead|Tail]) ->
    compareHeads(CurrentHead, Tail, [], [CurrentHead|Tail]).

compareHeads(CurrentHead, [NextHead|Tail], [], OriginalList) ->   
    if
        CurrentHead < NextHead ->
            compareHeads(NextHead, Tail, [CurrentHead], OriginalList);
        true ->
            compareHeads(CurrentHead, Tail, [NextHead], OriginalList)
    end;
    
compareHeads(CurrentHead, [NextHead|Tail], OriginalOutputList, OriginalList) ->
    if
        CurrentHead < NextHead ->
            OutputList = OriginalOutputList ++ [CurrentHead],
            compareHeads(NextHead, Tail, OutputList, OriginalList);
        true ->
            OutputList = OriginalOutputList ++ [NextHead],
            compareHeads(CurrentHead, Tail, OutputList, OriginalList)
    end;
  
compareHeads(CurrentHead, [], OriginalOutputList, OriginalList) ->
    OutputList = OriginalOutputList ++ [CurrentHead],
    if
        OriginalList == OutputList ->
            io:format("OutputList: ~w~n", [OutputList]);
        true ->
            startBubbleSort(OutputList)
    end.
  
main(_) ->
    UnsortedList = [69,7,4,44,2,9,10,6,26,1],
    startBubbleSort(UnsortedList).

Installation und Start

In Ubuntu ist Erlang sehr einfach zu installieren; geben Sie einfach sudo apt install erlang in das Terminal ein. In dieser Sprache muss jede Datei ein Modul sein, mit einer Liste von Funktionen, die extern verwendet werden können – Export. Zu den interessanten Merkmalen der Sprache gehören das Fehlen von Variablen, nur Konstanten, das Fehlen einer Standardsyntax für OOP (was die Verwendung von OOP-Techniken nicht verhindert) und natürlich parallele Berechnungen ohne Sperren basierend auf dem Akteurmodell.

Sie können das Modul entweder über die interaktive ERL-Konsole ausführen, indem Sie einen Befehl nach dem anderen ausführen, oder einfacher über escript bubbleSort.erl; In verschiedenen Fällen sieht die Datei anders aus, zum Beispiel müssen Sie für escript eine Hauptfunktion erstellen, von der aus sie gestartet wird.

Quellen

https://www.erlang.org/
https://habr.com/ru/post/197364/

Quellcode

https://gitlab.com/ demensdeum/algorithms/blob/master/bubbleSort/bubbleSort.erl

Lexikographischer Vergleichsalgorithmus

Der lexikografische String-Vergleichsalgorithmus funktioniert sehr einfach; die Zeichencodes werden in einer Schleife verglichen und das Ergebnis wird zurückgegeben, wenn die Zeichen nicht gleich sind.

Ein Beispiel für die C-Sprache finden Sie hier:
https://github.com/gcc-mirror/gcc/blob/master/libiberty/memcmp.c

Es sollte berücksichtigt werden, dass Sie Zeichen in einer einzigen statischen Codierung vergleichen müssen, zum Beispiel habe ich in Swift den zeichenweisen Vergleich in UTF-32 verwendet. Die Array-Sortieroption mit memcmp funktioniert genau für Einzelbyte-Zeichen. In anderen Fällen (Codierungen mit variabler Länge) ist die Reihenfolge möglicherweise falsch. Ich schließe die Möglichkeit einer Implementierung auf Basis von Codierungen mit variabler Länge nicht aus, aber höchstwahrscheinlich wird es um eine Größenordnung komplizierter sein.

Die zeitliche Komplexität des Algorithmus beträgt im besten Fall O(1), im Durchschnitt und im schlechtesten Fall O(n)

Quellen

https://ru.wikipedia.org/wiki/Lexicographic_order

Quellen

https://gitlab.com/demensdeum /algorithms/blob/master/lexiCompare/lexiCompare.swift

Spieleentwicklung für ZX Spectrum in C

Dieser Nonsens-Beitrag ist der Entwicklung eines Spiels für den alten ZX Spectrum-Computer in C gewidmet. Werfen wir einen Blick auf den hübschen Kerl:

Die Produktion begann 1982 und wurde bis 1992 produziert. Technische Eigenschaften der Maschine: 8-Bit-Z80-Prozessor, 16-128 KB Speicher und weitere Erweiterungen, wie der AY-3-8910-Soundchip.

Im Rahmen des Wettbewerbs Yandex Retro Games Battle 2019 für diese Maschine habe ich ein Spiel geschrieben namens Interceptor 2020. Da ich keine Zeit hatte, die Assemblersprache für den Z80 zu lernen, beschloss ich, sie in C zu entwickeln. Als Toolchain habe ich ein fertiges Set gewählt – z88dk, das C-Compiler und viele Hilfsbibliotheken enthält, um die Implementierung von Anwendungen für das Spectrum zu beschleunigen. Es unterstützt auch viele andere Z80-Geräte, wie z. B. Taschenrechner von MSX und Texas Instruments.

Als nächstes beschreibe ich meinen oberflächlichen Flug über die Computerarchitektur, die z88dk-Toolchain, und zeige, wie ich es geschafft habe, den OOP-Ansatz zu implementieren und Designmuster zu verwenden.

Installationsfunktionen

Die Installation von z88dk sollte gemäß der Anleitung aus dem Repository erfolgen, für Ubuntu-Benutzer möchte ich jedoch eine Funktion anmerken – Wenn Sie bereits Compiler für Z80 aus Deb-Paketen installiert haben, sollten Sie diese entfernen, da z88dk aufgrund der Inkompatibilität der Toolchain-Compiler-Versionen standardmäßig auf sie zugreift.< /p>

Hallo Welt

Hello World zu schreiben ist sehr einfach:

#include 

void main()
{
    printf("Hello World");
}

Es ist noch einfacher, eine Tap-Datei zu kompilieren:

zcc +zx -lndos -create-app -o helloworld helloworld.c

Verwenden Sie zum Ausführen einen beliebigen ZX Spectrum-Emulator mit Tap-Datei-Unterstützung, zum Beispiel online:
http://jsspeccy.zxdemo.org/

Zeichnen Sie im Vollbildmodus auf das Bild

tl;dr Bilder werden in Kacheln gezeichnet, Kacheln mit einer Größe von 8×8 Pixeln, die Kacheln selbst sind in die Schriftart Spectrum eingebaut, dann wird das Bild als Linie aus den Indizes gedruckt.

Die Sprite- und Kachelausgabebibliothek SP1 gibt Kacheln mithilfe von UDG aus. Das Bild wird in eine Reihe einzelner UDGs (Kacheln) übersetzt und dann mithilfe von Indizes auf dem Bildschirm zusammengesetzt. Beachten Sie, dass UDG zum Anzeigen von Text verwendet wird. Wenn Ihr Bild einen sehr großen Satz von Kacheln enthält (z. B. mehr als 128 Kacheln), müssen Sie über die Grenzen des Satzes hinausgehen und das Standardspektrum löschen Schriftart. Um diese Einschränkung zu umgehen, habe ich eine Basis von 128 – 255 durch Vereinfachung der Bilder, wobei die ursprüngliche Schriftart erhalten bleibt. Über die Vereinfachung der Bilder unten.

Um Vollbildbilder zu zeichnen, müssen Sie sich mit drei Dienstprogrammen ausrüsten:
Gimp
img2spec
png2c-z88dk

Für echte ZX-Männer, echte Retro-Krieger gibt es eine Möglichkeit: Öffnen Sie einen Grafikeditor mit der Spectrum-Palette, kennen Sie die Funktionen der Bildausgabe, bereiten Sie sie manuell vor und laden Sie sie mit png2c-z88dk oder png2scr hoch.< /p>

Der einfachere Weg – Nehmen Sie ein 32-Bit-Bild, ändern Sie die Anzahl der Farben in Gimp auf 3-4, bearbeiten Sie es leicht und importieren Sie es dann in img2spec, um nicht manuell mit Farbbeschränkungen zu arbeiten. Exportieren Sie PNG und konvertieren Sie es mit PNG2C in ein C-Array. z88dk.

Sie sollten bedenken, dass für einen erfolgreichen Export jede Kachel nicht mehr als zwei Farben enthalten darf.

Als Ergebnis erhalten Sie eine h-Datei, die die Anzahl der eindeutigen Kacheln enthält. Wenn es mehr als ~128 sind, vereinfachen Sie das Bild in Gimp (erhöhen Sie die Wiederholbarkeit) und führen Sie den Exportvorgang über ein neues aus .

Nach dem Export laden Sie buchstäblich die „Schriftart“ aus den Kacheln und drucken den „Text“ aus den Kachelindizes auf den Bildschirm. Unten ist ein Beispiel aus der Render-„Klasse“:

// грузим шрифт в память
    unsigned char *pt = fullscreenImage->tiles;

    for (i = 0; i < fullscreenImage->tilesLength; i++, pt += 8) {
            sp1_TileEntry(fullscreenImage->tilesBase + i, pt);
    }

    // ставим курсор в 0,0
    sp1_SetPrintPos(&ps0, 0, 0);

    // печатаем строку
    sp1_PrintString(&ps0, fullscreenImage->ptiles);

Sprites auf dem Bildschirm zeichnen

Als nächstes beschreibe ich eine Methode zum Zeichnen von Sprites mit 16 x 16 Pixeln auf dem Bildschirm. Ich bin nicht zur Animation und zum Ändern der Farben gekommen, weil… Es ist trivial, dass mir schon zu diesem Zeitpunkt, wie ich annehme, der Speicher ausgegangen ist. Daher enthält das Spiel nur transparente monochrome Sprites.

Wir zeichnen ein monochromes PNG-Bild 16×16 in Gimp, dann übersetzen wir es mit png2sp1sprite in eine ASM-Assembly-Datei, im C-Code deklarieren wir Arrays aus der Assembly-Datei und fügen die Datei in der Assembly-Phase hinzu.< /p>

Nach der Deklaration der Sprite-Ressource muss diese an der gewünschten Position zum Bildschirm hinzugefügt werden. Nachfolgend finden Sie ein Beispiel für den Code für die „Klasse“ des Spielobjekts:

    struct sp1_ss *bubble_sprite = sp1_CreateSpr(SP1_DRAW_MASK2LB, SP1_TYPE_2BYTE, 3, 0, 0);
    sp1_AddColSpr(bubble_sprite, SP1_DRAW_MASK2,    SP1_TYPE_2BYTE, col2-col1, 0);
    sp1_AddColSpr(bubble_sprite, SP1_DRAW_MASK2RB,  SP1_TYPE_2BYTE, 0, 0);
    sp1_IterateSprChar(bubble_sprite, initialiseColour);

Anhand der Namen der Funktionen können Sie die Bedeutung von – Weisen Sie dem Sprite Speicher zu, fügen Sie zwei 8×8 Spalten hinzu und fügen Sie eine Farbe für das Sprite hinzu.

Die Position des Sprites wird in jedem Frame angezeigt:

sp1_MoveSprPix(gameObject->gameObjectSprite, Renderer_fullScreenRect, gameObject->sprite_col, gameObject->x, gameObject->y);

OOP emulieren

In C gibt es keine Syntax für OOP. Was sollten Sie tun, wenn Sie es trotzdem wirklich wollen? Sie müssen Ihren Geist verbinden und sich darüber im Klaren sein, dass es so etwas wie OOP-Ausrüstung nicht gibt; letztendlich kommt es auf eine der Maschinenarchitekturen an, in denen es einfach kein Konzept eines Objekts und andere damit verbundene Abstraktionen gibt.< /p>

Diese Tatsache hat mich sehr lange daran gehindert zu verstehen, warum OOP überhaupt benötigt wird, warum es notwendig ist, es zu verwenden, wenn am Ende alles auf Maschinencode hinausläuft.

Nachdem ich jedoch in der Produktentwicklung gearbeitet hatte, entdeckte ich die Vorzüge dieses Programmierparadigmas, vor allem natürlich die Entwicklungsflexibilität, Code-Schutzmechanismen mit dem richtigen Ansatz, die Reduzierung der Entropie und die Vereinfachung der Teamarbeit. Alle oben genannten Vorteile basieren auf drei Säulen: Polymorphismus, Kapselung, Vererbung.

Bemerkenswert ist auch die Vereinfachung der Lösung von Problemen im Zusammenhang mit der Anwendungsarchitektur, da 80 % der Architekturprobleme im letzten Jahrhundert von Informatikern gelöst und in der Literatur zu Entwurfsmustern beschrieben wurden. Als Nächstes beschreibe ich Möglichkeiten, C eine OOP-ähnliche Syntax hinzuzufügen.

Es ist bequemer, C-Strukturen als Grundlage für die Speicherung von Daten einer Klasseninstanz zu verwenden. Natürlich können Sie einen Bytepuffer verwenden und Ihre eigene Struktur für Klassen und Methoden erstellen, aber warum das Rad neu erfinden? Schließlich erfinden wir die Syntax bereits neu.

Klassendaten

Beispiel für GameObject-„Klassen“-Datenfelder:

struct GameObjectStruct {
    struct sp1_ss *gameObjectSprite;
    unsigned char *sprite_col;
    unsigned char x;
    unsigned char y;
    unsigned char referenceCount;
    unsigned char beforeHideX;
    unsigned char beforeHideY;
};
typedef struct GameObjectStruct GameObject;

Speichern Sie unsere Klasse als „GameObject.h“, fügen Sie „GameObject.h“ an der richtigen Stelle ein und verwenden Sie sie.

Klassenmethoden

Unter Berücksichtigung der Erfahrung der Entwickler der Objective-C-Sprache besteht die Signatur einer Klassenmethode aus Funktionen in einem globalen Bereich. Das erste Argument ist immer die Datenstruktur, gefolgt von den Methodenargumenten. Unten finden Sie ein Beispiel für eine „Methode“ der „Klasse“ GameObject:

void GameObject_hide(GameObject *gameObject) {
    gameObject->beforeHideX = gameObject->x;
    gameObject->beforeHideY = gameObject->y;
    gameObject->y = 200;
}

Der Methodenaufruf sieht so aus:

GameObject_hide(gameObject);

Konstruktoren und Destruktoren werden auf die gleiche Weise implementiert. Es ist möglich, einen Konstruktor als Allokator und Feldinitialisierer zu implementieren, aber ich bevorzuge es, die Objektzuweisung und -initialisierung separat zu steuern.

Mit dem Gedächtnis arbeiten

Manuelle Speicherverwaltung des Formulars mit malloc und frei in neue und gelöschte Makros eingebunden, passend zu C++:

#define new(X) (X*)malloc(sizeof(X))
#define delete(X) free(X)

Für Objekte, die von mehreren Klassen gleichzeitig verwendet werden, wird eine halbmanuelle Speicherverwaltung basierend auf Referenzzählung implementiert, nach dem Vorbild und der Ähnlichkeit des alten Objective-C Runtime ARC-Mechanismus:

void GameObject_retain(GameObject *gameObject) {
    gameObject->referenceCount++;
}

void GameObject_release(GameObject *gameObject) {
    gameObject->referenceCount--;

    if (gameObject->referenceCount < 1) { sp1_MoveSprAbs(gameObject->gameObjectSprite, &Renderer_fullScreenRect, NULL, 0, 34, 0, 0);
        sp1_DeleteSpr(gameObject->gameObjectSprite);
        delete(gameObject);
    }
}

Daher muss jede Klasse die Verwendung eines gemeinsam genutzten Objekts mithilfe von „Retain“ deklarieren und das Eigentum durch „Release“ freigeben. Die moderne Version von ARC verwendet automatische Aufbewahrungs-/Freigabeaufrufe.

Ton!

Der Spectrum verfügt über einen Hochtöner, der 1-Bit-Musik wiedergeben kann; damalige Komponisten konnten bis zu 4 Tonkanäle gleichzeitig wiedergeben.

Spectrum 128k enthält einen separaten Soundchip AY-3-8910, der Tracker-Musik abspielen kann.

Für die Nutzung des Hochtöners im z88dk wird eine Bibliothek angeboten

Was noch gelernt werden muss

Ich war daran interessiert, das Spectrum kennenzulernen, das Spiel mit dem z88dk zu implementieren und viele interessante Dinge zu lernen. Ich muss noch viel lernen, zum Beispiel den Z80-Assembler, da er mir ermöglicht, die volle Leistung des Spectrum zu nutzen, mit Speicherbänken zu arbeiten und mit dem Soundchip AY-3-8910 zu arbeiten. Ich hoffe, nächstes Jahr am Wettbewerb teilnehmen zu können!

Links

https://rgb.yandex
https://vk.com/sinc_lair
https://www.z88dk.org/forum/

Quellcode

https://gitlab.com/demensdeum/ zx-projects/tree/master/interceptor2020

Binäre Suche

Angenommen, wir müssen herausfinden, ob die E-Mail-Adresse „demensdeum@gmail.com“ in der Liste der zulässigen E-Mail-Adressen für den Empfang von Briefen enthalten ist .

Lassen Sie uns die gesamte Liste vom ersten bis zum letzten Element durchgehen und prüfen, ob das Element mit der angegebenen Adresse übereinstimmt – Lassen Sie uns einen linearen Suchalgorithmus implementieren. Aber das wird lange dauern, oder nicht?

Um diese Frage zu beantworten, verwenden Sie die „Zeitkomplexität von Algorithmen“, „O“-Notation. Die Betriebszeit der linearen Suche entspricht im schlimmsten Fall der n-ten Anzahl von Array-Elementen. Schreiben wir dies in „O“-Notation – An). Als nächstes müssen wir erklären, dass es für jeden bekannten Algorithmus drei Leistungsindikatoren gibt: Best-Case-, Worst-Case- und Average-Case-Ausführungszeiten. Befindet sich beispielsweise die E-Mail-Adresse „demensdeum@gmail.com“ im ersten Index des Arrays, wird sie im ersten Schritt von gefunden Für den Algorithmus folgt daraus, dass die Ausführungszeit bestenfalls – O(1); und wenn am Ende der Liste, dann ist dies der schlimmste Fall – O(n)

Aber was ist mit den Details der Software-Implementierung und der Hardware-Leistung? Sie sollten einen großen Einfluss auf das O haben? Atmen Sie nun ein und stellen Sie sich vor, dass die Berechnung der Zeitkomplexität für eine abstrakte ideale Maschine berechnet wird, in der es nur diesen Algorithmus und sonst nichts gibt.

Algorithmus

Okay, es stellt sich heraus, dass die lineare Suche ziemlich langsam ist. Versuchen wir es mit der binären Suche. Zunächst sollte klargestellt werden, dass wir nicht mit Binärdaten arbeiten; dieser Name wurde dieser Methode aufgrund der Besonderheiten ihrer Arbeit gegeben. Zunächst sortieren wir das Array nach lexikografische Reihenfolge, dann nimmt der Algorithmus den Bereich des gesamten Arrays, ruft das mittlere Element des Bereichs ab und vergleicht es lexikographisch und abhängig vom Ergebnis des Vergleichs entscheidet, welcher Bereich für die weitere Suche verwendet werden soll – die obere Hälfte des Stroms oder die untere. Das heißt, bei jedem Suchschritt wird eine Entscheidung aus zwei möglichen – Binäre Logik. Dieser Schritt wird wiederholt, bis entweder das Wort gefunden wird oder nicht (der Schnittpunkt des unteren und oberen Index des Bereichs tritt auf).

Leistung dieses Algorithmus – Der beste Fall ist, wenn ein Element sofort in der Mitte des Arrays O(1) gefunden wird, der schlechteste Fall der Aufzählung ist O(log n)

Fallstricke

Bei der Implementierung der binären Suche bin ich nicht nur auf das interessante Problem der fehlenden Standardisierung des lexikografischen Vergleichs in Programmiersprachenbibliotheken gestoßen, sondern habe sogar festgestellt, dass es keinen einheitlichen Standard für die Implementierung gibt localeCompare innerhalb von JavaScript. Der ECMAScript-Standard erlaubt unterschiedliche Implementierungen dieser Funktion, weshalb beim Sortieren mit localeCompare auf verschiedenen JavaScript-Engines völlig unterschiedliche Ergebnisse beobachtet werden können.

Damit der Algorithmus korrekt funktioniert, ist es notwendig, nur denselben lexikografischen Vergleichsalgorithmus zu sortieren und zu verwenden, andernfalls funktioniert nichts. Co-aber wenn Sie beispielsweise versuchen, ein Array in Scala zu sortieren und mit NodeJS zu suchen, ohne Ihre eigene Sortierung/Sortierung einer Implementierung zu implementieren, dann erwartet Sie nichts außer Enttäuschung über die Menschheit.

Quellen

Was ist ein lexikografischer Vergleich und was stellt er dar?
Почему для вычисления сложности алгоритмов используется log N вместо lb N?
Двоичный поиск
Знай сложности алгоритмов
https://stackoverflow.com/questions/52941016/sorting-in-localecompare-in-javascript

Quellcode

https://gitlab.com/demensdeum/algorithms

Musterfassade


Fassade bezieht sich auf strukturelle Gestaltungsmuster. Es bietet eine einzige Schnittstelle, die die Arbeit mit komplexen Systemen ermöglicht, sodass Clients keine Implementierungsdetails zu diesen Systemen haben müssen, wodurch ihr Code vereinfacht wird und eine lose Kopplung zwischen Clients und Systemen auf niedrigerer Ebene implementiert wird. GoF hat ein gutes Beispiel für eine Fassade – Ein Programmiersprachen-Compiler, der verschiedenen Clients, die unterschiedliche Ziele verfolgen, die Möglichkeit bietet, Code über eine einzige Compiler-Fassadenschnittstelle zusammenzustellen.

Quellen

https://refactoring.guru/ru/design-patterns/facade
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

Abstraktes Fabrikmuster

Abstrakte Fabrik– Bietet eine Schnittstelle zum Erstellen verwandter Objekte, ohne bestimmte Klassen anzugeben.

Mir gefällt der alternative Name für dieses Muster wirklich – Kit (Kit)

Es ist der Factory-Methode sehr ähnlich, allerdings müssen Abstrakte Fabrikendie Beziehung zwischen den erstellten Objekten beschreiben, andernfalls handelt es sich einfach um ein Gottobjekt Antimuster, das alles erschafft, ist willkürlich.

Stellen Sie sich die Entwicklung eines AR-Frameworks für Brillen vor; wir zeigen auf dem Bildschirm Indoor-Navigationspfeile, Symbole von Geschäften, interessante Orte, Fenster und Schaltflächen mit Informationen über jeden Ort an, an dem sich der Benutzer gerade befindet.

Gleichzeitig benötigen wir die Möglichkeit, das Erscheinungsbild und Verhalten von AR-Umgebungssteuerungen anzupassen. Genau für diesen Fall müssen Sie das Muster Set verwenden.

Lassen Sie uns die Schnittstelle von Abstract Factory und Abstract Products schreiben – übergeordnete Protokolle, AR-Umgebungselemente:

protocol ARFactory {
    func arrow() -> ARArrow
    func icon() -> ARIcon
    func button() -> ARButton
    func window() -> ARWindow
}

protocol ARArrow {
    var image: { get }
    func handleSelection()
}

protocol ARIcon {
    var image: { get }
    var title: String
}

protocol ARButton {
    var title: String
    func handleSelection()
}

protocol ARWindow {
    var title: String
    var draw(canvas: Canvas)
}

Jetzt müssen Kit-Entwickler eine Concrete Factory basierend auf der Abstract Factory-Schnittstelle implementieren, und sie müssen alle Elemente zusammen implementieren; der Rest der Anwendung wird in der Lage sein, mit der Factory zu arbeiten, ohne ihren Code zu ändern.< /p>

Quellen

https://refactoring.guru/ru/design-patterns /abstract-factory
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

Fabrikmethode

Das Factory-Methode-Muster bezieht sich auf generative Designmuster.
Dieses Muster beschreibt die Erstellung einer Schnittstelle zum Erstellen eines Objekts einer bestimmten Klasse. Es scheint einfach, oder?

Theoretisch

Angenommen, wir entwickeln ein Framework für die Arbeit mit AR-Brillen. Wenn Sie den Kopf zur Seite neigen, sollte vor den Augen des Benutzers ein Menü mit verfügbaren Anwendungen erscheinen. Anwendungen werden von Drittunternehmen entwickelt, die Kunden unseres Frameworks sind. Natürlich wissen wir nicht, welche Anwendungen, Symbole und Namen angezeigt werden sollen. Daher müssen wir eine Schnittstelle bereitstellen, um das Symbol und die zugehörigen Informationen zur Anwendung zu implementieren. Nennen wir es Produkt:

protocol Product {
 var name: String { get }
 var image: Image { get }
 var executablePath: String { get }
}

Als nächstes müssen wir eine Schnittstelle bereitstellen, damit unsere Kunden die Ausstellung einer Reihe von Anwendungen für ihr spezifisches Produkt implementieren können – eine Reihe von Anwendungssymbolen mit Namen, die wir bereits im Framework zeichnen werden.

Lassen Sie uns diese Schnittstelle schreiben – Creator-Schnittstelle, die eine Factory-Methode enthält, die ein Array von Produkten zurückgibt.

protocol Creator {
 func factoryMethod() -> [Product]
}

In der Praxis

Der erste Kunde unseres AR-Frameworks war die Firma 7B – führender Anbieter von Software für Kaffeemaschinen in Honduras. Sie möchten Augmented-Reality-Brillen verkaufen, mit denen man Kaffee zubereiten, prüfen kann, ob Wasser/Bohnen voll sind, und mithilfe des Indoor-Kartenmodus den Weg zur nächsten Kaffeemaschine zeigen kann.

Sie übernehmen die Entwicklung der Software; wir sind lediglich verpflichtet, eine Dokumentation zu den Schnittstellen Creator und Product für die korrekte Anzeige der Liste der Anwendungen und deren Weitergabe bereitzustellen starten.

Nach der Übertragung der Dokumentation implementiert Unternehmen 7B mithilfe der Creator -Schnittstelle den Specific Creator – Klasse, die ein Array von Anwendungssymbolen zurückgibt. Die Symbolanwendungen selbst sind spezifische Produktklassen, die die Produkt-Schnittstelle implementieren.

Beispielcode für Spezifische Produkte:

class CoffeeMachineLocator: implements Product {
 let name = “7B Coffee Machine Locator v.3000”
 let image = Image.atPath(“images/locator.tga”)
 let executablePath = “CoffeeMachineLocator.wasm”
}

class iPuchinno: implements Product {
 let name = “iPuchinno 1.0.3”
 let image = Image.atPath(“images/puchino.pvrtc”)
 let executablePath = “neutron/ipuchBugFixFinalNoFreezeFixAlpha4.js”
}

Klasse Concrete Creator, die ein Array von zwei Anwendungen ergibt:

class 7BAppsCreator: implements Creator {
 func factoryMethod() -> [Product] {
  return [CoffeeMachineLocator(), iPuchinno()]
 }
}

Danach stellt das Unternehmen 7B die Bibliothek von Concrete Products, Concrete Creator, zusammen, kombiniert sie mit unserem Framework und beginnt mit dem Verkauf von AR-Brillen für seine Kaffeemaschinen, Ergänzungen unsererseits nicht erforderlich.

Quellen

https://refactoring.guru/ru/design-patterns/command
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

Musterbefehl

Befehlsmuster bezieht sich auf Verhaltensentwurfsmuster.

Das ist das Muster, an dem ich am längsten festgehalten habe. Es ist so einfach, dass es sehr komplex ist. Aber ich persönlich finde, dass das Schöne am Selbststudium darin besteht, dass man alle Zeit der Welt hat, ein bestimmtes Thema aus allen Blickwinkeln zu recherchieren.

In GoF wird die Anwendbarkeit also recht prägnant und klar beschrieben:
Verkapselt eine Anfrage als Objekt und ermöglicht es Ihnen, Clients mit unterschiedlichen Anfragen zu parametrisieren, Warteschlangen zu verwenden, Anfragen zu protokollieren und Abbruchvorgänge durchzuführen.

Jetzt implementieren wir eine einfache Version des Befehls aus der Beschreibung:

string fakeTrumpsRequest = “SELECT * from Users where name beginsWith DonaldTrump”

Wir haben die Anfrage in ein String-Klassenobjekt gekapselt. Es kann zum Konfigurieren von Clients, zum Hinzufügen von Befehlen zur Warteschlange, zum Protokollieren und zum Abbrechen (unter Verwendung des „Snapshot“-Musters) verwendet werden.

Mir scheint, dass dies völlig ausreicht, um SQL-Abfragen und dergleichen durchzuführen, aber dann gibt es noch Implementierungsdetails, unterschiedliche Anwendungsmöglichkeiten, die Codebasis des Musters, Client-Rollen und Hilfsklassen sind ebenfalls sehr unterschiedlich.

Materialteile

Das

Befehlsmuster beginnt mit einem Befehlsprotokoll, das eine einzelne execute()-Methode enthält. Als nächstes kommt der Spezifische Befehl und Empfänger. Der CC implementiert die Operation auf dem Empfänger und beschreibt die Verbindung zwischen dem Empfänger und der Aktion. Ist etwas unklar? Ich auch, aber lasst uns weitermachen. Der Clienterstellt eine Instanz eines Spezifischen Befehls und verknüpft ihn mit dem Empfänger. Aufrufer – Objekt, das den Prozess des Startens von Befehlen ausführt.

Jetzt versuchen wir es anhand eines Beispiels herauszufinden. Nehmen wir an, wir möchten myOS auf myPhone aktualisieren. Dazu starten wir die Anwendung myOS_Update! und drücken darin die Schaltfläche „Jetzt aktualisieren“. Nach 10 Sekunden wird das System dies tun Melden Sie ein erfolgreiches Update.

Der Client im obigen Beispiel ist die myOS_Update!-Anwendung, der Invoker ist die Schaltfläche „Jetzt aktualisieren!“ und startet den Spezifischen Befehl Aktualisierung des Systems mithilfe der Methodeexecute(), die auf den Receiver zugreift. Betriebssystem-Update-Daemon.

Beispiel verwenden

Akzeptieren wir die Benutzeroberfläche der myOS_Update-Anwendung! so gut, dass sie beschlossen, es als separates Produkt zu verkaufen, um eine Schnittstelle für die Aktualisierung anderer Betriebssysteme bereitzustellen. In diesem Fall implementieren wir eine Anwendung mit Unterstützung für Erweiterungen durch Bibliotheken. In den Bibliotheken wird es Implementierungen von Spezifischen Befehlen und Empfängern geben. Wir belassen den statischen/unveränderlichen Invoker , Client, Protokoll Befehle.

Es besteht also keine Notwendigkeit, veränderlichen Code zu unterstützen, da unser Code unverändert bleibt und Probleme aufgrund von Fehlern im Code ihrer Spezifischen Befehle nur bei der Implementierung auf der Clientseite auftreten können Empfänger. Außerdem besteht in dieser Implementierung keine Notwendigkeit, den Quellcode der Hauptanwendung zu übertragen, d. h. wir haben Befehle und UI-Interaktionen mithilfe des Musters Befehl gekapselt.

Quellen

https://refactoring.guru/ru/design-patterns/command
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

Erstellen von macOS-Anwendungen für Ubuntu OSXCross CMake

In diesem Beitrag beschreibe ich die Erstellung plattformübergreifender C++-Anwendungen für macOS auf einer Ubuntu-Build-Maschine mit CMake und osxcross.
Installieren Sie zunächst die osxcross-Toolchain:
https://github.com/tpoechtrager/osxcross
Die Installation erfolgt in drei Schritten, wobei Abhängigkeiten heruntergeladen werden:

cd tools
./get_dependencies.sh

Laden Sie XCode.xip von der offiziellen Apple-Website herunter und laden Sie dann das SDK von XCode herunter:

./gen_sdk_package_pbzx.sh /media/demensdeum/2CE62A79E62A4404/LinuxSupportStorage/xcode111.xip

Ich hoffe, Sie haben im letzten Schritt die XCode-Lizenzvereinbarung gelesen? Erstellen Sie als Nächstes die Toolchain mit dem erforderlichen Präfix:

INSTALLPREFIX=/home/demensdeum/Apps/osxcross ./build.sh 

Jetzt können Sie osxcross aus dem Präfixverzeichnis des vorherigen Schritts verwenden. Fügen wir ein neues Build-Makro für CMake hinzu und schreiben Sie alles Notwendige:

if (OSXCROSS)
SET(CMAKE_SYSTEM_NAME Darwin)
SET(CMAKE_C_COMPILER o64-clang)
SET(CMAKE_CXX_COMPILER o64-clang++)
SET(CMAKE_C_COMPILER_AR x86_64-apple-darwin19-ar)
SET(CMAKE_CXX_COMPILER_AR x86_64-apple-darwin19-ar)
SET(CMAKE_LINKER x86_64-apple-darwin19-ld)
SET(ENV{OSXCROSS_MP_INC} 1)
endif()

Die dynamische Verknüpfung war bei mir nicht erfolgreich, daher exportieren wir die Bibliotheken statisch:

if (OSXCROSS)
add_library(FlameSteelCore STATIC ${SOURCE_FILES})
else()

Als nächstes werden Sie vielleicht mit der Tatsache konfrontiert, dass Sie nicht über die notwendigen Bibliotheken für osxcross verfügen. Dies ist mir bei der Verwendung von SDL2 aufgefallen. osxcross unterstützt vorgefertigte Bibliothekspakete – Macports. Beispiel: Installation von SDL2-mixer:

osxcross-macports -v install libsdl2_mixer

Danach können Sie wie gewohnt mit dem Erstellen von Bibliotheken/Anwendungen im cmake-make-Link beginnen. Vergessen Sie nicht, bei Bedarf eine statische Verknüpfung von Bibliotheken anzugeben.

Manuelle Zusammenstellung von Bibliotheken

Derzeit bin ich auf das Problem einer fehlerhaften Archivierung von Bibliotheken während der statischen Verknüpfung gestoßen. Beim Erstellen der endgültigen Anwendung erhalte ich die Fehlermeldung:

file was built for archive which is not the architecture being linked (x86_64)

Sehr ähnlich zu diesem Ticket haben wir es geschafft, ein zu implementieren Dies führt zu einer Problemumgehung, die dazu führt, dass die Montage korrekt abgeschlossen wird. Entpacken wir die statische Bibliothek und erstellen sie mit dem osxcross-Archiver neu:

ar x ../libFlameSteelCore.a
rm ../libFlameSteelCore.a
x86_64-apple-darwin19-ar rcs ../libFlameSteelCore.a *.o

Ich persönlich halte auch die mangelnde Möglichkeit, macOS-Anwendungen direkt auf Ubuntu auszuführen (zumindest mit einigen Funktionen), für eines der Probleme. Natürlich gibt es ein Projekt Darling, aber die Unterstützung lässt immer noch zu wünschen übrig.

Quellen

https://github.com/tpoechtrager/osxcross

Erstellen Sie für Windows unter Ubuntu MinGW CMake

In diesem Beitrag beschreibe ich den Prozess der Erstellung von Bibliotheken und Anwendungen für Windows mithilfe der MinGW32-Toolchain unter Ubuntu.
Wine installieren, mingw:

sudo apt-get install wine mingw-w64

Danach können Sie bereits C/C++-Anwendungen für Windows erstellen:

# C
i686-w64-mingw32-gcc helloWorld.c -o helloWorld32.exe      # 32-bit
x86_64-w64-mingw32-gcc helloWorld.c -o helloWorld64.exe    # 64-bit
 
# C++
i686-w64-mingw32-g++ helloWorld.cc -o helloWorld32.exe     # 32-bit
x86_64-w64-mingw32-g++ helloWorld.cc -o helloWorld64.exe   # 64-bit

Die gesammelte Exe kann mit Wine überprüft werden.

Als nächstes schauen wir uns die Änderungen am CMake-Build, der Datei CMakeLists.txt, an und fügen MinGW-spezifische Dinge zur Build-Datei hinzu:

if (MINGW32)
set(CMAKE_SYSTEM_NAME Windows)
SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
SET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
SET(CMAKE_RC_COMPILER i686-w64-mingw32-windres)
set(CMAKE_RANLIB i686-w64-mingw32-ranlib)
endif()

// для сборки shared dll
elseif (MINGW32)
add_library(FlameSteelEngineGameToolkit.dll SHARED ${SOURCE_FILES})
else()

// обязательно линкуем со всеми зависимостями
if (MINGW32)
target_link_libraries(
                        FlameSteelEngineGameToolkit.dll 
                        -static-libgcc
                        -static-libstdc++
                        SDL2 
                        SDL2_mixer 
                        /home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCore/FlameSteelCore.dll
                        /home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelBattleHorn/FlameSteelBattleHorn.dll
                        /home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCommonTraits/FlameSteelCommonTraits.dll)

set_target_properties(FlameSteelEngineGameToolkit.dll PROPERTIES
        PREFIX ""
        SUFFIX ""
        LINK_FLAGS "-Wl,--add-stdcall-alias"
        POSITION_INDEPENDENT_CODE 0 # this is to avoid MinGW warning; 
        # MinGW generates position-independent-code for DLL by default
)
else()

Sammeln:

cmake -DMINGW32=1 .
make

Die Ausgabe ist eine DLL oder Exe, je nachdem, was Sie sammeln. Ein funktionierendes Beispiel finden Sie im Repository des neuen Cube-Art-Projekts und seiner Bibliotheken:
https://gitlab.com/demensdeum/cube-art-project
https://gitlab.com/demensdeum/FlameSteelEngineGameToolkitFSGL
https://gitlab.com/demensdeum/cube-art-project-bootstrap

Quellen
https://arrayfire.com/cross-compile-to-windows-from-linux/

Einfacher Emscripten-Autotest für ChromeDriver

In diesem Hinweis beschreibe ich die Implementierung eines Autotests für den ChromeDriver des Chrome-Browsers, der mit Emscripten einen aus C++ übersetzten Modul-Autotest ausführt, die Konsolenausgabe liest und das Testergebnis zurückgibt.
Zuerst müssen Sie Selenium installieren, für Python 3-Ubuntu geht das so:

pip3 install selenium

Laden Sie als Nächstes ChromeDriver von der offiziellen Website herunter und legen Sie chromedriver beispielsweise in /usr/local/bin ab. Danach können Sie mit der Implementierung des Autotests beginnen.
Im Folgenden gebe ich den Autotest-Code an, der den Chrome-Browser startet, während die Autotest-Seite auf Emscripten geöffnet ist, und prüft, ob der Text „Fenstertest erfolgreich“ vorhanden ist:

import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

capabilities = DesiredCapabilities.CHROME
capabilities['goog:loggingPrefs'] = { 'browser':'ALL' }
driver = webdriver.Chrome()
driver.get("http://localhost/windowInitializeTest/indexFullscreen.html")

time.sleep(2)

exitCode = 1

for entry in driver.get_log('browser'):
    if entry["source"] == "console-api":
        message = entry["message"]
        if "Window test succeded" in message:
            print("Test succeded")
            exitCode = 0

driver.close()
exit(exitCode)

Speichern Sie den Test als main.py und führen Sie python3 main.py aus

Erstellen eines Projekts mit Abhängigkeiten für Emscripten

In diesem Beitrag beschreibe ich den Aufbau eines Projekts, das aus mehreren Bibliotheken besteht, mit Emscripten.
Derzeit unterstützt Emscripten den Aufbau gemeinsam genutzter Bibliotheken nicht, daher besteht der erste Schritt darin, alle Bibliotheken von „Shared“ auf „Static“ zu übertragen. Emscripten arbeitet mit seinen eigenen Include-Dateien, daher muss das Problem mit der Sichtbarkeit von Header-Dateien gelöst werden, indem ich einen Symlink aus dem Systemverzeichnis an die Emscripten-Toolchain weitergeleitet habe:

ln -s /usr/local/include/FlameSteelFramework $EMSDK/fastcomp/emscripten/system/include/FlameSteelFramework

Wenn Sie CMake verwenden, müssen Sie SHARED->STATIC in der Datei CMakeLists.txt der Methode add_library ändern. Mit den folgenden Befehlen können Sie eine Bibliothek/Anwendung für weitere statische Verknüpfungen erstellen:

emcmake cmake .
emmake make

Als nächstes müssen Sie die Hauptanwendung erstellen und beim Verknüpfen *.a-Bibliotheksdateien angeben. Ich konnte keinen relativen Pfad angeben; der Build wurde erst korrekt abgeschlossen, nachdem die vollständigen Pfade in der Datei CMakeLists.txt angegeben wurden:

elseif(EMSCRIPTEN)
target_link_libraries(${FSEGT_PROJECT_NAME} GL GLEW 
/home/demensdeum/Sources/cube-art-project-bootstrap/cube-art-project/sharedLib/libCubeArtProject.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelEngineGameToolkitFSGL/libFlameSteelEngineGameToolkitFSGL.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelEngineGameToolkit/libFlameSteelEngineGameToolkit.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCore/libFlameSteelCore.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelBattleHorn/libFlameSteelBattleHorn.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FSGL/libFSGL.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCommonTraits/libFlameSteelCommonTraits.a)
else()

Quellen

https://emscripten.org/ docs/compiling/Building-Projects.html#using-libraries

Gemeinsame Bibliothek CMake C++

Ich habe vor kurzem beschlossen, alle Teile des FlameSteelFramework in separate gemeinsam genutzte Bibliotheken zu verwandeln. Anschließend zeige ich ein Beispiel einer CMakeLists.txt-Datei für FlameSteelCore:

cmake_minimum_required(VERSION 3.5)

project (FlameSteelCore)
set(CMAKE_BUILD_TYPE Release)

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)

file(GLOB_RECURSE SOURCE_FILES
    "src/FlameSteelCore/*.cpp"
)

add_library(FlameSteelCore SHARED ${SOURCE_FILES})

install(DIRECTORY "${CMAKE_SOURCE_DIR}/src/FlameSteelCore"
        DESTINATION include/FlameSteelFramework
        FILES_MATCHING
        PATTERN "*.h"
)

install(TARGETS FlameSteelCore DESTINATION lib)

Befehle, die CMake ausführt: Sammelt alle Dateien mit der Erweiterung *.cpp aus dem Verzeichnis src/FlameSteelCore/ in eine gemeinsam genutzte Bibliothek, kopiert alle Header mit der Erweiterung *.h von src/FlameSteelCore nach include/FlameSteelFramework (in meinem Fall). Dies ist /usr/local/include/FlameSteelFramework), kopiert die gemeinsam genutzte Bibliothek in das Verzeichnis lib (/usr/local/lib)
Nach der Installation kann es erforderlich sein, den LD-Cache zu aktualisieren – sudo ldconfig.
Um auf Ubuntu zu erstellen und zu installieren (wenn Sie über die richtige Build-Toolchain verfügen), führen Sie einfach die folgenden Befehle aus:

cmake . && make && sudo make install

Um den Installationsprozess zu testen, übergebe ich das Make-Präfix an den lokalen Ordner makeInstallTestPlayground:

cmake -DCMAKE_INSTALL_PREFIX:PATH=/home/demensdeum/makeInstallTestPlayground . && make && make install

Referenzen

https: //stackoverflow.com/questions/17511496/how-to-create-a-shared-library-with-cmake
https://stackoverflow.com/questions/6003374/what-is-cmake-equivalent-of-configure-prefix-dir-make-all-install

C++-Sprachinterpreter – Haften

Vor nicht allzu langer Zeit bin ich auf ein interessantes Projekt namens Cling gestoßen, einen C++-Sprachinterpreter, der unter anderem interaktiv von der Konsole aus arbeiten kann. Sie können das Projekt unter folgendem Link ansehen: https://github.com/root -project/ cling
Die Installation für Ubuntu ist einfach – Laden Sie das Archiv für die erforderliche Version herunter, entpacken Sie es, gehen Sie in den Ordner „bin“ und führen Sie „cling“ im Terminal aus.
Unten finden Sie ein Beispiel für das Laden der Bibliothek FlameSteelCore, das Initialisieren des Objekts und das Drucken der ID:

Verlorene Emscripten-Ausnahmen und Regex-Probleme

Verlorene Ausnahme

Eine interessante Funktion von Emscripten: Wenn Sie eine Spielschleife über emscripten_set_main_loop starten, sollten Sie bedenken, dass die Ausnahmebehandlung über try Catch direkt in der Schleifenmethode erneut hinzugefügt werden muss, weil Laufzeit verliert Try-Catch-Block von außen.
Am einfachsten ist es, den Fehlertext über den Browser per Javascript-Benachrichtigung anzuzeigen:

            catch (const std::exception &exc)
            {
                const char *errorText = exc.what();
                cout << "Exception: " << errorText << "; Stop execution" << endl;

                EM_ASM_(
                {
                    var errorText = UTF8ToString($0);
                    alert(errorText);

                }, errorText);

                abort();

Zu komplexer regulärer Ausdruck

Die Standardimplementierung von Regex kann eine error_complexity-Ausnahme auslösen, wenn sie den regulären Ausdruck für zu komplex hält. Dies geschieht in der aktuellen Implementierung von emscripten, daher empfehle ich Ihnen, Tests zum Parsen durch reguläre Ausdrücke zu implementieren oder Regex-Implementierungen von Drittanbietern zu verwenden.

Musterersteller

Das Builder-Muster gehört zu einer Gruppe von Mustern, deren Existenz mir nicht besonders klar ist, ich stelle die offensichtliche Redundanz fest. Gehört zur Gruppe der generativen Designmuster. Wird verwendet, um eine einfache Schnittstelle zum Erstellen komplexer Objekte zu implementieren.

Anwendbarkeit

Vereinfachung der Schnittstelle. Dies kann die Erstellung eines Objekts in Konstruktoren mit einer großen Anzahl von Argumenten erleichtern und die Lesbarkeit des Codes objektiv verbessern.

Beispiel in C++ ohne Builder:

auto weapon = new Weapon(“Claws”);
monster->weapon = weapon;
auto health = new MonsterHealth(100);
monster->health = health;

Пример со строителем на C++:

                  .addWeapon(“Claws”)
                  .addHealth(100)
                  .build();

Однако в языках поддерживающих именованные аргументы (named arguments), необходимость использовать именно для этого случая отпадает.

Пример на Swift с использованием named arguments:

let monster = Monster(weapon: “Claws”, health: 100)

Unveränderlichkeit. Mit dem Builder können Sie die Kapselung des erstellten Objekts bis zur endgültigen Montagephase sicherstellen. Hier müssen Sie sorgfältig darüber nachdenken, ob Sie durch die Verwendung eines Musters vor der hohen Dynamik der Umgebung, in der Sie arbeiten, bewahrt werden. Vielleicht bringt die Verwendung des Musters nichts, weil im Entwicklungsteam einfach keine Kapselungskultur herrscht .

Interaktion mit Komponenten in verschiedenen Phasen der Objekterstellung. Mithilfe des Musters ist es außerdem möglich, die schrittweise Erstellung eines Objekts bei der Interaktion mit anderen Komponenten des Systems sicherzustellen. Höchstwahrscheinlich ist dies sehr nützlich (?)

Kritik

Natürlich müssen Sie *sorgfältig* darüber nachdenken, ob es sich lohnt, das Muster in Ihrem Projekt flächendeckend zu nutzen. Sprachen mit moderner Syntax und einer erweiterten IDE machen die Verwendung des Builders überflüssig, was die Lesbarkeit des Codes verbessert (siehe den Punkt zu benannten Argumenten)
Hätte dieses Muster 1994 verwendet werden sollen, als das GoF-Buch veröffentlicht wurde? Höchstwahrscheinlich ja, aber gemessen an der Open-Source-Codebasis jener Jahre nutzten ihn nur wenige Menschen.

Quellen

https://refactoring.guru/ru/design-patterns/builder

Musterverbund

Das Composite-Muster bezieht sich auf strukturelle Designmuster; in inländischen Quellen ist es als „Compositor“ bekannt.
Nehmen wir an, wir entwickeln eine Anwendung – Fotoalbum. Der Benutzer kann Ordner erstellen, dort Fotos hinzufügen und andere Manipulationen durchführen. Sie benötigen auf jeden Fall die Möglichkeit, die Anzahl der Dateien in Ordnern und die Gesamtzahl aller Dateien und Ordner anzuzeigen.
Es ist offensichtlich, dass Sie einen Baum verwenden müssen, aber wie implementiert man eine Baumarchitektur mit einer einfachen und praktischen Schnittstelle? Das Composite-Muster kommt zur Rettung.

Sheila in Moonducks

Als nächstes implementieren wir im Verzeichnis die dataCount()-Methode – indem Sie alle im Array von Komponenten liegenden Elemente durchgehen und alle ihre dataCount’s addieren.
Alles ist bereit!
Unten ist ein Beispiel in Go:
package main

import "fmt"

type component interface {

dataCount() int

}

type file struct {

}

type directory struct {

c []component

}

func (f file) dataCount() int {

return 1

}

func (d directory) dataCount() int {

var outputDataCount int = 0

for _, v := range d.c {
outputDataCount += v.dataCount()
}

return outputDataCount

}

func (d *directory) addComponent(c component) {

d.c = append(d.c, c)

}

func main() {

var f file
var rd directory
rd.addComponent(f)
rd.addComponent(f)
rd.addComponent(f)
rd.addComponent(f)

fmt.Println(rd.dataCount())

var sd directory
sd.addComponent(f)

rd.addComponent(sd)
rd.addComponent(sd)
rd.addComponent(sd)

fmt.Println(sd.dataCount())
fmt.Println(rd.dataCount())

}

Quellen

https://refactoring.guru/ru/design-patterns/ zusammengesetzt

Musteradapter

Benjamín Núñez González

Das Adaptermuster bezieht sich auf strukturelle Designmuster.

Der Adapter ermöglicht die Daten-/Schnittstellenkonvertierung zwischen zwei Klassen/Schnittstellen.

Angenommen, wir entwickeln ein System zur Bestimmung der Käuferziele in einem Geschäft auf der Grundlage neuronaler Netze. Das System empfängt einen Videostream von einer Filialkamera, identifiziert Kunden anhand ihres Verhaltens und klassifiziert sie in Gruppen. Arten von Gruppen – kam, um zu kaufen (potenzieller Käufer), nur um zuzusehen (Zauberer), kam, um etwas zu stehlen (Dieb), kam, um die Ware zurückzugeben (unzufriedener Käufer), kam betrunken/high (potenzieller Rowdy).

Wie alle erfahrenen Entwickler finden wir ein fertiges neuronales Netzwerk, das Affenarten in einem Käfig anhand eines Videostreams klassifizieren kann, den das Zoologische Institut des Berliner Zoos freundlicherweise frei zur Verfügung gestellt hat, und auf einem Videostream nachzutrainieren aus dem Laden und erhalten Sie ein funktionierendes, hochmodernes System.

Es gibt nur ein kleines Problem – Der Videostream ist im MPEG2-Format kodiert und unser System unterstützt nur OGG Theora. Wir haben nicht den Quellcode des Systems, das Einzige, was wir tun können, ist – Ändern Sie den Datensatz und trainieren Sie das neuronale Netzwerk. Was zu tun? Schreiben Sie eine Adapterklasse, die den Stream von mpeg2 -> OGG Theora überträgt und an das neuronale Netzwerk sendet.

Nach dem klassischen Schema umfasst das Muster Client, Ziel, Adaptee und Adapter. Der Client ist in diesem Fall ein neuronales Netzwerk, das einen Videostream in OGG Theora empfängt, Ziel – die Schnittstelle, mit der es interagiert, adaptee – Schnittstelle zum Senden von Videostreams im MPEG2-Format, Adapter – konvertiert mpeg2 in OGG Theora und sendet es über die Zielschnittstelle.

Erscheint alles einfach?

Quellen

https://ru.wikipedia.org/wiki/Adapter_ (design_pattern)
https://refactoring.guru/ru/design-patterns/adapter

Delegiertenmuster

Das Delegatenmuster ist eines der wichtigsten Entwurfsmuster.
Nehmen wir an, wir entwickeln eine Friseuranwendung. Die Anwendung verfügt über einen Kalender zum Auswählen eines Tages für die Aufzeichnung; durch Tippen auf das Datum sollte eine Liste mit Friseuren mit einer Auswahl geöffnet werden.
Lassen Sie uns eine naive Verknüpfung von Systemkomponenten implementieren, Kalender und Bildschirm mithilfe von Zeigern aufeinander kombinieren, um eine Listenanzeige zu implementieren:


// псевдокод

class BarbershopScreen {
   let calendar: Calendar

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }
}

class Calendar {
    let screen: BarbershopScreen

    func handleTap(on date: Date) {
        screen.showBarbersList(date: date)
    }
}

Nach ein paar Tagen ändern sich die Anforderungen; vor der Anzeige der Liste müssen Sie Angebote mit einer Auswahl an Dienstleistungen (Bartschneiden usw.) anzeigen, jedoch nicht immer, an allen Tagen außer Samstag.
Wir fügen dem Kalender eine Prüfung hinzu, ob Samstag ist oder nicht. Abhängig davon nennen wir die Methode der Liste der Friseure oder der Liste der Dienstleistungen. Der Übersichtlichkeit halber werde ich Folgendes demonstrieren:


// псевдокод

class BarbershopScreen {
   let calendar: Calendar

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }

   func showOffersList() {
      showSelectionSheet(offers)
   }
}

class Calendar {
    let screen: BarbershopScreen

    func handleTap(on date: Date)  {
        if date.day != .saturday {
             screen.showOffersList()
        }
        else {
             screen.showBarbersList(date: date)
        }
    }
}

Eine Woche später werden wir gebeten, einen Kalender zum Feedback-Bildschirm hinzuzufügen, und in diesem Moment passiert das erste architektonische Ups!
Was zu tun? Der Kalender ist eng mit dem Friseurtermin-Bildschirm verknüpft.
Wow! Pfui! oh-oh
Wenn Sie weiterhin mit dieser verrückten Anwendungsarchitektur arbeiten, sollten Sie eine Kopie der gesamten Kalenderklasse erstellen und diese Kopie mit dem Feedback-Bildschirm verknüpfen.
Ok, sieht gut aus, dann haben wir noch ein paar Bildschirme und mehrere Kopien des Kalenders hinzugefügt, und dann war es soweit. Wir wurden gebeten, das Design des Kalenders zu ändern, was bedeutet, dass wir jetzt alle Kopien des Kalenders finden und bei allen die gleichen Änderungen vornehmen müssen. Dieser „Ansatz“ hat großen Einfluss auf die Entwicklungsgeschwindigkeit und erhöht die Wahrscheinlichkeit, einen Fehler zu machen. Dies hat zur Folge, dass solche Projekte im Chaos enden, wenn selbst der Autor der ursprünglichen Architektur nicht mehr versteht, wie Kopien seiner Klassen funktionieren, und andere im Laufe der Zeit hinzugefügte Hacks plötzlich auseinanderfallen.
Was musste getan werden, oder noch besser, womit konnte man noch nicht zu spät beginnen? Verwenden Sie das Delegationsmuster!
Die Delegation ist eine Möglichkeit, Klassenereignisse über eine gemeinsame Schnittstelle weiterzuleiten. Unten finden Sie ein Beispiel für einen Delegaten für einen Kalender:

protocol CalendarDelegate {
   func calendar(_ calendar: Calendar, didSelect date: Date)
}

Jetzt fügen wir dem Beispielcode den Code für die Arbeit mit dem Delegaten hinzu:


// псевдокод

class BarbershopScreen: CalendarDelegate {
   let calendar: Calendar

   init() {
       calendar.delegate = self
   }

   func calendar(_ calendar: Calendar, didSelect date: Date) {
        if date.day != .saturday {
            showOffersList()
        }
        else {
             showBarbersList(date: date)
        }
   }

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }

   func showOffersList() {
      showSelectionSheet(offers)
   }
}

class Calendar {
    weak var delegate: CalendarDelegate

    func handleTap(on date: Date)  {
        delegate?.calendar(self, didSelect: date)
    }
}

Daher haben wir den Kalender vollständig vom Bildschirm entkoppelt. Bei der Auswahl eines Datums aus dem Kalender wird das Datumsauswahlereignis – *delegiert* die Ereignisverarbeitung an den Abonnenten; Der Abonnent ist der Bildschirm.
Welche Vorteile ergeben sich für uns aus diesem Ansatz? Jetzt können wir die Kalender- und Bildschirmlogik unabhängig voneinander ändern, ohne Klassen zu duplizieren, was die weitere Unterstützung vereinfacht; Dadurch wird das „Prinzip der Alleinverantwortung“ für die Umsetzung von Systemkomponenten umgesetzt und das DRY-Prinzip eingehalten.
Wenn Sie die Delegation verwenden, können Sie die Logik für die Anzeige von Fenstern und die Reihenfolge von allem auf dem Bildschirm hinzufügen und ändern. Dies hat keinerlei Auswirkungen auf den Kalender und andere Klassen, die objektiv nicht an Prozessen teilnehmen sollten, die nicht direkt mit ihnen zusammenhängen.< br />Alternativ können Programmierer, die sich nicht allzu sehr darum kümmern, Nachrichten über einen gemeinsamen Bus senden, ohne eine separate Protokoll-/Delegiertenschnittstelle zu schreiben, wo es besser wäre, die Delegation zu verwenden. Ich habe in einem früheren Beitrag über die Nachteile dieses Ansatzes geschrieben – „Beobachtermuster.“

Quellen

https://refactoring.guru/ru/replace-inheritance -with-delegation

Beobachtermuster

Das Observer-Muster bezieht sich auf Verhaltensentwurfsmuster.
Das Muster ermöglicht es Ihnen, eine Änderung im Zustand eines Objekts über eine gemeinsame Schnittstelle an Abonnenten zu senden.
Nehmen wir an, wir entwickeln einen Messenger für Programmierer, wir haben einen Chat-Bildschirm in der Anwendung. Wenn Sie eine Nachricht mit dem Text „Problem“ und „Fehler“ oder „etwas stimmt nicht“ erhalten, müssen Sie den Fehlerlistenbildschirm und den Einstellungsbildschirm rot einfärben.
Als Nächstes beschreibe ich zwei Optionen zur Lösung des Problems. Die erste ist einfach, aber äußerst schwierig zu unterstützen, und die zweite ist wesentlich stabiler in der Unterstützung, erfordert aber bei der ersten Implementierung ein Kopfdrehen.

Gemeinsamer Bus

Alle Implementierungen des Musters beinhalten das Senden von Nachrichten bei Datenänderungen, das Abonnieren von Nachrichten und die weitere Verarbeitung in Methoden. Die Shared-Bus-Option enthält ein einzelnes Objekt (normalerweise ein Singleton), das Nachrichten an Empfänger versendet.
Die Einfachheit der Implementierung ist wie folgt:

  1. Das Objekt sendet eine abstrakte Nachricht an den gemeinsam genutzten Bus
  2. Ein anderes Objekt, das den gemeinsam genutzten Bus abonniert hat, fängt die Nachricht ab und entscheidet, ob sie verarbeitet wird oder nicht.

Eine der von Apple verfügbaren Implementierungsoptionen (NSNotificationCenter-Subsystem) fügte den Abgleich des Nachrichtenheaders mit dem Namen der Methode hinzu, die vom Empfänger bei der Zustellung aufgerufen wird.
Der größte Nachteil dieses Ansatzes – Wenn Sie die Nachricht weiter ändern, müssen Sie sich zunächst alle Orte merken und dann manuell bearbeiten, an denen sie verarbeitet und gesendet wird. Es handelt sich um eine schnelle Erstimplementierung, gefolgt von einem langen, komplexen Support, der eine Wissensbasis für den korrekten Betrieb erfordert.

Multicast-Delegierter

In dieser Implementierung erstellen wir die endgültige Multicast-Delegatenklasse; genau wie im Fall eines gemeinsam genutzten Busses können Objekte diese abonnieren, um „Nachrichten“ oder „Ereignisse“ zu empfangen, die Arbeit des Parsens und Filterns von Nachrichten ist jedoch anders nicht den Objekten zugeordnet. Stattdessen müssen Abonnentenklassen die Multicast-Methoden des Delegaten implementieren, mit denen er sie benachrichtigt.
Dies wird durch die Verwendung von Delegate-Schnittstellen/-Protokollen implementiert. Wenn sich die allgemeine Schnittstelle ändert, wird die Anwendung nicht mehr erstellt. Zu diesem Zeitpunkt müssen alle Stellen für die Verarbeitung einer bestimmten Nachricht neu erstellt werden, ohne dass eine separate Wissensdatenbank verwaltet werden muss um mich an diese Orte zu erinnern. Der Compiler ist dein Freund.
Dieser Ansatz erhöht die Produktivität des Teams, da keine Dokumentation geschrieben oder gespeichert werden muss und ein neuer Entwickler nicht versuchen muss, zu verstehen, wie eine Nachricht und ihre Argumente verarbeitet werden, sondern sie mit einer praktischen und verständlichen Benutzeroberfläche arbeiten , so wird das Dokumentationsparadigma durch Code umgesetzt.
Der Multicast-Delegat selbst basiert auf dem Delegatenmuster, über das ich im nächsten Beitrag schreiben werde.

Quellen

https://refactoring.gu/ru/design-patterns/observer

Proxy-Muster

Das Proxy-Muster bezieht sich auf strukturelle Entwurfsmuster.
Das Muster beschreibt die Technik der Arbeit mit einer Klasse über eine Klassenebene – Proxy. Ein Proxy ermöglicht es Ihnen, die Funktionalität der ursprünglichen Klasse zu ändern und dabei das ursprüngliche Verhalten beizubehalten und gleichzeitig die ursprüngliche Klassenschnittstelle beizubehalten.
Stellen wir uns die Situation vor – Im Jahr 2015 beschließt eines der Länder Westeuropas, alle Anfragen an die Websites der Benutzer des Landes aufzuzeichnen, um Statistiken und ein tieferes Verständnis der politischen Gefühle der Bürger zu verbessern.
Stellen wir uns den Pseudocode einer naiven Implementierung des Gateways vor, über das Bürger auf das Internet zugreifen:

class InternetRouter {

    private let internet: Internet

    init(internet: Internet) {
        self.internet = internet
    }

    func handle(request: Request, from client: Client) -> Data {
        return self.internet.handle(request)
    }

}

Im obigen Code erstellen wir eine Internet-Router-Klasse mit einem Zeiger auf ein Objekt, das den Internetzugang bereitstellt. Wenn ein Kunde eine Website-Anfrage stellt, geben wir eine Antwort aus dem Internet zurück.

Mithilfe des Proxy-Musters und des Singleton-Antimusters fügen wir Funktionen zum Protokollieren des Clientnamens und der URL hinzu:

class InternetRouterProxy {

    private let internetRouter: InternetRouter

    init(internet: Internet) {
        self.internetRouter = InternetRouter(internet: internet)
    }

    func handle(request: Request, from client: Client) -> Data {

        Logger.shared.log(“Client name: \(client.name), requested URL: \(request.URL)”)

        return self.internetRouter.handle(request: request, from: client)
    }

}

Aufgrund der Beibehaltung der ursprünglichen InternetRouter-Schnittstelle in der Proxy-Klasse InternetRouterProxy reicht es aus, die Initialisierungsklasse von InternerRouter durch ihren Proxy zu ersetzen, es sind keine weiteren Änderungen an der Codebasis erforderlich.

Quellen

https://refactoring.guru/ru/design-patterns/ Proxy

Muster-Prototyp

Das Prototypmuster gehört zur Gruppe der generativen Designmuster.
Nehmen wir an, wir entwickeln Dating-Apps Tender. Gemäß unserem Geschäftsmodell haben wir die Möglichkeit, Kopien Ihres eigenen Profils zu erstellen und dabei den Namen und die Reihenfolge der Fotos an einigen Stellen automatisch zu ändern. Dies wurde getan, damit der Benutzer die Möglichkeit hat, in der Anwendung mehrere Profile gleichzeitig mit unterschiedlichen Freunden zu verwalten.
Durch Klicken auf die Schaltfläche zum Erstellen einer Kopie des Profils müssen wir das Kopieren des Profils, die automatische Generierung eines Namens und die Neusortierung der Fotos implementieren.
Naive Pseudocode-Implementierung:

fun didPressOnCopyProfileButton() {
    let profileCopy = new Profile()
    profileCopy.name = generateRandomName()
    profileCopy.age = profile.age
    profileCopy.photos = profile.photos.randomize()
    storage.save(profileCopy)
}

Stellen wir uns nun vor, dass andere Teammitglieder den Kopiercode kopiert oder von Grund auf neu erfunden haben und danach ein neues Feld hinzugefügt wurde – mag. In diesem Feld wird die Anzahl der Profil-Likes gespeichert. Jetzt müssen Sie *alle* Stellen, an denen das Kopieren erfolgt, manuell aktualisieren, indem Sie ein neues Feld hinzufügen. Es ist sehr zeitaufwändig und schwierig, den Code zu warten und zu testen.
Um dieses Problem zu lösen, wurde das Prototype-Entwurfsmuster erfunden. Erstellen wir ein allgemeines Kopierprotokoll mit einer copy()-Methode, die eine Kopie eines Objekts mit den erforderlichen Feldern zurückgibt. Nach dem Ändern von Entitätsfeldern müssen Sie nur eine copy()-Methode aktualisieren, anstatt alle Stellen, die Kopiercode enthalten, manuell zu suchen und zu aktualisieren.

Quellen

https://refactoring.guru/ru/design-patterns/prototype

Zustandsmaschine und Musterbedingung

In diesem Artikel werde ich die Verwendung der Zustandsmaschine (State Machine) beschreiben und eine einfache Implementierung zeigen, eine Implementierung unter Verwendung des Zustandsmusters. Es ist erwähnenswert, dass es unerwünscht ist, das Statusmuster zu verwenden, wenn es weniger als drei Zustände gibt, weil Dies führt normalerweise zu einer unnötigen Komplexität der Codelesbarkeit und damit verbundenen Supportproblemen – Alles sollte in Maßen erfolgen.

MEAACT PHOTO / STUART PRICE.

Herr der Flaggen

Angenommen, wir entwickeln einen Videoplayer-Bildschirm für das Mediensystem eines Zivilflugzeugs. Der Player muss in der Lage sein, einen Videostream zu laden und abzuspielen, dem Benutzer das Stoppen des Downloadvorgangs, das Zurückspulen und die Ausführung anderer üblicher Vorgänge zu ermöglichen ein Spieler.
Nehmen wir an, der Player hat den nächsten Teil des Videostreams zwischengespeichert, überprüft, ob genügend Teile für die Wiedergabe vorhanden sind, hat mit der Wiedergabe des Fragments für den Benutzer begonnen und fährt gleichzeitig mit dem Herunterladen des nächsten Teils fort.
In diesem Moment spult der Benutzer zur Mitte des Videos zurück, d. h. Sie müssen jetzt die Wiedergabe des aktuellen Fragments stoppen und an einer neuen Position mit dem Laden beginnen. Es gibt jedoch Situationen, in denen dies nicht möglich ist – Der Benutzer kann die Wiedergabe des Videostreams nicht steuern, während ihm ein Video über Flugsicherheit gezeigt wird. Lassen Sie uns das isSafetyVideoPlaying-Flag überprüfen, um diese Situation zu überprüfen.
Das System muss außerdem in der Lage sein, das aktuelle Video anzuhalten und über den Player eine Warnung des Schiffskapitäns und der Schiffsbesatzung zu senden. Fügen wir ein weiteres isAnnouncementPlaying-Flag hinzu. Außerdem besteht die Anforderung, die Wiedergabe nicht anzuhalten, während Hilfe zur Arbeit mit dem Player angezeigt wird. Ein weiteres Flag ist HelpPresenting.

Beispiel-Pseudocode für einen Mediaplayer:

class MediaPlayer {

    public var isHelpPresenting = false
    public var isCaching = false
    public var isMediaPlaying: Bool = false
    public var isAnnouncementPlaying = false
    public var isSafetyVideoPlaying = false

    public var currentMedia: Media = null

    fun play(media: Media) {

        if isMediaPlaying == false, isAnnouncementPlaying == false, isSafetyVideoPlaying == false {

            if isCaching == false {
                if isHelpPresenting == false {
                    media.playAfterHelpClosed()
                }
                else {
                    media.playAfterCaching()
                }
            }
    }

    fun pause() {
        if isAnnouncementPlaying == false, isSafetyVideoPlaying == false {
            currentMedia.pause()
        }
    }
}

Das obige Beispiel ist aufgrund der hohen Variabilität (Entropie) schwer zu lesen und schwer zu warten. Dieses Beispiel basiert auf meiner Erfahrung mit der Codebasis *vieler* Projekte, die keine Zustandsmaschine verwendeten.
Jedes Kontrollkästchen muss speziell die Elemente der Schnittstelle und der Geschäftslogik der Anwendung „steuern“. Durch das Hinzufügen eines weiteren Kontrollkästchens muss der Entwickler in der Lage sein, sie zu jonglieren und alles mehrmals mit allen möglichen Optionen zu überprüfen.
Durch Einsetzen in die Formel „2 ^ Anzahl der Kontrollkästchen“ erhalten Sie 2 ^ 6 = 64 Optionen für das Anwendungsverhalten für nur 6 Kontrollkästchen. Alle diese Kombinationen von Kontrollkästchen müssen manuell überprüft und verwaltet werden.
Aus Sicht des Entwicklers sieht das Hinzufügen neuer Funktionen mit einem solchen System folgendermaßen aus:
– Wir müssen die Möglichkeit hinzufügen, die Browserseite der Fluggesellschaft anzuzeigen, und sie sollte wie bei Filmen minimiert werden, wenn Besatzungsmitglieder etwas ankündigen.
– Ok, ich werde es tun. (Oh verdammt, ich muss eine weitere Flagge hinzufügen und alle Stellen, an denen sich die Flaggen kreuzen, noch einmal überprüfen, das sind viele Dinge, die geändert werden müssen!)

Auch ein Schwachpunkt des Flaggensystems – Änderungen am Verhalten der Anwendung vornehmen. Es ist sehr schwer vorstellbar, wie man das Verhalten anhand von Flags schnell/flexibel ändern kann, wenn man nach der Änderung nur eines Flags alles noch einmal überprüfen muss. Dieser Entwicklungsansatz führt zu vielen Problemen, Zeit- und Geldverlusten.

Betreten Sie die Maschine

Wenn Sie sich die Flags genau ansehen, können Sie verstehen, dass wir tatsächlich versuchen, bestimmte Prozesse zu verarbeiten, die in der realen Welt auftreten. Wir listen sie auf: Normalmodus, Anzeige eines Sicherheitsvideos, Übertragung einer Nachricht des Kapitäns oder der Besatzungsmitglieder. Für jeden Prozess ist ein Regelwerk bekannt, das das Verhalten der Anwendung verändert.
Gemäß den Regeln des State-Machine-Musters (State-Machine) werden wir alle Prozesse als Zustände in der Aufzählung auflisten, ein solches Konzept als Zustand zum Player-Code hinzufügen und zustandsbasiertes Verhalten implementieren, indem wir Kombinationen auf den Flags entfernen. Dadurch reduzieren wir die Testmöglichkeiten auf genau die Anzahl der Zustände.

Pseudocode:

enum MediaPlayerState {
	mediaPlaying,
	mediaCaching,
	crewSpeaking,
	safetyVideoPlaying,
	presentingHelp
}

class MediaPlayer {
	fun play(media: Media) {
		media.play()
	}

	func pause() {
		media.pause()
	}
}

class MediaPlayerStateMachine {
	public state: MediaPlayerState
	public mediaPlayer: MediaPlayer
	public currentMedia: Media

	//.. init (mediaPlayer) etc

	public fun set(state: MediaPlayerState) {
		switch state {
			case mediaPlaying:
				mediaPlayer.play(currentMedia)
			case mediaCaching, crewSpeaking,
			safetyVideoPlaying, presentingHelp:
				mediaPlayer.pause()
		}
	}
}

Der große Unterschied zwischen einem Flag-System und einer Zustandsmaschine ist der logische Zustandsschalttrichter in der set(state: ..)-Methode, der es Ihnen ermöglicht, das menschliche Verständnis des Zustands in Programmcode zu übersetzen, ohne sich mit Logik auseinandersetzen zu müssen Spiele zum Konvertieren von Flags in Zustände bei weiterer Codeunterstützung.

Musterstatus

Als nächstes werde ich den Unterschied zwischen der naiven Implementierung der Zustandsmaschine und dem Zustandsmuster zeigen. Stellen wir uns vor, wir müssten 10 Zustände hinzufügen. Dadurch wächst die Zustandsmaschinenklasse auf die Größe eines Gottobjekts, dessen Wartung schwierig und kostspielig sein wird. Natürlich ist diese Implementierung besser als die Flag-Implementierung (mit dem Flag-System erschießt sich der Entwickler zuerst selbst, und wenn nicht, dann hängt sich die Qualitätssicherung bei 2 ^ 10 = 1024 Variationen auf, aber wenn beide *nicht tun Beachten* Sie die Komplexität der Aufgabe, dann wird der Benutzer, dessen Anwendung einfach ist, bemerken, dass sie sich weigert, mit einer bestimmten Kombination von Flags zu arbeiten)
Bei einer großen Anzahl von Zuständen ist die Verwendung des Zustandsmusters erforderlich.
Fügen wir dem State-Protokoll eine Reihe von Regeln hinzu:

protocol State {
    func playMedia(media: Media, context: MediaPlayerContext)
    func shouldCacheMedia(context: MediaPlayerContext)
    func crewSpeaking(context: MediaPlayerContext)
    func safetyVideoPlaying(context:MediaPlayerContext)
    func presentHelp(context: MediaPlayerContext)
}

Lassen Sie uns die Implementierung des Regelsatzes in separate Zustände verschieben, zum Beispiel den Code für einen Zustand:

class CrewSpeakingState: State {
	func playMedia(context: MediaPlayerContext) {
		showWarning(“Can’ t play media - listen to announce!”)
	}

	func mediaCaching(context: MediaPlayerContext) {
		showActivityIndicator()
	}

	func crewSpeaking(context: MediaPlayerContext) {
		set(volume: 100)
	}

	func safetyVideoPlaying(context: MediaPlayerContext) {
		set(volume: 100)
	}

	func presentHelp(context: MediaPlayerContext) {
		showWarning(“Can’ t present help - listen to announce!”)
	}
}

Als nächstes erstellen wir einen Kontext, mit dem jeder Zustand arbeiten wird, und integrieren die Zustandsmaschine:

final class MediaPlayerContext {
	private
	var state: State

	public fun set(state: State) {
		self.state = state
	}

	public fun play(media: Media) {
		state.play(media: media, context: this)
	}

	…
	остальные возможные события
}

Anwendungskomponenten arbeiten mit dem Kontext über öffentliche Methoden; Zustandsobjekte entscheiden selbst, von welchem ​​Zustand aus sie über die Zustandsmaschine innerhalb des Kontexts wechseln.
Daher haben wir die God-Object-Zerlegung implementiert. Die Aufrechterhaltung eines sich ändernden Zustands wird viel einfacher, da der Compiler Änderungen im Protokoll verfolgt und die Komplexität des Verständnisses von Zuständen aufgrund der Reduzierung der Anzahl der Codezeilen verringert und sich darauf konzentriert Lösung eines bestimmten Staatsproblems. Sie können jetzt auch die Arbeit in einem Team teilen und Teammitgliedern die Implementierung eines bestimmten Zustands übertragen, ohne sich Gedanken über die Notwendigkeit machen zu müssen, Konflikte zu „lösen“, was bei der Arbeit mit einer großen Zustandsmaschinenklasse der Fall ist.

Quellen

https://refactoring.guru/ru/design-patterns/state

Skelettanimation (Teil 1 – Shader)

In diesem Artikel werde ich mein Verständnis der Skelettanimation beschreiben, die in allen modernen 3D-Engines zur Animation von Charakteren, Spielumgebungen usw. verwendet wird.
Ich beginne die Beschreibung mit dem greifbarsten Teil – – Vertex-Shader, denn der gesamte Berechnungspfad, egal wie komplex er auch sein mag, endet mit der Übergabe der vorbereiteten Daten zur Anzeige an den Vertex-Shader.

Die Skelettanimation geht nach der Verarbeitung auf der CPU in den Vertex-Shader.
Ich möchte Sie an die Formel für Scheitelpunkte ohne Skelettanimation erinnern:
gl_Position = projectMatrix * viewMatrix * modelMatrix * vertex;
Für diejenigen, die nicht verstehen, wie diese Formel zustande kam, können Sie meinen Artikel lesen, der das Prinzip der Arbeit mit Matrizen zur Anzeige von 3D-Inhalten im Kontext von OpenGL beschreibt.
Im Übrigen – Formel zur Implementierung einer Skelettanimation:
” vec4animierterVertex = bone0matrix * vertex * bone0weight +”
“bone1matrix * vertex * bone1weight +”
“bone2matrix * vertex * bone2weight +”
“bone3matrix * vertex * bone3weight;\n”
” gl_Position = projectMatrix * viewMatrix * modelMatrix * animatedVertex;\n”

Das heißt, wir multiplizieren die endgültige Knochentransformationsmatrix mit dem Scheitelpunkt und mit dem Gewicht dieser Matrix relativ zum Scheitelpunkt. Jeder Scheitelpunkt kann durch 4 Knochen animiert werden, die Stärke des Aufpralls wird durch den Knochengewichtsparameter reguliert, die Summe der Aufschläge sollte gleich eins sein.
Was tun, wenn weniger als 4 Knochen den Scheitelpunkt betreffen? Wir müssen das Gewicht zwischen ihnen aufteilen und die Auswirkung des Rests auf Null setzen.
Mathematisch wird die Multiplikation einer Gewichtung mit einer Matrix „Matrix-Skalar-Multiplikation“ genannt. Durch Multiplikation mit einem Skalar können Sie die Wirkung der Matrizen auf den resultierenden Scheitelpunkt zusammenfassen.

Die Knochentransformationsmatrizen selbst werden als Array übertragen. Darüber hinaus enthält das Array Matrizen für das gesamte Modell als Ganzes und nicht für jedes Netz einzeln.

Aber für jeden Scheitelpunkt werden die folgenden Informationen separat übertragen:
– Index der Matrix, die den Scheitelpunkt beeinflusst
– Gewicht der Matrix, das den Scheitelpunkt beeinflusst
Es wird mehr als ein Knochen übertragen, meist wird die Wirkung von 4 Knochen am Scheitelpunkt genutzt.
Außerdem muss die Summe der Gewichte der 4 Würfel immer gleich eins sein.
Schauen wir uns als Nächstes an, wie es im Shader aussieht.
Matrix-Array:
“uniform mat4 bonesMatrices[kMaxBones];”

Informationen über die Wirkung von 4 Knochen auf jedem Scheitelpunkt:
“Attribut vec2 bone0info;”
“Attribut vec2 bone1info;”
“Attribut vec2 bone2info;”
“attribute vec2 bone3info;”

vec2 – In der X-Koordinate speichern wir den Index des Knochens (und konvertieren ihn im Shader in int), in der Y-Koordinate speichern wir das Gewicht des Aufpralls des Knochens auf den Scheitelpunkt. Warum müssen Sie diese Daten in einem zweidimensionalen Vektor übertragen? Weil GLSL die Übergabe von C-lesbaren Strukturen mit gültigen Feldern an den Shader nicht unterstützt.

Im Folgenden gebe ich ein Beispiel für den Erhalt der notwendigen Informationen aus einem Vektor für die weitere Substitution in die animierteVertex-Formel:

“int bone0Index = int(bone0info.x);”
“float bone0weight = bone0info.y;”
“mat4 bone0matrix = bonesMatrices[bone0Index];”

“int bone1Index = int(bone1info.x);”
“float bone1weight = bone1info.y;”
“mat4 bone1matrix = bonesMatrices[bone1Index];”

“int bone2Index = int(bone2info.x);”
“float bone2weight = bone2info.y;”
“mat4 bone2matrix = bonesMatrices[bone2Index];”

“int bone3Index = int(bone3info.x);”
“float bone3weight = bone3info.y;”
“mat4 bone3matrix = bonesMatrices[bone3Index];”

Jetzt sollte die auf der CPU gefüllte Vertex-Struktur wie folgt aussehen:
x, y, z, u, v, Bone0index, Bone0weight, Bone1index, Bone1weight, Bone2index, Bone2weight, Bone3index, Bone3weight

Die Vertex-Pufferstruktur wird beim Laden des Modells einmal gefüllt, aber Transformationsmatrizen werden bei jedem Rendering-Frame von der CPU an den Shader übertragen.

In den verbleibenden Teilen beschreibe ich das Prinzip der Animationsberechnung auf der CPU. Bevor ich sie an den Vertex-Shader übertrage, beschreibe ich den Baum der Knochenknoten und gehe durch die Hierarchie Animation-Modell-Knoten-Netz, Matrix Interpolation.

Quellen

http://ogldev.atspace.co. uk/www/tutorial38/tutorial38.html

Quellcode

https://gitlab.com/demensdeum/skeletal-animation