Blog tech https://www.synbioz.com/blog/tech Blog tech Synbioz fr-fr Thu, 01 Dec 2022 09:04:57 GMT 2022-12-01T09:04:57Z fr-fr Rails et les value objects https://www.synbioz.com/blog/tech/rails-et-les-value-objects <div class="hs-featured-image-wrapper"> <a href="https://www.synbioz.com/blog/tech/rails-et-les-value-objects" title="" class="hs-featured-image-link"> <img src="https://www.synbioz.com/hubfs/carlos-felipe-ramirez-mesa--hZiCUVvmtM-unsplash.jpg" alt="Focus sur un ensemble de dés multicolores à 4, 10 et 12 faces" class="hs-featured-image" style="width:auto !important; max-width:50%; float:left; margin:0 15px 15px 0;"> </a> </div> <p>Les entrailles d’un framework cachent parfois des bouts de code fort intéressants&nbsp;! C’est le cas de la méthode <code>composed_of</code> du module <code>ActiveRecord::Aggregations</code> qui par plusieurs aspects va nous intéresser aujourd’hui&nbsp;: elle nous permet d’introduire une notion importante d’architecture logiciel, les <em>value objects</em>&nbsp;; et de revenir sur 10 ans de rebondissements autour de cette méthode&nbsp;! Sortez les popcorns 🍿</p> <p>Les entrailles d’un framework cachent parfois des bouts de code fort intéressants&nbsp;! C’est le cas de la méthode <code>composed_of</code> du module <code>ActiveRecord::Aggregations</code> qui par plusieurs aspects va nous intéresser aujourd’hui&nbsp;: elle nous permet d’introduire une notion importante d’architecture logiciel, les <em>value objects</em>&nbsp;; et de revenir sur 10 ans de rebondissements autour de cette méthode&nbsp;! Sortez les popcorns 🍿</p> <h2>Une vie mouvementée</h2> <p>Nous sommes en juin 2012, Rails arbore fièrement sa version 3.2&nbsp;! Et <a href="https://blog.plataformatec.com.br/2012/06/about-the-composed_of-removal/">dans un post</a>, faisant suite à une <a href="https://github.com/rails/rails/pull/6743">PR de Steve Klabnik</a>, Rafael França nous explique pourquoi <code>composed_of</code> sera prochainement déprécié, puis retiré à compter de la version 4.0 du framework.</p> <p>Les raisons sont une complexité superflue pour une méthode rarement utilisée qu’on pourrait qualifier de cosmétique (nous y reviendrons), et de multiples bugs relatifs à cette méthode dans le framework à l’époque.</p> <p>Seulement, tout ne se passa pas comme prévu, et deux mois plus tard, en août 2012…</p> <blockquote> <p>We have decided to stop introducing API deprecations in all point releases going forward. From now on, it’ll only happen in majors/minors.</p> <p>— @Rails, Twitter, 1er août 2012</p> </blockquote> <p>La décision fut alors prise de réintroduire cette méthode, toujours présente à ce jour dans la version 7.0 de Rails&nbsp;! Cette méthode et la documentation qui lui est associée reçoivent d’ailleurs toujours des améliorations, comme le montre cette <a href="https://github.com/rails/rails/pull/45877">PR de Neil Carvalho</a> datant de septembre 2022.</p> <p>Mais alors, à quoi peut bien servir cette méthode méconnue qui a bien failli disparaitre&nbsp;?</p> <h2>Déclarez vos objets de valeur</h2> <p>La méthode <code>composed_of</code> du module <code>ActiveRecord::Aggregations</code> permet de manipuler des <em>value objects</em>, c’est-à-dire des objets ayant pour seule vocation que de véhiculer une valeur. Un <em>value object</em> a la particularité d’être identifiable par la valeur qu’il véhicule et non pas par un identifiant. En d’autres termes, deux <em>value objects</em> sont égaux s’ils représentent la même valeur. Autre condition nécessaire, <a href="https://wiki.c2.com/?ValueObjectsShouldBeImmutable=">un <em>value object</em> se doit d’être immuable</a>. La notion de <em>value object</em> est très présente dans la littérature portant sur le Domain Driven Design. Un excellent article de <a href="https://www.sitepoint.com/ddd-for-rails-developers-part-2-entities-and-values/">Victor Savkin</a> fait d’ailleurs le lien entre Rails et DDD.</p> <p>Notez également que <a href="https://github.com/ruby/ruby/pull/6353">Ruby 3.1 introduit une nouvelle classe</a> de base appelée <code>Data</code> destinée à représenter des <em>value objects</em> immuables. Ces objets peuvent être étendus avec des méthodes personnalisées lors de leur définition. Pour les plus curieuses et curieux d’entre vous, <a href="https://blog.saeloun.com/2022/11/22/data-immutable-object">un article de Swaathi Kakarla</a> en fait la présentation.</p> <h3>Un cas d’usage</h3> <p>Prenons un exemple qui parlera à tout le monde&nbsp;: la manipulation de valeurs monétaires. Il arrive assez fréquemment que l’on ait à manipuler des montants et des devises, que ce soit dans le cadre d’une application e-commerce, ou tout simplement l’établissement d’une facture. Dans ce cas, nous avons pris l’habitude de stocker en base, dans deux champs distincts mais étroitement liés, ce montant (appelons-le <code>amount</code>) et la devise associée (nommons-la <code>currency</code>).</p> <p>Un <em>value object</em> nous permettra ici de manipuler ces deux informations au sein d’une même représentation. Nous pourrions imaginer la chose comme ceci, par exemple&nbsp;:</p> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">class</span> <span class="nc">Money</span> <span class="nb">attr_reader</span> <span class="ss">:amount</span><span class="p">,</span> <span class="ss">:currency</span> <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">amount</span><span class="p">,</span> <span class="n">currency</span> <span class="o">=</span> <span class="s2">"EUR"</span><span class="p">)</span> <span class="vi">@amount</span> <span class="o">=</span> <span class="n">amount</span> <span class="vi">@currency</span> <span class="o">=</span> <span class="n">currency</span> <span class="k">end</span> <span class="k">end</span> </code></pre> </div> </div> <p>Nous avons là un objet <code>Money</code> qui nous permet de manipuler des valeurs monétaires, et nous assure de toujours conserver ce lien entre montant et devise, l’un n’allant pas sans l’autre d’un point de vue fonctionnel. Seulement, il nous manque un petit quelque chose pour en faire un <em>value object</em>&nbsp;: nous avons besoin de définir l’égalité entre deux objets de cette classe&nbsp;!</p> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">class</span> <span class="nc">Money</span> <span class="kp">include</span> <span class="no">Comparable</span> <span class="c1"># …</span> <span class="k">def</span> <span class="nf">==</span><span class="p">(</span><span class="n">other_money</span><span class="p">)</span> <span class="n">amount</span> <span class="o">==</span> <span class="n">other_money</span><span class="p">.</span><span class="nf">amount</span> <span class="o">&amp;&amp;</span> <span class="n">currency</span> <span class="o">==</span> <span class="n">other_money</span><span class="p">.</span><span class="nf">currency</span> <span class="k">end</span> <span class="k">end</span> </code></pre> </div> </div> <p>Grace au module <code>Comparable</code> que l’on vient d’inclure, et à la méthode <code>==</code>, nous voici en mesure de comparer deux objets de la classe <code>Money</code>&nbsp;:</p> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">)</span> <span class="o">==</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kp">true</span> <span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">)</span> <span class="o">!=</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"USD"</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kp">true</span> </code></pre> </div> </div> <p>Mais un <em>value object</em> ne se limite pas forcément à l’encapsulation d’une ou plusieurs valeurs, il peut aussi présenter un ensemble de méthodes qui lui sont propres&nbsp;! Ici nous pourrions par exemple souhaiter convertir un montant dans une autre devise, ou encore comparer deux montants déclarés dans des devises différentes.</p> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">class</span> <span class="nc">Money</span> <span class="no">EXCHANGE_RATES</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"EUR_TO_JPY"</span> <span class="o">=&gt;</span> <span class="mi">146</span> <span class="p">}</span> <span class="c1"># …</span> <span class="k">def</span> <span class="nf">exchange_to</span><span class="p">(</span><span class="n">other_currency</span><span class="p">)</span> <span class="n">exchanged_amount</span> <span class="o">=</span> <span class="p">(</span><span class="n">amount</span> <span class="o">*</span> <span class="no">EXCHANGE_RATES</span><span class="p">[</span><span class="s2">"</span><span class="si">#{</span><span class="n">currency</span><span class="si">}</span><span class="s2">_TO_</span><span class="si">#{</span><span class="n">other_currency</span><span class="si">}</span><span class="s2">"</span><span class="p">]).</span><span class="nf">floor</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">exchanged_amount</span><span class="p">,</span> <span class="n">other_currency</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">&lt;</span><span class="o">=&gt;</span><span class="p">(</span><span class="n">other_money</span><span class="p">)</span> <span class="k">if</span> <span class="n">currency</span> <span class="o">==</span> <span class="n">other_money</span><span class="p">.</span><span class="nf">currency</span> <span class="n">amount</span> <span class="o">&lt;=&gt;</span> <span class="n">other_money</span><span class="p">.</span><span class="nf">amount</span> <span class="k">else</span> <span class="n">amount</span> <span class="o">&lt;=&gt;</span> <span class="n">other_money</span><span class="p">.</span><span class="nf">exchange_to</span><span class="p">(</span><span class="n">currency</span><span class="p">).</span><span class="nf">amount</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> </code></pre> </div> </div> <p>Notons que notre objet est immuable, la méthode <code>exchange_to</code> retourne donc une nouvelle instance de notre classe <code>Money</code>.</p> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">)</span> <span class="o">==</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kp">true</span> <span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">).</span><span class="nf">exchange_to</span><span class="p">(</span><span class="s2">"JPY"</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="c1">#&lt;Money:0x00007faee7162f68 @amount=730, @currency="JPY"&gt;</span> <span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">)</span> <span class="o">==</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">730</span><span class="p">,</span> <span class="s2">"JPY"</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kp">true</span> <span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">&gt;</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">)</span> <span class="o">&gt;</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">500</span><span class="p">,</span> <span class="s2">"JPY"</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kp">true</span> </code></pre> </div> </div> <h3>Et composed_of dans tout ça&nbsp;?</h3> <p>La méthode de classe <code>composed_of</code> appliquée sur un modèle <code>ActiveRecord</code> nous permet de lier les attributs de celui-ci pour les manipuler sous la forme d’un <em>value object</em>. Voici un exemple d’utilisation de notre classe <code>Money</code>&nbsp;:</p> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c1"># == Schema Information</span> <span class="c1">#</span> <span class="c1"># Table name: invoices</span> <span class="c1">#</span> <span class="c1"># id :integer not null, primary key</span> <span class="c1"># total_amount :decimal(, )</span> <span class="c1"># total_currency :string</span> <span class="k">class</span> <span class="nc">Invoice</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span> <span class="n">composed_of</span> <span class="ss">:total</span><span class="p">,</span> <span class="ss">class_name: </span><span class="s2">"Money"</span><span class="p">,</span> <span class="ss">mapping: </span><span class="p">{</span> <span class="ss">total_amount: :amount</span><span class="p">,</span> <span class="ss">total_currency: :currency</span> <span class="p">}</span> <span class="k">end</span> </code></pre> </div> </div> <p>Ainsi, nous pouvons directement utiliser une instance de la classe <code>Money</code> à travers l’attribut <code>total</code>, et ce en lecture comme en écriture&nbsp;!</p> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">&gt;</span> <span class="n">invoice</span> <span class="o">=</span> <span class="no">Invoice</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">total: </span><span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">))</span> <span class="o">=&gt;</span> <span class="c1">#&lt;Invoice id: nil, total_amount: 0.5e1, total_currency: "EUR"&gt;</span> <span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">&gt;</span> <span class="n">invoice</span><span class="p">.</span><span class="nf">total</span> <span class="o">=&gt;</span> <span class="c1">#&lt;Money:0x00007f1d1006b038 @amount=5, @currency="EUR"&gt;</span> <span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">&gt;</span> <span class="n">invoice</span><span class="p">.</span><span class="nf">total</span> <span class="o">=</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">500</span><span class="p">,</span> <span class="s2">"JPY"</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="c1">#&lt;Money:0x000055eca216b658 @amount=500, @currency="JPY"&gt;</span> <span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">&gt;</span> <span class="n">invoice</span><span class="p">.</span><span class="nf">total_amounnt</span> <span class="o">=&gt;</span> <span class="mf">0.5e3</span> <span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">&gt;</span> <span class="n">invoice</span><span class="p">.</span><span class="nf">total_currency</span> <span class="o">=&gt;</span> <span class="s2">"JPY"</span> </code></pre> </div> </div> <p>Très utile cette méthode, et cela clarifie par la même occasion notre intention&nbsp;! Notre code s’en trouve plus explicite, et plus facile à comprendre et à maintenir. De plus, nous limitons les responsabilités de notre modèle en cloisonnant dans des <em>value objects</em> les méthodes qui leur sont propres.</p> <p>Mais alors, pourquoi vouloir la supprimer de Rails&nbsp;?</p> <h2>Valeur ajoutée &amp; maintenabilité</h2> <p>Tout est dans la mesure. Cette méthode n’est au final qu’un sucre syntaxique, une fonctionnalité cosmétique, et celle-ci a un coût, notamment en termes de maintenabilité pour l’équipe de développement du framework. Ce coût est loin d’être négligeable, à en croire les multiples remontées de bugs qui lui sont imputées, et il convient dans ce cas de peser le pour et le contre afin de choisir entre conserver cette fonctionnalité ou la supprimer.</p> <p>L’un des arguments de poids à l’encontre de cette méthode, est le fait de devoir lui passer des <em>procs</em> et des <em>hashes</em> pour obtenir magiquement un comportement qui pourrait être décrit de manière bien plus explicite avec un simple objet Ruby. Arrêtons-nous un moment pour prendre deux exemples.</p> <p>Dans le cas le plus simple, celui d’un attribut unique, nous pourrions nous contenter d’un <em>serializer</em>. Admettons que dans notre exemple précédent, nous ayons choisi de faire fi de la devise. Nous pourrions ainsi écrire ceci&nbsp;:</p> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">class</span> <span class="nc">MoneySerializer</span> <span class="k">def</span> <span class="nf">dump</span><span class="p">(</span><span class="n">money</span><span class="p">)</span> <span class="n">money</span><span class="p">.</span><span class="nf">amount</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">load</span><span class="p">(</span><span class="n">amount</span><span class="p">)</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">amount</span><span class="p">)</span> <span class="k">end</span> <span class="k">end</span> <span class="k">class</span> <span class="nc">Invoice</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span> <span class="n">serialize</span> <span class="ss">:total_amount</span><span class="p">,</span> <span class="no">MoneySerializer</span><span class="p">.</span><span class="nf">new</span> <span class="k">end</span> </code></pre> </div> </div> <p>Autre approche, nous pourrions aussi faire appel à de simples accesseurs, comme ceci par exemple&nbsp;:</p> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">class</span> <span class="nc">Invoice</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span> <span class="k">def</span> <span class="nf">total</span> <span class="vi">@total</span> <span class="o">||=</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">total_amount</span><span class="p">,</span> <span class="n">total_currency</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">total</span><span class="o">=</span><span class="p">(</span><span class="n">money</span><span class="p">)</span> <span class="nb">self</span><span class="p">[</span><span class="ss">:total_amount</span><span class="p">]</span> <span class="o">=</span> <span class="n">money</span><span class="p">.</span><span class="nf">amount</span> <span class="nb">self</span><span class="p">[</span><span class="ss">:total_currency</span><span class="p">]</span> <span class="o">=</span> <span class="n">money</span><span class="p">.</span><span class="nf">currency</span> <span class="vi">@total</span> <span class="o">=</span> <span class="n">money</span> <span class="k">end</span> <span class="k">end</span> </code></pre> </div> </div> <p>Ces deux exemples nous montrent à quel point il est facile d’obtenir le même résultat, sans la magie de <code>composed_of</code>, mais surtout avec beaucoup plus de clarté, j’en veux pour preuve cet exemple tiré de la documentation d’ActiveRecord&nbsp;:</p> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">class</span> <span class="nc">NetworkResource</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span> <span class="n">composed_of</span> <span class="ss">:cidr</span><span class="p">,</span> <span class="ss">class_name: </span><span class="s1">'NetAddr::CIDR'</span><span class="p">,</span> <span class="ss">mapping: </span><span class="p">[</span> <span class="sx">%w(network_address network)</span><span class="p">,</span> <span class="sx">%w(cidr_range bits)</span> <span class="p">],</span> <span class="ss">allow_nil: </span><span class="kp">true</span><span class="p">,</span> <span class="ss">constructor: </span><span class="no">Proc</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="o">|</span><span class="n">network_address</span><span class="p">,</span> <span class="n">cidr_range</span><span class="o">|</span> <span class="no">NetAddr</span><span class="o">::</span><span class="no">CIDR</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="n">network_address</span><span class="si">}</span><span class="s2">/</span><span class="si">#{</span><span class="n">cidr_range</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="p">},</span> <span class="ss">converter: </span><span class="no">Proc</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="o">|</span><span class="n">value</span><span class="o">|</span> <span class="no">NetAddr</span><span class="o">::</span><span class="no">CIDR</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="n">value</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span> <span class="p">?</span> <span class="n">value</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span> <span class="p">:</span> <span class="n">value</span><span class="p">)</span> <span class="p">}</span> <span class="k">end</span> </code></pre> </div> </div> <p>On comprend rapidement ici que maintenir ce code et le tester sera des plus pénibles&nbsp;!</p> <p>Ceci étant, dans sa configuration la plus simple, ce petit sucre syntaxique reste attirant à l’œil et, sans convaincre celles et ceux fortement attachés aux principes du Domain Driven Design, devrait séduire les plus Rails-istes d’entre nous — Il suffit de ne pas être trop regardant de ce qu’il y a sous le capot&nbsp;;)</p> <h3>Petit bonus</h3> <p>Puisque nous parlons d’ActiveRecord, qu’en est-il du requêtage de ces attributs&nbsp;? Eh bien tout semble se passer le plus intuitivement du monde&nbsp;:</p> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="no">Invoice</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">total: </span><span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">42</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">))</span> </code></pre> </div> </div> <p>Si vous avez choisi de vous passer de <code>composed_of</code>, il s’agira simplement d’être explicite là aussi, à l’aide d’une méthode de classe par exemple&nbsp;:</p> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">costing</span><span class="p">(</span><span class="n">money</span><span class="p">)</span> <span class="n">where</span><span class="p">(</span><span class="ss">total_amount: </span><span class="n">money</span><span class="p">.</span><span class="nf">amount</span><span class="p">,</span> <span class="ss">total_currency: </span><span class="n">money</span><span class="p">.</span><span class="nf">currency</span><span class="p">)</span> <span class="k">end</span> </code></pre> </div> </div> <p>Le coût d’un code explicite ne semble pas excessif. Surtout au regard des <a href="https://github.com/rails/rails/pull/6743/files">768 lignes de code</a> nécessaires à cette fonctionnalité cosmétique.</p> <h2>Du discernement</h2> <p>Cet exemple nous montre une nouvelle fois à quel point <a href="https://www.synbioz.com/blog/tech/rails-nest-pas-simple">Rails n’est pas simple&nbsp;!</a> Il nous faut donc rester sur nos gardes, et prendre la mesure des choix techniques que nous faisons. Aussi insignifiants qu’ils puissent nous paraitre à première vue, leurs répercussions peuvent être considérable avec le temps, en particulier sur la maintenabilité, la pérennité et la testabilité de nos applications.</p> <p>L’équipe Synbioz.<br>Libres d’être ensemble.</p> <img src="https://track.hubspot.com/__ptq.gif?a=5437879&amp;k=14&amp;r=https%3A%2F%2Fwww.synbioz.com%2Fblog%2Ftech%2Frails-et-les-value-objects&amp;bu=https%253A%252F%252Fwww.synbioz.com%252Fblog%252Ftech&amp;bvt=rss" alt="" width="1" height="1" style="min-height:1px!important;width:1px!important;border-width:0!important;margin-top:0!important;margin-bottom:0!important;margin-right:0!important;margin-left:0!important;padding-top:0!important;padding-bottom:0!important;padding-right:0!important;padding-left:0!important; "> rails architecture ruby Fri, 25 Nov 2022 08:11:29 GMT fvantomme@synbioz.com (François Vantomme) https://www.synbioz.com/blog/tech/rails-et-les-value-objects 2022-11-25T08:11:29Z Libretro et au delà https://www.synbioz.com/blog/tech/libretro-et-au-dela <div class="hs-featured-image-wrapper"> <a href="https://www.synbioz.com/blog/tech/libretro-et-au-dela" title="" class="hs-featured-image-link"> <img src="https://www.synbioz.com/hubfs/lorenzo-herrera-p0j-mE6mGo4-unsplash.jpg" alt="gamboy, cassettes audio, clavier et mini PC des années 90 sous une lumière bleu-violet" class="hs-featured-image" style="width:auto !important; max-width:50%; float:left; margin:0 15px 15px 0;"> </a> </div> <p>La dernière fois, nous avons pu mettre en place un Core Libretro basique, mais nous avons vu les principaux points à prendre en compte pour pouvoir itérer par-dessus. Aujourd’hui, je vous propose d’aller encore plus loin&nbsp;: intégrer <a href="https://mruby.org/">Mruby</a> à notre Core pour pouvoir le scripter en Ruby.</p> <p>La dernière fois, nous avons pu mettre en place un Core Libretro basique, mais nous avons vu les principaux points à prendre en compte pour pouvoir itérer par-dessus. Aujourd’hui, je vous propose d’aller encore plus loin&nbsp;: intégrer <a href="https://mruby.org/">Mruby</a> à notre Core pour pouvoir le scripter en Ruby.</p> <p>Pour information, Mruby est une implémentation plus légère du standard ISO de Ruby. Bien qu’il y ait d’autres usages, cette implémentation est principalement dédiée à être embarquée dans un autre programme pour lui ajouter des possibilités de script en Ruby.</p> <p>Avant d’aller plus loin, voilà quelques liens qui vous seront utiles&nbsp;:</p> <ul> <li><a href="https://www.synbioz.com/blog/tech/libretro">Le premier article de la série</a>. C’est une présentation de la Libretro et de ses concepts</li> <li><a href="https://www.synbioz.com/blog/tech/ecrire-un-core-libretro">Le deuxième article de la série</a>. Un guide pour implémenter un Core Libretro basique (en (Zig)[https://ziglang.org/])</li> <li><a href="https://github.com/dantecatalfamo/mruby-zig">Le binding Mruby</a>. Ce binding permet d’utiliser Mruby de manière plus idiomatique</li> <li><a href="https://github.com/hfabre/zigretro-core/tree/extract-engine">L’état du code après le dernier article et un peu de refactoristaion</a>. Je me baserais sur cette version pour la suite de l’article.</li> </ul> <h2>Intégrer Mruby</h2> <p>On suit simplement le <a href="https://github.com/dantecatalfamo/mruby-zig/blob/502e5b7b19dfabe9022861cb88d8fed08e6f6b39/README.md">cette version du Readme</a> qui va avec le <em>binding</em>.</p> <p>Petite note, si vous utilisez la version de en cours de développement (10.0.0) de Zig&nbsp;:</p> <p>Vous pouvez simplement vous référer à la <a href="https://github.com/dantecatalfamo/mruby-zig">version courrante du Readme</a>. Vous n’aurez pas non plus besoin de cloner Mruby comme un sous-module, le binding l’intégrant à votre place. Par contre il faudra penser à cloner le binding avec ses sous-modules&nbsp;:</p> <pre><code>git clone --recurse-submodules git@github.com:dantecatalfamo/mruby-zig.git </code></pre> <p>Sinon&nbsp;:</p> <p>Il faut d’abord récupérer <a href="https://github.com/mruby/mruby">le code source d’Mruby</a>, pour ma part je l’ai rajouté comme un sous-module Git. Si vous n’avez pas envie de rajouter cette complexité, libre à vous de simplement copier de votre répertoire de travail. On copie ensuite les fichiers <code>src/mruby.zig</code> et <code>src/mruby_compat.c</code>. Pour être clair, voilà mon arborescence après avoir fait tout ça&nbsp;:</p> <pre><code>. ├── build.zig ├── readme.md └── src ├── color.zig ├── engine.zig ├── libretro │&nbsp;&nbsp; └── libretro.h ├── main.zig └── mruby ├── mruby ├── * (Code source de Mruby) ├── mruby.zig └── mruby_compat.c </code></pre> <p>Il faut ensuite rajouter les instructions de compilation dans notre fichier <code>build.zig</code>. Je vous inviter à vous suivre les étapes ci-dessous&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// build.zig</span> <span class="c">// Vous pouvez simplement copier ça à la place de ligne 10 dans la précédente version du fichier</span> <span class="n">lib</span><span class="p">.</span><span class="nf">addIncludeDir</span><span class="p">(</span><span class="s">"src/libretro"</span><span class="p">);</span> <span class="n">lib</span><span class="p">.</span><span class="nf">addSystemIncludeDir</span><span class="p">(</span><span class="s">"src/mruby/mruby/include"</span><span class="p">);</span> <span class="n">lib</span><span class="p">.</span><span class="nf">addLibPath</span><span class="p">(</span><span class="s">"src/mruby/mruby/build/host/lib"</span><span class="p">);</span> <span class="n">lib</span><span class="p">.</span><span class="nf">addCSourceFile</span><span class="p">(</span><span class="s">"src/mruby/mruby_compat.c"</span><span class="p">,</span> <span class="o">&amp;.</span><span class="p">{});</span> <span class="n">lib</span><span class="p">.</span><span class="nf">addPackagePath</span><span class="p">(</span><span class="s">"mruby"</span><span class="p">,</span> <span class="s">"src/mruby/mruby.zig"</span><span class="p">);</span> <span class="n">lib</span><span class="p">.</span><span class="nf">linkSystemLibrary</span><span class="p">(</span><span class="s">"mruby"</span><span class="p">);</span> </code></pre> </div> </div> <p>Et pour finir on compile Mruby&nbsp;:</p> <div class="language-sh highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="nb">cd </span>src/mruby/mruby ./minirake </code></pre> </div> </div> <p>Si vous avez bien suivi les indications, tout devrait compiler sans problème&nbsp;:</p> <div class="language-sh highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code>zig build </code></pre> </div> </div> <h2>Mise en place de l’environnement</h2> <p>D’abord, on va définir ce que l’on cherche à obtenir. J’ai voulu rester sur quelque chose de simple mais qui couvre selon moi une bonne partie de ce qu’on peut vouloir faire avec Mruby&nbsp;:</p> <ul> <li>Mettre a disposition de notre environnement une nouvelle classe</li> <li>Appeler une fonction définie dans notre moteur en Ruby</li> <li>Appeler une fonction définie en Ruby depuis notre moteur</li> <li>Préremplir l’environnement Mruby en Ruby directement</li> </ul> <p>On va commencer par initialiser l’environnement Mruby&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// engine.zig</span> <span class="c">// On importe Mruby</span> <span class="k">const</span> <span class="n">mruby</span> <span class="o">=</span> <span class="nb">@import</span><span class="p">(</span><span class="s">"mruby"</span><span class="p">);</span> <span class="c">// La variable qui contiendra notre classe Mruby</span> <span class="k">var</span> <span class="n">color_class</span><span class="p">:</span> <span class="o">*</span><span class="n">mruby</span><span class="p">.</span><span class="py">RClass</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span> <span class="k">pub</span> <span class="k">const</span> <span class="n">Engine</span> <span class="o">=</span> <span class="k">struct</span> <span class="p">{</span> <span class="c">// On rajoute l'état de Mruby dans un nouveau membre de notre structure Engine</span> <span class="n">mrb</span><span class="p">:</span> <span class="o">*</span><span class="n">mruby</span><span class="p">.</span><span class="py">mrb_state</span><span class="p">,</span> <span class="c">// On modifie la fonction d'initialisation&nbsp;:</span> <span class="k">pub</span> <span class="k">fn</span> <span class="n">init</span><span class="p">(</span><span class="n">allocator</span><span class="p">:</span> <span class="n">std</span><span class="p">.</span><span class="py">mem</span><span class="p">.</span><span class="py">Allocator</span><span class="p">)</span> <span class="o">!</span><span class="n">Engine</span> <span class="p">{</span> <span class="k">var</span> <span class="n">screen_size</span> <span class="o">=</span> <span class="n">width</span> <span class="o">*</span> <span class="n">height</span><span class="p">;</span> <span class="k">var</span> <span class="n">framebuffer</span> <span class="o">=</span> <span class="k">try</span> <span class="n">allocator</span><span class="p">.</span><span class="nf">alloc</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span> <span class="n">screen_size</span> <span class="o">*</span> <span class="n">bpp</span><span class="p">);</span> <span class="c">// C'est ici que ça se passe</span> <span class="c">// On initialise l'environnement Mruby</span> <span class="k">var</span> <span class="n">new_mrb</span> <span class="o">=</span> <span class="k">try</span> <span class="n">mruby</span><span class="p">.</span><span class="nf">open</span><span class="p">();</span> <span class="c">// On enregistre notre nouvelle classe</span> <span class="n">color_class</span> <span class="o">=</span> <span class="k">try</span> <span class="n">new_mrb</span><span class="p">.</span><span class="nf">define_class</span><span class="p">(</span><span class="s">"Color"</span><span class="p">,</span> <span class="n">new_mrb</span><span class="p">.</span><span class="nf">object_class</span><span class="p">());</span> <span class="c">// On enregistre le constructeur de notre classe</span> <span class="c">// Le premier paramètre sert à définir dans quel scope sera accessible notre fonction (ici la classe Color)</span> <span class="c">// Le second c'est le nom de la fonction en Ruby</span> <span class="c">// Le troisième c'est la fonction Zig à appeler</span> <span class="c">// Le dernier paramètre sert à préciser les arguments qu'on attend, ici 3 obligatoires</span> <span class="n">new_mrb</span><span class="p">.</span><span class="nf">define_method</span><span class="p">(</span><span class="n">color_class</span><span class="p">,</span> <span class="s">"initialize"</span><span class="p">,</span> <span class="n">mrb_initialize_color</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span> <span class="p">.</span><span class="py">req</span> <span class="o">=</span> <span class="mi">3</span> <span class="p">});</span> <span class="c">// On prérempli l'environnement mruby avec le contenu du fichier donné</span> <span class="c">// La fonction builtin @embedFile permet d'écrire le contenu voulu dans le fichier</span> <span class="c">// qui sera ensuite remplacé au moment de la compilation à cet endroit</span> <span class="c">// C'est pratique pour éviter de prendre trop de place à définir</span> <span class="c">// une chaine de caractère multilignes</span> <span class="mi">_</span> <span class="o">=</span> <span class="n">new_mrb</span><span class="p">.</span><span class="nf">load_string</span><span class="p">(</span><span class="nb">@embedFile</span><span class="p">(</span><span class="s">"./mruby_ext.rb"</span><span class="p">));</span> <span class="c">// On défini une méthode, ici dans le module Kernel de Mruby, ce qui la rend accessible partout</span> <span class="n">new_mrb</span><span class="p">.</span><span class="nf">define_module_function</span><span class="p">(</span><span class="n">new_mrb</span><span class="p">.</span><span class="nf">kernel_module</span><span class="p">(),</span> <span class="s">"draw_rect"</span><span class="p">,</span> <span class="n">mrb_draw_rect</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span> <span class="p">.</span><span class="py">req</span> <span class="o">=</span> <span class="mi">5</span> <span class="p">});</span> <span class="c">// Et enfin on charge notre jeu</span> <span class="mi">_</span> <span class="o">=</span> <span class="k">try</span> <span class="n">new_mrb</span><span class="p">.</span><span class="nf">load_file</span><span class="p">(</span><span class="s">"./src/game.rb"</span><span class="p">);</span> <span class="k">return</span> <span class="n">Engine</span> <span class="p">{</span> <span class="p">.</span><span class="py">allocator</span> <span class="o">=</span> <span class="n">allocator</span><span class="p">,</span> <span class="p">.</span><span class="py">width</span> <span class="o">=</span> <span class="n">width</span><span class="p">,</span> <span class="p">.</span><span class="py">height</span> <span class="o">=</span> <span class="n">height</span><span class="p">,</span> <span class="p">.</span><span class="py">screen_size</span> <span class="o">=</span> <span class="n">screen_size</span><span class="p">,</span> <span class="p">.</span><span class="py">pitch</span> <span class="o">=</span> <span class="n">bpp</span> <span class="o">*</span> <span class="n">width</span> <span class="o">*</span> <span class="nb">@sizeOf</span><span class="p">(</span><span class="kt">u8</span><span class="p">),</span> <span class="p">.</span><span class="py">framebuffer</span> <span class="o">=</span> <span class="n">framebuffer</span><span class="p">,</span> <span class="p">.</span><span class="py">mrb</span> <span class="o">=</span> <span class="n">new_mrb</span><span class="p">,</span> <span class="c">// On oublie pas de stocker l'état de Mruby dans notre nouvelle instance</span> <span class="p">};</span> <span class="p">}</span> <span class="k">pub</span> <span class="k">fn</span> <span class="n">deinit</span><span class="p">(</span><span class="n">self</span><span class="p">:</span> <span class="n">Engine</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="c">// Et bien sur, on pense à fermer Mruby quand on quitte</span> <span class="n">self</span><span class="p">.</span><span class="py">mrb</span><span class="p">.</span><span class="nf">close</span><span class="p">();</span> <span class="n">self</span><span class="p">.</span><span class="py">allocator</span><span class="p">.</span><span class="nf">free</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="py">framebuffer</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> </code></pre> </div> </div> <p>Si vous êtes attentif, vous remarquerez qu’on demande à Mruby de faire appel à des fonctions que nous n’avons jamais définies. C’est le moment de le faire, pour ma part je les ai mises tout en bas du fichier <code>engine.zig</code>&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// engine.zig</span> <span class="c">// Les fonctions que nous allons intégrer à l'environnement Mruby ont toujours le même prototype</span> <span class="c">// En premier l'état actuel de l'interpréteur</span> <span class="c">// En second, le contexte, ici l'instance de la classe</span> <span class="c">// Et on doit renvoyer un `mruby.mrb_value` ce qui représente n'importe quelle valeur. Ici notre instance.</span> <span class="k">pub</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">mrb_initialize_color</span><span class="p">(</span><span class="n">mrb</span><span class="p">:</span> <span class="o">*</span><span class="n">mruby</span><span class="p">.</span><span class="py">mrb_state</span><span class="p">,</span> <span class="n">self</span><span class="p">:</span> <span class="n">mruby</span><span class="p">.</span><span class="py">mrb_value</span><span class="p">)</span> <span class="n">mruby</span><span class="p">.</span><span class="py">mrb_value</span> <span class="p">{</span> <span class="c">// L'interpreteur Mruby définis son propre type pour gérer les ints</span> <span class="k">var</span> <span class="n">r</span><span class="p">:</span> <span class="n">mruby</span><span class="p">.</span><span class="py">mrb_int</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">var</span> <span class="n">g</span><span class="p">:</span> <span class="n">mruby</span><span class="p">.</span><span class="py">mrb_int</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">var</span> <span class="n">b</span><span class="p">:</span> <span class="n">mruby</span><span class="p">.</span><span class="py">mrb_int</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c">// Cette fonction sert à récupérer les arguments d'un appel de fonction</span> <span class="c">// On passe par là simplement parce qu'en Ruby il n'y a pas de limite</span> <span class="c">// au nombre d'arguments, ça peut être arguments nommés ou bien avec une valeur par défaut</span> <span class="c">// et tout ça de n'importe quel type. Forcément le traitement est un peu complexe et</span> <span class="c">// c'est cette fonction qui va le faire pour nous selon le format qu'on lui donne.</span> <span class="c">// Pour avoir plus d'info sur ce format&nbsp;: https://github.com/mruby/mruby/blob/HEAD/include/mruby.h#L909</span> <span class="mi">_</span> <span class="o">=</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">get_args</span><span class="p">(</span><span class="s">"iii"</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span> <span class="o">&amp;</span><span class="n">r</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">g</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">b</span> <span class="p">});</span> <span class="c">// On remplit des variables d'instances avec les valeurs récupérées en paramètre</span> <span class="c">// La fonction intern("") sert à récupérer l'équivalent d'une chaine de caractère en symbole</span> <span class="c">// La fonction int_value(1) sert à passer d'un mrb_int à un mrb_value (le type utilisé de partout)</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">iv_set</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">intern</span><span class="p">(</span><span class="s">"@r"</span><span class="p">),</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">int_value</span><span class="p">(</span><span class="n">r</span><span class="p">));</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">iv_set</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">intern</span><span class="p">(</span><span class="s">"@g"</span><span class="p">),</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">int_value</span><span class="p">(</span><span class="n">g</span><span class="p">));</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">iv_set</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">intern</span><span class="p">(</span><span class="s">"@b"</span><span class="p">),</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">int_value</span><span class="p">(</span><span class="n">b</span><span class="p">));</span> <span class="c">// Et on retourne notre instance</span> <span class="k">return</span> <span class="n">self</span><span class="p">;</span> <span class="p">}</span> <span class="k">pub</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">mrb_draw_rect</span><span class="p">(</span><span class="n">mrb</span><span class="p">:</span> <span class="o">*</span><span class="n">mruby</span><span class="p">.</span><span class="py">mrb_state</span><span class="p">,</span> <span class="n">self</span><span class="p">:</span> <span class="n">mruby</span><span class="p">.</span><span class="py">mrb_value</span><span class="p">)</span> <span class="n">mruby</span><span class="p">.</span><span class="py">mrb_value</span> <span class="p">{</span> <span class="k">var</span> <span class="n">x</span><span class="p">:</span> <span class="n">mruby</span><span class="p">.</span><span class="py">mrb_int</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">var</span> <span class="n">y</span><span class="p">:</span> <span class="n">mruby</span><span class="p">.</span><span class="py">mrb_int</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">var</span> <span class="n">w</span><span class="p">:</span> <span class="n">mruby</span><span class="p">.</span><span class="py">mrb_int</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">var</span> <span class="n">h</span><span class="p">:</span> <span class="n">mruby</span><span class="p">.</span><span class="py">mrb_int</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">var</span> <span class="n">mrb_color</span><span class="p">:</span> <span class="n">mruby</span><span class="p">.</span><span class="py">mrb_value</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span> <span class="mi">_</span> <span class="o">=</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">get_args</span><span class="p">(</span><span class="s">"iiiio"</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span> <span class="o">&amp;</span><span class="n">x</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">y</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">w</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">h</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">mrb_color</span> <span class="p">});</span> <span class="c">// Ici le unreachable est généralement utilisé en Zig</span> <span class="c">// Pour signaler que s'il y a une erreur à cet endroit, c'est une erreur</span> <span class="c">// du développeur qui a mal utilisé quelque chose et sortir une backtrace</span> <span class="k">const</span> <span class="n">r</span> <span class="o">=</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">iv_get</span><span class="p">(</span><span class="n">mrb_color</span><span class="p">,</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">intern</span><span class="p">(</span><span class="s">"@r"</span><span class="p">)).</span><span class="nf">integer</span><span class="p">()</span> <span class="k">catch</span> <span class="k">unreachable</span><span class="p">;</span> <span class="k">const</span> <span class="n">g</span> <span class="o">=</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">iv_get</span><span class="p">(</span><span class="n">mrb_color</span><span class="p">,</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">intern</span><span class="p">(</span><span class="s">"@g"</span><span class="p">)).</span><span class="nf">integer</span><span class="p">()</span> <span class="k">catch</span> <span class="k">unreachable</span><span class="p">;</span> <span class="k">const</span> <span class="n">b</span> <span class="o">=</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">iv_get</span><span class="p">(</span><span class="n">mrb_color</span><span class="p">,</span> <span class="n">mrb</span><span class="p">.</span><span class="nf">intern</span><span class="p">(</span><span class="s">"@b"</span><span class="p">)).</span><span class="nf">integer</span><span class="p">()</span> <span class="k">catch</span> <span class="k">unreachable</span><span class="p">;</span> <span class="c">// On rappelle que cette fonction sera appelée depuis Ruby, on recevra donc des mrb_int</span> <span class="c">// On convertit donc tout ça vers le type dont nous avons besoin</span> <span class="k">var</span> <span class="n">c</span> <span class="o">=</span> <span class="n">clr</span><span class="p">.</span><span class="py">Color</span> <span class="p">{</span> <span class="p">.</span><span class="py">r</span> <span class="o">=</span> <span class="nb">@intCast</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span> <span class="n">r</span><span class="p">),</span> <span class="p">.</span><span class="py">g</span> <span class="o">=</span> <span class="nb">@intCast</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span> <span class="n">g</span><span class="p">),</span> <span class="p">.</span><span class="py">b</span> <span class="o">=</span> <span class="nb">@intCast</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span> <span class="n">b</span><span class="p">),</span> <span class="p">};</span> <span class="n">main</span><span class="p">.</span><span class="py">engine</span><span class="p">.</span><span class="nf">draw_rectangle</span><span class="p">(</span><span class="nb">@intCast</span><span class="p">(</span><span class="kt">u32</span><span class="p">,</span> <span class="n">x</span><span class="p">),</span> <span class="nb">@intCast</span><span class="p">(</span><span class="kt">u32</span><span class="p">,</span> <span class="n">y</span><span class="p">),</span> <span class="nb">@intCast</span><span class="p">(</span><span class="kt">u32</span><span class="p">,</span> <span class="n">w</span><span class="p">),</span> <span class="nb">@intCast</span><span class="p">(</span><span class="kt">u32</span><span class="p">,</span> <span class="n">h</span><span class="p">),</span> <span class="n">c</span><span class="p">);</span> <span class="k">return</span> <span class="n">self</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> </div> <p>Il nous reste plus que trois petites choses à faire pour pouvoir tester. En premier, écrire le fichier Ruby avec lequel on souhaite précharger des choses dans notre environnement Mruby&nbsp;:</p> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c1"># mruby_ext.rb</span> <span class="no">BLACK</span> <span class="o">=</span> <span class="no">Color</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="no">WHITE</span> <span class="o">=</span> <span class="no">Color</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">)</span> <span class="no">GREY</span> <span class="o">=</span> <span class="no">Color</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">122</span><span class="p">,</span> <span class="mi">122</span><span class="p">,</span> <span class="mi">122</span><span class="p">)</span> <span class="no">RED</span> <span class="o">=</span> <span class="no">Color</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="no">GREEN</span> <span class="o">=</span> <span class="no">Color</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="no">BLUE</span> <span class="o">=</span> <span class="no">Color</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">)</span> </code></pre> </div> </div> <p>Ensuite il faut notre jeu, pour le moment on va faire très simple, juste pour vérifier que tout fonctionne&nbsp;:</p> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c1"># game.rb</span> <span class="k">def</span> <span class="nf">run</span> <span class="n">c</span> <span class="o">=</span> <span class="no">Color</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">122</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">)</span> <span class="nb">p</span> <span class="n">c</span> <span class="n">draw_rect</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span> <span class="k">end</span> </code></pre> </div> </div> <p><em>Eh mais attends, c’est quoi cette fonction run ?</em></p> <p>Bien vu, il nous manque un petit quelque chose, pour cela on se retrouve dans la fonction <code>run()</code> de notre <code>engine</code> pour la modifier&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// engine.zig</span> <span class="k">pub</span> <span class="k">fn</span> <span class="n">run</span><span class="p">(</span><span class="n">self</span><span class="p">:</span> <span class="n">Engine</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="c">// On dessine un fond noir</span> <span class="n">self</span><span class="p">.</span><span class="nf">draw_rectangle</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="py">width</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="py">height</span><span class="p">,</span> <span class="n">clr</span><span class="p">.</span><span class="py">black</span><span class="p">);</span> <span class="c">// On appel la fonction run défini en Ruby</span> <span class="mi">_</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="py">mrb</span><span class="p">.</span><span class="nf">funcall</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="py">mrb</span><span class="p">.</span><span class="nf">kernel_module</span><span class="p">().</span><span class="nf">value</span><span class="p">(),</span> <span class="s">"run"</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span> <span class="c">// Et on colle tout ça dans notre frame buffer</span> <span class="n">self</span><span class="p">.</span><span class="nf">screen_to_frame_buffer</span><span class="p">();</span> <span class="p">}</span> </code></pre> </div> </div> <p>Si vous essayez de compiler ici, vous aurez quelques erreurs, voici les étapes qu’il nous manque&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// color.zig</span> <span class="c">// On a voulu trop bien, mais comme de toute façon la Libretro ne gère pas la transparence,</span> <span class="c">// on lui met une valeur par défaut à 0 dans notre structure Color&nbsp;:</span> <span class="k">pub</span> <span class="k">const</span> <span class="n">Color</span> <span class="o">=</span> <span class="k">struct</span> <span class="p">{</span> <span class="n">a</span><span class="p">:</span> <span class="kt">u8</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">r</span><span class="p">:</span> <span class="kt">u8</span><span class="p">,</span> <span class="n">g</span><span class="p">:</span> <span class="kt">u8</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="kt">u8</span> <span class="p">};</span> </code></pre> </div> </div> <p>Ensuite de nos fonctions mruby on a parfois besoin de faire appel à notre moteur, pour dessiner notamment. Pour ça on change la déclaration de notre variable <code>engine</code> dans le fichier <code>main.zig</code> pour la rendre publique&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// main.zig</span> <span class="c">// On rajoute simplement pub</span> <span class="k">pub</span> <span class="k">var</span> <span class="n">engine</span><span class="p">:</span> <span class="n">ngn</span><span class="p">.</span><span class="py">Engine</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span> </code></pre> </div> </div> <p>Et on importe notre fichier <code>main.zig</code></p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// engine.zig</span> <span class="k">const</span> <span class="n">main</span> <span class="o">=</span> <span class="nb">@import</span><span class="p">(</span><span class="s">"main.zig"</span><span class="p">):</span> </code></pre> </div> </div> <p>On compile puis on lance et vous devriez avoir un carré rose qui s’affiche ainsi que des logs qui affichent notre variable <code>c</code>.</p> <h2>Un peu d’interactivité</h2> <p>Et si on rajoutait un peu d’interactivité dans tout ça&nbsp;? Voilà le code du jeu auquel on voudrait arriver en Ruby&nbsp;:</p> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c1"># game.rb</span> <span class="k">class</span> <span class="nc">Game</span> <span class="no">SPEED</span> <span class="o">=</span> <span class="mi">1</span> <span class="k">def</span> <span class="nf">initialize</span> <span class="vi">@colors</span> <span class="o">=</span> <span class="p">[</span><span class="no">RED</span><span class="p">,</span> <span class="no">GREEN</span><span class="p">,</span> <span class="no">BLUE</span><span class="p">,</span> <span class="no">GREY</span><span class="p">,</span> <span class="no">WHITE</span><span class="p">]</span> <span class="vi">@color</span> <span class="o">=</span> <span class="no">WHITE</span> <span class="vi">@pos_x</span> <span class="o">=</span> <span class="mi">0</span> <span class="vi">@pos_y</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">move</span><span class="p">(</span><span class="n">direction</span><span class="p">)</span> <span class="k">case</span> <span class="n">direction</span> <span class="k">when</span> <span class="ss">:up</span> <span class="vi">@pos_y</span> <span class="o">-=</span> <span class="no">SPEED</span> <span class="k">when</span> <span class="ss">:down</span> <span class="vi">@pos_y</span> <span class="o">+=</span> <span class="no">SPEED</span> <span class="k">when</span> <span class="ss">:right</span> <span class="vi">@pos_x</span> <span class="o">+=</span> <span class="no">SPEED</span> <span class="k">when</span> <span class="ss">:left</span> <span class="vi">@pos_x</span> <span class="o">-=</span> <span class="no">SPEED</span> <span class="k">end</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">change_color</span> <span class="vi">@color</span> <span class="o">=</span> <span class="vi">@colors</span><span class="p">.</span><span class="nf">sample</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">tick</span> <span class="n">draw_rect</span><span class="p">(</span><span class="vi">@pos_x</span><span class="p">,</span> <span class="vi">@pos_y</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="vi">@color</span><span class="p">)</span> <span class="k">end</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">run</span> <span class="vi">@game</span> <span class="o">||=</span> <span class="no">Game</span><span class="p">.</span><span class="nf">new</span> <span class="vi">@game</span><span class="p">.</span><span class="nf">tick</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">up_press</span> <span class="vi">@game</span><span class="p">.</span><span class="nf">move</span><span class="p">(</span><span class="ss">:up</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">down_press</span> <span class="vi">@game</span><span class="p">.</span><span class="nf">move</span><span class="p">(</span><span class="ss">:down</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">right_press</span> <span class="vi">@game</span><span class="p">.</span><span class="nf">move</span><span class="p">(</span><span class="ss">:right</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">left_press</span> <span class="vi">@game</span><span class="p">.</span><span class="nf">move</span><span class="p">(</span><span class="ss">:left</span><span class="p">)</span> <span class="k">end</span> <span class="k">def</span> <span class="nf">start_press</span> <span class="vi">@game</span><span class="p">.</span><span class="nf">change_color</span> <span class="k">end</span> </code></pre> </div> </div> <p>Ensuite, il nous suffit de deux choses&nbsp;:</p> <ul> <li>Rajouter la gestion de la touche <em>start</em> (Entrée sur le clavier)</li> <li>Transmettre les appuis touche à notre script Ruby</li> </ul> <p>Pour ça, retour dans notre fichier <code>main.zig</code>&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// main.zig</span> <span class="c">// On rajoute la touche start à notre enum</span> <span class="k">const</span> <span class="n">Key</span> <span class="o">=</span> <span class="k">enum</span> <span class="p">{</span> <span class="n">up</span><span class="p">,</span> <span class="n">down</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">,</span> <span class="n">start</span> <span class="c">// Ici</span> <span class="p">};</span> <span class="c">// Puis on fait en sorte de transmettre l'information à notre engine</span> <span class="k">fn</span> <span class="n">process_inputs</span><span class="p">()</span> <span class="k">void</span> <span class="p">{</span> <span class="k">var</span> <span class="n">it</span> <span class="o">=</span> <span class="n">key_map</span><span class="p">.</span><span class="nf">iterator</span><span class="p">();</span> <span class="k">while</span> <span class="p">(</span><span class="n">it</span><span class="p">.</span><span class="nf">next</span><span class="p">())</span> <span class="p">|</span><span class="n">kv</span><span class="p">|</span> <span class="p">{</span> <span class="k">var</span> <span class="n">pressed</span> <span class="o">=</span> <span class="n">input_state_cb</span><span class="o">.?</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">lr</span><span class="p">.</span><span class="py">RETRO_DEVICE_JOYPAD</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">kv</span><span class="p">.</span><span class="py">key_ptr</span><span class="o">.*</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">pressed</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">switch</span> <span class="p">(</span><span class="n">kv</span><span class="p">.</span><span class="py">value_ptr</span><span class="o">.*</span><span class="p">)</span> <span class="p">{</span> <span class="n">Key</span><span class="p">.</span><span class="py">up</span> <span class="o">=&gt;</span> <span class="n">engine</span><span class="p">.</span><span class="nf">up_press</span><span class="p">(),</span> <span class="n">Key</span><span class="p">.</span><span class="py">down</span> <span class="o">=&gt;</span> <span class="n">engine</span><span class="p">.</span><span class="nf">down_press</span><span class="p">(),</span> <span class="n">Key</span><span class="p">.</span><span class="py">right</span> <span class="o">=&gt;</span> <span class="n">engine</span><span class="p">.</span><span class="nf">right_press</span><span class="p">(),</span> <span class="n">Key</span><span class="p">.</span><span class="py">left</span> <span class="o">=&gt;</span> <span class="n">engine</span><span class="p">.</span><span class="nf">left_press</span><span class="p">(),</span> <span class="n">Key</span><span class="p">.</span><span class="py">start</span> <span class="o">=&gt;</span> <span class="n">engine</span><span class="p">.</span><span class="nf">start_press</span><span class="p">(),</span> <span class="c">// Ici</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="c">// Et enfin on pense à signaler à la Libretro qu'on souhaite capter l'information</span> <span class="c">// lorsque la touche start est appuyée</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_init</span><span class="p">()</span> <span class="k">void</span> <span class="p">{</span> <span class="n">engine</span> <span class="o">=</span> <span class="n">ngn</span><span class="p">.</span><span class="py">Engine</span><span class="p">.</span><span class="nf">init</span><span class="p">(</span><span class="n">allocator</span><span class="p">)</span> <span class="k">catch</span> <span class="k">unreachable</span><span class="p">;</span> <span class="n">key_map</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="n">lr</span><span class="p">.</span><span class="py">RETRO_DEVICE_ID_JOYPAD_UP</span><span class="p">,</span> <span class="n">Key</span><span class="p">.</span><span class="py">up</span><span class="p">)</span> <span class="k">catch</span> <span class="p">{</span> <span class="n">handle_error</span><span class="p">(</span><span class="s">"Could not allocate memory"</span><span class="p">);</span> <span class="p">};</span> <span class="n">key_map</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="n">lr</span><span class="p">.</span><span class="py">RETRO_DEVICE_ID_JOYPAD_DOWN</span><span class="p">,</span> <span class="n">Key</span><span class="p">.</span><span class="py">down</span><span class="p">)</span> <span class="k">catch</span> <span class="p">{</span> <span class="n">handle_error</span><span class="p">(</span><span class="s">"Could not allocate memory"</span><span class="p">);</span> <span class="p">};</span> <span class="n">key_map</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="n">lr</span><span class="p">.</span><span class="py">RETRO_DEVICE_ID_JOYPAD_RIGHT</span><span class="p">,</span> <span class="n">Key</span><span class="p">.</span><span class="py">right</span><span class="p">)</span> <span class="k">catch</span> <span class="p">{</span> <span class="n">handle_error</span><span class="p">(</span><span class="s">"Could not allocate memory"</span><span class="p">);</span> <span class="p">};</span> <span class="n">key_map</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="n">lr</span><span class="p">.</span><span class="py">RETRO_DEVICE_ID_JOYPAD_LEFT</span><span class="p">,</span> <span class="n">Key</span><span class="p">.</span><span class="py">left</span><span class="p">)</span> <span class="k">catch</span> <span class="p">{</span> <span class="n">handle_error</span><span class="p">(</span><span class="s">"Could not allocate memory"</span><span class="p">);</span> <span class="p">};</span> <span class="n">key_map</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="n">lr</span><span class="p">.</span><span class="py">RETRO_DEVICE_ID_JOYPAD_START</span><span class="p">,</span> <span class="n">Key</span><span class="p">.</span><span class="py">start</span><span class="p">)</span> <span class="k">catch</span> <span class="p">{</span> <span class="n">handle_error</span><span class="p">(</span><span class="s">"Could not allocate memory"</span><span class="p">);</span> <span class="p">};</span> <span class="p">}</span> </code></pre> </div> </div> <p>Et enfin dans notre moteur il faut changer le fonctionnement des fonctions qui gère l’appui touche (et rajouter celle pour la touche <em>start</em>)&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// engine.zig</span> <span class="k">pub</span> <span class="k">fn</span> <span class="n">up_press</span><span class="p">(</span><span class="n">self</span><span class="p">:</span> <span class="o">*</span><span class="n">Engine</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="mi">_</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="py">mrb</span><span class="p">.</span><span class="nf">funcall</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="py">mrb</span><span class="p">.</span><span class="nf">kernel_module</span><span class="p">().</span><span class="nf">value</span><span class="p">(),</span> <span class="s">"up_press"</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span> <span class="p">}</span> <span class="k">pub</span> <span class="k">fn</span> <span class="n">down_press</span><span class="p">(</span><span class="n">self</span><span class="p">:</span> <span class="o">*</span><span class="n">Engine</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="mi">_</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="py">mrb</span><span class="p">.</span><span class="nf">funcall</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="py">mrb</span><span class="p">.</span><span class="nf">kernel_module</span><span class="p">().</span><span class="nf">value</span><span class="p">(),</span> <span class="s">"down_press"</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span> <span class="p">}</span> <span class="k">pub</span> <span class="k">fn</span> <span class="n">right_press</span><span class="p">(</span><span class="n">self</span><span class="p">:</span> <span class="o">*</span><span class="n">Engine</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="mi">_</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="py">mrb</span><span class="p">.</span><span class="nf">funcall</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="py">mrb</span><span class="p">.</span><span class="nf">kernel_module</span><span class="p">().</span><span class="nf">value</span><span class="p">(),</span> <span class="s">"right_press"</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span> <span class="p">}</span> <span class="k">pub</span> <span class="k">fn</span> <span class="n">left_press</span><span class="p">(</span><span class="n">self</span><span class="p">:</span> <span class="o">*</span><span class="n">Engine</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="mi">_</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="py">mrb</span><span class="p">.</span><span class="nf">funcall</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="py">mrb</span><span class="p">.</span><span class="nf">kernel_module</span><span class="p">().</span><span class="nf">value</span><span class="p">(),</span> <span class="s">"left_press"</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span> <span class="p">}</span> <span class="k">pub</span> <span class="k">fn</span> <span class="n">start_press</span><span class="p">(</span><span class="n">self</span><span class="p">:</span> <span class="o">*</span><span class="n">Engine</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="mi">_</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="py">mrb</span><span class="p">.</span><span class="nf">funcall</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="py">mrb</span><span class="p">.</span><span class="nf">kernel_module</span><span class="p">().</span><span class="nf">value</span><span class="p">(),</span> <span class="s">"start_press"</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span> <span class="p">}</span> </code></pre> </div> </div> <p>Vous pouvez aussi supprimer les membres <code>x</code> et <code>y</code> de notre structure <code>Engine</code> qui sont maintenant gérés par le script Ruby.</p> <p>On compile et on lance&nbsp;:</p> <div class="language-sh highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code>zig build chemin/vers/retroarch <span class="nt">-v</span> <span class="nt">-L</span> chemin/vers/ce/dossier/lib/libzigretro-core.<span class="o">{</span>dylib,so<span class="o">}</span> </code></pre> </div> </div> <p>Et voilà, si tout a bien fonctionné, on peut de nouveau faire bouger le carré blanc et le faire changer de couleur de manière aléatoire en appuyant sur la touche entrée.</p> <h2>Un peu plus d’intégration avec la Libretro</h2> <p>Si vous vous souvenez bien, dans la partie précédente, nous avions signalé à la Libretro que nous ne souhaitions pas démarrer avec un jeu vu que celui-ci était intégré à notre <em>core</em>.</p> <p>Eh bien maintenant que nous avons intégré Mruby je pense que nous aurions tout intérêt à la permettre, comme ça n’importe qui utilisant notre <em>core</em> pourrait le lancer avec le script (jeu) de son choix&nbsp;!</p> <p>En premier lieu, il faut retourner dans notre fichier principal. On devra ensuite modifier la configuration pour lui dire qu’on attend désormais un jeu, cela se fait dans la fonction <code>retro_set_environment</code> (tout ce qui est commenté, c’est ce qu’il faut supprimer)&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// main.zig</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_set_environment</span><span class="p">(</span><span class="n">cb</span><span class="p">:</span> <span class="n">lr</span><span class="p">.</span><span class="py">retro_environment_t</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="n">environ_cb</span> <span class="o">=</span> <span class="n">cb</span><span class="p">;</span> <span class="c">// Ici</span> <span class="c">// var allow_no_game = true;</span> <span class="k">if</span> <span class="p">(</span><span class="n">cb</span><span class="o">.?</span><span class="p">(</span><span class="n">lr</span><span class="p">.</span><span class="py">RETRO_ENVIRONMENT_GET_LOG_INTERFACE</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">logging</span><span class="p">))</span> <span class="p">{</span> <span class="n">log_cb</span> <span class="o">=</span> <span class="n">logging</span><span class="p">.</span><span class="py">log</span><span class="p">;</span> <span class="p">}</span> <span class="c">// Et ici</span> <span class="c">// if (cb.?(lr.RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME, &amp;allow_no_game)) {</span> <span class="c">// print("Unable to allow no game booting\n", .{});</span> <span class="c">// return;</span> <span class="c">// }</span> <span class="p">}</span> </code></pre> </div> </div> <p>Ensuite on modifie la fonction <code>retro_load_game</code> pour qu’elle charge notre jeu&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// main.zig</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_load_game</span><span class="p">(</span><span class="n">game_info</span><span class="p">:</span> <span class="p">[</span><span class="o">*</span><span class="n">c</span><span class="p">]</span><span class="n">lr</span><span class="p">.</span><span class="py">retro_game_info</span><span class="p">)</span> <span class="k">bool</span> <span class="p">{</span> <span class="k">var</span> <span class="n">fmt</span> <span class="o">=</span> <span class="n">lr</span><span class="p">.</span><span class="py">RETRO_PIXEL_FORMAT_XRGB8888</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">environ_cb</span><span class="o">.?</span><span class="p">(</span><span class="n">lr</span><span class="p">.</span><span class="py">RETRO_ENVIRONMENT_SET_PIXEL_FORMAT</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">fmt</span><span class="p">))</span> <span class="p">{</span> <span class="n">print</span><span class="p">(</span><span class="s">"XRGB8888 is not supported.</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span> <span class="p">}</span> <span class="c">// Ici. L'appel à @ptrCast permet de convertir la chaine de caractère C</span> <span class="c">// en slice Zig de manière à ne pas leaker le C dans notre moteur.</span> <span class="n">engine</span><span class="p">.</span><span class="nf">load_game</span><span class="p">(</span><span class="nb">@ptrCast</span><span class="p">([</span><span class="o">*</span><span class="p">:</span><span class="mi">0</span><span class="p">]</span><span class="k">const</span> <span class="kt">u8</span><span class="p">,</span> <span class="n">game_info</span><span class="o">.*</span><span class="p">.</span><span class="py">path</span><span class="p">))</span> <span class="k">catch</span> <span class="p">{</span> <span class="n">handle_error</span><span class="p">(</span><span class="s">"Failed to load game, make sure the path is correct"</span><span class="p">);</span> <span class="p">};</span> <span class="k">return</span> <span class="kc">true</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> </div> <p>Et enfin on modifie notre moteur pour ne plus charger le jeu de lui même et pour lui rajouter une fonction permettant de lui en faire charger un à la demande</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// engine.rb</span> <span class="k">pub</span> <span class="k">const</span> <span class="n">Engine</span> <span class="o">=</span> <span class="k">struct</span> <span class="p">{</span> <span class="c">// ...</span> <span class="k">pub</span> <span class="k">fn</span> <span class="n">init</span><span class="p">(</span><span class="n">allocator</span><span class="p">:</span> <span class="n">std</span><span class="p">.</span><span class="py">mem</span><span class="p">.</span><span class="py">Allocator</span><span class="p">)</span> <span class="o">!</span><span class="n">Engine</span> <span class="p">{</span> <span class="c">// ...</span> <span class="mi">_</span> <span class="o">=</span> <span class="k">try</span> <span class="n">new_mrb</span><span class="p">.</span><span class="nf">load_file</span><span class="p">(</span><span class="s">"./src/game.rb"</span><span class="p">);</span> <span class="c">// On supprime cette ligne</span> <span class="c">// ...</span> <span class="p">}</span> <span class="c">// Et on rajoute simplement une fonction pour charger un jeu</span> <span class="k">pub</span> <span class="k">fn</span> <span class="n">load_game</span><span class="p">(</span><span class="n">self</span><span class="p">:</span> <span class="n">Engine</span><span class="p">,</span> <span class="n">path</span><span class="p">:</span> <span class="p">[</span><span class="o">*</span><span class="p">:</span><span class="mi">0</span><span class="p">]</span><span class="k">const</span> <span class="kt">u8</span><span class="p">)</span> <span class="o">!</span><span class="k">void</span> <span class="p">{</span> <span class="mi">_</span> <span class="o">=</span> <span class="k">try</span> <span class="n">self</span><span class="p">.</span><span class="py">mrb</span><span class="p">.</span><span class="nf">load_file</span><span class="p">(</span><span class="n">path</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> </div> <p>On compile et on lance (on note le changement dans la seconde ligne de commande pour donner le chemin vers le jeu)</p> <div class="language-sh highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code>zig build /chemin/vers/RetroArch <span class="nt">-v</span> <span class="nt">-L</span> ./zig-out/lib/libzigretro-core.0.0.1.<span class="o">{</span>dylib,so<span class="o">}</span> ./src/game.rb </code></pre> </div> </div> <h2>Et voilà</h2> <p>Vous pouvez retrouver le code source complet de l’article <a href="https://github.com/hfabre/zigretro-core/tree/add-mruby">en suivant ce lien</a></p> <p>Je pense que cet article va clore la série sur la Libretro, nous avons maintenant toutes les bases pour nous amuser avec même s’il y a encore plein de choses à voir. Je suis vraiment content d’avoir pu intégrer Mruby au projet, c’est la première fois que je l’utilise et j’ai vraiment aimé découvrir son fonctionnement. Le gros bémol pour moi, c’est que la documentation qui est quasiment inexistante, lorsque j’étais bloqué j’ai quasiment tout le temps dû me tourner vers du code source (soit Mruby directement, soit des projets qui l’utilisent).</p> <p>Même si c’est génial de mettre les mains dans le cambouis, sachez pour les plus frileux qu’il existe des projets du même genre (pas basé sur la Libretro) qui vous permettront de coder vos jeux en Mruby, je pense bien sûr à <a href="https://dragonruby.org/">DragonRuby</a> au sujet duquel j’ai déjà écrit <a href="https://www.synbioz.com/blog/tech/introduction-dragonruby">une introduction</a> , mais aussi à son pendant libre, <a href="https://github.com/HellRok/Taylor">Taylor</a>.</p> <p>L’équipe Synbioz.<br>Libres d’être ensemble.</p> <img src="https://track.hubspot.com/__ptq.gif?a=5437879&amp;k=14&amp;r=https%3A%2F%2Fwww.synbioz.com%2Fblog%2Ftech%2Flibretro-et-au-dela&amp;bu=https%253A%252F%252Fwww.synbioz.com%252Fblog%252Ftech&amp;bvt=rss" alt="" width="1" height="1" style="min-height:1px!important;width:1px!important;border-width:0!important;margin-top:0!important;margin-bottom:0!important;margin-right:0!important;margin-left:0!important;padding-top:0!important;padding-bottom:0!important;padding-right:0!important;padding-left:0!important; "> back framework Fri, 07 Oct 2022 15:34:58 GMT hfabre@synbioz.com (Hugo Fabre) https://www.synbioz.com/blog/tech/libretro-et-au-dela 2022-10-07T15:34:58Z Recherche plein texte avec PostgreSQL https://www.synbioz.com/blog/tech/recherche-plein-texte-avec-postgresql <div class="hs-featured-image-wrapper"> <a href="https://www.synbioz.com/blog/tech/recherche-plein-texte-avec-postgresql" title="" class="hs-featured-image-link"> <img src="https://www.synbioz.com/hubfs/kelly-sikkema-gGsXfIql7BY-unsplash.jpg" alt="gaucher tenant un surligneur jaune au dessus d'un livre" class="hs-featured-image" style="width:auto !important; max-width:50%; float:left; margin:0 15px 15px 0;"> </a> </div> <p>J’ai récemment eu l’opportunité de travailler pour un client qui souhaitait mettre en place une recherche plus pertinente sur son logiciel. L’occasion rêvée de regarder du côté de la recherche plein texte (<em>full-text</em>) proposée nativement par PostgreSQL&nbsp;!</p> <p>J’ai récemment eu l’opportunité de travailler pour un client qui souhaitait mettre en place une recherche plus pertinente sur son logiciel. L’occasion rêvée de regarder du côté de la recherche plein texte (<em>full-text</em>) proposée nativement par PostgreSQL&nbsp;!</p> <h2>Révélez votre meilleur profil</h2> <p>La recherche était effectuée sur des profils&nbsp;: un intitulé, une description, rien de bien exotique.</p> <p>Historiquement la recherche de profil se faisait très sommairement sur la base de mots-clés et remontait des résultats peu pertinents. On recherchait, <a href="https://www.synbioz.com/blog/tech/faciliter-les-recherches-avec-ransack">via Ransack</a>, le terme exact faisant tout ou partie d’un mot, dans l’intitulé et la description des profils.</p> <p>Par ailleurs, les mots-clés n’étaient utilisés que pour filtrer les résultats, le tri, lui, était effectué selon le critère de tri choisi (par défaut&nbsp;: la date de publication, les plus récents en premier).</p> <p>Exemple&nbsp;: une recherche avec le mot clé «&nbsp;app&nbsp;» fera ressortir les profils dans lesquels figure le mot <code>app</code>, mais aussi <code>application</code>, <code>appétit</code> ou encore <code>rapport</code>.</p> <h2>Recherche plein texte</h2> <p>On comprend dès lors que cela manque de précision et que la pertinence n’est pas au rendez-vous. C’est là que PostgreSQL entre en jeu, et notamment ce que l’on nomme la recherche plein texte. Voici ce que nous en dit la documentation&nbsp;:</p> <blockquote> <p>La recherche plein texte (ou plus simplement la recherche de texte) permet de sélectionner des <strong>documents</strong> en langage naturel qui satisfont une <strong>requête</strong> et, en option, de les trier par intérêt suivant cette requête. Le type le plus fréquent de recherche concerne la récupération de tous les documents contenant les <strong>termes de recherche</strong> indiqués et de les renvoyer dans un ordre dépendant de leur <strong>similarité</strong> par rapport à la requête.</p> <p>— Chapitre 12. Recherche plein texte</p> </blockquote> <p>Il va donc nous être possible de formuler une requête SQL, un peu velue certes, qui nous permettra d’exprimer ce qui pour nous est digne d’intérêt.</p> <h3>Un peu de vocabulaire</h3> <p>À ce niveau, il est important de s’arrêter quelques instants sur le vocabulaire pour comprendre de quoi nous parlons exactement.</p> <p>Un <strong>document</strong> est l’unité de recherche, c’est-à-dire ce sur quoi nous souhaitons effectuer notre recherche. Cela peut être un simple champ d’une table de la base de données, ou la concaténation de plusieurs champs, éventuellement issus de plusieurs tables. Dans notre exemple, ce qui nous intéresse c’est l’intitulé et la description des profils.</p> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">SELECT</span> <span class="p">(</span><span class="n">coalesce</span><span class="p">(</span><span class="nv">"profiles"</span><span class="p">.</span><span class="nv">"label"</span><span class="p">::</span><span class="nb">text</span><span class="p">,</span> <span class="s1">''</span><span class="p">)</span> <span class="o">||</span> <span class="n">coalesce</span><span class="p">(</span><span class="nv">"profiles"</span><span class="p">.</span><span class="nv">"description"</span><span class="p">::</span><span class="nb">text</span><span class="p">,</span> <span class="s1">''</span><span class="p">))</span> <span class="k">AS</span> <span class="n">document</span> <span class="k">FROM</span> <span class="nv">"profiles"</span> </code></pre> </div> </div> <p>On utilise ici <code>coalesce()</code> pour éviter de manipuler <code>NULL</code> lors de la concaténation, ce qui conduirait à un résultat nul pour l’ensemble du document.</p> <p>Une <strong>requête</strong> (<code>tsquery</code>) se fait sur un document (<code>tsvector</code>) à l’aide de l’opérateur <code>@@</code>.</p> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">SELECT</span> <span class="n">to_tsvector</span><span class="p">(</span><span class="s1">'Portez ce vieux whisky au juge blond qui fume'</span><span class="p">)</span> <span class="o">@@</span> <span class="n">to_tsquery</span><span class="p">(</span><span class="s1">'vieux &amp; juge'</span><span class="p">)</span> <span class="o">+</span><span class="c1">----------+</span> <span class="o">|</span> <span class="o">?</span><span class="k">column</span><span class="o">?</span> <span class="o">|</span> <span class="o">|</span><span class="c1">----------|</span> <span class="o">|</span> <span class="k">True</span> <span class="o">|</span> <span class="o">+</span><span class="c1">----------+</span> </code></pre> </div> </div> <p>Remarquez que l’on utilise ici <code>to_tsvector()</code> et <code>to_tsquery()</code>. En effet, nous ne manipulons pas de simples textes. Un <code>tsquery</code> contient des termes de recherche qui doivent déjà être des lexèmes normalisés, et peut combiner plusieurs termes en utilisant les opérateurs AND, OR, NOT et FOLLOWED BY.</p> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">SELECT</span> <span class="n">to_tsquery</span><span class="p">(</span><span class="s1">'vieux &amp; whisky'</span><span class="p">)</span> <span class="o">+</span><span class="c1">--------------------+</span> <span class="o">|</span> <span class="n">to_tsquery</span> <span class="o">|</span> <span class="o">|</span><span class="c1">--------------------|</span> <span class="o">|</span> <span class="s1">'vieux'</span> <span class="o">&amp;</span> <span class="s1">'whiski'</span> <span class="o">|</span> <span class="o">+</span><span class="c1">--------------------+</span> </code></pre> </div> </div> <p>Notez le mot <code>whisky</code> remplacé par le lexème <code>whiski</code>.</p> <p>Pour pouvoir faire usage de ce <code>tsquery</code>, notre document lui sera présenté sous la forme d’un <code>tsvector</code>, c’est-à-dire une version pré-traitée et compacte de celui-ci.</p> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">SELECT</span> <span class="n">to_tsvector</span><span class="p">(</span><span class="s1">'Portez ce vieux whisky au juge blond qui fume'</span><span class="p">)</span> <span class="o">+</span><span class="c1">-----------------------------------------------------------------------------------+</span> <span class="o">|</span> <span class="n">to_tsvector</span> <span class="o">|</span> <span class="o">|</span><span class="c1">-----------------------------------------------------------------------------------|</span> <span class="o">|</span> <span class="s1">'au'</span><span class="p">:</span><span class="mi">5</span> <span class="s1">'blond'</span><span class="p">:</span><span class="mi">7</span> <span class="s1">'ce'</span><span class="p">:</span><span class="mi">2</span> <span class="s1">'fume'</span><span class="p">:</span><span class="mi">9</span> <span class="s1">'juge'</span><span class="p">:</span><span class="mi">6</span> <span class="s1">'portez'</span><span class="p">:</span><span class="mi">1</span> <span class="s1">'qui'</span><span class="p">:</span><span class="mi">8</span> <span class="s1">'vieux'</span><span class="p">:</span><span class="mi">3</span> <span class="s1">'whiski'</span><span class="p">:</span><span class="mi">4</span> <span class="o">|</span> <span class="o">+</span><span class="c1">-----------------------------------------------------------------------------------+</span> </code></pre> </div> </div> <p>Notre document est ici découpé en lexèmes présentés dans l’ordre alphabétique et suivis des indices auxquels on les retrouve dans le document. Pourtant, ces lexèmes n’ont pas l’air si normalisés que ça… c’est parce que la recherche plein texte se base sur une configuration qui par défaut considère qu’il s’agit d’un document en anglais. Sans entrer trop vite dans le détail de la configuration, sachez qu’on peut préciser un argument supplémentaire à nos fonctions <code>to_tsquery()</code> et <code>to_tsvector()</code>, voyez&nbsp;:</p> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c1">-- to_tsvector([ config regconfig, ] document text) returns tsvector</span> <span class="k">SELECT</span> <span class="n">to_tsvector</span><span class="p">(</span><span class="s1">'french'</span><span class="p">,</span> <span class="s1">'Portez ce vieux whisky au juge blond qui fume'</span><span class="p">)</span> <span class="o">+</span><span class="c1">---------------------------------------------------------+</span> <span class="o">|</span> <span class="n">to_tsvector</span> <span class="o">|</span> <span class="o">|</span><span class="c1">---------------------------------------------------------|</span> <span class="o">|</span> <span class="s1">'blond'</span><span class="p">:</span><span class="mi">7</span> <span class="s1">'fum'</span><span class="p">:</span><span class="mi">9</span> <span class="s1">'jug'</span><span class="p">:</span><span class="mi">6</span> <span class="s1">'port'</span><span class="p">:</span><span class="mi">1</span> <span class="s1">'vieux'</span><span class="p">:</span><span class="mi">3</span> <span class="s1">'whisky'</span><span class="p">:</span><span class="mi">4</span> <span class="o">|</span> <span class="o">+</span><span class="c1">---------------------------------------------------------+</span> </code></pre> </div> </div> <p>On comprend à présent que l’opérateur <code>@@</code> cherchera dans ce <code>tsvector</code> la présence (ou l’absence) de certains lexèmes décrits par notre <code>tsquery</code>. L’intérêt de passer par des lexèmes normalisés est de pouvoir découvrir les différentes formes d’un même mot sans avoir à toutes les préciser.</p> <p>Les plus attentifs d’entre vous auront remarqué la disparition des termes «&nbsp;ce&nbsp;», «&nbsp;au&nbsp;» et «&nbsp;qui&nbsp;». Il s’agit là d’un des effets de la configuration choisie qui ignore tout simplement certains mots jugés trop génériques et non pertinents.</p> <h2>Une recherche aux petits oignons</h2> <p>Mais alors, comment fonctionne cette configuration&nbsp;?</p> <blockquote> <p>En interne, la fonction to_tsvector appelle un <strong>analyseur</strong> qui casse le texte en <strong>jetons</strong> et affecte un <strong>type</strong> à chaque jeton. Pour chaque jeton, une liste de <strong>dictionnaires</strong> est consultée, liste pouvant varier suivant le type de jeton. Le premier dictionnaire qui reconnaît le jeton émet un ou plusieurs <strong>lexèmes</strong> pour représenter le jeton. Le choix de l’analyseur, des dictionnaires et des types de jetons à indexer est déterminé par la configuration de recherche plein texte sélectionnée. Il est possible d’avoir plusieurs configurations pour la même base, et des configurations prédéfinies sont disponibles pour différentes langues.</p> <p>— 12.3. Contrôler la recherche plein texte</p> </blockquote> <p>Ainsi, il nous est possible de choisir une configuration préexistante, comme <code>french</code> dans l’exemple précédent, mais aussi d’élaborer une configuration spécialement adaptée à nos besoins. Et cela tombe bien, car nous en aurons justement besoin&nbsp;! Prenons un exemple.</p> <h3>Cas particulier&nbsp;: C++, C#, .net</h3> <p>La recherche par dictionnaire, lorsque celle-ci est effectuée à l’aide de l’une des configurations mises à notre disposition, ignore un certain nombre de symboles (espaces, ponctuation) et les mots jugés non pertinents (<em>stopwords</em>&nbsp;; particules, mots de liaison). Or, certains langages de programmation, qui peuvent très bien faire l’objet d’une recherche, contiennent l’un ou l’autre, voire les deux&nbsp;!</p> <p>Pour palier cela, il nous faut constituer un <strong>thésaurus</strong> personnalisé, le porter à la connaissance de PostgreSQL, le lier à un dictionnaire lui aussi personnalisé, car il ne devra pas discriminer les <em>stopwords</em>, et enfin altérer la configuration du français pour les caractères ASCII et les symboles.</p> <p>Reprenons. Les dictionnaires sont utilisés pour éliminer les mots qui ne devraient pas être considérés dans une recherche et pour normaliser des mots qui peuvent prendre des formes diverses. Il existe différents types de dictionnaires&nbsp;:</p> <ol> <li>Termes courants (<em>stopwords</em>)</li> <li>Dictionnaire simple</li> <li>Dictionnaire des synonymes</li> <li>Dictionnaire thésaurus</li> <li>Dictionnaire Ispell</li> <li>Dictionnaire Snowball</li> </ol> <p>Une configuration définira ainsi la correspondance entre un type de jeton et un ou plusieurs dictionnaires. En ce qui nous concerne, nous avons besoin d’un thésaurus pour pouvoir associer un lexème à un ensemble de jetons.</p> <p>Notre thésaurus devra se trouver dans <code>/usr/local/share/postgresql/tsearch_data/</code> et nous le nommerons <code>prog_thesaurus.ths</code>. Sa syntaxe est plutôt transparente, voyez vous-même&nbsp;:</p> <pre><code>a + : aplus a # : asharp c - - : cminusminus c + + : cplusplus c/c + + : cplusplus c # : csharp . net : dotnet f # : fsharp f * : fstar j + + : jplusplus j # : jsharp m # : msharp q # : qsharp r + + : rplusplus xbase + + : xbaseplusplus x + + : xplusplus x # : xsharp z + + : zplusplus </code></pre> <p>Il nous faut maintenant instruire PostgreSQL de l’existence de ce thésaurus. Mais nous allons faire face à un petit souci&nbsp;: certains des jetons utilisés dans notre thésaurus sont des <em>stopwords</em>&nbsp;! Pour que notre thésaurus puisse les prendre en considération, il faut que ceux-ci ne soient pas ignorés par le dictionnaire de base. Ce dictionnaire (Snowball) est basé sur un algorithme de <em>stemming</em> qui sait comment réduire les variantes standard d’un mot vers une base, ou <em>stem</em>, en rapport avec la langue.</p> <p>Observons tout d’abord le comportement actuel.</p> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">ts_debug</span><span class="p">(</span><span class="s1">'french'</span><span class="p">,</span> <span class="s1">'c++'</span><span class="p">);</span> <span class="o">+</span><span class="c1">-----------+-----------------+-------+---------------+-------------+---------+</span> <span class="o">|</span> <span class="k">alias</span> <span class="o">|</span> <span class="n">description</span> <span class="o">|</span> <span class="n">token</span> <span class="o">|</span> <span class="n">dictionaries</span> <span class="o">|</span> <span class="k">dictionary</span> <span class="o">|</span> <span class="n">lexemes</span> <span class="o">|</span> <span class="o">|</span><span class="c1">-----------+-----------------+-------+---------------+-------------+---------|</span> <span class="o">|</span> <span class="n">asciiword</span> <span class="o">|</span> <span class="n">Word</span><span class="p">,</span> <span class="k">all</span> <span class="n">ASCII</span> <span class="o">|</span> <span class="k">c</span> <span class="o">|</span> <span class="p">{</span><span class="n">french_stem</span><span class="p">}</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span> <span class="p">[]</span> <span class="o">|</span> <span class="o">|</span> <span class="n">blank</span> <span class="o">|</span> <span class="k">Space</span> <span class="n">symbols</span> <span class="o">|</span> <span class="o">+</span> <span class="o">|</span> <span class="p">{}</span> <span class="o">|</span> <span class="o">&lt;</span><span class="k">null</span><span class="o">&gt;</span> <span class="o">|</span> <span class="o">&lt;</span><span class="k">null</span><span class="o">&gt;</span> <span class="o">|</span> <span class="o">|</span> <span class="n">blank</span> <span class="o">|</span> <span class="k">Space</span> <span class="n">symbols</span> <span class="o">|</span> <span class="o">+</span> <span class="o">|</span> <span class="p">{}</span> <span class="o">|</span> <span class="o">&lt;</span><span class="k">null</span><span class="o">&gt;</span> <span class="o">|</span> <span class="o">&lt;</span><span class="k">null</span><span class="o">&gt;</span> <span class="o">|</span> <span class="o">+</span><span class="c1">-----------+-----------------+-------+---------------+-------------+---------+</span> </code></pre> </div> </div> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">plainto_tsquery</span><span class="p">(</span><span class="s1">'french'</span><span class="p">,</span><span class="s1">'c++'</span><span class="p">)</span> <span class="n">NOTICE</span><span class="p">:</span> <span class="nb">text</span><span class="o">-</span><span class="k">search</span> <span class="n">query</span> <span class="k">contains</span> <span class="k">only</span> <span class="n">stop</span> <span class="n">words</span> <span class="k">or</span> <span class="n">does</span> <span class="k">not</span> <span class="n">contain</span> <span class="n">lexemes</span><span class="p">,</span> <span class="n">ignored</span> <span class="o">+</span><span class="c1">-----------------+</span> <span class="o">|</span> <span class="n">plainto_tsquery</span> <span class="o">|</span> <span class="o">|</span><span class="c1">-----------------|</span> <span class="o">|</span> <span class="o">|</span> <span class="o">+</span><span class="c1">-----------------+</span> </code></pre> </div> </div> <p>Effectivement, nous nous trouvons là dans une situation cocasse où l’ensemble des jetons de notre requête sont ignorés&nbsp;: le premier étant un <em>stopword</em>, les suivants des symboles.</p> <p>Créons donc un nouveau dictionnaire Snowball sans <em>stopwords</em>&nbsp;:</p> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">CREATE</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="k">DICTIONARY</span> <span class="k">public</span><span class="p">.</span><span class="n">french_synbioz_stem</span> <span class="p">(</span> <span class="k">TEMPLATE</span> <span class="o">=</span> <span class="n">pg_catalog</span><span class="p">.</span><span class="n">snowball</span><span class="p">,</span> <span class="k">LANGUAGE</span> <span class="o">=</span> <span class="s1">'french'</span> <span class="p">);</span> </code></pre> </div> </div> <p>Déclarons à présent notre thésaurus qui s’appuiera sur notre nouveau dictionnaire <code>french_synbioz_stem</code>&nbsp;:</p> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">CREATE</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="k">DICTIONARY</span> <span class="k">public</span><span class="p">.</span><span class="n">prog_thesaurus</span> <span class="p">(</span> <span class="k">TEMPLATE</span> <span class="o">=</span> <span class="n">pg_catalog</span><span class="p">.</span><span class="n">thesaurus</span><span class="p">,</span> <span class="n">DICTFILE</span> <span class="o">=</span> <span class="s1">'prog_thesaurus'</span><span class="p">,</span> <span class="k">DICTIONARY</span> <span class="o">=</span> <span class="s1">'public.french_synbioz_stem'</span> <span class="p">);</span> </code></pre> </div> </div> <p>Pour éviter toute mauvaise surprise, clonons la configuration <code>french</code>&nbsp;; c’est cette réplique que nous altèrerons par la suite&nbsp;:</p> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">CREATE</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="n">CONFIGURATION</span> <span class="k">public</span><span class="p">.</span><span class="n">french_synbioz</span> <span class="p">(</span> <span class="k">COPY</span> <span class="o">=</span> <span class="n">french</span> <span class="p">);</span> </code></pre> </div> </div> <p>Voyons quels dictionnaires sont définis par notre configuration&nbsp;:</p> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="err">\</span><span class="n">dF</span><span class="o">+</span> <span class="n">french_synbioz</span> <span class="nb">Text</span> <span class="k">search</span> <span class="n">configuration</span> <span class="nv">"pg_catalog.french_synbioz"</span> <span class="n">Parser</span><span class="p">:</span> <span class="nv">"pg_catalog.default"</span> <span class="o">+</span><span class="c1">-----------------+--------------+</span> <span class="o">|</span> <span class="n">Token</span> <span class="o">|</span> <span class="n">Dictionaries</span> <span class="o">|</span> <span class="o">|</span><span class="c1">-----------------+--------------|</span> <span class="o">|</span> <span class="n">asciihword</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span> <span class="o">|</span> <span class="n">asciiword</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span> <span class="o">|</span> <span class="n">email</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">file</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="nb">float</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="k">host</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">hword</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span> <span class="o">|</span> <span class="n">hword_asciipart</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span> <span class="o">|</span> <span class="n">hword_numpart</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">hword_part</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span> <span class="o">|</span> <span class="nb">int</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">numhword</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">numword</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">sfloat</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">uint</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">url</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">url_path</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="k">version</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">word</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span> <span class="o">+</span><span class="c1">-----------------+--------------+</span> </code></pre> </div> </div> <p>Modifions à présent notre configuration pour y lier les types de jetons de notre choix à notre thésaurus. L’ordre de déclaration des dictionnaires a ici une importance, on prendra garde à positionner notre thésaurus en tête de liste&nbsp;:</p> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">ALTER</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="n">CONFIGURATION</span> <span class="k">public</span><span class="p">.</span><span class="n">french_synbioz</span> <span class="k">ALTER</span> <span class="n">MAPPING</span> <span class="k">FOR</span> <span class="n">asciiword</span><span class="p">,</span> <span class="n">asciihword</span><span class="p">,</span> <span class="n">hword_asciipart</span><span class="p">,</span> <span class="n">word</span> <span class="k">WITH</span> <span class="n">prog_thesaurus</span><span class="p">,</span> <span class="n">french_synbioz_stem</span><span class="p">;</span> <span class="k">ALTER</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="n">CONFIGURATION</span> <span class="k">public</span><span class="p">.</span><span class="n">french_synbioz</span> <span class="k">DROP</span> <span class="n">MAPPING</span> <span class="n">IF</span> <span class="k">EXISTS</span> <span class="k">FOR</span> <span class="n">blank</span><span class="p">;</span> <span class="k">ALTER</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="n">CONFIGURATION</span> <span class="k">public</span><span class="p">.</span><span class="n">french_synbioz</span> <span class="k">ALTER</span> <span class="n">MAPPING</span> <span class="k">FOR</span> <span class="n">file</span><span class="p">,</span> <span class="k">host</span> <span class="k">WITH</span> <span class="n">prog_thesaurus</span><span class="p">,</span> <span class="k">simple</span><span class="p">;</span> </code></pre> </div> </div> <p>Pourquoi altérer <code>file</code>, me direz-vous&nbsp;? Parce que <code>c/c</code> dans «&nbsp;c/c++&nbsp;» est considéré comme un jeton de type <code>file</code>.</p> <p>Si l’on observe notre configuration, elle ressemble à présent à ceci&nbsp;:</p> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="err">\</span><span class="n">dF</span><span class="o">+</span> <span class="n">french_synbioz</span> <span class="nb">Text</span> <span class="k">search</span> <span class="n">configuration</span> <span class="nv">"public.french_synbioz"</span> <span class="n">Parser</span><span class="p">:</span> <span class="nv">"pg_catalog.default"</span> <span class="o">+</span><span class="c1">-----------------+------------------------------------+</span> <span class="o">|</span> <span class="n">Token</span> <span class="o">|</span> <span class="n">Dictionaries</span> <span class="o">|</span> <span class="o">|</span><span class="c1">-----------------+------------------------------------|</span> <span class="o">|</span> <span class="n">asciihword</span> <span class="o">|</span> <span class="n">prog_thesaurus</span><span class="p">,</span><span class="n">french_synbioz_stem</span> <span class="o">|</span> <span class="o">|</span> <span class="n">asciiword</span> <span class="o">|</span> <span class="n">prog_thesaurus</span><span class="p">,</span><span class="n">french_synbioz_stem</span> <span class="o">|</span> <span class="o">|</span> <span class="n">email</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">file</span> <span class="o">|</span> <span class="n">prog_thesaurus</span><span class="p">,</span><span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="nb">float</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="k">host</span> <span class="o">|</span> <span class="n">prog_thesaurus</span><span class="p">,</span><span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">hword</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span> <span class="o">|</span> <span class="n">hword_asciipart</span> <span class="o">|</span> <span class="n">prog_thesaurus</span><span class="p">,</span><span class="n">french_synbioz_stem</span> <span class="o">|</span> <span class="o">|</span> <span class="n">hword_numpart</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">hword_part</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span> <span class="o">|</span> <span class="nb">int</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">numhword</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">numword</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">sfloat</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">uint</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">url</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">url_path</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="k">version</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span> <span class="o">|</span> <span class="n">word</span> <span class="o">|</span> <span class="n">prog_thesaurus</span><span class="p">,</span><span class="n">french_synbioz_stem</span> <span class="o">|</span> <span class="o">+</span><span class="c1">-----------------+------------------------------------+</span> </code></pre> </div> </div> <p>Parfait&nbsp;! Si l’on teste à présent notre nouvelle configuration&nbsp;:</p> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">ts_debug</span><span class="p">(</span><span class="s1">'french_synbioz'</span><span class="p">,</span> <span class="s1">'c++'</span><span class="p">);</span> <span class="o">+</span><span class="c1">-------+-------------------+-------+--------------------------------------+----------------+---------------+</span> <span class="o">|</span> <span class="k">alias</span> <span class="o">|</span> <span class="n">description</span> <span class="o">|</span> <span class="n">token</span> <span class="o">|</span> <span class="n">dictionaries</span> <span class="o">|</span> <span class="k">dictionary</span> <span class="o">|</span> <span class="n">lexemes</span> <span class="o">|</span> <span class="o">|</span><span class="c1">-------+-------------------+-------+--------------------------------------+----------------+---------------|</span> <span class="o">|</span> <span class="n">word</span> <span class="o">|</span> <span class="n">Word</span><span class="p">,</span> <span class="k">all</span> <span class="n">letters</span> <span class="o">|</span> <span class="k">c</span><span class="o">++</span> <span class="o">|</span> <span class="p">{</span><span class="n">prog_thesaurus</span><span class="p">,</span><span class="n">french_synbioz_stem</span><span class="p">}</span> <span class="o">|</span> <span class="n">prog_thesaurus</span> <span class="o">|</span> <span class="p">[</span><span class="s1">'cplusplus'</span><span class="p">]</span> <span class="o">|</span> <span class="o">+</span><span class="c1">-------+-------------------+-------+--------------------------------------+----------------+---------------+</span> </code></pre> </div> </div> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">plainto_tsquery</span><span class="p">(</span><span class="s1">'french_synbioz'</span><span class="p">,</span><span class="s1">'c++'</span><span class="p">)</span> <span class="o">+</span><span class="c1">-----------------+</span> <span class="o">|</span> <span class="n">plainto_tsquery</span> <span class="o">|</span> <span class="o">|</span><span class="c1">-----------------|</span> <span class="o">|</span> <span class="s1">'cplusplus'</span> <span class="o">|</span> <span class="o">+</span><span class="c1">-----------------+</span> </code></pre> </div> </div> <p>Excellent&nbsp;! Nous observons à présent que notre thésaurus a reconnu «&nbsp;c++&nbsp;» comme étant un jeton et lui a substitué le lexème «&nbsp;cplusplus&nbsp;».</p> <h2>Stop ou encore&nbsp;?</h2> <p>Je tiens à attirer votre attention sur le fait qu’ignorer purement et simplement les <em>stopwords</em> n’est peut-être pas souhaitable en conditions réelles. En effet, les <em>stopwords</em> ont leur intérêt dans la mesure de pertinence des résultats retournés. Mais rien ne nous empêche de déclarer notre propre dictionnaire de <em>stopwords</em> en y excluant ceux qui entrent en conflit avec notre thésaurus&nbsp;!</p> <p>Pour cela on peut s’inspirer du dictionnaire <code>french.stop</code>.</p> <div class="language-sh highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="nb">cd</span> /usr/local/share/postgresql/tsearch_data/ <span class="nb">cp </span>french.stop french_synbioz.stop <span class="c"># modifier french_synbioz.stop</span> </code></pre> </div> </div> <p>Il nous suffit alors de préciser le dictionnaire <em>stopwords</em> à utiliser&nbsp;:</p> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">CREATE</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="k">DICTIONARY</span> <span class="k">public</span><span class="p">.</span><span class="n">french_synbioz_stem</span> <span class="p">(</span> <span class="k">TEMPLATE</span> <span class="o">=</span> <span class="n">pg_catalog</span><span class="p">.</span><span class="n">snowball</span><span class="p">,</span> <span class="k">LANGUAGE</span> <span class="o">=</span> <span class="s1">'french'</span><span class="p">,</span> <span class="n">STOPWORDS</span> <span class="o">=</span> <span class="s1">'french_synbioz'</span> <span class="p">);</span> </code></pre> </div> </div> <p>Si vous êtes amené à mettre à jour l’un de vos dictionnaires, pensez bien à recharger votre configuration&nbsp;! Pour cela, voici une petite astuce&nbsp;:</p> <div class="language-sql highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">ALTER</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="k">DICTIONARY</span> <span class="k">public</span><span class="p">.</span><span class="n">prog_thesaurus</span> <span class="p">(</span> <span class="n">dummy</span> <span class="p">);</span> <span class="k">ALTER</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="k">DICTIONARY</span> <span class="k">public</span><span class="p">.</span><span class="n">french_synbioz_stem</span> <span class="p">(</span> <span class="n">dummy</span> <span class="p">);</span> </code></pre> </div> </div> <h2>Et maintenant&nbsp;?</h2> <p>Nous venons de voir les bases de la recherche plein texte et de sa configuration. Cela fait déjà de nombreuses notions à assimiler, et encore, nous n’avons fait que les survoler&nbsp;! Pour approfondir cela, je vous invite à consulter la <a href="https://www.postgresql.org/docs/current/textsearch.html">documentation officielle</a> ou sa <a href="https://docs.postgresql.fr/current/textsearch.html">version française</a>, riches d’exemples et de détails.</p> <p>Dans un prochain article, nous aborderons un autre aspect important de la recherche plein texte&nbsp;: la pondération. S’ensuivra un dernier article pour clore cette série, il mettra l’accent sur l’indexation et <code>pg_search</code>, une gem Ruby nous permettant de créer des <em>scopes</em> ActiveRecord qui tirent parti de la recherche plein texte de PostgreSQL.</p> <p>L’équipe Synbioz.<br>Libres d’être ensemble.</p> <img src="https://track.hubspot.com/__ptq.gif?a=5437879&amp;k=14&amp;r=https%3A%2F%2Fwww.synbioz.com%2Fblog%2Ftech%2Frecherche-plein-texte-avec-postgresql&amp;bu=https%253A%252F%252Fwww.synbioz.com%252Fblog%252Ftech&amp;bvt=rss" alt="" width="1" height="1" style="min-height:1px!important;width:1px!important;border-width:0!important;margin-top:0!important;margin-bottom:0!important;margin-right:0!important;margin-left:0!important;padding-top:0!important;padding-bottom:0!important;padding-right:0!important;padding-left:0!important; "> back Thu, 29 Sep 2022 13:04:24 GMT fvantomme@synbioz.com (François Vantomme) https://www.synbioz.com/blog/tech/recherche-plein-texte-avec-postgresql 2022-09-29T13:04:24Z Le typage avec Sorbet https://www.synbioz.com/blog/tech/le-typage-avec-sorbet <div class="hs-featured-image-wrapper"> <a href="https://www.synbioz.com/blog/tech/le-typage-avec-sorbet" title="" class="hs-featured-image-link"> <img src="https://www.synbioz.com/hubfs/daniel-oberg-3sl9_ubYIno-unsplash.jpg" alt="sorbets aux fruits rouges disposés sur un plan en marbre blanc" class="hs-featured-image" style="width:auto !important; max-width:50%; float:left; margin:0 15px 15px 0;"> </a> </div> <p>Ruby est typé dynamiquement, c’est ce qui fait son charme, mais dans certaines situations cela peut s’avérer être un frein au développement si on n’a pas les bons outils pour détecter les erreurs rapidement. Qui ne s’est jamais trompé sur le type d’un paramètre ou fait une faute de syntaxe lors d’un appel de méthode un peu obscure&nbsp;?</p> <p>Ruby est typé dynamiquement, c’est ce qui fait son charme, mais dans certaines situations cela peut s’avérer être un frein au développement si on n’a pas les bons outils pour détecter les erreurs rapidement. Qui ne s’est jamais trompé sur le type d’un paramètre ou fait une faute de syntaxe lors d’un appel de méthode un peu obscure&nbsp;?</p> <p>Plus vite ce genre d’inattention est détecté, moins il est couteux de le corriger.</p> <p>Je vous propose donc de vous parler d’un outil qui peut être salvateur dans ces cas de figure&nbsp;: <a href="https://sorbet.org/">Sorbet</a>.</p> <p>En quelques mots, Sorbet est un vérificateur de type progressif pour Ruby. Développé par Stripe et devenu open-source en 2019, le projet est toujours en développement actif.</p> <p>Cette gem fait à la fois de la vérification statique et dynamique. Elle est composée d’une interface de ligne de commande qui peut analyser un projet <a href="https://blog.nelhage.com/post/why-sorbet-is-fast/">très rapidement</a>, ce qui peut, par exemple, réduire le temps d’exécution des tests ou d’un système qui tourne lors de l’exécution du code.</p> <p>L’aspect progressif de cette gem vient du fait qu’elle puisse être adoptée au fil du temps&nbsp;: par défaut rien n’est analysé. On peut donc, par exemple, développer une fonctionnalité puis ajouter le typage une fois le code solidifié. Il existe aussi plusieurs niveaux de rigueur allant du plus permissif jusqu’à interdire totalement l’usage du type <code>undefined</code>.</p> <h3>Comment ça marche concrètement&nbsp;?</h3> <p>Pour que Sorbet soit en mesure de vérifier le typage, il faut qu’il ait connaissance des types d’objets en entrée et sortie de chaque méthode, des types des constantes et variables, et ce, pour toute la base de code de la bibliothèque standard de Ruby jusqu’aux différents modules du framework sans oublier les gems.</p> <p>Pour cela il existe 2 systèmes d’annotations&nbsp;:</p> <ul> <li>la déclaration de signature de méthodes (appelée sig) directement dans le fichier source, qui nécessite d’utiliser le module dédié fournit par Sorbet pour écrire la signature, en Ruby, au-dessus de la méthode.</li> </ul> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c1"># typed: true</span> <span class="k">class</span> <span class="nc">Welcome</span> <span class="kp">extend</span> <span class="no">T</span><span class="o">::</span><span class="no">Sig</span> <span class="n">sig</span> <span class="p">{</span><span class="n">params</span><span class="p">(</span><span class="ss">name: </span><span class="no">String</span><span class="p">).</span><span class="nf">returns</span><span class="p">(</span><span class="no">String</span><span class="p">)}</span> <span class="k">def</span> <span class="nf">hello</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span> <span class="s2">"Hello </span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2"> !"</span> <span class="k">end</span> <span class="k">end</span> <span class="no">Welcome</span><span class="p">.</span><span class="nf">hello</span><span class="p">(</span><span class="s1">'World'</span><span class="p">)</span> <span class="c1"># =&gt; "Hello World"</span> <span class="no">Welcome</span><span class="p">.</span><span class="nf">hello</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span> <span class="c1"># =&gt; Parameter 'name': Expected type String, got type Integer with value 42 (TypeError)</span> </code></pre> </div> </div> <p>Cette déclaration est vérifiée aussi bien à l’exécution qu’en passant par la ligne de commande. L’accès au code source de la méthode étant obligatoire pour ce format, il est donc à privilégier pour tout le code qu’on écrit.</p> <ul> <li>la déclaration de signature dans un fichier à part, appelé RBI (<em>Ruby Interface</em>) spécifique à Sorbet, dont la syntaxe est identique au format précédent, mais sans avoir besoin ni de l’inclusion du module <code>T::Sig</code>, ni de l’implémentation finale de la méthode.</li> </ul> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c1"># welcome.rb</span> <span class="k">class</span> <span class="nc">Welcome</span> <span class="k">def</span> <span class="nf">hello</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span> <span class="s2">"Hello </span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2"> !"</span> <span class="k">end</span> <span class="k">end</span> <span class="c1"># welcome.rbi</span> <span class="k">class</span> <span class="nc">Welcome</span> <span class="n">sig</span> <span class="p">{</span><span class="n">params</span><span class="p">(</span><span class="ss">name: </span><span class="no">String</span><span class="p">).</span><span class="nf">returns</span><span class="p">(</span><span class="no">String</span><span class="p">)}</span> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">hello</span><span class="p">(</span><span class="nb">name</span><span class="p">);</span> <span class="k">end</span> <span class="k">end</span> </code></pre> </div> </div> <p>Cette méthode exige de synchroniser 2 fichiers, elle est donc à privilégier pour toutes les dépendances du projet qui n’ont pas vocation à être modifiées régulièrement&nbsp;: gems, bibliothèques, etc.</p> <p>On vient de mettre le doigt sur un inconvénient majeur&nbsp;: comment récupérer les signatures de toutes ces dépendances&nbsp;? Lorsqu’on travaille avec un framework (Rails ou autre) et plusieurs gems, devoir chercher les signatures de chaque méthode peut vite donner le tournis. Heureusement pour nous, Sorbet propose de générer les fichiers RBI à notre place, malheureusement pour nous il le fait assez mal. C’est ce qui m’a fait abandonner l’idée d’utiliser cette gem dans une application Rails lorsque j’avais voulu la tester. Mais depuis peu (<em>juillet 2022</em>), Sorbet recommande officiellement d’utiliser <a href="https://github.com/Shopify/tapioca">Tapioca</a> pour toute la génération des RBI et a mis à jour sa documentation en conséquence. On a maintenant à portée de main des commandes fonctionnelles pour récupérer les RBI communautaires ou officiels et générer les autres fichiers manquants.</p> <p>Après quelques jours d’utilisation, voici ce que j’ai noté&nbsp;:</p> <p>Ses avantages&nbsp;:</p> <ul> <li>la <a href="https://sorbet.org/docs/adopting">documentation de Sorbet</a> mise à jour avec Tapioca est claire, la mise en marche est très simple&nbsp;;</li> <li>la rapidité globale qui est toujours appréciable&nbsp;;</li> <li>la mise à disposition d’une extension VSCode et le support LSP pour son éditeur favori pour accéder sans effort aux définitions, autocompléter et plus encore&nbsp;;</li> <li>le choix du niveau de rigueur qui permet d’intégrer Sorbet dans un projet existant au fur et à mesure.</li> </ul> <p>Ses inconvénients&nbsp;:</p> <ul> <li>Sorbet comme Tapioca sont encore en développement et n’ont toujours pas de version stable&nbsp;;</li> <li>toutes les signatures se trouvent dans un dossier supplémentaire dans le projet, qui nécessite du versionnage&nbsp;;</li> <li>l’ajout de code Ruby par-dessus son code Ruby, c’est assez déroutant au début et cela n’en reste pas moins du code à maintenir, même s’il y a très peu de chance que la syntaxe change.</li> </ul> <h3>Et RBS dans tout ça&nbsp;?</h3> <p><a href="https://www.synbioz.com/blog/tech/ruby-3-les-nouveautes#typage">La V3 de Ruby a vu naître un tout nouveau système de typage nommé RBS</a>, bien que celui-ci soit natif il n’est encore qu’à ses débuts et est assez limité&nbsp;: il ne propose pas de solution pour vérifier les annotations RBS, ni d’annotations de type au même endroit que la définition de méthode. L’équipe de Sorbet est néanmoins impliquée dans le groupe de travail Ruby 3 pour le typage statique, on peut donc espérer une compatibilité entre les deux systèmes à l’avenir.</p> <p>L’équipe Synbioz.<br>Libres d’être ensemble.</p> <img src="https://track.hubspot.com/__ptq.gif?a=5437879&amp;k=14&amp;r=https%3A%2F%2Fwww.synbioz.com%2Fblog%2Ftech%2Fle-typage-avec-sorbet&amp;bu=https%253A%252F%252Fwww.synbioz.com%252Fblog%252Ftech&amp;bvt=rss" alt="" width="1" height="1" style="min-height:1px!important;width:1px!important;border-width:0!important;margin-top:0!important;margin-bottom:0!important;margin-right:0!important;margin-left:0!important;padding-top:0!important;padding-bottom:0!important;padding-right:0!important;padding-left:0!important; "> back ruby Fri, 23 Sep 2022 06:49:59 GMT https://www.synbioz.com/blog/tech/le-typage-avec-sorbet 2022-09-23T06:49:59Z Émilie Podczaszy Tests Rails - Comment s'affranchir du passé ? https://www.synbioz.com/blog/tech/tests-rails-comment-saffranchir-du-passe <div class="hs-featured-image-wrapper"> <a href="https://www.synbioz.com/blog/tech/tests-rails-comment-saffranchir-du-passe" title="" class="hs-featured-image-link"> <img src="https://www.synbioz.com/hubfs/jason-strull-KQ0C6WtEGlo-unsplash.jpg" alt="homme de dos assis à son bureau face à son ordinateur, les bras croisés derrière la tête" class="hs-featured-image" style="width:auto !important; max-width:50%; float:left; margin:0 15px 15px 0;"> </a> </div> <p>Récemment j’ai travaillé sur un nouveau projet qui a déjà toute une batterie de tests présente et je me suis heurtée à une problématique&nbsp;:</p> <p>Récemment j’ai travaillé sur un nouveau projet qui a déjà toute une batterie de tests présente et je me suis heurtée à une problématique&nbsp;:</p> <p>Comment ajouter de nouveaux tests, avec de nouvelles <em>fixtures</em>, sans risquer de casser les tests existants&nbsp;?</p> <p>Typiquement lorsque j’ai voulu ajouter des <em>fixtures</em> pour mes nouveaux tests, les anciens ne passaient plus, car ils n’ont pas été assez bien pensés initialement. Au lieu d’investir davantage de temps à réécrire ces tests existants j’aimerais rendre indépendant mes nouveaux tests des anciennes données.</p> <p>Pour m’affranchir de cette contrainte et pouvoir ajouter de nouveaux tests en toute sérénité, j’ai eu envie d’utiliser un genre de contexte. D’écrire mes tests et <em>fixtures</em> dans un nouveau dossier bien séparé de l’existant. L’idée semble simple en apparence, mais en vérité, j’ai passé plusieurs jours à me casser la tête dessus. Les <em>fixtures</em> au sein d’une application Rails sont faites pour fonctionner facilement sans configuration mais dès qu’on essaie d’être originaux on est vite bloqués.</p> <p>Voici donc, humblement, la solution que j’ai adoptée. Il existe probablement d’autres façons de faire, mieux ou pire. Moi j’ai trouvé ça&nbsp;!</p> <p>J’ai développé de nouvelles fonctionnalités autour d’un projet client que l’on nommera ici <em>Address Book</em>. J’ai aussi en tête que j’aurai sans doute besoin d’ajouter de nouveaux contextes de tests à l’avenir. J’ai donc créé le dossier <code>test/contexts/address_book/</code>, racine de mon contexte&nbsp;:</p> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c1"># test/contexts/address_book/address_book_test_helper.rb</span> <span class="nb">require_relative</span> <span class="s1">'../../../config/environment'</span> <span class="nb">require</span> <span class="s1">'rails/test_help'</span> <span class="k">class</span> <span class="nc">ActiveSupport::AddressBookIntegrationTest</span> <span class="o">&lt;</span> <span class="no">ActionDispatch</span><span class="o">::</span><span class="no">IntegrationTest</span> <span class="nb">self</span><span class="p">.</span><span class="nf">fixture_path</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="no">Rails</span><span class="p">.</span><span class="nf">root</span><span class="si">}</span><span class="s2">/test/contexts/address_book/fixtures/"</span> <span class="nb">self</span><span class="p">.</span><span class="nf">use_transactional_tests</span> <span class="o">=</span> <span class="kp">false</span> <span class="n">fixtures</span> <span class="ss">:all</span> <span class="k">end</span> </code></pre> </div> </div> <p>Grace à <code>fixture_path</code>, les <em>fixtures</em> se trouvant dans le dossier <code>fixtures/</code> du dossier de mon contexte seront utilisées.</p> <p>J’ai également dû désactiver <code>use_transactional_tests</code> car la commande utilisée pour lancer les tests est <code>$ rake db:drop; rake db:create &amp;&amp; rake db:structure:load &amp;&amp; rake db:fixtures:load &amp;&amp; rake test ALL=yes BACKTRACE=yes</code>. Les <em>fixtures</em> sont donc chargées avant les tests et chaque test va déclencher un <em>rollback</em> de la base de données. Or mes tests utilisent un contexte différent donc si je ne désactive pas cela mes nouvelles <em>fixtures</em> seront <em>rollback</em>.</p> <p>Je peux maintenant utiliser ce <code>AddressBookIntegrationTest</code> comme base pour mes tests d’intégration&nbsp;:</p> <div class="language-yaml highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c1"># test/contexts/address_book/fixtures/contacts.yml</span> <span class="na">jeremy</span><span class="pi">:</span> <span class="na">address_1</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Rue</span><span class="nv"> </span><span class="s">de</span><span class="nv"> </span><span class="s">la</span><span class="nv"> </span><span class="s">ré"</span> <span class="na">address_2</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Au</span><span class="nv"> </span><span class="s">rez</span><span class="nv"> </span><span class="s">de</span><span class="nv"> </span><span class="s">chaussé"</span> <span class="na">zip_code</span><span class="pi">:</span> <span class="s2">"</span><span class="s">69001"</span> <span class="na">city</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Lyon"</span> <span class="na">country</span><span class="pi">:</span> <span class="s2">"</span><span class="s">France"</span> <span class="na">phone</span><span class="pi">:</span> <span class="s2">"</span><span class="s">+33423434565"</span><span class="err">,</span> <span class="na">mobile_phone</span><span class="pi">:</span> <span class="s2">"</span><span class="s">+33623434565"</span><span class="err">,</span> <span class="na">email</span><span class="pi">:</span> <span class="s2">"</span><span class="s">jeremy@yopmail.com"</span> </code></pre> </div> </div> <div class="language-ruby highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c1"># test/contexts/address_book/contact_test_helper.rb</span> <span class="nb">require_relative</span> <span class="s2">"address_book_test_helper"</span> <span class="k">class</span> <span class="nc">ContactTest</span> <span class="o">&lt;</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">AddressBookIntegrationTest</span> <span class="nb">test</span> <span class="s2">"using a basic contact"</span> <span class="k">do</span> <span class="n">get</span><span class="p">(</span> <span class="s2">"/api/v2/contacts/</span><span class="si">#{</span><span class="n">contacts</span><span class="p">(</span><span class="ss">:jeremy</span><span class="p">).</span><span class="nf">email</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="p">)</span> <span class="n">expected</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"id"</span> <span class="o">=&gt;</span> <span class="n">contacts</span><span class="p">(</span><span class="ss">:jeremy</span><span class="p">).</span><span class="nf">id</span><span class="p">,</span> <span class="s2">"address_1"</span> <span class="o">=&gt;</span> <span class="s2">"Rue de la ré"</span><span class="p">,</span> <span class="s2">"address_2"</span> <span class="o">=&gt;</span> <span class="s2">"Au rez de chaussé"</span><span class="p">,</span> <span class="s2">"zip_code"</span> <span class="o">=&gt;</span> <span class="s2">"69001"</span><span class="p">,</span> <span class="s2">"city"</span> <span class="o">=&gt;</span> <span class="s2">"Lyon"</span><span class="p">,</span> <span class="s2">"country"</span> <span class="o">=&gt;</span> <span class="s2">"France"</span><span class="p">,</span> <span class="s2">"phone"</span> <span class="o">=&gt;</span> <span class="s2">"+33423434565"</span><span class="p">,</span> <span class="s2">"mobile_phone"</span> <span class="o">=&gt;</span> <span class="s2">"+33623434565"</span><span class="p">,</span> <span class="s2">"email"</span> <span class="o">=&gt;</span> <span class="s2">"jeremy@yopmail.com"</span> <span class="p">}</span> <span class="n">assert_response</span> <span class="ss">:success</span> <span class="n">assert_equal</span> <span class="n">expected</span><span class="p">,</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="vi">@response</span><span class="p">.</span><span class="nf">body</span><span class="p">)</span> <span class="k">end</span> <span class="nb">test</span> <span class="s2">"using an unknown email"</span> <span class="k">do</span> <span class="n">get</span><span class="p">(</span> <span class="s2">"/api/v2/contacts/toot@tiit.com"</span><span class="p">,</span> <span class="p">)</span> <span class="n">assert_response</span> <span class="mi">404</span> <span class="k">end</span> <span class="k">end</span> </code></pre> </div> </div> <p>Voilà voilà. Des bisous à la moi du futur&nbsp;!</p> <p>L’équipe Synbioz.<br>Libres d’être ensemble.</p> <img src="https://track.hubspot.com/__ptq.gif?a=5437879&amp;k=14&amp;r=https%3A%2F%2Fwww.synbioz.com%2Fblog%2Ftech%2Ftests-rails-comment-saffranchir-du-passe&amp;bu=https%253A%252F%252Fwww.synbioz.com%252Fblog%252Ftech&amp;bvt=rss" alt="" width="1" height="1" style="min-height:1px!important;width:1px!important;border-width:0!important;margin-top:0!important;margin-bottom:0!important;margin-right:0!important;margin-left:0!important;padding-top:0!important;padding-bottom:0!important;padding-right:0!important;padding-left:0!important; "> rails Mon, 29 Aug 2022 07:15:53 GMT wbarraco@synbioz.com (Willow Barraco) https://www.synbioz.com/blog/tech/tests-rails-comment-saffranchir-du-passe 2022-08-29T07:15:53Z Écrire un Core Libretro https://www.synbioz.com/blog/tech/ecrire-un-core-libretro <div class="hs-featured-image-wrapper"> <a href="https://www.synbioz.com/blog/tech/ecrire-un-core-libretro" title="" class="hs-featured-image-link"> <img src="https://www.synbioz.com/hubfs/julian-scagliola-lgzsfFHCEIE-unsplash.jpg" alt="borne d'arcade rétrogaming pour deux joueurs en bois vue de profil" class="hs-featured-image" style="width:auto !important; max-width:50%; float:left; margin:0 15px 15px 0;"> </a> </div> <p>Aujourd’hui j’aimerais vous faire découvrir ce qu’implique l’écriture d’un Core Libretro.</p> <p>Aujourd’hui j’aimerais vous faire découvrir ce qu’implique l’écriture d’un Core Libretro.</p> <p>Je vous conseille de lire d’abord mon <a href="https://www.synbioz.com/blog/tech/libretro">article précédent</a> sur la Libretro. Pour ceux qui l’auraient loupé, je vais faire un petit récapitulatif.</p> <p>La Libretro est une interface distribuée sous la forme d’un fichier <em>header</em> en langage C. Il y a deux façons de l’implémenter. D’un côté, le Front qui implémente de manière indépendante toute la partie graphique et la récupération des entrées utilisateur (clavier/souris, manette) pour les plateformes qu’il souhaite supporter. Charge à lui ensuite d’appeler les différentes fonctions définies par la Libretro pour faire fonctionner les différents Cores disponibles.</p> <p>Le Core quant à lui implémente directement les fonctions définies par la Libretro que le Front pourra appeler au moment où il en a besoin. La majorité des Cores sont aujourd’hui des émulateurs de vieux matériels, mais il est tout à faire possible d’écrire directement un jeu vidéo. Les avantages pour le développeur du Core c’est de pouvoir faire tourner son jeu sur la multitude de plateformes supportées par les différents Fronts existants sans se soucier de l’afficher de la gestion des <em>inputs</em> (ce qui peut être un vrai casse-tête si l’on souhaite supporter plusieurs plateformes très différentes).</p> <p>De mon côté j’ai décidé de me lancer dans l’aventure en écrivant un Core minimal pour en apprendre plus sur le fonctionnement de la Libretro et pourquoi pas aller plus loin plus tard. À la fin de l’article, nous aurons donc un core tout simple, capable de dessiner des rectangles de couleur où l’on veut dans la fenêtre et de récupérer des entrées utilisateur.</p> <h2>Les technologies</h2> <p>Bien sûr on utilisera le <em>header</em> de la Libretro dont la dernière révision se trouve sur le <a href="https://github.com/libretro/RetroArch/blob/master/libretro-common/include/libretro.h">GitHub de Retroarch</a>. Ensuite, pour tester notre Core il nous faudra bien sûr un Front pour le lancer. Pour ma part j’ai décidé d’utiliser le Front de référence <a href="https://www.retroarch.com/">Retroarch</a>, j’ai fait ce choix, car il est stable et supporte une multitude de plateformes.</p> <p>Ensuite, il nous faut choisir un langage. La Libretro étant un <em>header</em> C, naturellement on se tourne vers le C ou bien le C++. Pour ma part j’ai décidé de suivre une route différente. En effet, même si j’aime bien le C, j’ai beaucoup de mal à me faire au manque d’outillage moderne (je peux me tromper ne pratiquant pas régulièrement ni professionnellement) notamment au niveau des <em>build systems</em>. De plus j’apprécie d’avoir sous la main certains avantages que peut offrir un langage plus moderne comme une bibliothèque standard plus fournie.</p> <p>Mais bon, c’est bien de rêver, il faut quand même un langage qui s’interface bien avec le C, sinon on court à la catastrophe ou au maintien très complexe d’un <em>binding</em> entre le C et le langage choisi. Je ne me lancerai pas ici dans une comparaison des différents concurrents possibles, je ne maitrise pas assez le sujet et ça pourrait être un article à part entière. J’ai donc limité mon choix au <a href="https://dlang.org/">D</a> et à <a href="https://ziglang.org/">Zig</a>, les deux sont très compatibles avec le C et ont une documentation à disposition sur le sujet. J’ai commencé par D pour lequel la documentation est plus fournie, mais pour différentes raisons, j’ai finalement basculé sur le Zig malgré le manque (à mon gout) de documentation orientée utilisateur et pas technique (c’est un sujet connu dans la communauté qui souhaite prioriser la première version stable).</p> <p>Bon, fini le bla-bla, passons au code&nbsp;!</p> <h2>Un Core</h2> <p>Je ne rentrerai pas en détail dans le fonctionnement de Zig parce que c’est mon premier programme écrit dans ce langage, je ne le maitrise donc pas. Par contre, quand il y aura des petits détails intéressants ou différents de ce que l’on voit d’habitude, j’expliquerai au mieux ce que j’en ai compris. Pour que vous ne soyez pas perdu, voici l’arborescence du projet&nbsp;:</p> <pre><code>. ├── build.zig ├── readme.md ├── src │&nbsp;&nbsp; ├── libretro │&nbsp;&nbsp; │&nbsp;&nbsp; └── libretro.h │&nbsp;&nbsp; └── main.zig └── zig-out │ └── lib │ ├── libzigretro-core.0.0.1.dylib │ ├── libzigretro-core.0.dylib -&gt; libzigretro-core.0.0.1.dylib │ └── libzigretro-core.dylib -&gt; libzigretro-core.0.dylib └──zig-cache </code></pre> <p>Le fichier <code>build.zig</code> sera l’équivalent d’un <code>Makefile</code>, dans <code>zig-out</code> nous retrouverons le résultat de nos <em>builds</em> (l’extension peut varier selon votre système d’exploitation). Le dossier <code>zig-cache</code> est un peu à part, il contient des <em>artifacts</em> de <em>build</em>, nous ne nous y intéresserons pas, il contient des informations uniquement utiles au compilateur.</p> <h3>Le build</h3> <p>C’est bien beau d’écrire un Core, mais il faut déjà être capable de le compiler comme il faut pour qu’il soit utilisable par un Front. Voilà notre fichier de définition de <em>build</em>&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// build.zig</span> <span class="k">const</span> <span class="n">std</span> <span class="o">=</span> <span class="nb">@import</span><span class="p">(</span><span class="s">"std"</span><span class="p">);</span> <span class="k">pub</span> <span class="k">fn</span> <span class="n">build</span><span class="p">(</span><span class="n">b</span><span class="p">:</span> <span class="o">*</span><span class="n">std</span><span class="p">.</span><span class="py">build</span><span class="p">.</span><span class="py">Builder</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="c">// Les options de version standard permettent à la personne qui exécute</span> <span class="c">// `zig build` de sélectionner entre Debug, ReleaseSafe, ReleaseFast et</span> <span class="c">// ReleaseSmall.</span> <span class="k">const</span> <span class="n">mode</span> <span class="o">=</span> <span class="n">b</span><span class="p">.</span><span class="nf">standardReleaseOptions</span><span class="p">();</span> <span class="c">// On veut obtenir une bibliothèque partagée (en gros, utilisable</span> <span class="c">// au _runtime_ et pas au moment de la compilation).</span> <span class="k">const</span> <span class="n">lib</span> <span class="o">=</span> <span class="n">b</span><span class="p">.</span><span class="nf">addSharedLibrary</span><span class="p">(</span><span class="s">"zigretro-core"</span><span class="p">,</span> <span class="s">"src/main.zig"</span><span class="p">,</span> <span class="n">b</span><span class="p">.</span><span class="nf">version</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">));</span> <span class="n">lib</span><span class="p">.</span><span class="nf">setBuildMode</span><span class="p">(</span><span class="n">mode</span><span class="p">);</span> <span class="n">lib</span><span class="p">.</span><span class="nf">addIncludeDir</span><span class="p">(</span><span class="s">"src/libretro"</span><span class="p">);</span> <span class="c">// On link la bibliothèque standard C car nous en aurons besoin.</span> <span class="n">lib</span><span class="p">.</span><span class="nf">linkLibC</span><span class="p">();</span> <span class="n">lib</span><span class="p">.</span><span class="nf">install</span><span class="p">();</span> <span class="p">}</span> </code></pre> </div> </div> <p>Pour plus d’informations sur le sujet, vous pouvez lire <a href="https://zig.news/xq/zig-build-explained-part-1-59lf">cette suite d’articles dédiée</a> (en anglais).</p> <h3>Notre Core</h3> <p>Vous pouvez retrouver le code source complet de cette première partie <a href="https://github.com/hfabre/zigretro-core/tree/minimal-core">ici</a>.</p> <p>En premier lieu, il va falloir faire le point sur le fonctionnement de la Libretro. D’abord, nous devrons implémenter un certain nombre de fonctions dont les symboles doivent obligatoirement se retrouver dans notre bibliothèque une fois compilée, car le Front va les appeler et crasher si elles ne sont pas présentes. La documentation sur le sujet étant assez éparse, je me suis fixé comme premier objectif de traduire le projet <a href="https://github.com/libretro/skeletor">skeletor</a> qui est une implémentation minimale de la Libretro en C.</p> <p>En premier lieu tout en haut de notre fichier <code>main.zig</code>, je vous invite à définir les imports donc nous aurons besoin.</p> <pre><code>// main.zig const std = @import("std"); const print = @import("std").debug.print; const lr = @cImport(@cInclude("libretro.h")); </code></pre> <p>Très simple jusque-là, Zig nous offrant la possibilité d’importer directement et d’utiliser un <em>header</em> C sans action de notre part.</p> <p>Avant de passer à la suite, deux petits points importants pour la compréhension de la suite&nbsp;:</p> <p>Même si on ne gère pas directement l’affichage, on doit remplir un tableau de pixels (<em>framebuffer</em>) que l’on renverra au front. Il y a différents formats disponibles pour gérer ces pixels, j’ai choisi d’utiliser le <code>RETRO_PIXEL_FORMAT_XRGB8888</code> en gros un pixel sera composé de 4 bits, un pour la transparence (non utilisé par la Libretro mais défini dans le format), un pour le rouge, un pour vert et un pour le bleu (<code>ARGB</code>).</p> <p>Deuxième point, la Libretro fonctionne principalement avec un système de <em>callbacks</em>, en C ce sont des pointeurs sur fonctions. Charge à nous d’appeler les différents <em>callbacks</em> dont nous aurons besoin au bon moment.</p> <p>Petite aide au besoin, vous pourrez retrouver <a href="https://ziglang.org/documentation/master/#toc-Primitive-Types">ici un tableau</a> explicitant les différences de nommage entre les types en C et en Zig</p> <p>Ensuite, nous aurons besoin de quelques valeurs (variables ou constantes) dont on se servira plus tard&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// main.zig</span> <span class="c">// ...</span> <span class="c">// Ici on définit la taille de notre fenêtre</span> <span class="k">const</span> <span class="n">video_width</span> <span class="o">=</span> <span class="mi">200</span><span class="p">;</span> <span class="k">const</span> <span class="n">video_height</span> <span class="o">=</span> <span class="mi">150</span><span class="p">;</span> <span class="k">const</span> <span class="n">video_pixels</span> <span class="o">=</span> <span class="n">video_height</span> <span class="o">*</span> <span class="n">video_width</span><span class="p">;</span> <span class="c">// Bits par pixel</span> <span class="k">const</span> <span class="n">bpp</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span> <span class="c">// Notre allocateur.</span> <span class="c">// En effet en Zig, toutes les fonctions qui allouent de la mémoire le font</span> <span class="c">// explicitement. Plusieurs avantages à ça, on peut en changer facilement</span> <span class="c">// et on sait lorsque de la mémoire est allouée et doit être libérée.</span> <span class="c">// Pour savoir comment choisir l'allocateur qui convient le mieux, il</span> <span class="c">// existe une documentation dédiée&nbsp;:</span> <span class="c">// https://ziglang.org/documentation/0.9.1/#Choosing-an-Allocator</span> <span class="c">//</span> <span class="c">// Ici on choisit l'allocateur standard de la libc.</span> <span class="k">const</span> <span class="n">allocator</span> <span class="o">=</span> <span class="n">std</span><span class="p">.</span><span class="py">heap</span><span class="p">.</span><span class="py">c_allocator</span><span class="p">;</span> <span class="c">// Nous ne les utiliserons pas, mais il faut les définir&nbsp;; on met donc tout à 0.</span> <span class="k">var</span> <span class="n">last_aspect</span><span class="p">:</span> <span class="kt">f32</span> <span class="o">=</span> <span class="mf">0.0</span><span class="p">;</span> <span class="k">var</span> <span class="n">last_sample_rate</span><span class="p">:</span> <span class="kt">f32</span> <span class="o">=</span> <span class="mf">0.0</span><span class="p">;</span> <span class="c">// Et enfin nos callbacks. En zig une variable doit être initialisée.</span> <span class="c">// Ici on ne connait pas leur valeur, c'est le front qui sera chargé de</span> <span class="c">// les définir en appelant les fonctions que nous implémenterons plus tard.</span> <span class="k">var</span> <span class="n">logging</span><span class="p">:</span> <span class="n">lr</span><span class="p">.</span><span class="py">retro_log_callback</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span> <span class="k">var</span> <span class="n">log_cb</span><span class="p">:</span> <span class="n">lr</span><span class="p">.</span><span class="py">retro_log_printf_t</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span> <span class="k">var</span> <span class="n">environ_cb</span><span class="p">:</span> <span class="n">lr</span><span class="p">.</span><span class="py">retro_environment_t</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span> <span class="k">var</span> <span class="n">video_cb</span><span class="p">:</span> <span class="n">lr</span><span class="p">.</span><span class="py">retro_video_refresh_t</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span> <span class="k">var</span> <span class="n">audio_cb</span><span class="p">:</span> <span class="n">lr</span><span class="p">.</span><span class="py">retro_audio_sample_t</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span> <span class="k">var</span> <span class="n">audio_batch_cb</span><span class="p">:</span> <span class="n">lr</span><span class="p">.</span><span class="py">retro_audio_sample_batch_t</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span> <span class="k">var</span> <span class="n">input_poll_cb</span><span class="p">:</span> <span class="n">lr</span><span class="p">.</span><span class="py">retro_input_poll_t</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span> <span class="k">var</span> <span class="n">input_state_cb</span><span class="p">:</span> <span class="n">lr</span><span class="p">.</span><span class="py">retro_input_state_t</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span> <span class="c">// Le framebuffer qu'on passera au callback utilisé par le Front.</span> <span class="c">// Un simple tableau de pixel.</span> <span class="k">var</span> <span class="n">frame_buffer</span><span class="p">:</span> <span class="p">[]</span><span class="kt">u8</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span> <span class="c">// Pour nous simplifier la vie nous travaillerons avec un tableau à deux</span> <span class="c">// dimensions pour pouvoir utiliser un système de coordonnées classique (x-y).</span> <span class="k">var</span> <span class="n">screen</span><span class="p">:</span> <span class="p">[</span><span class="n">video_height</span><span class="p">][</span><span class="n">video_width</span><span class="p">]</span><span class="n">Color</span> <span class="o">=</span> <span class="k">undefined</span><span class="p">;</span> </code></pre> </div> </div> <p>Nous aurons besoin ensuite de quelques utilitaires. D’abord nous allons définir une structure pour nos couleurs et en prédéfinir deux&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// main.zig</span> <span class="c">// ...</span> <span class="k">const</span> <span class="n">Color</span> <span class="o">=</span> <span class="k">struct</span> <span class="p">{</span> <span class="n">a</span><span class="p">:</span> <span class="kt">u8</span><span class="p">,</span> <span class="n">r</span><span class="p">:</span> <span class="kt">u8</span><span class="p">,</span> <span class="n">g</span><span class="p">:</span> <span class="kt">u8</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="kt">u8</span> <span class="p">};</span> <span class="k">const</span> <span class="n">black</span> <span class="o">=</span> <span class="n">Color</span> <span class="p">{</span> <span class="p">.</span><span class="py">a</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="p">.</span><span class="py">r</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="p">.</span><span class="py">g</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="p">.</span><span class="py">b</span> <span class="o">=</span> <span class="mi">0</span> <span class="p">};</span> <span class="k">const</span> <span class="n">white</span> <span class="o">=</span> <span class="n">Color</span> <span class="p">{</span> <span class="p">.</span><span class="py">a</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="p">.</span><span class="py">r</span> <span class="o">=</span> <span class="mi">255</span><span class="p">,</span> <span class="p">.</span><span class="py">g</span> <span class="o">=</span> <span class="mi">255</span><span class="p">,</span> <span class="p">.</span><span class="py">b</span> <span class="o">=</span> <span class="mi">255</span> <span class="p">};</span> </code></pre> </div> </div> <p>Rien de bien particulier ici si ce n’est la syntaxe de Zig un peu différente de ce dont on a l’habitude.</p> <p>Puis deux fonctions, une pour dessiner sur notre écran un rectangle à une position donnée de la couleur souhaitée, et une pour effectuer le rendu de notre écran en deux dimensions dans notre <em>framebuffer</em> :</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// main.zig</span> <span class="c">// ...</span> <span class="k">fn</span> <span class="n">draw_rectangle</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="kt">i32</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="kt">i32</span><span class="p">,</span> <span class="n">w</span><span class="p">:</span> <span class="kt">i32</span><span class="p">,</span> <span class="n">h</span><span class="p">:</span> <span class="kt">i32</span><span class="p">,</span> <span class="n">color</span><span class="p">:</span> <span class="n">Color</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="c">// En Zig il y a plusieurs moyens de caster un nombre entier selon</span> <span class="c">// le besoin (vérification de dépassement par exemple).</span> <span class="c">// Vous pouvez retrouver plus d'information sur cet article&nbsp;:</span> <span class="c">// https://www.lagerdata.com/articles/an-intro-to-zigs-integer-casting-for-c-programmers</span> <span class="k">var</span> <span class="n">i</span><span class="p">:</span> <span class="kt">usize</span> <span class="o">=</span> <span class="nb">@intCast</span><span class="p">(</span><span class="kt">usize</span><span class="p">,</span> <span class="n">y</span><span class="p">);</span> <span class="k">while</span> <span class="p">(</span><span class="n">i</span> <span class="o">&lt;</span> <span class="n">y</span> <span class="o">+</span> <span class="n">h</span><span class="p">)</span> <span class="p">{</span> <span class="k">var</span> <span class="n">j</span><span class="p">:</span> <span class="kt">usize</span> <span class="o">=</span> <span class="nb">@intCast</span><span class="p">(</span><span class="kt">usize</span><span class="p">,</span> <span class="n">y</span><span class="p">);</span> <span class="k">while</span> <span class="p">(</span><span class="n">j</span> <span class="o">&lt;</span> <span class="n">x</span> <span class="o">+</span> <span class="n">w</span><span class="p">)</span> <span class="p">{</span> <span class="n">screen</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">color</span><span class="p">;</span> <span class="n">j</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span> <span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="k">fn</span> <span class="n">screen_to_frame_buffer</span><span class="p">()</span> <span class="k">void</span> <span class="p">{</span> <span class="k">var</span> <span class="n">y</span><span class="p">:</span> <span class="kt">usize</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">while</span> <span class="p">(</span><span class="n">y</span> <span class="o">&lt;</span> <span class="n">video_height</span><span class="p">)</span> <span class="p">{</span> <span class="k">var</span> <span class="n">x</span><span class="p">:</span> <span class="kt">usize</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">while</span> <span class="p">(</span><span class="n">x</span> <span class="o">&lt;</span> <span class="n">video_width</span><span class="p">)</span> <span class="p">{</span> <span class="k">var</span> <span class="n">pixel</span> <span class="o">=</span> <span class="n">screen</span><span class="p">[</span><span class="n">y</span><span class="p">][</span><span class="n">x</span><span class="p">];</span> <span class="k">var</span> <span class="n">pixel_index</span> <span class="o">=</span> <span class="n">y</span> <span class="o">*</span> <span class="n">video_width</span> <span class="o">+</span> <span class="n">x</span><span class="p">;</span> <span class="k">var</span> <span class="n">base_index</span> <span class="o">=</span> <span class="n">pixel_index</span> <span class="o">*</span> <span class="n">bpp</span><span class="p">;</span> <span class="n">frame_buffer</span><span class="p">[</span><span class="n">base_index</span><span class="p">]</span> <span class="o">=</span> <span class="n">pixel</span><span class="p">.</span><span class="py">r</span><span class="p">;</span> <span class="n">frame_buffer</span><span class="p">[</span><span class="n">base_index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">pixel</span><span class="p">.</span><span class="py">g</span><span class="p">;</span> <span class="n">frame_buffer</span><span class="p">[</span><span class="n">base_index</span> <span class="o">+</span> <span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="n">pixel</span><span class="p">.</span><span class="py">b</span><span class="p">;</span> <span class="n">frame_buffer</span><span class="p">[</span><span class="n">base_index</span> <span class="o">+</span> <span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="n">pixel</span><span class="p">.</span><span class="py">a</span><span class="p">;</span> <span class="n">x</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span> <span class="n">y</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> </div> <p>C’est un code assez classique qui parlera à tout le monde sauf le point expliqué en commentaire.</p> <p>C’est bon, nous pouvons commencer à implémenter l’interface de la Libretro. On commence par le principal, la fonction qui exécutera notre logique&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// main.zig</span> <span class="c">// ...</span> <span class="c">// L'utilisation d'export permet d'exposer la fonction</span> <span class="c">// dans notre bibliothèque une fois compilée.</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_run</span><span class="p">()</span> <span class="k">void</span> <span class="p">{</span> <span class="n">draw_rectangle</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">video_width</span><span class="p">,</span> <span class="n">video_height</span><span class="p">,</span> <span class="n">black</span><span class="p">);</span> <span class="n">draw_rectangle</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="n">white</span><span class="p">);</span> <span class="n">screen_to_frame_buffer</span><span class="p">();</span> <span class="c">// Alors, là c'est de la magie noire, j'explique ça hors commentaire.</span> <span class="n">video_cb</span><span class="o">.?</span><span class="p">(</span><span class="nb">@ptrCast</span><span class="p">(</span><span class="o">*</span><span class="n">anyopaque</span><span class="p">,</span> <span class="n">frame_buffer</span><span class="p">.</span><span class="py">ptr</span><span class="p">),</span> <span class="n">video_width</span><span class="p">,</span> <span class="n">video_height</span><span class="p">,</span> <span class="n">bpp</span> <span class="o">*</span> <span class="n">video_width</span> <span class="o">*</span> <span class="nb">@sizeOf</span><span class="p">(</span><span class="kt">u8</span><span class="p">));</span> <span class="p">}</span> </code></pre> </div> </div> <p>Première chose, l’appel à la fonction qui est fait de manière un peu particulière <code>video_cb.?()</code>, en gros on signale une fonction <em>unsafe</em> qui pourrait renvoyer une erreur. Ensuite, le premier paramètre <code>@ptrCast(*anyopaque, frame_buffer.ptr)</code>. Ça parait complexe mais c’est assez simple. Le <em>callback</em> que l’on appelle prend en premier paramètre un type <code>void*</code>, en gros en C c’est un type un peu fourre-tout qui signale un pointeur vers n’importe quel type. Son équivalent en Zig (à priori jamais utilisé sauf pour la compatibilité avec le C) c’est <code>*anyopaque</code>, et enfin en C on peut faire ce qu’on veut et <em>caster</em> comme on souhaite un pointeur, en Zig il faut passer par une fonction <code>builtin</code>&nbsp;: <code>@ptrCast</code>.</p> <p>C’est tout pour les grosses complexités ici, un petit point sur le dernier paramètre, c’est la Libretro qui nous l’impose, il s’appelle le <code>pitch</code>. Vous pouvez retrouver dans la <a href="https://github.com/libretro/RetroArch/blob/master/libretro-common/include/libretro.h#L3758">Libretro</a> l’explication exacte, en gros, c’est la variable qui permettra de passer facilement de notre <em>framebuffer</em> à plat à un écran en deux dimensions.</p> <p>Ensuite, nous allons devoir initialiser plusieurs choses. Certaines pour le bon fonctionnement du Front avec notre Core et d’autres pour nous, par exemple, il va falloir allouer notre <em>framebuffer</em>. Ça va prendre un peu de place, mais pas grand-chose de compliqué ici.</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// main.zig</span> <span class="c">// ...</span> <span class="c">// On alloue donc notre framebuffer via notre allocateur.</span> <span class="c">// Vous noterez la gestion d'erreur un peu particulière de Zig,</span> <span class="c">// Pour plus d'information&nbsp;: https://ziglearn.org/chapter-1/#errors.</span> <span class="c">// De plus lorsque qu'une fonction peu renvoyer une erreur,</span> <span class="c">// Zig nous force à la traiter.</span> <span class="c">// C'est un avantage mais dans le cadre d'un PoC ça peut être frustrant.</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_init</span><span class="p">()</span> <span class="k">void</span> <span class="p">{</span> <span class="n">frame_buffer</span> <span class="o">=</span> <span class="n">allocator</span><span class="p">.</span><span class="nf">alloc</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span> <span class="n">video_pixels</span> <span class="o">*</span> <span class="n">bpp</span><span class="p">)</span> <span class="k">catch</span> <span class="p">{</span> <span class="n">std</span><span class="p">.</span><span class="py">log</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s">"Could not allocate memory"</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span> <span class="n">std</span><span class="p">.</span><span class="py">os</span><span class="p">.</span><span class="nf">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span> <span class="p">};</span> <span class="p">}</span> <span class="c">// On n'oublie pas d'être poli, on libère la mémoire qu'on avait réservé.</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_deinit</span><span class="p">()</span> <span class="k">void</span> <span class="p">{</span> <span class="n">allocator</span><span class="p">.</span><span class="nf">free</span><span class="p">(</span><span class="n">frame_buffer</span><span class="p">);</span> <span class="p">}</span> <span class="c">// Second usage des callbacks, passer des informations au Front.</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_set_environment</span><span class="p">(</span><span class="n">cb</span><span class="p">:</span> <span class="n">lr</span><span class="p">.</span><span class="py">retro_environment_t</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="n">environ_cb</span> <span class="o">=</span> <span class="n">cb</span><span class="p">;</span> <span class="k">var</span> <span class="n">allow_no_game</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> <span class="c">// Si un logger est défini par le front, on l'utilise.</span> <span class="k">if</span> <span class="p">(</span><span class="n">cb</span><span class="o">.?</span><span class="p">(</span><span class="n">lr</span><span class="p">.</span><span class="py">RETRO_ENVIRONMENT_GET_LOG_INTERFACE</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">logging</span><span class="p">))</span> <span class="p">{</span> <span class="n">log_cb</span> <span class="o">=</span> <span class="n">logging</span><span class="p">.</span><span class="py">log</span><span class="p">;</span> <span class="p">}</span> <span class="c">// On prévient le Front que notre Core peut se lancer sans jeu.</span> <span class="c">// La libretro étant à l'origine dédiée aux émulateurs, elle considère</span> <span class="c">// que les cores doivent être démarrés avec un jeu. Nous n'allons</span> <span class="c">// pas utiliser cette fonctionnalité vu que nous développons une</span> <span class="c">// simple application.</span> <span class="k">if</span> <span class="p">(</span><span class="n">cb</span><span class="o">.?</span><span class="p">(</span><span class="n">lr</span><span class="p">.</span><span class="py">RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">allow_no_game</span><span class="p">))</span> <span class="p">{}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">print</span><span class="p">(</span><span class="s">"Unable to allow no game booting</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="c">// Ici on ne charge rien vu qu'on démarre sans jeu,</span> <span class="c">// en revanche on spécifie le format de pixel</span> <span class="c">// qu'on va utiliser dans notre framebuffer.</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_load_game</span><span class="p">(</span><span class="mi">_</span><span class="p">:</span> <span class="p">[</span><span class="o">*</span><span class="n">c</span><span class="p">]</span><span class="n">lr</span><span class="p">.</span><span class="py">retro_game_info</span><span class="p">)</span> <span class="k">bool</span> <span class="p">{</span> <span class="k">var</span> <span class="n">fmt</span> <span class="o">=</span> <span class="n">lr</span><span class="p">.</span><span class="py">RETRO_PIXEL_FORMAT_XRGB8888</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">environ_cb</span><span class="o">.?</span><span class="p">(</span><span class="n">lr</span><span class="p">.</span><span class="py">RETRO_ENVIRONMENT_SET_PIXEL_FORMAT</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">fmt</span><span class="p">))</span> <span class="p">{</span> <span class="n">print</span><span class="p">(</span><span class="s">"XRGB8888 is not supported.</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">.</span><span class="p">{});</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span> <span class="kc">true</span><span class="p">;</span> <span class="p">}</span> <span class="c">// Des informations sur notre Core qui sont mises à disposition du Front.</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_get_system_info</span><span class="p">(</span><span class="n">info</span><span class="p">:</span> <span class="p">[</span><span class="o">*</span><span class="n">c</span><span class="p">]</span><span class="n">lr</span><span class="p">.</span><span class="py">struct_retro_system_info</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="n">info</span><span class="o">.*</span><span class="p">.</span><span class="py">library_name</span> <span class="o">=</span> <span class="s">"zigretro"</span><span class="p">;</span> <span class="n">info</span><span class="o">.*</span><span class="p">.</span><span class="py">library_version</span> <span class="o">=</span> <span class="s">"0.1"</span><span class="p">;</span> <span class="n">info</span><span class="o">.*</span><span class="p">.</span><span class="py">need_fullpath</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> <span class="n">info</span><span class="o">.*</span><span class="p">.</span><span class="py">valid_extensions</span> <span class="o">=</span> <span class="s">""</span><span class="p">;</span> <span class="p">}</span> <span class="c">// Idem</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_get_system_av_info</span><span class="p">(</span><span class="n">info</span><span class="p">:</span> <span class="p">[</span><span class="o">*</span><span class="n">c</span><span class="p">]</span><span class="n">lr</span><span class="p">.</span><span class="py">retro_system_av_info</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="k">const</span> <span class="n">aspect</span> <span class="o">=</span> <span class="mf">0.0</span><span class="p">;</span> <span class="k">const</span> <span class="n">sampling_rate</span> <span class="o">=</span> <span class="mf">30000.0</span><span class="p">;</span> <span class="n">info</span><span class="o">.*</span><span class="p">.</span><span class="py">geometry</span><span class="p">.</span><span class="py">base_width</span> <span class="o">=</span> <span class="n">video_width</span><span class="p">;</span> <span class="n">info</span><span class="o">.*</span><span class="p">.</span><span class="py">geometry</span><span class="p">.</span><span class="py">base_height</span> <span class="o">=</span> <span class="n">video_height</span><span class="p">;</span> <span class="n">info</span><span class="o">.*</span><span class="p">.</span><span class="py">geometry</span><span class="p">.</span><span class="py">max_width</span> <span class="o">=</span> <span class="n">video_width</span><span class="p">;</span> <span class="n">info</span><span class="o">.*</span><span class="p">.</span><span class="py">geometry</span><span class="p">.</span><span class="py">max_height</span> <span class="o">=</span> <span class="n">video_height</span><span class="p">;</span> <span class="n">info</span><span class="o">.*</span><span class="p">.</span><span class="py">geometry</span><span class="p">.</span><span class="py">aspect_ratio</span> <span class="o">=</span> <span class="n">aspect</span><span class="p">;</span> <span class="n">last_aspect</span> <span class="o">=</span> <span class="n">aspect</span><span class="p">;</span> <span class="n">last_sample_rate</span> <span class="o">=</span> <span class="n">sampling_rate</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> </div> <p>Ensuite, nous allons implémenter les différentes fonctions que le Front appellera pour assigner les différents <em>callback</em> auxquels nous aurons accès&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// main.zig</span> <span class="c">// ...</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_set_audio_sample</span><span class="p">(</span><span class="n">cb</span><span class="p">:</span> <span class="n">lr</span><span class="p">.</span><span class="py">retro_audio_sample_t</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="n">audio_cb</span> <span class="o">=</span> <span class="n">cb</span><span class="p">;</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_set_audio_sample_batch</span><span class="p">(</span><span class="n">cb</span><span class="p">:</span> <span class="n">lr</span><span class="p">.</span><span class="py">retro_audio_sample_batch_t</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="n">audio_batch_cb</span> <span class="o">=</span> <span class="n">cb</span><span class="p">;</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_set_input_poll</span><span class="p">(</span><span class="n">cb</span><span class="p">:</span> <span class="n">lr</span><span class="p">.</span><span class="py">retro_input_poll_t</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="n">input_poll_cb</span> <span class="o">=</span> <span class="n">cb</span><span class="p">;</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_set_input_state</span><span class="p">(</span><span class="n">cb</span><span class="p">:</span> <span class="n">lr</span><span class="p">.</span><span class="py">retro_input_state_t</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="n">input_state_cb</span> <span class="o">=</span> <span class="n">cb</span><span class="p">;</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_set_video_refresh</span><span class="p">(</span><span class="n">cb</span><span class="p">:</span> <span class="n">lr</span><span class="p">.</span><span class="py">retro_video_refresh_t</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="n">video_cb</span> <span class="o">=</span> <span class="n">cb</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> </div> <p>Et enfin nous allons finir par implémenter des fonctions obligatoires qui ne nous serviront pas pour notre Core et qui seront donc tout simplement vides&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// main.zig</span> <span class="c">// ...</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_api_version</span><span class="p">()</span> <span class="k">c_uint</span> <span class="p">{</span> <span class="k">return</span> <span class="n">lr</span><span class="p">.</span><span class="py">RETRO_API_VERSION</span><span class="p">;</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_set_controller_port_device</span><span class="p">(</span><span class="mi">_</span><span class="p">:</span> <span class="k">c_uint</span><span class="p">,</span> <span class="mi">_</span><span class="p">:</span> <span class="k">c_uint</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_reset</span><span class="p">()</span> <span class="k">void</span> <span class="p">{</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">audio_callback</span><span class="p">()</span> <span class="k">void</span> <span class="p">{</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">audio_set_state</span><span class="p">(</span><span class="mi">_</span><span class="p">:</span> <span class="k">bool</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_unload_game</span><span class="p">()</span> <span class="k">void</span> <span class="p">{</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_get_region</span><span class="p">()</span> <span class="k">c_uint</span> <span class="p">{</span> <span class="k">return</span> <span class="n">lr</span><span class="p">.</span><span class="py">RETRO_REGION_NTSC</span><span class="p">;</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_load_game_special</span><span class="p">(</span><span class="mi">_</span><span class="p">:</span> <span class="k">c_uint</span><span class="p">,</span> <span class="mi">_</span><span class="p">:</span> <span class="p">[</span><span class="o">*</span><span class="n">c</span><span class="p">]</span><span class="n">lr</span><span class="p">.</span><span class="py">retro_game_info</span><span class="p">,</span> <span class="mi">_</span><span class="p">:</span> <span class="kt">usize</span><span class="p">)</span> <span class="k">bool</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_serialize_size</span><span class="p">()</span> <span class="kt">usize</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_serialize</span><span class="p">(</span><span class="mi">_</span><span class="p">:</span> <span class="o">*</span><span class="n">anyopaque</span><span class="p">,</span> <span class="mi">_</span><span class="p">:</span> <span class="kt">usize</span><span class="p">)</span> <span class="k">bool</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_unserialize</span><span class="p">(</span><span class="mi">_</span><span class="p">:</span> <span class="o">*</span><span class="n">anyopaque</span><span class="p">,</span> <span class="mi">_</span><span class="p">:</span> <span class="kt">usize</span><span class="p">)</span> <span class="k">bool</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_get_memory_data</span><span class="p">(</span><span class="mi">_</span><span class="p">:</span> <span class="k">c_uint</span><span class="p">)</span> <span class="o">?*</span><span class="n">anyopaque</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">null</span><span class="p">;</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_get_memory_size</span><span class="p">(</span><span class="mi">_</span><span class="p">:</span> <span class="k">c_uint</span><span class="p">)</span> <span class="kt">usize</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_cheat_reset</span><span class="p">()</span> <span class="k">void</span> <span class="p">{</span> <span class="p">}</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_cheat_set</span><span class="p">(</span><span class="mi">_</span><span class="p">:</span> <span class="k">c_uint</span><span class="p">,</span> <span class="mi">_</span><span class="p">:</span> <span class="k">bool</span><span class="p">,</span> <span class="mi">_</span><span class="p">:</span> <span class="p">[</span><span class="o">*</span><span class="n">c</span><span class="p">]</span><span class="kt">u8</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="p">}</span> </code></pre> </div> </div> <p>Pas grand-chose à signaler ici, si ce n’est certaines syntaxes de Zig&nbsp;:</p> <ul> <li><code>[*c]u8</code> signaler un pointeur C vers un <code>u8</code></li> <li><code>_</code> Zig nous interdit de définir une variable non utilisée, on lui donne donc le nom de <code>_</code> on retrouve cette convention (non obligatoire) en Ruby</li> <li><code>usize</code>, <code>c_uint</code>, <code>*anyopaque</code> on en a déjà parlé, l’équivalent Zig de <code>size_t</code>, <code>unsigned int</code> et <code>void*</code></li> <li>Le type de retour <code>?*anyopaque</code> qui signale qu’on retourne un <code>void*</code> ou <code>null</code></li> </ul> <p>Voilà pour cette première partie. Pour tester tout ça, rien de plus simple&nbsp;:</p> <div class="language-sh highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code>zig build</code></pre> <pre class="highlight"><code>chemin_vers_retroarch <span class="nt">-v</span> <span class="nt">-L</span> chemin_vers_ce_projet/zig-out/lib/libzigretro-core.0.0.1.dylib </code></pre> </div> </div> <p>Le <code>-v</code> sert à rendre Retroarch plus verbeux pour avoir des informations de <em>debug</em> et le <code>-L</code> à préciser le chemin vers notre Core. Si vous lancez les commandes, vous aurez un beau fond noir avec dessiné par-dessus un petit rectangle blanc à la position donnée.</p> <h3>Les inputs</h3> <p>Vous pouvez retrouver directement le code source de cette partie <a href="https://github.com/hfabre/zigretro-core/tree/input-handling">ici</a></p> <p>Bon c’est sympa ce que nous avons, mais ça serait vraiment bien de dynamiser tout ça&nbsp;! Pour ça nous allons récupérer les <em>inputs</em> grâce à la Libretro et faire bouger notre carré blanc. Il est important de noter que nous allons utiliser une toute petite partie de ce qui est possible de faire grâce à la Libretro (gérer deux joueurs via plusieurs manettes par exemple).</p> <p>Tout le code source présenté est à rajouter dans le fichier <code>main.zig</code></p> <p>Vous allez voir c’est très simple, on va commencer par définir trois nouvelles variables pour la position de notre carré et la vitesse de déplacement&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">var</span> <span class="n">player_x</span><span class="p">:</span> <span class="kt">i32</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">var</span> <span class="n">player_y</span><span class="p">:</span> <span class="kt">i32</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">const</span> <span class="n">speed</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> </code></pre> </div> </div> <p>Ensuite, nous allons définir une structure de données pour faire un <em>mapping</em> entre les définitions de touches dans la Libretro et chez nous&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="c">// Un hash comme on peut en retrouver en Ruby,</span> <span class="c">// avec pour clef un nombre entier non signé et pour valeur un enum maison.</span> <span class="k">var</span> <span class="n">key_map</span> <span class="o">=</span> <span class="n">std</span><span class="p">.</span><span class="nf">AutoHashMap</span><span class="p">(</span><span class="k">c_uint</span><span class="p">,</span> <span class="n">Key</span><span class="p">).</span><span class="nf">init</span><span class="p">(</span><span class="n">allocator</span><span class="p">);</span> <span class="k">const</span> <span class="n">Key</span> <span class="o">=</span> <span class="k">enum</span> <span class="p">{</span> <span class="n">up</span><span class="p">,</span> <span class="n">down</span><span class="p">,</span> <span class="n">left</span><span class="p">,</span> <span class="n">right</span> <span class="p">};</span> </code></pre> </div> </div> <p>Avant toute chose, on va écraser notre fonction d’initialisation pour allouer notre <code>HashMap</code>&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">export</span> <span class="k">fn</span> <span class="n">retro_init</span><span class="p">()</span> <span class="k">void</span> <span class="p">{</span> <span class="n">frame_buffer</span> <span class="o">=</span> <span class="n">allocator</span><span class="p">.</span><span class="nf">alloc</span><span class="p">(</span><span class="kt">u8</span><span class="p">,</span> <span class="n">video_pixels</span> <span class="o">*</span> <span class="n">bpp</span><span class="p">)</span> <span class="k">catch</span> <span class="p">{</span> <span class="n">handle_error</span><span class="p">(</span><span class="s">"Could not allocate memory"</span><span class="p">);</span> <span class="c">// Pour être tout à fait franc, je ne comprend pas</span> <span class="c">// pourquoi j'ai besoin de faire ça, à creuser.</span> <span class="k">return</span><span class="p">;</span> <span class="p">};</span> <span class="n">key_map</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="n">lr</span><span class="p">.</span><span class="py">RETRO_DEVICE_ID_JOYPAD_UP</span><span class="p">,</span> <span class="n">Key</span><span class="p">.</span><span class="py">up</span><span class="p">)</span> <span class="k">catch</span> <span class="p">{</span> <span class="n">handle_error</span><span class="p">(</span><span class="s">"Could not allocate memory"</span><span class="p">);</span> <span class="p">};</span> <span class="n">key_map</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="n">lr</span><span class="p">.</span><span class="py">RETRO_DEVICE_ID_JOYPAD_DOWN</span><span class="p">,</span> <span class="n">Key</span><span class="p">.</span><span class="py">down</span><span class="p">)</span> <span class="k">catch</span> <span class="p">{</span> <span class="n">handle_error</span><span class="p">(</span><span class="s">"Could not allocate memory"</span><span class="p">);</span> <span class="p">};</span> <span class="n">key_map</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="n">lr</span><span class="p">.</span><span class="py">RETRO_DEVICE_ID_JOYPAD_RIGHT</span><span class="p">,</span> <span class="n">Key</span><span class="p">.</span><span class="py">right</span><span class="p">)</span> <span class="k">catch</span> <span class="p">{</span> <span class="n">handle_error</span><span class="p">(</span><span class="s">"Could not allocate memory"</span><span class="p">);</span> <span class="p">};</span> <span class="n">key_map</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="n">lr</span><span class="p">.</span><span class="py">RETRO_DEVICE_ID_JOYPAD_LEFT</span><span class="p">,</span> <span class="n">Key</span><span class="p">.</span><span class="py">left</span><span class="p">)</span> <span class="k">catch</span> <span class="p">{</span> <span class="n">handle_error</span><span class="p">(</span><span class="s">"Could not allocate memory"</span><span class="p">);</span> <span class="p">};</span> <span class="p">}</span> <span class="c">// J'en ai profité pour définir une petite fonction pour gérer les erreurs.</span> <span class="c">// Pas d'export ici vu qu'elle est uniquement utilisée dans notre code Zig.</span> <span class="k">fn</span> <span class="n">handle_error</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="p">[]</span><span class="k">const</span> <span class="kt">u8</span><span class="p">)</span> <span class="k">void</span> <span class="p">{</span> <span class="n">std</span><span class="p">.</span><span class="py">log</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s">"{s}"</span><span class="p">,</span> <span class="o">.</span><span class="p">{</span><span class="n">message</span><span class="p">});</span> <span class="n">std</span><span class="p">.</span><span class="py">os</span><span class="p">.</span><span class="nf">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span> <span class="p">}</span> <span class="c">// Et on n'oublie pas de libérer notre mémoire !</span> <span class="k">export</span> <span class="k">fn</span> <span class="n">retro_deinit</span><span class="p">()</span> <span class="k">void</span> <span class="p">{</span> <span class="n">allocator</span><span class="p">.</span><span class="nf">free</span><span class="p">(</span><span class="n">frame_buffer</span><span class="p">);</span> <span class="n">key_map</span><span class="p">.</span><span class="nf">deinit</span><span class="p">();</span> <span class="p">}</span> </code></pre> </div> </div> <p>Ensuite nous aurons besoin de gérer les <em>inputs</em>, pour ça on rajoute une fonction&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">fn</span> <span class="n">process_inputs</span><span class="p">()</span> <span class="k">void</span> <span class="p">{</span> <span class="k">var</span> <span class="n">it</span> <span class="o">=</span> <span class="n">key_map</span><span class="p">.</span><span class="nf">iterator</span><span class="p">();</span> <span class="k">while</span> <span class="p">(</span><span class="n">it</span><span class="p">.</span><span class="nf">next</span><span class="p">())</span> <span class="p">|</span><span class="n">kv</span><span class="p">|</span> <span class="p">{</span> <span class="c">// On appelle un callback sur lequel le Front nous renvoie 0 si la</span> <span class="c">// touche en question n'est pas appuyée.</span> <span class="k">var</span> <span class="n">pressed</span> <span class="o">=</span> <span class="n">input_state_cb</span><span class="o">.?</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">lr</span><span class="p">.</span><span class="py">RETRO_DEVICE_JOYPAD</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">kv</span><span class="p">.</span><span class="py">key_ptr</span><span class="o">.*</span><span class="p">);</span> <span class="c">// Si la touche demandée est appuyée, la Libretro nous renvoie autre</span> <span class="c">// chose que 0.</span> <span class="k">if</span> <span class="p">(</span><span class="n">pressed</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">switch</span> <span class="p">(</span><span class="n">kv</span><span class="p">.</span><span class="py">value_ptr</span><span class="o">.*</span><span class="p">)</span> <span class="p">{</span> <span class="n">Key</span><span class="p">.</span><span class="py">up</span> <span class="o">=&gt;</span> <span class="n">player_y</span> <span class="o">-=</span> <span class="n">speed</span><span class="p">,</span> <span class="n">Key</span><span class="p">.</span><span class="py">down</span> <span class="o">=&gt;</span> <span class="n">player_y</span> <span class="o">+=</span> <span class="n">speed</span><span class="p">,</span> <span class="n">Key</span><span class="p">.</span><span class="py">right</span> <span class="o">=&gt;</span> <span class="n">player_x</span> <span class="o">+=</span> <span class="n">speed</span><span class="p">,</span> <span class="n">Key</span><span class="p">.</span><span class="py">left</span> <span class="o">=&gt;</span> <span class="n">player_x</span> <span class="o">-=</span> <span class="n">speed</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> </div> <p>Assez classique hormis les syntaxes de Zig encore une fois.</p> <p>Et pour finir, il va falloir apporter quelques changements à notre fonction principale&nbsp;:</p> <div class="language-zig highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="k">export</span> <span class="k">fn</span> <span class="n">retro_run</span><span class="p">()</span> <span class="k">void</span> <span class="p">{</span> <span class="c">// On fait des actions en fonction des entrées utilisateurs</span> <span class="n">process_inputs</span><span class="p">();</span> <span class="n">draw_rectangle</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">video_width</span><span class="p">,</span> <span class="n">video_height</span><span class="p">,</span> <span class="n">black</span><span class="p">);</span> <span class="c">// On utilise maintenant notre position</span> <span class="n">draw_rectangle</span><span class="p">(</span><span class="n">player_x</span><span class="p">,</span> <span class="n">player_y</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="n">white</span><span class="p">);</span> <span class="n">screen_to_frame_buffer</span><span class="p">();</span> <span class="n">video_cb</span><span class="o">.?</span><span class="p">(</span><span class="nb">@ptrCast</span><span class="p">(</span><span class="o">*</span><span class="n">anyopaque</span><span class="p">,</span> <span class="n">frame_buffer</span><span class="p">.</span><span class="py">ptr</span><span class="p">),</span> <span class="n">video_width</span><span class="p">,</span> <span class="n">video_height</span><span class="p">,</span> <span class="n">pitch</span><span class="p">);</span> <span class="p">}</span> </code></pre> </div> </div> <p>Et voilà, c’est tout. On re-compile via <code>zig build</code> et on lance. Vous devriez avoir un carré blanc qui se déplace en fonction de vos appuis sur les flèches de votre clavier. Attention vous aurez remarqué qu’on n’a pas mis de garde-fou, si vous essayez de bouger en dehors de l’écran, vous aurez le droit à un beau crash parce que vous tentez d’accéder à une zone mémoire qui n’est pas à vous.</p> <p>Vous avez maintenant tout ce dont vous avez besoin pour implémenter votre propre Core (bon il y a encore beaucoup de fonctionnalités à explorer, comme le son), à partir de maintenant votre imagination sera votre seule limite&nbsp;!</p> <p>Vous pouvez retrouver une version un peu plus travaillée du code source <a href="https://github.com/hfabre/zigretro-core/tree/extract-engine">ici</a>. Et pour les plus curieux, les équivalents de la <a href="https://github.com/hfabre/dretro-core/tree/minimal-core">première partie</a> et de la <a href="https://github.com/hfabre/dretro-core/tree/input-handling">deuxième partie</a> en D.</p> <p>L’équipe Synbioz.<br>Libres d’être ensemble.</p> <img src="https://track.hubspot.com/__ptq.gif?a=5437879&amp;k=14&amp;r=https%3A%2F%2Fwww.synbioz.com%2Fblog%2Ftech%2Fecrire-un-core-libretro&amp;bu=https%253A%252F%252Fwww.synbioz.com%252Fblog%252Ftech&amp;bvt=rss" alt="" width="1" height="1" style="min-height:1px!important;width:1px!important;border-width:0!important;margin-top:0!important;margin-bottom:0!important;margin-right:0!important;margin-left:0!important;padding-top:0!important;padding-bottom:0!important;padding-right:0!important;padding-left:0!important; "> framework Wed, 20 Jul 2022 17:16:15 GMT hfabre@synbioz.com (Hugo Fabre) https://www.synbioz.com/blog/tech/ecrire-un-core-libretro 2022-07-20T17:16:15Z Un développeur au Forum International de la Cybersécurité https://www.synbioz.com/blog/tech/un-developpeur-au-forum-international-de-la-cybersecurite <div class="hs-featured-image-wrapper"> <a href="https://www.synbioz.com/blog/tech/un-developpeur-au-forum-international-de-la-cybersecurite" title="" class="hs-featured-image-link"> <img src="https://narcisse.synbioz.com/attachments/868ae26f-4102-4769-9544-7e6520072ddb/variants/c42439c1-417c-45da-a94a-9cf396cfcf8c/src" alt="des informaticiens à l'œuvre au cœur d'un forum dédié à la cybersécurité" class="hs-featured-image" style="width:auto !important; max-width:50%; float:left; margin:0 15px 15px 0;"> </a> </div> <p>Du 7 au 9 juin 2022 s’est tenu le <a href="https://www.forum-fic.com/">Forum International de la Cybersécurité (FIC)</a> au Grand Palais de Lille. Se présentant comme le principal événement européen sur les questions de la sécurité et de la confiance numérique, le FIC a pour objectif de rassembler l’ensemble de l’écosystème de la Cybersécurité. Il regroupe de multiples acteurs de ce domaine tels que les éditeurs de solutions en passant par les clients finaux ainsi que les écoles et universités, le FIC cherche à répondre à un double enjeu&nbsp;: faire face aux défis de la Cybersécurité tout en contribuant à la construction d’un futur numérique conforme aux valeurs et aux intérêts européens. Le Forum International de la Cybersécurité est alors avant tout un salon où des prestataires de solutions et de services de cybersécurité font découvrir leurs produits et leurs expertises à des clients finaux mais c’est aussi un lieu d’échange autour des grandes thématiques de la cybersécurité, à travers notamment des conférences ou des <em>Masterclass</em>.</p> <p>Du 7 au 9 juin 2022 s’est tenu le <a href="https://www.forum-fic.com/">Forum International de la Cybersécurité (FIC)</a> au Grand Palais de Lille. Se présentant comme le principal événement européen sur les questions de la sécurité et de la confiance numérique, le FIC a pour objectif de rassembler l’ensemble de l’écosystème de la Cybersécurité. Il regroupe de multiples acteurs de ce domaine tels que les éditeurs de solutions en passant par les clients finaux ainsi que les écoles et universités, le FIC cherche à répondre à un double enjeu&nbsp;: faire face aux défis de la Cybersécurité tout en contribuant à la construction d’un futur numérique conforme aux valeurs et aux intérêts européens. Le Forum International de la Cybersécurité est alors avant tout un salon où des prestataires de solutions et de services de cybersécurité font découvrir leurs produits et leurs expertises à des clients finaux mais c’est aussi un lieu d’échange autour des grandes thématiques de la cybersécurité, à travers notamment des conférences ou des <em>Masterclass</em>.</p> <p>Curieux et intéressé par la cybersécurité, j’ai eu l’opportunité de visiter ce salon le jeudi 9 juin 2022, l’occasion pour moi de vous faire vivre et découvrir cet événement, que vous soyez amateur de cybersécurité ou simplement curieux.</p> <h2>Un Forum pour les entreprises et les laboratoires de recherche</h2> <p><img src="https://narcisse.synbioz.com/attachments/b169b426-a9bc-4270-abdd-20ea75315e5b/variants/0a638c21-a6f9-4e70-bfda-073093707547/src" alt="photographie vue d'ensemble"></p> <p>Le FIC est avant tout pour les entreprises l’occasion de se trouver des nouveaux clients tout en exposant les différentes solutions plus ou moins innovantes qu’elles proposent. Désireuses de montrer et de vendre leurs solutions, les personnes présentes au stand des entreprises, commerciaux ou développeurs, sont souvent très heureuses de répondre à nos questions techniques sur leurs solutions. Certaines entreprises proposent aussi des démonstrations en direct de leurs produits afin de prouver leur efficacité. Malgré tout, les discussions les plus intéressantes sont celle que l’on peut avoir directement avec les développeurs de solutions. Au FIC, les entreprises les plus intéressantes ne sont que très rarement les grandes entreprises que l’on connait déjà (Orange Cyberdéfense, Thalès, etc.) mais plutôt les petites et moyennes entreprises et leurs développeurs avec qui l’échange en direct nous permet de mieux comprendre l’objectif de leurs produits. Le mot d’ordre au FIC est avant tout la curiosité et donc d’aller découvrir de nouvelles entreprises qui pourraient devenir des partenaires intéressantes sur le long terme.</p> <p>Pour les laboratoires de recherche, le FIC est aussi l’occasion de pouvoir montrer ses nouveaux travaux. Que ce soit à travers une présence sur un stand, en animant une masterclass ou une conférence, les laboratoires de recherche démontrent chaque année les progrès ou découvertes qu’ils ont pu réaliser. Le forum peut aussi être l’occasion de rencontrer de nouveaux partenaires dans l’optique d’effectuer une collaboration. Les entreprises privées apprécient de pouvoir contacter les laboratoires de recherches ayant réalisé des nouvelles attaques pour tester leurs capacités de résistance face à ces nouveaux défis. De cette manière, les laboratoires de recherche peuvent tester dans de véritables environnements des attaques parfois théoriques et les entreprises privées peuvent tester les outils qu’elles mettent en vente.</p> <p>Le FIC peut donc être le lieu de naissance de nouvelles collaborations mais aussi un lieu de recherche de nouveaux talents à recruter. Les chercheurs et ingénieurs de la cybersécurité peuvent ainsi se créer des contacts ou même se voir proposer un poste au sein d’une entreprise durant le forum. Plus qu’un lieu d’échange, le forum se pose aussi comme un véritable terrain de recrutement pour un domaine manquant cruellement de main d’œuvre. Les entreprises cherchant à développer leur pôle de cybersécurité peuvent donc aussi y gagner sur le terrain du recrutement en participant au FIC.</p> <h2>Un Forum pour les étudiants</h2> <p><img src="https://narcisse.synbioz.com/attachments/cad02ab3-a974-4d68-99d3-da0ad51b9ac8/variants/e423e702-d94c-4abd-b390-1cf0b632263e/src" alt="photographie du salon 2"></p> <p>Le FIC est aussi un forum intéressant pour les étudiants ou futurs étudiants en informatique. De part la présence de multiples grandes universités spécialisées en Cybersécurité<br>telle que la CyberSchool en Bretagne, les étudiants souhaitant choisir leur poursuite d’études seront bien renseignés.</p> <p>Bien que les universités au schéma classique soient très bien représentées avec la présence des universités de Bretagne, Nouvelle-Aquitaine et celle des Hauts de France, le FIC n’oublie pas non plus les développeurs ou les personnes désireuses d’effectuer une reconversion. Les entreprises proposant des formations courte et moyenne durée avec certaines certifications à la clef sont aussi prêtes à vous accueillir sur leurs stands respectifs.</p> <p>Mais le FIC n’est pas seulement à destination des étudiants en informatique ou en cybersécurité. Les nouveaux modes de communication et de partage de l’information provoquent de grands changements dans la vision de la justice et des lois sur l’encadrement des activités sur Internet. Les étudiants en droit se retrouvent aussi au forum, pour comprendre et étudier les nouveaux textes ou démarches relatives à la protection des données ou aux actions en justice. Les futurs avocats en droit du numérique, de l’informatique et d’Internet peuvent eux aussi en apprendre plus sur la cybersécurité et notamment sur l’accompagnement que les entreprises peuvent attendre d’eux.</p> <h2>Un Forum pour les curieux (et les joueurs)</h2> <p><img src="https://narcisse.synbioz.com/attachments/868ae26f-4102-4769-9544-7e6520072ddb/variants/c42439c1-417c-45da-a94a-9cf396cfcf8c/src" alt="photographie espace hackaton"></p> <p>Durant les trois jours de salon, de multiples conférences et <em>Masterclass</em> ont lieu dans les différents lieux ou amphithéâtres du Grand Palais. Les curieux de cybersécurité ou les désireux d’apprendre pourront ainsi suivre, que ce soit en direct ou via une retransmission simultanée sur l’application, les multiples conférences données. Réalisées par des pointures de la cybersécurité, ces conférences permettent de comprendre les démarches qui peuvent ou doivent être mises en place dans les entreprises en fonction de certains besoins.</p> <p>Certains adeptes des célèbres <em>Capture The Flag (CTF)</em> ou amoureux du <em>Forensic</em> pourront aussi s’en donner à cœur joie. Tout au long du salon, de multiples jeux sont organisés afin de mettre en compétition plusieurs équipes de <em>white hackers</em> ou <em>pentester</em>. Les défis sont multiples et peuvent s’étendre d’un escape game / enquêtes virtuelles jusqu’à la mise en situation de gestion de crise face à une attaque critique sur un système d’information. Le but de ces challenges est avant tout de s’amuser tout en apprenant et en démontrant aux visiteurs non initiés ce que peut-être la cybersécurité.</p> <p>Le FIC est un salon incontournable pour toutes les personnes souhaitant découvrir la cybersécurité. En tant que développeur, la cybersécurité dépend aussi de nous et des services que nous pouvons proposer aux porteurs de projets. Nul besoin d’être un expert dans tous les domaines pour s’y intéresser mais simplement d’être curieux et d’essayer de mettre en place, à notre échelle, des solutions simples et sécurisées.</p> <p>S’inscrivant parfaitement dans le programme européen «Décennie numérique», Le Forum International de la Cybersécurité sera renouvelé en 2023. L’occasion, peut-être, de se retrouver ensemble à cette prochaine édition.</p> <p>L’équipe Synbioz.<br>Libres d’être ensemble.</p> <img src="https://track.hubspot.com/__ptq.gif?a=5437879&amp;k=14&amp;r=https%3A%2F%2Fwww.synbioz.com%2Fblog%2Ftech%2Fun-developpeur-au-forum-international-de-la-cybersecurite&amp;bu=https%253A%252F%252Fwww.synbioz.com%252Fblog%252Ftech&amp;bvt=rss" alt="" width="1" height="1" style="min-height:1px!important;width:1px!important;border-width:0!important;margin-top:0!important;margin-bottom:0!important;margin-right:0!important;margin-left:0!important;padding-top:0!important;padding-bottom:0!important;padding-right:0!important;padding-left:0!important; "> conférences sécurité Fri, 01 Jul 2022 12:55:08 GMT vbout@synbioz.com (Valentin Bout) https://www.synbioz.com/blog/tech/un-developpeur-au-forum-international-de-la-cybersecurite 2022-07-01T12:55:08Z Les side-projects sont le sésame du développeur Junior https://www.synbioz.com/blog/tech/les-side-projects-sont-le-sesame-du-developpeur-junior <div class="hs-featured-image-wrapper"> <a href="https://www.synbioz.com/blog/tech/les-side-projects-sont-le-sesame-du-developpeur-junior" title="" class="hs-featured-image-link"> <img src="https://www.synbioz.com/hubfs/octavian-dan-b21Ty33CqVs-unsplash.jpg" alt="Lettering «&nbsp;projects&nbsp;» embossé et vu de profil sur fond allant du violet à l'orange avec un focus sur les premières lettres" class="hs-featured-image" style="width:auto !important; max-width:50%; float:left; margin:0 15px 15px 0;"> </a> </div> <p>En stage chez Synbioz depuis le 11 avril, il est aujourd’hui venu le temps pour moi de rédiger mon premier article. Ilias a déjà pu revenir, à travers ses articles sur l’alternance chez Synbioz (notamment <a href="https://www.synbioz.com/blog/tech/fin-de-mon-alternance-chez-synbioz">sur ces premiers mois</a> puis <a href="https://www.synbioz.com/blog/tech/retour-sur-mes-trois-mois-d-alternance-chez-synbioz">sur la fin</a> de son alternance), sur les différents processus internes de l’entreprise. Souhaitant à l’origine continuer sur le sujet de l’intégration chez Synbioz, j’ai finalement décidé d’évoquer l’importance à mon sens des <em>side-projects</em>, notamment en tant que développeur Junior, qui peuvent parfois être un levier important durant une recherche de stage, d’alternance ou d’un emploi.</p> <p>En stage chez Synbioz depuis le 11 avril, il est aujourd’hui venu le temps pour moi de rédiger mon premier article. Ilias a déjà pu revenir, à travers ses articles sur l’alternance chez Synbioz (notamment <a href="https://www.synbioz.com/blog/tech/fin-de-mon-alternance-chez-synbioz">sur ces premiers mois</a> puis <a href="https://www.synbioz.com/blog/tech/retour-sur-mes-trois-mois-d-alternance-chez-synbioz">sur la fin</a> de son alternance), sur les différents processus internes de l’entreprise. Souhaitant à l’origine continuer sur le sujet de l’intégration chez Synbioz, j’ai finalement décidé d’évoquer l’importance à mon sens des <em>side-projects</em>, notamment en tant que développeur Junior, qui peuvent parfois être un levier important durant une recherche de stage, d’alternance ou d’un emploi.</p> <p>Les <em>side-projects</em>, parfois traduits par projet parallèle, secondaire ou annexe sont des projets issus à l’origine d’une idée ou d’une envie que l’on souhaite mettre en place en dehors de nos heures de travail. Tout commence alors par une idée ou une envie, que nous allons développer et qui va à son tour nous former et nous pousser à la curiosité. Ces petits projets, rarement finis, bien que très énergivores et chronophages, ne seront jamais du temps perdu, car ils vont nous permettre de nous développer et d’acquérir des compétences que nous pourrons remettre à profit plus tard.</p> <h2>C’est l’histoire d’une idée…</h2> <p>Les idées sont des traits incroyables de l’esprit qui peuvent parfois donner naissance à des projets. Certaines personnes ont quinze idées par jour alors que d’autre aucune pendant trois ans. Mais quand elle se présente, cette idée doit nous transporter&nbsp;! Sans cela, démarrer un <em>side-project</em> semble mission impossible, car ces petits projets prennent du temps sur notre vie personnelle et sans une idée qui nous passionne, difficile de garder la motivation pour donner vie à cette belle idée.</p> <p>Cette motivation est peut-être encore plus importante en tant que développeur junior, car durant le développement de notre projet, beaucoup d’embûches vont s’y glisser. Sans une motivation d’acier, nos belles idées peuvent vite être abandonnées face aux difficultés que nous allons rencontrer.</p> <p>La question est donc&nbsp;: comment trouver une idée qui nous intéresse avant tout&nbsp;? Si vous faites partie de ce cercle privé des personnes aux 15 idées par jour, cette question ne vous concerne pas. Pour tous les autres, nous devons réfléchir longuement à ce qui nous intéresse. Au final, ce qui compte, c’est que l’idée vous passionne. Regroupez vos passions, pensez à ce que vous souhaiteriez améliorer ou à ce qui vous énerve. Réfléchissez-y quelques instants, mais gardez en tête que l’important est d’essayer de régler un problème qui idéalement vous concerne ou vous tient à cœur. Cela vous aidera à garder votre motivation durant le développement de votre projet.</p> <p>Parfois, il suffit aussi simplement de se lancer un défi. Que ce soit réaliser un projet en une semaine ou réaliser un clone d’un outil déjà existant que vous souhaitez améliorer. Votre idée ne doit pas spécialement être originale, mais plutôt vous donner envie de vous surpasser.</p> <p>Une fois votre idée trouvée, il est maintenant l’heure de se pencher sur les bienfaits de travailler sur un <em>side-project</em>.</p> <h2>Qui nous forme…</h2> <p>En développant notre projet, nous allons rencontrer de nouveaux problèmes. Des problèmes que nous aurions pu parfois mettre des années à rencontrer au sein de notre entreprise. Lorsque l’on débute, il est rare que nous devions réaliser un projet entièrement&nbsp;: de la définition des besoins à leurs réalisations. Mais sur nos projets, nous sommes seuls à bord. C’est donc à nous que reviennent ces tâches d’ordinaires réservées à des collaborateurs plus expérimentés. En réalisant ce <em>side-project</em>, nous approfondissons notre expertise sur certains domaines.</p> <p>Ces petits projets vont aussi nous former personnellement et nous aider à définir ce que l’on aime faire le plus. Il est difficile lorsque nous débutons dans un domaine de choisir une branche de ce dernier et de se dire avec confiance&nbsp;: «Je serais encore heureux de faire cela dans cinq ou dix ans». En s’occupant de tous les aspects de notre projet, nous nous forçons à découvrir des domaines uniques mais surtout nous observons naturellement ce que l’on préfère faire. Ces projets nous aident pour définir une partie de notre avenir, ou du moins à identifier nos domaines de prédilections, ce qui nous motive et nous donne envie de travailler.</p> <p>Les <em>side-projects</em> sont aussi de superbes environnements dit de «bac à sable». Pouvoir tout développer, tout casser et tout recommencer est un luxe dans un projet. Ces derniers vont nous permettre de tester des outils, technologies ou domaines que nous n’aurions pas pu essayer dans notre travail, pour ne pas faire subir les répercussions de nos essais sur nos collègues.</p> <p>Ces projets vont aussi parfois nous pousser à faire de nouvelles rencontres pour nous aider, à surmonter les échecs et surtout à être patient et relativiser face au stress du travail quotidien.</p> <h2>Et nous pousse à la curiosité.</h2> <p>Lorsque l’on démarre un projet, nous avons rarement une route toute tracée déjà définie dans notre tête. Petit à petit, les défis que nous avons pu rencontrer nous ont poussés dans nos retranchements, nous forçant à chercher des nouvelles solutions pour ne pas abandonner.</p> <p>En se lançant ces défis, on pique et développe notre curiosité. En ne sachant pas quelle direction prendre ou quelle solution choisir parmi celles proposées, nous avons dû chercher et nous intéresser à de nouvelles compétences. Si vous avez choisi par exemple de créer vos vêtements et de les vendre, il y a fort à parier que vous n’aviez aucune connaissance dans au moins l’un des domaines se rattachant à cette activité.</p> <p>Réaliser ces petits projets nous permet de nous ouvrir à de nouvelles connaissances, de trouver des solutions à nos problèmes sans même y penser. Pour un développeur Junior comme moi, cela permet aussi de découvrir l’ensemble complet d’un projet et aussi de faire ses armes sur des projets personnels sans stress ni impact, quelles que soient nos décisions. En nous poussant ainsi à la curiosité, on développe naturellement cette compétence que l’on pourra à tout moment remettre à profit.</p> <h2>Des projets rarement finis…</h2> <p>Comme beaucoup de nos petits «à côté», il n’est pas rare de voir notre projet abandonné. Un empêchement un jour, un oubli un autre jour et voilà l’euphorie de notre idée qui retombe. Mais rassurons-nous, la malédiction des projets non finis ne touche pas que nous. Mais à l’inverse des entreprises qui abandonnent leurs idées, nous n’avons aucune contrainte à respecter. Nous n’avons aucun investisseur à satisfaire et aucun employé ne dépend de nous pour vivre. L’intérêt même de faire ses projets quand on veut est de pouvoir en être maître et de décider directement si nous voulons continuer de développer notre idée ou non.</p> <p>Il est peut-être là, le secret de ces «startups» issues de <em>side-projects</em>. Il est parfois plus facile de développer notre idée sans aucune contrainte réelle, face à des concurrents qui se doivent eux de répondre à des contraintes fortes et inévitables.</p> <p>Certaines personnes diront alors que nous avons perdu quelque chose de beaucoup plus important&nbsp;: du temps. Pourtant, peu importe le temps passé sur ce projet, au final, les <em>side-projects</em> valent toujours le coup d’être tentés.</p> <h2>Mais qui en valent le prix.</h2> <p>Bien que ces <em>side-projects</em> prennent du temps, ce qu’ils nous apportent en échange vaut l’investissement. Les compétences que nous accumulons et ces domaines que nous découvrons nous permettent de progresser dans des domaines qui nous tiennent à cœur. Ces <em>side-projects</em> sont aussi à mettre en avant lors de la recherche d’un stage, d’une alternance ou d’un emploi. À eux seuls, ils démontrent une passion pour nos domaines de prédilections, une volonté de prise de décision et de gestion des risques et témoignent d’une grande autonomie. Alors même si ce projet n’est pas fini, ne le cachez surtout pas sous le tapis et soyez-en fier.</p> <p>Certaines grandes entreprises ont compris la valeur que pouvaient avoir les <em>side-projects</em> pour leurs employés. C’est ainsi qu’on peut voir de plus en plus d’entreprise offrir à leurs collaborateurs la possibilité de travailler sur leur <em>side-project</em> durant les heures de travail (sous certaines conditions). L’implémentation la plus connue étant celle de Google, avec le «Projet 20%», que l’on retrouve aussi chez d’autres entreprises telles qu’Apple, 3M, BBC, ou Atlassian. Mais faire rentrer ces projets qui nous tiennent à cœur dans notre travail peut vite être sujet à débat, voir poser des questions d’ordre juridique sur la propriété de ce qui est développé durant les heures de travail.</p> <p>Au final, les <em>side-projects</em> peuvent nous permettent de progresser, d’apprendre ou de comprendre de nouveaux concepts à travers des idées qui nous tiennent à cœur. C’est en partie grâce à ces <em>side-projects</em> que j’ai pu apprendre le développement.</p> <p>À titre personnel, je me lance aujourd’hui dans deux nouveaux projets et qui sait, peut-être que ceux-ci verront le jour tôt ou tard&nbsp;!</p> <p>L’équipe Synbioz.<br>Libres d’être ensemble.</p> <img src="https://track.hubspot.com/__ptq.gif?a=5437879&amp;k=14&amp;r=https%3A%2F%2Fwww.synbioz.com%2Fblog%2Ftech%2Fles-side-projects-sont-le-sesame-du-developpeur-junior&amp;bu=https%253A%252F%252Fwww.synbioz.com%252Fblog%252Ftech&amp;bvt=rss" alt="" width="1" height="1" style="min-height:1px!important;width:1px!important;border-width:0!important;margin-top:0!important;margin-bottom:0!important;margin-right:0!important;margin-left:0!important;padding-top:0!important;padding-bottom:0!important;padding-right:0!important;padding-left:0!important; "> dev Fri, 24 Jun 2022 08:16:40 GMT vbout@synbioz.com (Valentin Bout) https://www.synbioz.com/blog/tech/les-side-projects-sont-le-sesame-du-developpeur-junior 2022-06-24T08:16:40Z Adieu phobie administrative quand on démarre dans la vie active https://www.synbioz.com/blog/tech/adieu-phobie-administrative-quand-on-demarre-dans-la-vie-active <div class="hs-featured-image-wrapper"> <a href="https://www.synbioz.com/blog/tech/adieu-phobie-administrative-quand-on-demarre-dans-la-vie-active" title="" class="hs-featured-image-link"> <img src="https://www.synbioz.com/hubfs/adieu-phobie-administrative.jpg" alt="bonshommes en costume rouge marchant sur des feuilles de papier géantes autour d'un point d'interrogation flottant" class="hs-featured-image" style="width:auto !important; max-width:50%; float:left; margin:0 15px 15px 0;"> </a> </div> <p>S’il y a des questions qu’on se pose avant de faire un entretien d’embauche, il y a également celles qui viennent après l’intégration dans l’entreprise.</p> <p>S’il y a des questions qu’on se pose avant de faire un entretien d’embauche, il y a également celles qui viennent après l’intégration dans l’entreprise.</p> <p>Pas toujours évident de s’y retrouver sur la gestion des congés, la mutuelle, la fiche de paie, les types d’entretiens ou encore les absences.</p> <p>Voici un petit pêle-mêle de questions-réponses qui pourront je l’espère vous éclairer.</p> <h2>Congés</h2> <h3>Mes congés peuvent-ils être pris dès l’embauche &nbsp;?</h3> <p>Oui &nbsp;!</p> <p><img src="https://media1.giphy.com/media/l8XYZYdlOHSrS/giphy.gif?cid=790b76119b23c917116b6fd956d133d78b45587dbf153ba9&amp;rid=giphy.gif&amp;ct=g" alt="Des congés dès l'embauche"></p> <p>Depuis la Loi El Khomri de 2016, les congés peuvent être pris dès l’embauche <em>«sans préjudice des règles de détermination de la période de prise des congés et de l’ordre des départs et des règles de fractionnement du congé.» (Code du travail - Article L3141-12)</em></p> <p>Si vous n’avez pas acquis suffisamment de congés, vous pouvez en demander par anticipation (payés ou sans solde), sous réserve de l’accord de votre employeur.</p> <p>Exemple&nbsp;:</p> <p>Vous intégrez l’entreprise le 1er juin 2022 et souhaitez poser 12 jours en août. Vous avez donc cumulé 2,5 jours * 2 mois soit 5 jours. Vous avez alors le droit depuis 2016 de poser ces congés sans attendre le 1er juin 2023.</p> <p>En revanche, vous devrez faire une demande de congés anticipés à votre employeur pour les 7 jours restants. Il sera libre de l’accepter ou de la refuser.</p> <p>À noter&nbsp;: <strong>la période de prise de congés comprend dans tous les cas la période du 1er mai au 31 octobre de chaque année.</strong></p> <h3>S’il me reste des congés non soldés sur la période, ai-je le droit de les reporter sur la période suivante &nbsp;?</h3> <p>Vous pouvez les reporter si votre employeur l’accepte. Dans le cas contraire, ces jours seraient alors payés.</p> <h3>Pourquoi quand je pose des congés mon salaire est plus élevé &nbsp;?</h3> <p>Car votre entreprise utilise la règle du 10ᵉ et non le maintien de salaire.</p> <p>C’est-à-dire qu’elle suit le calcul ci-dessous pour déterminer votre indemnité&nbsp;:</p> <p>Rémunération annuelle brute totale (perçue au cours de l’année de référence) / 10 = indemnité totale de congés payés. Il faut ensuite la rapporter au nombre de jours posés.</p> <p>Exemple&nbsp;:</p> <p>Votre rémunération brute mensuelle est de 2865 €.</p> <p>Toutes primes confondues sur la période de référence, votre rémunération est de 34&nbsp;380&nbsp;€. Vous avez travaillé sur toute la période et avez donc cumulé 30 jours ouvrables (ou 25 jours ouvrés).</p> <p>Si vous posez 2 semaines de congés, le calcul au 10ᵉ est le suivant&nbsp;:</p> <ul> <li>en jours ouvrables (soit 12 jours) : (34380/10) ⨯ (12/30) = 1371,2 €</li> <li>en jours ouvrés (soit 10 jours) : (34380/10) ⨯ (10/25) = 1645,44 €</li> </ul> <p>Dans le cas du maintien de salaire, on prend les infos suivantes&nbsp;:</p> <ul> <li>Horaire réel du mois&nbsp;: 7 h/jour</li> <li>Mois de 21 jours ouvrés</li> <li>Nombre d’heures réelles du mois&nbsp;: 147 (21 ⨯ 7)</li> <li>12CP&nbsp;=&nbsp;10 jours ouvrés soit 70&nbsp;heures non travaillées (10 ⨯ 7)</li> </ul> <p>Le calcul est le suivant&nbsp;: 2865&nbsp;€ ⨯ 70 / 147 = soit 1364,28&nbsp;€.</p> <h3>Dois-je poser mon samedi en congés si je ne travaille pas &nbsp;?</h3> <p>Tout dépend de votre convention collective ou votre accord d’entreprise.</p> <p>Vous avez droit soit à 30 jours ouvrables (c’est-à-dire hors dimanche) soit à 25 jours ouvrés (jours effectivement travaillés dans l’entreprise, c’est-à-dire hors samedi et dimanche).</p> <p>Dans le cas où vous êtes en jours ouvrables, vous devez en conséquence poser 5 samedis dans l’année.</p> <h2>Fiche de paie - Maladie</h2> <h3>À quoi servent mes cotisations sociales sur ma fiche de paie &nbsp;?</h3> <p><img src="https://narcisse.synbioz.com/attachments/c198e901-ae6d-4891-8b70-4322f570398b/variants/e0dbfa2c-e719-4ac8-be92-69ee3b02129e/src" alt="Fiche de paie"></p> <p>Le principe de ces cotisations repose sur la collectivité. Grâce à la solidarité commune, vous (salariés) bénéficiez de protections qui s’étendent aux domaines de la retraite, maladie, accident, prévoyance…</p> <p>Ces cotisations permettent par exemple le versement d’indemnités par la sécurité sociale lorsque vous êtes en arrêt maladie ou d’indemnités lorsque vous êtes au chômage ou encore de financer le régime des retraites.</p> <p>Les cotisations sociales représentent à la fois les cotisations patronales (versées par l’employeur) et les cotisations salariales (versées par le travailleur).</p> <h3>Quelle est la différence entre la mutuelle, la sécurité sociale et la prévoyance &nbsp;?</h3> <p><img src="https://narcisse.synbioz.com/attachments/64eb4315-d052-48b1-aea4-8ffdddbf3c79/variants/72b1800b-5282-4d3d-b497-f457f2a9b42c/src" alt="Santé" class="w-auto"></p> <p>La sécurité sociale, c’est l’organisme auquel vous cotisez par le biais des prélèvements sur votre fiche de paie. Ceux-ci sont reversés par l’employeur à l’Urssaf.</p> <p><img src="https://narcisse.synbioz.com/attachments/74e83012-2841-4ed8-ad6e-f2d1bbbc110e/variants/5fb0d4ab-5b31-439a-8c39-525825d7aaf5/src" alt="Carte vitale" class="w-auto"></p> <p>La mutuelle, c’est la complémentaire santé qui va venir abonder le montant pris en charge par la sécurité sociale sur certains actes médicaux plus onéreux. Elle est devenue obligatoire en entreprise et peut être prise en charge à hauteur de 50 ou 100% selon les employeurs. Chez Synbioz, la prise en charge est totale.</p> <p>La prévoyance sert à vous protéger contre les aléas de la vie (invalidité, incapacité ou décès). C’est elle qui va venir compléter les indemnités de la sécurité sociale en cas d’arrêt maladie de longue durée ou qui va permettre en cas de décès le versement d’un capital décès à la personne mentionnée dans votre bulletin d’affiliation.</p> <p>Comme je le répète souvent, soyez vigilants à mettre bien à jour votre bénéficiaire en cas de changement de situation&nbsp;! Le capital en cas de décès pourrait être versé au mauvais destinataire.</p> <p><img src="https://media1.giphy.com/media/WpaVhEcp3Qo2TjwyI1/giphy.gif?cid=790b7611437404ab0414f62e7e32af5acedab2267070f4b6&amp;rid=giphy.gif&amp;ct=g" alt="Erreur de bénéficiaire en cas de décès"></p> <p>La prévoyance est facultative, les entreprises sont libres de la proposer ou non sauf pour les cadres pour lesquels elle est obligatoire et également dans le cas de certaines conventions collectives.</p> <h3>C’est quoi les jours de carence &nbsp;?</h3> <p>Les indemnités journalières versées en cas d’arrêt maladie sont dues après un délai de carence.</p> <p>S’il est de 3 jours, elles seront donc versées à compter du 4ᵉ jour. Le point de départ du délai de carence correspond au premier jour entièrement non travaillé.</p> <h3>Je suis en arrêt maladie, quelle est la procédure pour percevoir mes indemnités &nbsp;?</h3> <p>On ne le dit jamais assez, il vous faut retourner à votre service RH votre arrêt maladie dans un délai de 48 heures.</p> <p>Vous devez transmettre les feuillets n°1 et n°2 à la CPAM et le feuillet n°3 à votre employeur.</p> <p><img src="https://narcisse.synbioz.com/attachments/bfa4079f-17f2-4391-999d-ad4c1ac2bd94/variants/cf962828-6d37-4865-ad78-abf2d5e04b74/src" alt="Feuillets arrêt maladie"></p> <p>Si vous adressez l’arrêt hors délai, la CPAM vous informe du retard constaté et précise que vous risquez une retenue financière en cas de nouvel envoi tardif dans les 2 ans qui suivent, ce qui n’est pas rien.</p> <h2>Temps de travail</h2> <h3>Suis-je obligé(e) de prévenir à chaque fois que je démarre plus tard ou termine plus tôt &nbsp;?</h3> <p>Oui &nbsp;! Car en ne le faisant pas, vous prenez un risque. Vous décidez de terminer plus tôt votre journée sans autorisation ni justification et pensez qu’après tout le travail est fait. Une procédure disciplinaire peut être initiée et conduire au licenciement.</p> <p>D’autre part, si vous êtes victime ou causez un accident sur ces horaires, vous pouvez perdre tout ou partie de votre indemnisation.</p> <h3>Ma femme est enceinte, est-ce que je peux l’accompagner aux visites pré-natales sur mon temps de travail &nbsp;?</h3> <p>Oui, et c’est d’ailleurs peu connu. Le futur père bénéficie d’autorisations d’absence pendant la grossesse de sa conjointe.</p> <p>Il s’agit de trois autorisations d’absence rémunérées pour l’accompagner lors d’examens de suivi de grossesse.</p> <p>Cette autorisation d’absence comprend non seulement le temps de l’examen médical, mais également le temps du trajet aller et retour.</p> <p>L’employeur peut exiger du salarié qu’il justifie de son lien avec la future mère et d’un certificat du médecin suivant la grossesse et attestant que l’absence est liée à un examen prénatal obligatoire.</p> <h3>Le lundi de Pentecôte est-il férié ou travaillé&nbsp;?</h3> <p>Le lundi de Pentecôte est un jour férié qui n’est pas obligatoirement chômé. Celui-ci peut donc être travaillé ou non selon l’entreprise dans laquelle vous travaillez. À l’origine, la Journée de solidarité était obligatoirement fixée le lundi de Pentecôte, ce qui n’est plus systématiquement le cas partout. S’il est choisit comme journée de solidarité alors vous devez soit travailler sans être payé, soit vous posez un congés ou 7 heures et votre entreprise s’acquitte ainsi d’une contribution reversée à la CNSA (la Caisse nationale de solidarité pour l’autonomie).</p> <h2>Vie en entreprise</h2> <h3>Quelles sont les différences entre un entretien annuel et un entretien professionnel &nbsp;?</h3> <p>Même s’il est fortement recommandé, sachez que l’entretien annuel, aussi appelé EAD n’est pas obligatoire en entreprise contrairement à l’entretien professionnel.</p> <p>L’entretien annuel est centré sur votre performance et vos objectifs à court terme (1 an). Il s’agit du bilan de votre activité professionnelle sur l’année écoulée. Les objectifs opérationnels pour l’année à venir sont fixés pendant ce rendez-vous.</p> <p>Chez Synbioz on a également opté pour un entetien trimestriel permettant des temps d’échanges plus fréquents pour des prises de décisions plus rapides sur des sujets d’évolution, de formation ou tout autre souhait qui ne peuvent attendre le prochain entretien annuel.</p> <p>L’entretien professionnel quant à lui doit être réalisé tous les 2 ans et est centré sur vos souhaits d’évolution dans l’entreprise. Il s’inscrit dans une démarche de gestion des compétences. Les questions d’évolution, de projet professionnel et de formation y sont centrales.</p> <h3>Le remboursement des frais de transport par l’employeur ce n’est valable qu’en région parisienne &nbsp;?</h3> <p>Depuis 2009, tous les employeurs doivent rembourser sur la base du tarif de seconde classe, 50 % minimum du prix des titres d’abonnements (attention les titres à l’unité ne sont pas remboursés) souscrits par ses salariés pour le trajet domicile-lieu de travail accompli avec les transports publics (train, vélo, bus…).</p> <p>Le montant de la prise en charge doit être mentionné sur votre fiche de paie.</p> <p>Et si vous vous rendez au travail à vélo, sachez que l’indémnité kilométrique vélo (IKV) a été remplacée par le forfait mobilités durables. Sa prise en charge ne doit pas excéder 500 € par an et par salarié et n’est pas obligatoire pour les employeurs.</p> <p>Et si vous voulez aller plus loin, suggérez-nous de nouvelles questions sur <a href="https://twitter.com/synbioz">Twitter</a>, on se fera un plaisir d’y répondre dans un prochain article.</p> <p>L’équipe Synbioz.<br>Libres d’être ensemble.</p> <img src="https://track.hubspot.com/__ptq.gif?a=5437879&amp;k=14&amp;r=https%3A%2F%2Fwww.synbioz.com%2Fblog%2Ftech%2Fadieu-phobie-administrative-quand-on-demarre-dans-la-vie-active&amp;bu=https%253A%252F%252Fwww.synbioz.com%252Fblog%252Ftech&amp;bvt=rss" alt="" width="1" height="1" style="min-height:1px!important;width:1px!important;border-width:0!important;margin-top:0!important;margin-bottom:0!important;margin-right:0!important;margin-left:0!important;padding-top:0!important;padding-bottom:0!important;padding-right:0!important;padding-left:0!important; "> entreprise Mon, 20 Jun 2022 12:15:51 GMT mducrocq@synbioz.com (Marie Ducrocq) https://www.synbioz.com/blog/tech/adieu-phobie-administrative-quand-on-demarre-dans-la-vie-active 2022-06-20T12:15:51Z Exécuter des commandes dans un conteneur avec ECS d’AWS https://www.synbioz.com/blog/tech/executer-des-commandes-dans-un-conteneur-avec-ecs-aws <div class="hs-featured-image-wrapper"> <a href="https://www.synbioz.com/blog/tech/executer-des-commandes-dans-un-conteneur-avec-ecs-aws" title="" class="hs-featured-image-link"> <img src="https://www.synbioz.com/hubfs/onder-ortel-6BQZrjHP8J8-unsplash.jpg" alt="wagons de marchandises sur rails sur ciel nuageux" class="hs-featured-image" style="width:auto !important; max-width:50%; float:left; margin:0 15px 15px 0;"> </a> </div> <p>Pour les projets dont nous gérons l’infrastructure de production, nous utilisons depuis longtemps des outils tels que Rancher et Kubernetes.</p> <p>Pour les projets dont nous gérons l’infrastructure de production, nous utilisons depuis longtemps des outils tels que Rancher et Kubernetes.</p> <p>Avec <a href="https://kubernetes.io/fr/docs/reference/kubectl/">kubectl</a> on s’habitue vite au confort de pouvoir visualiser la sortie standard de ses conteneurs, exécuter des commandes à l’intérieur, <em>forwarder</em> des ports. Je vous en parlerai un peu plus à l’occasion si vous voulez.</p> <p>À l’inverse lorsqu’on utilise les solutions maisons (et fermées) de fournisseurs tel qu’AWS, on se retrouve vite démunis et tributaires du bon vouloir du fournisseur pour nous fournir les outils adaptés.</p> <h2>Le cas ECS</h2> <p>ECS est le service de gestion de conteneurs d’Amazon Web Services. Il s’appuie sur le service historique EC2.</p> <p>Pour faire simple, vous disposez d’instances EC2 pré-configurées avec un démon Docker, ainsi que d’un orchestrateur qui vient organiser et gérer les conteneurs qui seront répartis sur ces instances.</p> <p>Dans ce mode de fonctionnement, vous n’avez pas la main sur l’orchestrateur mais les nœuds (instances EC2) restent visibles et potentiellement accessibles. On peut donc s’y connecter, notamment en SSH, et interagir avec les conteneurs qui s’y trouvent.</p> <p>Toutefois cela pose un certain nombre de problèmes&nbsp;:</p> <ul> <li>obligation d’ouvrir le service SSH, ce qui augmente inutilement la surface d’exposition</li> <li>définir au préalable sur quel nœud est exécuté le conteneur avec lequel on souhaite interagir</li> <li>pas de possibilité d’interagir avec les conteneurs depuis la console (UI)</li> </ul> <h2>Le cas Fargate</h2> <p>Fargate pousse la logique d’ECS un cran plus loin. Cette fois-ci vous n’avez même plus accès aux ressources sous-jacentes.</p> <p>Évidemment il y a une infrastructure derrière, mais elle est complètement gérée pour vous, avec la promesse de ne pas avoir à gérer le dimensionnement, la sécurisation, etc.</p> <p>L’inconvénient par rapport à notre besoin d’interaction avec les conteneurs tient au fait que vous n’avez plus du tout de moyen de vous connecter sur les serveurs ni de rentrer dans les conteneurs par ce biais.</p> <p>Devant cette situation de blocage AWS a mis à jour son CLI pour proposer des commandes permettant d’interagir avec les conteneurs, quel que soit le système utilisé.</p> <h2>AWS CLI à la rescousse</h2> <p>En premier lieu vous aurez besoin du CLI AWS. Je ne vous fais pas l’affront de paraphraser <a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html">la documentation</a> pour l’installer.</p> <p>Une fois réalisé vous aurez également besoin d’<a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html#install-plugin-macos">installer le plugin Session Manager</a> pour le CLI.</p> <p>On s’assure que tout est installé correctement&nbsp;:</p> <div class="language-shell highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code>fuse@archibald:~ →&nbsp;aws <span class="nt">--version</span> aws-cli/2.6.3 Python/3.9.13 Darwin/21.5.0 <span class="nb">source</span>/arm64 prompt/off </code></pre> </div> </div> <div class="language-shell highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code>fuse@archibald:~ →&nbsp;session-manager-plugin The Session Manager plugin was installed successfully. Use the AWS CLI to start a session. </code></pre> </div> </div> <p>Pour permettre l’accès au conteneur, AWS utilise son service <code>Systems Manager</code> (SSM) pour établir la connexion au conteneur et vérifier que l’utilisateur exécutant les commandes a les droits suffisants pour ce faire.</p> <p>Dans les faits, un agent SSM va être monté et démarré dans le conteneur pour permettre l’exécution de commandes.</p> <h2>Rendre l’agent SSM disponible dans les conteneurs</h2> <h3>Création et configuration de la politique</h3> <p>Pour mettre en place cet agent nous allons devoir en premier lieu créer une politique (policy) associée&nbsp;:</p> <div class="language-json highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"Version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2012-10-17"</span><span class="p">,</span><span class="w"> </span><span class="nl">"Statement"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"Effect"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Allow"</span><span class="p">,</span><span class="w"> </span><span class="nl">"Action"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="s2">"ssmmessages:CreateControlChannel"</span><span class="p">,</span><span class="w"> </span><span class="s2">"ssmmessages:CreateDataChannel"</span><span class="p">,</span><span class="w"> </span><span class="s2">"ssmmessages:OpenControlChannel"</span><span class="p">,</span><span class="w"> </span><span class="s2">"ssmmessages:OpenDataChannel"</span><span class="w"> </span><span class="p">],</span><span class="w"> </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"*"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre> </div> </div> <p>Cette politique devra ensuite être rattachée à un rôle dont dispose l’utilisateur qui doit exécuter les commandes.</p> <p><img src="https://narcisse.synbioz.com/attachments/6a27dd2a-ddaa-4f9d-ab0c-305a46c079d4/variants/dd44a8bd-4a3d-4f87-aa6f-84b0ae8c9076/src" alt="Création de la police AWS"></p> <h2>Utilisation du rôle par les instances</h2> <p>Par défaut, les conteneurs en cours d’exécution ne seront sûrement pas en capacité d’exécuter des commandes.</p> <p>Nous allons devoir déployer une nouvelle définition de tâche et activer l’exécution de commandes.</p> <p>Vous pouvez créer une définition de tâche identique à l’actuelle, simplement pour effectuer la mise à jour. En effet, si vous demandez à ECS de passer de la version N à N il ne fera rien.</p> <p>Il faut lui dire de passer à la N+1, même si elle est identique.</p> <pre><code>aws ecs update-service --cluster nom_du_cluster \ --service nom_du_service \ --task-definition nom_de_definition_de_tache:version \ --enable-execute-command </code></pre> <p>Une fois cette commande lancée, ECS ou Fargate va rouler les conteneurs, en en démarrant de nouveaux puis en stoppant les anciens.</p> <p>Pour vérifier que tout fonctionne correctement on récupère un identifiant de tâche&nbsp;:</p> <pre><code>fuse@archibald:~ →&nbsp;aws ecs list-tasks --cluster nom_du_cluster { "taskArns": [ "arn:aws:ecs:eu-west-1:034473609837:task/nom_du_cluster/63832eb11b4641729c376b95bee02f82", "arn:aws:ecs:eu-west-1:034473609837:task/nom_du_cluster/c9b7a91f6cdc43e0a7d6d70835b1db61" ] } </code></pre> <p>Et on regarde si l’agent est démarré sur cette tâche&nbsp;:</p> <pre><code>fuse@archibald:~ →&nbsp;aws ecs describe-tasks \ --tasks c9b7a91f6cdc43e0a7d6d70835b1db61 \ --cluster nom_du_cluster | jq ".tasks[].containers[].managedAgents" [ { "lastStartedAt": "2022-05-31T17:24:35.002000+02:00", "name": "ExecuteCommandAgent", "lastStatus": "RUNNING" } ] </code></pre> <p>On voit que l’agent est bien en cours d’exécution, on va pouvoir exécuter nos commandes&nbsp;:</p> <div class="language-shell highlighter-rouge"> <div class="highlight"> <pre class="highlight"><code>fuse@archibald:~ →&nbsp;aws ecs execute-command <span class="nt">--cluster</span> nom_du_cluster <span class="se">\</span> <span class="nt">--task</span> c9b7a91f6cdc43e0a7d6d70835b1db61 <span class="se">\</span> <span class="nt">--container</span> nom_du_conteneur <span class="se">\</span> <span class="nt">--interactive</span> <span class="nt">--command</span> <span class="s2">"bash"</span> The Session Manager plugin was installed successfully. Use the AWS CLI to start a session. Starting session with SessionId: ecs-execute-command-0ef9e2cdabcb37a0a root@ip-172-23-4-93:/app# </code></pre> </div> </div> <p>À vous la gloire et la prospérité, vous pouvez maintenant lancer une commande à l’intérieur de votre conteneur&nbsp;!</p> <p>L’équipe Synbioz.<br>Libres d’être ensemble.</p> <img src="https://track.hubspot.com/__ptq.gif?a=5437879&amp;k=14&amp;r=https%3A%2F%2Fwww.synbioz.com%2Fblog%2Ftech%2Fexecuter-des-commandes-dans-un-conteneur-avec-ecs-aws&amp;bu=https%253A%252F%252Fwww.synbioz.com%252Fblog%252Ftech&amp;bvt=rss" alt="" width="1" height="1" style="min-height:1px!important;width:1px!important;border-width:0!important;margin-top:0!important;margin-bottom:0!important;margin-right:0!important;margin-left:0!important;padding-top:0!important;padding-bottom:0!important;padding-right:0!important;padding-left:0!important; "> ops Fri, 10 Jun 2022 13:35:01 GMT mcatty@synbioz.com (Martin Catty) https://www.synbioz.com/blog/tech/executer-des-commandes-dans-un-conteneur-avec-ecs-aws 2022-06-10T13:35:01Z