Dans cette note, je décrirai le processus de lecture du joystick, de changement de position du sprite, de retournement horizontal, de l’émulateur Sega Genesis et potentiellement de la console elle-même.
La lecture des clics et le traitement des « événements » d’un joystick shogi se déroulent selon le schéma suivant :
- Demande de combinaison de bits de boutons enfoncés
- Lecture de morceaux de boutons enfoncés
- Traitement au niveau de la logique du jeu
Pour déplacer le sprite squelette, nous devons stocker les variables de la position actuelle.
RAM
Les variables logiques du jeu sont stockées dans la RAM ; jusqu’à présent, les gens n’ont rien trouvé de mieux. Déclarons les adresses de variables et modifions le code de rendu :
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
Comme vous pouvez le constater, l’adresse disponible pour le travail commence à 0xFF0000 et se termine à 0xFFFFFF, au total 64 Ko de mémoire sont à notre disposition. Les positions des squelettes sont déclarées à skeletonXpos, skeletonYpos, flip horizontal à skeletonHorizontalFlip.
Joypad
Par analogie avec VDP, le travail avec les joypads s’effectue via deux ports séparément – ; port de contrôle et port de données, pour le premier 0xA10009 et 0xA10003 co-no. Il y a une fonctionnalité intéressante lorsque l’on travaille avec un joypad : Vous devez d’abord demander une combinaison de boutons pour l’interrogation, puis, après avoir attendu une mise à jour sur le bus, lire les pressions requises. Pour les boutons C/B et D-pad, il s’agit de 0x40, exemple ci-dessous :
move.b #$40,joypad_one_control_port; C/B/Dpad
nop ; bus sync
nop ; bus sync
move.b joypad_one_data_port,d2
rts
Dans le registre d2, l’état des boutons enfoncés ou non enfoncés restera, en général, ce qui a été demandé via le port date restera. Après cela, accédez à la visionneuse de registre Motorola 68000 de votre émulateur préféré, voyez à quoi est égal le registre d2, en fonction des frappes au clavier. De manière intelligente, vous pouvez le découvrir dans le manuel, mais nous ne les croyons pas sur parole. Traitement ultérieur des boutons enfoncés dans le registre d2
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
Après cela, le squelette fonctionne plus lentement, ce qui était nécessaire. Comme je le sais, l’option la plus courante pour « ralentir » consiste à compter le drapeau de synchronisation verticale ; vous pouvez compter combien de fois l’écran a été dessiné, étant ainsi lié à un fps spécifique.
Liens
https://gitlab .com/demensdeum/segagenesisamples/-/blob/main/8Joypad/vasm/main.asm
Sources
https://www.chibiakumas.com/68000/platform2.php
https://huguesjohnson.com/programming/genesis/tiles-sprites/
Écriture en assemblage pour Sega Genesis #4
Dans cet article, je vais décrire comment dessiner des sprites à l’aide de l’émulateur VDP de la console Sega Genesis.
Le processus de rendu des sprites est très similaire au rendu des tuiles :
- Chargement des couleurs dans CRAM
- Importation de parties de sprites 8×8 dans la VRAM
- Remplir la table des sprites dans la VRAM
Par exemple, prenons un sprite de squelette avec une épée de 32 à 32 pixels![]()
Skeleton Guy [Animated] by Disthorn
CRAM
À l’aide d’ImaGenesis, convertissons-le en couleurs CRAM et modèles VRAM pour l’assembleur. Après cela, nous obtiendrons deux fichiers au format asm, puis nous réécrirons les couleurs à la taille du mot et les carreaux devront être placés dans le bon ordre pour le dessin.
Information intéressante : vous pouvez basculer l’auto-incrémentation VDP via le registre 0xF sur la taille des mots, cela supprimera l’incrément d’adresse du code de remplissage de couleur CRAM.
VRAM
Le manuel Shogi a le bon ordre des tuiles pour les grands sprites, mais nous sommes plus intelligents, nous prendrons donc les index du blog ChibiAkumas, commençons à compter à partir de l’index 0 :
0 4 8 12
1 5 9 13
2 6 10 14
3 7 11 15
Pourquoi tout est à l’envers ? Que veux-tu, la console est japonaise ! Cela pourrait même être de droite à gauche !
Modifions manuellement l’ordre dans le fichier de sprite asm :
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
и т.д.
Chargez le sprite comme des tuiles/motifs normaux :
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
Pour dessiner un sprite, il ne reste plus qu’à remplir la Table des Sprites
Tableau des Sprites
La table des sprites est remplie en VRAM, l’adresse de son emplacement est inscrite dans le registre VDP 0x05, l’adresse est encore une fois délicate, vous pouvez la voir dans le manuel, un exemple pour l’adresse F000 :
Ок, теперь запишем наш спрайт в таблицу. Для этого нужно заполнить “структуру” данных состоящую из четырех word. Бинарное описание структуры вы можете найти в мануале. Лично я сделал проще, таблицу спрайтов можно редактировать вручную в эмуляторе Exodus.![]()
Les paramètres de structure ressortent clairement du nom, par exemple XPos, YPos – coordonnées, Tuiles – numéro de la tuile de départ pour le dessin, HSize, VSize – tailles de sprite en ajoutant les parties 8×8, HFlip, VFlip – rotation matérielle du sprite horizontalement et verticalement.![]()
Il est très important de se rappeler que les sprites peuvent être hors écran, c’est un comportement correct, car… décharger les sprites hors écran de la mémoire – une activité assez gourmande en ressources.
Après avoir rempli les données dans l’émulateur, elles doivent être copiées de la VRAM à l’adresse 0xF000, Exodus prend également en charge cette fonctionnalité.
Par analogie avec le dessin des tuiles, on accède d’abord au port de contrôle VDP pour commencer l’écriture à l’adresse 0xF000, puis on écrit la structure sur le port de données.
Je vous rappelle que la description de l’adressage VRAM peut être lue dans le manuel ou dans le blog Algorithme sans nom .
En résumé, l’adressage VDP fonctionne comme ceci :
[..DC BA98 7654 3210…. …. …. ..FE]
Où hex est la position du bit dans l’adresse souhaitée. Les deux premiers bits correspondent au type de commande demandé, par exemple 01 – écrire dans la VRAM. Ensuite, pour l’adresse 0XF000, il s’avère :
0111 0000 0000 0000 0000 0000 0000 0011 (70000003)
En conséquence, nous obtenons le 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
Après cela, le sprite squelette sera affiché aux coordonnées 256, 256. Cool, non ?
Liens
https://gitlab.com/demensdeum /segagenesissamples/-/tree/main/7Sprite/vasm
https://opengameart.org/content/skeleton-guy-animated
Sources
https://namelessalgorithm.com/genesis/blog/vdp/ a>
https://www.chibiakumas.com/68000/platform3.php#LessonP27
https://plutiedev.com/sprites
Écriture en assemblage pour Sega Genesis #3
Dans cet article, je vais décrire comment afficher une image à partir de tuiles sur l’émulateur Sega Genesis à l’aide de l’assembleur.
L’image de démarrage Demens Deum dans l’émulateur Exodus ressemblera à ceci :

Le processus de sortie d’une image PNG à l’aide de tuiles se fait étape par étape :
- Réduire l’image à la taille de l’écran Shogi
- Convertir le format PNG en code de données d’assemblage, séparé en couleurs et en mosaïques
- Charger une palette de couleurs dans CRAM
- Chargement de tuiles/motifs dans la VRAM
- Chargement des index de tuiles aux adresses du plan A/B dans la VRAM
- Vous pouvez réduire l’image à la taille de l’écran Shogi à l’aide de votre éditeur graphique préféré, tel que Blender.
Conversion PNG
Pour convertir des images, vous pouvez utiliser l’outil ImaGenesis ; pour travailler sous wine, les bibliothèques Visual Basic 6 sont requises, elles peuvent être installées à l’aide de winetricks (winetricks vb6run), ou RICHTX32.OCX peut être téléchargé depuis Internet et placé dans le dossier de l’application pour un fonctionnement correct.< /p>
Dans ImaGenesis, vous devez sélectionner une couleur 4 bits, exporter les couleurs et les tuiles vers deux fichiers au format assembleur. Ensuite, dans le fichier avec les couleurs, vous devez mettre chaque couleur dans un mot (2 octets), pour cela vous utilisez l’opcode dc.w.
Par exemple, écran de démarrage CRAM :
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
Laissez le fichier de tuiles tel quel, il contient déjà le format correct pour le chargement. Exemple de partie d’un fichier de tuiles :
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
Comme vous pouvez le voir dans l’exemple ci-dessus, les tuiles sont une grille 8×8 composée d’indices de palette de couleurs CRAM.
Couleurs dans CRAM
Le chargement dans la CRAM s’effectue en définissant une commande de chargement de couleur sur une adresse CRAM spécifique dans le port de contrôle (contrôle vdp). Le format de commande est décrit dans le manuel du logiciel Sega Genesis (1989), j’ajouterai juste qu’il suffit d’ajouter 0x20000 à l’adresse pour passer à la couleur suivante.
Ensuite, vous devez charger la couleur dans le port de données (données vdp) ; La façon la plus simple de comprendre le chargement est d’utiliser l’exemple ci-dessous :
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
Tuiles dans VRAM
Vient ensuite le chargement des tuiles/motifs dans la mémoire vidéo VRAM. Pour cela, sélectionnez une adresse dans la VRAM, par exemple 0x00000000. Par analogie avec la CRAM, nous contactons le port de contrôle VDP avec une commande pour écrire dans la VRAM et l’adresse de départ.
Après cela, vous pouvez télécharger des mots longs vers la VRAM ; par rapport à la CRAM, vous n’avez pas besoin de spécifier l’adresse pour chaque mot long, car il existe un mode d’incrémentation automatique de la VRAM. Vous pouvez l’activer en utilisant l’indicateur de registre VDP 0x0F (dc.b $02)
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
Index de tuiles dans le plan A/B
Maintenant, nous devons remplir l’écran avec des tuiles en fonction de leur index. Pour ce faire, la VRAM est remplie à l’adresse Plane A/B, qui est inscrite dans les registres VDP (0x02, 0x04). Plus d’informations sur l’adressage délicat se trouvent dans le manuel de Sega ; dans mon exemple, l’adresse VRAM est 0xC000, téléchargeons les index ici.
Votre image remplira de toute façon l’espace VRAM hors écran, donc après avoir dessiné l’espace de l’écran, votre moteur de rendu devrait arrêter de dessiner et continuer lorsque le curseur se déplace vers une nouvelle ligne. Il existe de nombreuses options pour implémenter cela ; j’ai utilisé la version la plus simple de comptage sur deux registres du compteur de largeur d’image et du compteur de position du curseur.
Exemple de code :
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
Après cela, il ne reste plus qu’à assembler la rom à l’aide de vasm, lancer le simulateur et voir l’image.
Débogage
Tout ne fonctionnera pas tout de suite, c’est pourquoi je voudrais recommander les outils d’émulation Exodus suivants :
- Débogueur de processeur M68k
- Modification du nombre de cycles du processeur m68k (pour le mode ralenti dans le débogueur)
- Visionneuses CRAM, VRAM, plan A/B
- Lisez attentivement la documentation de m68k, les opcodes utilisés (tout n’est pas aussi évident qu’il y paraît à première vue)
- Afficher des exemples de code de jeu/démontage sur github
- Implémenter des sous-programmes d’exceptions de processeur et les traiter
Les pointeurs vers les sous-programmes d’exception du processeur sont placés dans l’en-tête de la rom ; il existe également un projet sur GitHub avec un débogueur d’exécution interactif pour Sega, appelé genesis-debugger.
Utilisez tous les outils disponibles, ayez un bon codage à l’ancienne et que Blast Processing soit avec vous !
Liens
https://gitlab.com/demensdeum /segagenesisamples/-/tree/main/6Image/vasm
http://devster.monkeeh.com/sega/imagenesis/
https://github.com/flamewing/genesis-debugger
Sources
https://www.chibiakumas.com/68000/helloworld .php#LeçonH5
https://huguesjohnson.com/programming/genesis/tiles-sprites/
Écriture en assemblage pour Sega Genesis #2
Dans cet article, je vais décrire comment charger des couleurs dans la palette Shogi en langage assembleur.
Le résultat final dans l’émulateur Exodus ressemblera à ceci :
Pour faciliter le processus, trouvez un pdf sur Internet appelé Genesis Software Manual (1989), il décrit l’ensemble du processus de manière très détaillée, en fait, cette note est un commentaire sur le manuel original.Genesis Software Manual (1989). /p>
Pour écrire des couleurs sur la puce VDP de l’émulateur Sega, vous devez effectuer les opérations suivantes :
- Désactiver la protection TMSS
- Écrire les paramètres corrects dans les registres VDP
- Écrivez les couleurs souhaitées dans CRAM
Pour l’assemblage, nous utiliserons vasmm68k_mot et un éditeur de texte préféré, par exemple echo. Le montage s’effectue avec la commande :
Порты 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
Écrire les paramètres corrects dans les registres VDP
Pourquoi définir les paramètres corrects dans les registres VDP ? L'idée est que VDP peut faire beaucoup de choses, donc avant le rendu, vous devez l'initialiser avec les fonctionnalités nécessaires, sinon il ne comprendra tout simplement pas ce qu'il attend de lui.
Chaque registre est responsable d'un réglage/mode de fonctionnement spécifique. Le manuel Segov indique tous les bits/drapeaux pour chacun des 24 registres, une description des registres eux-mêmes.
Prenons des paramètres prêts à l'emploi avec les commentaires du blog bigevilcorporation :
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, allons maintenant au port de contrôle et écrivons tous les indicateurs dans les registres VDP :
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; Отправляем цвет в порт данных
Après avoir construit et exécuté l'émulateur dans Exodus, votre écran devrait être rempli de la couleur 228.
Remplissons-le avec une deuxième couleur, basée sur le dernier octet 127.
move.l #$C07f0000,vdp_control_port ; Доступ к цвету по байту 127 в CRAM через порт контроля
move.w #69,d0; Цвет в D0
move.w d0,vdp_data_port; Отправляем цвет в порт данных
Liens
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
Sources
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/
Écriture en assemblage pour Sega Genesis #1
Le premier article consacré à l’écriture de jeux pour la console classique Sega Genesis dans Motorola 68000 Assembly.
Écrivons la boucle infinie la plus simple pour Sega. Pour cela nous aurons besoin : d’un assembleur, d’un émulateur avec un désassembleur, d’un éditeur de texte préféré, d’une compréhension de base de la structure du rhum Sega.
Pour le développement, j’utilise mon propre assembleur/désassembleur Gen68KryBaby :
https://gitlab.com/demensdeum/gen68krybaby/ p>
L’outil est développé en Python 3, pour l’assemblage un fichier avec l’extension .asm ou .gen68KryBabyDisasm est fourni en entrée, la sortie est un fichier avec l’extension .gen68KryBabyAsm.bin, qui peut être exécuté dans l’émulateur ou sur une vraie console (attention, éloignez-vous, la console risque d’exploser !)
Le désassemblage des roms est également pris en charge, pour cela vous devez soumettre un fichier rom en entrée, sans les extensions .asm ou .gen68KryBabyDisasm. Le support des opcodes augmentera ou diminuera en fonction de mon intérêt pour le sujet et de la participation des contributeurs.
Structure
L’en-tête de la ROM Sega occupe les 512 premiers octets. Il contient des informations sur le jeu, le nom, les périphériques pris en charge, la somme de contrôle et d’autres indicateurs système. Je suppose que sans titre, la console ne regardera même pas le rhum, pensant que c’est incorrect, en disant “qu’est-ce que tu me donnes ici ?”
Après l’en-tête, il y a un sous-programme/sous-programme Reset, avec lequel commence le travail du processeur m68K. D’accord, c’est une petite affaire – trouver des opcodes (codes d’opération), à savoir ne rien faire (!) et passer au sous-programme à l’adresse en mémoire. En cherchant sur Google, vous pouvez trouver l’opcode NOP, qui ne fait rien, et l’opcode JSR, qui effectue un saut inconditionnel vers l’adresse de l’argument, c’est-à-dire qu’il déplace simplement le chariot là où nous le demandons, sans aucun caprice.
Rassembler tout cela
Le donneur d’en-tête de la rom était l’un des jeux de la version bêta, actuellement enregistré sous forme de données hexadécimales.
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
Exemple complet avec en-tête de rom :
https://gitlab.com /demensdeum/segagenesisamples/-/blob/main/1InfiniteLoop/1infiniteloop.asm
Nous collectons ensuite :
Запускаем ром 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
Comment j’ai raté le gars sur le poteau ou une histoire d’ingéniosité incroyable
Dans cette note, j’écrirai sur l’importance des décisions architecturales lors du développement, de la prise en charge d’une application et dans un environnement de développement en équipe.
Auto- serviette d’opération Professeur Lucifer Gorgonzola. Rubé Goldberg
Dans ma jeunesse, j’ai travaillé sur une application de commande de taxi. Dans le programme, vous pouvez sélectionner un point de prise en charge, un point de dépôt, calculer le coût du trajet, le type de tarif et, effectivement, commander un taxi. J’ai reçu l’application lors de la dernière étape du pré-lancement ; après avoir ajouté plusieurs correctifs, l’application a été publiée dans l’AppStore. Déjà à ce stade, toute l’équipe a compris qu’il était très mal implémenté, que les modèles de conception n’étaient pas utilisés, que tous les composants du système étaient étroitement liés, en général, il était possible de l’écrire dans une grande classe continue (objet Dieu), rien n’aurait changé, de même la manière dont les classes ont mélangé leurs frontières de responsabilité et, dans leur masse totale, se sont superposées dans un couplage mort. Plus tard, la direction a décidé d’écrire l’application à partir de zéro, en utilisant la bonne architecture, ce qui a été fait et le produit final a été implémenté chez plusieurs dizaines de clients B2B.
Cependant, je vais décrire un curieux incident de l’architecture passée, dont je me réveille parfois avec des sueurs froides au milieu de la nuit, ou dont je me souviens soudainement au milieu de la journée et me mets à rire hystériquement. Le problème, c’est que je n’ai pas réussi à frapper le gars sur le poteau du premier coup, ce qui a fait échouer la plupart des applications, mais avant tout.
C’était une journée de travail ordinaire, l’un des clients a reçu pour tâche d’affiner légèrement la conception de l’application – Il est simple de déplacer l’icône au centre de l’écran de sélection de l’adresse de retrait de quelques pixels. Eh bien, après avoir professionnellement estimé la tâche à 10 minutes, j’ai élevé l’icône de 20 pixels, ne me doutant absolument de rien, j’ai décidé de vérifier la commande de taxi.
Quoi ? L’application n’affiche plus le bouton de commande ? Comment est-ce arrivé ?
Je n’en croyais pas mes yeux ; après avoir augmenté l’icône de 20 pixels, l’application a cessé d’afficher le bouton Continuer la commande. Après avoir annulé le changement, j’ai revu le bouton. Quelque chose n’allait pas ici. Après avoir passé 20 minutes dans le débogueur, j’en avais un peu marre de dérouler les spaghettis d’appels à des classes qui se chevauchent, mais j’ai découvert que *déplacer l’image change vraiment la logique de l’application*
Tout était question de l’icône au centre – un homme sur un poteau, en déplaçant la carte il sautait pour animer le mouvement de la caméra, cette animation était suivie de la disparition du bouton en bas. Apparemment, le programme pensait que l’homme décalé de 20 pixels était en train de sauter, donc selon sa logique interne, il a caché le bouton de confirmation.
Comment cela peut-il arriver ? L’*état* de l’écran ne dépend-il vraiment pas du modèle de la machine à états, mais de la *représentation* de la position de l’homme sur le poteau ?
Il s’est avéré que chaque fois que la carte est dessinée, l’application *a poussé visuellement* au milieu de l’écran et a vérifié ce qu’il y avait là, s’il y a un homme sur un poteau, cela signifie que l’animation de changement de carte est terminée et doit être affichée bouton. Si l’homme n’est pas là, alors la carte est décalée et le bouton doit être masqué.
Dans l’exemple ci-dessus, tout va bien, d’une part, c’est un exemple de machines Goldberg (machines abscons), et d’autre part, un exemple de la réticence du développeur à interagir d’une manière ou d’une autre avec d’autres développeurs de l’équipe (essayez de le comprendre sans moi), troisièmement, vous pouvez lister tous les problèmes selon SOLID, les modèles (odeurs de code), les violations MVC et bien plus encore.
Essayez de ne pas faire cela, développez-vous dans toutes les directions possibles, aidez vos collègues dans leur travail. Bonne année à tous)
Liens
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
Devinez le groupe
Dans cet article, je décrirai l’utilisation du classificateur de texte fasttext.
Fasttext – bibliothèque d’apprentissage automatique pour la classification de texte. Essayons de lui apprendre à identifier un groupe de métal par le titre de la chanson. Pour ce faire, nous utilisons l’apprentissage supervisé à l’aide d’un ensemble de données.
Créons un ensemble de données de chansons avec des noms de groupe :
__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")
Chargez le modèle entraîné et demandez à identifier le groupe par le nom de la chanson :
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 Assembleur + C = Un Amour
Dans cette note, je décrirai le processus d’appel de fonctions C depuis l’assembleur.
Essayons d’appeler printf(“Hello World!\n”); et quitter(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
Tout est beaucoup plus simple qu’il n’y paraît, dans la section .rodata nous décrirons les données statiques, dans ce cas la ligne “Hello, world!”, 10 est un caractère de nouvelle ligne, et nous n’oublions pas non plus de l’annuler.
Dans la section code nous déclarerons les fonctions externes printf, exit des bibliothèques stdio, stdlib, et nous déclarerons également la fonction d’entrée main :
extern printf
extern exit
global main
Nous passons 0 au registre de retour depuis la fonction rax, vous pouvez utiliser mov rax, 0; mais pour accélérer, ils utilisent xor rax, rax ; Ensuite, nous passons un pointeur vers la chaîne au premier argument :
Далее вызываем внешнюю функцию Си printf:
xor rax, rax
mov rdi, message
call printf
xor rdi, rdi
call exit
Par analogie, on passe 0 au premier argument et on appelle exit :
call exit
Comme disent les Américains :
Qui n'écoute personne
Ce pilaf est en train de manger @ Alexandre Pelevin
Sources
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
Code source
https://gitlab.com/demensdeum/assembly-playground
Assembleur Hello World x86_64
Dans cet article, je décrirai le processus de configuration de l’EDI, en écrivant le premier Hello World en assembleur x86_64 pour le système d’exploitation Ubuntu Linux.
Commençons par installer l’IDE SASM, l’assembleur nasm :
Далее запустим 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
Code Hello World extrait du blog James Fisher, adapté pour l'assemblage et le débogage dans SASM. La documentation SASM indique que le point d'entrée doit être une fonction nommée main, sinon le débogage et la compilation du code seront incorrects.
Qu'avons-nous fait dans ce code ? J'ai passé un appel système – accès au noyau du système d'exploitation Linux avec des arguments corrects dans les registres, un pointeur vers une chaîne dans la section données.
Sous une loupe
Regardons le code plus en détail :
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
Table de hachage
La table de hachage vous permet d’implémenter une structure de données de tableau associatif (dictionnaire) avec des performances moyennes O(1) pour les opérations d’insertion, de suppression et de recherche.
Vous trouverez ci-dessous un exemple de l’implémentation la plus simple d’une carte de hachage dans nodeJS :
Comment ça marche ? Surveillez vos mains :
- À l’intérieur de la carte de hachage se trouve un tableau
- À l’intérieur de l’élément du tableau se trouve un pointeur vers le premier nœud de la liste chaînée
- La mémoire est allouée à un tableau de pointeurs (par exemple, 65 535 éléments)
- Ils implémentent une fonction de hachage, la clé du dictionnaire est l’entrée, et en sortie elle peut tout faire, mais à la fin elle renvoie l’index de l’élément du tableau
Comment fonctionne l’enregistrement :
- A l’entrée, il y a une paire de clés – valeur
- La fonction de hachage renvoie l’index par clé
- Obtenir un nœud de liste chaînée à partir d’un tableau par index
- Vérifiez si cela correspond à la clé
- Si cela correspond, remplacez la valeur
- S’il ne correspond pas, passez au nœud suivant jusqu’à ce que nous trouvions ou trouvions un nœud avec la clé requise.
- Si le nœud n’est toujours pas trouvé, créez-le à la fin de la liste chaînée
Fonctionnement de la recherche par clé :
- A l’entrée, il y a une paire de clés – valeur
- La fonction de hachage renvoie l’index par clé
- Obtenir un nœud de liste chaînée à partir d’un tableau par index
- Vérifiez si cela correspond à la clé
- Si cela correspond, renvoyez la valeur
- S’il ne correspond pas, passez au nœud suivant jusqu’à ce que nous trouvions ou trouvions un nœud avec la clé requise.
Pourquoi avons-nous besoin d’une liste chaînée dans un tableau ? En raison de collisions possibles lors du calcul de la fonction de hachage. Dans ce cas, plusieurs paires clé-valeur différentes seront situées au même index dans le tableau, auquel cas la liste chaînée est parcourue pour trouver la clé requise.
Sources
https://ru.wikipedia.org/wiki/Hash table
https://www.youtube.com/watch?v=wg8hZxMRwcw
Code source
https://gitlab.com/demensdeum/datastructures
Travailler avec des ressources dans Android C++
Pour travailler avec des ressources sous Android via ndk – En C++, il existe plusieurs options :
- Utiliser l’accès aux ressources à partir d’un fichier apk à l’aide d’AssetManager
- Téléchargez les ressources depuis Internet et décompressez-les dans le répertoire de l’application, utilisez-les à l’aide des méthodes C++ standard
- Méthode combinée – accédez à l’archive avec les ressources dans l’apk via AssetManager, décompressez-les dans le répertoire de l’application, puis utilisez-les à l’aide des méthodes C++ standard
Ensuite, je décrirai la méthode d’accès combinée utilisée dans le moteur de jeu Flame Steel Engine.
Lorsque vous utilisez SDL, vous pouvez simplifier l’accès aux ressources à partir d’un apk ; la bibliothèque encapsule les appels à AssetManager, offrant des interfaces similaires à stdio (fopen, fread, fclose, etc.)
SDL_RWops *io = SDL_RWFromFile("files.fschest", "r");
Après avoir téléchargé l’archive de l’apk vers le tampon, vous devez remplacer le répertoire de travail actuel par le répertoire de l’application, il est disponible pour l’application sans obtenir d’autorisations supplémentaires. Pour ce faire, nous utiliserons un wrapper SDL :
chdir(SDL_AndroidGetInternalStoragePath());
Ensuite, écrivez l’archive du tampon vers le répertoire de travail actuel en utilisant fopen, fwrite, fclose. Une fois l’archive dans un répertoire accessible en C++, décompressez-la. Les archives Zip peuvent être décompressées à l’aide d’une combinaison de deux bibliothèques : minizip et zlib, le premier peut travailler avec la structure des archives, tandis que le second décompresse les données.
Pour gagner plus de contrôle et faciliter le portage, j’ai implémenté mon propre format d’archive sans compression appelé FSChest (Flame Steel Chest). Ce format prend en charge l’archivage d’un répertoire avec des fichiers et le déballage ; La hiérarchie des dossiers n’est pas prise en charge ; vous pouvez uniquement travailler avec des fichiers.
On connecte l’en-tête de la bibliothèque FSChest, on décompresse l’archive :
#include "fschest.h"
FSCHEST_extractChestToDirectory(archivePath, SDL_AndroidGetInternalStoragePath());
Après le déballage, les interfaces C/C++ auront accès aux fichiers de l’archive. Ainsi, je n’ai pas eu à réécrire tout le travail avec les fichiers dans le moteur, mais j’ai seulement ajouté le déballage des fichiers au stade du lancement.
Sources
https://developer.android.com/ndk/ référence/groupe/actif
Code source
https://gitlab.com/demensdeum/space- jaguar-action-rpg
https://gitlab.com/demensdeum/fschest
Machine à empiler et RPN
Supposons que nous devions implémenter un simple interpréteur de bytecode, quelle approche pour implémenter cette tâche devrions-nous choisir ?
Structure des données La pile offre la possibilité d’implémenter une machine de bytecode simple. Les fonctionnalités et les implémentations des machines à pile sont décrites dans de nombreux articles sur l’Internet occidental et national ; je mentionnerai simplement que la machine virtuelle Java est un exemple de machine à pile.
Le principe de fonctionnement de la machine est simple, un programme contenant des données et des codes d’opération (opcodes) est fourni à l’entrée, et les opérations nécessaires sont mises en œuvre à l’aide de manipulations avec la pile. Regardons un exemple de programme de bytecode de ma machine à pile :
пMVkcatS olleHП
En sortie, nous recevrons la chaîne « Hello StackVM ». La machine à pile lit le programme de gauche à droite, chargeant les données caractère par caractère sur la pile lorsqu’un opcode apparaît dans le symbole – implémente la commande en utilisant la pile.
Exemple d’implémentation d’une stack machine dans nodejs :
Notation polonaise inversée (RPN)
Les machines Stack sont également faciles à utiliser pour mettre en œuvre des calculatrices, pour cela elles utilisent la notation polonaise inversée (notation suffixe).
Exemple de notation infixe régulière :
2*2+3*4
Convertit en RPN :
22*34*+
Pour compter l’enregistrement postfix, nous utilisons une machine à pile :
2– en haut de la pile (pile : 2)
2– en haut de la pile (pile : 2,2)
*– obtenir le haut de la pile deux fois, multiplier le résultat, l’envoyer en haut de la pile (pile : 4)
3– en haut de la pile (pile : 4, 3)
4– en haut de la pile (pile : 4, 3, 4)
*– obtenir le haut de la pile deux fois, multiplier le résultat, l’envoyer en haut de la pile (pile : 4, 12)
+– récupérez deux fois le haut de la pile, ajoutez le résultat, envoyez-le en haut de la pile (pile : 16)
Comme vous pouvez le voir – le résultat des opérations 16 reste sur la pile, il peut être imprimé en implémentant des opcodes d’impression de pile, par exemple :
p22*34*+P
P – Opcode de démarrage de l’impression de la pile, p – opcode pour terminer l’impression de la pile et envoyer la ligne finale pour le rendu.
Pour convertir les opérations arithmétiques d’infixe en suffixe, l’algorithme d’Edsger Dijkstra appelé « Sorting Yard » est utilisé. Un exemple d’implémentation peut être vu ci-dessus, ou dans le référentiel du projet de pile de machines nodejs ci-dessous.
Sources
https:/ /tech.badoo.com/ru/article/579/interpretatory-bajt-kodov-svoimi-rukami/
https://ru.wikipedia.org/wiki/Обратная_польская_запись
Code source
https://gitlab.com/demensdeum/stackvm/< /p>
Animation squelettique (partie 2 & # 8211; hiérarchie de nœuds, interpolation)
Je continue à décrire l’algorithme d’animation squelettique tel qu’il est implémenté dans Flame Steel Engine.
Étant donné que l’algorithme est le plus complexe de tous ceux que j’ai implémentés, des erreurs peuvent apparaître dans les notes sur le processus de développement. Dans l’article précédent sur cet algorithme, j’ai commis une erreur : le réseau d’os est transféré au shader pour chaque maillage séparément, et non pour l’ensemble du modèle.
Hiérarchie des nœuds
Pour que l’algorithme fonctionne correctement, il est nécessaire que le modèle contienne une connexion entre les os entre eux (graphique). Imaginons une situation dans laquelle deux animations sont jouées simultanément – sautez et levez la main droite. L’animation de saut doit soulever le modèle le long de l’axe Y, tandis que l’animation de lever de bras doit en tenir compte et monter avec le modèle au fur et à mesure qu’il saute, sinon le bras restera en place tout seul.
Nous décrirons la connexion des nœuds pour ce cas – le corps contient la main. Lors de l’élaboration de l’algorithme, le graphique osseux sera lu, toutes les animations seront prises en compte avec les connexions correctes. Dans la mémoire du modèle, le graphique est stocké séparément de toutes les animations, uniquement pour refléter la connectivité des os du modèle.
Interpolation sur CPU
Dans le dernier article, j’ai décrit le principe du rendu de l’animation squelettique : “Les matrices de transformation sont transférées du CPU au shader à chaque image de rendu.”
Chaque image de rendu est traitée sur le processeur ; pour chaque os de maillage, le moteur reçoit la matrice de transformation finale en utilisant l’interpolation de position, la rotation et le zoom. Lors de l’interpolation de la matrice osseuse finale, un passage est effectué à travers l’arborescence des nœuds pour toutes les animations de nœuds actives, la matrice finale est multipliée par celles parent, puis envoyée pour rendu au vertex shader.
Les vecteurs sont utilisés pour l’interpolation de position et le grossissement ; les quaternions sont utilisés pour la rotation, car ils sont très faciles à interpoler (SLERP), contrairement aux angles d’Euler, et ils sont également très faciles à représenter sous forme de matrice de transformation.
Comment simplifier la mise en œuvre
Pour faciliter le débogage du vertex shader, j’ai ajouté une simulation du vertex shader sur le CPU à l’aide de la macro FSGLOGLNEWAGERENDERER_CPU_BASED_VERTEX_MODS_ENABLED. Le fabricant de la carte vidéo NVIDIA dispose d’un utilitaire de débogage du code de shader Nsight, peut-être qu’il peut aussi simplifier le développement d’algorithmes complexes de shader de sommets/pixels, mais je n’ai jamais pu tester sa fonctionnalité sur le CPU, c’était suffisant.
Dans le prochain article, je prévois de décrire le mélange de plusieurs animations et de combler les lacunes restantes.
Sources
https://www.youtube.com/watch?v= f3Cr8Yx3GGA
Ajout de la prise en charge des scripts JavaScript en C++
Dans cet article, je décrirai une manière d’ajouter la prise en charge des scripts JavaScript à une application C++ à l’aide de la bibliothèque Tiny-JS.
Tiny-JS est une bibliothèque à intégrer en C++, permettant l’exécution de code JavaScript, avec prise en charge des liaisons (possibilité d’appeler du code C++ à partir de scripts)
Au début, je voulais utiliser les bibliothèques populaires ChaiScript, Duktape ou connect Lua, mais en raison des dépendances et des éventuelles difficultés de portabilité vers différentes plates-formes, il a été décidé de trouver une bibliothèque MIT JS simple, minimale mais puissante ; JS répond à ces critères. Le seul inconvénient de cette bibliothèque est le manque de support/développement par l’auteur, mais son code est assez simple, ce qui permet de prendre en charge le support si nécessaire.
Téléchargez Tiny-JS depuis le référentiel :
https://github.com/gfwilliams/tiny-js
Ensuite, ajoutez les en-têtes Tiny-JS au code responsable des scripts :
#include "tiny-js/TinyJS.h"
#include "tiny-js/TinyJS_Functions.h"
Ajoutez les fichiers TinyJS .cpp à l’étape de construction, vous pourrez alors commencer à écrire des scripts de chargement et d’exécution.
Un exemple d’utilisation de la bibliothèque est disponible dans le dépôt :
https://github.com/gfwilliams/tiny-js/blob/master/Script.cpp
https://github.com/gfwilliams/tiny-js/blob/wiki/CodeExamples.md
Un exemple d’implémentation de la classe handler peut être trouvé dans le projet SpaceJaguar :
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
Exemple de script de jeu ajouté à l’application :
https://gitlab.com/demensdeum/space-jaguar-action-rpg/-/blob/master/project/resources/com.demensdeum.spacejaguaractionrpg.scripts.sceneController.js
Sources
https://github.com/gfwilliams/tiny-js
https://github.com/dbohdan/embedded-scripting-languages
https://github.com/AlexKotik/embeddable-scripting-languages
Création d’une application C++ SDL pour iOS sous Linux
Dans cet article, je décrirai la procédure pour créer une application C++ SDL pour iOS sous Linux, signer une archive ipa sans abonnement Apple Developer payant et l’installer sur un appareil propre (iPad) utilisant macOS sans Jailbreak.< /p>
Tout d’abord, installons la chaîne d’outils de build pour Linux :
https://github.com/tpoechtrager/cctools-port
La chaîne d’outils doit être téléchargée depuis le référentiel, puis suivez les instructions sur le site Web Godot Engine pour terminer l’installation :
https://docs.godotengine.org/ru/latest/development/compiling/cross-compiling_for_ios_on_linux.html
Pour le moment, vous devez télécharger Xcode dmg et copier le SDK à partir de là pour créer le port cctools. Cette étape est plus facile à réaliser sur macOS ; copiez simplement les fichiers SDK nécessaires à partir du Xcode installé. Après un assemblage réussi, le terminal contiendra le chemin d’accès à la chaîne d’outils du compilateur croisé.
Vous pouvez ensuite commencer à créer l’application SDL pour iOS. Ouvrons cmake et ajoutons les modifications nécessaires pour construire le code C++ :
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)
Vous pouvez maintenant compiler en utilisant cmake et make, mais n’oubliez pas d’ajouter $PATH à la chaîne d’outils du compilateur croisé :
PATH=$PATH:~/Sources/cctools-port/usage_examples/ios_toolchain/target/bin
Pour une liaison correcte avec les frameworks et SDL, nous les écrivons en cmake, dépendances du jeu Space Jaguar par exemple :
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"
)
Dans mon cas, les bibliothèques SDL, SDL_Image et SDL_mixer sont compilées à l’avance dans Xcode sur macOS pour les liaisons statiques ; Frameworks copiés depuis Xcode. La bibliothèque libclang_rt.ios.a a également été ajoutée, qui inclut des appels d’exécution spécifiques à iOS, par exemple isOSVersionAtLeast. Une macro est incluse pour travailler avec OpenGL ES, désactivant les fonctions non prises en charge dans la version mobile, similaire à Android.
Après avoir résolu tous les problèmes de construction, vous devriez obtenir le binaire assemblé pour arm. Ensuite, envisageons d’exécuter le binaire assemblé sur un appareil sans Jailbreak.
Sur macOS, installez Xcode, inscrivez-vous sur le portail Apple, sans payer pour le programme développeur. Ajouter un compte dans Xcode -> Préférences -> Comptes, créez une application vierge et construisez sur un appareil réel. Lors de l’assemblage, l’appareil sera ajouté au compte développeur gratuit. Après l’assemblage et le lancement, vous devez créer l’archive ; pour ce faire, sélectionnez Appareil et produit iOS génériques -> Archive. Une fois l’archive créée, extrayez-en les fichiersembedded.mobileprovision et PkgInfo. Depuis le journal de build vers l’appareil, recherchez la ligne de codedesign avec la clé de signature correcte, le chemin d’accès au fichier de droits avec l’extension app.xcent, copiez-le.
Copiez le dossier .app de l’archive, remplacez le binaire de l’archive par un compilé par un compilateur croisé sous Linux (par exemple SpaceJaguar.app/SpaceJaguar), puis ajoutez les ressources nécessaires au .app, vérifiez le intégrité des fichiers PkgInfo et Embedded.mobileprovision dans le .app à partir de l’archive, copiez à nouveau si nécessaire. Nous re-signons le .app à l’aide de la commande codesign – le codedesign nécessite une clé d’entrée pour la signature, le chemin d’accès au fichier de droits (peut être renommé avec une extension .plist)
Après la re-signature, créez un dossier Payload, déplacez-y le dossier avec l’extension .app, créez une archive zip avec Payload à la racine, renommez l’archive avec l’extension .ipa. Après cela, dans Xcode, ouvrez la liste des appareils et faites glisser le nouvel ipa vers la liste des applications de l’appareil ; L’installation via Apple Configurator 2 ne fonctionne pas pour cette méthode. Si la re-signature est effectuée correctement, alors l’application avec le nouveau binaire sera installée sur un appareil iOS (par exemple iPad) avec un certificat de 7 jours, cela suffit pour la période de test.
Sources
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
Réparer un disque dur lent sous Windows 10
Cette note est dédiée à tous les utilisateurs de disques durs qui n’abandonnent pas.
Après 1 an et demi d’utilisation de l’ordinateur portable HP Pavilion avec un double disque dur (Windows 10) et SSD (Ubuntu), j’ai commencé à remarquer des temps de chargement très longs pour les applications, une absence de réponse générale de l’interface et des blocages sur les opérations les plus simples. sous Windows 10. Le problème a été minimisé dans la mesure où il est devenu possible d’utiliser à nouveau l’ordinateur portable. Ensuite, je décrirai les étapes que j’ai suivies pour résoudre le problème.
Diagnostic
Pour commencer la recherche, nous devons éliminer tout type de canular ; commençons par déterminer les principales causes des pannes de disque dur. Qu’est-ce qui peut mal se passer lorsque l’on travaille avec un disque dur ? Des problèmes peuvent survenir au niveau physique de l’électronique et au niveau des données logiques et logicielles.
Les problèmes électroniques incluent des éléments tels que : une alimentation électrique d’ordinateur/ordinateur portable qui ne fonctionne pas, des problèmes avec la batterie de l’ordinateur portable ; usure des composants du disque dur, problèmes dans les circuits et puces des composants internes du disque, erreurs de micrologiciel, conséquences de chocs/chutes du disque ou problèmes similaires avec d’autres appareils qui affectent son fonctionnement.
L’usure critique d’un disque dur est considérée comme le moment où un tel nombre de secteurs défectueux (bloc défectueux) apparaît qu’un fonctionnement ultérieur du disque est impossible. Ces blocs sont bloqués par le firmware du disque dur, les données sont transférées automatiquement vers d’autres secteurs et ne devraient affecter le fonctionnement du disque qu’à un certain moment critique.
Les problèmes de logique du programme incluent des erreurs dans le système de fichiers dues à un fonctionnement incorrect des applications, aux actions de l’utilisateur : éteindre l’appareil lorsqu’il est chaud, terminer les processus d’enregistrement sans arrêter correctement les applications, des erreurs dans les pilotes, les services du système d’exploitation.
Sans outils de diagnostic électronique spécialisés, nous ne pouvons que vérifier l’exactitude du niveau du logiciel ; ce faisant, des problèmes électroniques peuvent être découverts, qui sont généralement éliminés par la méthode de réparation en bloc (remplacement de composants/puces) ; Ensuite, nous examinerons les méthodes de diagnostic logiciel utilisant des utilitaires de diagnostic. Il convient de noter que tous les utilitaires doivent être lancés sur le système avec la priorité maximale, car d’autres applications peuvent interférer avec les mesures de performances et bloquer la lecture/écriture du disque, ce qui entraînera des résultats de diagnostic incorrects.
INTELLIGENT
S.M.A.R.T. système de surveillance de l’état des périphériques de stockage – HDD, SDD, eMMC, etc. Vous permet d’évaluer l’usure de l’appareil, d’afficher le nombre de blocs défectueux et de prendre d’autres actions en fonction des données. Vous pouvez afficher SMART dans différentes applications pour travailler avec des disques ; je préfère utiliser les utilitaires du fabricant. Pour mon disque dur Seagate, j’ai utilisé l’utilitaire SeaTools, pour lequel l’état était affiché comme BON, c’est-à-dire que le micrologiciel du disque pense que tout va bien.
Utilitaires du fabricant
Les utilitaires du fabricant du disque proposent des tests pour vérifier son fonctionnement. SeaTools propose plusieurs types de tests, vous pouvez tous les utiliser pour localiser le problème. Des tests simples et rapides peuvent ne révéler aucun problème, préférez donc les tests longs. Dans mon cas, seul Long Test a trouvé des erreurs.
Slowride
Pour vérifier l’exactitude de la lecture, trouver des blocs lents ou morts, j’ai écrit une application slowride a>, cela fonctionne sur un principe très simple – ouvre un descripteur de périphérique de bloc, avec les paramètres utilisateur spécifiés, lit les données de l’ensemble du périphérique, avec des mesures de temps, la sortie de blocs lents. Le programme s’arrête à la première erreur ; dans ce cas, vous devrez passer à des utilitaires de suppression de données plus sérieux, car il n’est pas possible de lire les données du disque avec des méthodes simples.
Dans mon cas, la lecture de l’intégralité du disque s’est effectuée correctement, avec une légère baisse de vitesse – 90 Mo/s (5 400 tr/min) en une seconde, sur certaines zones du disque. D’où on pourrait conclure que j’avais affaire à un problème logiciel.
Analyse acoustique
Cette méthode ne s’applique pas aux méthodes de diagnostic logiciel, mais il est très important de résoudre le problème. Par exemple, si l’alimentation électrique fonctionne partiellement, le disque dur peut geler/geler et émettre un clic fort.
Dans mon cas, lorsque je travaillais avec un disque sous Windows 10, j’ai entendu quelque chose de familier à tous les propriétaires de disque dur, bruit de craquement fort de la tête du disque qui va et vient lorsque vous essayez de faire quelque chose dans le système d’exploitation, mais le son était presque constant, cela m’a fait penser qu’il y avait trop de fragmentation disque, surcharge du disque avec les services en arrière-plan.
Réparer
Aucun problème électronique n’a été détecté lors des diagnostics logiciels ; la lecture bloc par bloc de l’intégralité du disque s’est terminée correctement, mais SeaTools a montré des erreurs lors du test long.
Utilitaires du fabricant
En plus des diagnostics, le logiciel du fabricant du disque fournit des procédures de correction des erreurs. Dans SeaTools, le bouton Réparer tout en est responsable ; après avoir confirmé votre consentement à la perte potentielle de données, le processus de correction commencera. Ce correctif a-t-il été utile dans mon cas ? Non, le disque a continué à fonctionner bruyamment et lentement, mais le test long n’a plus montré d’erreurs.
CHKDSK
CHKSDK est un utilitaire Microsoft permettant de dépanner les erreurs logicielles des systèmes de fichiers Windows. Au fil du temps, ces erreurs s’accumulent sur le disque et peuvent grandement interférer avec le travail, conduisant notamment à l’impossibilité de lire/écrire des données. Vous pouvez trouver des instructions d’utilisation de l’utilitaire sur le site Web de Microsoft, mais je vous recommande d’utiliser tous les indicateurs possibles pour corriger les erreurs (au moment de la rédaction, il s’agit de /r /b /f) ; Vous devez exécuter l’analyse avec les droits d’administrateur via le terminal Windows (cmd), pour la partition système, elle aura lieu au démarrage du système, et cela peut prendre très longtemps, dans mon cas, cela a pris 12 heures.
Ce correctif a-t-il été utile dans mon cas ? Non.
Défragmentation de disque
Les données sur le disque sont traitées en blocs ; les fichiers volumineux sont généralement écrits en plusieurs blocs/fragments. Au fil du temps, de nombreux fichiers supprimés créent des blocs vides qui ne sont pas à proximité, de ce fait, lors de l’écriture de fichiers, ils remplissent ces vides et la tête de disque doit parcourir physiquement de longues distances. Ce problème est appelé fragmentation et seuls les utilisateurs de disques durs en sont confrontés. Lors de plusieurs correctifs, la fragmentation de mon disque dur était de 41%, visuellement cela ressemblait à ceci :

Autrement dit, tout va mal. Vous pouvez voir la fragmentation et la défragmenter à l’aide de l’utilitaire Defragger ou du défragmenteur intégré. Vous pouvez également activer le service « Optimiser les lecteurs ». sous Windows 10, planifiez la défragmentation dans le panneau de configuration. Seuls les disques durs nécessitent une défragmentation ; il n’est pas conseillé de l’activer pour les disques SSD, car cela entraînerait une usure accélérée du disque, apparemment pour cette raison, la défragmentation en arrière-plan est désactivée par défaut.
Une autre option de défragmentation est également connue : transférer des données sur un autre disque, formater le disque et recopier les données. Dans ce cas, les données seront écrites dans des secteurs complètement vides, tout en conservant la structure logique correcte pour le fonctionnement du système. Cette option pose de nombreux problèmes lors de la réinitialisation des métadonnées potentiellement critiques qui peuvent ne pas bouger lors d’une copie normale.
Désactiver les services
À l’aide de l’utilitaire Process Monitor vous pouvez suivre les processus qui chargent le disque dur avec leur travail, activez simplement les colonnes IO Write/Read. Après avoir recherché cette chronique, j’ai désactivé le service Xbox Game Bar, le service d’accélération en arrière-plan bien connu pour les programmes Superfetch sous le nouveau nom SysMain, via le panneau des services du panneau de configuration. Superfetch doit constamment analyser les applications que l’utilisateur utilise et accélérer leur lancement en les mettant en cache dans la RAM ; dans mon cas, cela a conduit au chargement en arrière-plan de l’ensemble du disque et à l’incapacité de travailler.
Nettoyer le disque
J’ai également supprimé les anciennes applications et les fichiers inutiles, libérant ainsi des secteurs pour une fragmentation correcte, simplifiant le fonctionnement du système d’exploitation, réduisant le nombre de services et de programmes inutiles et lourds.
Total
Qu’est-ce qui a le plus aidé ? Une différence notable de performances a été obtenue après la défragmentation du disque ; les blocages spontanés ont été éliminés en désactivant les services Xbox et Superfetch. Ces problèmes ne se produiraient-ils pas si j’avais utilisé un SSD ? Il n’y aurait certainement aucun problème de fonctionnement lent dû à la fragmentation, les problèmes de services devraient de toute façon être résolus et les erreurs logicielles ne dépendent pas du type de lecteur. Dans un avenir proche, je prévois une transition complète vers le SSD, mais pour l’instant « Vive les crêpes, les crêpes pour toujours ! »
Liens
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/
Écrire un serveur backend en C++ FCGI
Une brève note sur la façon dont j’ai écrit la partie serveur pour l’éditeur 3D Cube Art Project, le serveur doit enregistrer et afficher le travail des utilisateurs de la version Web, en leur donnant des URL courtes à l’aide du bouton Enregistrer. Au début, je voulais utiliser Swift/PHP/Ruby/JS ou un langage moderne similaire pour le backend, mais après avoir examiné les caractéristiques de mon VPS, j’ai décidé d’écrire le serveur en C/C++.
Vous devez d’abord installer libfcgi sur le serveur et le module de support fcgi pour votre serveur Web, exemple pour Ubuntu et Apache :
sudo apt install libfcgi libapache2-mod-fcgid
Ensuite, nous configurons le module dans la configuration :

FcgidMaxProcessesPerClass – nombre maximum de processus par classe, je l’ai défini sur 1 processus car je ne m’attends pas à une charge importante.
AddHandler fcgid-script .fcgi – extension de fichier avec laquelle le module fcgi doit démarrer.
Ajoutez à la config le dossier à partir duquel les applications cgi seront lancées :

Ensuite, nous écrivons une application en C/C++ avec le support fcgi, l’assemblons et la copions dans le dossier /var/www/html/cgi-bin.
Exemples de code et de script de build :
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
Après cela, vous devrez redémarrer votre serveur Web :
systemctl restart apache2
Ensuite, entrez les autorisations nécessaires pour exécuter le dossier cgi-bin via chmod.
Après cela, votre programme cgi devrait fonctionner via un navigateur en utilisant le lien, exemple pour le serveur Cube Art Project :
http://192.243.103.70/cgi-bin/cubeArtProject/cubeArtProjectServer.fcgi
Si quelque chose ne fonctionne pas, consultez les journaux du serveur Web ou connectez-vous avec un débogueur au processus en cours ; le processus de débogage ne doit pas différer du processus de débogage d’une application client standard.
Sources
https://habr.com/ru/post/154187/ a>
http://chriswu.me/blog/writing-hello-world-in-fcgi-with-c-plus-plus/
Code source
https://gitlab.com/demensdeum/cube-art -serveur-de-projet
Portage d’une application C++ SDL sur Android
Dans cet article, je décrirai mon expérience de portage d’un prototype d’éditeur 3D Cube Art Projectsur Android.
Tout d’abord, regardons le résultat : un éditeur avec un curseur cubique 3D rouge est exécuté dans l’émulateur :

Pour réussir l’assemblage, vous deviez procéder comme suit :
- Installez les derniers SDK et NDK Android (plus la version du NDK est récente, mieux c’est).
- Téléchargez le code source SDL2, puis prenez le modèle à partir de là pour créer l’application Android.
- Ajoutez une image SDL et un mélangeur SDL à l’assemblage.
- Ajouter les bibliothèques de mon moteur de jeu et de mon kit d’outils, leurs dépendances (GLM, JSON pour Modern C++)
- Adapter les fichiers d’assemblage pour Gradle.
- Adapter le code C++ pour la compatibilité avec Android, modifications affectées aux composants dépendants de la plate-forme (OpenGL ES, initialisation du contexte graphique)
- Créez et testez le projet sur l’émulateur.
Modèle de projet
Chargement des sources SDL, SDL Image, SDL Mixer :
https://www.libsdl.org/download-2.0.php
Le dossier docs contient des instructions détaillées pour travailler avec le modèle de projet Android ; copiez le répertoire du projet Android dans un dossier séparé, créez un lien symbolique ou copiez le dossier SDL dans Android-project/app/jni.
Nous substituons l’identifiant correct au drapeau avd, lançons l’émulateur Android depuis le répertoire Sdk :
cd ~/Android/Sdk/emulator
./emulator -avd Pixel_2_API_24
Spécifiez les chemins dans le script, assemblez le projet :
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
Le modèle de projet SDL avec le code C du fichier doit être assemblé
android-sdl-test-app/cube-art-project-android/app/jni/src/YourSourceHere.c
Dépendances
Téléchargez le code source dans les archives pour SDL_image, SDL_mixer :
https://www.libsdl.org/projects/SDL_image/
https://www.libsdl.org/projects/SDL_mixer/
Chargement des dépendances de votre projet, par exemple mes bibliothèques partagées :
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
Nous téléchargeons tout cela dans app/jni, chaque « module » dans un dossier séparé, par exemple app/jni/FSGL. Ensuite, vous avez la possibilité de trouver des générateurs fonctionnels pour les fichiers Application.mk et Android.mk, je ne les ai pas trouvés, mais il existe peut-être une solution simple basée sur CMake. Suivez les liens et commencez à vous familiariser avec le format de fichier d’assemblage pour Android NDK :
https://developer.android.com/ndk/guides/application_mk
https://developer.android.com/ndk/guides/android_mk
Vous devriez également en savoir plus sur les différentes implémentations d’APP_STL dans NDK :
https://developer.android.com/ndk/guides/cpp-support.html
Après familiarisation, nous créons un fichier Android.mk pour chaque « module », suivi d’un exemple de fichier d’assemblage de la bibliothèque partagée 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)
Tout utilisateur expérimenté de CMake comprendra cette configuration dès les premières lignes, les formats sont très similaires, Android.mk n’a pas GLOB_RECURSIVE, vous devez donc rechercher de manière récursive les fichiers sources à l’aide de la fonction walk.
Nous modifions Application.mk, Android.mk pour créer du code C++ et non C :
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
APP_PLATFORM=android-16
APP_STL := c++_static
APP_CPPFLAGS := -fexceptions
Renommer YourSourceHere.c -> YourSourceHere.cpp, récupérer les entrées, modifier le chemin dans l’assembly, par exemple :
app/jni/src/Android.mk:LOCAL_SRC_FILES := YourSourceHere.cpp
Ensuite, essayez de construire le projet, si vous voyez des erreurs du compilateur concernant l’absence d’en-têtes, puis vérifiez l’exactitude des chemins dans Android.mk ; S’il y a des erreurs de l’éditeur de liens comme « référence non définie », vérifiez que les fichiers de code source dans les assemblys sont correctement spécifiés ; les listes peuvent être tracées en spécifiant $(info $(FILE_LIST)) dans le fichier Android.mk. N’oubliez pas le mécanisme de double liaison, en utilisant des modules dans la clé LOCAL_SHARED_LIBRARIES et en corrigeant la liaison via LD, par exemple pour FSGL :
LOCAL_LDLIBS := -lEGL -lGLESv2
Adaptation et lancement
J’ai dû modifier certaines choses, par exemple supprimer GLEW des versions pour iOS et Android, renommer certains appels OpenGL, ajouter le suffixe EOS (glGenVertexArrays -> glGenVertexArraysOES), inclure une macro pour les fonctions de débogage modernes manquantes , la cerise sur le gâteau est l’inclusion implicite des en-têtes GLES2 indiquant la macro GL_GLEXT_PROTOTYPES 1 :
#define GL_GLEXT_PROTOTYPES 1
#include "SDL_opengles2.h"
J’ai aussi observé un écran noir aux premiers lancements avec une erreur du type “E/libEGL: validate_display:255 error 3008 (EGL_BAD_DISPLAY)”, j’ai changé l’initialisation de la fenêtre SDL, le profil OpenGL et tout a fonctionné :
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 );
Sur l’émulateur, l’application est installée par défaut avec l’icône SDL et le nom « Jeu ».
Il me reste juste à explorer la possibilité de générer automatiquement des fichiers d’assembly basés sur CMake, ou de migrer des assemblys pour toutes les plateformes vers Gradle ; cependant, CMake reste le choix de facto pour le développement C++ en cours.
Code source
https://gitlab.com/demensdeum/android- sdl-test-app
https://gitlab.com/demensdeum/android-sdl-test-app/tree/master/cube-art-project-android
Sources
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
Monde à l’envers
Pour développer un nouveau projet, Cube Art Project a adopté la méthodologie Test Driven Development. Dans cette approche, un test pour une fonctionnalité spécifique de l’application est d’abord implémenté, puis la fonctionnalité spécifique est implémentée. Je considère que le gros avantage de cette approche réside dans la mise en œuvre des interfaces finales, qui sont aussi peu impliquées que possible dans les détails de mise en œuvre, avant le début du développement des fonctionnalités. Avec cette approche, le test dicte la poursuite de l’implémentation, ajoutant tous les avantages de la programmation contractuelle, lorsque les interfaces sont des contrats pour une implémentation spécifique.
Projet d’art cubique – Un éditeur 3D dans lequel l’utilisateur construit des figures à partir de cubes ; il n’y a pas si longtemps, ce genre était très populaire. Puisqu’il s’agit d’une application graphique, j’ai décidé d’ajouter des tests avec validation de capture d’écran.
Pour valider les captures d’écran, vous devez les récupérer depuis le contexte OpenGL, cela se fait à l’aide de la fonction glReadPixels. La description des arguments de la fonction est simple : position de départ, largeur, hauteur, format (RGB/RGBA/etc.), pointeur vers le tampon de sortie ; toute personne ayant travaillé avec SDL ou ayant de l’expérience avec les tampons de données en C remplacera simplement les arguments nécessaires. Cependant, je pense qu’il est nécessaire de décrire une fonctionnalité intéressante du tampon de sortie glReadPixels : les pixels y sont stockés de bas en haut, tandis que dans SDL_Surface, toutes les opérations de base se déroulent de haut en bas.
Autrement dit, après avoir chargé une capture d’écran de référence à partir d’un fichier png, je n’ai pas pu comparer directement les deux tampons, car l’un d’eux était à l’envers.
Pour retourner le tampon de sortie d’OpenGL, vous devez le remplir en soustrayant la hauteur de la capture d’écran pour la coordonnée Y. Cependant, il convient de considérer qu’il y a une chance d’aller au-delà des limites du tampon si vous n’en soustrayez pas une lors du remplissage, ce qui le fera. conduire à une corruption de la mémoire.
Comme j’essaie toujours d’utiliser le paradigme POO de « programmation par interfaces », au lieu d’un accès direct à la mémoire de type C par pointeur, lorsque j’ai essayé d’écrire des données en dehors du tampon, l’objet m’en a informé grâce à la validation des limites dans la méthode. .
Le code final pour la méthode permettant d’obtenir une capture d’écran de haut en bas :
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);
Sources
https://community.khronos.org/ t/glreadpixels-fliped-image/26561
https://stackoverflow.com/questions/8346115/why-are-bmps-stored-upside-down
Code source
https://gitlab.com/demensdeum/cube- art-project-bootstrap
Sous-chaîne commune la plus longue
Dans cet article, je décrirai un algorithme permettant de résoudre le plus grand problème de sous-chaîne courant. Supposons que nous essayions de décrypter des données binaires chiffrées. Essayons d’abord de trouver des modèles communs en recherchant la plus grande sous-chaîne.
Exemple de chaîne d’entrée :
adasDATAHEADER??jpjjwerthhkjbcvkDATAHEADER??kkasdf
Nous recherchons une chaîne qui se répète deux fois :
EN-TÊTE DE DONNÉES ??
Préfixes
Tout d’abord, écrivons une méthode pour comparer les préfixes de deux chaînes, laissons-la renvoyer la chaîne résultante dans laquelle les caractères du préfixe de gauche sont égaux aux caractères du préfixe de droite.
Par exemple, pour les lignes :
val lhs = "asdfWUKI"
val rhs = "asdfIKUW"
Chaîne de résultat – asdf
Exemple en 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)
}
Force Brute
Lorsque les choses ne fonctionnent pas bien, vous devriez recourir à la force brute. En utilisant la méthode longestPrefix, nous allons parcourir la chaîne en deux boucles, la première prend la chaîne de i à la fin, la seconde de i + 1 à la fin, les transmet pour rechercher le plus grand préfixe. La complexité temporelle de cet algorithme est d’environ O(n^2) ~ O(n*^3).
Exemple en 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
}
Tableau de suffixes
Pour une solution plus élégante, nous avons besoin d'un outil : une structure de données appelée "Suffix Array". Cette structure de données est un tableau de sous-chaînes remplies dans une boucle, où chaque sous-chaîne commence du caractère suivant de la ligne jusqu'à la fin.
Par exemple, pour la ligne :
adasDATAHEADER??
Le tableau de suffixes ressemble à ceci :
adasDATAHEADER??
dasDATAHEADER??
asDATAHEADER??
sDATAHEADER??
DATAHEADER??
ATAHEADER??
TAHEADER??
AHEADER??
HEADER??
EADER??
ADER??
DER??
ER??
R??
??
?
On résout en triant
Trions le tableau de suffixes, puis parcourons tous les éléments dans une boucle où l'élément actuel est dans la main gauche (à gauche), le suivant est dans la main droite (à droite) et calculons le préfixe le plus long en utilisant le plus longPrefix méthode.
Exemple en 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
}
La complexité temporelle de l'algorithme est O(N log N), ce qui est bien meilleur qu'une solution simple.
Sources
https://en.wikipedia.org/wiki/Longest_common_substring_problem
Code source
https://gitlab.com/demensdeum/algorithms
Tri par insertion, tri par fusion
Tri par insertion
Tri par insertion – chaque élément est comparé aux précédents de la liste et l’élément est échangé avec le plus grand, le cas échéant, sinon la boucle de comparaison interne s’arrête. Étant donné que les éléments sont triés du premier au dernier, chaque élément est comparé à une liste déjà triée, ce qui *éventuellement* réduit le temps d’exécution global. La complexité temporelle de l’algorithme est O(n^2), c’est-à-dire identique à la variété des bulles.
Fusionner le tri
Tri par fusion – la liste est divisée en groupes d’un élément, puis les groupes sont « fusionnés » par paires avec comparaison simultanée. Dans mon implémentation, lors de la fusion de paires, les éléments de gauche sont comparés aux éléments de droite, puis déplacés vers la liste résultante si les éléments de gauche ont disparu, alors tous les éléments de droite sont ajoutés à la liste résultante ; liste (leur comparaison supplémentaire est inutile, puisque tous les éléments des groupes passent par des itérations de tri)< br />Le travail de cet algorithme est très simple à paralléliser ; l’étape de fusion des paires peut être effectuée en threads, en attendant la fin des itérations dans le répartiteur.
Résultat de l’algorithme pour l’exécution monothread :
["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", "Артем"]
Sortie de l’algorithme pour l’exécution multithread :
["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", "Артем"]
La complexité temporelle de l’algorithme est O(n*log(n)), ce qui est légèrement meilleur que O(n^2)
Sources
https://en.wikipedia.org/wiki/Insertion_sort
https://en.wikipedia.org/wiki/Merge_sort
Code source
https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/insertionSort
https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/mergeSort
Tri à bulles à Erlang
Le tri par bulles est assez ennuyeux, mais il devient plus intéressant si vous essayez de l’implémenter dans un langage fonctionnel pour les télécommunications – c’est à dire. Erlang.
Nous avons une liste de numéros, nous devons la trier. L’algorithme de tri à bulles parcourt toute la liste, en itérant et en comparant les nombres par paires. Lors de la vérification, ce qui suit se produit : un nombre plus petit est ajouté à la liste de sortie, ou les nombres sont intervertis dans la liste actuelle s’il y en a moins à droite, la recherche continue avec le numéro suivant dans l’itération ; Ce parcours est répété jusqu’à ce qu’il n’y ait plus de remplacements dans la liste.
En pratique, cela ne vaut pas la peine d’être utilisé en raison de la grande complexité temporelle de l’algorithme – O(n^2); Je l’ai implémenté en Erlang, dans le style impératif, mais si vous êtes intéressé, vous pouvez rechercher de meilleures options :
-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 et lancement
Dans Ubuntu, Erlang est très facile à installer ; il suffit de taper sudo apt install erlang dans le terminal. Dans ce langage, chaque fichier doit être un module, avec une liste de fonctions pouvant être utilisées en externe – exporter. Les fonctionnalités intéressantes du langage incluent l’absence de variables, uniquement des constantes, l’absence de syntaxe standard pour la POO (ce qui n’empêche pas l’utilisation de techniques de POO), et bien sûr des calculs parallèles sans verrous basés sur le modèle d’acteur.
Vous pouvez exécuter le module soit via la console erl interactive, en exécutant une commande après l’autre, soit plus simplement via l’escript bubbleSort.erl ; Dans différents cas, le fichier aura un aspect différent, par exemple, pour escript, vous devez créer une fonction principale à partir de laquelle il démarrera.
Sources
https://www.erlang.org/
https://habr.com/ru/post/197364/
Code source
https://gitlab.com/ demensdeum/algorithms/blob/master/bubbleSort/bubbleSort.erl
Algorithme de comparaison lexicographique
L’algorithme de comparaison de chaînes lexicographiques fonctionne très simplement : les codes de caractères sont comparés en boucle et le résultat est renvoyé si les caractères ne sont pas égaux.
Un exemple pour le langage C peut être trouvé ici :
https://github.com/gcc-mirror/gcc/blob/master/libiberty/memcmp.c
Il convient de prendre en compte le fait que vous devez comparer les caractères dans un seul encodage statique, par exemple dans Swift, j’ai utilisé la comparaison caractère par caractère en UTF-32. L’option de tri de tableau utilisant memcmp fonctionnera exactement pour les caractères à un octet, dans d’autres cas (codages de longueur variable), l’ordre peut être incorrect. Je n’exclus pas la possibilité d’une implémentation basée sur des encodages de longueur variable, mais ce sera très probablement un ordre de grandeur plus compliqué.
La complexité temporelle de l’algorithme est O(1) dans le meilleur des cas, O(n) dans la moyenne et le pire des cas
Sources
https://ru.wikipedia.org/wiki/Lexicographic_order
Sources
https://gitlab.com/demensdeum /algorithms/blob/master/lexiCompare/lexiCompare.swift
Recherche binaire
Supposons que nous ayons besoin de savoir si l’adresse e-mail « demensdeum@gmail.com » est incluse dans la liste des adresses e-mail autorisées pour la réception de lettres. .
Parcourons toute la liste du premier au dernier élément, en vérifiant si l’élément est égal à l’adresse spécifiée – Implémentons un algorithme de recherche linéaire. Mais cela prendra beaucoup de temps, n’est-ce pas ?
Pour répondre à cette question, utilisez la notation « Complexité temporelle des algorithmes », « O ». Le temps de fonctionnement de la recherche linéaire dans le pire des cas est égal au nième nombre d’éléments du tableau, écrivons cela en notation « O » – Sur). Ensuite, nous devons expliquer que pour tout algorithme connu, il existe trois indicateurs de performance : temps d’exécution dans le meilleur des cas, le pire des cas et la moyenne. Par exemple, l’adresse mail « demensdeum@gmail.com » est dans le premier index du tableau, elle sera alors trouvée dans la première étape de l’algorithme, il s’ensuit que le temps d’exécution est au mieux – O(1); et si à la fin de la liste, alors c’est le pire des cas – O(n)
Mais qu’en est-il des détails de la mise en œuvre logicielle et des performances matérielles ? Ils devraient influencer Big O ? Maintenant, respirez et imaginez que le calcul de la complexité temporelle soit calculé pour une machine idéale abstraite, dans laquelle il n’y a que cet algorithme et rien d’autre.
Algorithme
Ok, il s’avère que la recherche linéaire est assez lente, essayons d’utiliser la recherche binaire. Pour commencer, il convient de préciser que nous ne travaillerons pas avec des données binaires ; ce nom a été donné à cette méthode en raison des particularités de son travail. Initialement, nous trions le tableau en ordre lexicographique, puis l’algorithme prend la plage de l’ensemble du tableau, obtient l’élément central de la plage, le compare lexicographiquement, et en fonction du résultat de la comparaison, décide quelle plage utiliser pour poursuivre la recherche – la moitié supérieure du courant ou la moitié inférieure. Autrement dit, à chaque étape de recherche, une décision est prise parmi deux options possibles : logique binaire. Cette étape est répétée jusqu’à ce que le mot soit trouvé ou non (l’intersection des indices inférieur et supérieur de la plage se produit).
Performances de cet algorithme – le meilleur des cas est lorsqu’un élément est immédiatement trouvé au milieu du tableau O(1), le pire des cas d’énumération est O(log n)
Pièges
Lors de l’implémentation de la recherche binaire, j’ai non seulement rencontré le problème intéressant du manque de standardisation de la comparaison lexicographique dans les bibliothèques de langages de programmation, mais j’ai même découvert l’absence d’un standard unifié pour l’implémentation localeCompare dans JavaScript. La norme ECMAScript permet différentes implémentations de cette fonction, c’est pourquoi lors du tri à l’aide de localeCompare, des résultats complètement différents peuvent être observés sur différents moteurs JavaScript.
Par conséquent, pour que l’algorithme fonctionne correctement, il faut trier et utiliser uniquement le même algorithme de comparaison lexicographique, sinon rien ne fonctionnera. Co-mais si, par exemple, vous essayez de trier un tableau dans Scala et d’effectuer une recherche à l’aide de nodejs, sans implémenter votre propre tri/tri d’une implémentation, alors rien ne vous attend sauf une déception humaine.
Sources
Qu’est-ce que la comparaison lexicographique et que représente-t-elle ?
Почему для вычисления сложности алгоритмов используется log N вместо lb N?
Двоичный поиск
Знай сложности алгоритмов
https://stackoverflow.com/questions/52941016/sorting-in-localecompare-in-javascript
Code source
https://gitlab.com/demensdeum/algorithms
Façade à motifs

La façade fait référence aux modèles de conception structurelle. Il fournit une interface unique qui permet de travailler avec des systèmes complexes, permettant aux clients de ne pas avoir de détails d’implémentation sur ces systèmes, simplifiant ainsi leur code et implémentant un couplage lâche entre les clients et les systèmes de niveau inférieur. GoF a un bon exemple de façade – un compilateur de langage de programmation qui offre à différents clients poursuivant différents objectifs la possibilité d’assembler du code via une interface de façade de compilateur unique.
Sources
https://refactoring.guru/ru/design-patterns/facade
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612
Modèle d’usine abstrait

Usine abstraite– fournit une interface pour créer des objets associés, sans spécifier de classes spécifiques.
J’aime vraiment le nom alternatif de ce modèle : ; Kit (Kit)
Elle est très similaire à la Méthode d’usine, cependant, les Usines abstraites doivent décrire la relation entre les objets en cours de création, sinon il s’agit simplement d’un Objet divin. l’anti-modèle qui crée tout est aléatoire.
Imaginez développer un framework AR pour lunettes ; nous affichons sur l’écran des flèches de navigation intérieures, des icônes de magasins, des lieux intéressants, des fenêtres et des boutons avec des informations sur tout endroit où se trouve actuellement l’utilisateur.
Dans le même temps, nous avons besoin de pouvoir personnaliser l’apparence et le comportement des contrôles de l’environnement AR. C’est précisément dans ce cas que vous devez utiliser le modèle Set.
Écrivons l’interface de Abstract Factory et des Abstract Products – protocoles parents, éléments de l’environnement AR :
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)
}
Les développeurs de kits devront désormais implémenter une Concrete Factory basée sur l’interface Abstract Factory, et ils devront implémenter tous les éléments ensemble ; le reste de l’application pourra travailler avec l’usine sans changer leur code.< /p>
Sources
https://refactoring.guru/ru/design-patterns /usine-abstraite
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612
Méthode d’usine
Le modèle Factory Method fait référence à des modèles de conception génératifs.
Ce modèle décrit la création d’une interface pour créer un objet d’une classe spécifique. Cela semble simple, non ?
En théorie
Supposons que nous développions un cadre pour travailler avec des lunettes AR. Lorsque vous inclinez la tête sur le côté, un menu d’applications disponibles devrait apparaître devant les yeux de l’utilisateur. Les applications seront développées par des sociétés tierces, clientes de notre framework. Naturellement, nous ne savons pas quelles applications, icônes, noms doivent apparaître, nous devons donc fournir une interface pour implémenter l’icône et les informations associées sur l’application. Appelons-le Produit :
protocol Product {
var name: String { get }
var image: Image { get }
var executablePath: String { get }
}
Ensuite, nous devons fournir une interface afin que nos clients puissent mettre en œuvre la délivrance d’une gamme d’applications pour leur Produit spécifique – un tableau d’icônes d’application avec des noms, que nous dessinerons déjà dans le framework.
Écrivons cette interface – Interface Creator contenant une Méthode Factory renvoyant un tableau de Produits.
protocol Creator {
func factoryMethod() -> [Product]
}
En pratique
Le premier client de notre framework AR était la société 7B – principal fournisseur de logiciels pour cafetières au Honduras. Ils souhaitent vendre des lunettes de réalité augmentée capables de préparer du café, de vérifier si l’eau ou les grains sont pleins et d’indiquer le chemin jusqu’à la cafetière la plus proche en utilisant le mode carte intérieure.
Ils se chargent du développement du logiciel ; nous sommes uniquement tenus de fournir une documentation sur les interfaces Creator et Produit pour le bon affichage de la liste des applications et leurs suites. lancer.
Après avoir transféré la documentation, la société 7B, à l’aide de l’interface Creator , implémente le Specific Creator – classe renvoyant un tableau d’icônes d’application. Les applications d’icônes elles-mêmes sont des classes Produit spécifique qui implémentent l’interface Produit.
Exemple de code pour Produits spécifiques :
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”
}
Classe Concrete Creator, offrant un éventail de deux applications :
class 7BAppsCreator: implements Creator {
func factoryMethod() -> [Product] {
return [CoffeeMachineLocator(), iPuchinno()]
}
}
Après cela, la société 7B compile la bibliothèque de Concrete Products, Concrete Creator et la combine avec notre framework, commence à vendre des lunettes AR pour ses cafetières, ajouts de notre part non requis.
Sources
https://refactoring.guru/ru/design-patterns/command
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612
Commande de modèle
Le modèle de commande fait référence aux modèles de conception comportementale.
C’est le modèle avec lequel je suis resté le plus longtemps, il est si simple qu’il en est très complexe. Mais personnellement, je trouve que la beauté de l’auto-apprentissage est que vous avez tout le temps du monde pour rechercher un certain sujet sous tous les angles.
Ainsi, dans le GoF, l’applicabilité est décrite de manière assez succincte et claire :
Encapsule une requête en tant qu’objet, vous permettant de paramétrer des clients avec différentes requêtes, d’utiliser des files d’attente, de consigner les requêtes et d’effectuer des opérations d’annulation.
Implémentons maintenant une version simple de la commande à partir de la description :
string fakeTrumpsRequest = “SELECT * from Users where name beginsWith DonaldTrump”
Nous avons encapsulé la requête dans un objet de classe chaîne, il peut être utilisé pour configurer les clients, ajouter des commandes à la file d’attente, enregistrer, annuler (en utilisant le modèle « Snapshot »)
Il me semble que cela suffit amplement pour effectuer des requêtes SQL et autres, mais il y a ensuite des détails d’implémentation, différentes options d’application, la base de code du modèle, les rôles client et les classes auxiliaires. p>
Pièces matérielles
Le
modèle de commande commence par un protocole de commande, qui contient une seule méthode execute(). Vient ensuite la Commande spécifique et le récepteur. Le CC implémente l’opération sur le récepteur, décrit la connexion entre le récepteur et l’action. Quelque chose n’est pas clair ? Moi aussi, mais passons à autre chose. Le Client crée une instance d’une Commande Spécifique, l’associe au Récepteur. Invocateur – objet qui exécute le processus de lancement des Commandes.
Essayons maintenant de comprendre à l’aide d’un exemple, disons que nous voulons mettre à jour myOS sur myPhone, pour ce faire, nous lançons l’application myOS_Update !, nous y appuyons sur le bouton Mettre à jour maintenant, après 10 secondes, le système le fera ! signaler une mise à jour réussie.
Le client dans l’exemple ci-dessus est l’application myOS_Update!, l’Invoker est le bouton “Mettre à jour maintenant !”, il lance la Commande spécifique b>mettre à jour le système à l’aide de la méthode execute(), qui accède au Récepteur– Démon de mise à jour du système d’exploitation.
Utiliser un exemple
Acceptons l’interface utilisateur de l’application myOS_Update ! si bon qu’ils ont décidé de le vendre en tant que produit distinct pour fournir une interface permettant de mettre à jour d’autres systèmes d’exploitation. Dans ce cas, nous implémenterons une application avec prise en charge des extensions via des bibliothèques, dans les bibliothèques il y aura des implémentations de Commandes spécifiques, récepteurs, nous laisserons Invoker statique/immuable. , Client, protocole Commandes.
Ainsi, il n’est pas nécessaire de prendre en charge le code mutable, puisque notre code restera inchangé, des problèmes ne peuvent survenir que lorsqu’il est implémenté côté client, en raison d’erreurs dans le code de leurs Commandes spécifiques et Récepteurs. De plus, dans cette implémentation, il n’est pas nécessaire de transférer le code source de l’application principale, c’est-à-dire que nous avons encapsulé les commandes et les interactions de l’interface utilisateur à l’aide du modèle Command.
Sources
https://refactoring.guru/ru/design-patterns/command
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612
Création d’applications macOS pour Ubuntu OSXCross CMake
Dans cet article, je décrirai la création d’applications C++ multiplateformes pour macOS sur une machine de build Ubuntu à l’aide de CMake et osxcross.
Tout d’abord, installez la chaîne d’outils osxcross :
https://github.com/tpoechtrager/osxcross
L’installation se déroule en 3 étapes, téléchargement des dépendances :
cd tools
./get_dependencies.sh
Téléchargez XCode.xip depuis le site officiel d’Apple, puis téléchargez le SDK depuis XCode :
./gen_sdk_package_pbzx.sh /media/demensdeum/2CE62A79E62A4404/LinuxSupportStorage/xcode111.xip
J’espère que vous avez lu le contrat de licence XCode lors de la dernière étape ? Ensuite, créez la chaîne d’outils avec le préfixe requis :
INSTALLPREFIX=/home/demensdeum/Apps/osxcross ./build.sh
Vous pouvez maintenant utiliser osxcross à partir du répertoire de préfixes de l’étape précédente. Ajoutons une nouvelle macro de build pour CMake, écrivons tout ce qui est nécessaire :
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()
La liaison dynamique n’a pas fonctionné pour moi, nous exportons donc les bibliothèques de manière statique :
if (OSXCROSS)
add_library(FlameSteelCore STATIC ${SOURCE_FILES})
else()
Ensuite, vous pourriez être confronté au fait que vous ne disposez pas des bibliothèques nécessaires pour osxcross, j’ai rencontré ce problème lors de l’utilisation de SDL2. osxcross prend en charge les packages de bibliothèques prêts à l’emploi – macports. Par exemple, en installant SDL2-mixer :
osxcross-macports -v install libsdl2_mixer
Après cela, vous pouvez commencer à créer des bibliothèques/applications comme d’habitude dans le lien cmake-make, n’oubliez pas de spécifier des liens statiques de bibliothèques si nécessaire.
Assemblage manuel des bibliothèques
Actuellement, j’ai rencontré le problème d’un archivage incorrect des bibliothèques lors de la liaison statique ; lors de la construction de l’application finale, je reçois l’erreur :
file was built for archive which is not the architecture being linked (x86_64)
Très similaire à ce ticket, nous avons réussi à implémenter un solution de contournement qui permet à l’assemblage de se terminer correctement. Décompressons la bibliothèque statique et recréons-la à l’aide de l’archiveur osxcross :
ar x ../libFlameSteelCore.a
rm ../libFlameSteelCore.a
x86_64-apple-darwin19-ar rcs ../libFlameSteelCore.a *.o
Personnellement, je considère aussi que l’un des problèmes est le manque de possibilité d’exécuter des applications macOS directement sur Ubuntu (au moins avec certaines fonctionnalités). Bien sûr, il existe un projet darling, mais le support laisse encore beaucoup à désirer.
Sources
https://github.com/tpoechtrager/osxcross
Construire pour Windows sous Ubuntu MinGW CMake
Dans cet article, je décrirai le processus de création de bibliothèques et d’applications pour Windows à l’aide de la chaîne d’outils MinGW32 sur Ubuntu.
Installez wine, mingw :
sudo apt-get install wine mingw-w64
Après cela, vous pouvez déjà créer des applications C/C++ pour Windows :
# C
i686-w64-mingw32-gcc helloWorld.c -o helloWorld32.exe # 32-bit
x86_64-w64-mingw32-gcc helloWorld.c -o helloWorld64.exe # 64-bit
# C++
i686-w64-mingw32-g++ helloWorld.cc -o helloWorld32.exe # 32-bit
x86_64-w64-mingw32-g++ helloWorld.cc -o helloWorld64.exe # 64-bit
L’exe collecté peut être vérifié à l’aide de wine.
Ensuite, regardons les modifications apportées à la build CMake, le fichier CMakeLists.txt, en ajoutant des éléments spécifiques à MinGW au fichier de build :
if (MINGW32)
set(CMAKE_SYSTEM_NAME Windows)
SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
SET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
SET(CMAKE_RC_COMPILER i686-w64-mingw32-windres)
set(CMAKE_RANLIB i686-w64-mingw32-ranlib)
endif()
// для сборки shared dll
elseif (MINGW32)
add_library(FlameSteelEngineGameToolkit.dll SHARED ${SOURCE_FILES})
else()
// обязательно линкуем со всеми зависимостями
if (MINGW32)
target_link_libraries(
FlameSteelEngineGameToolkit.dll
-static-libgcc
-static-libstdc++
SDL2
SDL2_mixer
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCore/FlameSteelCore.dll
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelBattleHorn/FlameSteelBattleHorn.dll
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCommonTraits/FlameSteelCommonTraits.dll)
set_target_properties(FlameSteelEngineGameToolkit.dll PROPERTIES
PREFIX ""
SUFFIX ""
LINK_FLAGS "-Wl,--add-stdcall-alias"
POSITION_INDEPENDENT_CODE 0 # this is to avoid MinGW warning;
# MinGW generates position-independent-code for DLL by default
)
else()
Collecte :
cmake -DMINGW32=1 .
make
Le résultat sera une DLL ou un exe, selon ce que vous collectez. Pour un exemple fonctionnel, vous pouvez consulter le référentiel du nouveau Cube-Art-Project et ses bibliothèques :
https://gitlab.com/demensdeum/cube-art-project
https://gitlab.com/demensdeum/FlameSteelEngineGameToolkitFSGL
https://gitlab.com/demensdeum/cube-art-project-bootstrap
Sources
https://arrayfire.com/cross-compile-to-windows-from-linux/





