Notre premier projet étant fait, et le second ayant des besoins commun, j’ai voulu créer ma première librairie et ainsi la partager entre mes projets comme le fait NPM si je publiais. Évidemment je suis sur un git privé, pas sur Github, ni sur NPM, du coup pour le déploiement etc. ben, c’est toute une histoire…
Donc on part d’un code fonctionnel inclus dans un folder de mon app, on crée un nouveau projet de type librairie et on colle ça dedans, on nettoie le premier projet et on fait le lien entre les libs, on compile l’un et l’autre, l’impatience est à son comble ! Et ça ne va pas…
Donc on part d’une lib de référence (Material Design) et on regarde comment ils font, on s’en inspire, on prend les conventions et on observe les liens, on compile etc. Le stress est palpable ! Et ça ne va pas…
Là, le doute s’installe, malgré les lectures officielles, des articles, des mails, un stackoverflow, un appel à l’aide général et de la patience… rien n’y fit. Et la raison reste un mystère.
Que ça soit l’inclusion d’une lib en local, ou via son git perso, que ça soit un module qui ne va pas alors que l’autre oui, la folie s’installe… et surtout le rejet de ce méchant projet, ce qui le met en péril de progression.
Après avoir accepté l’échec de cette noble tentative et un gros FUCK à Angular et leur système « y a qu’les hypsters qui croivent » (comprendre : une élite peu nombreuse et souvent prétentieuse). Ô rage et désespoir… Enfin donc une idée surgit de la bonne vieille école des barbus, à l’ancienne mais restons propre.
Donc on part d’un code fonctionnel inclus dans un folder de mon app et on crée un répertoire vide en dehors et on tape le code dedans sans lui faire de mal, on ajoute un index.ts et on check que tous les liens de notre point d’entrée sont bons et accessibles.
On adapte notre projet avec le tsconfig.json et le package.json. On ajoute un script de post installation et on peut résumer ainsi : je git clone mon projet ou un nouveau et je fais le npm install, il rapatrie les node_modules nécessaires, ça on connait, il lance tout seul comme un grand le script de postinstall et va cloner notre « lib maison » sous forme de répertoire à l’intérieur de notre projet (ajouté au .gitignore), ensuite grâce aux modif du tsconfig.json, il suffit de faire une référence unique vers cette lib « comme » si c’était une lib NPM.
Toujours dans l’optique de documenter le plus proprement et intelligemment possible le projet Folkopedia, j’ai voulu m’occuper de la définition de l’API concernant Schema Definer.
Tout d’abord j’ai pensé un Google Sheet/Doc, mais, bien que fonctionnel, ce n’est pas le plus sexy ni adapté en terme de documentation.
Il existe des solutions comme ApiDoc, Swager, etc. et RAML sur lequel je me suis arrêté. L’idée n’est pas d’avoir un générateur d’API mais un moyen de documenter. Donc un fichier structurant me permettant d’avoir une sortie sous forme de documentation, ici un HTML dynamique.
Comme si c’était notre credo, la documentation stricte du site de base raml.org n’est pas tout à fait bonne, peut-être une confusion entre la v0.8 et la 1.0. Du coup, belotte et rebelotte, vous aurez à croiser quelques liens.
Dans mon cas je suis sous Visual Studio Code pour éditer mon fichier raml. Il y a l’extension RAML 3.0.1 de blzjns nécessitant raml2html à installer en console (besoin de node) via la commande :
npm i -g raml2html
Pour ma part le preview interne ne donne pas un résultat idéal, je vous conseil donc de compiler vous même votre HTML avec la commande :
raml2html api.raml > api.html
Du coup j’en profite pour vous montrer un exemple fonctionnel et pas fini.
Ce qui une fois compilé nous donnera ce type de rendu plutôt sympa.
Oui c’est en UTF-8 mais ça n’a pas bien géré le ô :s
Tout doucement une solution complète se met en place et ce n’est pas faute de l’avoir simplifié à la base. Tant mieux ça fait de l’expérience ! Et de belles prises de têtes en tête à tête ^^. Y a du boulot…
Suite de l’article précédent sur le même sujet, mais cette fois de manière plus technique. Nous voilà déjà 7 mois plus tard… Rien n’a été fait si ce n’est ce mois de Mai, du coup décortiquons.
Comme dit précédemment, c’est un gros projet, ambitieux et novateurs à différentes échelles, et donc accompagnés de complexités et difficultés pour le démarrer/réaliser. Oui OK je me cherche quelques excuses mais elles sont véritables.
« Mais tout vient à point à qui sait attendre » un sage aurait-il dit; et c’est effectivement le cas : l’illumination du premier jalon. Je vais donc vous parler de ce qui porte le nom, actuellement, de Schema Definer ou le définisseur de schéma.
« Mais qu’est-ce donc ? » demande la foule tel un 42 en réponse d’une longue attente. Tout simplement notre première étape, car vous n’imaginiez quand même pas que le site pouvait se gérer sans structure ?
Aussi permissif le site, en objectif, sera; autant il lui faut une mécanique de définitions pour vous proposer celle-ci. Quelle belle phrase.
Récapitulons après ce blabla. Jusque là nous avons défini un schéma de base de données, donc un moyen de stocker de l’informations organisées selon une définition. Cette dernière décrit un ensemble de tables représentant notre infinie possibilité à décrire tout système folklorique.
Ça c’était il y a +2 ans, selon les idées sur les ontologies. Ensuite j’ai cherché le stack ultime du web de demain… Ok sans succès, on va partir sur un Angular/Lumen pour débuter. Mais je ne désespère pas de trouver le design du web de demain quand on attaquera le front (partie visible et utilisable).
Déjà le fait d’avoir simplifié et décidé du stack, ça soulage, beaucoup même. Je vous recommande Homestead via Vagrant pour démarrer avec simplicité.
Revenons à nos moutons après cette digression. On a de quoi stocker de l’information brute qui n’aura du sens que si on lui en donne, c’est le rôle du Schema Definer. Le schéma sémantique sur le schéma structurelle (stockage).
Si je stock 36, rien ne me dit que c’est mon age, sauf si une définition l’indique au système.
Là je vous ai perdu, entre logique effrayante et explications à rallonge.
Comme dit nous avons un système de stockage différent de ce que l’on a habituellement (relationnel, non relationnel structuré, …) où la conception contient le sens des données. Mais nous, non. Notre mécanique retiendra des nombres et chaînes de textes reliées entre elles, de manière relationnelle.
OK, c’est quoi ce schéma ? Tout simplement la définition de ce que l’on veut encoder sur Folkopedia : une personne, un groupe, un document, …
Pour faire simple, en expliquant sommairement la mécanique triplet RDF, vous aurez un objet/entité (virtuel), qui a des propriétés avec des valeurs.
De manière appliquée : je suis l’instance d’une Personne, ayant pour surnom : Killan. Le schéma précise qu’il existe une entité Personne, avec pour propriété possible un surnom qui sera de valeur texte.
La valeur pourrait être la référence d’une autre entité. Genre une entité livre peut avoir une propriété auteur qui a comme valeur la référence d’une entité personne. Etc.
Avec une table de 3 colonnes vous avez votre système, simple mais horriblement pas performant. Et on ne va pas entrer dans ces histoires-là ici. Le schéma Folkopedia est différent tout en suivant le principe et un futur système de cache fera le delta de performances. On reviendra dessus bien plus tard.
Pour revenir sur le Schema Definer, vous pouvez prendre exemple sur le site de schema.org qui est le premier objectif. Vous pouvez prendre l’entité Book (livre) comme exemple.
Pour résumer, on peut dire que le Schema Definer est une administration de la structure de données du site Folkopedia.
C’est pour ça que j’ai parlé d’Angular, il nous faut une interface pour manipuler ce schéma. Peut-être qu’un jour on exposera ce site, comme explication de la démarche scientifique, on verra, rien n’est figé actuellement. La démarche m’importe beaucoup.
Bref, là on a un Lumen installé sur un Vagrant/Homestead, tout frais, et cet articles un point de départ publique. C’est parti !
Le rendu actuel est bien joli, mais ça manque d’effet de lumière et donc d’obscurité. Du coup j’ai tenté l’expérience, pleuré, cassé, et réussi à avoir un premier résultat.
D’abord c’est quoi cette notion de lumière/obscurité ? C’est tout simplement le fait d’avoir une image lumineuse ou assombrie sur base d’une source supposée de lumière et d’un calcul pour définir l’effet.
Une source ? N’importe quel Element peut être une source de lumière, j’ai créé un interface Illuminate pour qu’il se prenne pour un truc radioactif.
Un calcul ? J’ai déclaré,pour le test, que la lumière allait à 6 cases de distance en décroissant de 20% de manière arbitraire. Cependant j’ai prévu une interface (pas finie) dont le but sera de définir des paramètres pour l’éclairage, permettant ainsi des variables d’affichage (couleur, force, …).
Il s’agit donc de définir pour chaque Element de chaque Zones de tous les GridBlocks du subsetMap une valeur de « Brightness » (luminosité) sur base du calcul et des sources. Le tout sans faire de calculs inutiles.
J’ai opté pour le moment de la création du subsetMap pour agir. Ceci arrive uniquement lors d’un changement d’offset de la caméra (déplacement du héro en bord d’écran), on peut alors profiter de la boucle de création pour trouver les lumières de la Scene. Ensuite il faut parcourir à nouveau le subsetMap pour mettre le « Brightness » de tout le monde à 0 : le noir total. Oui deux fois, car nous agissons dans PlayerScene basé sur un IsoScene et la création du subsetMap est dans IsoScene.
Ensuite pour savoir ce que la lumière va affecter je me suis dit que le PathFinder ferait un bon système de propagation, respectant les murs et les objets. Ce n’est pas au point, et cette idée doit être poussée plus loin, mais le départ est pas mal. On imagine une source de 100% qui perd 20% à chaque changement de case, et on ajoute ceci à l’éclairage déjà présent.
La théorie est pas mal, on est en 2D isométrique quand même, on ne peut pas faire trop de folies et on est limité par l’architecture de TARS et des possibilités de « Canvas » (notre support HTML5 de dessin). En pratique : les performances s’effondre…
Mais que se passe-t-il ? Il faut savoir que pour avoir cet effet j’utilise le « filter » « brightness » de « Canvas », en gros une propriété au moment du dessin permettant d’affecter la luminosité de l’image que j’ajout pour composer la Scene. Je le fais déjà avec l’alpha sans soucis (cf titre daaboo). Bref, ça fonctionne mais prend un temps de dingue, ce qui rend le résultat non réactif et donne du mal au navigateur pour répondre.
Il n’y a pas d’autre façon, l’accès au données de l’image n’est permis qu’au niveau du rendu final et non à l’image que l’on va ajouter et on ne peut pas travailler sur l’image stockée en mémoire, d’une part car c’est pas permis et de l’autre car il nous faut l’original pour modifier les effets.
Une seule solution : la protestation ! À chanter comme une manif de métallo un jour de pont entre deux grèves…
Comment améliorer les performances ? Comment faire quand les outils manquent, les accès limités, les possibilités réduites… ? Il y a bien quelques solutions, mais l’idée n’est pas de demander plus de travail aux utilisateurs de TARS, enfin, le moins possible.
Je suis parti sur l’idée d’un cache, le soucis étant le temps de rendu, et on sait que les mises à jour ne sont que ponctuelles par comparaison. Mais comment ? Nous allons créer un « Canvas » par IsoElement (on va surement le descendre en SpriteElement par la suite (c’est fait :p) et on fait le rendu dans ce « Canvas » invisible. Au moment de composer la scène on copie le contenu à sa destination comme on le faisait avant, et on ne fait plus que ça jusqu’à ce que l’on doive régénérer le cache.
Pour vérifier que ça fonctionne bien avant de péter tous mes calculs, j’ai simulé un rendu dans mon cache, un carré rouge et au lieu de dessiner comme l’image ci-avant, on utilise le contenu du cache.
Je vous épargne le retour du bug de hauteur, le curseur qui fait bouger les Elements, les soucis liés à la division du calcul en 2 étapes, etc. On a déjà connu, déjà fixé, et j’ai dû le refaire encore une fois…
Une anecdote cependant, si vous ne dite pas à votre cache de se régénérer sur base des paramètres de votre Element (PlayerElement, TreasureElement, etc.), il restera tel quel depuis son premier rendu, en gros Squellettore ne marchait plus, il glissait, droit comme un pique sans changer d’orientation. Logique si vous avez lu. Du coup on doit penser à invalider le cache lors de nos mises à jour.
Vu qu’on parle d’animation, il faudra permettre de savoir quand une animation change afin de régénérer le cache à point nommé. Et hop un TODO de plus…
Une fois tout cela fait, on a un rendu avec des performances bien meilleures, cela demande un peu plus de manipulations car il faut penser au cache mais globalement c’est une nette différence.
Concernant le cache, certains automatismes pourront être mis en place en interne au lieu de demander à l’utilisateur de TARS d’y penser. Comme invalider le cache si vous changez d’orientation, l’alpha ou la luminosité. Ce qui est fait en même temps qu’écrire cet article :p et ajouter 3 TODOs… comme si j’en avais pas assez… ^^
Revenons à nos lumières. On a un rendu correct sur base du calcul déclaré plus avant, simple, efficace, même si pas fini (murs opposés, murs d’une case supérieure à la distance prévue, …). Du coup je décide d’en ajouter une seconde pour voir si le cumul fonctionne bien et c’est la fête du slip… Je n’ai pas fait de photo, mais en gros le point d’origine est assombri et génère de l’obscurité, les cases éclairées sont plus loin, bref ça ne va pas.
Mais pourquoi ? C’est une sacrée boucle à investiguer. Ce n’est pas la fonction d’action sur les Elements, ce n’est pas les préparatifs, ni les accès aux variables. C’est le propagateur (par extension le Pathfinder). En fait, par un malheureux hasard, il se peut que vous passiez plusieurs fois sur la même case au cours d’une boucle de propagation (ce qui peut arriver). Le truc c’est que je pensais que je les avais bien exclues, et c’est vrai, mais il faut aussi vérifier en préventif.
Mes tests montraient une quantité astronomique de calculs pour ma petite zone de 10×10, ce qui ne me semblait pas vraisemblable. C’est ce qui m’a mit la puce à l’oreille en gros. Un « If » de vérification et d’un coup la folie s’arrête, les lumières apparaissent et se cumulent proprement. En plus dans l’histoire on a augmenté les performances du Pathfinder et du propagateur pour les lumières !
Voici un résultat avec 2 lumières, le coffre statique et le héro mobile.
Il ne faut pas perdre de vue que ce n’est pas fini. Il y a un gros soucis de performance à la mise en place des lumières quand on s’occupe du subsetMap. Il faut considérer les lumières animées et s’occuper également des occlusions (ça serait top) et de l’orientations des objets, par exemple les murs, opposés ou de la case d’à côté nous tournant le dos. Là il va falloir creuser les théories les meilleures pour augmenter la qualité de l’effet sans défoncer les performances.
Une autre chose aussi, vu que nous travaillons en 2D isométrique, serait d’ombrer selon le caractère cubique de nos objets et de prévoir ainsi, soit une image South, une image East et pourquoi pas une image Top pour ombrer selon des calculs d’angle (les normales) et de distance. Le tout serait de fournir à ces objets des informations plus complète qu’actuellement.
Ceci dit, en l’état, c’est déjà chouette comme rendu !
Suite à mon article précédent j’ai souhaité investiguer, attiré par la curiosité maladive et la difficulté jusqu’à lors de compréhension de cette mécanique. Je vous liste ci dessous quelques articles qui m’ont guidé tant pour démarrer que pour me mélanger les pinceaux.
Pour résumer, si comme moi, vous êtes perdu voici un snippet de démarrage et une copie d’un conseil donné dans le dernier lien.
ng new tars --directory . --create-application=false
ng generate library tars-lib --prefix=tars
ng generate application tars-test
La dernière ligne est utile plus tard quand il faudra tester via une app angular la librairie que l’on souhaite publier.
À noter que mon but n’est pas de passer par NPM pour la publication ni d’aller sur GitHub pour distribuer mon code.
Le conseil du dernier article proposait les lignes ci-dessous dans votre package.json (le premier en root) dans l’espace des scripts.
"build_lib": "ng build tars-lib",
"npm_pack": "cd dist/tars-lib && npm pack",
"package": "npm run build_lib && npm run npm_pack",
Ma prochaine étape est le déplacement du code TARS au sein de l’espace tars-lib créé et de refaire les liens vers le fichier public_api.ts. Ensuite de créer, comme actuellement, une app démo/test, sans inclure l’éditeur, juste une scène et un rendu fonctionnel, simple et expressif.
Une fois validé, je pense que l’on peut alors faire le build de la version de la librairie TARS et créé un nouveau projet dédié pour HeroQuest et là, voir si l’éditeur et la base du jeu prenne bien la lib’.
Cette réflexion a pour origine le fait trop fréquent d’indécisions concernant l’affection envers une personne.
Concrètement le « je ne sais plus si je l’aime » ou le « tu l’aimes ? je ne sais pas » est un simple manque de logique.
Partons du principe que notre affection envers une personne est une valeur parmi une jauge, théorie que j’expliquerai peut-être plus tard.
Partant d’une jauge n’ayant qu’une graduation, plus vous rencontrez de personne plus votre jauge en aura. Car à chaque rencontre vous définirez une valeur pour cette personne.
Comment est définie cette valeur, là est la question qui nous permet de répondre à la question initiale de l’affection.
Dans un premier temps vous rencontrez la personne, elle va vous plaire ou non pour x raisons (autres jauges). Ensuite, peut-être que vous allez vous y intéresser et creuser la personne. Vous mettrez continuellement à jour vos jauges.
Jusque là nous avons une kyrielle d’informations sur la personne ainsi que des jauges ayant chacune leur valeur.
La somme de ces jauges définit la valeur d’affection. Plusieurs personne peuvent avoir la même valeur finale en ayant des valeurs de jauges différentes.
A cela on peut parler de goûts et donc d’un coefficient donnés par jauges ce qui va rendre les individus différent dans leur calcul de jauge. ainsi 2 personnes connaissant le même groupe d’autres personnes auront différentes valeurs affective à leur égards.
Pour répondre à la question il suffit de mettre à jour ses jauges, d’additionner et de prendre la décision qui s’impose.
Prenons le cas concret ou le partenaire d’un couple a une valeur affective qui diminue pour l’autre. Une partie des autres valeurs présentent au même instant peuvent passer au dessus. Il sera clairement question que notre individu est plus intéressé par cette autre personne que par l’initiale.
La raison dès lors sera trouvée parmi les jauges additionnées et ainsi il pourra expliquer son changement. Évidemment, selon l’individu il l’exprimera à sa façons…
Suite à cette première réflexion, une machine, une I.A. pour être précis, pourra selon exactement les même principes, mais avec moins de jauges, aimer une personne/I.A..
Comme dit, la seule différence réside dans la quantité de jauges. Même chez les humains, tous les individus n’ont pas le même nombre de jauges. Certains poussent leur réflexions, d’autres se contente de vivre.
Enfin, selon le principe expliqué précédemment que l’ont peut avoir une même valeur finale à plusieurs personnes, on peut donc aimer plusieurs personnes. Nos m?urs, notre éducation, etc. nous disent que l’on ne peut aimer qu’une personne mais dans la logique présente il ne sera que question de choix parmi les personnes ayant la valeur la plus hautes.
Le choix, toujours le choix, peut-être la partie la plus dure 🙂
Évidemment, avec la difficulté du choix, parfois on descend la sélection à la valeur suivante, et ainsi de suite. Mais là on parle du genre humain et de ses incohérences. 😉