Warum TROCKEN wichtig ist

Es gibt viele Artikel zum Thema DRY, ich empfehle die Lektüre der Originalquelle „The Pragmatist Programmer“ von Andy Hunt und Dave Thomas. Allerdings sehe ich immer noch, wie viele Entwickler Fragen zu diesem Prinzip in der Softwareentwicklung haben.

Das DRY-Prinzip besagt, dass wir uns nicht wiederholen sollten. Dies gilt sowohl für den Code als auch für die Prozesse, die wir als Programmierer ausführen. Beispielcode, der gegen DRY verstößt:

class Client {
    public let name: String
    private var messages: [String] = []
    
    init(name: String) {
        self.name = name
    }
    
    func receive(_ message: String) {
        messages.append(message)
    }
}

class ClientController {
    func greet(client: Client?) {
        guard let client else {
            debugPrint("No client!")
            return
        }
        client.receive("Hello \(client.name)!")
    }

    func goodbye(client: Client?) {
        guard let client else {
            debugPrint("No client!!")
            return
        }
        client.receive("Bye \(client.name)!")
    }
}

Wie Sie in den Methoden „greet“ und „goodbye“ sehen können, wird eine optionale Instanz der Client-Klasse übergeben, die dann auf Null überprüft werden muss, und dann können Sie mit der Arbeit beginnen. Um der DRY-Methode zu entsprechen, müssen Sie die doppelte Nullprüfung für die Klasseninstanz entfernen. Dies kann auf viele Arten implementiert werden; eine Möglichkeit besteht darin, die Instanz an den Klassenkonstruktor zu übergeben, wonach keine Überprüfungen erforderlich sind.

Wir halten DRY ein, indem wir die ClientController-Spezialisierung auf einer einzelnen Client-Instanz verwenden:

class Client {
    public let name: String
    private var messages: [String] = []
    
    init(name: String) {
        self.name = name
    }
    
    func receive(_ message: String) {
        messages.append(message)
    }
}

class ClientController {
    private let client: Client

    init(client: Client) {
        self.client = client
    }

    func greet() {
        client.receive("Hello \(client.name)!")
    }

    func goodbye() {
        client.receive("Bye \(client.name)!")
    }
}

DRY betrifft auch Prozesse, die während der Softwareentwicklung auftreten. Stellen wir uns eine Situation vor, in der ein Entwicklungsteam selbst eine Version auf den Markt hochladen muss und sie dadurch von der Softwareentwicklung ablenkt. Dies ist ebenfalls ein Verstoß gegen DRY. Diese Situation wird durch die Anbindung einer CI/CD-Pipeline gelöst, in der das Release automatisch veröffentlicht wird, sofern bestimmte Bedingungen von den Entwicklern erfüllt werden.

Im Allgemeinen geht es bei DRY um das Fehlen von Wiederholungen sowohl in Prozessen als auch im Code. Dies ist auch aufgrund des menschlichen Faktors wichtig: Code, der weniger sich wiederholenden, verrauschten Code enthält, lässt sich leichter auf Fehler überprüfen; Automatisierte Prozesse machen es für Menschen unmöglich, bei der Ausführung Fehler zu machen, da kein Mensch beteiligt ist.

Steve Jobs hatte ein Sprichwort: „Eine Codezeile, die Sie nie schreiben mussten, ist eine Codezeile, die Sie nie debuggen mussten.“

Quellen

https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/
https://youtu.be/-msIEOGvTYM

Ich helfe Ihnen bei der Entwicklung von iOS für Swift oder Objective-C

Ich freue mich, Ihnen mitteilen zu können, dass ich meine Dienste jetzt als iOS-Entwickler auf Fiverr anbiete. Wenn Sie Hilfe bei der Entwicklung hochwertiger iOS-Apps oder der Verbesserung bestehender Projekte benötigen, schauen Sie sich mein Profil an:
https://www.fiverr.com/s/Q7x4kb6

Ich würde mich freuen, die Möglichkeit zu haben, an Ihrem Projekt mitzuarbeiten.
E-Mail: demensdeum@gmail.com
Telegramm: https://t.me/demensdeum

Dynamische Verknüpfung von Qt-Anwendungen unter macOS

Heute habe ich eine Version von RaidenVideoRipper für Apple-Geräte mit macOS und M1/M2/M3/M4-Prozessoren (Apple Silicon) veröffentlicht. RaidenVideoRipper ist eine schnelle Videobearbeitungsanwendung, mit der Sie einen Teil einer Videodatei in eine neue Datei schneiden können. Sie können auch GIFs erstellen und die Audiospur als MP3 exportieren.

Als nächstes werde ich kurz beschreiben, welche Befehle ich verwendet habe, um dies zu erreichen. Die Theorie, was hier passiert, die Dokumentation der Versorgungsunternehmen, kann unter den folgenden Links nachgelesen werden:
https://www.unix.com/man-page/osx/1/otool/
https://www.unix.com/man-page/osx/1/install_name_tool/
https://llvm.org/docs/CommandGuide/llvm-nm.html
https://linux.die.net/man/1/file
https://www.unix.com/man-page/osx/8/SPCTL/
https://linux.die.net/man/1/chmod
https://linux.die.net/man/1/ls
https://man7.org/linux/man-pages/man7/xattr.7.html
https://doc.qt.io/qt-6/macos-deployment.html

Installieren Sie zunächst Qt auf Ihrem macOS und installieren Sie auch die Umgebung für Qt Desktop Development. Anschließend stellen Sie Ihr Projekt beispielsweise in Qt Creator zusammen. Anschließend beschreibe ich, was erforderlich ist, um sicherzustellen, dass Abhängigkeiten mit externen dynamischen Bibliotheken bei der Verteilung der Anwendung an Endbenutzer korrekt verarbeitet werden.

Erstellen Sie ein Frameworks-Verzeichnis im Ordner YOUR_APP.app/Contents Ihrer Anwendung und platzieren Sie darin externe Abhängigkeiten. So sehen beispielsweise die Frameworks für die RaidenVideoRipper-Anwendung aus:

Frameworks
├── DullahanFFmpeg.framework
│   ├── dullahan_ffmpeg.a
│   ├── libavcodec.60.dylib
│   ├── libavdevice.60.dylib
│   ├── libavfilter.9.dylib
│   ├── libavformat.60.dylib
│   ├── libavutil.58.dylib
│   ├── libpostproc.57.dylib
│   ├── libswresample.4.dylib
│   └── libswscale.7.dylib
├── QtCore.framework
│   ├── Headers -> Versions/Current/Headers
│   ├── QtCore -> Versions/Current/QtCore
│   ├── Resources -> Versions/Current/Resources
│   └── Versions
├── QtGui.framework
│   ├── Headers -> Versions/Current/Headers
│   ├── QtGui -> Versions/Current/QtGui
│   ├── Resources -> Versions/Current/Resources
│   └── Versions
├── QtMultimedia.framework
│   ├── Headers -> Versions/Current/Headers
│   ├── QtMultimedia -> Versions/Current/QtMultimedia
│   ├── Resources -> Versions/Current/Resources
│   └── Versions
├── QtMultimediaWidgets.framework
│   ├── Headers -> Versions/Current/Headers
│   ├── QtMultimediaWidgets -> Versions/Current/QtMultimediaWidgets
│   ├── Resources -> Versions/Current/Resources
│   └── Versions
├── QtNetwork.framework
│   ├── Headers -> Versions/Current/Headers
│   ├── QtNetwork -> Versions/Current/QtNetwork
│   ├── Resources -> Versions/Current/Resources
│   └── Versions
└── QtWidgets.framework
    ├── Headers -> Versions/Current/Headers
    ├── QtWidgets -> Versions/Current/QtWidgets
    ├── Resources -> Versions/Current/Resources
    └── Versions

Der Einfachheit halber habe ich nur die zweite Verschachtelungsebene gedruckt.

Als nächstes drucken wir die aktuellen dynamischen Abhängigkeiten Ihrer Anwendung aus:

otool -L RaidenVideoRipper 

Ausgabe für die RaidenVideoRipper-Binärdatei, die sich in RaidenVideoRipper.app/Contents/MacOS befindet:

RaidenVideoRipper:
	@rpath/DullahanFFmpeg.framework/dullahan_ffmpeg.a (compatibility version 0.0.0, current version 0.0.0)
	@rpath/QtMultimediaWidgets.framework/Versions/A/QtMultimediaWidgets (compatibility version 6.0.0, current version 6.8.1)
	@rpath/QtWidgets.framework/Versions/A/QtWidgets (compatibility version 6.0.0, current version 6.8.1)
	@rpath/QtMultimedia.framework/Versions/A/QtMultimedia (compatibility version 6.0.0, current version 6.8.1)
	@rpath/QtGui.framework/Versions/A/QtGui (compatibility version 6.0.0, current version 6.8.1)
	/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 2575.20.19)
	/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/Metal.framework/Versions/A/Metal (compatibility version 1.0.0, current version 367.4.0)
	@rpath/QtNetwork.framework/Versions/A/QtNetwork (compatibility version 6.0.0, current version 6.8.1)
	@rpath/QtCore.framework/Versions/A/QtCore (compatibility version 6.0.0, current version 6.8.1)
	/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit (compatibility version 1.0.0, current version 275.0.0)
	/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/UniformTypeIdentifiers.framework/Versions/A/UniformTypeIdentifiers (compatibility version 1.0.0, current version 709.0.0)
	/System/Library/Frameworks/AGL.framework/Versions/A/AGL (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
	/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1800.101.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1351.0.0)

Wie im RaidenVideoRipper in den Qt- und dullahan_ffmpeg-Abhängigkeiten zu sehen ist. Dullahan FFmpeg ist ein Zweig von FFmpeg, der seine Funktionalität in einer dynamischen Bibliothek kapselt und die Möglichkeit bietet, den aktuellen Ausführungsfortschritt und den Abbruch mithilfe von C-Routinen abzurufen.
Ersetzen Sie als Nächstes die Pfade der Anwendung und aller erforderlichen Bibliotheken durch install_name_tool.

Der Befehl hierfür lautet:

install_name_tool -change old_path new_path target

Anwendungsbeispiel:

install_name_tool -change /usr/local/lib/libavfilter.9.dylib @rpath/DullahanFFmpeg.framework/libavfilter.9.dylib dullahan_ffmpeg.a

Nachdem Sie alle Pfade korrekt eingegeben haben, sollte die Anwendung korrekt starten. Überprüfen Sie, ob alle Pfade zu Bibliotheken relativ sind, übertragen Sie die Binärdatei und öffnen Sie sie erneut.
Wenn Sie einen Fehler sehen, überprüfen Sie die Pfade über otool und ändern Sie sie erneut über install_name_tool.

Es gibt auch einen Fehler bei der Verwechslung von Abhängigkeiten. Wenn die von Ihnen ersetzte Bibliothek kein Symbol in der Tabelle hat, können Sie das Vorhandensein oder Fehlen eines Symbols wie folgt überprüfen:

nm -gU path

Nach der Ausführung sehen Sie die gesamte Symboltabelle der Bibliothek oder Anwendung.
Es ist auch möglich, dass Sie die Abhängigkeiten der falschen Architektur kopiert haben. Sie können dies mit der Datei überprüfen:

file path

Das Dateidienstprogramm zeigt Ihnen, zu welcher Architektur die Bibliothek oder Anwendung gehört.

Qt benötigt außerdem einen Plugins-Ordner im Contents-Ordner Ihres YOUR_APP.app-Verzeichnisses. Kopieren Sie die Plugins von Qt nach Contents. Überprüfen Sie als Nächstes die Funktionalität der Anwendung. Anschließend können Sie mit der Optimierung des Plugins-Ordners beginnen, Elemente aus diesem Ordner löschen und die Anwendung testen.

macOS-Sicherheit

Nachdem Sie alle Abhängigkeiten kopiert und die Pfade für die dynamische Verknüpfung korrigiert haben, müssen Sie die Anwendung mit der Signatur des Entwicklers signieren und zusätzlich die Anwendungsversion zur Beglaubigung an Apple senden.

Wenn Sie keine 100 US-Dollar für eine Entwicklerlizenz haben oder nichts unterschreiben möchten, schreiben Sie Ihren Benutzern Anweisungen zum Starten der Anwendung.

Diese Anleitung funktioniert auch für RaidenVideoRipper:

  • Gatekeeper deaktivieren: spctl –master-disable
  • Start von beliebigen Quellen in „Datenschutz und Sicherheit“ zulassen: Wechseln von Anwendungen zu „Überall“ zulassen
  • Entfernen Sie die Quarantäne-Flagge nach dem Herunterladen aus einer ZIP- oder DMG-Anwendung: xattr -d com.apple.quarantine app.dmg
  • Überprüfen Sie, ob das Quarantäne-Flag (com.apple.quarantine) fehlt: ls -l@ app.dmg
  • Fügen Sie bei Bedarf eine Bestätigung zum Starten der Anwendung in Datenschutz und Sicherheit hinzu

Ein Fehler mit der Quarantäne-Flagge wird normalerweise dadurch reproduziert, dass auf dem Bildschirm des Benutzers die Fehlermeldung „Anwendung ist beschädigt“ erscheint. In diesem Fall müssen Sie das Quarantäne-Flag aus den Metadaten entfernen.

Link zum Erstellen von RaidenVideoRipper für Apple Silicon:
https://github.com/demensdeum/RaidenVideoRipper/releases/download/1.0.1.0/RaidenVideoRipper-1.0.1.0.dmg

Videostabilisierung mit ffmpeg

Wenn Sie Videos stabilisieren und Kameraverwacklungen entfernen möchten, bietet das Tool „ffmpeg“ eine leistungsstarke Lösung. Dank der integrierten Filter „vidstabdetect“ und „vidstabtransform“ können Sie professionelle Ergebnisse erzielen, ohne komplexe Videoeditoren zu verwenden.

Vorbereitung auf die Arbeit

Bevor Sie beginnen, stellen Sie sicher, dass Ihr „ffmpeg“ die „vidstab“-Bibliothek unterstützt. Unter Linux können Sie dies mit dem Befehl überprüfen:

bash  
ffmpeg -filters | grep vidstab  

Wenn die Bibliothek nicht installiert ist, können Sie sie hinzufügen:

sudo apt install ffmpeg libvidstab-dev  

Installation für macOS über brew:

brew install libvidstab
brew install ffmpeg

Kommen wir nun zum Prozess.

Schritt 1: Bewegungsanalyse

Zuerst müssen Sie die Bewegung des Videos analysieren und eine Datei mit Stabilisierungsparametern erstellen.

ffmpeg -i input.mp4 -vf vidstabdetect=shakiness=10:accuracy=15 transfile=transforms.trf -f null -  

Parameter:

Wackeligkeit: Video-Verwackelungsstufe (Standard 5, kann für komplexere Fälle auf 10 erhöht werden).
Genauigkeit: Analysegenauigkeit (Standard 15).
transfile: Dateiname zum Speichern der Bewegungsparameter.

Schritt 2: Stabilisierung anwenden

Jetzt können Sie die Stabilisierung mithilfe der Transformationsdatei anwenden:

ffmpeg -i input.mp4 -vf vidstabtransform=input=transforms.trf:zoom=5 output.mp4

Parameter:

Eingabe: Zeigt auf die Datei mit Transformationsparametern (im ersten Schritt erstellt).
Zoom: Zoomfaktor zum Entfernen schwarzer Ränder (z. B. 5 – automatischer Zoom, bis Artefakte entfernt sind).

Automatische Code-Analyse mit Bistr

Wenn Sie den Quellcode eines Projekts analysieren müssen, den Prozess aber automatisieren und die lokale Leistung Ihres Computers nutzen möchten, kann das Dienstprogramm Bistr eine großartige Lösung sein. In diesem Artikel werden wir uns ansehen, wie dieses Dienstprogramm bei der Analyse von Code mithilfe des Ollama-Modells für maschinelles Lernen hilft.

Was ist Bistr?

Bistr ist ein Dienstprogramm zur Quellcode-Analyse, mit dem Sie ein lokales LLM-Modell (Large Language Model) wie Ollama zur Code-Analyse und -Verarbeitung integrieren können. Mit Bistr können Sie Dateien in verschiedenen Programmiersprachen wie Python, C, Java, JavaScript, HTML und mehr analysieren.

Bistr nutzt das Modell, um Dateien anhand bestimmter Abfragen zu prüfen, beispielsweise um eine Antwort auf eine Frage zur Funktionalität des Codes oder eines Teils davon zu finden. Dies bietet eine strukturierte Analyse, die bei der Entwicklung, Prüfung und Wartung von Projekten hilft.

Wie funktioniert Bistr?

  • Ladestatus: Wenn Sie eine Analyse starten, prüft das Dienstprogramm, ob der Analysestatus zuvor gespeichert wurde. Dies hilft Ihnen, dort weiterzumachen, wo Sie aufgehört haben, ohne dieselben Dateien erneut analysieren zu müssen.
  • Code-Analyse: Jede Datei wird mithilfe des Ollama-Modells analysiert. Das Dienstprogramm sendet eine Anfrage an das Modell, um einen bestimmten Codeabschnitt zu analysieren. Das Modell gibt als Antwort auf die Abfrage Informationen über die Relevanz des Codes zurück und liefert außerdem eine Texterklärung, warum ein bestimmtes Fragment für die Aufgabe relevant ist.
  • Statusspeicherung: Nachdem jede Datei analysiert wurde, wird der Status aktualisiert, sodass Sie beim nächsten Mal mit den neuesten Informationen fortfahren können.
  • Ergebnisausgabe: Alle Analyseergebnisse können in eine HTML-Datei exportiert werden, die eine Tabelle mit einer Rangfolge der Dateien nach Relevanz enthält, die hilft zu verstehen, welche Teile des Codes für die weitere Analyse am wichtigsten sind.

Installation und Start

Um Bistr verwenden zu können, müssen Sie Ollama installieren und ausführen, eine Plattform, die LLM-Modelle auf Ihrem lokalen Computer bereitstellt. Nachfolgend werden Anweisungen zur Installation von Ollama für macOS, Windows und Linux beschrieben.

Laden Sie die neueste Version von Bistr von Git herunter:
https://github.com/demensdeum/Bistr/

Nach der Installation von Ollama und Bistr können Sie die Codeanalyse ausführen. Dazu müssen Sie den Quellcode vorbereiten und den Pfad zum Verzeichnis angeben, das die zu analysierenden Dateien enthält. Das Dienstprogramm ermöglicht es Ihnen, die Analyse dort fortzusetzen, wo Sie aufgehört haben, und bietet außerdem die Möglichkeit, Ergebnisse im HTML-Format zu exportieren, um die weitere Analyse zu erleichtern.

Beispielbefehl zum Ausführen der Analyse:


python bistr.py /path/to/code --model llama3.1:latest --output-html result.html --research "What is the purpose of this function?"

In diesem Team:

–model gibt das für die Analyse zu verwendende Modell an.
–output-html gibt den Pfad zum Speichern der Analyseergebnisse in einer HTML-Datei an.
–research ermöglicht es Ihnen, eine Frage zu stellen, deren Antwort Sie durch die Analyse des Codes beantworten möchten.

Vorteile der Verwendung von Bistr

  • Lokale Ausführung: Die Analyse erfolgt auf Ihrem Computer, ohne dass eine Verbindung zu Cloud-Diensten erforderlich ist, was den Prozess beschleunigt.
  • Flexibilität: Sie können Code in verschiedenen Programmiersprachen analysieren.
  • Automatisierung: Die gesamte Codeüberprüfung erfolgt automatisiert, was insbesondere bei der Arbeit mit großen Projekten Zeit und Aufwand spart.

Lokale neuronale Netze mit Ollama

Wenn Sie den Wunsch hatten, so etwas wie ChatGPT zu starten, und über einen ziemlich leistungsstarken Computer verfügen, beispielsweise mit einer Nvidia RTX-Grafikkarte, können Sie das Ollama-Projekt ausführen, mit dem Sie eines der vorgefertigten LLM-Modelle verwenden können Ihrem lokalen Computer, absolut kostenlos. Ollama bietet die Möglichkeit, mit LLM-Modellen zu kommunizieren. In der neuesten Version wurde auch die Möglichkeit angekündigt, Bilder zu lesen und die Ausgabedaten im JSON-Format zu formatieren.

Ich habe das Projekt selbst auch auf einem MacBook mit einem Apple M2-Prozessor ausgeführt und weiß, dass die neuesten Grafikkartenmodelle von AMD unterstützt werden.

Um es unter macOS zu installieren, gehen Sie zur Ollama-Website:
https://ollama.com/download/mac

Klicken Sie auf „Für macOS herunterladen“. Sie laden ein Archiv der Form ollama-darwin.zip herunter. Im Archiv befindet sich Ollama.app, das nach „Anwendungen“ kopiert werden muss. Starten Sie anschließend Ollama.app. Der Installationsvorgang wird höchstwahrscheinlich beim ersten Start ausgeführt. Danach haben Sie in der Taskleiste das Ollama-Symbol gesehen, die Taskleiste befindet sich oben rechts neben der Uhr.

Starten Sie anschließend ein normales macOS-Terminal und geben Sie den Befehl ein, um ein beliebiges Ollama-Modell herunterzuladen, zu installieren und auszuführen. Eine Liste der verfügbaren Modelle, Beschreibungen und ihre Eigenschaften finden Sie auf der Ollama-Website:
https://ollama.com/search

Wählen Sie das Modell mit den wenigsten Parametern, wenn es beim Start nicht in Ihre Grafikkarte passt.

Beispielbefehle zum Ausführen des Modells llama3.1:latest:


ollama run llama3.1:latest

Die Installation für Windows und Linux ist im Allgemeinen ähnlich, in einem Fall gibt es ein Ollama-Installationsprogramm und die weitere Arbeit damit über Powershell.
Bei Linux erfolgt die Installation über ein Skript, ich empfehle jedoch die Verwendung der Version Ihres spezifischen Paketmanagers. Unter Linux kann Ollama auch über ein normales Bash-Terminal gestartet werden.

Quellen
https://www.youtube.com/watch?v=Wjrdr0NU4Sk
https://ollama.com

Unreal Engine auf dem MacBook M2

Wenn Sie den Unreal Engine 5 Editor auf einem MacBook mit einem Apple-Prozessor ausführen konnten, ist Ihnen vielleicht aufgefallen, dass dieses Ding ziemlich langsam ist.

Um die Leistung des Editors und der Engine zu steigern, stellen Sie Engine-Skalierbarkeitseinstellungen -> Mittel ein. Danach fängt die Engine an, alles nicht mehr so ​​schön zu zeichnen, aber Sie können normal mit der Engine auf Ihrem MacBook arbeiten.

Korrektur des mobilen Menüs in WordPress


document.addEventListener('DOMContentLoaded', function() {
    new navMenu('primary');
    new navMenu('woo');
});

Wenn Sie in Ihrem WordPress-Blog bei Verwendung des Seedlet-Themes auch das Blog-Menü unter iOS/Android mehrere Jahre lang nicht geöffnet hatten, dann fügen Sie einfach Folgendes hinzu:
Schließen Sie die Datei wp-content/themes/seedlet/assets/js/primary-navigation.js in die Funktion ein und fügen Sie neben dem Standardabonnementfenster EventListener „load“ hinzu.

Radio-Maximum-Electron

Radio Maximum Electron ist eine leistungsstarke und praktische Anwendung, mit der Sie den Stream des Radiosenders Radio Maximum auf Ihrem Computer mit den Betriebssystemen Windows, Linux und macOS hören können. Dieser Player kombiniert Benutzerfreundlichkeit mit hoher Funktionalität und ermöglicht Ihnen den Zugriff auf Live-Streaming mit minimalem Aufwand.

Laden Sie einfach die Anwendung von GitHub herunter:

https://github.com/demensdeum/Radio-Maximum-Electron/releases

Der Autor hat nichts mit Radio Maximum zu tun, er mag dieses Radio einfach wirklich.
Die Hauptfunktionalität wird vom Nativifier-Projekt implementiert

https://github.com/nativefier/nativefier

Lizenz für MIT-Build-Skripte, Runtime hat eine eigene Lizenz!

Unterwasser-Eisbären-Abenteuer

Ein einfaches Spiel mit endlos generierten Labyrinthen mit ThreeJS.

Entstanden im Rahmen eines 3-tägigen Game Jams „Start the Game“ zum Thema „Familienspiel“.

Ein Polarforscherjunges spazierte mit seiner Mutter über das Eis, als plötzlich eine Katastrophe geschah – das Eis brach und er fiel in das eisige Wasser des Ozeans. Mama hatte keine Zeit, ihn zu retten, und der Bär landete in einer mysteriösen Unterwasserhöhle. Zu seiner Überraschung stellte er fest, dass er unter Wasser atmen konnte. Es gibt nur einen Weg, dieser Falle zu entkommen – indem man die Tiefen des Meeres überwindet, Rätsel löst und aggressive Haie bekämpft, die man mit gezielten Apfelwürfen abwehren kann.

Sein Ziel ist es nun, einen Ausweg aus dieser Unterwasserfalle zu finden und zu seiner Mutter zurückzukehren, die Gefahren der Tiefsee zu überwinden und Rätsel zu lösen.

https://demensdeum.com/demos/arctica/

Nixy Player

Nixy Player – Kleine, erweiterbare, plattformübergreifende JavaScript-Laufzeitumgebung.

Plattformübergreifend: Verfügbar auf Windows, macOS und Linux sowie auf jeder anderen Plattform mit Unterstützung für C++ und dynamische Bibliotheken.
Leicht: Minimaler Ressourcenverbrauch bei effizienter Leistung.
Erweiterbar: Entwickelt für eine einfache Erweiterung mit Plugins und zusätzlichen Bibliotheken.

Die neuesten Veröffentlichungen und Updates finden Sie auf der Seite „Veröffentlichungen“:
https://github.com/demensdeum/NixyPlayer/releases/

Raiden Video Ripper

Raiden Video Ripper ist ein Open-Source-Projekt zur Videobearbeitung und Formatkonvertierung. Es basiert auf Qt 6 (Qt Creator) und ermöglicht das Zuschneiden und Konvertieren von Videos in die Formate MP4, GIF und WebM. Sie können auch Audio aus Videos extrahieren und in das MP3-Format konvertieren.
Интерфейс RaidenVideoRipper

Standbild aus COSTA RICA IN 4K 60fps HDR (ULTRA HD)
https://www.youtube.com/watch?v=LXb3EKWsInQ
Die neuesten Veröffentlichungen und Updates finden Sie auf der Seite „Veröffentlichungen“:
https://github.com/demensdeum/RaidenVideoRipper/releases

Donki Hills

Innerhalb eines Monats habe ich einen lustigen Gag gemacht, ein Parodiespiel mit der Unreal Engine 5. Die Entwicklung erfolgte im Twitch-Stream.

Die Geschichte dieses Spiels erzählt von einem gewöhnlichen Russen, James, der auf Tinder ein Mädchen, Maria, gefunden hat, aber aufgrund von Sanktionen und der Einstellung von Tinder in Russland den Kontakt zu ihr verloren hat. Jetzt bleibt ihm nur noch ein Screenshot ihres Fotos, über Google Maps findet er den Ort, an dem das Foto aufgenommen wurde – das Dorf Tikhie Donki in der Nähe von Nowosibirsk. James macht sich auf die Suche nach Maria.

https://demensdeum.itch.io/donki-hills

Roboterverteidiger

Sehr oft stoße ich bei Diskussionen über die korrekte Funktionsweise einer Softwarefunktion auf eine Situation, in der die Funktionalität aus der Sicht des Benutzers seltsam und unlogisch erschien. Die Diskussion mit dem Product Owner sah in etwa so aus:

– Hier liegt eindeutig ein Verhaltensproblem vor
– Nun, wir werden es veröffentlichen und wenn Benutzer anfangen, sich zu beschweren, werden wir es beheben
– ??? Naja, ok…

Es scheint ein funktionierender Plan zu sein, oder? Ein ziemlich optimaler Algorithmus für Teams mit kleinem Budget, engen Fristen, unzureichender Recherche/Fehlen eines UI/UX-Spezialisten. Benutzer werden sich beschweren, wenn etwas passiert, das ist in Ordnung.
Eine Google-Suche zeigt, dass die Quelle dieser Methode aus einem Artikel stammt – „Beschwerdegesteuerte Entwicklung“ von Coding Horror

Einmal habe ich Lebensmittel verkauft, darunter auch Arztwurst für 300 Rubel. Über ein Terminal in einem Supermarkt verließ ich den Laden mit dieser Wurst im vollen Vertrauen, dass sie bezahlt war – Das Terminal bot an, den Scheck nicht auszudrucken, und ich stimmte zu, um für diesen Scheck kein wertvolles Papier zu verschwenden. Während des „Stanzens“ der Ware für jedes Produkt gab das Terminal ein Quietschen von sich, das signalisierte, dass alles korrekt funktioniert hat. Außerdem ertönte ein akustisches Signal, und das Terminal blinkte mit der Hintergrundbeleuchtung des Barcode-Scanners.

Am nächsten Tag ging ich erneut zum Supermarkt, um Lebensmittel einzukaufen, und gab die Lebensmittel über das Terminal ab. Am Ausgang wurde ich von einem Mann mit südländischem Aussehen und dickem Bart empfangen, der mir ein Smartphone hinhielt und sagte: „ „Bist du das vor der Kamera?“, ich schaute auf sein Handy und sah mich in einem Melodic-Death-Metal-T-Shirt von Arch Enemy mit Totenköpfen und all dem, es gab keinen Grund daran zu zweifeln.
„Ja, ich bin es, was ist los?“, sagte der Mann mit zusammengekniffenen Augen, „Gestern hast du die Wurst nicht geschlagen.“… Wow

Nach einer kurzen Untersuchung darüber, wer er war und wie er zu diesen Schlussfolgerungen kam, zeigte er mir ein Video, das an der Decke des Ladens hängt. In dem Video schlage ich auf die Wurst, das Terminal blinkt mit der Hintergrundbeleuchtung des Scanners. Ich habe die Wurst in die Tüte gesteckt.

– Das Video zeigt, wie der Scanner funktioniert
– Hat nichts gebracht, zahlen Sie für die Wurst!

Ein wenig verblüfft über diese Einstellung, bat ich um ein Beschwerdebuch, um zu schreiben, dass das Terminal Softwareverbesserungen benötige, da es alle Anzeichen für einen korrekten Betrieb zeige, in Wirklichkeit aber einfach fehlerhaft sei, ohne dass dies auf dem Bildschirm angezeigt würde in irgendeiner Weise.

Nach 10 Minuten Streit mit ihm und seinem Chef, der sofort zur Verteidigung seines Angestellten und des beschissenen Terminals eilte, beschlossen sie, die Freundin des Administrators anzurufen, damit sie ein Beschwerdebuch mitbringt und das des Arztes schlägt Wurst.

An diesem Tag wurde mir klar, wie schwierig es für Benutzer wirklich ist, sich über Hardware- und Softwareprodukte zu beschweren, und dass höchstwahrscheinlich das Mantra „Die Leute werden sich beschweren“ gilt. „Lasst es uns reparieren“ funktioniert sehr schlecht. Der Hauptgrund sind Menschen, die kaputte Roboter und kaputte Softwarelösungen verteidigen. Der Einfachheit halber schlage ich vor, neue Begriffe einzuführen – Verteidiger kaputter Roboter und Verteidiger kaputter Systeme.

Normale Benutzer können sich nicht über die Fehlfunktion der Terminals beschweren, weil sie durch ZasRoshniks gestört werden, die aus irgendeinem Grund an die Maschinen, mit denen sie arbeiten, hängen und anfangen, sie zu lieben, sie vielleicht für eine Art belebte Wesenheiten halten und dabei vergessen, dass es nichts gibt dort leben.< /p>

Eine ähnliche Situation tritt bei ZaSSoshniki auf. Diese Leute können trotz Beschwerden von Benutzern und anderen Entwicklern Schaum vor dem Mund haben, um einige dumme Mängel in Frameworks, Programmiersprachen oder anderen Softwareprodukten zu verteidigen.
Ein typisches Gespräch mit ZaSSoshnik läuft wie folgt ab:

– Hier funktioniert etwas nicht, laut Dokumentation scheint alles korrekt zu sein
– Oh, Sie haben also nicht das Handbuch von 2005 gelesen, in dem unten in Kleinbuchstaben steht, dass Sie PROGRAM_START:6969
hinzufügen müssen
– ??? äh

Solche Menschen verstehen möglicherweise nicht, wie sie selbst zur Verbreitung von Problemen, Fehlern, Zeit- und Geldverschwendung bei sich selbst und anderen Menschen beitragen. Darunter leiden alle, denn die digitale Transformation ist unmöglich, wenn nicht offensichtliche Dinge und Probleme mit Software- und Hardwarelösungen vertuscht werden.
Mir ist die jüngste Geschichte eines Fehlers in der Horizon-Software der britischen Post bekannt, der Menschen in Schulden trieb, Ehen ruinierte und das Leben anderer Menschen jahrzehntelang ruinierte. All dies geschah aufgrund der Duldung von Leuten, die über Probleme in der Software schwiegen und so „schützten“. ihn.

Freunde, seid keine ZaSRoshniks und ZaSSoshniks, behandelt die Werkzeuge, mit denen ihr arbeitet, mit Vorsicht, sonst droht euch die völlige Versklavung beschissener, kaputter Systeme, wie Geiseln in der neuen digitalen Welt der Zukunft. Für diejenigen, die nicht – Stören Sie zumindest nicht andere Leute, die versuchen, auf nicht funktionierende, störende Software/Hardware aufmerksam zu machen, denn die Entwickler dieser Produkte haben zugestimmt – „Wenn Benutzer anfangen, sich zu beschweren, werden wir das Problem beheben.“

Quellen
https://blog.codinghorror.com/complaint-driven-development/< /a>
https://habr.com/ru/articles/554404/< br />
https://en.wikipedia.org/wiki/British_Post_Office_scandal

Erstellen einer bgfx-Emscripten-Anwendung

In diesem Beitrag beschreibe ich eine Möglichkeit, BGFX-Anwendungen für das Web (WebAssembly) mit Emscripten zu erstellen.

Die Installationsplattform ist Linux x86-64, zum Beispiel Arch Linux.

Lassen Sie uns zunächst Emscripten Version 3.1.51 installieren, sonst werden Sie keinen Erfolg haben, und zwar aufgrund der Änderung des Typs der dynamischen Bibliotheken in der neuesten Version von Emscripten. Mehr können Sie hier lesen:
https://github.com/bkaradzic/bgfx/discussions/3266

Das geht so:


git clone https://github.com/emscripten-core/emsdk.git



cd emsdk



./emsdk install 3.1.51



./emsdk activate 3.1.51



source ./emsdk_env.sh



Lassen Sie uns bgfx für WebAssembly zusammenbauen – Emscripten:


mkdir bgfx-build-test



cd bgfx-build-test



git clone https://github.com/bkaradzic/bx.git



git clone https://github.com/bkaradzic/bimg.git



git clone https://github.com/bkaradzic/bgfx.git



cd bgfx



emmake make wasm-debug



Als Ergebnis finden Sie im .build-Ordner Bitcode-Dateien mit der Erweiterung .bc, die mit Ihrer bgfx-Anwendung verknüpft werden müssen.
Sollte bgfx.bc, bx.bc, bimg.bc sein; Verschiedene Assemblys haben je nach Assemblytyp (Release/Debug) unterschiedliche Namen für diese Dateien.

Fügen Sie einen Link zur Datei CMakeLists.txt mit .bc-Dateien hinzu, zum Beispiel absolute Pfade zu Dateien aus dem bgfx-experiments-Projekt:


target_link_libraries(${PROJECT_NAME} SDL2 GL /home/demensdeum_stream/Sources/bgfx-build/bgfx/.build/wasm/bin/bgfxDebug.bc /home/demensdeum_stream/Sources/bgfx-build/bgfx/.build/wasm/bin/bxDebug.bc /home/demensdeum_stream/Sources/bgfx-build/bgfx/.build/wasm/bin/bimgDebug.bc)



Ändern Sie nun das native Fensterhandle in den Plattformdaten in „BGFX-Initialisierung“:


bgfx::PlatformData platformData{};



platformData.context = NULL;



platformData.backBuffer = NULL;



platformData.backBufferDS = NULL;



platformData.nwh = (void*)"#canvas";



Sie müssen außerdem den Rendertyp in OpenGL ändern:


bgfx::Init init;



init.type = bgfx::RendererType::OpenGL;







init.resolution.width = screenWidth;



init.resolution.height = screenHeight;



init.resolution.reset = BGFX_RESET_VSYNC;



init.platformData = platformData;







if (!bgfx::init(init))



{



    throw std::runtime_error("Failed to initialize bgfx");



}



GLSL-Shader auf 120 neu kompilieren:


shaderc -f "VertexShader.vs" -o "VertexShader.glsl" --type "v" -p "120"



shaderc -f "FragmentShader.fs" -o "FragmentShader.glsl" --type "f" -p "120"



Ja, aber .glsl-Dateien müssen zu CMakeLists.txt als –preload-file:

hinzugefügt werden


set(CMAKE_CXX_FLAGS ... <Остальная часть>



--preload-file VertexShader.glsl \



--preload-file FragmentShader.glsl \



Es bleibt noch, die Hauptrenderschleife in Ihrer Anwendung durch einen While-Funktionsaufruf über emscripten_set_main_loop zu ersetzen.

Sie können hier darüber lesen:
https ://demensdeum.com/blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/

Als nächstes stellen Sie Ihr Emscripten-Projekt wie gewohnt zusammen, alles sollte funktionieren.
Interessant – Im Emscripten 3.1.51-Build scheint OpenAL zu fehlen (oder nur ich).

Quellcode des Projekts, der mit bgfx und Emscripten korrekt kompiliert wird:
https://github.com/demensdeum/ bgfx-experiments/tree/main/2-emscripten-build

Quellen

https://github.com/bkaradzic/bgfx/discussions/3266
https://bkaradzic.github.io/bgfx/build.html
https://emscripten.org/docs/getting_started/downloads.html
https ://demensdeum.com/blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/
https://llvm.org/docs/BitCodeFormat.html

Portierung der Surreal Engine C++ auf WebAssembly

In diesem Beitrag beschreibe ich, wie ich die Surreal Engine-Spiel-Engine auf WebAssembly portiert habe.

Surreal Engine – eine Spiel-Engine, die die meisten Funktionen der Unreal Engine 1 implementiert, berühmte Spiele auf dieser Engine – Unreal Tournament 99, Unreal, Deus Ex, Undying. Es bezieht sich auf klassische Engines, die hauptsächlich in einer Single-Threaded-Ausführungsumgebung arbeiteten.

Anfangs hatte ich die Idee, ein Projekt zu übernehmen, das ich nicht in angemessener Zeit abschließen konnte, und so meinen Twitch-Followern zu zeigen, dass es Projekte gibt, die selbst ich nicht schaffen kann. Während meines ersten Streams wurde mir plötzlich klar, dass die Aufgabe, Surreal Engine C++ mit Emscripten auf WebAssembly zu portieren, machbar ist.

Surreal Engine Emscripten Demo

Nach einem Monat kann ich meine Gabel- und Motorbaugruppe auf WebAssembly demonstrieren:
https://demensdeum.com/demos/SurrealEngine/

Die Steuerung erfolgt wie im Original über die Tastaturpfeile. Als nächstes plane ich, es für die mobile Steuerung (Tachi) anzupassen und dabei die richtige Beleuchtung und andere grafische Funktionen des Unreal Tournament 99-Renderings hinzuzufügen.

Wo soll ich anfangen?

Das erste, was ich sagen möchte, ist, dass jedes Projekt mit Emscripten von C++ nach WebAssembly portiert werden kann. Die Frage ist nur, wie vollständig die Funktionalität sein wird. Wählen Sie ein Projekt, dessen Bibliotheksports bereits für Emscripten verfügbar sind; im Fall von Surreal Engine haben Sie großes Glück, denn Die Engine verwendet die Bibliotheken SDL 2, OpenAL – Sie sind beide auf Emscripten portiert. Allerdings kommt als Grafik-API Vulkan zum Einsatz, was aktuell nicht für HTML5 verfügbar ist, an der Implementierung von WebGPU wird gearbeitet, befindet sich aber ebenfalls im Entwurfsstadium und es ist auch unbekannt, wie einfach die weitere Portierung von Vulkan auf WebGPU sein wird , nachdem es vollständig standardisiert ist. Daher musste ich mein eigenes grundlegendes OpenGL-ES/WebGL-Rendering für die Surreal Engine schreiben.

Erstellung des Projekts

System in Surreal Engine erstellen – CMake, was auch die Portierung vereinfacht, weil Emscripten stellt seinen nativen Buildern – emcmake, emmake.
Die Surreal Engine-Portierung basierte auf dem Code meines neuesten Spiels in WebGL/OpenGL ES und C++ namens Death-Mask. Dadurch war die Entwicklung viel einfacher, ich hatte alle notwendigen Build-Flags und Codebeispiele dabei.

Einer der wichtigsten Punkte in CMakeLists.txt sind die Build-Flags für Emscripten, unten ist ein Beispiel aus der Projektdatei:


-s MAX_WEBGL_VERSION=2 \

-s EXCEPTION_DEBUG \

-fexceptions \

--preload-file UnrealTournament/ \

--preload-file SurrealEngine.pk3 \

--bind \

--use-preload-plugins \

-Wall \

-Wextra \

-Werror=return-type \

-s USE_SDL=2 \

-s ASSERTIONS=1 \

-w \

-g4 \

-s DISABLE_EXCEPTION_CATCHING=0 \

-O3 \

--no-heap-copy \

-s ALLOW_MEMORY_GROWTH=1 \

-s EXIT_RUNTIME=1")

Das Build-Skript selbst:


emmake make -j 16

cp SurrealEngine.data /srv/http/SurrealEngine/SurrealEngine.data

cp SurrealEngine.js /srv/http/SurrealEngine/SurrealEngine.js

cp SurrealEngine.wasm /srv/http/SurrealEngine/SurrealEngine.wasm

cp ../buildScripts/Emscripten/index.html /srv/http/SurrealEngine/index.html

cp ../buildScripts/Emscripten/background.png /srv/http/SurrealEngine/background.png

Als nächstes bereiten wir den Index vor .html , das den Projektdateisystem-Preloader enthält. Zum Hochladen ins Web habe ich die Unreal Tournament Demo-Version 338 verwendet. Wie Sie der CMake-Datei entnehmen können, wurde der entpackte Spielordner zum Build-Verzeichnis hinzugefügt und als Preload-Datei für Emscripten verlinkt.

Hauptcodeänderungen

Dann war es notwendig, die Spielschleife des Spiels zu ändern, man kann keine Endlosschleife ausführen, das führt zum Einfrieren des Browsers, stattdessen muss man emscripten_set_main_loop verwenden, über diese Funktion habe ich in meiner Notiz von 2017 geschrieben „< a href="https://demensdeum.com /blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/" rel="noopener" target="_blank">SDL C++-Spiel auf HTML5 (Emscripten) portieren“
Wir ändern den Code zum Beenden der while-Schleife in if, zeigen dann die Hauptklasse der Spiel-Engine, die die Spielschleife enthält, im globalen Bereich an und schreiben eine globale Funktion, die den Spielschleifenschritt vom globalen Objekt aus aufruft :


#include <emscripten.h>

Engine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;

void emscripten_game_loop_step() {

	EMSCRIPTEN_GLOBAL_GAME_ENGINE->Run();

}

#endif

Danach müssen Sie sicherstellen, dass in der Anwendung keine Hintergrundthreads vorhanden sind. Wenn ja, dann bereiten Sie sich darauf vor, diese für die Single-Thread-Ausführung neu zu schreiben, oder verwenden Sie die phtread-Bibliothek in Emscripten.
Der Hintergrund-Thread in Surreal Engine wird zum Abspielen von Musik verwendet. Vom Haupt-Engine-Thread kommen Daten über den aktuellen Titel, die Notwendigkeit, Musik abzuspielen, oder dessen Fehlen. Anschließend erhält der Hintergrund-Thread über einen Mutex einen neuen Status und beginnt mit der Wiedergabe neuer Musik , oder pausiert es. Der Hintergrundstream wird auch zum Puffern von Musik während der Wiedergabe verwendet.
Meine Versuche, die Surreal Engine für Emscripten mit pthread zu erstellen, waren erfolglos, da die SDL2- und OpenAL-Ports ohne pthread-Unterstützung erstellt wurden und ich sie nicht aus Gründen der Musik neu erstellen wollte. Daher habe ich die Funktionalität des Hintergrundmusik-Streams mithilfe einer Schleife auf die Single-Threaded-Ausführung übertragen. Durch das Entfernen von pthread-Aufrufen aus dem C++-Code habe ich die Pufferung und Musikwiedergabe in den Hauptthread verschoben, damit es keine Verzögerungen gibt, ich habe den Puffer um einige Sekunden erhöht.

Als nächstes werde ich spezifische Implementierungen von Grafik und Sound beschreiben.

Vulkan wird nicht unterstützt!

Ja, Vulkan wird in HTML5 nicht unterstützt, obwohl alle Marketingbroschüren die plattformübergreifende und breite Plattformunterstützung als Hauptvorteil von Vulkan darstellen. Aus diesem Grund musste ich meinen eigenen grundlegenden Grafikrenderer für einen vereinfachten OpenGL-Typ schreiben – ES, es wird auf mobilen Geräten verwendet, enthält manchmal nicht die modischen Funktionen des modernen OpenGL, lässt sich aber sehr gut auf WebGL portieren, was genau das ist, was Emscripten implementiert. Das Schreiben des grundlegenden Kachel-Renderings, des BSP-Renderings für die einfachste GUI-Anzeige und des Renderns von Modellen und Karten wurde in zwei Wochen abgeschlossen. Dies war vielleicht der schwierigste Teil des Projekts. Es liegt noch viel Arbeit vor uns, die volle Funktionalität des Surreal Engine-Renderings zu implementieren, daher ist jede Hilfe von Lesern in Form von Code und Pull-Requests willkommen.

OpenAL unterstützt!

Das große Glück ist, dass Surreal Engine OpenAL für die Audioausgabe verwendet. Nachdem ich eine einfache Hallo-Welt in OpenAL geschrieben und sie mit Emscripten in WebAssembly zusammengestellt hatte, wurde mir klar, wie einfach alles war, und ich machte mich an die Portierung des Sounds.
Nach mehreren Stunden des Debuggens stellte sich heraus, dass die OpenAL-Implementierung von Emscripten mehrere Fehler aufweist. Beispielsweise gab die Methode beim Initialisieren des Lesens der Anzahl der Monokanäle eine unendliche Zahl zurück, und nachdem versucht wurde, einen Vektor unendlicher Größe zu initialisieren, C++ stürzt mit der Ausnahme vector::length_error ab.

Wir haben es geschafft, dies zu umgehen, indem wir die Anzahl der Monokanäle auf 2048 fest codiert haben:


		alcGetIntegerv(alDevice, ALC_STEREO_SOURCES, 1, &stereoSources);



#if __EMSCRIPTEN__

		monoSources = 2048; // for some reason Emscripten's OpenAL gives infinite monoSources count, bug?

#endif



Gibt es ein Netzwerk?

Surreal Engine unterstützt derzeit kein Online-Spielen, das Spielen mit Bots wird unterstützt, aber wir brauchen jemanden, der KI für diese Bots schreibt. Theoretisch können Sie mithilfe von Websockets ein Netzwerkspiel auf WebAssembly/Emscripten implementieren.

Schlussfolgerung

Abschließend möchte ich sagen, dass die Portierung der Surreal Engine aufgrund der Verwendung von Bibliotheken, für die es Emscripten-Portierungen gibt, sowie meiner bisherigen Erfahrung bei der Implementierung eines Spiels in C++ für WebAssembly recht reibungslos verlief auf Emscripten. Nachfolgend finden Sie Links zu Wissensquellen und Repositories zum Thema.
M-M-M-MONSTER TÖTEN!

Außerdem, wenn Sie dem Projekt helfen möchten, vorzugsweise mit WebGL/OpenGL ES-Rendering-Code, dann schreiben Sie mir per Telegram:
https://t.me/demenscave

Links

https://demensdeum.com/demos/SurrealEngine/
https://github.com/demensdeum/SurrealEngine-Emscripten

https://github.com/dpjudas/SurrealEngine

Flash Forever – Interceptor 2021

Recently, it turned out that Adobe Flash works quite stably under Wine. During a 4-hour stream, I made the game Interceptor 2021, which is a sequel to the game Interceptor 2020, written for the ZX Spectrum.

For those who are not in the know – the Flash technology provided interactivity on the web from 2000 to around 2015. Its shutdown was prompted by an open letter from Steve Jobs, in which he wrote that Flash should be consigned to history because it lagged on the iPhone. Since then, JS has become even more sluggish than Flash, and Flash itself has been wrapped in JS, making it possible to run it on anything thanks to the Ruffle player.

You can play it here:
https://demensdeum.com/demos/Interceptor2021

Video:
https://www.youtube.com/watch?v=-3b5PkBvHQk

Source code:
https://github.com/demensdeum/Interceptor-2021

Masons-DR-Demospiele

Masonry-AR ist ein Augmented-Reality-Spiel, in dem Sie in der realen Welt durch die Stadt navigieren und freimaurerisches Wissen aus Büchern sammeln müssen, um Geld zu erhalten und Gebiete für Ihren Freimaurerorden zu erobern. Das Spiel hat keinen Bezug zu echten Organisationen, alle Spiele sind zufällig.

Spieldemo:
https://demensdeum.com/demos/masonry-ar/client

Vicki:
https://demensdeum.com/masonry-ar-wiki-ru/

Quellcode:
https://github.com/demensdeum/Masonry-AR

CRUD-Repository

In dieser Notiz beschreibe ich die Grundprinzipien des bekannten klassischen CRUD-Musters und die Implementierung in der Swift-Sprache. Swift ist eine offene, plattformübergreifende Sprache, die für Windows, Linux, macOS, iOS und Android verfügbar ist.

Es gibt viele Lösungen zur Abstraktion von Datenspeicherung und Anwendungslogik. Eine solche Lösung ist der CRUD-Ansatz, ein Akronym für C– Erstellen, R -Lesen, U – Update, D– Löschen.
Typischerweise wird dieses Prinzip durch die Implementierung einer Schnittstelle zur Datenbank implementiert, in der Elemente mithilfe einer eindeutigen Kennung, beispielsweise einer ID, manipuliert werden. Für jeden Buchstaben CRUD – wird eine Schnittstelle erstellt. Erstellen (Objekt, ID), Lesen (ID), Aktualisieren (Objekt, ID), Löschen (Objekt, ID).
Wenn ein Objekt eine ID in sich selbst enthält, kann das ID-Argument in den Methoden (Create, Update, Delete) weggelassen werden, da das gesamte Objekt zusammen mit seinem Feld – Ausweis. Aber für – Für das Lesen ist eine ID erforderlich, da wir anhand der ID ein Objekt aus der Datenbank abrufen möchten.

Alle Namen sind fiktiv

Stellen wir uns vor, dass eine hypothetische AssistantAI-Anwendung mit dem kostenlosen EtherRelm-Datenbank-SDK erstellt wurde. Die Integration war einfach, die API war sehr praktisch und als Ergebnis wurde die Anwendung auf den Markt gebracht.
Plötzlich beschließt der SDK-Entwickler EtherRelm, es kostenpflichtig zu machen und legt den Preis auf 100 US-Dollar pro Jahr und Anwendungsbenutzer fest.
Was? Ja! Was sollen die Entwickler von AssistantAI jetzt tun, da sie bereits 1 Million aktive Benutzer haben! 100 Millionen US-Dollar zahlen?
Stattdessen wird beschlossen, die Übertragung der Anwendung in die plattformeigene RootData-Datenbank zu evaluieren. Nach Angaben der Programmierer wird eine solche Übertragung etwa sechs Monate dauern, wobei die Implementierung neuer Funktionen in der Anwendung nicht berücksichtigt ist. Nach einigem Überlegen wurde beschlossen, die Anwendung vom Markt zu nehmen und sie auf einem anderen kostenlosen plattformübergreifenden Framework mit integrierter BueMS-Datenbank neu zu schreiben. Dadurch wird das Problem mit der kostenpflichtigen Datenbank gelöst und die Entwicklung auf anderen Plattformen vereinfacht.
Ein Jahr später wurde die Anwendung in BueMS umgeschrieben, doch dann beschließt der Framework-Entwickler plötzlich, sie kostenpflichtig zu machen. Es stellt sich heraus, dass das Team zweimal in die gleiche Falle getappt ist; ob es beim zweiten Mal wieder herauskommt, ist eine ganz andere Geschichte.

Abstraktion zur Rettung

Diese Probleme hätten vermieden werden können, wenn Entwickler eine Abstraktion von Schnittstellen innerhalb der Anwendung verwendet hätten. Zu den drei Säulen von OOP – Polymorphismus, Kapselung, Vererbung, vor nicht allzu langer Zeit haben sie ein weiteres – Abstraktion.
Mit der Datenabstraktion können Sie Ideen und Modelle allgemein und mit einem Minimum an Details beschreiben und gleichzeitig genau genug sein, um spezifische Implementierungen zu implementieren, die zur Lösung von Geschäftsproblemen verwendet werden.
Wie können wir den Datenbankbetrieb abstrahieren, sodass die Anwendungslogik nicht davon abhängt? Wir verwenden den CRUD-Ansatz!

Ein vereinfachtes UML CRUD-Diagramm sieht so aus:

Beispiel mit einer fiktiven EtherRelm-Datenbank:

Beispiel mit einer echten SQLite-Datenbank:

Wie Sie bereits bemerkt haben, ändert sich beim Wechseln der Datenbank nur die CRUD-Schnittstelle, mit der die Anwendung interagiert. CRUD ist eine Implementierung des GoF-Musters – Adapter, weil Damit passen wir Anwendungsschnittstellen an beliebige Datenbanken an und kombinieren inkompatible Schnittstellen.
Worte sind leer, zeig mir den Code
Um Abstraktionen in Programmiersprachen zu implementieren, werden Schnittstellen/Protokolle/abstrakte Klassen verwendet. All dies sind Phänomene der gleichen Art. In Interviews werden Sie jedoch möglicherweise gebeten, den Unterschied zwischen ihnen zu benennen. Ich persönlich denke, dass dies nicht viel Sinn macht, weil Der einzige Verwendungszweck besteht darin, eine Datenabstraktion zu implementieren, andernfalls dient es dazu, das Gedächtnis des Befragten zu testen.
CRUD wird oft im Rahmen des Repository-Musters implementiert. Das Repository kann jedoch die CRUD-Schnittstelle implementieren oder auch nicht, alles hängt vom Einfallsreichtum des Entwicklers ab.

Stellen Sie sich einen ziemlich typischen Swift-Code für das Book-Struktur-Repository vor, der direkt mit der UserDefaults-Datenbank arbeitet:


struct Book: Codable {
	let title: String
	let author: String
}

class BookRepository {
	func save(book: Book) {
    		let record = try! JSONEncoder().encode(book)
    		UserDefaults.standard.set(record, forKey: book.title)
	}
    
	func get(bookWithTitle title: String) -> Book? {
    		guard let data = UserDefaults.standard.data(forKey: title) else { return nil }
    		let book = try! JSONDecoder().decode(Book.self, from: data)
    		return book
	}
    
	func delete(book: Book) {
    		UserDefaults.standard.removeObject(forKey: book.title)
	}
}

let book = Book(title: "Fear and Loathing in COBOL", author: "Sir Edsger ZX Spectrum")
let repository = BookRepository()
repository.save(book: book)
print(repository.get(bookWithTitle: book.title)!)
repository.delete(book: book)
guard repository.get(bookWithTitle: book.title) == nil else {
	print("Error: can't delete Book from repository!")
	exit(1)
}

Der obige Code scheint einfach zu sein, aber zählen wir die Anzahl der Verstöße gegen das DRY-Prinzip (Do not Repeat Yourself) und die Kohärenz des Codes:
Konnektivität zur UserDefaults-Datenbank
Konnektivität mit JSON-Encodern und -Decodern – JSONEncoder, JSONDecoder
Verbunden mit der Book-Struktur, aber wir benötigen ein abstraktes Repository, um nicht für jede Struktur, die wir in der Datenbank speichern, eine Repository-Klasse zu erstellen (DRY-Verletzung)

Ich sehe diese Art von CRUD-Repository-Code ziemlich oft, er kann verwendet werden, aber eine hohe Kopplung und Duplizierung des Codes führt dazu, dass seine Unterstützung mit der Zeit sehr kompliziert wird. Dies macht sich besonders bemerkbar, wenn Sie versuchen, zu einer anderen Datenbank zu wechseln, oder wenn Sie die interne Logik der Arbeit mit der Datenbank in allen in der Anwendung erstellten Repositorys ändern.
Anstatt Code zu duplizieren, halten Sie die Kopplung hoch – Schreiben wir ein Protokoll für das CRUD-Repository und abstrahieren so die Datenbankschnittstelle und die Anwendungsgeschäftslogik unter Berücksichtigung von DRY und implementieren eine geringe Kopplung:

    typealias Item = Codable
    typealias ItemIdentifier = String
    
    func create<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws
    func read<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier) async throws -> T
    func update<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws
    func delete(id: CRUDRepository.ItemIdentifier) async throws
}

Das CRUDRepository-Protokoll beschreibt Schnittstellen und zugehörige Datentypen für die weitere Implementierung eines bestimmten CRUD-Repositorys.

Als nächstes schreiben wir eine spezifische Implementierung für die UserDefaults-Datenbank:

    private typealias RecordIdentifier = String
    
    let tableName: String
    let dataTransformer: DataTransformer
    
    init(
   	 tableName: String = "",
   	 dataTransformer: DataTransformer = JSONDataTransformer()
    ) {
   	 self.tableName = tableName
   	 self.dataTransformer = dataTransformer
    }
    
    private func key(id: CRUDRepository.ItemIdentifier) -> RecordIdentifier {
   	 "database_\(tableName)_item_\(id)"
    }
   	 
    private func isExists(id: CRUDRepository.ItemIdentifier) async throws -> Bool {
   	 UserDefaults.standard.data(forKey: key(id: id)) != nil
    }
    
    func create<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
   	 let data = try await dataTransformer.encode(item)
   	 UserDefaults.standard.set(data, forKey: key(id: id))
   	 UserDefaults.standard.synchronize()
    }
    
    func read<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier) async throws -> T {
   	 guard let data = UserDefaults.standard.data(forKey: key(id: id)) else {
   		 throw CRUDRepositoryError.recordNotFound(id: id)
   	 }
   	 let item: T = try await dataTransformer.decode(data: data)
   	 return item
    }
    
    func update<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
   	 guard try await isExists(id: id) else {
   		 throw CRUDRepositoryError.recordNotFound(id: id)
   	 }
   	 let data = try await dataTransformer.encode(item)
   	 UserDefaults.standard.set(data, forKey: key(id: id))
   	 UserDefaults.standard.synchronize()
    }
    
    func delete(id: CRUDRepository.ItemIdentifier) async throws {
   	 guard try await isExists(id: id) else {
   		 throw CRUDRepositoryError.recordNotFound(id: id)
   	 }
   	 UserDefaults.standard.removeObject(forKey: key(id: id))
   	 UserDefaults.standard.synchronize()
    }
}

Der Code sieht lang aus, enthält aber eine vollständige konkrete Implementierung eines CRUD-Repositorys mit loser Kopplung, Details unten.
Typalias wurden zur Selbstdokumentation des Codes hinzugefügt.
Schwache Kopplung und starke Kopplung
Die Loslösung von einer bestimmten Struktur (Struktur) wird mithilfe des generischen T implementiert, das wiederum die Codable-Protokolle implementieren muss. Mit Codable können Sie Strukturen mithilfe von Klassen konvertieren, die die Protokolle TopLevelEncoder und TopLevelDecoder implementieren, beispielsweise JSONEncoder und JSONDecoder. Bei Verwendung von Basistypen (Int, String, Float usw.) ist es nicht erforderlich, zusätzlichen Code zum Konvertieren von Strukturen zu schreiben.

Die Entkopplung von einem bestimmten Encoder und Decoder erfolgt mithilfe der Abstraktion im DataTransformer-Protokoll:

	func encode<T: Encodable>(_ object: T) async throws -> Data
	func decode<T: Decodable>(data: Data) async throws -> T
}

Durch die Implementierung eines Datentransformators haben wir eine Abstraktion der Encoder- und Decoder-Schnittstellen implementiert und eine lose Kopplung implementiert, um die Arbeit mit verschiedenen Arten von Datenformaten sicherzustellen.

Das Folgende ist der Code für einen bestimmten DataTransformer, nämlich für JSON:

	func encode<T>(_ object: T) async throws -> Data where T : Encodable {
    		let data = try JSONEncoder().encode(object)
    		return data
	}
    
	func decode<T>(data: Data) async throws -> T where T : Decodable {
    		let item: T = try JSONDecoder().decode(T.self, from: data)
    		return item
	}
}

War es möglich?

Was hat sich geändert? Jetzt reicht es aus, ein bestimmtes Repository zu initialisieren, um mit jeder Struktur zu arbeiten, die das Codable-Protokoll implementiert, wodurch die Notwendigkeit entfällt, Code zu duplizieren und eine lose Kopplung der Anwendung zu implementieren.

Ein Beispiel für ein Client-CRUD mit einem bestimmten Repository, UserDefaults ist die Datenbank, JSON-Datenformat, Client-Struktur, außerdem ein Beispiel für das Schreiben und Lesen eines Arrays:


print("One item access example")

do {
	let clientRecordIdentifier = "client"
	let clientOne = Client(name: "Chill Client")
	let repository = UserDefaultsRepository(
    	tableName: "Clients Database",
    	dataTransformer: JSONDataTransformer()
	)
	try await repository.create(id: clientRecordIdentifier, item: clientOne)
	var clientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print("Client Name: \(clientRecord.name)")
	clientRecord.name = "Busy Client"
	try await repository.update(id: clientRecordIdentifier, item: clientRecord)
	let updatedClient: Client = try await repository.read(id: clientRecordIdentifier)
	print("Updated Client Name: \(updatedClient.name)")
	try await repository.delete(id: clientRecordIdentifier)
	let removedClientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print(removedClientRecord)
}
catch {
	print(error.localizedDescription)
}

print("Array access example")

let clientArrayRecordIdentifier = "clientArray"
let clientOne = Client(name: "Chill Client")
let repository = UserDefaultsRepository(
	tableName: "Clients Database",
	dataTransformer: JSONDataTransformer()
)
let array = [clientOne]
try await repository.create(id: clientArrayRecordIdentifier, item: array)
let savedArray: [Client] = try await repository.read(id: clientArrayRecordIdentifier)
print(savedArray.first!)

Während der ersten CRUD-Prüfung wurde eine Ausnahmebehandlung implementiert, bei der das Lesen des Remote-Elements nicht mehr möglich ist.

Datenbank wechseln

Jetzt zeige ich Ihnen, wie Sie Ihren aktuellen Code in eine andere Datenbank übertragen. Als Beispiel nehme ich den SQLite-Repository-Code, den ChatGPT generiert hat:


class SQLiteRepository: CRUDRepository {
    private typealias RecordIdentifier = String
    
    let tableName: String
    let dataTransformer: DataTransformer
    private var db: OpaquePointer?

    init(
   	 tableName: String,
   	 dataTransformer: DataTransformer = JSONDataTransformer()
    ) {
   	 self.tableName = tableName
   	 self.dataTransformer = dataTransformer
   	 self.db = openDatabase()
   	 createTableIfNeeded()
    }
    
    private func openDatabase() -> OpaquePointer? {
   	 var db: OpaquePointer? = nil
   	 let fileURL = try! FileManager.default
   		 .url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
   		 .appendingPathComponent("\(tableName).sqlite")
   	 if sqlite3_open(fileURL.path, &db) != SQLITE_OK {
   		 print("error opening database")
   		 return nil
   	 }
   	 return db
    }
    
    private func createTableIfNeeded() {
   	 let createTableString = """
   	 CREATE TABLE IF NOT EXISTS \(tableName) (
   	 id TEXT PRIMARY KEY NOT NULL,
   	 data BLOB NOT NULL
   	 );
   	 """
   	 var createTableStatement: OpaquePointer? = nil
   	 if sqlite3_prepare_v2(db, createTableString, -1, &createTableStatement, nil) == SQLITE_OK {
   		 if sqlite3_step(createTableStatement) == SQLITE_DONE {
       		 print("\(tableName) table created.")
   		 } else {
       		 print("\(tableName) table could not be created.")
   		 }
   	 } else {
   		 print("CREATE TABLE statement could not be prepared.")
   	 }
   	 sqlite3_finalize(createTableStatement)
    }
    
    private func isExists(id: CRUDRepository.ItemIdentifier) async throws -> Bool {
   	 let queryStatementString = "SELECT data FROM \(tableName) WHERE id = ?;"
   	 var queryStatement: OpaquePointer? = nil
   	 if sqlite3_prepare_v2(db, queryStatementString, -1, &queryStatement, nil) == SQLITE_OK {
   		 sqlite3_bind_text(queryStatement, 1, id, -1, nil)
   		 if sqlite3_step(queryStatement) == SQLITE_ROW {
       		 sqlite3_finalize(queryStatement)
       		 return true
   		 } else {
       		 sqlite3_finalize(queryStatement)
       		 return false
   		 }
   	 } else {
   		 print("SELECT statement could not be prepared.")
   		 throw CRUDRepositoryError.databaseError
   	 }
    }
    
    func create<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
   	 let insertStatementString = "INSERT INTO \(tableName) (id, data) VALUES (?, ?);"
   	 var insertStatement: OpaquePointer? = nil
   	 if sqlite3_prepare_v2(db, insertStatementString, -1, &insertStatement, nil) == SQLITE_OK {
   		 let data = try await dataTransformer.encode(item)
   		 sqlite3_bind_text(insertStatement, 1, id, -1, nil)
   		 sqlite3_bind_blob(insertStatement, 2, (data as NSData).bytes, Int32(data.count), nil)
   		 if sqlite3_step(insertStatement) == SQLITE_DONE {
       		 print("Successfully inserted row.")
   		 } else {
       		 print("Could not insert row.")
       		 throw CRUDRepositoryError.databaseError
   		 }
   	 } else {
   		 print("INSERT statement could not be prepared.")
   		 throw CRUDRepositoryError.databaseError
   	 }
   	 sqlite3_finalize(insertStatement)
    }
    
    func read<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier) async throws -> T {
   	 let queryStatementString = "SELECT data FROM \(tableName) WHERE id = ?;"
   	 var queryStatement: OpaquePointer? = nil
   	 var item: T?
   	 if sqlite3_prepare_v2(db, queryStatementString, -1, &queryStatement, nil) == SQLITE_OK {
   		 sqlite3_bind_text(queryStatement, 1, id, -1, nil)
   		 if sqlite3_step(queryStatement) == SQLITE_ROW {
       		 let queryResultCol1 = sqlite3_column_blob(queryStatement, 0)
       		 let queryResultCol1Length = sqlite3_column_bytes(queryStatement, 0)
       		 let data = Data(bytes: queryResultCol1, count: Int(queryResultCol1Length))
       		 item = try await dataTransformer.decode(data: data)
   		 } else {
       		 throw CRUDRepositoryError.recordNotFound(id: id)
   		 }
   	 } else {
   		 print("SELECT statement could not be prepared")
   		 throw CRUDRepositoryError.databaseError
   	 }
   	 sqlite3_finalize(queryStatement)
   	 return item!
    }
    
    func update<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
   	 guard try await isExists(id: id) else {
   		 throw CRUDRepositoryError.recordNotFound(id: id)
   	 }
   	 let updateStatementString = "UPDATE \(tableName) SET data = ? WHERE id = ?;"
   	 var updateStatement: OpaquePointer? = nil
   	 if sqlite3_prepare_v2(db, updateStatementString, -1, &updateStatement, nil) == SQLITE_OK {
   		 let data = try await dataTransformer.encode(item)
   		 sqlite3_bind_blob(updateStatement, 1, (data as NSData).bytes, Int32(data.count), nil)
   		 sqlite3_bind_text(updateStatement, 2, id, -1, nil)
   		 if sqlite3_step(updateStatement) == SQLITE_DONE {
       		 print("Successfully updated row.")
   		 } else {
       		 print("Could not update row.")
       		 throw CRUDRepositoryError.databaseError
   		 }
   	 } else {
   		 print("UPDATE statement could not be prepared.")
   		 throw CRUDRepositoryError.databaseError
   	 }
   	 sqlite3_finalize(updateStatement)
    }
    
    func delete(id: CRUDRepository.ItemIdentifier) async throws {
   	 guard try await isExists(id: id) else {
   		 throw CRUDRepositoryError.recordNotFound(id: id)
   	 }
   	 let deleteStatementString = "DELETE FROM \(tableName) WHERE id = ?;"
   	 var deleteStatement: OpaquePointer? = nil
   	 if sqlite3_prepare_v2(db, deleteStatementString, -1, &deleteStatement, nil) == SQLITE_OK {
   		 sqlite3_bind_text(deleteStatement, 1, id, -1, nil)
   		 if sqlite3_step(deleteStatement) == SQLITE_DONE {
       		 print("Successfully deleted row.")
   		 } else {
       		 print("Could not delete row.")
       		 throw CRUDRepositoryError.databaseError
   		 }
   	 } else {
   		 print("DELETE statement could not be prepared.")
   		 throw CRUDRepositoryError.databaseError
   	 }
   	 sqlite3_finalize(deleteStatement)
    }
}

Oder der CRUD-Code des Repositorys für das Dateisystem, der ebenfalls von ChatGPT generiert wurde:


class FileSystemRepository: CRUDRepository {
	private typealias RecordIdentifier = String
    
	let directoryName: String
	let dataTransformer: DataTransformer
	private let fileManager = FileManager.default
	private var directoryURL: URL
    
	init(
    	directoryName: String = "Database",
    	dataTransformer: DataTransformer = JSONDataTransformer()
	) {
    	self.directoryName = directoryName
    	self.dataTransformer = dataTransformer
   	 
    	let paths = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
    	directoryURL = paths.first!.appendingPathComponent(directoryName)
   	 
    	if !fileManager.fileExists(atPath: directoryURL.path) {
        	try? fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
    	}
	}
    
	private func fileURL(id: CRUDRepository.ItemIdentifier) -> URL {
    	return directoryURL.appendingPathComponent("item_\(id).json")
	}
    
	private func isExists(id: CRUDRepository.ItemIdentifier) async throws -> Bool {
    	return fileManager.fileExists(atPath: fileURL(id: id).path)
	}
    
	func create<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
    	let data = try await dataTransformer.encode(item)
    	let url = fileURL(id: id)
    	try data.write(to: url)
	}
    
	func read<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier) async throws -> T {
    	let url = fileURL(id: id)
    	guard let data = fileManager.contents(atPath: url.path) else {
        	throw CRUDRepositoryError.recordNotFound(id: id)
    	}
    	let item: T = try await dataTransformer.decode(data: data)
    	return item
	}
    
	func update<T: CRUDRepository.Item>(id: CRUDRepository.ItemIdentifier, item: T) async throws {
    	guard try await isExists(id: id) else {
        	throw CRUDRepositoryError.recordNotFound(id: id)
    	}
    	let data = try await dataTransformer.encode(item)
    	let url = fileURL(id: id)
    	try data.write(to: url)
	}
    
	func delete(id: CRUDRepository.ItemIdentifier) async throws {
    	guard try await isExists(id: id) else {
        	throw CRUDRepositoryError.recordNotFound(id: id)
    	}
    	let url = fileURL(id: id)
    	try fileManager.removeItem(at: url)
	}
}

Ersetzen Sie das Repository im Client-Code:


print("One item access example")

do {
	let clientRecordIdentifier = "client"
	let clientOne = Client(name: "Chill Client")
	let repository = FileSystemRepository(
    	directoryName: "Clients Database",
    	dataTransformer: JSONDataTransformer()
	)
	try await repository.create(id: clientRecordIdentifier, item: clientOne)
	var clientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print("Client Name: \(clientRecord.name)")
	clientRecord.name = "Busy Client"
	try await repository.update(id: clientRecordIdentifier, item: clientRecord)
	let updatedClient: Client = try await repository.read(id: clientRecordIdentifier)
	print("Updated Client Name: \(updatedClient.name)")
	try await repository.delete(id: clientRecordIdentifier)
	let removedClientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print(removedClientRecord)
}
catch {
	print(error.localizedDescription)
}

print("Array access example")

let clientArrayRecordIdentifier = "clientArray"
let clientOne = Client(name: "Chill Client")
let repository = FileSystemRepository(
	directoryName: "Clients Database",
	dataTransformer: JSONDataTransformer()
)
let array = [clientOne]
try await repository.create(id: clientArrayRecordIdentifier, item: array)
let savedArray: [Client] = try await repository.read(id: clientArrayRecordIdentifier)
print(savedArray.first!)

Die Initialisierung von UserDefaultsRepository wurde durch FileSystemRepository mit den entsprechenden Argumenten ersetzt.
Nachdem Sie die zweite Version des Client-Codes ausgeführt haben, finden Sie im Dokumentenordner ein Verzeichnis „Clients Database“, das eine Datei eines in JSON serialisierten Arrays mit einer Client-Struktur enthält.

Datenspeicherformat wechseln

Jetzt bitten wir ChatGPT, einen Encoder und Decoder für XML zu generieren:

	let formatExtension = "xml"
    
	func encode<T: Encodable>(_ item: T) async throws -> Data {
    	let encoder = PropertyListEncoder()
    	encoder.outputFormat = .xml
    	return try encoder.encode(item)
	}
    
	func decode<T: Decodable>(data: Data) async throws -> T {
    	let decoder = PropertyListDecoder()
    	return try decoder.decode(T.self, from: data)
	}
}

Dank der in Swift integrierten Typen wird die Aufgabe für ein neuronales Netzwerk elementar.

Ersetzen Sie JSON durch XML im Client-Code:


print("One item access example")

do {
	let clientRecordIdentifier = "client"
	let clientOne = Client(name: "Chill Client")
	let repository = FileSystemRepository(
    	directoryName: "Clients Database",
    	dataTransformer: XMLDataTransformer()
	)
	try await repository.create(id: clientRecordIdentifier, item: clientOne)
	var clientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print("Client Name: \(clientRecord.name)")
	clientRecord.name = "Busy Client"
	try await repository.update(id: clientRecordIdentifier, item: clientRecord)
	let updatedClient: Client = try await repository.read(id: clientRecordIdentifier)
	print("Updated Client Name: \(updatedClient.name)")
	try await repository.delete(id: clientRecordIdentifier)
	let removedClientRecord: Client = try await repository.read(id: clientRecordIdentifier)
	print(removedClientRecord)
}
catch {
	print(error.localizedDescription)
}

print("Array access example")

let clientArrayRecordIdentifier = "clientArray"
let clientOne = Client(name: "Chill Client")
let repository = FileSystemRepository(
	directoryName: "Clients Database",
	dataTransformer: XMLDataTransformer()
)
let array = [clientOne]
try await repository.create(id: clientArrayRecordIdentifier, item: array)
let savedArray: [Client] = try await repository.read(id: clientArrayRecordIdentifier)
print(savedArray.first!)

Der Client-Code wurde in nur einen Ausdruck JSONDataTransformer geändert -> XMLDataTransformer

Gesamt

CRUD-Repositorys sind eines der Entwurfsmuster, die zur Implementierung einer losen Kopplung von Anwendungsarchitekturkomponenten verwendet werden können. Eine weitere Lösung – unter Verwendung von ORM (Object-Relational Mapping), kurz gesagt, ORM verwendet einen Ansatz, bei dem Strukturen vollständig auf die Datenbank abgebildet werden und dann Änderungen an Modellen in der Datenbank angezeigt (zugeordnet (!)) werden sollen.
Aber das ist eine ganz andere Geschichte.

Eine vollständige Implementierung von CRUD-Repositorys für Swift ist verfügbar unter:
https://gitlab.com/demensdeum/crud-example

Swift wird übrigens schon lange außerhalb von macOS unterstützt; der Code aus dem Artikel wurde vollständig unter Arch Linux geschrieben und getestet.

Quellen

https://developer.apple.com/documentation/combine/topleveldecoder
https://developer.apple.com/documentation/combine/toplevelencoder
https://en.wikipedia.org/wiki/Create,_read,_update_and_delete

dd Ein-/Ausgabefehler

Was sollten Sie tun, wenn Sie beim Kopieren einer normalen Festplatte mit dd unter Linux einen Ein-/Ausgabefehler erhalten?

Situevina ist sehr traurig, aber lösbar. Höchstwahrscheinlich haben Sie es mit einer ausgefallenen Festplatte zu tun, die fehlerhafte Blöcke enthält, die nicht mehr verwendet, beschrieben oder gelesen werden können.

Überprüfen Sie eine solche Festplatte unbedingt mit S.M.A.R.T., da Ihnen höchstwahrscheinlich Festplattenfehler angezeigt werden. Dies war in meinem Fall der Fall; die Anzahl der fehlerhaften Blöcke war so groß, dass ich mich von der alten Festplatte verabschieden und sie durch eine neue SSD ersetzen musste.

Das Problem bestand darin, dass diese Diskette ein voll funktionsfähiges System mit lizenzierter Software enthielt, die für die Arbeit erforderlich war. Ich habe versucht, partimage zum schnellen Kopieren von Daten zu verwenden, aber plötzlich habe ich festgestellt, dass das Dienstprogramm nur ein Drittel der Festplatte kopiert. Dann endet es entweder mit einem Segfault oder mit einem anderen lustigen Sishny/Sipplusplus-Witz.

Als nächstes habe ich versucht, die Daten mit dd zu kopieren, und es stellte sich heraus, dass dd ungefähr an die gleiche Stelle wie partimage gelangt und dann ein Eingabe-/Ausgabefehler auftritt. Gleichzeitig haben alle möglichen lustigen Flags wie conv=noerr, skip oder etwas anderes überhaupt nicht geholfen.

Aber die Daten konnten mit einem GNU-Dienstprogramm namens ddrescue problemlos auf eine andere Festplatte kopiert werden.

После этого мои волосы стали шелковистыми, вернулась жена, дети и собака перестала кусать диван.

Большим плюсом ddrescue является наличие встроенного прогрессбара, поэтому не приходится костылять какие-то ухищрения навроде pv и всяких не особо красивых флажков dd. Также ddrescure показывает количество попыток прочитать данные; еще на вики написано что утилита обладает каким-то сверх алгоритмом для считывания поврежденных данных, оставим это на проверку людям которые любят ковыряться в исходниках, мы же не из этих да?

https://ru.wikipedia.org/wiki/Ddrescue
https://www.gnu.org/software/ddrescue/ddrescue_ru.html

ChatGPT

Hallo zusammen! In diesem Artikel möchte ich über ChatGPT sprechen – leistungsstarke Sprachmodellierung von OpenAI, die bei der Lösung einer Vielzahl von Textverarbeitungsproblemen helfen kann. Ich zeige Ihnen, wie dieses Tool funktioniert und wie es in der Praxis eingesetzt werden kann. Fangen wir an!

ChatGPT ist derzeit eines der weltweit besten Sprachmodelle auf Basis neuronaler Netze. Es wurde mit dem Ziel entwickelt, Entwicklern dabei zu helfen, intelligente Systeme zu erstellen, die in der Lage sind, natürliche Sprache zu erzeugen und mit den darin enthaltenen Menschen zu kommunizieren.

Einer der Hauptvorteile von ChatGPT ist seine Fähigkeit, Text kontextbezogen zu modellieren. Das bedeutet, dass das Modell frühere Dialoge berücksichtigt und diese nutzt, um die Situation genauer zu verstehen und eine natürlichere Reaktion zu generieren.

Sie können ChatGPT für eine Vielzahl von Aufgaben verwenden, wie zum Beispiel die Automatisierung des Kundensupports, die Erstellung von Chatbots, die Textgenerierung und vieles mehr.

Die neuronalen Netze hinter ChatGPT wurden anhand großer Textmengen trainiert, um äußerst genaue Vorhersagen zu gewährleisten. Dadurch kann das Modell natürlichen Text generieren, der den Dialog unterstützen und Fragen beantworten kann.

Mit ChatGPT können Sie Ihre eigenen Chatbots und andere intelligente Systeme erstellen, die mit Menschen in natürlicher Sprache interagieren können. Dies kann besonders in Branchen wie Tourismus, Einzelhandel und Kundenbetreuung nützlich sein.

Zusammenfassend lässt sich sagen, dass ChatGPT – Es ist ein leistungsstarkes Werkzeug zur Lösung verschiedener Sprachmodellierungsprobleme. Seine Fähigkeit zur kontextbezogenen Modellierung macht es besonders nützlich für die Erstellung von Chatbots und intelligenten Systemen.


Tatsächlich hat ChatGPT alles oben Genannte selbst geschrieben. Was? Ja? Ich bin selbst schockiert!

Sie können das Raster selbst hier ausprobieren:
https://chat.openai.com/chat

Schalten Sie die Hintergrundbeleuchtung der USB-Tastatur unter macOS ein

Ich habe kürzlich eine sehr preiswerte Getorix GK-45X USB-Tastatur mit RGB-Hintergrundbeleuchtung gekauft. Nachdem ich es an ein MacBook Pro mit M1-Prozessor angeschlossen hatte, stellte sich heraus, dass die RGB-Hintergrundbeleuchtung nicht funktionierte. Selbst durch Drücken der magischen Kombination Fn + Scroll Lock konnte die Hintergrundbeleuchtung nicht eingeschaltet werden; nur die Hintergrundbeleuchtung des MacBook-Bildschirms änderte sich.
Es gibt mehrere Lösungen für dieses Problem, nämlich OpenRGB (funktioniert nicht), HID-LED-Test (funktioniert nicht). Nur das Dienstprogramm kvmswitch hat funktioniert:
https://github.com/stoutput/OSX-KVM

Sie müssen es von GitHub herunterladen und die Ausführung über das Terminal im Bereich „Sicherheit“ der Systemeinstellungen zulassen.
Wie ich der Beschreibung entnehme, sendet das Dienstprogramm nach dem Start einen Tastendruck auf Fn + Scroll Lock und schaltet so die Hintergrundbeleuchtung der Tastatur ein/aus.

Mitsume ga Tōru (NES) – Drittes Auge auf Dandy

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

Mitsume ga Tooru (三つ目がとおる Mitsume ga tōru?, wörtlich „dreiäugig“) ist ein Plattform-Videospiel, das 1992 von Natsume exklusiv für das Nintendo Entertainment System entwickelt wurde. Das Spiel basiert auf dem Manga und Anime Mitsume ga Tooru. Dies ist das zweite von Natsume entwickelte Anime-basierte Spiel. das vorherige war Mitsume ga Tooru: 3Lie-Mon, das zwei Jahre zuvor für die MSX-Plattform veröffentlicht wurde. In Russland ist das Spiel besser bekannt als „3 Eyes“ oder „3 Eyes“. Drittes Auge”.

Number 2

Comrades, I take pride in projects that were created on the basis of Flame Steel Framework 1 and specifically on Flame Steel Engine 1, namely Death-Mask, Cube Art Project, since all this was conceived as a big experiment, creating a multimedia framework alone that can work on the most platforms. I think the experiment ended successfully immediately after the release of the Cube Art Project.

Now about the decisions that I came to during the development of new projects on FSFramework 1

During the development of Space Jaguar and the Space Jaguar Galaxy Bastards shooter, it became clear that the Flame Steel Framework tools were already outdated, not even having time to become at least somewhat convenient.

Therefore, I decided to develop a completely new Flame Steel Framework 2. The main decision will be to switch to my Rise 2 transpiler language, and the Component System (ECS) will no longer be used architecturally, because. it turned out to be needed only within the framework of game logic with great dynamics. For this reason, in Flame Steel Framework 2, the component system will only be possible while using the scripting languages ​​that are planned to be implemented (at least Lua and JavaScript), an interesting feature is that these languages ​​​​are dynamic in nature, so additional creation of the component system is redundant.

You can follow the development of new projects on the blog and on Gitlab:

https://gitlab.com/demensdeum/rise2

https://gitlab.com/demensdeum/flamesteelengine2

https://gitlab.com/demensdeum/flame-steel-engine-2-demo-projects

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

https://gitlab.com/demensdeum/space-jaguar-galaxy-bastards

Baumsortierung

Baumsortierung – Sortierung mithilfe eines binären Suchbaums. Zeitkomplexität – O(n²). In einem solchen Baum hat jeder Knoten links Zahlen, die kleiner sind als der Knoten, rechts sind es mehr als der Knoten. Wenn wir von der Wurzel kommen und die Werte von links nach rechts drucken, erhalten wir eine sortierte Liste von Zahlen . Überraschend, oder?

Betrachten Sie das binäre Suchbaumdiagramm:

Derrick Coetzee (gemeinfrei)

Versuchen Sie, die Zahlen manuell abzulesen, beginnend beim vorletzten linken Knoten der unteren linken Ecke, für jeden Knoten links – einen Knoten – rechts.

Es wird so aussehen:

  1. Vorletzter Knoten unten links – 3.
  2. Es hat einen linken Zweig – 1.
  3. Nehmen Sie diese Nummer (1)
  4. Als nächstes nehmen wir Scheitelpunkt 3 (1, 3)
  5. Auf der rechten Seite befindet sich Zweig 6, der jedoch Zweige enthält. Deshalb lesen wir es genauso.
  6. Linker Zweig von Knoten 6 Nummer 4 (1, 3, 4)
  7. Der Knoten selbst ist 6 (1, 3, 4, 6)
  8. Rechts 7 (1, 3, 4, 6, 7)
  9. Gehen Sie zum Wurzelknoten hoch – 8 (1,3, 4,6, 7, 8)
  10. Alles auf der rechten Seite drucken wir analog aus
  11. Wir erhalten die endgültige Liste – 1, 3, 4, 6, 7, 8, 10, 13, 14

Um den Algorithmus im Code zu implementieren, benötigen Sie zwei Funktionen:

  1. Zusammenstellen eines binären Suchbaums
  2. Den binären Suchbaum in der richtigen Reihenfolge ausdrucken

Der binäre Suchbaum wird auf die gleiche Weise zusammengestellt, wie er gelesen wird. An jeden Knoten wird links oder rechts eine Zahl angehängt, je nachdem, ob er kleiner oder größer ist.

Beispiel in Lua:


function Node:new(value, lhs, rhs)
    output = {}
    setmetatable(output, self)
    self.__index = self  
    output.value = value
    output.lhs = lhs
    output.rhs = rhs
    output.counter = 1
    return output  
end

function Node:Increment()
    self.counter = self.counter + 1
end

function Node:Insert(value)
    if self.lhs ~= nil and self.lhs.value > value then
        self.lhs:Insert(value)
        return
    end

    if self.rhs ~= nil and self.rhs.value < value then
        self.rhs:Insert(value)
        return
    end

    if self.value == value then
        self:Increment()
        return
    elseif self.value > value then
        if self.lhs == nil then
            self.lhs = Node:new(value, nil, nil)
        else
            self.lhs:Insert(value)
        end
        return
    else
        if self.rhs == nil then
            self.rhs = Node:new(value, nil, nil)
        else
            self.rhs:Insert(value)
        end
        return
    end
end

function Node:InOrder(output)
    if self.lhs ~= nil then
       output = self.lhs:InOrder(output)
    end
    output = self:printSelf(output)
    if self.rhs ~= nil then
        output = self.rhs:InOrder(output)
    end
    return output
end

function Node:printSelf(output)
    for i=0,self.counter-1 do
        output = output .. tostring(self.value) .. " "
    end
    return output
end

function PrintArray(numbers)
    output = ""
    for i=0,#numbers do
        output = output .. tostring(numbers[i]) .. " "
    end    
    print(output)
end

function Treesort(numbers)
    rootNode = Node:new(numbers[0], nil, nil)
    for i=1,#numbers do
        rootNode:Insert(numbers[i])
    end
    print(rootNode:InOrder(""))
end


numbersCount = 10
maxNumber = 9

numbers = {}

for i=0,numbersCount-1 do
    numbers[i] = math.random(0, maxNumber)
end

PrintArray(numbers)
Treesort(numbers)

Важный нюанс что для чисел которые равны вершине придумано множество интересных механизмов подцепления к ноде, я же просто добавил счетчик к классу вершины, при распечатке числа возвращаются по счетчику.

Ссылки

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

Источники

TreeSort Algorithm Explained and Implemented with Examples in Java | Sorting Algorithms | Geekific – YouTube

Tree sort – YouTube

Convert Sorted Array to Binary Search Tree (LeetCode 108. Algorithm Explained) – YouTube

Sorting algorithms/Tree sort on a linked list – Rosetta Code

Tree Sort – GeeksforGeeks

Tree sort – Wikipedia

How to handle duplicates in Binary Search Tree? – GeeksforGeeks

Tree Sort | GeeksforGeeks – YouTube

Eimersortierung

Bucket Sort – Sortierung nach Buckets. Der Algorithmus ähnelt der Zählsortierung, mit dem Unterschied, dass die Zahlen in „Buckets“-Bereichen gesammelt werden, dann die Buckets mit einem anderen, ausreichend produktiven Sortieralgorithmus sortiert werden und der letzte Schritt darin besteht, den „Buckets“-Bereich zu entfalten um eins, was zu einer sortierten Liste führt

Die zeitliche Komplexität des Algorithmus beträgt O(nk). Der Algorithmus arbeitet in linearer Zeit für Daten, die einem Gleichverteilungsgesetz gehorchen. Vereinfacht ausgedrückt müssen die Elemente in einem bestimmten Bereich liegen, ohne „Spitzen“, zum Beispiel Zahlen von 0,0 bis 1,0. Wenn unter solchen Zahlen 4 oder 999 sind, dann gilt eine solche Reihe nach den Hofgesetzen nicht mehr als „gerade“.

Implementierungsbeispiel in Julia:

    buckets = Vector{Vector{Int}}()
    
    for i in 0:bucketsCount - 1
        bucket = Vector{Int}()
        push!(buckets, bucket)
    end

    maxNumber = maximum(numbers)

    for i in 0:length(numbers) - 1
        bucketIndex = 1 + Int(floor(bucketsCount * numbers[1 + i] / (maxNumber + 1)))
        push!(buckets[bucketIndex], numbers[1 + i])
    end

    for i in 0:length(buckets) - 1
        bucketIndex = 1 + i
        buckets[bucketIndex] = sort(buckets[bucketIndex])
    end

    flat = [(buckets...)...]
    print(flat, "\n")

end

numbersCount = 10
maxNumber = 10
numbers = rand(1:maxNumber, numbersCount)
print(numbers,"\n")
bucketsCount = 10
bucketSort(numbers, bucketsCount)

На производительность алгоритма также влияет число ведер, для большего количества чисел лучше взять большее число ведер (Algorithms in a nutshell by George T. Heineman)

Ссылки

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

Источники

https://www.youtube.com/watch?v=VuXbEb5ywrU
https://www.youtube.com/watch?v=ELrhrrCjDOA
https://medium.com/karuna-sehgal/an-introduction-to-bucket-sort-62aa5325d124
https://www.geeksforgeeks.org/bucket-sort-2/
https://ru.wikipedia.org/wiki/%D0%91%D0%BB%D0%BE%D1%87%D0%BD%D0%B0%D1%8F_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0
https://www.youtube.com/watch?v=LPrF9yEKTks
https://en.wikipedia.org/wiki/Bucket_sort
https://julialang.org/
https://www.oreilly.com/library/view/algorithms-in-a/9780596516246/ch04s08.html

Radixsort

Radix-Sortierung – Basissortierung. Der Algorithmus ähnelt der Zählsortierung darin, dass es keinen Vergleich von Elementen gibt; stattdessen werden Elemente *Zeichen für Zeichen* in *Buckets* (Buckets) gruppiert, der Bucket wird anhand des Index des aktuellen Zahlenzeichens ausgewählt. Zeitkomplexität – O(nd).

Es funktioniert ungefähr so:

  • Die Eingabe erfolgt aus den Zahlen 6, 12, 44, 9
  • Wir werden 10 Buckets mit Listen (0-9) erstellen, in die wir Zahlen Stück für Stück hinzufügen/sortieren.

Weiter:

  1. Starten Sie eine Schleife mit Zähler i bis zur maximalen Anzahl von Zeichen in der Zahl
  2. Durch den Index i von rechts nach links erhalten wir ein Symbol für jede Zahl; wenn es kein Symbol gibt, dann gehen wir davon aus, dass es Null ist
  3. Konvertieren Sie das Symbol in eine Zahl
  4. Wählen Sie einen Bucket nach Indexnummer aus und geben Sie dort die ganze Zahl ein
  5. Nachdem Sie mit der Suche durch die Zahlen fertig sind, wandeln Sie alle Buckets wieder in eine Zahlenliste um
  6. Erhalten Sie Zahlen nach Rang sortiert
  7. Wiederholen Sie den Vorgang, bis alle Ziffern verschwunden sind

Beispiel für Radix-Sortierung in Scala:


import scala.util.Random.nextInt



object RadixSort {

    def main(args: Array[String]) = {

        var maxNumber = 200

        var numbersCount = 30

        var maxLength = maxNumber.toString.length() - 1



        var referenceNumbers = LazyList.continually(nextInt(maxNumber + 1)).take(numbersCount).toList

        var numbers = referenceNumbers

        

        var buckets = List.fill(10)(ListBuffer[Int]())



        for( i <- 0 to maxLength) { numbers.foreach( number => {

                    var numberString = number.toString

                    if (numberString.length() > i) {

                        var index = numberString.length() - i - 1

                        var character = numberString.charAt(index).toString

                        var characterInteger = character.toInt  

                        buckets.apply(characterInteger) += number

                    }

                    else {

                        buckets.apply(0) += number

                    }

                }

            )

            numbers = buckets.flatten

            buckets.foreach(x => x.clear())

        }

        println(referenceNumbers)

        println(numbers)

        println(s"Validation result: ${numbers == referenceNumbers.sorted}")

    }

}

Der Algorithmus verfügt auch über eine Version zur parallelen Ausführung, beispielsweise auf einer GPU; Es gibt auch eine Möglichkeit zum Sortieren von Bits, die sehr interessant und wirklich atemberaubend sein muss!

Links

https://gitlab .com/demensdeum/algorithms/-/blob/master/sortAlgorithms/radixSort/radixSort.scala

Quellen

https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D1%80%D0%B0%D0%B7%D1%80%D1%8F%D 0%B4%D0%BD%D0%B0%D1%8F_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0% BA%D0%B0
https://www.geeksforgeeks.org/radix-sort/

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

https://github.com/gyatskov/radix-sort

Haufensort

Heapsort – Pyramidensortierung. Zeitliche Komplexität des Algorithmus – O(n log n), schnell, oder? Ich würde das Sortieren das Sortieren fallender Kieselsteine ​​nennen. Es scheint mir, dass man es am einfachsten visuell erklären kann.

Die Eingabe ist eine Liste von Zahlen, zum Beispiel:
5, 0, 7, 2, 3, 9, 4

Von links nach rechts wird eine Datenstruktur erstellt – ein Binärbaum, oder wie ich es nenne – Pyramide. Pyramidenelemente können maximal zwei untergeordnete Elemente, aber nur ein oberstes Element haben.

Lassen Sie uns einen Binärbaum erstellen:
⠀⠀5
⠀0⠀7
2 3 9 4

Wenn Sie die Pyramide längere Zeit betrachten, können Sie erkennen, dass es sich nur um Zahlen aus einer Reihe handelt, die nacheinander auftauchen. Die Anzahl der Elemente in jeder Etage wird mit zwei multipliziert.

Als nächstes beginnt der Spaß: Sortieren wir die Pyramide von unten nach oben mit der Methode der fallenden Kieselsteine ​​(aufhäufen). Mit dem Sortieren könnte man ab der letzten Etage beginnen (2 3 9 4), aber es hat keinen Sinn, weil Es gibt keine Etage darunter, in die man fallen könnte.

Daher beginnen wir, Elemente aus der vorletzten Etage (0 7) fallen zu lassen
⠀⠀5
⠀0⠀7
2 3 9 4

Das erste fallende Element wird von rechts ausgewählt, in unserem Fall ist es 7, dann schauen wir uns an, was darunter liegt, und darunter sind 9 und 4, neun ist größer als vier, und auch neun ist größer als Sieben! Wir lassen 7 auf 9 fallen und heben 9 auf Platz 7.
⠀⠀5
⠀0⠀9
2 3 7 4

Als nächstes verstehen wir, dass die Sieben nirgendwo tiefer fallen kann, und gehen weiter zur Zahl 0, die sich im vorletzten Stockwerk auf der linken Seite befindet:
⠀⠀5
0⠀9
2 3 7 4

Mal sehen, was sich darunter verbirgt – 2 und 3, zwei ist kleiner als drei, drei ist mehr als null, also vertauschen wir null und drei:
⠀⠀5
⠀3⠀9
2 0 7 4

Wenn Sie das Ende der Etage erreichen, gehen Sie in die darüber liegende Etage und lassen Sie dort, wenn möglich, alles ab.
Das Ergebnis ist eine Datenstruktur – ein Heap, nämlich Max Heap, weil Oben ist das größte Element:
⠀⠀9
⠀3⠀7
2 0 5 4

Wenn Sie es in eine Array-Darstellung zurückführen, erhalten Sie eine Liste:
[9, 3, 7, 2, 0, 5, 4]

Daraus können wir schließen, dass wir durch Vertauschen des ersten und letzten Elements die erste Zahl an der endgültigen sortierten Position erhalten, nämlich 9 sollte am Ende der sortierten Liste stehen, Plätze vertauschen:
[4, 3, 7, 2, 0, 5, 9]

Sehen wir uns einen Binärbaum an:
⠀⠀4
⠀3⠀7
2 0 5 9

Das Ergebnis ist eine Situation, in der der untere Teil des Baums sortiert ist. Sie müssen lediglich 4 an der richtigen Position ablegen, den Algorithmus wiederholen, aber die bereits sortierten Zahlen, nämlich 9, nicht berücksichtigen:
⠀⠀4
⠀3⠀7
2 0 5 9

⠀⠀7
⠀3⠀4
2 0 5 9

⠀⠀7
⠀3⠀5
2 0 4 9

Es stellte sich heraus, dass wir, nachdem wir 4 verloren hatten, die nächstgrößte Zahl nach 9 erhöht hatten – 7. Vertauschen Sie die letzte unsortierte Zahl (4) und die größte Zahl (7)
⠀⠀4
⠀3⠀5
2 0 7 9

Es stellt sich heraus, dass wir jetzt zwei Zahlen an der richtigen Endposition haben:
4, 3, 5, 2, 0, 7, 9

Als nächstes wiederholen wir den Sortieralgorithmus und ignorieren die bereits sortierten. Am Ende erhalten wir ein Heap Typ:
⠀⠀0
⠀2⠀3
4 5 7 9

Oder als Liste:
0, 2, 3, 4, 5, 7, 9

Implementierung

Der Algorithmus ist normalerweise in drei Funktionen unterteilt:

  1. Einen Heap erstellen
  2. Sifting-Algorithmus (Heapify)
  3. Ersetzen des letzten unsortierten Elements durch das erste

Der Heap wird erstellt, indem die vorletzte Zeile des Binärbaums mithilfe der Heapify-Funktion von rechts nach links bis zum Ende des Arrays durchlaufen wird. Als nächstes im Zyklus erfolgt die erste Ersetzung der Zahlen, danach fällt/bleibt das erste Element an Ort und Stelle, wodurch das größte Element an die erste Stelle fällt, der Zyklus wird mit einer Verringerung der Teilnehmerzahl um eins wiederholt, weil Nach jedem Durchlauf bleiben sortierte Zahlen am Ende der Liste.

Heapsort-Beispiel in Ruby:






module Colors



    BLUE = "\033[94m"



    RED = "\033[31m"



    STOP = "\033[0m"



end







def heapsort(rawNumbers)



    numbers = rawNumbers.dup







    def swap(numbers, from, to)



        temp = numbers[from]



        numbers[from] = numbers[to]



        numbers[to] = temp



    end







    def heapify(numbers)



        count = numbers.length()



        lastParentNode = (count - 2) / 2







        for start in lastParentNode.downto(0)



            siftDown(numbers, start, count - 1)



            start -= 1 



        end







        if DEMO



            puts "--- heapify ends ---"



        end



    end







    def siftDown(numbers, start, rightBound)      



        cursor = start



        printBinaryHeap(numbers, cursor, rightBound)







        def calculateLhsChildIndex(cursor)



            return cursor * 2 + 1



        end







        def calculateRhsChildIndex(cursor)



            return cursor * 2 + 2



        end            







        while calculateLhsChildIndex(cursor) <= rightBound



            lhsChildIndex = calculateLhsChildIndex(cursor)



            rhsChildIndex = calculateRhsChildIndex(cursor)







            lhsNumber = numbers[lhsChildIndex]



            biggerChildIndex = lhsChildIndex







            if rhsChildIndex <= rightBound



                rhsNumber = numbers[rhsChildIndex]



                if lhsNumber < rhsNumber



                    biggerChildIndex = rhsChildIndex



                end



            end







            if numbers[cursor] < numbers[biggerChildIndex]



                swap(numbers, cursor, biggerChildIndex)



                cursor = biggerChildIndex



            else



                break



            end



            printBinaryHeap(numbers, cursor, rightBound)



        end



        printBinaryHeap(numbers, cursor, rightBound)



    end







    def printBinaryHeap(numbers, nodeIndex = -1, rightBound = -1)



        if DEMO == false



            return



        end



        perLineWidth = (numbers.length() * 4).to_i



        linesCount = Math.log2(numbers.length()).ceil()



        xPrinterCount = 1



        cursor = 0



        spacing = 3



        for y in (0..linesCount)



            line = perLineWidth.times.map { " " }



            spacing = spacing == 3 ? 4 : 3



            printIndex = (perLineWidth / 2) - (spacing * xPrinterCount) / 2



            for x in (0..xPrinterCount - 1)



                if cursor >= numbers.length



                    break



                end



                if nodeIndex != -1 && cursor == nodeIndex



                    line[printIndex] = "%s%s%s" % [Colors::RED, numbers[cursor].to_s, Colors::STOP]



                elsif rightBound != -1 && cursor > rightBound



                    line[printIndex] = "%s%s%s" % [Colors::BLUE, numbers[cursor].to_s, Colors::STOP]



                else



                    line[printIndex] = numbers[cursor].to_s



                end



                cursor += 1



                printIndex += spacing



            end



            print line.join()



            xPrinterCount *= 2           



            print "\n"            



        end



    end







    heapify(numbers)



    rightBound = numbers.length() - 1







    while rightBound > 0



        swap(numbers, 0, rightBound)   



        rightBound -= 1



        siftDown(numbers, 0, rightBound)     



    end







    return numbers



end







numbersCount = 14



maximalNumber = 10



numbers = numbersCount.times.map { Random.rand(maximalNumber) }



print numbers



print "\n---\n"







start = Time.now



sortedNumbers = heapsort(numbers)



finish = Time.now



heapSortTime = start - finish







start = Time.now



referenceSortedNumbers = numbers.sort()



finish = Time.now



referenceSortTime = start - finish







print "Reference sort: "



print referenceSortedNumbers



print "\n"



print "Reference sort time: %f\n" % referenceSortTime



print "Heap sort:      "



print sortedNumbers



print "\n"



if DEMO == false



    print "Heap sort time:      %f\n" % heapSortTime



else



    print "Disable DEMO for performance measure\n"



end







if sortedNumbers != referenceSortedNumbers



    puts "Validation failed"



    exit 1



else



    puts "Validation success"



    exit 0



end



Dieser Algorithmus ist ohne Visualisierung nicht leicht zu verstehen, daher empfehle ich als Erstes, eine Funktion zu schreiben, die die aktuelle Ansicht des Binärbaums druckt.

Links

https://gitlab.com/demensdeum/algorithms/-/blob/master/sortAlgorithms/heapsort/heapsort.rb

Quellen

http://rosettacode.org/wiki/Sorting_algorithms/Heapsort
https://www.youtube.com/watch?v=LbB357_RwlY

https://habr.com/ru/company/ otus/blog/460087/

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

https://neerc.ifmo.ru/wiki /index.php?title=Heap_sort

https://wiki5.ru/wiki/Heapsort

https://wiki.c2.com/?HeapSort

https://ru.wikipedia.org/wiki/Tree (Datenstruktur)

https://ru.wikipedia.org/wiki/Heap (Datenstruktur)

https://www.youtube.com/watch?v=2DmK_H7IdTo

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

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

https://www.youtube.com/watch?v =BzQGPA_v-vc

https://www.geeksforgeeks.org/ array-representation-of-binary-heap/

https://habr.com/ru/post/112222/

https://www.cs.usfca. edu/~galles/visualization/BST.html

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

https://medium.com/@dimko1/%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC% D1 %8B-%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8-heapsort-796ba965018b

https://ru.wikibrief.org/wiki/Heapsort

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

Bumblebee All Troubles

Recently, it turned out that users of modern Nvidia GPUs under Arch Linux do not need to use the bumblebee package at all, for example, for me it did not detect an external monitor when connected. I recommend removing the bumblebee package and all related packages, and installing prime using the instructions on the Arch Wiki.
Next, to launch all games on Steam and 3D applications, add prime-run, for Steam this is done like this prime-run %command% in additional launch options.
To check the correctness, you can use glxgears, prime-run glxgears.
https://bbs.archlinux.org/viewtopic.php? pid=2048195#p2048195