Blockierungen in der Praxis ohne Formalin blockieren

Das Blockdiagramm ist ein visuelles Werkzeug, das dazu beiträgt, einen komplexen Algorithmus in eine verständliche und strukturierte Folge von Aktionen zu verwandeln. Von der Programmierung bis zum Geschäftsprozessmanagement dienen sie als universelle Sprache für die Visualisierung, Analyse und Optimierung der komplexesten Systeme.

Stellen Sie sich eine Karte vor, auf der anstelle von Straßen Logik und anstelle von Städten – Aktionen ist. Dies ist ein Blockdiagramm-ein unverzichtbares Werkzeug für die Navigation in den verwirrendsten Prozessen.

Beispiel 1: vereinfachtes Spielstartschema
Um das Arbeitsprinzip zu verstehen, präsentieren wir ein einfaches Spielstartschema.

Dieses Schema zeigt das perfekte Skript, wenn alles ohne Fehler passiert. Aber im wirklichen Leben ist alles viel komplizierter.

Beispiel 2: Erweitertes Schema zum Starten des Spiels mit Datenladen
Moderne Spiele erfordern häufig eine Internetverbindung, um Benutzerdaten, Speichern oder Einstellungen herunterzuladen. Fügen wir diese Schritte zu unserem Schema hinzu.

Dieses Schema ist bereits realistischer, aber was wird passieren, wenn etwas schief geht?

Wie war es: Ein Spiel, das mit dem Verlust des Internets “brach”

“brach”

Zu Beginn des Projekts konnten Entwickler nicht alle möglichen Szenarien berücksichtigen. Zum Beispiel konzentrierten sie sich auf die Hauptlogik des Spiels und überlegten nicht, was passieren würde, wenn der Spieler eine Internetverbindung hat.

In einer solchen Situation würde das Blockdiagramm ihres Codes so aussehen:

In diesem Fall hat das Spiel in der Phase des Wartens auf Daten, die sie aufgrund des Fehlens einer Verbindung nicht erhielt, anstatt einen Fehler oder korrekt zu schließen. Dies führte zum “schwarzen Bildschirm” und zum Einfrieren der Anwendung.

Wie es wurde: Korrektur bei Benutzerbeschwerden

Nach zahlreichen Beschwerden der Benutzer über das Schwebewesen erkannte das Entwicklerteam, dass wir den Fehler korrigieren mussten. Sie nahmen Änderungen am Code vor, indem sie eine Fehlerverarbeitungseinheit hinzufügen, mit der die Anwendung auf den mangelnden Verbindungsmangel korrekt reagiert.

So sieht das korrigierte Blockdiagramm aus, in dem beide Szenarien berücksichtigt werden:

Dank dieses Ansatzes informiert das Spiel jetzt den Benutzer über das Problem und kann in einigen Fällen sogar in den Offline -Modus gehen, sodass Sie das Spiel fortsetzen können. Dies ist ein gutes Beispiel dafür, warum Blockdiagramme so wichtig sind.

Unsicheres Verhalten

Das Hängen und Fehler sind nur ein Beispiel für unvorhersehbare Verhalten des Programms. Bei der Programmierung gibt es ein Konzept von unsicherem Verhalten (undefiniertes Verhalten) – Dies ist eine Situation, in der der Standard der Sprache nicht beschreibt, wie sich das Programm in einem bestimmten Fall verhalten sollte.

Dies kann zu allem führen: vom zufälligen „Müll“ beim Rückzug zum Versagen des Programms oder sogar der schwerwiegenden Sicherheitsanfälligkeit. Unbestimmte Verhalten tritt häufig bei der Arbeit mit dem Gedächtnis auf, zum Beispiel mit Zeilen in der Sprache von C.

Ein Beispiel aus der Sprache c:

Stellen Sie sich vor, der Entwickler hat die Linie in den Puffer kopiert, aber vergessen, das Ende des Null -Symbols (\ 0`) zu addieren, das das Ende der Linie markiert.

So sieht der Code aus:

#include 

int main() {
char buffer[5];
char* my_string = "hello";

memcpy(buffer, my_string, 5);

printf("%s\n", buffer);
return 0;
}

Erwartete Ergebnis: “Hallo”
Das wirkliche Ergebnis ist unvorhersehbar.

Warum passiert das? Die Funktion “printf`) mit dem Spezifizierer%S` erwartet, dass die Linie mit einem Nullsymbol endet. Wenn er nicht ist, wird sie die Erinnerung außerhalb des hervorgehobenen Puffers weiter lesen.

Hier ist das Blockdiagramm dieses Prozesses mit zwei möglichen Ergebnissen:

Dies ist ein klares Beispiel dafür, warum die Blockdiagramme so wichtig sind: Sie lassen den Entwickler nicht nur über die ideale Ausführungsweise nachdenken, sondern auch über alle möglichen Fehler, einschließlich solcher Probleme mit niedrigem Niveau, was das Endprodukt viel stabiler und zuverlässiger macht.

Pixel perfekt: Mythos oder Realität in der Ära der Deklarativität?

In der Welt der Schnittstellenentwicklung gibt es ein gemeinsames Konzept – “Pixel perfekt in der Lodge” . Es impliziert die genaueste Reproduktion der Konstruktionsmaschine auf das kleinste Pixel. Lange Zeit war es ein Goldstandard, insbesondere in der Ära eines klassischen Webdesigns. Mit der Ankunft der deklarativen Meile und dem schnellen Wachstum der Vielfalt der Geräte wird das Prinzip von “Pixel Perfect” jedoch zunehmend kurzlebiger. Versuchen wir herauszufinden, warum.

Imperial Wysiwyg vs. Deklarativer Code: Was ist der Unterschied?

Traditionell wurden viele Schnittstellen, insbesondere der Desktop, mit imperativen Ansätzen oder wysiwyg (was Sie sehen, was Sie erhalten) von Herausgebern erstellt. In solchen Tools manipuliert der Designer oder Entwickler direkt mit Elementen und platziert sie auf Leinwand mit Genauigkeit zum Pixel. Es ähnelt der Arbeit mit einem Grafikeditor – Sie sehen, wie Ihr Element aussieht, und Sie können es definitiv positionieren. In diesem Fall war die Leistung von “Pixel Perfect” ein sehr reales Ziel.

Die moderne Entwicklung basiert jedoch zunehmend auf deklarativen Meilen . Dies bedeutet, dass Sie dem Computer nicht sagen, dass er “diese Schaltfläche hier einstellen”, sondern beschreiben, was Sie bekommen möchten. Anstatt die spezifischen Koordinaten des Elements anzuzeigen, beschreiben Sie beispielsweise seine Eigenschaften: “Diese Schaltfläche sollte rot sein, 16px -Eindringlinge von allen Seiten haben und in der Mitte des Behälters stehen.” Freiimvorki wie React, Vue, Swiftui oder Jetpack komponieren nur dieses Prinzip.

Warum “Pixel Perfect” für viele Geräte nicht mit einer deklarativen Meile funktioniert

Stellen Sie sich vor, Sie erstellen eine Anwendung, die auf dem iPhone 15 Pro Max, Samsung Galaxy Fold, iPad Pro und einer 4K -Auflösung gleich gut aussehen soll. Jedes dieser Geräte hat eine unterschiedliche Bildschirmauflösung, Pixeldichte, Parteien und physische Größen.

Wenn Sie den deklarativen Ansatz verwenden, entscheidet das System selbst unter Berücksichtigung aller Parameter, wie Sie Ihre beschriebene Schnittstelle auf einem bestimmten Gerät anzeigen. Sie setzen die Regeln und Abhängigkeiten, nicht die harten Koordinaten.

* Anpassungsfähigkeit und Reaktionsfähigkeit: Das Hauptziel der deklarativen Meilen ist es, adaptive und reaktionsschnelle Schnittstellen zu erstellen. Dies bedeutet, dass sich Ihre Schnittstelle automatisch an die Größe und Ausrichtung des Bildschirms anpassen sollte, ohne die Lesbarkeit zu brechen und aufrechtzuerhalten. Wenn wir auf jedem Gerät „Pixel Perfect“ beantragen möchten, müssten wir unzählige Optionen für dieselbe Schnittstelle erstellen, was die Vorteile des deklarativen Ansatzes vollständig ausgleichen wird.
* Pixeldichte (DPI/PPI): Die Geräte haben eine unterschiedliche Pixeldichte. Das gleiche Element mit der Größe von 100 “virtuellen” Pixeln auf einem Gerät mit hoher Dichte sieht viel kleiner aus als bei einem Gerät mit niedrigem Dichte, wenn Sie die Skalierung nicht berücksichtigen. Deklarative Rahmenbedingungen werden von physischen Pixeln abstrahiert und arbeiten mit logischen Einheiten.
* Dynamischer Inhalt: Inhalt in modernen Anwendungen ist häufig dynamisch – sein Volumen und die Struktur können variieren. Wenn wir hart auf die Pixel gerissen würden, würde jede Änderung des Textes oder des Bildes zum “Zusammenbruch” des Layouts führen.
* Verschiedene Plattformen: Zusätzlich zur Vielfalt der Geräte gibt es verschiedene Betriebssysteme (iOS, Android, Web, Desktop). Jede Plattform verfügt über eigene Design, Standardsteuerungen und Schriftarten. Ein Versuch, auf allen Plattformen eine absolut identische Pixel -Perfect -Schnittstelle zu erstellen, würde zu einem unnatürlichen Typ und einer schlechten Benutzererfahrung führen.

Die alten Ansätze gingen nicht verschwunden, sondern entwickelten

Es ist wichtig zu verstehen, dass der Ansatz zu Schnittstellen keine binäre Wahl zwischen “imperativ” und “deklarativ” ist. Historisch gesehen gab es für jede Plattform ihre eigenen Werkzeuge und Ansätze zur Erstellung von Schnittstellen.

* Native Schnittstellendateien: Für iOS waren es XIB/Storyboards für Android-XML-Markierungsdateien. Diese Dateien sind ein Pixel-Perfekt-Wysiwyg-Layout, das dann wie im Editor im Radio angezeigt wird. Dieser Ansatz ist nirgendwo verschwunden und entwickelt sich weiter und integriert sich in moderne deklarative Rahmen. Zum Beispiel haben Swiftui in Apple und Jetpack in Android auf dem Pfad eines rein deklarativen Code eingeschaltet, behielten gleichzeitig die Möglichkeit, ein klassisches Layout zu verwenden.
* Hybridlösungen: häufig in realen Projekten wird eine Kombination von Ansätzen verwendet. Beispielsweise kann die Grundstruktur der Anwendung deklarativ implementiert werden und für spezifische, die eine genaue Positionierung von Elementen, niedrigere Level, imperative Methoden erfordern, oder native Komponenten, die unter Berücksichtigung der Einzelheiten der Plattform entwickelt wurden.

vom Monolith zur Anpassungsfähigkeit: Wie die Entwicklung von Geräten eine deklarative Meile bildete

Die Welt der digitalen Schnittstellen hat sich in den letzten Jahrzehnten enorme Veränderungen verändert. Von stationären Computern mit festen Genehmigungen kamen wir in die Ära des exponentiellen Wachstums der Vielfalt der Benutzergeräte . Heute sollten unsere Bewerbungen gleich gut funktionieren:

* Smartphones aller Formfaktoren und Bildschirmgrößen.
* Tablets mit ihren einzigartigen Ausrichtungsmodi und einem getrennten Bildschirm.
* Laptops und Desktops mit verschiedenen Monitorengenehmigungen.
* TVS- und Medienzentren , kontrolliert remote. Es ist bemerkenswert, dass selbst für Fernseher, deren Bemerkungen mit einem Minimum an Schaltflächen, oder umgekehrt, mit vielen Funktionen überladen sind, selbst für die Schnittstellen so einfach sind, dass der Code keine spezifische Anpassung für diese Eingabefunktionen benötigt. Die Schnittstelle sollte “als ob für sich selbst” funktionieren, ohne eine zusätzliche Beschreibung des “Wie” mit einer bestimmten Fernbedienung zu interagieren.
* Smart Watches und Wearable -Geräte mit minimalistischen Bildschirmen.
* Virtual Reality Helme (VR) , die einen völlig neuen Ansatz für eine räumliche Schnittstelle erfordern.
* Augmented Reality Device Devices (AR) , die Informationen über die reale Welt anwenden.
* Automobilinformationen und Unterhaltungssysteme .
* Und sogar Haushaltsgeräte : Von Kühlschränken mit sensorischen Bildschirmen und Waschmaschinen mit interaktiven Displays bis hin zu intelligenten Öfen und Systemen des Smart House.

Jedes dieser Geräte verfügt über eigene Merkmale: physikalische Dimensionen, Parteienverhältnisse, Pixeldichte, Eingabemethoden (Touchscreen, Maus, Controller, Gesten, Stimmbefehle) und vor allem die Feinheiten der Benutzerumgebung . Beispielsweise erfordert ein VR Shlesh ein tiefes Eintauchen und eine Smartphone-schnelle und intuitive Arbeit unterwegs, während die Kühlschrankschnittstelle für schnelle Navigation genauso einfach und groß sein sollte.

Klassischer Ansatz: Die Belastung für die Unterstützung einzelner Schnittstellen

In der Zeit der Dominanz von Desktops und der ersten mobilen Geräte war das übliche Geschäft die Erstellung und Unterstützung von von einzelnen Schnittstellendateien oder sogar eines vollständig separaten Schnittstellencodes für jede Plattform .

* Die Entwicklung unter iOS erforderte häufig die Verwendung von Storyboards oder XIB-Dateien in Xcode, das Code auf Objective-C oder Swift schreibt.
* Für Android wurden die XML -Markierungsdateien und der Code auf Java oder Kotlin erstellt.
* Web -Schnittstellen haben HTML/CSS/JavaScript eingeschaltet.
* Für Anwendungen c ++ auf verschiedenen Desktop -Plattformen wurden ihre spezifischen Frameworks und Tools verwendet:
* In Windows waren dies MFC (Microsoft Foundation Classes), Win32 -API mit manuellen Zeichnungselementen oder Verwendung von Ressourcendateien für Dialogfenster und Steuerelemente.
* Kakao (Objektiv-C/Swift) oder die alte Kohlenstoff-API zur direkten Kontrolle der grafischen Grenzfläche wurden in macOS verwendet.
* In Linux/UNIX-ähnlichen Systemen wurden häufig Bibliotheken wie GTK+ oder QT verwendet, die ihre Widgets und Mechanismen zum Erstellen von Schnittstellen bereitstellten, häufig über XML-ähnliche Markierungsdateien (z.

Dieser Ansatz sorgte für eine maximale Kontrolle über jede Plattform, sodass Sie alle spezifischen Funktionen und nativen Elemente berücksichtigen können. Er hatte jedoch einen großen Nachteil: Duplizierung von Bemühungen und enorme Kosten für Unterstützung . Die geringste Änderung des Designs oder der Funktionalität erforderte die Einführung eines Rechts auf mehrere unabhängige Codebasen. Dies wurde zu einem echten Albtraum für Entwicklerteams, verlangsamte die Ausgabe neuer Funktionen und erhöhte die Wahrscheinlichkeit von Fehlern.

deklarative Meilen: Eine einzelne Sprache für Vielfalt

Als Reaktion auf diese schnelle Komplikation erschien die deklarativen Meilen als dominantes Paradigma. Framws wie React, Vue, Swiftui, Jetpack Compose und andere sind nicht nur eine neue Art, Code zu schreiben, sondern eine grundlegende Verschiebung des Denkens.

Die Hauptidee des deklarativen Ansatzes : Anstatt das System zu sagen, wie man jedes Element zeichnet (imperativ), beschreiben wir „was“ wir sehen wollen (deklarativ). Wir setzen die Eigenschaften und den Zustand der Schnittstelle, und das Framework entscheidet, wie Sie sie am besten auf einem bestimmten Gerät anzeigen können.

Dies wurde dank der folgenden wichtigen Vorteile möglich:

1. Abstraktion aus den Details der Plattform: deklarative Fraimvorki sind speziell so konzipiert, dass sie die Details mit niedrigem Level für jede Plattform vergessen. Der Entwickler beschreibt die Komponenten und ihre Beziehungen auf einer höheren Abstraktionsebene unter Verwendung eines einzelnen, übertragenen Codes.
2. Automatische Anpassung und Reaktionsfähigkeit: Freiimvorki Übernehmen Sie die Verantwortung für die automatische Skalierung , ändern Sie das Layout und die Anpassung von Elementen in verschiedene Größen von Bildschirmen, Pixeldichte und Eingabemethoden. Dies wird durch die Verwendung flexibler Layoutsysteme wie Flexbox oder Grid und Konzepte erreicht, die “logischen Pixeln” oder “DP” ähneln.
3.. Dies vereinfacht den Testprozess und bietet vorhersehbarere Benutzererfahrung.
V. Die Teams können sich auf Funktionalität und Design konzentrieren und nicht auf wiederholte Umschreiben derselben Schnittstelle.
5. Freiimvorki kann aktualisiert werden, um neue Technologien zu unterstützen, und Ihr bereits geschriebener Code erhält. Diese Unterstützung ist relativ nahtlos.

Schlussfolgerung

Die deklarative Meile ist nicht nur ein Modetrend, sondern auch die notwendige evolutionäre Stufe , die durch die schnelle Entwicklung von Benutzergeräten verursacht wird, einschließlich der Sphäre des Internet der Dinge (IoT) und intelligente Haushaltsgeräte. Es ermöglicht Entwicklern und Designern, komplexe, adaptive und gleichmäßige Schnittstellen zu erstellen, ohne in endlosen spezifischen Implementierungen für jede Plattform zu ertrinken. Der Übergang von der imperativen Kontrolle über jedes Pixel zur deklarativen Beschreibung des gewünschten Zustands ist eine Erkenntnis, dass in der Welt der zukünftigen Schnittstellen flexibel, übertragen und intuitiv unabhängig davon, welcher Bildschirm angezeigt wird.

Programmierer, Designer und Benutzer müssen lernen, wie man in dieser neuen Welt lebt. Die zusätzlichen Details des Pixel Perfect, das auf ein bestimmtes Gerät oder eine bestimmte Auflösung ausgelegt ist, führen zu unnötigen Zeitkosten für die Entwicklung und Unterstützung. Darüber hinaus funktionieren solche harten Layouts möglicherweise nicht auf Geräten mit nicht standardmäßigen Schnittstellen wie begrenzten Eingangsfernsehern, VR- und AR-Verschiebungen sowie anderen Geräten der Zukunft, von denen wir heute noch nicht einmal wissen. Flexibilität und Anpassungsfähigkeit – Dies sind die Schlüssel zur Schaffung erfolgreicher Schnittstellen in der modernen Welt.

Vibe-Core-Tricks: Warum LLM immer noch nicht mit festem, trockenem und sauberem funktioniert

Mit der Entwicklung von großsprachigen Modellen (LLM) wie Chatgpt verwenden immer mehr Entwickler sie, um Code, Designarchitektur und Beschleunigung der Integration zu generieren. Bei der praktischen Anwendung wird jedoch spürbar: Die klassischen Prinzipien der Architektur – solide, trocken, sauber – verstehen Sie die Besonderheiten der LLM -Codgendation schlecht.

Dies bedeutet nicht, dass die Prinzipien veraltet sind – im Gegenteil, sie arbeiten perfekt zur manuellen Entwicklung. Aber mit LLM muss der Ansatz angepasst werden.

Warum LLM nicht mit architektonischen Prinzipien fertig werden kann

Kapselung

Die Zusammenfassung erfordert das Verständnis der Grenzen zwischen Teilen des Systems, Kenntnissen über die Absichten des Entwicklers sowie den strengen Zugriffsbeschränkungen. LLM vereinfacht häufig die Struktur, macht Felder ohne Grund öffentlich oder dupliziert die Implementierung. Dies macht den Code anfälliger für Fehler und verstößt gegen die architektonischen Grenzen.

Abstracts und Schnittstellen

Entwurfsmuster wie eine abstrakte Fabrik oder Strategie erfordern eine ganzheitliche Sicht des Systems und das Verständnis seiner Dynamik. Modelle können eine Schnittstelle ohne klaren Zweck erstellen, ohne die Implementierung zu gewährleisten, oder gegen die Verbindung zwischen Schichten verstoßen. Das Ergebnis ist eine überschüssige oder nicht funktionsfähige Architektur.

trocken (Donolt wiederholt sich)

LLM bemüht sich nicht, den sich wiederholenden Code zu minimieren – im Gegenteil, es ist für sie einfacher, Blöcke zu duplizieren, als allgemeine Logik zu erstellen. Obwohl sie auf Anfrage auf Anfrage eingerichtet werden können, neigen Modelle neigen dazu, „selbstfassende“ Fragmente zu erzeugen, auch wenn dies zu Redundanz führt.

saubere Architektur

Clean impliziert eine strenge Hierarchie, Unabhängigkeit von Frameworks, gerichtete Abhängigkeit und minimale Verbindung zwischen Schichten. Die Erzeugung einer solchen Struktur erfordert ein globales Verständnis des Systems – und LLM arbeiten auf der Ebene der Wahrscheinlichkeit von Wörtern, nicht auf architektonischer Integrität. Daher ist der Code unter Verstoß gegen die Anweisungen der Abhängigkeit und eine vereinfachte Aufteilung in Ebenen gemischt.

Was funktioniert besser bei der Arbeit mit LLM

Nass statt trocken
Der nasses (zweimal alles schreiben) ist praktischer bei der Arbeit mit LLM. Die Duplikation von Code erfordert keinen Kontext aus dem Modell der Aufbewahrung, was bedeutet, dass das Ergebnis vorhersehbar ist und einfacher zu korrekt ist. Es reduziert auch die Wahrscheinlichkeit von nicht offenen Verbindungen und Fehler.

Darüber hinaus hilft die Duplikation, den kurzen Speicher des Modells zu kompensieren: Wenn ein bestimmtes Logikfragment an mehreren Stellen gefunden wird, berücksichtigt LLM es mit größerer Wahrscheinlichkeit mit weiterer Generation. Dies vereinfacht die Begleitung und erhöht den Widerstand gegen “Vergessen”.

einfache Strukturen anstelle von Kapselung

Vermeiden Sie eine komplexe Kapselung und stützen sich auf die direkte Übertragung von Daten zwischen den Teilen des Codes und können sowohl die Generation als auch das Debuggen erheblich vereinfachen. Dies gilt insbesondere für eine schnelle iterative Entwicklung oder Schaffung von MVP.

vereinfachte Architektur

Eine einfache, flache Struktur des Projekts mit einer minimalen Menge an Abhängigkeiten und Abstraktionen liefert während der Erzeugung ein stabileres Ergebnis. Das Modell passt einen solchen Code leichter an und verletzt die erwarteten Verbindungen zwischen den Komponenten.

SDK -Integration – Manuell zuverlässig


Die meisten Sprachmodelle werden auf veralteten Dokumentationsversionen geschult. Bei der Erstellung von Anweisungen zur Installation von SDK werden daher häufig Fehler angezeigt: veraltete Befehle, irrelevante Parameter oder Links zu unzugänglichen Ressourcen. Praxis zeigt: Es ist am besten, offizielle Dokumentation und manuelle Abstimmung zu verwenden und LLM eine Hilfsrolle zu hinterlassen – beispielsweise eine Vorlagencode oder eine Anpassung von Konfigurationen zu generieren.

Warum funktionieren die Prinzipien noch – aber mit manueller Entwicklung

Es ist wichtig zu verstehen, dass die Schwierigkeiten von festem, trockenem und sauberem Zusammenhang die Codhegeneration durch LLM betreffen. Wenn der Entwickler den Code manuell schreibt, demonstrieren diese Prinzipien weiterhin seinen Wert: Sie reduzieren die Verbundenheit, vereinfachen die Unterstützung und erhöhen die Lesbarkeit und Flexibilität des Projekts.

Dies liegt an der Tatsache, dass menschliches Denken anfällig für die Verallgemeinerung ist. Wir suchen nach Mustern, wir bringen wiederholte Logik in einzelne Entitäten und erstellen Muster. Wahrscheinlich hat dieses Verhalten evolutionäre Wurzeln: Reduzierung der Anzahl der Informationen spart kognitive Ressourcen.

LLM handelt anders: Sie erleben keine Lasten aus dem Datenvolumen und streben nicht nach Einsparungen. Im Gegenteil, es ist für sie einfacher, mit doppelten, fragmentierten Informationen zu arbeiten, als komplexe Abstraktionen aufzubauen und aufrechtzuerhalten. Aus diesem Grund ist es für sie einfacher, mit dem Code ohne Kapselung fertig zu werden, mit wiederholenden Strukturen und minimaler architektonischer Schwere.

Schlussfolgerung

Großsprachenmodelle sind ein nützliches Instrument in der Entwicklung, insbesondere in den frühen Stadien oder beim Erstellen eines Hilfscode. Es ist jedoch wichtig, den Ansatz an sie anzupassen: die Architektur zu vereinfachen, die Abstraktion zu begrenzen, komplexe Abhängigkeiten zu vermeiden und bei der Konfiguration von SDK nicht auf sie zu verlassen.

Die Prinzipien von fester, trocken und sauber sind immer noch relevant, aber sie haben den besten Effekt in den Händen einer Person. Bei der Arbeit mit LLM ist es vernünftig, einen vereinfachten, praktischen Stil zu verwenden, der es Ihnen ermöglicht, einen zuverlässigen und verständlichen Code zu erhalten, der leicht manuell abschließen kann. Und wo LLM vergisst – die Duplizierung von Code hilft ihm, sich zu erinnern.

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

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.

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