Bloquer les diagrammes en pratique sans formol

Le diagramme de blocs est un outil visuel qui aide à transformer un algorithme complexe en une séquence d’actions compréhensible et structurée. De la programmation à la gestion des processus métier, ils servent de langage universel pour la visualisation, l’analyse et l’optimisation des systèmes les plus complexes.

Imaginez une carte où au lieu des routes est logique et au lieu des villes – des actions. Il s’agit d’un schéma de bloc – un outil indispensable pour la navigation dans les processus les plus déroutants.

Exemple 1: Schéma de lancement de jeu simplifié
Pour comprendre le principe du travail, présentons un schéma de lancement de jeu simple.

Ce schéma montre le script parfait lorsque tout se produit sans échecs. Mais dans la vraie vie, tout est beaucoup plus compliqué.

Exemple 2: Schéma élargi pour démarrer le jeu avec le chargement des données
Les jeux modernes nécessitent souvent une connexion Internet pour télécharger les données des utilisateurs, l’enregistrement ou les paramètres. Ajoutons ces étapes à notre schéma.

Ce schéma est déjà plus réaliste, mais que se passera-t-il en cas de problème?

comment était-ce: un jeu qui “s’est cassé” avec la perte d’Internet

Au début du projet, les développeurs n’ont pas pu prendre en compte tous les scénarios possibles. Par exemple, ils se sont concentrés sur la logique principale du jeu et ne pensaient pas ce qui se passerait si le joueur avait une connexion Internet.

Dans une telle situation, le schéma de bloc de leur code ressemblerait à ceci:

Dans ce cas, au lieu d’émettre une erreur ou de fermer correctement, le jeu s’est figé au stade de l’attente des données qu’elle n’a pas reçues en raison de l’absence de connexion. Cela a conduit à “l’écran noir” et au gel de l’application.

comment il est devenu: correction sur les plaintes des utilisateurs

Après les plaintes de nombreux utilisateurs concernant le plan de volant, l’équipe du développeur s’est rendu compte que nous devions corriger l’erreur. Ils ont apporté des modifications au code en ajoutant une unité de traitement d’erreur qui permet à l’application de répondre correctement au manque de connexion.

C’est à quoi ressemble le diagramme de bloc corrigé, où les deux scénarios sont pris en compte:

Grâce à cette approche, le jeu informe désormais correctement l’utilisateur du problème, et dans certains cas, il peut même passer en mode hors ligne, vous permettant de continuer le jeu. Ceci est un bon exemple de la raison pour laquelle les diagrammes de blocs sont si importants : ils font que le développeur réfléchit non seulement à la façon idéale d’exécution, mais aussi à toutes les échecs possibles, ce qui rend le produit final beaucoup plus stable et fiable.

comportement incertain

La suspension et les erreurs ne sont qu’un exemple de comportement imprévisible du programme. En programmation, il existe un concept de comportement incertain (comportement non défini) – Il s’agit d’une situation où la norme du langage ne décrit pas comment le programme devrait se comporter dans un certain cas.

Cela peut conduire à n’importe quoi: des «ordures» aléatoires dans le retrait de l’échec du programme ou même de la vulnérabilité de sécurité sérieuse. Le comportement indéfini se produit souvent lorsque vous travaillez avec la mémoire, par exemple, avec des lignes dans la langue de C.

Un exemple de la langue C:

Imaginez que le développeur a copié la ligne dans le tampon, mais a oublié d’ajouter à la fin le symbole zéro (`\ 0`) , qui marque la fin de la ligne.

Voici à quoi ressemble le code:

#include 

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

memcpy(buffer, my_string, 5);

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

Résultat attendu: “Bonjour”
Le résultat réel est imprévisible.

Pourquoi cela se produit-il? La fonction `printf` avec le spécificateur ‘% S` s’attend à ce que la ligne se termine par un symbole zéro. S’il ne l’est pas, elle continuera à lire la mémoire en dehors du tampon mis en évidence.

Voici le schéma de bloc de ce processus avec deux résultats possibles:

Ceci est un exemple clair de la raison pour laquelle les diagrammes de bloc sont si importants: ils font réfléchir le développeur non seulement à la manière idéale d’exécution, mais aussi à tous les échecs possibles, y compris des problèmes de niveau de bas niveau, ce qui rend le produit final beaucoup plus stable et fiable.

Pixel Perfect: Mythe ou réalité à l’ère de la déclarativité?

Dans le monde du développement des interfaces, il existe un concept commun – “Pixel parfait dans le lodge” . Cela implique la reproduction la plus précise de la machine de conception au plus petit pixel. Pendant longtemps, c’était une étalon-or, en particulier à l’ère d’une conception Web classique. Cependant, avec l’arrivée du mile déclarant et la croissance rapide de la variété des appareils, le principe de “Pixel parfait” devient plus éphémère. Essayons de comprendre pourquoi.

Imperial Wysiwyg vs code déclaratif: Quelle est la différence?

Traditionnellement, de nombreuses interfaces, en particulier de bureau, ont été créées en utilisant des approches impératives ou Wysiwyg (ce que vous voyez est ce que vous obtenez) des éditeurs. Dans de tels outils, le concepteur ou le développeur manipule directement avec des éléments, en les plaçant sur toile avec une précision au pixel. Il est similaire à travailler avec un éditeur graphique – vous voyez à quoi ressemble votre élément, et vous pouvez certainement le positionner. Dans ce cas, la réalisation de “Pixel Perfect” était un objectif très réel.

Cependant, le développement moderne est de plus en plus basé sur miles déclaratifs . Cela signifie que vous ne dites pas à l’ordinateur de “mettre ce bouton ici”, mais décrivez ce que vous voulez obtenir. Par exemple, au lieu d’indiquer les coordonnées spécifiques de l’élément, vous décrivez ses propriétés: “Ce bouton doit être rouge, avoir des indentations 16px de tous les côtés et être au centre du conteneur.” Freimvorki comme React, Vue, Swiftui ou Jetpack composent simplement ce principe.

Pourquoi “Pixel Perfect” ne fonctionne pas avec un mile déclaratif pour de nombreux appareils

Imaginez que vous créez une application qui devrait être tout aussi bonne sur l’iPhone 15 Pro Max, Samsung Galaxy Fold, iPad Pro et une résolution 4K. Chacun de ces appareils a une résolution d’écran différente, une densité de pixels, des parties et des tailles physiques.

Lorsque vous utilisez l’approche déclarative, le système lui-même décide comment afficher votre interface décrite sur un appareil particulier, en tenant compte de tous ses paramètres. Vous définissez les règles et les dépendances, pas les coordonnées sévères.

* Adaptabilité et réactivité: L’objectif principal des miles déclaratifs est de créer des interfaces adaptatives et réactives . Cela signifie que votre interface doit s’adapter automatiquement à la taille et à l’orientation de l’écran sans se casser et maintenir la lisibilité. Si nous cherchions à «Pixel parfait» sur chaque appareil, nous devions créer d’innombrables options pour la même interface, ce qui nivellera complètement les avantages de l’approche déclarative.
* densité de pixels (dpi / ppi): Les appareils ont une densité de pixels différente. Le même élément avec la taille de 100 pixels “virtuels” sur un appareil à haute densité sera beaucoup plus petit que sur un dispositif à faible densité, si vous ne prenez pas en compte la mise à l’échelle. Les cadres déclaratifs sont résumés par des pixels physiques, en travaillant avec des unités logiques.
* Contenu dynamique: Le contenu dans les applications modernes est souvent dynamique – son volume et sa structure peuvent varier. Si nous détendions durement les pixels, tout changement de texte ou d’image entraînerait “l’effondrement” de la mise en page.
* diverses plates-formes: En plus de la variété des appareils, il existe différents systèmes d’exploitation (iOS, Android, Web, Desktop). Chaque plate-forme a sa propre conception, ses commandes standard et ses polices. Une tentative de faire une interface parfaite absolument identique et parfaite sur toutes les plates-formes conduirait à un type non naturel et à une mauvaise expérience utilisateur.

Les anciennes approches n’ont pas disparu, mais ont évolué

Il est important de comprendre que l’approche des interfaces n’est pas un choix binaire entre “impératif” et “déclaratif”. Historiquement, pour chaque plate-forme, il y avait ses propres outils et approches de la création d’interfaces.

* Fichiers d’interface natif: Pour iOS, c’était XIB / storyboards, pour les fichiers de marquage Android-XML. Ces fichiers sont une disposition WYSIWYG parfaite avec pixel, qui est ensuite affichée à la radio comme dans l’éditeur. Cette approche n’a disparu nulle part, elle continue de se développer, s’intégrant à des cadres déclaratifs modernes. Par exemple, Swiftui dans Apple et Jetpack composent dans Android se déroulent sur le chemin d’un code purement déclaratif, mais en même temps, a conservé l’opportunité d’utiliser une disposition classique.
* Solutions hybrides: Souvent dans les projets réels, une combinaison d’approches est utilisée. Par exemple, la structure de base de l’application peut être mise en œuvre de manière déclarative et, pour un positionnement précis des éléments, des méthodes impératives de niveau inférieur peuvent être utilisées ou des composants natifs développés en tenant compte des spécificités de la plate-forme.

du monolithe à l’adaptabilité: comment l’évolution des appareils a formé un mile déclaratif

Le monde des interfaces numériques a subi d’énormes changements au cours des dernières décennies. Des ordinateurs stationnaires avec des permis fixes, nous sommes arrivés à l’ère de la croissance exponentielle de la variété des appareils utilisateur . Aujourd’hui, nos applications devraient fonctionner aussi bien sur:

* Smartphones de tous les facteurs de forme et tailles d’écran.
* tablettes avec leurs modes d’orientation uniques et un écran séparé.
* ordinateurs portables et ordinateurs de bureau avec divers permis de moniteurs.
* TVS et centres de médias , contrôlés à distance. Il est à noter que même pour les téléviseurs, dont les remarques peuvent être simples en tant que Apple TV Remote avec un minimum de boutons, ou vice versa, surchargée de nombreuses fonctions, les exigences modernes pour les interfaces sont telles que le code ne doit pas nécessiter une adaptation spécifique pour ces caractéristiques d’entrée. L’interface doit fonctionner “comme si elle-même”, sans une description supplémentaire de ce que “comment” interagir avec une télécommande spécifique.
* montres intelligentes et appareils portables avec des écrans minimalistes.
* Casques de réalité virtuelle (VR) , nécessitant une approche complètement nouvelle d’une interface spatiale.
* Dispositifs de réalité augmentés (AR) , appliquant des informations sur le monde réel.
* Informations automobiles et systèmes de divertissement .
* Et même appareils ménagers : des réfrigérateurs avec des écrans sensoriels et des machines à laver avec des écrans interactifs aux fours et systèmes intelligents de la maison intelligente.

Chacun de ces appareils a ses propres fonctionnalités uniques: dimensions physiques, rapport des parties, densité de pixels, méthodes d’entrée (écran tactile, souris, contrôleurs, gestes, commandes vocales) et, surtout, les subtilités de l’environnement utilisateur . Par exemple, un VR Shlesh nécessite une immersion profonde, et un travail rapide et intuitif pour les smartphones en déplacement, tandis que l’interface du réfrigérateur doit être aussi simple et grande pour une navigation rapide.

Approche classique: le fardeau de soutenir les interfaces individuelles

À l’ère de la domination des ordinateurs de bureau et des premiers appareils mobiles, l’activité habituelle était la création et la prise en charge de de fichiers d’interface individuels ou même un code d’interface complètement séparé pour chaque plate-forme .

* Développement sous iOS nécessite souvent l’utilisation de storyboards ou de fichiers XIB dans Xcode, en écrivant du code sur objectif-c ou Swift.
* Pour Android Les fichiers de marquage XML et le code sur Java ou Kotlin ont été créés.
* Les interfaces Web activées sur HTML / CSS / JavaScript.
* Pour les applications C ++ sur diverses plates-formes de bureau, leurs cadres et outils spécifiques ont été utilisés:
* Dans Windows il s’agissait de MFC (classes Microsoft Foundation), de l’API WIN32 avec des éléments de dessin manuel ou à l’aide de fichiers de ressources pour les fenêtres de dialogue et les éléments de contrôle.
* Le cacao (objectif-c / swift) ou l’ancienne API carbone pour le contrôle direct de l’interface graphique a été utilisé dans macOS .
* Dans Systems de type Linux / Unix , des bibliothèques comme GTK + ou QT ont souvent été utilisées, ce qui a fourni leur ensemble de widgets et de mécanismes pour créer des interfaces, souvent via des fichiers de marquage de type XML (par exemple, des fichiers .UI dans le concepteur QT) ou une création de logiciels directs d’éléments.

Cette approche a assuré un contrôle maximal sur chaque plate-forme, vous permettant de prendre en compte toutes ses caractéristiques spécifiques et ses éléments indigènes. Cependant, il a eu un énorme inconvénient: duplication des efforts et des coûts énormes de soutien . Le moindre changement de conception ou de fonctionnalité a nécessité l’introduction d’un droit à plusieurs bases de code indépendantes, en fait. Cela s’est transformé en un vrai cauchemar pour les équipes de développeurs, ralentissant la sortie des nouvelles fonctions et augmentant la probabilité d’erreurs.

Miles déclaratifs: une seule langue pour la diversité

C’est en réponse à cette complication rapide que les miles déclaratifs sont apparus comme le paradigme dominant. Les framws comme react, vue, swiftui, jetpack compose et d’autres ne sont pas seulement une nouvelle façon d’écrire du code, mais un changement fondamental dans la pensée.

L’idée principale de l’approche déclarative : Au lieu de dire que le système «comment» de dessiner chaque élément (impératif), nous décrivons «ce que« nous voulons voir (déclaratif). Nous définissons les propriétés et l’état de l’interface, et le cadre décide comment l’afficher au mieux sur un appareil particulier.

Cela est devenu possible grâce aux principaux avantages suivants:

1. L’abstraction des détails de la plate-forme: Les Fraimvorki déclaratifs sont spécialement conçus pour oublier les détails de bas niveau de chaque plate-forme. Le développeur décrit les composants et leurs relations à un niveau d’abstraction plus élevé, en utilisant un seul code transféré.
2. Adaptation et réactivité automatique: Freimvorki assumez la responsabilité de la mise à l’échelle automatique, de la mise en page et de l’adaptation des éléments en différentes tailles d’écrans, de densité de pixels et de méthodes d’entrée. Ceci est réalisé grâce à l’utilisation de systèmes de disposition flexibles, tels que Flexbox ou Grid, et des concepts similaires aux “pixels logiques” ou “DP”.
3. Cohérence de l’expérience utilisateur: Malgré les différences externes, l’approche déclarative vous permet de maintenir une seule logique de comportement et d’interaction dans toute la famille des appareils. Cela simplifie le processus de test et offre une expérience utilisateur plus prévisible.
4. Accélération du développement et réduction des coûts: Avec le même code capable de travailler sur de nombreuses plateformes, est considérablement réduit par le temps et le coût de développement et de soutien . Les équipes peuvent se concentrer sur la fonctionnalité et la conception, et non sur la réécriture répétée de la même interface.
5. Prépité pour l’avenir: La capacité de résumer des spécificités des appareils actuels rend le code déclaratif plus plus résistant à l’émergence de nouveaux types de dispositifs et de facteurs de forme . Freimvorki peut être mis à jour pour prendre en charge les nouvelles technologies, et votre code déjà écrit recevra ce support relativement transparent.

Conclusion

Le mile déclaratif n’est pas seulement une tendance de la mode, mais l’étape évolutive nécessaire causée par le développement rapide des appareils utilisateur, y compris la sphère l’Internet des objets (IoT) et les appareils de ménage intelligents. Il permet aux développeurs et aux concepteurs de créer des interfaces complexes, adaptatives et uniformes, sans se noyer dans des implémentations spécifiques sans fin pour chaque plate-forme. La transition du contrôle impératif de chaque pixel à la description déclarative de l’état souhaité est une reconnaissance que dans le monde des futures interfaces devrait être flexible, transféré et intuitif quel que soit l’écran qu’ils sont affichés.

Les programmeurs, les concepteurs et les utilisateurs doivent apprendre à vivre dans ce nouveau monde. Les détails supplémentaires du Pixel Perfect, conçu pour un appareil ou une résolution particulière, conduisent à des coûts de temps inutiles pour le développement et le support. De plus, de telles dispositions sévères peuvent tout simplement ne pas fonctionner sur des appareils avec des interfaces non standard, telles que les téléviseurs d’entrée limités, les changements VR et AR, ainsi que d’autres appareils du futur, que nous ne connaissons même pas aujourd’hui. Flexibilité et adaptabilité – Ce sont les clés de la création d’interfaces réussies dans le monde moderne.

Astuces de core de vibration: pourquoi LLM ne fonctionne toujours pas avec un solide, sec et propre

Avec le développement de modèles de grandes langues (LLM), tels que Chatgpt, de plus en plus de développeurs les utilisent pour générer du code, une architecture de conception et une intégration accélérée. Cependant, avec une application pratique, elle devient perceptible: les principes classiques de l’architecture – solide, sec, propre – s’entendent mal avec les particularités de la codgendation LLM.

Cela ne signifie pas que les principes sont obsolètes – au contraire, ils fonctionnent parfaitement avec le développement manuel. Mais avec LLM, l’approche doit être adaptée.

Pourquoi LLM ne peut pas faire face aux principes architecturaux

Encapsulation

L’incapsulation nécessite de comprendre les frontières entre parties du système, des connaissances sur les intentions du développeur, ainsi que des restrictions d’accès strictes. LLM simplifie souvent la structure, rend les champs publics sans raison ou dupliquent la mise en œuvre. Cela rend le code plus vulnérable aux erreurs et viole les limites architecturales.

Résumé et interfaces

Les modèles de conception, tels qu’une usine abstraite ou une stratégie, nécessitent une vision holistique du système et la compréhension de sa dynamique. Les modèles peuvent créer une interface sans objectif clair sans assurer sa mise en œuvre ou violer la connexion entre les couches. Le résultat est une architecture excessive ou non fonctionnelle.

sec (donolt répétez-vous)

LLM ne cherchez pas à minimiser le code de répétition – au contraire, il leur est plus facile de dupliquer des blocs que de faire une logique générale. Bien qu’ils puissent offrir une refactorisation sur demande, par défaut, les modèles ont tendance à générer des fragments «auto-suffisants», même si cela conduit à une redondance.

Architecture propre

Clean implique une hiérarchie stricte, une indépendance des cadres, une dépendance dirigée et une connectivité minimale entre les couches. La génération d’une telle structure nécessite une compréhension globale du système – et du travail LLM au niveau de la probabilité de mots, et non de l’intégrité architecturale. Par conséquent, le code est mitigé, avec violation des directions de dépendance et une division simplifiée en niveaux.

ce qui fonctionne mieux lorsque vous travaillez avec LLM


Mouillé au lieu de sec
L’approche humide (écrivez tout deux fois) est plus pratique pour travailler avec LLM. La duplication du code ne nécessite pas de contexte du modèle de rétention, ce qui signifie que le résultat est prévisible et est plus facile à corriger correctement. Il réduit également la probabilité de connexions et de bogues non évidents.

De plus, la duplication aide à compenser la mémoire courte du modèle: si un certain fragment de logique se trouve à plusieurs endroits, LLM est plus susceptible de le prendre en compte avec une génération plus approfondie. Cela simplifie l’accompagnement et augmente la résistance à “l’oubli”.

Structures simples au lieu de l’encapsulation

Éviter l’encapsulation complexe et s’appuyer sur la transmission directe des données entre les parties du code, vous pouvez considérablement simplifier à la fois la génération et le débogage. Cela est particulièrement vrai avec un développement itératif rapide ou une création de MVP.

Architecture simplifiée

Une structure simple et plate du projet avec une quantité minimale de dépendances et d’abstractions donne un résultat plus stable pendant la génération. Le modèle adapte un tel code plus facile et moins souvent viole les connexions attendues entre les composants.

Intégration du SDK – fiable manuellement

La plupart des modèles linguistiques sont formés sur des versions obsolètes de la documentation. Par conséquent, lors de la génération d’instructions pour l’installation du SDK, les erreurs apparaissent souvent: commandes obsolètes, paramètres non pertinents ou liens vers des ressources inaccessibles. La pratique montre: il est préférable d’utiliser la documentation officielle et le réglage manuel, laissant LLM un rôle auxiliaire – par exemple, générer un code de modèle ou une adaptation des configurations.

Pourquoi les principes fonctionnent-ils toujours – mais avec le développement manuel

Il est important de comprendre que les difficultés de solides, sèches et propres concernent la codhégénération par LLM. Lorsque le développeur écrit le code manuellement, ces principes continuent de démontrer leur valeur: ils réduisent la connectivité, simplifient le soutien, augmentent la lisibilité et la flexibilité du projet.

Cela est dû au fait que la pensée humaine est sujette à la généralisation. Nous recherchons des modèles, nous apportons une logique répétitive dans des entités individuelles, créons des modèles. Ce comportement a probablement des racines évolutives: la réduction de la quantité d’informations permet d’économiser des ressources cognitives.

LLM agit différemment: ils ne ressentent pas de charges du volume de données et ne visent pas d’économies. Au contraire, il leur est plus facile de travailler avec des informations fragmentées en double que de construire et de maintenir des abstractions complexes. C’est pourquoi il leur est plus facile de faire face au code sans encapsulation, avec des structures répétitives et une gravité architecturale minimale.

Conclusion

Les modèles de grandes langues sont un outil utile en développement, en particulier dans les premiers stades ou lors de la création d’un code auxiliaire. Mais il est important de s’adapter à l’approche: pour simplifier l’architecture, limiter l’abstraction, éviter les dépendances complexes et ne pas compter sur eux lors de la configuration du SDK.

Les principes du solide, du sec et de la propreté sont toujours pertinents, mais ils donnent le meilleur effet entre les mains d’une personne. Lorsque vous travaillez avec LLM, il est raisonnable d’utiliser un style simplifié et pratique qui vous permet d’obtenir un code fiable et compréhensible qui est facile à finaliser manuellement. Et où LLM oublie – la duplication du code l’aide à se souvenir.

Portage du Surreal Engine C++ vers WebAssembly

Dans cet article, je décrirai comment j’ai porté le moteur de jeu Surreal Engine vers WebAssembly.

Moteur surréaliste &#8211 ; un moteur de jeu qui implémente la plupart des fonctionnalités de l’Unreal Engine 1, des jeux célèbres sur ce moteur – Unreal Tournament 99, Unreal, Deus Ex, Undying. Il fait référence aux moteurs classiques qui fonctionnaient principalement dans un environnement d’exécution monothread.

Au départ, j’ai eu l’idée de me lancer dans un projet que je ne pouvais pas réaliser dans un délai raisonnable, montrant ainsi à mes abonnés Twitch qu’il y a des projets que même moi je ne peux pas réaliser. Lors de mon premier stream, j’ai soudainement réalisé que la tâche de porter Surreal Engine C++ vers WebAssembly à l’aide d’Emscripten était réalisable.

Surreal Engine Emscripten Demo

Après un mois, je peux faire une démonstration de mon ensemble fourche et moteur sur WebAssembly :
https://demensdeum.com/demos/SurrealEngine/

Le contrôle, comme dans l’original, s’effectue à l’aide des flèches du clavier. Ensuite, je prévois de l’adapter au contrôle mobile (tachi), en ajoutant un éclairage correct et d’autres fonctionnalités graphiques du rendu Unreal Tournament 99.

Par où commencer ?

La première chose que je veux dire est que n’importe quel projet peut être porté de C++ vers WebAssembly en utilisant Emscripten, la seule question est de savoir dans quelle mesure la fonctionnalité sera complète. Choisissez un projet dont les ports de bibliothèque sont déjà disponibles pour Emscripten ; dans le cas de Surreal Engine, vous avez beaucoup de chance, car le moteur utilise les bibliothèques SDL 2, OpenAL – ils sont tous deux portés sur Emscripten. Cependant, Vulkan est utilisé comme API graphique, qui n’est actuellement pas disponible pour HTML5, des travaux sont en cours pour implémenter WebGPU, mais il est également au stade de projet, et on ne sait pas non plus à quel point le port ultérieur de Vulkan vers WebGPU sera simple. , une fois qu’il est entièrement standardisé. Par conséquent, j’ai dû écrire mon propre rendu de base OpenGL-ES/WebGL pour Surreal Engine.

Construire le projet

Construire un système dans Surreal Engine – CMake, qui simplifie également le portage, car Emscripten fournit à ses constructeurs natifs – emcmake, emmake.
Le portage de Surreal Engine était basé sur le code de mon dernier jeu en WebGL/OpenGL ES et C++ appelé Death-Mask, de ce fait le développement était beaucoup plus simple, j’avais tous les indicateurs de build nécessaires avec moi et des exemples de code.

L’un des points les plus importants de CMakeLists.txt concerne les indicateurs de build pour Emscripten. Vous trouverez ci-dessous un exemple tiré du fichier de projet :


-s MAX_WEBGL_VERSION=2 \

-s EXCEPTION_DEBUG \

-fexceptions \

--preload-file UnrealTournament/ \

--preload-file SurrealEngine.pk3 \

--bind \

--use-preload-plugins \

-Wall \

-Wextra \

-Werror=return-type \

-s USE_SDL=2 \

-s ASSERTIONS=1 \

-w \

-g4 \

-s DISABLE_EXCEPTION_CATCHING=0 \

-O3 \

--no-heap-copy \

-s ALLOW_MEMORY_GROWTH=1 \

-s EXIT_RUNTIME=1")

Le script de build lui-même :


emmake make -j 16

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

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

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

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

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

Ensuite, nous préparerons l’index .html , qui inclut le préchargeur du système de fichiers du projet. Pour télécharger sur le Web, j’ai utilisé Unreal Tournament Demo version 338. Comme vous pouvez le voir sur le fichier CMake, le dossier du jeu décompressé a été ajouté au répertoire de construction et lié en tant que fichier de préchargement pour Emscripten.

Modifications du code principal

Ensuite, il a fallu changer la boucle de jeu du jeu, vous ne pouvez pas exécuter une boucle sans fin, cela conduit au gel du navigateur, vous devez plutôt utiliser emscripten_set_main_loop, j’ai écrit à propos de cette fonctionnalité dans ma note de 2017 « < a href="https://demensdeum.com /blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/" rel="noopener" target="_blank">Porter le jeu SDL C++ vers HTML5 (Emscripten)”
Nous modifions le code pour quitter la boucle while en if, puis nous affichons la classe principale du moteur de jeu, qui contient la boucle de jeu, dans la portée globale, et écrivons une fonction globale qui appellera l’étape de boucle de jeu à partir de l’objet global :


#include <emscripten.h>

Engine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;

void emscripten_game_loop_step() {

	EMSCRIPTEN_GLOBAL_GAME_ENGINE->Run();

}

#endif

Après cela, vous devez vous assurer qu’il n’y a pas de threads d’arrière-plan dans l’application ; s’il y en a, préparez-vous à les réécrire pour une exécution monothread, ou utilisez la bibliothèque phtread dans Emscripten.
Le fil d’arrière-plan dans Surreal Engine est utilisé pour lire de la musique, les données proviennent du fil du moteur principal concernant la piste en cours, la nécessité de jouer de la musique ou son absence, puis le fil d’arrière-plan reçoit un nouvel état via un mutex et commence à jouer de la nouvelle musique. , ou le met en pause. Le flux d’arrière-plan est également utilisé pour mettre la musique en mémoire tampon pendant la lecture.
Mes tentatives de création de Surreal Engine pour Emscripten avec pthread ont échoué, car les ports SDL2 et OpenAL ont été construits sans le support de pthread, et je ne voulais pas les reconstruire pour le plaisir de la musique. Par conséquent, j’ai transféré la fonctionnalité du flux de musique de fond vers une exécution monothread à l’aide d’une boucle. En supprimant les appels pthread du code C++, j’ai déplacé la mise en mémoire tampon et la lecture de musique vers le thread principal, afin qu’il n’y ait pas de retard, j’ai augmenté la mémoire tampon de quelques secondes.

Ensuite, je décrirai des implémentations spécifiques des graphiques et du son.

Vulkan n’est pas pris en charge !

Oui, Vulkan n’est pas pris en charge en HTML5, bien que toutes les brochures marketing présentent le support multiplateforme et large plate-forme comme le principal avantage de Vulkan. Pour cette raison, j’ai dû écrire mon propre moteur de rendu graphique de base pour un type OpenGL simplifié – – ES, il est utilisé sur les appareils mobiles, parfois il ne contient pas les fonctionnalités à la mode de l’OpenGL moderne, mais il se porte très bien sur WebGL, ce qui est exactement ce qu’Emscripten implémente. L’écriture du rendu de tuiles de base, du rendu bsp, pour l’affichage GUI le plus simple et du rendu des modèles + cartes a été achevée en deux semaines. C’était peut-être la partie la plus difficile du projet. Il reste encore beaucoup de travail à faire pour implémenter toutes les fonctionnalités du rendu Surreal Engine, donc toute aide des lecteurs est la bienvenue sous forme de code et de demandes d’extraction.

OpenAL pris en charge !

La grande chance est que Surreal Engine utilise OpenAL pour la sortie audio. Après avoir écrit un simple hello world dans OpenAL et l’avoir assemblé dans WebAssembly à l’aide d’Emscripten, il m’est devenu clair à quel point tout était simple et j’ai décidé de porter le son.
Après plusieurs heures de débogage, il est devenu évident que l’implémentation OpenAL d’Emscripten avait plusieurs bugs, par exemple, lors de l’initialisation de la lecture du nombre de canaux mono, la méthode a renvoyé un nombre infini, et après avoir essayé d’initialiser un vecteur de taille infinie, C++ plante avec l’exception vector::length_error.
Nous avons réussi à contourner ce problème en codant en dur le nombre de canaux mono à 2 048 :


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



#if __EMSCRIPTEN__

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

#endif



Y a-t-il un réseau ?

Surreal Engine ne prend actuellement pas en charge le jeu en ligne, jouer avec des robots est pris en charge, mais nous avons besoin de quelqu’un pour écrire l’IA pour ces robots. Théoriquement, vous pouvez implémenter un jeu en réseau sur WebAssembly/Emscripten à l’aide de Websockets.

Conclusion

En conclusion, je voudrais dire que le portage de Surreal Engine s’est avéré assez fluide grâce à l’utilisation de bibliothèques pour lesquelles il existe des ports Emscripten, ainsi que mon expérience passée dans l’implémentation d’un jeu en C++ pour WebAssembly. sur Emscripten. Vous trouverez ci-dessous des liens vers des sources de connaissances et des référentiels sur le sujet.
M-M-M-MONSTER KILL !

De plus, si vous souhaitez aider le projet, de préférence avec le code de rendu WebGL/OpenGL ES, alors écrivez-moi dans Telegram :
https://t.me/demenscave

Liens

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

https://github.com/dpjudas/SurrealEngine

Activer le rétroéclairage du clavier USB sur macOS

J’ai récemment acheté un clavier USB Getorix GK-45X très bon marché avec rétroéclairage RVB. Après l’avoir connecté à un MacBook Pro équipé d’un processeur M1, il est devenu évident que le rétroéclairage RVB ne fonctionnait pas. Même en appuyant sur la combinaison magique Fn + Scroll Lock, seul le niveau de rétroéclairage de l’écran du MacBook a changé.
Il existe plusieurs solutions à ce problème, à savoir OpenRGB (ne fonctionne pas), HID LED Test (ne fonctionne pas). Seul l’utilitaire kvmswitch a fonctionné :
https://github.com/stoutput/OSX-KVM

Vous devez le télécharger depuis GitHub et l’autoriser à s’exécuter à partir du terminal dans le panneau de sécurité des paramètres système.
Si je comprends bien de la description, après avoir lancé l’utilitaire, il envoie une pression Fn + Scroll Lock, allumant/éteignant ainsi le rétroéclairage du clavier.

Tri des arbres

Tri par arbre : tri à l’aide d’un arbre de recherche binaire. Complexité temporelle – O(n²). Dans un tel arbre, chaque nœud à gauche a des nombres inférieurs au nœud, à droite il y en a plus que le nœud, en venant de la racine et en imprimant les valeurs de gauche à droite, on obtient une liste triée de nombres . Surprenant, non ?

Considérez l’arbre de recherche binaire :

Derrick Coetzee (domaine public)

Essayez de lire manuellement les nombres en partant de l’avant-dernier nœud gauche du coin inférieur gauche, pour chaque nœud à gauche – un nœud – à droite.

Cela ressemblera à ceci :

  1. Avant-dernier nœud en bas à gauche – 3.
  2. Il a une branche gauche – 1.
  3. Prenez ce numéro (1)
  4. Ensuite, nous prenons le sommet 3 (1, 3)
  5. À droite se trouve la branche 6, mais elle contient des branches. Par conséquent, nous le lisons de la même manière.
  6. Branche gauche du nœud 6 numéro 4 (1, 3, 4)
  7. Le nœud lui-même est 6 (1, 3, 4, 6)
  8. Droite 7 (1, 3, 4, 6, 7)
  9. Montez jusqu’au nœud racine – 8 (1,3, 4,6, 7, 8)
  10. Nous imprimons tout à droite par analogie
  11. Nous obtenons la liste finale – 1, 3, 4, 6, 7, 8, 10, 13, 14

Pour implémenter l’algorithme dans le code, vous aurez besoin de deux fonctions :

  1. Assembler un arbre de recherche binaire
  2. Imprimer l’arbre de recherche binaire dans le bon ordre

L’arbre binaire de recherche est assemblé de la même manière qu’il est lu, un numéro est attaché à chaque nœud à gauche ou à droite, selon qu’il est inférieur ou supérieur.

Exemple en Lua :


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

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

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

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

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

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

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

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

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


numbersCount = 10
maxNumber = 9

numbers = {}

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

PrintArray(numbers)
Treesort(numbers)

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

Ссылки

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

Источники

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

Tree sort – YouTube

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

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

Tree Sort – GeeksforGeeks

Tree sort – Wikipedia

How to handle duplicates in Binary Search Tree? – GeeksforGeeks

Tree Sort | GeeksforGeeks – YouTube

Tri par seau

Tri par seaux : tri par seaux. L’algorithme est similaire au tri par comptage, à la différence que les nombres sont collectés dans des plages de « seaux », puis les seaux sont triés à l’aide de tout autre algorithme de tri suffisamment productif, et l’étape finale consiste à déplier les « seaux » par un, ce qui donne une liste triée

La complexité temporelle de l’algorithme est O(nk). L’algorithme fonctionne en temps linéaire pour des données obéissant à une loi de distribution uniforme. Pour faire simple, les éléments doivent être dans une certaine plage, sans « pointes », par exemple des nombres de 0,0 à 1,0. Si parmi ces nombres il y en a 4 ou 999, alors selon les lois de la cour, une telle rangée n’est plus considérée comme « paire ».

Exemple d’implémentation dans Julia :

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

    maxNumber = maximum(numbers)

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

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

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

end

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

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

Ссылки

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

Источники

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