x86_64 Assembler + C = Eine Liebe

In dieser Notiz beschreibe ich den Prozess des Aufrufs von C-Funktionen aus dem Assembler.
Versuchen wir, printf(“Hello World!\n”); aufzurufen. und Exit(0);

    message: db "Hello, world!", 10, 0

section .text
    extern printf
    extern exit
    global main

main:
    xor	rax, rax
    mov	rdi, message    
    call printf
    xor rdi, rdi
    call exit

Alles ist viel einfacher als es scheint. Im Abschnitt .rodata beschreiben wir statische Daten, in diesem Fall die Zeile „Hallo Welt!“, 10 ist ein Zeilenumbruchzeichen und wir vergessen auch nicht, es auf Null zu setzen.

Im Codeabschnitt deklarieren wir die externen Funktionen printf, Exit der stdio- und stdlib-Bibliotheken und deklarieren auch die Eingabefunktion main:

    extern printf
    extern exit
    global main

Wir übergeben 0 von der Rax-Funktion an das Rückgaberegister. Sie können mov rax, 0; aber um es zu beschleunigen, benutzen sie xor rax, rax; Als nächstes übergeben wir einen Zeiger auf die Zeichenfolge an das erste Argument:

Далее вызываем внешнюю функцию Си printf:

    xor	rax, rax
    mov	rdi, message    
    call printf
    xor rdi, rdi
    call exit

In Analogie dazu übergeben wir 0 an das erste Argument und rufen „exit:“ auf.

    call exit

Wie die Amerikaner sagen:
Wer hört niemandem zu
Dieser Pilaw isst @ Alexander Pelevin

Quellen

https://www.devdungeon. com/content/how-mix-c-and-assembly
https://nekosecurity.com/x86-64-assembly/part-3-nasm-anatomy-syscall-passing-argument
https://www.cs.uaf.edu/2017/fall/cs301/reference/x86_64.html

Quellcode

https://gitlab.com/demensdeum/assembly-playground

Hallo Welt x86_64-Assembler

In diesem Beitrag beschreibe ich den Prozess der Einrichtung der IDE und schreibe den ersten Hello World in x86_64-Assembler für das Ubuntu-Linux-Betriebssystem.
Beginnen wir mit der Installation der SASM-IDE, Nasm-Assembler:

Далее запустим SASM и напишем Hello World:


section .text

main:
    mov rbp, rsp      ; for correct debugging
    mov rax, 1        ; write(
    mov rdi, 1        ;   STDOUT_FILENO,
    mov rsi, msg      ;   "Hello, world!\n",
    mov rdx, msglen   ;   sizeof("Hello, world!\n")
    syscall           ; );

    mov rax, 60       ; exit(
    mov rdi, 0        ;   EXIT_SUCCESS
    syscall           ; );

section .rodata
    msg: db "Hello, world!"
    msglen: equ $-msg

Hello World-Code aus dem Blog James Fisher, angepasst für Assemblierung und Debugging in SASM. In der SASM-Dokumentation heißt es, dass der Einstiegspunkt eine Funktion namens „main“ sein muss, da sonst das Debuggen und Kompilieren des Codes fehlerhaft ist.
Was haben wir in diesem Code gemacht? Habe einen Systemaufruf getätigt – Zugriff auf den Kernel des Linux-Betriebssystems mit korrekten Argumenten in Registern, einem Zeiger auf eine Zeichenfolge im Datenabschnitt.

Unter der Lupe

Sehen wir uns den Code genauer an:

global – директива ассемблера позволяющая задавать глобальные символы со строковыми именами. Хорошая аналогия – интерфейсы заголовочных файлов языков C/C++. В данном случае мы задаем символ main для функции входа.

section – директива ассемблера позволяющая задавать секции (сегменты) кода. Директивы section или segment равнозначны. В секции .text помещается код программы.

Обьявляем начало функции main. В ассемблере функции называются подпрограммами (subroutine)

Первая машинная команда mov – помещает значение из аргумента 1 в аргумент 2. В данном случае мы переносим значение регистра rbp в rsp. Из комментария можно понять что эту строку добавил SASM для упрощения отладки. Видимо это личные дела между SASM и дебаггером gdb.

Далее посмотрим на код до сегмента данных .rodata, два вызова syscall, первый выводит строку Hello World, второй обеспечивает выход из приложения с корректным кодом 0.

Представим себе что регистры это переменные с именами rax, rdi, rsi, rdx, r10, r8, r9. По аналогии с высокоуровневыми языками, перевернем вертикальное представление ассемблера в горизонтальное, тогда вызов syscall будет выглядеть так:

Тогда вызов печати текста:

Вызов exit с корректным кодом 0:

Рассмотрим аргументы подробнее, в заголовочном файле asm/unistd_64.h находим номер функции __NR_write – 1, далее в документации смотрим аргументы для write:
ssize_t write(int fd, const void *buf, size_t count);

Первый аргумент – файловый дескриптор, второй – буфер с данными, третий – счетчик байт для записи в дескриптор. Ищем номер файлового дескриптора для стандартного вывода, в мануале по stdout находим код 1. Далее дело за малым, передать указатель на буфер строки Hello World из секции данных .rodata – msg, счетчик байт – msglen, передать в регистры rax, rdi, rsi, rdx корректные аргументы и вызвать syscall.

Обозначение константных строк и длины описывается в мануале nasm:

Eigenschaften im Space Jaguar Action-Rollenspiel

Der erste Artikel über das in der Entwicklung befindliche Spiel, Space Jaguar Action RPG. In diesem Artikel beschreibe ich die Gameplay-Funktion des Jaguar – Eigenschaften.

Viele Rollenspiele verwenden ein statisches Charakterstatistiksystem, wie zum Beispiel die Statistiken von DnD (Stärke, Konstitution, Geschicklichkeit, Intelligenz, Weisheit, Charisma) oder Fallout – S.P.E.C.I.A.L (Stärke, Wahrnehmung, Ausdauer, Charisma, Intelligenz, Geschicklichkeit, Glück). ).

In Space Jaguar plane ich die Implementierung eines dynamischen Systems von Eigenschaften. Beispielsweise hat die Hauptfigur des Spiels Jag zu Beginn nur drei Eigenschaften – Beherrschung einer Klinge (Halbsäbel), zwielichtige Operationen (Geschäfte in der kriminellen Welt abschließen), Schurkenfähigkeiten (Schlösser knacken, Diebstahl). Während des Spiels werden den Charakteren im Rahmen des Spielmoduls dynamische Eigenschaften verliehen und entzogen. Alle Überprüfungen erfolgen auf der Grundlage des Niveaus bestimmter Eigenschaften, die für eine bestimmte Spielsituation erforderlich sind. Beispielsweise kann Jag eine Schachpartie nicht gewinnen, wenn er nicht über die Eigenschaft verfügt, Schach zu spielen, oder nicht über ein ausreichendes Level verfügt, um die Prüfung zu bestehen.

Zur Vereinfachung der Prüflogik erhält jedes Merkmal einen 6-stelligen Code in englischen Buchstaben, einen Namen und eine Beschreibung. Um beispielsweise eine Klinge zu besitzen:

bladeFightingAbility.name = "BLADFG"; 
bladeFightingAbility.description = "Blade fighting ability"; 
bladeFightingAbility.points = 3;

Перед стартом игрового модуля можно будет просмотреть список публичных проверок необходимых для прохождения, также создатель может скрыть часть проверок для создания интересных игровых ситуаций.

Ноу-хау? Будет ли интересно? Лично я нахожу такую систему интересной, позволяющей одновременно обеспечить свободу творчества создателям игровых модулей, и возможность переноса персонажей из разных, но похожих по характеристикам, модулей для игроков.

Hash-Tabelle

Mit der Hash-Tabelle können Sie eine assoziative Array-Datenstruktur (Wörterbuch) mit einer durchschnittlichen Leistung von O(1) für Einfüge-, Lösch- und Suchvorgänge implementieren.

Unten finden Sie ein Beispiel für die einfachste Implementierung einer Hash-Map in nodeJS:

Wie funktioniert es? Passen Sie auf Ihre Hände auf:

  • Innerhalb der Hash-Map befindet sich ein Array
  • Im Array-Element befindet sich ein Zeiger auf den ersten Knoten der verknüpften Liste
  • Speicher wird für ein Array von Zeigern zugewiesen (z. B. 65535 Elemente)
  • Sie implementieren eine Hash-Funktion, der Wörterbuchschlüssel ist die Eingabe, und am Ausgang kann sie alles tun, aber am Ende gibt sie den Index des Array-Elements zurück

So funktioniert die Aufnahme:

  • Am Eingang liegt ein Schlüsselpaar – Wert
  • Hash-Funktion gibt Index nach Schlüssel zurück
  • Einen verknüpften Listenknoten aus einem Array nach Index abrufen
  • Überprüfen Sie, ob es mit dem Schlüssel übereinstimmt
  • Wenn es übereinstimmt, ersetzen Sie den Wert
  • Wenn es nicht übereinstimmt, fahren Sie mit dem nächsten Knoten fort, bis wir einen Knoten mit dem erforderlichen Schlüssel finden oder finden.
  • Wenn der Knoten immer noch nicht gefunden wird, erstellen Sie ihn am Ende der verknüpften Liste

So funktioniert die Suche nach Schlüssel:

  • Am Eingang liegt ein Schlüsselpaar – Wert
  • Hash-Funktion gibt Index nach Schlüssel zurück
  • Einen verknüpften Listenknoten aus einem Array nach Index abrufen
  • Überprüfen Sie, ob es mit dem Schlüssel übereinstimmt
  • Wenn es übereinstimmt, wird der Wert zurückgegeben
  • Wenn es nicht übereinstimmt, fahren Sie mit dem nächsten Knoten fort, bis wir einen Knoten mit dem erforderlichen Schlüssel finden oder finden.

Warum brauchen wir eine verknüpfte Liste innerhalb eines Arrays? Aufgrund möglicher Kollisionen bei der Berechnung der Hash-Funktion. In diesem Fall befinden sich mehrere verschiedene Schlüssel-Wert-Paare am selben Index im Array. In diesem Fall wird die verknüpfte Liste durchlaufen, um den erforderlichen Schlüssel zu finden.

Quellen

https://ru.wikipedia.org/wiki/Hash-Tabelle
https://www.youtube.com/watch?v=wg8hZxMRwcw

Quellcode

https://gitlab.com/demensdeum/datastructures

Arbeiten mit Ressourcen in Android C++

Um mit Ressourcen in Android über ndk zu arbeiten – C++ gibt es mehrere Möglichkeiten:

  1. Verwenden Sie den Zugriff auf Ressourcen aus einer APK-Datei mit AssetManager
  2. Laden Sie Ressourcen aus dem Internet herunter, entpacken Sie sie in das Anwendungsverzeichnis und verwenden Sie sie mit Standard-C++-Methoden
  3. Kombinierte Methode – Greifen Sie über AssetManager auf das Archiv mit Ressourcen in der APK zu, entpacken Sie sie in das Anwendungsverzeichnis und verwenden Sie sie dann mit Standard-C++-Methoden

Als nächstes werde ich die kombinierte Zugriffsmethode beschreiben, die in der Flame Steel Engine-Spiel-Engine verwendet wird.
Wenn Sie SDL verwenden, können Sie den Zugriff auf Ressourcen von einer APK aus vereinfachen. Die Bibliothek umschließt Aufrufe an AssetManager und bietet Schnittstellen ähnlich wie stdio (fopen, fread, fclose usw.).

SDL_RWops *io = SDL_RWFromFile("files.fschest", "r");

Nachdem Sie das Archiv von der APK in den Puffer heruntergeladen haben, müssen Sie das aktuelle Arbeitsverzeichnis in das Anwendungsverzeichnis ändern. Es steht der Anwendung zur Verfügung, ohne dass zusätzliche Berechtigungen eingeholt werden müssen. Dazu verwenden wir einen SDL-Wrapper:

chdir(SDL_AndroidGetInternalStoragePath());

Als nächstes schreiben Sie das Archiv mit fopen, fwrite, fclose aus dem Puffer in das aktuelle Arbeitsverzeichnis. Sobald sich das Archiv in einem für C++ zugänglichen Verzeichnis befindet, entpacken Sie es. Zip-Archive können mit einer Kombination aus zwei Bibliotheken entpackt werden – minizip und zlib, das erste kann mit der Struktur von Archiven arbeiten, während das zweite Daten entpackt.
Um mehr Kontrolle zu erlangen und die Portierung zu vereinfachen, habe ich mein eigenes Archivformat ohne Komprimierung namens FSCest (Flame Steel Chest) implementiert. Dieses Format unterstützt das Archivieren eines Verzeichnisses mit Dateien und das Entpacken; Es gibt keine Unterstützung für die Ordnerhierarchie; Sie können nur mit Dateien arbeiten.
Wir verbinden den Header der FSCest-Bibliothek, entpacken das Archiv:

#include "fschest.h" 
FSCHEST_extractChestToDirectory(archivePath, SDL_AndroidGetInternalStoragePath()); 

Nach dem Entpacken haben die C/C++-Schnittstellen Zugriff auf die Dateien aus dem Archiv. Daher musste ich nicht die gesamte Arbeit mit Dateien in der Engine neu schreiben, sondern fügte nur das Entpacken von Dateien in der Startphase hinzu.

Quellen

https://developer.android.com/ndk/ Referenz/Gruppe/Asset

Quellcode

https://gitlab.com/demensdeum/space- Jaguar-Action-RPG
https://gitlab.com/demensdeum/fschest

Stapelmaschine und RPN

Angenommen, wir müssen einen einfachen Bytecode-Interpreter implementieren. Welchen Ansatz zur Implementierung dieser Aufgabe sollten wir wählen?

Datenstruktur Der Stack bietet die Möglichkeit, eine einfache Bytecode-Maschine zu implementieren. Funktionen und Implementierungen von Stack-Maschinen werden in vielen Artikeln im westlichen und inländischen Internet beschrieben; ich möchte nur erwähnen, dass die Java Virtual Machine ein Beispiel für eine Stack-Maschine ist.

Das Funktionsprinzip der Maschine ist einfach: Ein Programm mit Daten und Operationscodes (Opcodes) wird dem Eingang zugeführt und die erforderlichen Operationen werden durch Manipulationen am Stapel implementiert. Schauen wir uns ein Beispiel-Bytecode-Programm von meiner Stack-Maschine an:

пMVkcatS olleHП
 

Am Ausgang erhalten wir die Zeichenfolge „Hello StackVM“. Die Stapelmaschine liest das Programm von links nach rechts und lädt Daten Zeichen für Zeichen auf den Stapel, wenn ein Opcode im Symbol – implementiert den Befehl mithilfe des Stapels.

Beispiel für die Implementierung einer Stack-Maschine in nodejs:

Umgekehrte polnische Notation (RPN)

Stack -Maschinen sind auch einfach zum Implementieren von Taschenrechnern zu verwenden. Dafür verwenden sie umgekehrte polnische Notation (Postfix -Notation).
Beispiel einer regulären Infix-Notation:
2*2+3*4

Konvertiert in RPN:
22*34*+

Um den Postfix-Datensatz zu zählen, verwenden wir eine Stapelmaschine:
2– an die Spitze des Stapels (Stapel: 2)
2– an die Spitze des Stapels (Stapel: 2,2)
*– Holen Sie sich zweimal die Oberseite des Stapels, multiplizieren Sie das Ergebnis und senden Sie es an die Oberseite des Stapels (Stapel: 4)
3– an die Spitze des Stapels (Stapel: 4, 3)
4– an die Spitze des Stapels (Stapel: 4, 3, 4)
*– Holen Sie sich zweimal die Oberseite des Stapels, multiplizieren Sie das Ergebnis und senden Sie es an die Oberseite des Stapels (Stapel: 4, 12)
+– Holen Sie sich zweimal die Spitze des Stapels, addieren Sie das Ergebnis und senden Sie es an die Spitze des Stapels (Stapel: 16)

Wie Sie sehen können – Das Ergebnis der Operationen 16 verbleibt auf dem Stapel. Es kann durch Implementierung von Stapeldruck-Opcodes gedruckt werden, zum Beispiel:
p22*34*+P

P – Stapeldruck-Start-Opcode, p – Opcode zum Fertigstellen des Stapeldrucks und zum Senden der letzten Zeile zum Rendern.
Um arithmetische Operationen von Infix in Postfix umzuwandeln, wird der Algorithmus „Sorting Yard“ von Edsger Dijkstra verwendet. Ein Beispiel für die Implementierung finden Sie oben oder im Repository des NodeJS-Maschinenstack-Projekts unten.

Quellen

https:/ /tech.badoo.com/ru/article/579/interpretatory-bajt-kodov-svoimi-rukami/
https://ru.wikipedia.org/wiki/Обратная_польская_запись

Quellcode

https://gitlab.com/demensdeum/stackvm/< /p>

Skelettanimation (Teil 2 – Knotenhierarchie, Interpolation)

Ich beschreibe weiterhin den Skelettanimationsalgorithmus, wie er in der Flame Steel Engine implementiert ist.

Da der Algorithmus der komplexeste von allen ist, die ich implementiert habe, können in den Notizen zum Entwicklungsprozess Fehler auftreten. Im vorherigen Artikel über diesen Algorithmus habe ich einen Fehler gemacht; das Array von Knochen wird für jedes Netz separat und nicht für das gesamte Modell an den Shader übertragen.

Knotenhierarchie

Damit der Algorithmus korrekt funktioniert, ist es notwendig, dass das Modell eine Verbindung zwischen den Knochen untereinander enthält (Grafik). Stellen wir uns eine Situation vor, in der zwei Animationen gleichzeitig abgespielt werden – Springe und hebe deine rechte Hand. Die Sprunganimation muss das Modell entlang der Y-Achse anheben, während die Animation zum Anheben des Arms dies berücksichtigen und beim Springen mit dem Modell ansteigen muss, sonst bleibt der Arm von selbst an Ort und Stelle.

Wir werden die Verbindung von Knoten für diesen Fall beschreiben – Der Körper enthält die Hand. Bei der Ausarbeitung des Algorithmus wird der Knochengraph ausgelesen, alle Animationen werden mit den richtigen Verbindungen berücksichtigt. Im Speicher des Modells wird das Diagramm getrennt von allen Animationen gespeichert, nur um die Konnektivität der Knochen des Modells widerzuspiegeln.

Interpolation auf der CPU

Im letzten Artikel habe ich das Prinzip des Renderns von Skelettanimationen beschrieben – „Transformationsmatrizen werden bei jedem Rendering-Frame von der CPU an den Shader übertragen.“

Jeder Rendering-Frame wird auf der CPU verarbeitet; für jeden Mesh-Bone erhält die Engine die endgültige Transformationsmatrix mithilfe von Positionsinterpolation, Drehung und Zoom. Während der Interpolation der endgültigen Knochenmatrix wird für alle aktiven Knotenanimationen ein Durchlauf durch den Knotenbaum durchgeführt, die endgültige Matrix wird mit den übergeordneten Matrix multipliziert und dann zum Rendern an den Vertex-Shader gesendet.

Vektoren werden zur Positionsinterpolation und Vergrößerung verwendet; Quaternionen werden zur Rotation verwendet, weil Sie sind im Gegensatz zu Euler-Winkeln sehr einfach zu interpolieren (SLERP) und auch sehr einfach als Transformationsmatrix darzustellen.

So vereinfachen Sie die Implementierung

Um das Debuggen des Vertex-Shaders zu vereinfachen, habe ich mithilfe des Makros FSGLOGLNEWAGERENDERER_CPU_BASED_VERTEX_MODS_ENABLED eine Simulation des Vertex-Shaders auf der CPU hinzugefügt. Der Grafikkartenhersteller NVIDIA verfügt über ein Dienstprogramm zum Debuggen von Shader-Code, Nsight. Vielleicht kann es auch die Entwicklung komplexer Vertex-/Pixel-Shader-Algorithmen vereinfachen, aber ich konnte seine Funktionalität nie ausreichend auf der CPU testen.

Im nächsten Artikel möchte ich das Mischen mehrerer Animationen beschreiben und die verbleibenden Lücken schließen.

Quellen

https://www.youtube.com/watch?v= f3Cr8Yx3GGA

Unterstützung für JavaScript-Skripte in C++ hinzugefügt

In diesem Beitrag beschreibe ich eine Möglichkeit, mithilfe der Tiny-JS-Bibliothek Unterstützung für JavaScript-Skripte zu einer C++-Anwendung hinzuzufügen.

Tiny-JS ist eine Bibliothek zum Einbetten in C++, die die Ausführung von JavaScript-Code ermöglicht und Bindungen unterstützt (die Möglichkeit, C++-Code aus Skripten aufzurufen)

Zuerst wollte ich die beliebten Bibliotheken ChaiScript, Duktape oder Connect Lua verwenden, aber aufgrund von Abhängigkeiten und möglichen Schwierigkeiten bei der Portabilität auf verschiedene Plattformen entschied ich mich, eine einfache, minimale, aber leistungsstarke MIT Tiny-Bibliothek zu finden; JS erfüllt diese Kriterien. Der einzige Nachteil dieser Bibliothek ist die fehlende Unterstützung/Entwicklung durch den Autor, ihr Code ist jedoch recht einfach, sodass Sie bei Bedarf die Unterstützung übernehmen können.

Laden Sie Tiny-JS aus dem Repository herunter:
https://github.com/gfwilliams/tiny-js

Fügen Sie als Nächstes Tiny-JS-Header zum Code hinzu, der für die Skripte verantwortlich ist:

#include "tiny-js/TinyJS.h"
#include "tiny-js/TinyJS_Functions.h"

Fügen Sie TinyJS-CPP-Dateien zur Build-Phase hinzu, dann können Sie mit dem Schreiben von Lade- und Ausführungsskripten beginnen.

Ein Beispiel für die Verwendung der Bibliothek ist im Repository verfügbar:
https://github.com/gfwilliams/tiny-js/blob/master/Script.cpp
https://github.com/gfwilliams/tiny-js/blob/wiki/CodeExamples.md

Ein Beispiel für die Implementierung der Handler-Klasse finden Sie im SpaceJaguar-Projekt:
https://gitlab.com/demensdeum/space-jaguar-action-rpg/-/blob/master/project/src/Controllers/SpaceJaguarScriptController/SpaceJaguarScriptController.h
https://gitlab.com/demensdeum/space-jaguar-action-rpg/-/blob/master/project/src/Controllers/SpaceJaguarScriptController/SpaceJaguarScriptController.cpp

Beispiel eines der Anwendung hinzugefügten Spielskripts:
https://gitlab.com/demensdeum/space-jaguar-action-rpg/-/blob/master/project/resources/com.demensdeum.spacejaguaractionrpg.scripts.sceneController.js

Quellen

https://github.com/gfwilliams/tiny-js
https://github.com/dbohdan/embedded-scripting-languages
https://github.com/AlexKotik/embeddable-scripting-languages

Erstellen einer C++-SDL-Anwendung für iOS unter Linux

In diesem Beitrag beschreibe ich das Verfahren zum Erstellen einer C++-SDL-Anwendung für iOS unter Linux, zum Signieren eines IPA-Archivs ohne kostenpflichtiges Apple Developer-Abonnement und zum Installieren auf einem sauberen Gerät (iPad) mit macOS ohne Jailbreak.< /p>

Zuerst installieren wir die Build-Toolchain für Linux:
https://github.com/tpoechtrager/cctools-port

Die Toolchain muss aus dem Repository heruntergeladen werden. Befolgen Sie dann die Anweisungen auf der Godot Engine-Website, um die Installation abzuschließen:
https://docs.godotengine.org/ru/latest/development/compiling/cross-compiling_for_ios_on_linux.html

Im Moment müssen Sie Xcode dmg herunterladen und das SDK von dort kopieren, um den cctools-Port zu erstellen. Dieser Schritt ist unter macOS einfacher durchzuführen; kopieren Sie einfach die erforderlichen SDK-Dateien aus dem installierten Xcode. Nach erfolgreicher Assemblierung enthält das Terminal den Pfad zur Cross-Compiler-Toolchain.

Als nächstes können Sie mit der Erstellung der SDL-Anwendung für iOS beginnen. Öffnen wir cmake und fügen die notwendigen Änderungen hinzu, um den C++-Code zu erstellen:

SET(CMAKE_SYSTEM_NAME Darwin)
SET(CMAKE_C_COMPILER arm-apple-darwin11-clang)
SET(CMAKE_CXX_COMPILER arm-apple-darwin11-clang++)
SET(CMAKE_LINKER arm-apple-darwin11-ld)

Jetzt können Sie mit cmake und make kompilieren, aber vergessen Sie nicht, $PATH zur Cross-Compiler-Toolchain hinzuzufügen:


PATH=$PATH:~/Sources/cctools-port/usage_examples/ios_toolchain/target/bin

Für eine korrekte Verknüpfung mit Frameworks und SDL schreiben wir diese in cmake, Abhängigkeiten des Spiels Space Jaguar zum Beispiel:


target_link_libraries(
${FSEGT_PROJECT_NAME}
${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libclang_rt.ios.a
${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2.a
${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2_mixer.a
${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2_image.a
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreServices.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/ImageIO.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/Metal.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/AVFoundation.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/GameController.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreMotion.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreGraphics.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/AudioToolbox.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreAudio.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/QuartzCore.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/OpenGLES.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/UIKit.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/Foundation.framework"
)

In meinem Fall werden die Bibliotheken SDL, SDL_Image und SDL_mixer vorab in Xcode unter macOS für die statische Verknüpfung kompiliert; Von Xcode kopierte Frameworks. Außerdem wurde die Bibliothek libclang_rt.ios.a hinzugefügt, die iOS-spezifische Laufzeitaufrufe enthält, beispielsweise isOSVersionAtLeast. Für die Arbeit mit OpenGL ES ist ein Makro enthalten, das ähnlich wie bei Android nicht unterstützte Funktionen in der mobilen Version deaktiviert.

Nachdem Sie alle Build-Probleme gelöst haben, sollten Sie die zusammengestellte Binärdatei für arm erhalten. Betrachten wir als Nächstes die Ausführung der zusammengestellten Binärdatei auf einem Gerät ohne Jailbreak.

Installieren Sie unter macOS Xcode, registrieren Sie sich im Apple-Portal, ohne für das Entwicklerprogramm zu bezahlen. Fügen Sie ein Konto in Xcode hinzu -> Einstellungen -> Konten, erstellen Sie eine leere Anwendung und bauen Sie auf einem echten Gerät auf. Während der Montage wird das Gerät dem kostenlosen Entwicklerkonto hinzugefügt. Nach der Zusammenstellung und dem Start müssen Sie das Archiv erstellen. Wählen Sie dazu „Generisches iOS-Gerät und -Produkt“ aus. Archiv. Sobald das Archiv erstellt ist, extrahieren Sie die Dateien „embedded.mobileprovision“ und „PkgInfo“ daraus. Suchen Sie im Build-Protokoll auf dem Gerät die Codesign-Zeile mit dem richtigen Signaturschlüssel und den Pfad zur Berechtigungsdatei mit der Erweiterung app.xcent und kopieren Sie sie.

Kopieren Sie den .app-Ordner aus dem Archiv, ersetzen Sie die Binärdatei im Archiv durch eine, die von einem Cross-Compiler unter Linux kompiliert wurde (z. B. SpaceJaguar.app/SpaceJaguar), fügen Sie dann die erforderlichen Ressourcen zur .app hinzu und überprüfen Sie die Integrität der Dateien „PkgInfo“ und „embedded.mobileprovision“ in der .app aus dem Archiv, ggf. erneut kopieren. Wir signieren die .app erneut mit dem Codesign-Befehl – Codesign erfordert einen Eingabeschlüssel für sign, den Pfad zur Berechtigungsdatei (kann mit der Erweiterung .plist umbenannt werden)

Erstellen Sie nach dem erneuten Signieren einen Payload-Ordner, verschieben Sie den Ordner mit der Erweiterung .app dorthin, erstellen Sie ein Zip-Archiv mit Payload im Stammverzeichnis und benennen Sie das Archiv mit der Erweiterung .ipa um. Öffnen Sie anschließend in Xcode die Geräteliste und ziehen Sie das neue ipa per Drag & Drop in die Anwendungsliste des Geräts. Die Installation über Apple Configurator 2 funktioniert bei dieser Methode nicht. Wenn die Neusignierung korrekt durchgeführt wurde, wird die Anwendung mit der neuen Binärdatei auf einem iOS-Gerät (z. B. iPad) mit einem 7-Tage-Zertifikat installiert, das reicht für den Testzeitraum.

Quellen

https://github.com/tpoechtrager/cctools-port
https://docs.godotengine.org/ru/latest/development/compiling/cross-compiling_for_ios_on_linux.html
https://jonnyzzz.com/blog/2018/06/13/link-error-3/
https://stackoverflow.com/questions/6896029/re-sign-ipa-iphone
https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html

Spielevision Nr. 4

Die vierte Ausgabe einer sehr inkonsistenten Kolumne über Games Vision-Spiele.

World Of Horror (plattformübergreifend, panstasz) – schurkenhaftes Early-Access-Spiel im Stil äh was? Text-Horror-Adventure mit RPG-Elementen? Grafisch erinnert es an Spiele der 80er Jahre. Sie können zwischen einer 1-Bit- oder 2-Bit-Palette mit Variationen wählen.

Die Steuerung scheint zunächst seltsam, aber mit der Zeit gewöhnt man sich daran, denn deshalb ist es ein Roguelike – in jeder Hinsicht zu überraschen und originell zu sein. Erstaunliche Chiptune-Musik, japanische Ästhetik der späten 80er, von der Arbeit von Junji Ito inspirierte Visuals, seltsame Geschichten im Stil von Lovecraft, nahezu endloser Wiederspielwert.
Was brauchen Sie noch?
Bewertung: 9/10

Eternal Castle [REMASTERED] (PC, Daniele Vicinanzo, Giulio Perrone, Leonard Menchiari) – ein modernes Spiel im Stil von Another World, Flashback. Die Palette wurde speziell auf CGA-Farben reduziert. Die Beschreibung dieses Spiels muss mit der Legende seiner Entstehung beginnen: Etwa 1987 sah eines der Kinder der Eternal Castle-Entwickler das Spiel und erinnerte sich für den Rest seines Lebens daran. Infolgedessen wurde das Spiel nie veröffentlicht Der Quellcode wurde 2019 gefunden und wiederhergestellt, wodurch eine verbesserte Version veröffentlicht wurde. Die Beschreibung auf Steam enthält jedoch Hinweise darauf, dass es sich um ein Remaster eines Bestsellers aus dem Jahr 1987 handelt, in diesem Jahr jedoch kein Spiel mit diesem Namen veröffentlicht wurde. Was wahr ist und was nicht, liegt bei Ihnen.

Die Grafik und das Gameplay sind eindeutig auf diejenigen zugeschnitten, die sich gerne an die guten alten Zeiten erinnern; sehr oft gibt es Momente, in denen es scheint, als sei das Spiel eingefroren, aber in Wirklichkeit muss man nur die Bewegungs- oder Aktionstasten drücken, um zu sehen, was passiert auf dem Bildschirm. Dieses Gimmick erzeugt ein Gefühl der Unbeholfenheit und des Kontrollverlusts, das in älteren Spielen oft verwendet wurde, in modernen Spielen jedoch völlig aufgegeben wurde.
Bewertung: 8/10

Tod & Steuern (PC, Placeholder Gameworks) – Haben Sie jemals davon geträumt, gleichzeitig als Richter und Henker zu arbeiten? Magst du lange schwarze Roben, Metallzöpfe und das Knacken deiner Knöchel? Dann ist dies das perfekte Spiel für Sie, denn „Die einzigen zwei Dinge, die Sie nicht vermeiden können, sind Tod und Steuern.“

Dies ist ein einzigartiger Simulator des Todesengels. Sie müssen entscheiden, wer lebt und wer stirbt. Zusätzlich zum Töten oder Geben von Leben können Sie den Newsfeed auf Ihrem Smartphone lesen und sehen, wie sich Ihre Wahl auf die Welt der Erde auswirkt. Sie müssen auch mit Ihrem direkten Vorgesetzten namens Faith (Schicksal) kommunizieren, Gegenstände und alle möglichen Utensilien für den Tisch kaufen, zum Beispiel habe ich mir einen brutalen Kaktus gekauft. Vergessen Sie nicht, mit dem Spiegel zu sprechen, es ist sehr aufregend. Unter den Minuspunkten ist die allgemeine Intimität des Geschehens zu erwähnen; nach ein paar Tagen im Spiel wird das Spiel etwas eintönig.
Bewertung: 8/10

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/

Slowride-Benchmark von Blockgeräten

Slowride – Dienstprogramm zur Überprüfung der Lesegeschwindigkeit von Blockgeräten für POSIX-kompatible Betriebssysteme mit Root-Zugriff auf /dev/sd*. Sie können die Leseleistung von Blockgeräten mithilfe eines Zeitschwellenwerts testen, um die Leseleistung zu diagnostizieren.
Befehl zum Lesen von 100-MB-Blöcken auf dem gesamten Gerät und zur Ausgabe von Blöcken über dem 2-Sekunden-Schwellenwert:

sudo ./slowride /dev/sda 100 2000

Quellcode

https://gitlab.com/demensdeum/slowride

Space Jaguar 3D-Action-Rollenspiel

Ich habe schon lange keine neuen Projekte mehr angekündigt) Das nächste Projekt, an dem ich zu arbeiten beginne – 3D-Action-Rollenspiel namens Space Jaguar. Eine Geschichte in einer Science-Fiction-Umgebung über einen harten Kerl namens Jag und sein schwieriges Abenteuer auf der Suche nach seinem vermissten Vater. Es wird 3D-Grafiken auf der Flame Steel Engine (oder möglicherweise einer anderen beliebten Engine) geben, die Entwicklungen aus früheren Projekten (Death Mask, Cube Art Project) verwenden, eine Comedy-Handlung mit vielen Referenzen, Arcade-Kämpfe und Bosse. Ich bin noch nicht bereit, über das Erscheinungsdatum der Vollversion zu sprechen; ich habe vor, das Spiel in Teilen zu veröffentlichen.

Projekt-Repository:
https://gitlab.com/demensdeum/space-jaguar-action-rpg

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

Würfelkunstprojekt

Cube Art Project – kubischer 3D-Editor.
Sie haben die großartige Möglichkeit, sich auf der Bühne zu bewegen, Würfel mit den Tasten WSAD + E zu erstellen und zu löschen und das Mausrad zu drehen, um die Farbe des Würfels zu ändern. Derzeit werden nur 16 Farben unterstützt, für die Zukunft sind jedoch viele Verbesserungen geplant.

Webversion
https://demensdeum.com/games/CubeArtProjectWEB/

Windows
https://demensdeum.com/games/CubeArtProjectReleases/CubeArtProjectWin32.zip

macOS
https://demensdeum.com/games/CubeArtProjectReleases/CubeArtProjectMacOS.zip

Linux (x86-64)
https://demensdeum.com/games/CubeArtProjectReleases/CubeArtProjectLinux86_64.zip

Android
(Konzept, erfordert USB-Maus)
https://demensdeum.com/games/CubeArtProjectReleases/CubeArtProject.apk

Quellcode
https://gitlab.com/demensdeum/cube-art-project-bootstrap
https://gitlab.com/demensdeum/cube-art-project-server

Technologien: SDL, Emscripten, MinGW, Glew, GLM, Cpp-JSON

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

LazyFoo Productions-Website

Vielleicht ist es erwähnenswert, die Website zu erwähnen, auf der meine Projekte und Neuentwicklungen fast immer beginnen. Dies ist die Website von LazyFoo Productions, auf der Sie Antworten auf ziemlich schwierige Themen finden: Beispiele für die Verwendung komplexer APIs und lernen, wie man scheinbar Inkompatibles kombiniert Systeme (Android/C++) mit einer detaillierten Erläuterung der Funktionsprinzipien und funktionierenden Codebeispielen.

https://lazyfoo.net/

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