Im Jahr 1936 beschrieb der Wissenschaftler Alan Turing in seiner Veröffentlichung „On Computable Numbers, With An Application to Entscheidungsproblem“ den Einsatz einer universellen Rechenmaschine, die dem Problem der Lösbarkeit in der Mathematik ein Ende setzen könnte. Daraus kommt er zu dem Schluss, dass eine solche Maschine nichts richtig lösen könnte, wenn das Ergebnis ihrer Arbeit invertiert und auf sich selbst zurückgeführt würde. Es stellt sich heraus, dass es unmöglich ist, ein *ideales* Antivirenprogramm, einen *idealen* Kachelsetzer, ein Programm, das ideale Phrasen für Ihren Absturz vorschlägt usw. zu erstellen. Paradox!
Diese universelle Rechenmaschine kann jedoch zur Implementierung jedes beliebigen Algorithmus verwendet werden, den sich der britische Geheimdienst zunutze machte, indem er Turing engagierte und die Entwicklung einer „Bombe“-Maschine zur Entschlüsselung deutscher Nachrichten während des Zweiten Weltkriegs ermöglichte.
Das Folgende ist eine OOP-Modellierung eines Einzelbandcomputers in der Dart-Sprache, basierend auf dem Originaldokument.
Eine Turingmaschine besteht aus einem in Abschnitte unterteilten Film, jeder Abschnitt enthält ein Symbol, die Symbole können gelesen oder geschrieben werden. Beispiel einer Filmklasse:
final _map = Map<int, String>();
String read({required int at}) {
return _map[at] ?? "";
}
void write({required String symbol, required int at}) {
_map[at] = symbol;
}
}
Es gibt auch ein „Scan-Quadrat“, es kann sich über den Film bewegen, Informationen lesen oder schreiben, in moderner Sprache – Magnetkopf. Beispiel einer Magnetkopfklasse:
int _index = 0;
InfiniteTape _infiniteTape;
TapeHead(this._infiniteTape) {}
String next() {
_index += 1;
move(to: _index);
final output = read();
return output;
}
String previous() {
_index -= 1;
move(to: _index);
final output = read();
return output;
}
void move({required int to}) {
this._index = to;
}
String read() {
return _infiniteTape.read(at: this._index);
}
void write(String symbol) {
_infiniteTape.write(symbol: symbol, at: this._index);
}
int index() {
return _index;
}
}
Die Maschine enthält „M-Konfigurationen“, anhand derer sie entscheiden kann, was als nächstes zu tun ist. In moderner Sprache – Staaten und Zustandsverwalter. Beispiel für einen Statushandler:
FiniteStateControlDelegate? delegate = null;
void handle({required String symbol}) {
if (symbol == OPCODE_PRINT) {
final argument = delegate?.nextSymbol();
print(argument);
}
else if (symbol == OPCODE_GENERATE_RANDOM_NUMBER_FROM_ZERO_TO_AND_WRITE_AFTER) {
final to = int.tryParse(delegate!.nextSymbol())!;
final value = new Random().nextInt(to);
delegate!.nextSymbol();
delegate!.write(value.toString());
}
else if (symbol == OPCODE_INPUT_TO_NEXT) {
final input = stdin.readLineSync()!;
delegate?.nextSymbol();
delegate?.write(input);
}
else if (symbol == OPCODE_COPY_FROM_TO) {
final currentIndex = delegate!.index();
и т.д.
Danach müssen Sie „Konfigurationen“ erstellen. In der modernen Sprache sind dies Operationscodes (Opcodes) und ihre Handler. Beispiel-Opcodes:
const OPCODE_PRINT = "print";
const OPCODE_INCREMENT_NEXT = "increment next";
const OPCODE_DECREMENT_NEXT = "decrement next";
const OPCODE_IF_PREVIOUS_NOT_EQUAL = "if previous not equal";
const OPCODE_MOVE_TO_INDEX = "move to index";
const OPCODE_COPY_FROM_TO = "copy from index to index";
const OPCODE_INPUT_TO_NEXT = "input to next";
const OPCODE_GENERATE_RANDOM_NUMBER_FROM_ZERO_TO_AND_WRITE_AFTER = "generate random number from zero to next and write after";
Vergessen Sie nicht, einen Opcode und einen Stop-Handler zu erstellen, sonst können Sie das Auflösungsproblem nicht beweisen oder nicht beweisen (sic!).
Jetzt verbinden wir mithilfe des „Mediator“-Musters alle Klassen in der Turing-Maschine-Klasse, erstellen eine Instanz der Klasse, zeichnen das Programm mit einem Tonbandgerät auf, laden das Band und Sie können es verwenden!
Für mich persönlich blieb die Frage, was primär war, interessant – Schaffung eines Universalrechners bzw. Beweis des „Entscheidungsproblems“, wodurch als Nebenprodukt ein Rechner entstand.
Kassetten
Zum Spaß habe ich mehrere Kassettenprogramme für meine Version der Maschine aufgenommen.
Hallo Welt
hello world
stop
Считаем до 16-ти
0
if previous not equal
16
copy from index to index
1
8
print
?
move to index
0
else
copy from index to index
1
16
print
?
print
Finished!
stop
Самой интересной задачей было написание Quine программы, которая печатает свой исходный код, для одноленточной машины. Первые 8 часов мне казалось что эта задача не решаема с таким малым количеством опкодов, однако всего через 16 часов оказалось что я был не прав.
Реализация и примеры кассет, источники ниже.
Ссылки
https://gitlab.com/demensdeum/turing-machine
Источники
https://www.astro.puc.cl/~rparra/tools/PAPERS/turing_1936.pdf
https://kpolyakov.spb.ru/prog/turing.htm
https://www.youtube.com/watch?v=dNRDvLACg5Q
https://www.youtube.com/watch?v=jP3ceURvIYc
https://www.youtube.com/watch?v=9QCJj5QzETI
https://www.youtube.com/watch?v=HeQX2HjkcNo&t=0s
Schreiben in Assembly für Sega Genesis #5
In dieser Notiz beschreibe ich den Prozess des Lesens des Joysticks, des Änderns der Position des Sprites, des horizontalen Umdrehens, des Sega Genesis-Emulators und möglicherweise der Konsole selbst.
Das Lesen von Klicks und die Verarbeitung von „Ereignissen“ eines Shogi-Joysticks erfolgt nach folgendem Schema:
- Anfrage nach einer Kombination von Bits gedrückter Tasten
- Teile gedrückter Tasten lesen
- Verarbeitung auf der Ebene der Spiellogik
Um das Skelett-Sprite zu verschieben, müssen wir Variablen der aktuellen Position speichern.
RAM
Spiellogikvariablen werden im RAM gespeichert; bisher ist noch nichts Besseres erfunden worden. Lassen Sie uns Variablenadressen deklarieren und den Rendering-Code ändern:
skeletonYpos = $FF0002
frameCounter = $FF0004
skeletonHorizontalFlip = $FF0006
move.w #$0100,skeletonXpos
move.w #$0100,skeletonYpos
move.w #$0001,skeletonHorizontalFlip
FillSpriteTable:
move.l #$70000003,vdp_control_port
move.w skeletonYpos,vdp_data_port
move.w #$0F00,vdp_data_port
move.w skeletonHorizontalFlip,vdp_data_port
move.w skeletonXpos,vdp_data_port
Wie Sie sehen, beginnt die für die Arbeit verfügbare Adresse bei 0xFF0000 und endet bei 0xFFFFFF, insgesamt stehen uns 64 KB Speicher zur Verfügung. Skeleton-Positionen werden bei SkeletonXpos, SkeletonYpos, horizontale Drehung bei SkeletonHorizontalFlip deklariert.
Joypad
In Analogie zu VDP erfolgt die Arbeit mit Joypads über zwei separate Ports – Steuerport und Datenport, für den ersten 0xA10009 und 0xA10003 Co-Nr. Bei der Arbeit mit einem Joypad gibt es eine interessante Funktion: Zuerst müssen Sie eine Tastenkombination für die Abfrage anfordern und dann, nachdem Sie auf ein Update am Bus gewartet haben, die erforderlichen Tastendrücke lesen. Für die C/B- und D-Pad-Tasten ist dies 0x40, Beispiel unten:
move.b #$40,joypad_one_control_port; C/B/Dpad
nop ; bus sync
nop ; bus sync
move.b joypad_one_data_port,d2
rts
Im Register d2 bleibt der Zustand der gedrückten oder nicht gedrückten Tasten erhalten, im Allgemeinen bleibt das, was über den Datumsport angefordert wurde, erhalten. Gehen Sie anschließend zum Motorola 68000-Register-Viewer Ihres Lieblingsemulators und sehen Sie, was das d2-Register je nach Tastenanschlägen entspricht. Auf clevere Weise können Sie es im Handbuch herausfinden, aber wir verlassen uns nicht auf ihr Wort. Weiterverarbeitung gedrückter Tasten im d2
-Register
cmp #$FFFFFF7B,d2; handle left
beq MoveLeft
cmp #$FFFFFF77,d2; handle right
beq MoveRight
cmp #$FFFFFF7E,d2; handle up
beq MoveUp
cmp #$FFFFFF7D,d2; handle down
beq MoveDown
rts
Проверять нужно конечно отдельные биты, а не целыми словами, но пока и так сойдет. Теперь осталось самое простое – написать обработчики всех событий перемещения по 4-м направлениям. Для этого меняем переменные в RAM, и запускаем процедуру перерисовки.
Пример для перемещения влево + изменение горизонтального флипа:
move.w skeletonXpos,d0
sub.w #1,d0
move.w d0,skeletonXpos
move.w #$0801,skeletonHorizontalFlip
jmp FillSpriteTable
После добавления всех обработчиков и сборки, вы увидите как скелет перемещается и поворачивается по экрану, но слишком быстро, быстрее самого ежа Соника.
Не так быстро!
Чтобы замедлить скорость игрового цикла, существуют несколько техник, я выбрал самую простую и не затрагивающую работу с внешними портами – подсчет цифры через регистр пока она не станет равна нулю.
Пример замедляющего цикла и игрового цикла:
move.w #512,frameCounter
WaitFrame:
move.w frameCounter,d0
sub.w #1,d0
move.w d0,frameCounter
dbra d0,WaitFrame
GameLoop:
jsr ReadJoypad
jsr HandleJoypad
jmp GameLoop
Danach läuft das Skelett langsamer, was erforderlich war. Wie ich weiß, ist die häufigste Option zum „Verlangsamen“ das Zählen der vertikalen Synchronisierungsflagge. Sie können zählen, wie oft der Bildschirm gezeichnet wurde, und sind somit an eine bestimmte Anzahl an Bildern pro Sekunde gebunden.
Links
https://gitlab .com/demensdeum/segagenesisamples/-/blob/main/8Joypad/vasm/main.asm
Quellen
https://www.chibiakumas.com/68000/platform2.php
https://huguesjohnson.com/programming/genesis/tiles-sprites/
Schreiben in Assembly für Sega Genesis #4
In diesem Beitrag beschreibe ich, wie man Sprites mit dem VDP-Emulator der Sega Genesis-Konsole zeichnet.
Der Prozess des Renderns von Sprites ist dem Rendern von Kacheln sehr ähnlich:
- Farben in CRAM laden
- Teile der Sprites 8×8 in VRAM hochladen
- Sprite-Tabelle im VRAM füllen
Nehmen wir zum Beispiel ein Sprite eines Skeletts mit einem Schwert mit 32×32 Pixeln![]()
Skeleton Guy [Animated] by Disthorn
CRAM
Mit ImaGenesis konvertieren wir es in CRAM-Farben und VRAM-Muster für Assembler. Danach erhalten wir zwei Dateien im ASM-Format, dann schreiben wir die Farben auf die Wortgröße um und die Kacheln müssen zum Zeichnen in der richtigen Reihenfolge platziert werden.
Interessante Informationen: Sie können die automatische VDP-Inkrementierung über das 0xF-Register auf die Wortgröße umstellen. Dadurch wird die Adresserhöhung aus dem CRAM-Farbfüllcode entfernt.
VRAM
Das Shogi-Handbuch enthält die korrekte Reihenfolge der Kacheln für große Sprites, aber wir sind schlauer, also übernehmen wir die Indizes aus dem Blog ChibiAkumas, beginnen wir mit dem Zählen ab Index 0:
0 4 8 12
1 5 9 13
2 6 10 14
3 7 11 15
Warum steht alles auf dem Kopf? Was willst du, die Konsole ist japanisch! Es könnte sogar von rechts nach links sein!
Lassen Sie uns die Reihenfolge in der ASM-Sprite-Datei manuell ändern:
dc.l $11111111 ; Tile #0
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111 ; Tile #4
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111 ; Tile #8
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111122
dc.l $11111122
dc.l $11111166
dc.l $11111166 ; Tile #12
dc.l $11111166
dc.l $11111166
и т.д.
Laden Sie das Sprite wie normale Kacheln/Muster:
lea Sprite,a0
move.l #$40200000,vdp_control_port; write to VRAM command
move.w #128,d0 ; (16*8 rows of sprite) counter
SpriteVRAMLoop:
move.l (a0)+,vdp_data_port;
dbra d0,SpriteVRAMLoop
Um ein Sprite zu zeichnen, müssen Sie nur noch die Sprite-Tabelle ausfüllen
Sprite-Tabelle
Die Sprite-Tabelle wird im VRAM gefüllt, die Adresse ihres Standorts wird in das VDP-Register 0x05 eingetragen, die Adresse ist wieder knifflig, man kann es im Handbuch sehen, ein Beispiel für Adresse F000:
Ок, теперь запишем наш спрайт в таблицу. Для этого нужно заполнить “структуру” данных состоящую из четырех word. Бинарное описание структуры вы можете найти в мануале. Лично я сделал проще, таблицу спрайтов можно редактировать вручную в эмуляторе Exodus.![]()
Die Strukturparameter sind aus dem Namen ersichtlich, zum Beispiel XPos, YPos – Koordinaten, Kacheln – Nummer der Startkachel zum Zeichnen, HSize, VSize – Sprite-Größen durch Hinzufügen der Teile 8×8, HFlip, VFlip – Hardware-Rotation des Sprites horizontal und vertikal.![]()
Es ist sehr wichtig, sich daran zu erinnern, dass Sprites außerhalb des Bildschirms sein können. Dies ist das richtige Verhalten, weil… Off-Screen-Sprites aus dem Speicher entladen – eine ziemlich ressourcenintensive Aktivität.
Nachdem die Daten im Emulator eingegeben wurden, müssen sie vom VRAM an die Adresse 0xF000 kopiert werden. Exodus unterstützt diese Funktion ebenfalls.
Analog zum Zeichnen von Kacheln greifen wir zunächst auf den VDP-Steuerport zu, um mit dem Schreiben an der Adresse 0xF000 zu beginnen, und schreiben dann die Struktur in den Datenport.
Ich möchte Sie daran erinnern, dass die Beschreibung der VRAM-Adressierung im Handbuch oder im Blog Namenloser Algorithmus .
Kurz gesagt funktioniert die VDP-Adressierung folgendermaßen:
[..DC BA98 7654 3210 …. …. …. ..FE]
Dabei ist Hex die Bitposition in der gewünschten Adresse. Die ersten beiden Bits geben die Art des angeforderten Befehls an, zum Beispiel 01 – in den VRAM schreiben. Für die Adresse 0XF000 ergibt sich dann:
0111 0000 0000 0000 0000 0000 0000 0011 (70000003)
Als Ergebnis erhalten wir den Code:
move.l #$70000003,vdp_control_port
move.w #$0100,vdp_data_port
move.w #$0F00,vdp_data_port
move.w #$0001,vdp_data_port
move.w #$0100,vdp_data_port
Danach wird das Skelett-Sprite an den Koordinaten 256, 256 angezeigt. Cool, oder?
Links
https://gitlab.com/demensdeum /segagenesissamples/-/tree/main/7Sprite/vasm
https://opengameart.org/content/skeleton-guy-animated
Quellen
https://namelessalgorithm.com/genesis/blog/vdp/ a>
https://www.chibiakumas.com/68000/platform3.php#LessonP27
https://plutiedev.com/sprites
Schreiben in Assembly für Sega Genesis #3
In diesem Beitrag beschreibe ich, wie man mithilfe von Assembler ein Bild aus Kacheln auf dem Sega Genesis-Emulator anzeigt.
Das Splash-Bild von Demens Deum im Exodus-Emulator sieht folgendermaßen aus:

Die Ausgabe eines PNG-Bildes mithilfe von Kacheln erfolgt Schritt für Schritt:
- Verkleinerung des Bildes auf die Größe des Shogi-Bildschirms
- Konvertieren Sie PNG in Assembly-Datencode, getrennt in Farben und Kacheln
- Laden einer Farbpalette in CRAM
- Laden von Kacheln/Mustern in VRAM
- Laden von Kachelindizes an Plane A/B-Adressen im VRAM
- Sie können das Bild mit Ihrem bevorzugten Grafikeditor wie Blender auf die Größe des Shogi-Bildschirms verkleinern.
PNG-Konvertierung
Zum Konvertieren von Bildern können Sie das ImaGenesis-Tool verwenden; für die Arbeit unter Wine sind Visual Basic 6-Bibliotheken erforderlich, diese können mit winetricks (winetricks vb6run) installiert werden, oder RICHTX32.OCX kann aus dem Internet heruntergeladen und abgelegt werden für den ordnungsgemäßen Betrieb im Anwendungsordner.< /p>
In ImaGenesis müssen Sie 4-Bit-Farbe auswählen und Farben und Kacheln in zwei Dateien im Assembler-Format exportieren. Als nächstes müssen Sie in der Datei mit den Farben jede Farbe in ein Wort (2 Bytes) einfügen. Dazu verwenden Sie den Opcode dc.w.
Zum Beispiel CRAM-Begrüßungsbildschirm:
dc.w $0000
dc.w $0000
dc.w $0222
dc.w $000A
dc.w $0226
dc.w $000C
dc.w $0220
dc.w $08AA
dc.w $0446
dc.w $0EEE
dc.w $0244
dc.w $0668
dc.w $0688
dc.w $08AC
dc.w $0200
dc.w $0000
Lassen Sie die Kacheldatei unverändert, sie enthält bereits das richtige Format zum Laden. Beispiel eines Teils einer Kacheldatei:
dc.l $11111111 ; Tile #0
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111 ; Tile #1
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
dc.l $11111111
Wie Sie dem obigen Beispiel entnehmen können, handelt es sich bei den Kacheln um ein 8×8-Raster, das aus CRAM-Farbpalettenindizes besteht.
Farben im CRAM
Das Laden in den CRAM erfolgt durch Setzen eines Farbladebefehls an eine bestimmte CRAM-Adresse im Steuerport (vdp-Steuerung). Das Befehlsformat ist im Sega Genesis Software Manual (1989) beschrieben. Ich füge nur hinzu, dass Sie nur 0x20000 zur Adresse hinzufügen müssen, um zur nächsten Farbe zu wechseln.
Als nächstes müssen Sie die Farbe in den Datenport (vdp-Daten) laden; Der einfachste Weg, das Laden zu verstehen, ist das folgende Beispiel:
lea Colors,a0 ; pointer to Colors label
move.l #15,d7; colors counter
VDPCRAMFillLoopStep:
move.l d0,vdp_control_port ;
move.w (a0)+,d1;
move.w d1,(vdp_data_port);
add.l #$20000,d0 ; increment CRAM address
dbra d7,VDPCRAMFillLoopStep
Kacheln im VRAM
Als nächstes erfolgt das Laden von Kacheln/Mustern in den VRAM-Videospeicher. Wählen Sie dazu eine Adresse im VRAM aus, zum Beispiel 0x00000000. Analog zum CRAM kontaktieren wir den VDP-Steuerport mit einem Befehl zum Schreiben in den VRAM und die Startadresse.
Danach können Sie Langwörter in den VRAM hochladen; im Vergleich zum CRAM müssen Sie nicht für jedes Langwort die Adresse angeben, da es einen VRAM-Auto-Inkrementierungsmodus gibt. Sie können es mit dem VDP-Registerflag 0x0F (dc.b $02)
aktivieren
lea Tiles,a0
move.l #$40200000,vdp_control_port; write to VRAM command
move.w #6136,d0 ; (767 tiles * 8 rows) counter
TilesVRAMLoop:
move.l (a0)+,vdp_data_port;
dbra d0,TilesVRAMLoop
Kachelindizes in Ebene A/B
Jetzt müssen wir den Bildschirm entsprechend ihrem Index mit Kacheln füllen. Dazu wird VRAM an der Plane A/B-Adresse gefüllt, die in den VDP-Registern (0x02, 0x04) eingetragen ist. Weitere Informationen zur kniffligen Adressierung finden Sie im Handbuch von Sega; in meinem Beispiel ist die VRAM-Adresse 0xC000, laden wir die Indizes dort hoch.
Ihr Bild füllt ohnehin den VRAM-Bereich außerhalb des Bildschirms aus. Nach dem Zeichnen des Bildschirmbereichs sollte Ihr Renderer also mit dem Zeichnen aufhören und wieder fortfahren, wenn sich der Cursor in eine neue Zeile bewegt. Es gibt viele Möglichkeiten, dies zu implementieren; ich habe die einfachste Version des Zählens auf zwei Registern des Bildbreitenzählers und des Cursorpositionszählers verwendet.
Codebeispiel:
move.w #0,d0 ; column index
move.w #1,d1 ; tile index
move.l #$40000003,(vdp_control_port) ; initial drawing location
move.l #2500,d7 ; how many tiles to draw (entire screen ~2500)
imageWidth = 31
screenWidth = 64
FillBackgroundStep:
cmp.w #imageWidth,d0
ble.w FillBackgroundStepFill
FillBackgroundStep2:
cmp.w #imageWidth,d0
bgt.w FillBackgroundStepSkip
FillBackgroundStep3:
add #1,d0
cmp.w #screenWidth,d0
bge.w FillBackgroundStepNewRow
FillBackgroundStep4:
dbra d7,FillBackgroundStep ; loop to next tile
Stuck:
nop
jmp Stuck
FillBackgroundStepNewRow:
move.w #0,d0
jmp FillBackgroundStep4
FillBackgroundStepFill:
move.w d1,(vdp_data_port) ; copy the pattern to VPD
add #1,d1
jmp FillBackgroundStep2
FillBackgroundStepSkip:
move.w #0,(vdp_data_port) ; copy the pattern to VPD
jmp FillBackgroundStep3
Danach müssen Sie nur noch das ROM mit Vasm zusammenbauen, den Simulator starten und sich das Bild ansehen.
Debuggen
Es wird nicht alles auf Anhieb klappen, daher möchte ich die folgenden Exodus-Emulator-Tools empfehlen:
- M68k-Prozessor-Debugger
- Ändern der Anzahl der m68k-Prozessorzyklen (für den Zeitlupenmodus im Debugger)
- Zuschauer CRAM, VRAM, Plane A/B
- Lesen Sie sorgfältig die Dokumentation für m68k und die verwendeten Opcodes (nicht alles ist so offensichtlich, wie es auf den ersten Blick scheint)
- Beispiele für Spielcode/Demontage auf Github ansehen
- Unterprogramme von Prozessorausnahmen implementieren und verarbeiten
Zeiger auf Prozessor-Ausnahme-Subroutinen werden im ROM-Header platziert; es gibt auch ein Projekt auf GitHub mit einem interaktiven Laufzeit-Debugger für Sega, genannt genesis-debugger.
Verwenden Sie alle verfügbaren Tools, haben Sie eine nette Codierung der alten Schule und vielleicht ist Blast Processing dabei!
Links
https://gitlab.com/demensdeum /segagenesisamples/-/tree/main/6Image/vasm
http://devster.monkeeh.com/sega/imagenesis/
https://github.com/flamewing/genesis-debugger
Quellen
https://www.chibiakumas.com/68000/helloworld .php#LessonH5
https://huguesjohnson.com/programming/genesis/tiles-sprites/
Schreiben in Assembly für Sega Genesis #2
In diesem Beitrag beschreibe ich, wie man Farben in Assemblersprache in die Shogi-Palette lädt.
Das Endergebnis im Exodus-Emulator sieht folgendermaßen aus:
Um den Prozess zu vereinfachen, finden Sie im Internet ein PDF mit dem Titel Genesis Software Manual (1989). Es beschreibt den gesamten Prozess sehr detailliert. Tatsächlich handelt es sich bei dieser Notiz um einen Kommentar zum Originalhandbuch.< /p>
Um Farben auf den VDP-Chip des Sega-Emulators zu schreiben, müssen Sie die folgenden Dinge tun:
- TMSS-Schutz deaktivieren
- Korrekte Parameter in VDP-Register schreiben
- Schreiben Sie die gewünschten Farben in CRAM
Für den Zusammenbau verwenden wir vasmm68k_mot und einen bevorzugten Texteditor, zum Beispiel Echo. Der Zusammenbau erfolgt mit dem Befehl:
Порты VDP
VDP чип общается с M68K через два порта в оперативной памяти – порт контроля и порт данных.
По сути:
- Через порт контроля можно выставлять значения регистрам VDP.
- Также порт контроля является указателем на ту часть VDP (VRAM, CRAM, VSRAM etc.) через которую передаются данные через порт данных
Интересная информация: Сега сохранила совместимость с играми Master System, на что указывает MODE 4 из мануала разработчика, в нем VDP переключается в режим Master System.
Объявим порты контроля и данных:
vdp_data_port = $C00000
Отключить систему защиты TMSS
Защита от нелицензионных игр TMSS имеет несколько вариантов разблокировки, например требуется чтобы до обращения к VDP в адресном регистре A1 лежала строка “SEGA”.
MOVE.B A1,D0; Получаем версию хардвары цифрой из A1 в регистр D0
ANDI.B 0x0F,D0; По маске берем последние биты, чтобы ничего не сломать
BEQ.B SkipTmss; Если версия равна 0, скорее всего это японка или эмулятор без включенного TMSS, тогда идем в сабрутину SkipTmss
MOVE.L "SEGA",A1; Или записываем строку SEGA в A1
Korrekte Parameter in VDP-Register schreiben
Warum überhaupt die richtigen Parameter in den VDP-Registern einstellen? Der Grundgedanke ist, dass VDP viel kann. Sie müssen es also vor dem Rendern mit den erforderlichen Funktionen initialisieren, sonst versteht es einfach nicht, was Sie von ihm erwarten.
Jedes Register ist für eine bestimmte Einstellung/Betriebsart verantwortlich. Das Segov-Handbuch gibt alle Bits/Flags für jedes der 24 Register an, eine Beschreibung der Register selbst.
Nehmen wir vorgefertigte Parameter mit Kommentaren aus dem Bigevilcorporation-Blog:
VDPReg0: dc.b $14 ; 0: H interrupt on, palettes on
VDPReg1: dc.b $74 ; 1: V interrupt on, display on, DMA on, Genesis mode on
VDPReg2: dc.b $30 ; 2: Pattern table for Scroll Plane A at VRAM $C000
; (bits 3-5 = bits 13-15)
VDPReg3: dc.b $00 ; 3: Pattern table for Window Plane at VRAM $0000
; (disabled) (bits 1-5 = bits 11-15)
VDPReg4: dc.b $07 ; 4: Pattern table for Scroll Plane B at VRAM $E000
; (bits 0-2 = bits 11-15)
VDPReg5: dc.b $78 ; 5: Sprite table at VRAM $F000 (bits 0-6 = bits 9-15)
VDPReg6: dc.b $00 ; 6: Unused
VDPReg7: dc.b $00 ; 7: Background colour - bits 0-3 = colour,
; bits 4-5 = palette
VDPReg8: dc.b $00 ; 8: Unused
VDPReg9: dc.b $00 ; 9: Unused
VDPRegA: dc.b $FF ; 10: Frequency of Horiz. interrupt in Rasters
; (number of lines travelled by the beam)
VDPRegB: dc.b $00 ; 11: External interrupts off, V scroll fullscreen,
; H scroll fullscreen
VDPRegC: dc.b $81 ; 12: Shadows and highlights off, interlace off,
; H40 mode (320 x 224 screen res)
VDPRegD: dc.b $3F ; 13: Horiz. scroll table at VRAM $FC00 (bits 0-5)
VDPRegE: dc.b $00 ; 14: Unused
VDPRegF: dc.b $02 ; 15: Autoincrement 2 bytes
VDPReg10: dc.b $01 ; 16: Vert. scroll 32, Horiz. scroll 64
VDPReg11: dc.b $00 ; 17: Window Plane X pos 0 left
; (pos in bits 0-4, left/right in bit 7)
VDPReg12: dc.b $00 ; 18: Window Plane Y pos 0 up
; (pos in bits 0-4, up/down in bit 7)
VDPReg13: dc.b $FF ; 19: DMA length lo byte
VDPReg14: dc.b $FF ; 20: DMA length hi byte
VDPReg15: dc.b $00 ; 21: DMA source address lo byte
VDPReg16: dc.b $00 ; 22: DMA source address mid byte
VDPReg17: dc.b $80 ; 23: DMA source address hi byte,
; memory-to-VRAM mode (bits 6-7)
Ok, jetzt gehen wir zum Steuerport und schreiben alle Flags in die VDP-Register:
move.l #VDPRegisters,a0 ; Пишем адрес таблицы параметров в A1
move.l #$18,d0 ; Счетчик цикла - 24 = 18 (HEX) в D0
move.l #$00008000,d1 ; Готовим команду на запись в регистр VDP по индексу 0, по мануалу - 1000 0000 0000 0000 (BIN) = 8000 (HEX)
FillInitialStateForVDPRegistersLoop:
move.b (a0)+,d1 ; Записываем в D1 итоговое значение регистра VDP из таблицы параметров, на отправку в порт контроля VDP
move.w d1,vdp_control_port ; Отправляем итоговую команду + значение из D1 в порт контроля VDP
add.w #$0100,d1 ; Поднимаем индекс регистра VDP на 1 (бинарное сложение +1 к индексу по мануалу Сеги)
dbra d0,FillInitialStateForVDPRegistersLoop ; Уменьшаем счетчик регистров, продолжаем цикл если необходимо
Самое сложное это прочитать мануал и понять в каком формате подаются данные на порт контроля, опытные разработчики разберутся сразу, а вот неопытные… Немного подумают и поймут, что синтаксис для записи регистров такой:
0B100(5 бит – индекс регистра)(8 бит/байт – значение)
0B1000001001000101 – записать в регистр VDP 2 (00010), значение флажков 01000101.
Записать нужные цвета в CRAM
Далее идем писать два цвета в память цветов CRAM (Color RAM). Для этого пишем в порт контроля команду на доступ к цвету по индексу 0 в CRAM и отправляем по дата порту цвет. Все!
Пример:
move.l #$C0000000,vdp_control_port ; Доступ к цвету по индексу 0 в CRAM через порт контроля
move.w #228,d0; Цвет в D0
move.w d0,vdp_data_port; Отправляем цвет в порт данных
Nachdem Sie den Emulator in Exodus erstellt und ausgeführt haben, sollte Ihr Bildschirm mit der Farbe 228 gefüllt sein.
Füllen wir es mit einer zweiten Farbe, basierend auf dem letzten Byte 127.
move.l #$C07f0000,vdp_control_port ; Доступ к цвету по байту 127 в CRAM через порт контроля
move.w #69,d0; Цвет в D0
move.w d0,vdp_data_port; Отправляем цвет в порт данных
Links
https://gitlab.com/demensdeum/segagenesissamples
https://www.exodusemulator.com/
http://sun.hasenbraten.de/vasm/
https://tomeko.net/online_tools/bin_to_32bit_hex.php?lang=en
Quellen
https://namelessalgorithm.com/genesis/blog/genesis/ a>
https://plutiedev.com/vdp-commands
https://huguesjohnson.com/programming/genesis/palettes/
https://www.chibiakumas.com/68000/helloworld.php#LessonH5
https://blog.bigevilcorporation.co.uk/2012/03/09/sega-megadrive-3-awaking-the-beast/
Schreiben in Assembly für Sega Genesis #1
Der erste Artikel über das Schreiben von Spielen für die klassische Sega Genesis-Konsole in Motorola 68000 Assembly.
Lassen Sie uns die einfachste Endlosschleife für Sega schreiben. Dafür benötigen wir: einen Assembler, einen Emulator mit Disassembler, einen bevorzugten Texteditor, ein grundlegendes Verständnis der Struktur von Sega Rum.
Für die Entwicklung verwende ich meinen eigenen Assembler/Disassembler Gen68KryBaby:
https://gitlab.com/demensdeum/gen68krybaby/ p>
Das Tool ist in Python 3 entwickelt, zum Zusammenstellen wird eine Datei mit der Erweiterung .asm oder .gen68KryBabyDisasm als Eingabe bereitgestellt, die Ausgabe ist eine Datei mit der Erweiterung .gen68KryBabyAsm.bin, die im Emulator oder auf ausgeführt werden kann eine echte Konsole (Vorsicht, weggehen, die Konsole könnte explodieren!)
Das Zerlegen von ROMs wird ebenfalls unterstützt. Dazu müssen Sie eine ROM-Datei als Eingabe übermitteln, ohne die Erweiterungen .asm oder .gen68KryBabyDisasm. Die Opcode-Unterstützung wird abhängig von meinem Interesse am Thema und der Beteiligung von Mitwirkenden steigen oder sinken.
Struktur
Der Sega-ROM-Header belegt die ersten 512 Bytes. Es enthält Informationen über das Spiel, den Namen, unterstützte Peripheriegeräte, Prüfsumme und andere Systemflags. Ich gehe davon aus, dass die Konsole ohne Titel nicht einmal auf den Rum schaut und denkt, dass er falsch ist, und sagt: „Was gibst du mir hier?“
Nach dem Header kommt das Unterprogramm/Reset-Unterprogramm, in dem der m68K-Prozessor seine Arbeit beginnt. Okay, es ist eine Kleinigkeit – Opcodes (Operationscodes) finden, nämlich nichts tun (!) und zur Unterroutine an der Adresse im Speicher wechseln. Wenn Sie googeln, können Sie den NOP-Opcode finden, der nichts tut, und den JSR-Opcode, der einen bedingungslosen Sprung zur Argumentadresse ausführt, das heißt, er bewegt den Schlitten einfach ohne irgendwelche Launen dorthin, wo wir ihn fragen.
Alles zusammenfügen
Der Header-Donor für die ROM war eines der Spiele in der Beta-Version, derzeit als Hex-Daten aufgezeichnet.
00 ff 2b 52 00 00 02 00 00 00 49 90 00 00 49 90 00 00 49 90 00...и т.д.
Код программы со-но представляет из себя объявление сабрутины Reset/EntryPoint в 512 (0x200) байте, NOP, возврат каретки к 0x00000200, таким образом мы получим бесконечный цикл.
Ассемблерный код сабрутины Reset/EntryPoint:
NOP
NOP
NOP
NOP
NOP
JSR 0x00000200
Vollständiges Beispiel zusammen mit ROM-Header:
https://gitlab.com /demensdeum/segagenesisamples/-/blob/main/1InfiniteLoop/1infiniteloop.asm
Wir sammeln als nächstes:
Запускаем ром 1infiniteloop.asm.gen68KryBabyAsm.bin в режиме дебаггера эмулятора Exodus/Gens, смотрим что m68K корректно считывает NOP, и бесконечно прыгает к EntryPoint в 0x200 на JSR

Здесь должен быть Соник показывающий V, но он уехал на Вакен.
Ссылки
https://gitlab.com/demensdeum/gen68krybaby/
https://gitlab.com/demensdeum/segagenesissamples
https://www.exodusemulator.com/downloads/release-archive
Источники
ROM Hacking Demo – Genesis and SNES games in 480i
https://www.chibiakumas.com/68000/genesis.php
https://plutiedev.com/rom-header
https://blog.bigevilcorporation.co.uk/2012/02/28/sega-megadrive-1-getting-started/
https://opensource.apple.com/source/cctools/cctools-836/as/m68k-opcode.h.auto.html
Wie ich den Kerl an der Stange vermisst habe oder eine Geschichte über erstaunlichen Einfallsreichtum
In dieser Notiz werde ich über die Bedeutung von Architekturentscheidungen bei der Entwicklung, der Unterstützung einer Anwendung und in einer Teamentwicklungsumgebung schreiben.
Selbst- Betriebsserviette Professor Lucifer Gorgonzola. Rube Goldberg
In meiner Jugend habe ich an einer Taxi-Bestellanwendung gearbeitet. Im Programm können Sie einen Abholpunkt und einen Abgabepunkt auswählen, die Fahrtkosten und die Tarifart berechnen und tatsächlich ein Taxi bestellen. Ich habe die Anwendung in der letzten Phase des Vorabstarts erhalten; nach dem Hinzufügen mehrerer Korrekturen wurde die Anwendung im AppStore veröffentlicht. Bereits zu diesem Zeitpunkt war dem gesamten Team klar, dass die Implementierung sehr schlecht war, keine Entwurfsmuster verwendet wurden, alle Komponenten des Systems eng miteinander verbunden waren und es im Allgemeinen möglich war, es in eine große kontinuierliche Klasse (Gottobjekt) zu schreiben. Es hätte sich nichts geändert, so wie die Klassen ihre Verantwortungsgrenzen durcheinander brachten und sich in ihrer Gesamtmasse in einer toten Kopplung überlappten. Später beschloss das Management, die Anwendung unter Verwendung der richtigen Architektur von Grund auf neu zu schreiben, was auch geschah und das Endprodukt für mehrere Dutzend B2B-Kunden implementiert wurde.
Ich werde jedoch einen merkwürdigen Vorfall aus der Architektur der Vergangenheit beschreiben, von dem ich manchmal mitten in der Nacht schweißgebadet aufwache oder mich mitten am Tag plötzlich daran erinnere und hysterisch zu lachen beginne. Die Sache ist die, dass ich den Kerl an der Stange beim ersten Mal nicht treffen konnte, was den Großteil der Bewerbung zum Scheitern brachte, aber das Wichtigste zuerst.
Es war ein gewöhnlicher Arbeitstag, einer der Kunden erhielt die Aufgabe, das Anwendungsdesign leicht zu verfeinern – Es ist einfach, das Symbol in der Mitte des Auswahlbildschirms für die Abholadresse um ein paar Pixel nach oben zu verschieben. Nun, nachdem ich die Aufgabe professionell auf 10 Minuten geschätzt hatte, hob ich das Symbol um 20 Pixel an, völlig ahnungslos, und beschloss, den Taxiauftrag zu überprüfen.
Was? Die App zeigt den Bestellbutton nicht mehr an? Wie ist das passiert?
Ich traute meinen Augen nicht; nachdem ich das Symbol um 20 Pixel erhöht hatte, zeigte die Anwendung die Schaltfläche „Bestellung fortsetzen“ nicht mehr an. Nachdem ich die Änderung rückgängig gemacht hatte, sah ich die Schaltfläche wieder. Hier stimmte etwas nicht. Nachdem ich 20 Minuten im Debugger verbracht hatte, hatte ich es ein wenig satt, die vielen Aufrufe überlappender Klassen abzuwickeln, aber ich entdeckte, dass *das Verschieben des Bildes die Logik der Anwendung wirklich verändert*
Es drehte sich alles um das Symbol in der Mitte – Ein Mann auf einer Stange, der beim Bewegen der Karte nach oben sprang, um die Bewegung der Kamera zu animieren. Auf diese Animation folgte das Verschwinden des Knopfes unten. Anscheinend ging das Programm davon aus, dass der um 20 Pixel verschobene Mann einen Sprung machte, und versteckte daher gemäß seiner internen Logik die Bestätigungsschaltfläche.
Wie kann das passieren? Hängt der *Zustand* des Bildschirms wirklich nicht vom Muster der Zustandsmaschine ab, sondern von der *Darstellung* der Position des Mannes auf der Stange?
Es stellte sich heraus, dass jedes Mal, wenn die Karte gezeichnet wurde, die Anwendung *visuell* in die Mitte des Bildschirms gestochen und überprüft, was dort war. Wenn sich ein Mann auf einer Stange befindet, bedeutet dies, dass die Kartenverschiebungsanimation beendet ist und angezeigt werden muss Taste. Wenn der Mann nicht da ist, wird die Karte verschoben und die Schaltfläche muss ausgeblendet werden.
Im obigen Beispiel ist alles in Ordnung, erstens ist es ein Beispiel für Goldberg-Maschinen (abstruse Maschinen), zweitens ein Beispiel für die Zurückhaltung des Entwicklers, irgendwie mit anderen Entwicklern im Team zu interagieren (versuchen Sie, es ohne herauszufinden). Drittens können Sie alle Probleme nach SOLID, Mustern (Code-Smells), MVC-Verletzungen und vielem mehr auflisten.
Versuchen Sie, dies nicht zu tun, entwickeln Sie sich in alle möglichen Richtungen und helfen Sie Ihren Kollegen bei ihrer Arbeit. Frohes neues Jahr euch allen.
Links
https://ru.wikipedia.org/wiki/Goldberg_Machine
https://ru.wikipedia.org/wiki/SOLID
https://refactoring.guru/ru/refactoring/smells
https://ru.wikipedia.org/wiki/Model -View-Controller
https://refactoring.guru/ru/design-patterns/state
Erraten Sie die Gruppe
In diesem Beitrag beschreibe ich die Arbeit mit dem Fasttext-Textklassifikator.
Fasttext – Bibliothek für maschinelles Lernen zur Textklassifizierung. Versuchen wir ihr beizubringen, eine Metal-Band anhand des Songtitels zu identifizieren. Dazu nutzen wir überwachtes Lernen anhand eines Datensatzes.
Lassen Sie uns einen Datensatz von Liedern mit Gruppennamen erstellen:
__label__metallica fuel
__label__metallica escape
__label__black_sabbath gypsy
__label__black_sabbath snowblind
__label__black_sabbath am i going insane
__label__anthrax anthrax
__label__anthrax i'm alive
__label__anthrax antisocial
[и т.д.]
Формат обучающей выборки:
Обучим fasttext и сохраним модель:
model.save_model("model.bin")
Laden Sie das trainierte Modell und bitten Sie darum, die Gruppe anhand des Namens des Songs zu identifizieren:
predictResult = model.predict("Bleed")
print(predictResult)
В результате мы получим список классов на которые похож данный пример, с указанием уровня похожести цифрой, в нашем случае похожесть названия песни Bleed на одну из групп датасета.
Для того чтобы модель fasttext умела работать с датасетом выходящим за границы обучающей выборки, используют режим autotune с использованием файла валидации (файл тест). Во время автотюна fasttext подбирает оптимальные гиперпараметры модели, проводя валидацию результата на выборке из тест файла. Время автотюна ограничивается пользователем в самостоятельно, с помощью передачи аргумента autotuneDuration.
Пример создания модели с использованием файла тест:
Источники
https://fasttext.cc
https://gosha20777.github.io/tutorial/2018/04/12/fasttext-for-windows
Исходный код
https://gitlab.com/demensdeum/MachineLearning/-/tree/master/6bandClassifier
x86_64 Assembler + C = Eine Liebe
In dieser Notiz beschreibe ich den Prozess des Aufrufs von C-Funktionen aus dem Assembler.
Versuchen wir, printf(“Hello World!\n”); aufzurufen. und Exit(0);
message: db "Hello, world!", 10, 0
section .text
extern printf
extern exit
global main
main:
xor rax, rax
mov rdi, message
call printf
xor rdi, rdi
call exit
Alles ist viel einfacher als es scheint. Im Abschnitt .rodata beschreiben wir statische Daten, in diesem Fall die Zeile „Hallo Welt!“, 10 ist ein Zeilenumbruchzeichen und wir vergessen auch nicht, es auf Null zu setzen.
Im Codeabschnitt deklarieren wir die externen Funktionen printf, Exit der stdio- und stdlib-Bibliotheken und deklarieren auch die Eingabefunktion main:
extern printf
extern exit
global main
Wir übergeben 0 von der Rax-Funktion an das Rückgaberegister. Sie können mov rax, 0; aber um es zu beschleunigen, benutzen sie xor rax, rax; Als nächstes übergeben wir einen Zeiger auf die Zeichenfolge an das erste Argument:
Далее вызываем внешнюю функцию Си printf:
xor rax, rax
mov rdi, message
call printf
xor rdi, rdi
call exit
In Analogie dazu übergeben wir 0 an das erste Argument und rufen „exit:“ auf.
call exit
Wie die Amerikaner sagen:
Wer hört niemandem zu
Dieser Pilaw isst @ Alexander Pelevin
Quellen
https://www.devdungeon. com/content/how-mix-c-and-assembly
https://nekosecurity.com/x86-64-assembly/part-3-nasm-anatomy-syscall-passing-argument
https://www.cs.uaf.edu/2017/fall/cs301/reference/x86_64.html
Quellcode
https://gitlab.com/demensdeum/assembly-playground
Hallo Welt x86_64-Assembler
In diesem Beitrag beschreibe ich den Prozess der Einrichtung der IDE und schreibe den ersten Hello World in x86_64-Assembler für das Ubuntu-Linux-Betriebssystem.
Beginnen wir mit der Installation der SASM-IDE, Nasm-Assembler:
Далее запустим SASM и напишем Hello World:
section .text
main:
mov rbp, rsp ; for correct debugging
mov rax, 1 ; write(
mov rdi, 1 ; STDOUT_FILENO,
mov rsi, msg ; "Hello, world!\n",
mov rdx, msglen ; sizeof("Hello, world!\n")
syscall ; );
mov rax, 60 ; exit(
mov rdi, 0 ; EXIT_SUCCESS
syscall ; );
section .rodata
msg: db "Hello, world!"
msglen: equ $-msg
Hello World-Code aus dem Blog James Fisher, angepasst für Assemblierung und Debugging in SASM. In der SASM-Dokumentation heißt es, dass der Einstiegspunkt eine Funktion namens „main“ sein muss, da sonst das Debuggen und Kompilieren des Codes fehlerhaft ist.
Was haben wir in diesem Code gemacht? Habe einen Systemaufruf getätigt – Zugriff auf den Kernel des Linux-Betriebssystems mit korrekten Argumenten in Registern, einem Zeiger auf eine Zeichenfolge im Datenabschnitt.
Unter der Lupe
Sehen wir uns den Code genauer an:
global – директива ассемблера позволяющая задавать глобальные символы со строковыми именами. Хорошая аналогия – интерфейсы заголовочных файлов языков C/C++. В данном случае мы задаем символ main для функции входа.
section – директива ассемблера позволяющая задавать секции (сегменты) кода. Директивы section или segment равнозначны. В секции .text помещается код программы.
Обьявляем начало функции main. В ассемблере функции называются подпрограммами (subroutine)
Первая машинная команда mov – помещает значение из аргумента 1 в аргумент 2. В данном случае мы переносим значение регистра rbp в rsp. Из комментария можно понять что эту строку добавил SASM для упрощения отладки. Видимо это личные дела между SASM и дебаггером gdb.
Далее посмотрим на код до сегмента данных .rodata, два вызова syscall, первый выводит строку Hello World, второй обеспечивает выход из приложения с корректным кодом 0.
Представим себе что регистры это переменные с именами rax, rdi, rsi, rdx, r10, r8, r9. По аналогии с высокоуровневыми языками, перевернем вертикальное представление ассемблера в горизонтальное, тогда вызов syscall будет выглядеть так:
Тогда вызов печати текста:
Вызов exit с корректным кодом 0:
Рассмотрим аргументы подробнее, в заголовочном файле asm/unistd_64.h находим номер функции __NR_write – 1, далее в документации смотрим аргументы для write:
ssize_t write(int fd, const void *buf, size_t count);
Первый аргумент – файловый дескриптор, второй – буфер с данными, третий – счетчик байт для записи в дескриптор. Ищем номер файлового дескриптора для стандартного вывода, в мануале по stdout находим код 1. Далее дело за малым, передать указатель на буфер строки Hello World из секции данных .rodata – msg, счетчик байт – msglen, передать в регистры rax, rdi, rsi, rdx корректные аргументы и вызвать syscall.
Обозначение константных строк и длины описывается в мануале nasm:
msglen equ $-message
Достаточно просто да?
Источники
https://github.com/Dman95/SASM
https://www.nasm.us/xdoc/2.15.05/html/nasmdoc0.html
http://acm.mipt.ru/twiki/bin/view/Asm/HelloNasm
https://jameshfisher.com/2018/03/10/linux-assembly-hello-world/
http://www.ece.uah.edu/~milenka/cpe323-10S/labs/lab3.pdf
https://c9x.me/x86/html/file_module_x86_id_176.html
https://www.recurse.com/blog/7-understanding-c-by-learning-assembly
https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D0%BB%D0%BE%D0%B3_%D0%BF%D1%80%D0%BE%D1%86%D0%B5%D0%B4%D1%83%D1%80%D1%8B
https://www.tutorialspoint.com/assembly_programming/assembly_basic_syntax.html
https://nekosecurity.com/x86-64-assembly/part-3-nasm-anatomy-syscall-passing-argument
https://man7.org/linux/man-pages/man2/syscall.2.html
https://en.wikipedia.org/wiki/Write_(system_call)
Исходный код
https://gitlab.com/demensdeum/assembly-playground
Hash-Tabelle
Mit der Hash-Tabelle können Sie eine assoziative Array-Datenstruktur (Wörterbuch) mit einer durchschnittlichen Leistung von O(1) für Einfüge-, Lösch- und Suchvorgänge implementieren.
Unten finden Sie ein Beispiel für die einfachste Implementierung einer Hash-Map in nodeJS:
Wie funktioniert es? Passen Sie auf Ihre Hände auf:
- Innerhalb der Hash-Map befindet sich ein Array
- Im Array-Element befindet sich ein Zeiger auf den ersten Knoten der verknüpften Liste
- Speicher wird für ein Array von Zeigern zugewiesen (z. B. 65535 Elemente)
- Sie implementieren eine Hash-Funktion, der Wörterbuchschlüssel ist die Eingabe, und am Ausgang kann sie alles tun, aber am Ende gibt sie den Index des Array-Elements zurück
So funktioniert die Aufnahme:
- Am Eingang liegt ein Schlüsselpaar – Wert
- Hash-Funktion gibt Index nach Schlüssel zurück
- Einen verknüpften Listenknoten aus einem Array nach Index abrufen
- Überprüfen Sie, ob es mit dem Schlüssel übereinstimmt
- Wenn es übereinstimmt, ersetzen Sie den Wert
- Wenn es nicht übereinstimmt, fahren Sie mit dem nächsten Knoten fort, bis wir einen Knoten mit dem erforderlichen Schlüssel finden oder finden.
- Wenn der Knoten immer noch nicht gefunden wird, erstellen Sie ihn am Ende der verknüpften Liste
So funktioniert die Suche nach Schlüssel:
- Am Eingang liegt ein Schlüsselpaar – Wert
- Hash-Funktion gibt Index nach Schlüssel zurück
- Einen verknüpften Listenknoten aus einem Array nach Index abrufen
- Überprüfen Sie, ob es mit dem Schlüssel übereinstimmt
- Wenn es übereinstimmt, wird der Wert zurückgegeben
- Wenn es nicht übereinstimmt, fahren Sie mit dem nächsten Knoten fort, bis wir einen Knoten mit dem erforderlichen Schlüssel finden oder finden.
Warum brauchen wir eine verknüpfte Liste innerhalb eines Arrays? Aufgrund möglicher Kollisionen bei der Berechnung der Hash-Funktion. In diesem Fall befinden sich mehrere verschiedene Schlüssel-Wert-Paare am selben Index im Array. In diesem Fall wird die verknüpfte Liste durchlaufen, um den erforderlichen Schlüssel zu finden.
Quellen
https://ru.wikipedia.org/wiki/Hash-Tabelle
https://www.youtube.com/watch?v=wg8hZxMRwcw
Quellcode
https://gitlab.com/demensdeum/datastructures
Arbeiten mit Ressourcen in Android C++
Um mit Ressourcen in Android über ndk zu arbeiten – C++ gibt es mehrere Möglichkeiten:
- Verwenden Sie den Zugriff auf Ressourcen aus einer APK-Datei mit AssetManager
- Laden Sie Ressourcen aus dem Internet herunter, entpacken Sie sie in das Anwendungsverzeichnis und verwenden Sie sie mit Standard-C++-Methoden
- Kombinierte Methode – Greifen Sie über AssetManager auf das Archiv mit Ressourcen in der APK zu, entpacken Sie sie in das Anwendungsverzeichnis und verwenden Sie sie dann mit Standard-C++-Methoden
Als nächstes werde ich die kombinierte Zugriffsmethode beschreiben, die in der Flame Steel Engine-Spiel-Engine verwendet wird.
Wenn Sie SDL verwenden, können Sie den Zugriff auf Ressourcen von einer APK aus vereinfachen. Die Bibliothek umschließt Aufrufe an AssetManager und bietet Schnittstellen ähnlich wie stdio (fopen, fread, fclose usw.).
SDL_RWops *io = SDL_RWFromFile("files.fschest", "r");
Nachdem Sie das Archiv von der APK in den Puffer heruntergeladen haben, müssen Sie das aktuelle Arbeitsverzeichnis in das Anwendungsverzeichnis ändern. Es steht der Anwendung zur Verfügung, ohne dass zusätzliche Berechtigungen eingeholt werden müssen. Dazu verwenden wir einen SDL-Wrapper:
chdir(SDL_AndroidGetInternalStoragePath());
Als nächstes schreiben Sie das Archiv mit fopen, fwrite, fclose aus dem Puffer in das aktuelle Arbeitsverzeichnis. Sobald sich das Archiv in einem für C++ zugänglichen Verzeichnis befindet, entpacken Sie es. Zip-Archive können mit einer Kombination aus zwei Bibliotheken entpackt werden – minizip und zlib, das erste kann mit der Struktur von Archiven arbeiten, während das zweite Daten entpackt.
Um mehr Kontrolle zu erlangen und die Portierung zu vereinfachen, habe ich mein eigenes Archivformat ohne Komprimierung namens FSCest (Flame Steel Chest) implementiert. Dieses Format unterstützt das Archivieren eines Verzeichnisses mit Dateien und das Entpacken; Es gibt keine Unterstützung für die Ordnerhierarchie; Sie können nur mit Dateien arbeiten.
Wir verbinden den Header der FSCest-Bibliothek, entpacken das Archiv:
#include "fschest.h"
FSCHEST_extractChestToDirectory(archivePath, SDL_AndroidGetInternalStoragePath());
Nach dem Entpacken haben die C/C++-Schnittstellen Zugriff auf die Dateien aus dem Archiv. Daher musste ich nicht die gesamte Arbeit mit Dateien in der Engine neu schreiben, sondern fügte nur das Entpacken von Dateien in der Startphase hinzu.
Quellen
https://developer.android.com/ndk/ Referenz/Gruppe/Asset
Quellcode
https://gitlab.com/demensdeum/space- Jaguar-Action-RPG
https://gitlab.com/demensdeum/fschest
Stapelmaschine und RPN
Angenommen, wir müssen einen einfachen Bytecode-Interpreter implementieren. Welchen Ansatz zur Implementierung dieser Aufgabe sollten wir wählen?
Datenstruktur Der Stack bietet die Möglichkeit, eine einfache Bytecode-Maschine zu implementieren. Funktionen und Implementierungen von Stack-Maschinen werden in vielen Artikeln im westlichen und inländischen Internet beschrieben; ich möchte nur erwähnen, dass die Java Virtual Machine ein Beispiel für eine Stack-Maschine ist.
Das Funktionsprinzip der Maschine ist einfach: Ein Programm mit Daten und Operationscodes (Opcodes) wird dem Eingang zugeführt und die erforderlichen Operationen werden durch Manipulationen am Stapel implementiert. Schauen wir uns ein Beispiel-Bytecode-Programm von meiner Stack-Maschine an:
пMVkcatS olleHП
Am Ausgang erhalten wir die Zeichenfolge „Hello StackVM“. Die Stapelmaschine liest das Programm von links nach rechts und lädt Daten Zeichen für Zeichen auf den Stapel, wenn ein Opcode im Symbol – implementiert den Befehl mithilfe des Stapels.
Beispiel für die Implementierung einer Stack-Maschine in nodejs:
Umgekehrte polnische Notation (RPN)
Stack -Maschinen sind auch einfach zum Implementieren von Taschenrechnern zu verwenden. Dafür verwenden sie umgekehrte polnische Notation (Postfix -Notation).
Beispiel einer regulären Infix-Notation:
2*2+3*4
Konvertiert in RPN:
22*34*+
Um den Postfix-Datensatz zu zählen, verwenden wir eine Stapelmaschine:
2– an die Spitze des Stapels (Stapel: 2)
2– an die Spitze des Stapels (Stapel: 2,2)
*– Holen Sie sich zweimal die Oberseite des Stapels, multiplizieren Sie das Ergebnis und senden Sie es an die Oberseite des Stapels (Stapel: 4)
3– an die Spitze des Stapels (Stapel: 4, 3)
4– an die Spitze des Stapels (Stapel: 4, 3, 4)
*– Holen Sie sich zweimal die Oberseite des Stapels, multiplizieren Sie das Ergebnis und senden Sie es an die Oberseite des Stapels (Stapel: 4, 12)
+– Holen Sie sich zweimal die Spitze des Stapels, addieren Sie das Ergebnis und senden Sie es an die Spitze des Stapels (Stapel: 16)
Wie Sie sehen können – Das Ergebnis der Operationen 16 verbleibt auf dem Stapel. Es kann durch Implementierung von Stapeldruck-Opcodes gedruckt werden, zum Beispiel:
p22*34*+P
P – Stapeldruck-Start-Opcode, p – Opcode zum Fertigstellen des Stapeldrucks und zum Senden der letzten Zeile zum Rendern.
Um arithmetische Operationen von Infix in Postfix umzuwandeln, wird der Algorithmus „Sorting Yard“ von Edsger Dijkstra verwendet. Ein Beispiel für die Implementierung finden Sie oben oder im Repository des NodeJS-Maschinenstack-Projekts unten.
Quellen
https:/ /tech.badoo.com/ru/article/579/interpretatory-bajt-kodov-svoimi-rukami/
https://ru.wikipedia.org/wiki/Обратная_польская_запись
Quellcode
https://gitlab.com/demensdeum/stackvm/< /p>
Skelettanimation (Teil 2 – Knotenhierarchie, Interpolation)
Ich beschreibe weiterhin den Skelettanimationsalgorithmus, wie er in der Flame Steel Engine implementiert ist.
Da der Algorithmus der komplexeste von allen ist, die ich implementiert habe, können in den Notizen zum Entwicklungsprozess Fehler auftreten. Im vorherigen Artikel über diesen Algorithmus habe ich einen Fehler gemacht; das Array von Knochen wird für jedes Netz separat und nicht für das gesamte Modell an den Shader übertragen.
Knotenhierarchie
Damit der Algorithmus korrekt funktioniert, ist es notwendig, dass das Modell eine Verbindung zwischen den Knochen untereinander enthält (Grafik). Stellen wir uns eine Situation vor, in der zwei Animationen gleichzeitig abgespielt werden – Springe und hebe deine rechte Hand. Die Sprunganimation muss das Modell entlang der Y-Achse anheben, während die Animation zum Anheben des Arms dies berücksichtigen und beim Springen mit dem Modell ansteigen muss, sonst bleibt der Arm von selbst an Ort und Stelle.
Wir werden die Verbindung von Knoten für diesen Fall beschreiben – Der Körper enthält die Hand. Bei der Ausarbeitung des Algorithmus wird der Knochengraph ausgelesen, alle Animationen werden mit den richtigen Verbindungen berücksichtigt. Im Speicher des Modells wird das Diagramm getrennt von allen Animationen gespeichert, nur um die Konnektivität der Knochen des Modells widerzuspiegeln.
Interpolation auf der CPU
Im letzten Artikel habe ich das Prinzip des Renderns von Skelettanimationen beschrieben – „Transformationsmatrizen werden bei jedem Rendering-Frame von der CPU an den Shader übertragen.“
Jeder Rendering-Frame wird auf der CPU verarbeitet; für jeden Mesh-Bone erhält die Engine die endgültige Transformationsmatrix mithilfe von Positionsinterpolation, Drehung und Zoom. Während der Interpolation der endgültigen Knochenmatrix wird für alle aktiven Knotenanimationen ein Durchlauf durch den Knotenbaum durchgeführt, die endgültige Matrix wird mit den übergeordneten Matrix multipliziert und dann zum Rendern an den Vertex-Shader gesendet.
Vektoren werden zur Positionsinterpolation und Vergrößerung verwendet; Quaternionen werden zur Rotation verwendet, weil Sie sind im Gegensatz zu Euler-Winkeln sehr einfach zu interpolieren (SLERP) und auch sehr einfach als Transformationsmatrix darzustellen.
So vereinfachen Sie die Implementierung
Um das Debuggen des Vertex-Shaders zu vereinfachen, habe ich mithilfe des Makros FSGLOGLNEWAGERENDERER_CPU_BASED_VERTEX_MODS_ENABLED eine Simulation des Vertex-Shaders auf der CPU hinzugefügt. Der Grafikkartenhersteller NVIDIA verfügt über ein Dienstprogramm zum Debuggen von Shader-Code, Nsight. Vielleicht kann es auch die Entwicklung komplexer Vertex-/Pixel-Shader-Algorithmen vereinfachen, aber ich konnte seine Funktionalität nie ausreichend auf der CPU testen.
Im nächsten Artikel möchte ich das Mischen mehrerer Animationen beschreiben und die verbleibenden Lücken schließen.
Quellen
https://www.youtube.com/watch?v= f3Cr8Yx3GGA
Unterstützung für JavaScript-Skripte in C++ hinzugefügt
In diesem Beitrag beschreibe ich eine Möglichkeit, mithilfe der Tiny-JS-Bibliothek Unterstützung für JavaScript-Skripte zu einer C++-Anwendung hinzuzufügen.
Tiny-JS ist eine Bibliothek zum Einbetten in C++, die die Ausführung von JavaScript-Code ermöglicht und Bindungen unterstützt (die Möglichkeit, C++-Code aus Skripten aufzurufen)
Zuerst wollte ich die beliebten Bibliotheken ChaiScript, Duktape oder Connect Lua verwenden, aber aufgrund von Abhängigkeiten und möglichen Schwierigkeiten bei der Portabilität auf verschiedene Plattformen entschied ich mich, eine einfache, minimale, aber leistungsstarke MIT Tiny-Bibliothek zu finden; JS erfüllt diese Kriterien. Der einzige Nachteil dieser Bibliothek ist die fehlende Unterstützung/Entwicklung durch den Autor, ihr Code ist jedoch recht einfach, sodass Sie bei Bedarf die Unterstützung übernehmen können.
Laden Sie Tiny-JS aus dem Repository herunter:
https://github.com/gfwilliams/tiny-js
Fügen Sie als Nächstes Tiny-JS-Header zum Code hinzu, der für die Skripte verantwortlich ist:
#include "tiny-js/TinyJS.h"
#include "tiny-js/TinyJS_Functions.h"
Fügen Sie TinyJS-CPP-Dateien zur Build-Phase hinzu, dann können Sie mit dem Schreiben von Lade- und Ausführungsskripten beginnen.
Ein Beispiel für die Verwendung der Bibliothek ist im Repository verfügbar:
https://github.com/gfwilliams/tiny-js/blob/master/Script.cpp
https://github.com/gfwilliams/tiny-js/blob/wiki/CodeExamples.md
Ein Beispiel für die Implementierung der Handler-Klasse finden Sie im SpaceJaguar-Projekt:
https://gitlab.com/demensdeum/space-jaguar-action-rpg/-/blob/master/project/src/Controllers/SpaceJaguarScriptController/SpaceJaguarScriptController.h
https://gitlab.com/demensdeum/space-jaguar-action-rpg/-/blob/master/project/src/Controllers/SpaceJaguarScriptController/SpaceJaguarScriptController.cpp
Beispiel eines der Anwendung hinzugefügten Spielskripts:
https://gitlab.com/demensdeum/space-jaguar-action-rpg/-/blob/master/project/resources/com.demensdeum.spacejaguaractionrpg.scripts.sceneController.js
Quellen
https://github.com/gfwilliams/tiny-js
https://github.com/dbohdan/embedded-scripting-languages
https://github.com/AlexKotik/embeddable-scripting-languages
Erstellen einer C++-SDL-Anwendung für iOS unter Linux
In diesem Beitrag beschreibe ich das Verfahren zum Erstellen einer C++-SDL-Anwendung für iOS unter Linux, zum Signieren eines IPA-Archivs ohne kostenpflichtiges Apple Developer-Abonnement und zum Installieren auf einem sauberen Gerät (iPad) mit macOS ohne Jailbreak.< /p>
Zuerst installieren wir die Build-Toolchain für Linux:
https://github.com/tpoechtrager/cctools-port
Die Toolchain muss aus dem Repository heruntergeladen werden. Befolgen Sie dann die Anweisungen auf der Godot Engine-Website, um die Installation abzuschließen:
https://docs.godotengine.org/ru/latest/development/compiling/cross-compiling_for_ios_on_linux.html
Im Moment müssen Sie Xcode dmg herunterladen und das SDK von dort kopieren, um den cctools-Port zu erstellen. Dieser Schritt ist unter macOS einfacher durchzuführen; kopieren Sie einfach die erforderlichen SDK-Dateien aus dem installierten Xcode. Nach erfolgreicher Assemblierung enthält das Terminal den Pfad zur Cross-Compiler-Toolchain.
Als nächstes können Sie mit der Erstellung der SDL-Anwendung für iOS beginnen. Öffnen wir cmake und fügen die notwendigen Änderungen hinzu, um den C++-Code zu erstellen:
SET(CMAKE_SYSTEM_NAME Darwin)
SET(CMAKE_C_COMPILER arm-apple-darwin11-clang)
SET(CMAKE_CXX_COMPILER arm-apple-darwin11-clang++)
SET(CMAKE_LINKER arm-apple-darwin11-ld)
Jetzt können Sie mit cmake und make kompilieren, aber vergessen Sie nicht, $PATH zur Cross-Compiler-Toolchain hinzuzufügen:
PATH=$PATH:~/Sources/cctools-port/usage_examples/ios_toolchain/target/bin
Für eine korrekte Verknüpfung mit Frameworks und SDL schreiben wir diese in cmake, Abhängigkeiten des Spiels Space Jaguar zum Beispiel:
target_link_libraries(
${FSEGT_PROJECT_NAME}
${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libclang_rt.ios.a
${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2.a
${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2_mixer.a
${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2_image.a
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreServices.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/ImageIO.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/Metal.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/AVFoundation.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/GameController.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreMotion.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreGraphics.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/AudioToolbox.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreAudio.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/QuartzCore.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/OpenGLES.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/UIKit.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/Foundation.framework"
)
In meinem Fall werden die Bibliotheken SDL, SDL_Image und SDL_mixer vorab in Xcode unter macOS für die statische Verknüpfung kompiliert; Von Xcode kopierte Frameworks. Außerdem wurde die Bibliothek libclang_rt.ios.a hinzugefügt, die iOS-spezifische Laufzeitaufrufe enthält, beispielsweise isOSVersionAtLeast. Für die Arbeit mit OpenGL ES ist ein Makro enthalten, das ähnlich wie bei Android nicht unterstützte Funktionen in der mobilen Version deaktiviert.
Nachdem Sie alle Build-Probleme gelöst haben, sollten Sie die zusammengestellte Binärdatei für arm erhalten. Betrachten wir als Nächstes die Ausführung der zusammengestellten Binärdatei auf einem Gerät ohne Jailbreak.
Installieren Sie unter macOS Xcode, registrieren Sie sich im Apple-Portal, ohne für das Entwicklerprogramm zu bezahlen. Fügen Sie ein Konto in Xcode hinzu -> Einstellungen -> Konten, erstellen Sie eine leere Anwendung und bauen Sie auf einem echten Gerät auf. Während der Montage wird das Gerät dem kostenlosen Entwicklerkonto hinzugefügt. Nach der Zusammenstellung und dem Start müssen Sie das Archiv erstellen. Wählen Sie dazu „Generisches iOS-Gerät und -Produkt“ aus. Archiv. Sobald das Archiv erstellt ist, extrahieren Sie die Dateien „embedded.mobileprovision“ und „PkgInfo“ daraus. Suchen Sie im Build-Protokoll auf dem Gerät die Codesign-Zeile mit dem richtigen Signaturschlüssel und den Pfad zur Berechtigungsdatei mit der Erweiterung app.xcent und kopieren Sie sie.
Kopieren Sie den .app-Ordner aus dem Archiv, ersetzen Sie die Binärdatei im Archiv durch eine, die von einem Cross-Compiler unter Linux kompiliert wurde (z. B. SpaceJaguar.app/SpaceJaguar), fügen Sie dann die erforderlichen Ressourcen zur .app hinzu und überprüfen Sie die Integrität der Dateien „PkgInfo“ und „embedded.mobileprovision“ in der .app aus dem Archiv, ggf. erneut kopieren. Wir signieren die .app erneut mit dem Codesign-Befehl – Codesign erfordert einen Eingabeschlüssel für sign, den Pfad zur Berechtigungsdatei (kann mit der Erweiterung .plist umbenannt werden)
Erstellen Sie nach dem erneuten Signieren einen Payload-Ordner, verschieben Sie den Ordner mit der Erweiterung .app dorthin, erstellen Sie ein Zip-Archiv mit Payload im Stammverzeichnis und benennen Sie das Archiv mit der Erweiterung .ipa um. Öffnen Sie anschließend in Xcode die Geräteliste und ziehen Sie das neue ipa per Drag & Drop in die Anwendungsliste des Geräts. Die Installation über Apple Configurator 2 funktioniert bei dieser Methode nicht. Wenn die Neusignierung korrekt durchgeführt wurde, wird die Anwendung mit der neuen Binärdatei auf einem iOS-Gerät (z. B. iPad) mit einem 7-Tage-Zertifikat installiert, das reicht für den Testzeitraum.
Quellen
https://github.com/tpoechtrager/cctools-port
https://docs.godotengine.org/ru/latest/development/compiling/cross-compiling_for_ios_on_linux.html
https://jonnyzzz.com/blog/2018/06/13/link-error-3/
https://stackoverflow.com/questions/6896029/re-sign-ipa-iphone
https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html
Behebung einer langsamen Festplatte in Windows 10
Dieser Hinweis richtet sich an alle Festplattennutzer, die nicht aufgeben.
Nachdem ich den HP Pavilion Laptop 1,5 Jahre lang mit einer Dual-HDD (Windows 10) und SSD (Ubuntu) verwendet hatte, bemerkte ich sehr lange Ladezeiten für Anwendungen, eine allgemeine Nichtreaktion der Benutzeroberfläche und ein Einfrieren bei den einfachsten Vorgängen in Windows 10. Das Problem wurde soweit minimiert, dass der Laptop wieder verwendet werden konnte. Als Nächstes beschreibe ich die Schritte, die ich unternommen habe, um das Problem zu beheben.
Diagnose
Um mit der Forschung zu beginnen, müssen wir zunächst jede Art von Falschmeldung beseitigen. Lassen Sie uns die Hauptursachen für Festplattenausfälle ermitteln. Was kann beim Arbeiten mit einer Festplatte schiefgehen? Probleme können auf der physischen Ebene der Elektronik und auf der logischen Software-Datenebene auftreten.
Zu den elektronischen Problemen gehören beispielsweise: ein nicht funktionierendes Computer-/Laptop-Netzteil, Probleme mit dem Laptop-Akku; Abnutzung von Festplattenkomponenten, Probleme in den Schaltkreisen und Chips der internen Komponenten des Laufwerks, Firmware-Fehler, Folgen von Stößen/Stürzen des Laufwerks oder ähnliche Probleme mit anderen Geräten, die seinen Betrieb beeinträchtigen.
Als kritischer Verschleiß einer Festplatte gilt der Moment, in dem so viele fehlerhafte Sektoren (Bad Block) auftreten, dass ein weiterer Betrieb der Festplatte unmöglich ist. Diese Blöcke werden von der Festplatten-Firmware blockiert, die Daten werden automatisch in andere Sektoren übertragen und sollten den Betrieb der Festplatte bis zu einem bestimmten kritischen Moment nicht beeinträchtigen.
Zu den Programmlogikproblemen gehören Fehler im Dateisystem aufgrund fehlerhafter Ausführung von Anwendungen, Benutzeraktionen: Ausschalten des Geräts im heißen Zustand, Abschließen von Aufzeichnungsvorgängen ohne ordnungsgemäßes Stoppen von Anwendungen, Fehler in Treibern und Betriebssystemdiensten.
Ohne spezielle elektronische Diagnosetools können wir nur die Richtigkeit des Softwarestands überprüfen. Dabei können elektronische Probleme entdeckt werden, die normalerweise durch die Blockreparaturmethode (Austausch von Komponenten/Chips) behoben werden. Als nächstes betrachten wir Softwarediagnosemethoden mithilfe von Diagnosedienstprogrammen. Es ist zu beachten, dass alle Dienstprogramme mit höchster Priorität auf dem System gestartet werden müssen, denn Andere Anwendungen können die Leistungsmessung beeinträchtigen und die Festplatte beim Lesen/Schreiben blockieren, was zu falschen Diagnoseergebnissen führt.
SMART
S.M.A.R.T. Speichergeräte-Statusüberwachungssystem – HDD, SDD, eMMC usw. Ermöglicht Ihnen, den Verschleiß des Geräts zu bewerten, die Anzahl fehlerhafter Blöcke anzuzeigen und basierend auf den Daten weitere Maßnahmen zu ergreifen. Sie können SMART in verschiedenen Anwendungen zum Arbeiten mit Datenträgern anzeigen. Ich bevorzuge die Verwendung von Dienstprogrammen des Herstellers. Für meine Seagate-Festplatte habe ich das Dienstprogramm SeaTools verwendet, dessen Status als GUT angezeigt wurde, d. h. die Festplatten-Firmware geht davon aus, dass alles in Ordnung ist.
Dienstprogramme des Herstellers
Die Dienstprogramme des Festplattenherstellers bieten Tests zur Überprüfung der Funktionsfähigkeit an. SeaTools verfügt über mehrere Arten von Tests. Sie können sie alle verwenden, um das Problem zu lokalisieren. Schnelle und einfache Tests offenbaren möglicherweise keine Probleme, bevorzugen Sie daher lange Tests. In meinem Fall hat nur Long Test Fehler gefunden.
Slowride
Um die Richtigkeit des Lesens zu überprüfen und langsame oder tote Blöcke zu finden, habe ich eine Anwendung geschrieben slowride a>, es funktioniert nach einem sehr einfachen Prinzip – öffnet einen Block-Gerätedeskriptor, mit den angegebenen Benutzereinstellungen, liest die Daten des gesamten Geräts, mit Zeitmessungen, Ausgabe langsamer Blöcke. Das Programm stoppt beim ersten Fehler; in diesem Fall müssen Sie zu ernsthafteren Dienstprogrammen zum Entfernen von Daten übergehen, da es nicht möglich ist, die Festplattendaten mit einfachen Methoden zu lesen.
In meinem Fall wurde das Lesen der gesamten Festplatte korrekt durchgeführt, mit einem leichten Geschwindigkeitsabfall – 90 MB/Sek. (5400 U/min) in einer Sekunde in einigen Bereichen der Festplatte. Daraus könnte man schließen, dass es sich bei mir um ein Softwareproblem handelte.
Akustische Analyse
Diese Methode gilt nicht für Softwarediagnosemethoden, ist aber sehr wichtig, um das Problem zu beheben. Wenn beispielsweise das Netzteil teilweise funktioniert, kann es sein, dass die Festplatte einfriert/einfriert und ein lautes Klicken von sich gibt.
In meinem Fall hörte ich beim Arbeiten mit einer Festplatte in Windows 10 etwas, das allen Festplattenbesitzern bekannt ist: lautes Knackgeräusch des Plattenkopfes, der hin und her läuft, wenn versucht wird, etwas im Betriebssystem zu tun, aber das Geräusch war fast konstant, das ließ mich denken, dass es zu viel Fragmentierung gab Festplatte, Festplattenüberlastung durch Hintergrunddienste.
Reparatur
Während der Softwarediagnose wurden keine Elektronikprobleme festgestellt; das blockweise Lesen der gesamten Festplatte wurde korrekt abgeschlossen, aber SeaTools zeigte während des Langzeittests Fehler.
Dienstprogramme des Herstellers
Zusätzlich zur Diagnose bietet die Software des Festplattenherstellers Fehlerkorrekturverfahren. In SeaTools ist hierfür die Schaltfläche „Alle reparieren“ zuständig; nach Bestätigung Ihrer Zustimmung zum möglichen Datenverlust beginnt der Korrekturvorgang. Hat dieser Fix in meinem Fall geholfen? Nein, die Festplatte arbeitete weiterhin laut und langsam, aber der Langzeittest zeigte keine Fehler mehr.
CHKDSK
CHKSDK ist ein Microsoft-Dienstprogramm zur Fehlerbehebung von Softwarefehlern für Windows-Dateisysteme. Mit der Zeit sammeln sich solche Fehler auf der Festplatte an und können die Arbeit stark beeinträchtigen, bis hin zur Unfähigkeit, überhaupt Daten zu lesen/schreiben. Anweisungen zur Verwendung des Dienstprogramms finden Sie auf der Microsoft-Website. Ich empfehle jedoch, alle möglichen Flags zu verwenden, um Fehler zu beheben (zum Zeitpunkt des Verfassens dieses Artikels ist dies /r /b /f); Sie müssen den Scan mit Administratorrechten über das Windows-Terminal (cmd) ausführen. Für die Systempartition erfolgt er beim Systemstart und kann sehr lange dauern, in meinem Fall dauerte er 12 Stunden.
Hat dieser Fix in meinem Fall geholfen? Nein.
Festplattendefragmentierung
Daten auf der Festplatte werden in Blöcken verarbeitet; große Dateien werden normalerweise in mehreren Blöcken/Fragmenten geschrieben. Im Laufe der Zeit erzeugen viele gelöschte Dateien leere Blöcke, die nicht in der Nähe sind. Aus diesem Grund füllen sie beim Schreiben von Dateien diese Lücken und der Plattenkopf muss physisch weite Strecken zurücklegen. Dieses Problem wird Fragmentierung genannt und tritt nur bei Festplattenbenutzern auf. Zum Zeitpunkt mehrerer Korrekturen betrug die Fragmentierung meiner Festplatte 41 %, optisch sah es so aus:

Das heißt, alles ist schlecht. Sie können die Fragmentierung erkennen und mit dem Defragmentierungsprogramm oder dem integrierten Defragmentierer defragmentieren. Sie können auch den Dienst „Laufwerke optimieren“ aktivieren. Planen Sie in Windows 10 die Defragmentierung in der Systemsteuerung. Nur HDD-Laufwerke benötigen eine Defragmentierung; es ist nicht ratsam, sie für SSD-Laufwerke zu aktivieren, da dies zu einem beschleunigten Festplattenverschleiß führt. Aus diesem Grund ist die Hintergrunddefragmentierung offenbar standardmäßig deaktiviert.
Eine alternative Defragmentierungsoption ist ebenfalls bekannt – Übertragen von Daten auf eine andere Festplatte, Formatieren der Festplatte und Zurückkopieren der Daten. In diesem Fall werden die Daten in völlig leere Sektoren geschrieben, während die korrekte logische Struktur für den Systembetrieb beibehalten wird. Diese Option ist mit Problemen beim Zurücksetzen potenziell kritischer Metadaten behaftet, die beim normalen Kopieren möglicherweise nicht verschoben werden.
Dienste deaktivieren
Verwenden des Dienstprogramms Process Monitor von Mark Russinovich Sie können die Prozesse verfolgen, die die Festplatte mit ihrer Arbeit belasten, indem Sie einfach die Spalten „IO Write/Read“ aktivieren. Nachdem ich diese Kolumne recherchiert hatte, habe ich den Xbox Game Bar-Dienst, den bekannten Hintergrundbeschleunigungsdienst für Superfetch-Programme unter dem neuen Namen SysMain, über das Bedienfeld „Dienste“ deaktiviert. Superfetch muss ständig die vom Benutzer verwendeten Anwendungen analysieren und deren Start durch Zwischenspeichern im RAM beschleunigen. Dies führte in meinem Fall dazu, dass die gesamte Festplatte im Hintergrund geladen wurde und nicht mehr funktionierte.
Reinigen der Festplatte
Ich habe auch alte Anwendungen und unnötige Dateien gelöscht, wodurch Sektoren für eine korrekte Fragmentierung frei wurden, die Bedienung des Betriebssystems vereinfacht und die Anzahl nutzloser, schwerer Dienste und Programme reduziert wurde.
Gesamt
Was hat am meisten geholfen? Ein spürbarer Leistungsunterschied wurde nach der Defragmentierung der Festplatte erzielt; spontane Einfrierungen wurden durch die Deaktivierung der Xbox- und Superfetch-Dienste beseitigt. Würden diese Probleme nicht auftreten, wenn ich eine SSD verwendet hätte? Probleme mit einem langsamen Betrieb durch Fragmentierung gäbe es definitiv nicht, Probleme mit Diensten müssten auf jeden Fall behoben werden und Softwarefehler seien nicht von der Art des Laufwerks abhängig. In naher Zukunft plane ich einen kompletten Umstieg auf SSD, aber vorerst gilt: „Lang lebe Pfannkuchen, Pfannkuchen für immer!“
Links
http://www.outsidethebox.ms/why-windows-8-defragments-your-ssd-and-how-you-can-avoid-this/
https://channel9.msdn.com/Shows/The-Defrag-Show
https://www.seagate.com/ru/ru/support/downloads/seatools/
https://www.ccleaner.com/defraggler/download
https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/chkdsk
https://gitlab.com/demensdeum/slowride/
Schreiben eines Backend-Servers in C++ FCGI
Eine kurze Anmerkung dazu, wie ich den Serverteil für den 3D-Editor Cube Art Project geschrieben habe: Der Server sollte die Arbeit der Benutzer der Webversion speichern und anzeigen und ihnen über die Schaltfläche „Speichern“ kurze URLs geben. Zuerst wollte ich Swift/PHP/Ruby/JS oder eine ähnliche moderne Sprache für das Backend verwenden, aber nachdem ich mir die Eigenschaften meines VPS angesehen hatte, entschied ich mich, den Server in C/C++ zu schreiben.
Zuerst müssen Sie libfcgi auf dem Server und das fcgi-Unterstützungsmodul für Ihren Webserver installieren, Beispiel für Ubuntu und Apache:
sudo apt install libfcgi libapache2-mod-fcgid
Als nächstes konfigurieren wir das Modul in der config:

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

Als nächstes schreiben wir eine Anwendung in C/C++ mit fcgi-Unterstützung, assemblieren sie und kopieren sie in den Ordner /var/www/html/cgi-bin.
Beispiele für Code und Build-Skript:
https://gitlab.com/demensdeum/cube-art-project-server/-/blob/master/src/cubeArtProjectServer.cpp
https://gitlab.com/demensdeum/cube-art-project-server/-/blob/master/src/build.sh
Danach müssen Sie Ihren Webserver neu starten:
systemctl restart apache2
Als nächstes geben Sie die notwendigen Berechtigungen ein, um den cgi-bin-Ordner über chmod auszuführen.
Danach sollte Ihr CGI-Programm über einen Browser über den Link funktionieren, Beispiel für den Cube Art Project-Server:
http://192.243.103.70/cgi-bin/cubeArtProject/cubeArtProjectServer.fcgi
Wenn etwas nicht funktioniert, sehen Sie sich die Webserverprotokolle an oder stellen Sie eine Verbindung mit einem Debugger zum laufenden Prozess her. Der Debugging-Prozess sollte sich nicht vom Debugging-Prozess einer regulären Clientanwendung unterscheiden.
Quellen
https://habr.com/ru/post/154187/ a>
http://chriswu.me/blog/writing-hello-world-in-fcgi-with-c-plus-plus/
Quellcode
https://gitlab.com/demensdeum/cube-art -Projektserver
Portieren einer C++-SDL-Anwendung auf Android
In diesem Beitrag beschreibe ich meine Erfahrungen mit der Portierung eines Prototyps eines 3D-Editors Cube Art Projectauf Android.
Schauen wir uns zunächst das Ergebnis an: Im Emulator läuft ein Editor mit einem roten 3D-Würfelcursor:

Für eine erfolgreiche Montage mussten Sie Folgendes tun:
- Installieren Sie das neueste Android SDK und NDK (je neuer die NDK-Version, desto besser).
- Laden Sie den SDL2-Quellcode herunter und verwenden Sie die Vorlage von dort, um die Android-Anwendung zu erstellen.
- SDL Image und SDL Mixer zur Baugruppe hinzufügen.
- Fügen Sie die Bibliotheken meiner Spiel-Engine und meines Toolkits sowie deren Abhängigkeiten hinzu (GLM, JSON für Modern C++)
- Assembly-Dateien für Gradle anpassen.
- C++-Code für Kompatibilität mit Android anpassen, betroffene plattformabhängige Komponenten ändern (OpenGL ES, Grafikkontextinitialisierung)
- Erstellen und testen Sie das Projekt auf dem Emulator.
Projektvorlage
Laden der Quellen SDL, SDL Image, SDL Mixer:
https://www.libsdl.org/download-2.0.php
Der Ordner „docs“ enthält detaillierte Anweisungen zum Arbeiten mit der Android-Projektvorlage. Kopieren Sie das Android-Projektverzeichnis in einen separaten Ordner, erstellen Sie einen Symlink oder kopieren Sie den SDL-Ordner nach Android-Projekt/App/jni.
Wir ersetzen das Avd-Flag durch die richtige Kennung und starten den Android-Emulator aus dem Verzeichnis Sdk:
cd ~/Android/Sdk/emulator
./emulator -avd Pixel_2_API_24
Geben Sie die Pfade im Skript an und stellen Sie das Projekt zusammen:
rm -rf app/build || true
export ANDROID_HOME=/home/demensdeum/Android/Sdk/
export ANDROID_NDK_HOME=/home/demensdeum/Android/android-ndk-r21-beta2/
./gradlew clean build
./gradlew installDebug
Die SDL-Projektvorlage mit C-Code aus der Datei sollte zusammengestellt werden
android-sdl-test-app/cube-art-project-android/app/jni/src/YourSourceHere.c
Abhängigkeiten
Laden Sie den Quellcode in den Archiven für SDL_image und SDL_mixer herunter:
https://www.libsdl.org/projects/SDL_image/
https://www.libsdl.org/projects/SDL_mixer/
Laden der Abhängigkeiten Ihres Projekts, zum Beispiel meiner gemeinsam genutzten Bibliotheken:
https://gitlab.com/demensdeum/FlameSteelCore/
https://gitlab.com/demensdeum/FlameSteelCommonTraits
https://gitlab.com/demensdeum/FlameSteelBattleHorn
https://gitlab.com/demensdeum/FlameSteelEngineGameToolkit/
https://gitlab.com/demensdeum/FlameSteelEngineGameToolkitFSGL
https://gitlab.com/demensdeum/FSGL
https://gitlab.com/demensdeum/cube-art-project
Wir laden das alles nach app/jni hoch, jedes „Modul“ in einen separaten Ordner, zum Beispiel app/jni/FSGL. Als nächstes haben Sie die Möglichkeit, funktionierende Generatoren für die Dateien Application.mk und Android.mk zu finden. Ich habe sie nicht gefunden, aber vielleicht gibt es eine einfache Lösung, die auf CMake basiert. Folgen Sie den Links und machen Sie sich mit dem Assembly-Dateiformat für Android NDK vertraut:
https://developer.android.com/ndk/guides/application_mk
https://developer.android.com/ndk/guides/android_mk
Sie sollten auch über verschiedene APP_STL-Implementierungen in NDK lesen:
https://developer.android.com/ndk/guides/cpp-support.html
Nach der Einarbeitung erstellen wir für jedes „Modul“ eine Android.mk-Datei, gefolgt von einer Beispiel-Assembly-Datei der gemeinsam genutzten Bibliothek Cube-Art-Project:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
APP_STL := c++_static
APP_CPPFLAGS := -fexceptions
LOCAL_MODULE := CubeArtProject
LOCAL_C_INCLUDES := $(LOCAL_PATH)/src $(LOCAL_PATH)/../include $(LOCAL_PATH)/../include/FlameSteelCommonTraits/src/FlameSteelCommonTraits
LOCAL_EXPORT_C_INCLUDES = $(LOCAL_PATH)/src/
define walk
$(wildcard $(1)) $(foreach e, $(wildcard $(1)/*), $(call walk, $(e)))
endef
ALLFILES = $(call walk, $(LOCAL_PATH)/src)
FILE_LIST := $(filter %.cpp, $(ALLFILES))
$(info CubeArtProject source code files list)
$(info $(FILE_LIST))
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)
LOCAL_SHARED_LIBRARIES += FlameSteelCore
LOCAL_SHARED_LIBRARIES += FlameSteelBattleHorn
LOCAL_SHARED_LIBRARIES += FlameSteelCommonTraits
LOCAL_SHARED_LIBRARIES += FlameSteelEngineGameToolkit
LOCAL_SHARED_LIBRARIES += FlameSteelEngineGameToolkitFSGL
LOCAL_SHARED_LIBRARIES += FSGL
LOCAL_SHARED_LIBRARIES += SDL2
LOCAL_SHARED_LIBRARIES += SDL2_image
LOCAL_LDFLAGS := -static-libstdc++
include $(BUILD_SHARED_LIBRARY)
Jeder erfahrene CMake-Benutzer wird diese Konfiguration von den ersten Zeilen an verstehen, die Formate sind sehr ähnlich, Android.mk fehlt GLOB_RECURSIVE, sodass Sie mit der Walk-Funktion rekursiv nach Quelldateien suchen müssen.
Wir ändern Application.mk und Android.mk, um C++ und nicht C-Code zu erstellen:
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
APP_PLATFORM=android-16
APP_STL := c++_static
APP_CPPFLAGS := -fexceptions
Benennen Sie YourSourceHere.c -> YourSourceHere.cpp um, überprüfen Sie die Einträge, ändern Sie den Pfad in der Assembly, zum Beispiel:
app/jni/src/Android.mk:LOCAL_SRC_FILES := YourSourceHere.cpp
Versuchen Sie als Nächstes, das Projekt zu erstellen. Wenn Sie vom Compiler Fehlermeldungen über das Fehlen von Headern sehen, überprüfen Sie die Richtigkeit der Pfade in Android.mk. Wenn vom Linker Fehler wie „undefinierte Referenz“ auftreten, überprüfen Sie, ob die Quellcodedateien in den Assemblys korrekt angegeben sind, indem Sie $(info $(FILE_LIST)) in der Datei Android.mk angeben. Vergessen Sie nicht den doppelten Verknüpfungsmechanismus, die Verwendung von Modulen im Schlüssel LOCAL_SHARED_LIBRARIES und die korrekte Verknüpfung über LD, zum Beispiel für FSGL:
LOCAL_LDLIBS := -lEGL -lGLESv2
Anpassung und Einführung
Ich musste einige Dinge ändern, zum Beispiel GLEW aus den Builds für iOS und Android entfernen, einige der OpenGL-Aufrufe umbenennen, das EOS-Postfix hinzufügen (glGenVertexArrays -> glGenVertexArraysOES) und ein Makro für die fehlenden modernen Debugging-Funktionen einfügen , das Sahnehäubchen ist die implizite Einbindung von GLES2-Headern, die auf Makros hinweisen GL_GLEXT_PROTOTYPES 1:
#define GL_GLEXT_PROTOTYPES 1
#include "SDL_opengles2.h"
Ich habe bei den ersten Starts auch einen schwarzen Bildschirm mit einem Fehler wie „E/libEGL: validate_display:255 error 3008 (EGL_BAD_DISPLAY)“ beobachtet, die Initialisierung des SDL-Fensters und des OpenGL-Profils geändert und alles hat funktioniert:
SDL_DisplayMode mode;
SDL_GetDisplayMode(0,0,&mode);
int width = mode.w;
int height = mode.h;
window = SDL_CreateWindow(
title,
0,
0,
width,
height,
SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_RESIZABLE
);
SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES );
Auf dem Emulator wird die Anwendung standardmäßig mit dem SDL-Symbol und dem Namen „Spiel“ installiert.
Ich muss nur die Möglichkeit erkunden, Assembly-Dateien automatisch auf Basis von CMake zu generieren oder Assemblys für alle Plattformen nach Gradle zu migrieren; CMake bleibt jedoch de facto die Wahl für die fortlaufende C++-Entwicklung.
Quellcode
https://gitlab.com/demensdeum/android- SDL-Test-App
https://gitlab.com/demensdeum/android-sdl-test-app/tree/master/cube-art-project-android
Quellen
https://developer.android.com/ ndk/guides/cpp-support.html
https://developer.android.com/ndk/guides/application_mk
https://developer.android.com/ndk/guides/android_mk
https://lazyfoo.net/tutorials/SDL/52_hello_mobile/android_windows/index.php
https://medium.com/androiddevelopers/getting-started-with-c-and-android-native-activities-2213b402ffff
Auf den Kopf gestellte Welt
Um ein neues Projekt zu entwickeln, hat Cube Art Project die Test Driven Development-Methodik übernommen. Bei diesem Ansatz wird zunächst ein Test für eine bestimmte Funktionalität der Anwendung durchgeführt und anschließend die spezifische Funktionalität implementiert. Den großen Vorteil dieses Ansatzes sehe ich darin, dass die finalen Schnittstellen möglichst unbeeinflusst von Implementierungsdetails implementiert werden, bevor mit der Entwicklung der Funktionalität begonnen wird. Bei diesem Ansatz bestimmt der Test die weitere Implementierung und bietet alle Vorteile der Vertragsprogrammierung, wenn es sich bei Schnittstellen um Verträge für eine bestimmte Implementierung handelt.
Würfelkunstprojekt – Ein 3D-Editor, in dem der Benutzer Figuren aus Würfeln baut; vor nicht allzu langer Zeit war dieses Genre sehr beliebt. Da es sich um eine grafische Anwendung handelt, habe ich beschlossen, Tests mit Screenshot-Validierung hinzuzufügen.
Um Screenshots zu validieren, müssen Sie sie aus dem OpenGL-Kontext abrufen. Dies geschieht mit der glReadPixels-Funktion. Die Beschreibung der Funktionsargumente ist einfach – Startposition, Breite, Höhe, Format (RGB/RGBA/usw.), Zeiger auf Ausgabepuffer; jeder, der mit SDL gearbeitet hat oder Erfahrung mit Datenpuffern in C hat, ersetzt einfach die notwendigen Argumente. Ich halte es jedoch für notwendig, eine interessante Funktion des glReadPixels-Ausgabepuffers zu beschreiben: Pixel werden darin von unten nach oben gespeichert, während in SDL_Surface alle grundlegenden Operationen von oben nach unten erfolgen.
Das heißt, nachdem ich einen Referenz-Screenshot aus einer PNG-Datei geladen hatte, konnte ich die beiden Puffer nicht direkt vergleichen, da einer von ihnen auf dem Kopf stand.
Um den Ausgabepuffer von OpenGL umzudrehen, müssen Sie ihn füllen, indem Sie die Screenshot-Höhe von der Y-Koordinate subtrahieren. Es ist jedoch zu bedenken, dass die Möglichkeit besteht, dass die Puffergrenzen überschritten werden, wenn Sie beim Füllen nichts subtrahieren zu Speicherbeschädigungen führen.
Da ich immer versuche, das OOP-Paradigma der „Programmierung durch Schnittstellen“ anstelle des direkten C-ähnlichen Speicherzugriffs durch Zeiger zu verwenden, hat mich das Objekt beim Versuch, Daten außerhalb des Puffers zu schreiben, dank der Grenzvalidierung in der Methode darüber informiert .
Der endgültige Code für die Methode zum Erstellen eines Screenshots im Top-Down-Stil:
auto width = params->width;
auto height = params->height;
auto colorComponentsCount = 3;
GLubyte *bytes = (GLubyte *)malloc(colorComponentsCount * width * height);
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, bytes);
auto screenshot = make_shared(width, height);
for (auto y = 0; y < height; y++) {
for (auto x = 0; x < width; x++) {
auto byteX = x * colorComponentsCount;
auto byteIndex = byteX + (y * (width * colorComponentsCount));
auto redColorByte = bytes[byteIndex];
auto greenColorByte = bytes[byteIndex + 1];
auto blueColorByte = bytes[byteIndex + 2];
auto color = make_shared(redColorByte, greenColorByte, blueColorByte, 255);
screenshot->setColorAtXY(color, x, height - y - 1);
}
}
free(bytes);
Quellen
https://community.khronos.org/ t/glreadpixels-fliped-image/26561
https://stackoverflow.com/questions/8346115/why-are-bmps-stored-upside-down
Quellcode
https://gitlab.com/demensdeum/cube- Kunstprojekt-Bootstrap
Längster gemeinsamer Teilstring
In diesem Beitrag beschreibe ich einen Algorithmus zur Lösung des größten häufigen Teilstringproblems. Nehmen wir an, wir versuchen, verschlüsselte Binärdaten zu entschlüsseln. Versuchen wir zunächst, gemeinsame Muster zu finden, indem wir nach der größten Teilzeichenfolge suchen.
Beispiel-Eingabezeichenfolge:
adasDATAHEADER??jpjjwerthhkjbcvkDATAHEADER??kkasdf
Wir suchen nach einer Zeichenfolge, die sich zweimal wiederholt:
DATAHEADER??
Präfixe
Schreiben wir zunächst eine Methode zum Vergleichen der Präfixe zweier Zeichenfolgen und lassen Sie sie die resultierende Zeichenfolge zurückgeben, in der die Zeichen des linken Präfixes den Zeichen des rechten Präfixes entsprechen.
Zum Beispiel für die Zeilen:
val lhs = "asdfWUKI"
val rhs = "asdfIKUW"
Ergebniszeichenfolge – asdf
Beispiel in Kotlin:
fun longestPrefix(lhs: String, rhs: String): String {
val maximalLength = min(lhs.length-1, rhs.length -1)
for (i in 0..maximalLength) {
val xChar = lhs.take(i)
val yChar = rhs.take(i)
if (xChar != yChar) {
return lhs.substring(0, i-1)
}
}
return lhs.substring(0,maximalLength)
}
Brute Force
Wenn etwas nicht klappt, sollten Sie zu roher Gewalt greifen. Mit der longestPrefix-Methode durchlaufen wir die Zeichenfolge in zwei Schleifen. Die erste Schleife nimmt die Zeichenfolge von i bis zum Ende, die zweite von i + 1 bis zum Ende und gibt sie weiter, um nach dem größten Präfix zu suchen. Die zeitliche Komplexität dieses Algorithmus beträgt ungefähr O(n^2) ~ O(n*^3).
Beispiel in Kotlin:
fun searchLongestRepeatedSubstring(searchString: String): String {
var longestRepeatedSubstring = ""
for (x in 0..searchString.length-1) {
val lhs = searchString.substring(x)
for (y in x+1..searchString.length-1) {
val rhs = searchString.substring(y)
val longestPrefix = longestPrefix(lhs, rhs)
if (longestRepeatedSubstring.length < longestPrefix.length) {
longestRepeatedSubstring = longestPrefix
}
}
}
return longestRepeatedSubstring
}
Suffix-Array
Für eine elegantere Lösung benötigen wir ein Werkzeug – eine Datenstruktur namens „Suffix Array“. Diese Datenstruktur ist ein Array von Teilzeichenfolgen, die in einer Schleife gefüllt werden, wobei jede Teilzeichenfolge vom nächsten Zeichen der Zeile bis zum Ende beginnt.
Zum Beispiel für die Zeile:
adasDATAHEADER??
Das Suffix-Array sieht folgendermaßen aus:
adasDATAHEADER??
dasDATAHEADER??
asDATAHEADER??
sDATAHEADER??
DATAHEADER??
ATAHEADER??
TAHEADER??
AHEADER??
HEADER??
EADER??
ADER??
DER??
ER??
R??
??
?
Wir lösen durch Sortieren
Sortieren wir das Suffix-Array, gehen wir dann alle Elemente in einer Schleife durch, wobei sich das aktuelle Element in der linken Hand (links) und das nächste in der rechten Hand (rechts) befindet, und berechnen wir das längste Präfix mithilfe des longestPrefix Methode.
Beispiel in Kotlin:
fun searchLongestRepeatedSubstring(searchString: String): String {
val suffixTree = suffixArray(searchString)
val sortedSuffixTree = suffixTree.sorted()
var longestRepeatedSubstring = ""
for (i in 0..sortedSuffixTree.count() - 2) {
val lhs = sortedSuffixTree[i]
val rhs = sortedSuffixTree[i+1]
val longestPrefix = longestPrefix(lhs, rhs)
if (longestRepeatedSubstring.length < longestPrefix.length) {
longestRepeatedSubstring = longestPrefix
}
}
return longestRepeatedSubstring
}
Die zeitliche Komplexität des Algorithmus beträgt O(N log N), was viel besser ist als eine einfache Lösung.
Quellen
https://en.wikipedia.org/wiki/Longest_common_substring_problem
Quellcode
https://gitlab.com/demensdeum/algorithms
Einfügungssortierung, Zusammenführungssortierung
Einfügungssortierung
Einfügesortierung – Jedes Element wird mit den vorherigen Elementen in der Liste verglichen und das Element wird mit dem größeren ausgetauscht, falls vorhanden, andernfalls die innere Vergleichsschleife stoppt. Da die Elemente vom ersten bis zum letzten sortiert werden, wird jedes Element mit einer bereits sortierten Liste verglichen, was *möglicherweise* die Gesamtlaufzeit verkürzt. Die zeitliche Komplexität des Algorithmus beträgt O(n^2), also identisch mit der Blasenvariante.
Sortierung zusammenführen
Sortierung zusammenführen – Die Liste wird in Gruppen eines Elements unterteilt. Anschließend werden die Gruppen paarweise „zusammengeführt“ und gleichzeitig verglichen. In meiner Implementierung werden beim Zusammenführen von Paaren die Elemente auf der linken Seite mit den Elementen auf der rechten Seite verglichen und dann in die resultierende Liste verschoben. Wenn die Elemente auf der linken Seite nicht mehr vorhanden sind, werden alle Elemente auf der rechten Seite zur Ergebnisliste hinzugefügt Liste (ihr zusätzlicher Vergleich ist unnötig, da alle Elemente in den Gruppen Sortieriterationen durchlaufen)< br />Die Arbeit dieses Algorithmus lässt sich sehr einfach parallelisieren; die Phase der Zusammenführung von Paaren kann in Threads durchgeführt werden, wobei auf das Ende der Iterationen im Dispatcher gewartet wird.
Ausgabe des Algorithmus für Single-Threaded-Ausführung:
["John", "Alice", "Mike", "#1", "Артем", "20", "60", "60", "DoubleTrouble"]
[["John"], ["Alice"], ["Mike"], ["#1"], ["Артем"], ["20"], ["60"], ["60"], ["DoubleTrouble"]]
[["Alice", "John"], ["#1", "Mike"], ["20", "Артем"], ["60", "60"], ["DoubleTrouble"]]
[["#1", "Alice", "John", "Mike"], ["20", "60", "60", "Артем"], ["DoubleTrouble"]]
[["#1", "20", "60", "60", "Alice", "John", "Mike", "Артем"], ["DoubleTrouble"]]
["#1", "20", "60", "60", "Alice", "DoubleTrouble", "John", "Mike", "Артем"]
Ausgabe des Algorithmus für die Multithread-Ausführung:
["John", "Alice", "Mike", "#1", "Артем", "20", "60", "60", "DoubleTrouble"]
[["John"], ["Alice"], ["Mike"], ["#1"], ["Артем"], ["20"], ["60"], ["60"], ["DoubleTrouble"]]
[["20", "Артем"], ["Alice", "John"], ["60", "60"], ["#1", "Mike"], ["DoubleTrouble"]]
[["#1", "60", "60", "Mike"], ["20", "Alice", "John", "Артем"], ["DoubleTrouble"]]
[["DoubleTrouble"], ["#1", "20", "60", "60", "Alice", "John", "Mike", "Артем"]]
["#1", "20", "60", "60", "Alice", "DoubleTrouble", "John", "Mike", "Артем"]
Die zeitliche Komplexität des Algorithmus beträgt O(n*log(n)), was etwas besser ist als O(n^2)
Quellen
https://en.wikipedia.org/wiki/Insertion_sort
https://en.wikipedia.org/wiki/Merge_sort
Quellcode
https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/insertionSort
https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/mergeSort
Blasensortierung in Erlang
Bubble Sort ist ziemlich langweilig, aber es wird interessanter, wenn Sie versuchen, es in einer funktionalen Sprache für die Telekommunikation zu implementieren – Erlang.
Wir haben eine Liste mit Zahlen, wir müssen sie sortieren. Der Blasensortierungsalgorithmus durchläuft die gesamte Liste und iteriert und vergleicht die Zahlen paarweise. Bei der Prüfung geschieht Folgendes: Eine kleinere Zahl wird zur Ausgabeliste hinzugefügt, oder die Zahlen werden in der aktuellen Liste vertauscht, wenn rechts weniger vorhanden sind, wird die Suche mit der nächsten Zahl in der Iteration fortgesetzt. Dieser Durchlauf wird wiederholt, bis die Liste keine Ersetzungen mehr enthält.
In der Praxis lohnt sich der Einsatz aufgrund der hohen zeitlichen Komplexität des Algorithmus nicht – O(n^2); Ich habe es in Erlang im Imperativstil implementiert, aber wenn Sie interessiert sind, können Sie nach besseren Optionen suchen:
-module(bubbleSort).
-export([main/1]).
startBubbleSort([CurrentHead|Tail]) ->
compareHeads(CurrentHead, Tail, [], [CurrentHead|Tail]).
compareHeads(CurrentHead, [NextHead|Tail], [], OriginalList) ->
if
CurrentHead < NextHead ->
compareHeads(NextHead, Tail, [CurrentHead], OriginalList);
true ->
compareHeads(CurrentHead, Tail, [NextHead], OriginalList)
end;
compareHeads(CurrentHead, [NextHead|Tail], OriginalOutputList, OriginalList) ->
if
CurrentHead < NextHead ->
OutputList = OriginalOutputList ++ [CurrentHead],
compareHeads(NextHead, Tail, OutputList, OriginalList);
true ->
OutputList = OriginalOutputList ++ [NextHead],
compareHeads(CurrentHead, Tail, OutputList, OriginalList)
end;
compareHeads(CurrentHead, [], OriginalOutputList, OriginalList) ->
OutputList = OriginalOutputList ++ [CurrentHead],
if
OriginalList == OutputList ->
io:format("OutputList: ~w~n", [OutputList]);
true ->
startBubbleSort(OutputList)
end.
main(_) ->
UnsortedList = [69,7,4,44,2,9,10,6,26,1],
startBubbleSort(UnsortedList).
Installation und Start
In Ubuntu ist Erlang sehr einfach zu installieren; geben Sie einfach sudo apt install erlang in das Terminal ein. In dieser Sprache muss jede Datei ein Modul sein, mit einer Liste von Funktionen, die extern verwendet werden können – Export. Zu den interessanten Merkmalen der Sprache gehören das Fehlen von Variablen, nur Konstanten, das Fehlen einer Standardsyntax für OOP (was die Verwendung von OOP-Techniken nicht verhindert) und natürlich parallele Berechnungen ohne Sperren basierend auf dem Akteurmodell.
Sie können das Modul entweder über die interaktive ERL-Konsole ausführen, indem Sie einen Befehl nach dem anderen ausführen, oder einfacher über escript bubbleSort.erl; In verschiedenen Fällen sieht die Datei anders aus, zum Beispiel müssen Sie für escript eine Hauptfunktion erstellen, von der aus sie gestartet wird.
Quellen
https://www.erlang.org/
https://habr.com/ru/post/197364/
Quellcode
https://gitlab.com/ demensdeum/algorithms/blob/master/bubbleSort/bubbleSort.erl
Lexikographischer Vergleichsalgorithmus
Der lexikografische String-Vergleichsalgorithmus funktioniert sehr einfach; die Zeichencodes werden in einer Schleife verglichen und das Ergebnis wird zurückgegeben, wenn die Zeichen nicht gleich sind.
Ein Beispiel für die C-Sprache finden Sie hier:
https://github.com/gcc-mirror/gcc/blob/master/libiberty/memcmp.c
Es sollte berücksichtigt werden, dass Sie Zeichen in einer einzigen statischen Codierung vergleichen müssen, zum Beispiel habe ich in Swift den zeichenweisen Vergleich in UTF-32 verwendet. Die Array-Sortieroption mit memcmp funktioniert genau für Einzelbyte-Zeichen. In anderen Fällen (Codierungen mit variabler Länge) ist die Reihenfolge möglicherweise falsch. Ich schließe die Möglichkeit einer Implementierung auf Basis von Codierungen mit variabler Länge nicht aus, aber höchstwahrscheinlich wird es um eine Größenordnung komplizierter sein.
Die zeitliche Komplexität des Algorithmus beträgt im besten Fall O(1), im Durchschnitt und im schlechtesten Fall O(n)
Quellen
https://ru.wikipedia.org/wiki/Lexicographic_order
Quellen
https://gitlab.com/demensdeum /algorithms/blob/master/lexiCompare/lexiCompare.swift
Binäre Suche
Angenommen, wir müssen herausfinden, ob die E-Mail-Adresse „demensdeum@gmail.com“ in der Liste der zulässigen E-Mail-Adressen für den Empfang von Briefen enthalten ist .
Lassen Sie uns die gesamte Liste vom ersten bis zum letzten Element durchgehen und prüfen, ob das Element mit der angegebenen Adresse übereinstimmt – Lassen Sie uns einen linearen Suchalgorithmus implementieren. Aber das wird lange dauern, oder nicht?
Um diese Frage zu beantworten, verwenden Sie die „Zeitkomplexität von Algorithmen“, „O“-Notation. Die Betriebszeit der linearen Suche entspricht im schlimmsten Fall der n-ten Anzahl von Array-Elementen. Schreiben wir dies in „O“-Notation – An). Als nächstes müssen wir erklären, dass es für jeden bekannten Algorithmus drei Leistungsindikatoren gibt: Best-Case-, Worst-Case- und Average-Case-Ausführungszeiten. Befindet sich beispielsweise die E-Mail-Adresse „demensdeum@gmail.com“ im ersten Index des Arrays, wird sie im ersten Schritt von gefunden Für den Algorithmus folgt daraus, dass die Ausführungszeit bestenfalls – O(1); und wenn am Ende der Liste, dann ist dies der schlimmste Fall – O(n)
Aber was ist mit den Details der Software-Implementierung und der Hardware-Leistung? Sie sollten einen großen Einfluss auf das O haben? Atmen Sie nun ein und stellen Sie sich vor, dass die Berechnung der Zeitkomplexität für eine abstrakte ideale Maschine berechnet wird, in der es nur diesen Algorithmus und sonst nichts gibt.
Algorithmus
Okay, es stellt sich heraus, dass die lineare Suche ziemlich langsam ist. Versuchen wir es mit der binären Suche. Zunächst sollte klargestellt werden, dass wir nicht mit Binärdaten arbeiten; dieser Name wurde dieser Methode aufgrund der Besonderheiten ihrer Arbeit gegeben. Zunächst sortieren wir das Array nach lexikografische Reihenfolge, dann nimmt der Algorithmus den Bereich des gesamten Arrays, ruft das mittlere Element des Bereichs ab und vergleicht es lexikographisch und abhängig vom Ergebnis des Vergleichs entscheidet, welcher Bereich für die weitere Suche verwendet werden soll – die obere Hälfte des Stroms oder die untere. Das heißt, bei jedem Suchschritt wird eine Entscheidung aus zwei möglichen – Binäre Logik. Dieser Schritt wird wiederholt, bis entweder das Wort gefunden wird oder nicht (der Schnittpunkt des unteren und oberen Index des Bereichs tritt auf).
Leistung dieses Algorithmus – Der beste Fall ist, wenn ein Element sofort in der Mitte des Arrays O(1) gefunden wird, der schlechteste Fall der Aufzählung ist O(log n)
Fallstricke
Bei der Implementierung der binären Suche bin ich nicht nur auf das interessante Problem der fehlenden Standardisierung des lexikografischen Vergleichs in Programmiersprachenbibliotheken gestoßen, sondern habe sogar festgestellt, dass es keinen einheitlichen Standard für die Implementierung gibt localeCompare innerhalb von JavaScript. Der ECMAScript-Standard erlaubt unterschiedliche Implementierungen dieser Funktion, weshalb beim Sortieren mit localeCompare auf verschiedenen JavaScript-Engines völlig unterschiedliche Ergebnisse beobachtet werden können.
Damit der Algorithmus korrekt funktioniert, ist es notwendig, nur denselben lexikografischen Vergleichsalgorithmus zu sortieren und zu verwenden, andernfalls funktioniert nichts. Co-aber wenn Sie beispielsweise versuchen, ein Array in Scala zu sortieren und mit NodeJS zu suchen, ohne Ihre eigene Sortierung/Sortierung einer Implementierung zu implementieren, dann erwartet Sie nichts außer Enttäuschung über die Menschheit.
Quellen
Was ist ein lexikografischer Vergleich und was stellt er dar?
Почему для вычисления сложности алгоритмов используется log N вместо lb N?
Двоичный поиск
Знай сложности алгоритмов
https://stackoverflow.com/questions/52941016/sorting-in-localecompare-in-javascript
Quellcode
https://gitlab.com/demensdeum/algorithms
Musterfassade

Fassade bezieht sich auf strukturelle Gestaltungsmuster. Es bietet eine einzige Schnittstelle, die die Arbeit mit komplexen Systemen ermöglicht, sodass Clients keine Implementierungsdetails zu diesen Systemen haben müssen, wodurch ihr Code vereinfacht wird und eine lose Kopplung zwischen Clients und Systemen auf niedrigerer Ebene implementiert wird. GoF hat ein gutes Beispiel für eine Fassade – Ein Programmiersprachen-Compiler, der verschiedenen Clients, die unterschiedliche Ziele verfolgen, die Möglichkeit bietet, Code über eine einzige Compiler-Fassadenschnittstelle zusammenzustellen.
Quellen
https://refactoring.guru/ru/design-patterns/facade
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612
Abstraktes Fabrikmuster

Abstrakte Fabrik– Bietet eine Schnittstelle zum Erstellen verwandter Objekte, ohne bestimmte Klassen anzugeben.
Mir gefällt der alternative Name für dieses Muster wirklich – Kit (Kit)
Es ist der Factory-Methode sehr ähnlich, allerdings müssen Abstrakte Fabrikendie Beziehung zwischen den erstellten Objekten beschreiben, andernfalls handelt es sich einfach um ein Gottobjekt Antimuster, das alles erschafft, ist willkürlich.
Stellen Sie sich die Entwicklung eines AR-Frameworks für Brillen vor; wir zeigen auf dem Bildschirm Indoor-Navigationspfeile, Symbole von Geschäften, interessante Orte, Fenster und Schaltflächen mit Informationen über jeden Ort an, an dem sich der Benutzer gerade befindet.
Gleichzeitig benötigen wir die Möglichkeit, das Erscheinungsbild und Verhalten von AR-Umgebungssteuerungen anzupassen. Genau für diesen Fall müssen Sie das Muster Set verwenden.
Lassen Sie uns die Schnittstelle von Abstract Factory und Abstract Products schreiben – übergeordnete Protokolle, AR-Umgebungselemente:
protocol ARFactory {
func arrow() -> ARArrow
func icon() -> ARIcon
func button() -> ARButton
func window() -> ARWindow
}
protocol ARArrow {
var image: { get }
func handleSelection()
}
protocol ARIcon {
var image: { get }
var title: String
}
protocol ARButton {
var title: String
func handleSelection()
}
protocol ARWindow {
var title: String
var draw(canvas: Canvas)
}
Jetzt müssen Kit-Entwickler eine Concrete Factory basierend auf der Abstract Factory-Schnittstelle implementieren, und sie müssen alle Elemente zusammen implementieren; der Rest der Anwendung wird in der Lage sein, mit der Factory zu arbeiten, ohne ihren Code zu ändern.< /p>
Quellen
https://refactoring.guru/ru/design-patterns /abstract-factory
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612
Fabrikmethode
Das Factory-Methode-Muster bezieht sich auf generative Designmuster.
Dieses Muster beschreibt die Erstellung einer Schnittstelle zum Erstellen eines Objekts einer bestimmten Klasse. Es scheint einfach, oder?
Theoretisch
Angenommen, wir entwickeln ein Framework für die Arbeit mit AR-Brillen. Wenn Sie den Kopf zur Seite neigen, sollte vor den Augen des Benutzers ein Menü mit verfügbaren Anwendungen erscheinen. Anwendungen werden von Drittunternehmen entwickelt, die Kunden unseres Frameworks sind. Natürlich wissen wir nicht, welche Anwendungen, Symbole und Namen angezeigt werden sollen. Daher müssen wir eine Schnittstelle bereitstellen, um das Symbol und die zugehörigen Informationen zur Anwendung zu implementieren. Nennen wir es Produkt:
protocol Product {
var name: String { get }
var image: Image { get }
var executablePath: String { get }
}
Als nächstes müssen wir eine Schnittstelle bereitstellen, damit unsere Kunden die Ausstellung einer Reihe von Anwendungen für ihr spezifisches Produkt implementieren können – eine Reihe von Anwendungssymbolen mit Namen, die wir bereits im Framework zeichnen werden.
Lassen Sie uns diese Schnittstelle schreiben – Creator-Schnittstelle, die eine Factory-Methode enthält, die ein Array von Produkten zurückgibt.
protocol Creator {
func factoryMethod() -> [Product]
}
In der Praxis
Der erste Kunde unseres AR-Frameworks war die Firma 7B – führender Anbieter von Software für Kaffeemaschinen in Honduras. Sie möchten Augmented-Reality-Brillen verkaufen, mit denen man Kaffee zubereiten, prüfen kann, ob Wasser/Bohnen voll sind, und mithilfe des Indoor-Kartenmodus den Weg zur nächsten Kaffeemaschine zeigen kann.
Sie übernehmen die Entwicklung der Software; wir sind lediglich verpflichtet, eine Dokumentation zu den Schnittstellen Creator und Product für die korrekte Anzeige der Liste der Anwendungen und deren Weitergabe bereitzustellen starten.
Nach der Übertragung der Dokumentation implementiert Unternehmen 7B mithilfe der Creator -Schnittstelle den Specific Creator – Klasse, die ein Array von Anwendungssymbolen zurückgibt. Die Symbolanwendungen selbst sind spezifische Produktklassen, die die Produkt-Schnittstelle implementieren.
Beispielcode für Spezifische Produkte:
class CoffeeMachineLocator: implements Product {
let name = “7B Coffee Machine Locator v.3000”
let image = Image.atPath(“images/locator.tga”)
let executablePath = “CoffeeMachineLocator.wasm”
}
class iPuchinno: implements Product {
let name = “iPuchinno 1.0.3”
let image = Image.atPath(“images/puchino.pvrtc”)
let executablePath = “neutron/ipuchBugFixFinalNoFreezeFixAlpha4.js”
}
Klasse Concrete Creator, die ein Array von zwei Anwendungen ergibt:
class 7BAppsCreator: implements Creator {
func factoryMethod() -> [Product] {
return [CoffeeMachineLocator(), iPuchinno()]
}
}
Danach stellt das Unternehmen 7B die Bibliothek von Concrete Products, Concrete Creator, zusammen, kombiniert sie mit unserem Framework und beginnt mit dem Verkauf von AR-Brillen für seine Kaffeemaschinen, Ergänzungen unsererseits nicht erforderlich.
Quellen
https://refactoring.guru/ru/design-patterns/command
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612
Musterbefehl
Befehlsmuster bezieht sich auf Verhaltensentwurfsmuster.
Das ist das Muster, an dem ich am längsten festgehalten habe. Es ist so einfach, dass es sehr komplex ist. Aber ich persönlich finde, dass das Schöne am Selbststudium darin besteht, dass man alle Zeit der Welt hat, ein bestimmtes Thema aus allen Blickwinkeln zu recherchieren.
In GoF wird die Anwendbarkeit also recht prägnant und klar beschrieben:
Verkapselt eine Anfrage als Objekt und ermöglicht es Ihnen, Clients mit unterschiedlichen Anfragen zu parametrisieren, Warteschlangen zu verwenden, Anfragen zu protokollieren und Abbruchvorgänge durchzuführen.
Jetzt implementieren wir eine einfache Version des Befehls aus der Beschreibung:
string fakeTrumpsRequest = “SELECT * from Users where name beginsWith DonaldTrump”
Wir haben die Anfrage in ein String-Klassenobjekt gekapselt. Es kann zum Konfigurieren von Clients, zum Hinzufügen von Befehlen zur Warteschlange, zum Protokollieren und zum Abbrechen (unter Verwendung des „Snapshot“-Musters) verwendet werden.
Mir scheint, dass dies völlig ausreicht, um SQL-Abfragen und dergleichen durchzuführen, aber dann gibt es noch Implementierungsdetails, unterschiedliche Anwendungsmöglichkeiten, die Codebasis des Musters, Client-Rollen und Hilfsklassen sind ebenfalls sehr unterschiedlich.
Materialteile
Das
Befehlsmuster beginnt mit einem Befehlsprotokoll, das eine einzelne execute()-Methode enthält. Als nächstes kommt der Spezifische Befehl und Empfänger. Der CC implementiert die Operation auf dem Empfänger und beschreibt die Verbindung zwischen dem Empfänger und der Aktion. Ist etwas unklar? Ich auch, aber lasst uns weitermachen. Der Clienterstellt eine Instanz eines Spezifischen Befehls und verknüpft ihn mit dem Empfänger. Aufrufer – Objekt, das den Prozess des Startens von Befehlen ausführt.
Jetzt versuchen wir es anhand eines Beispiels herauszufinden. Nehmen wir an, wir möchten myOS auf myPhone aktualisieren. Dazu starten wir die Anwendung myOS_Update! und drücken darin die Schaltfläche „Jetzt aktualisieren“. Nach 10 Sekunden wird das System dies tun Melden Sie ein erfolgreiches Update.
Der Client im obigen Beispiel ist die myOS_Update!-Anwendung, der Invoker ist die Schaltfläche „Jetzt aktualisieren!“ und startet den Spezifischen Befehl b>Aktualisierung des Systems mithilfe der Methodeexecute(), die auf den Receiver zugreift. Betriebssystem-Update-Daemon.
Beispiel verwenden
Akzeptieren wir die Benutzeroberfläche der myOS_Update-Anwendung! so gut, dass sie beschlossen, es als separates Produkt zu verkaufen, um eine Schnittstelle für die Aktualisierung anderer Betriebssysteme bereitzustellen. In diesem Fall implementieren wir eine Anwendung mit Unterstützung für Erweiterungen durch Bibliotheken. In den Bibliotheken wird es Implementierungen von Spezifischen Befehlen und Empfängern geben. Wir belassen den statischen/unveränderlichen Invoker , Client, Protokoll Befehle.
Es besteht also keine Notwendigkeit, veränderlichen Code zu unterstützen, da unser Code unverändert bleibt und Probleme aufgrund von Fehlern im Code ihrer Spezifischen Befehle nur bei der Implementierung auf der Clientseite auftreten können Empfänger. Außerdem besteht in dieser Implementierung keine Notwendigkeit, den Quellcode der Hauptanwendung zu übertragen, d. h. wir haben Befehle und UI-Interaktionen mithilfe des Musters Befehl gekapselt.
Quellen
https://refactoring.guru/ru/design-patterns/command
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612
Erstellen von macOS-Anwendungen für Ubuntu OSXCross CMake
In diesem Beitrag beschreibe ich die Erstellung plattformübergreifender C++-Anwendungen für macOS auf einer Ubuntu-Build-Maschine mit CMake und osxcross.
Installieren Sie zunächst die osxcross-Toolchain:
https://github.com/tpoechtrager/osxcross
Die Installation erfolgt in drei Schritten, wobei Abhängigkeiten heruntergeladen werden:
cd tools
./get_dependencies.sh
Laden Sie XCode.xip von der offiziellen Apple-Website herunter und laden Sie dann das SDK von XCode herunter:
./gen_sdk_package_pbzx.sh /media/demensdeum/2CE62A79E62A4404/LinuxSupportStorage/xcode111.xip
Ich hoffe, Sie haben im letzten Schritt die XCode-Lizenzvereinbarung gelesen? Erstellen Sie als Nächstes die Toolchain mit dem erforderlichen Präfix:
INSTALLPREFIX=/home/demensdeum/Apps/osxcross ./build.sh
Jetzt können Sie osxcross aus dem Präfixverzeichnis des vorherigen Schritts verwenden. Fügen wir ein neues Build-Makro für CMake hinzu und schreiben Sie alles Notwendige:
if (OSXCROSS)
SET(CMAKE_SYSTEM_NAME Darwin)
SET(CMAKE_C_COMPILER o64-clang)
SET(CMAKE_CXX_COMPILER o64-clang++)
SET(CMAKE_C_COMPILER_AR x86_64-apple-darwin19-ar)
SET(CMAKE_CXX_COMPILER_AR x86_64-apple-darwin19-ar)
SET(CMAKE_LINKER x86_64-apple-darwin19-ld)
SET(ENV{OSXCROSS_MP_INC} 1)
endif()
Die dynamische Verknüpfung war bei mir nicht erfolgreich, daher exportieren wir die Bibliotheken statisch:
if (OSXCROSS)
add_library(FlameSteelCore STATIC ${SOURCE_FILES})
else()
Als nächstes werden Sie vielleicht mit der Tatsache konfrontiert, dass Sie nicht über die notwendigen Bibliotheken für osxcross verfügen. Dies ist mir bei der Verwendung von SDL2 aufgefallen. osxcross unterstützt vorgefertigte Bibliothekspakete – Macports. Beispiel: Installation von SDL2-mixer:
osxcross-macports -v install libsdl2_mixer
Danach können Sie wie gewohnt mit dem Erstellen von Bibliotheken/Anwendungen im cmake-make-Link beginnen. Vergessen Sie nicht, bei Bedarf eine statische Verknüpfung von Bibliotheken anzugeben.
Manuelle Zusammenstellung von Bibliotheken
Derzeit bin ich auf das Problem einer fehlerhaften Archivierung von Bibliotheken während der statischen Verknüpfung gestoßen. Beim Erstellen der endgültigen Anwendung erhalte ich die Fehlermeldung:
file was built for archive which is not the architecture being linked (x86_64)
Sehr ähnlich zu diesem Ticket haben wir es geschafft, ein zu implementieren Dies führt zu einer Problemumgehung, die dazu führt, dass die Montage korrekt abgeschlossen wird. Entpacken wir die statische Bibliothek und erstellen sie mit dem osxcross-Archiver neu:
ar x ../libFlameSteelCore.a
rm ../libFlameSteelCore.a
x86_64-apple-darwin19-ar rcs ../libFlameSteelCore.a *.o
Ich persönlich halte auch die mangelnde Möglichkeit, macOS-Anwendungen direkt auf Ubuntu auszuführen (zumindest mit einigen Funktionen), für eines der Probleme. Natürlich gibt es ein Projekt Darling, aber die Unterstützung lässt immer noch zu wünschen übrig.
Quellen
https://github.com/tpoechtrager/osxcross





