Archives pour la catégorie Theory-oriented tutorials

Some thoery-oriented tutorials

L’Essentiel pour bien comprendre

Cette partie va tenter de vous expliquer comment le Lua fonctionne concrètement dans l’OS et vous aider à comprendre ce que vous faites lorsque vous écrivez un script pour TI-Nspire. Toute connaissances préalable en Lua ou d’un quelconque langage évènementiel n’est pas inutile, mais pas nécessaire.

Le Lua est un langage script interprété, ce qui signifie que ce n’est pas aussi rapide que l’ASM/C mais que cela reste bien mieux que le TI-BASIC. Une bonne chose à noter, c’est que ce langage est en parfaite harmonie avec l’OS car lié par une structure évènementielle et un assez confortable environnement graphique (GC). Tout d’abord, nous devons comprendre comment tout cela fonctionne.

Le Lua est normalement un langage script séquentiel. Par exemple, quand on demande d’afficher une valeur avec la commande print(), on peut très facilement deviner dans le code quand est-ce que la commande sera lancée. Voici un code d’exemple :

a = 1
print(a)
a = a + 1
print(a)

En sortie :

1
2

Rien de très surprenant. Seulement, sur TI-Nspire, le Lua a une toute autre approche. On rencontre cette approche lorsqu’on programme avec des langages de haut niveau ou objet (tel que le C++/Qt4, C#, etc …). Dans ces langages, nous ne sommes pas maîtres de l’exécution de tel ou tel code. Oui, c’est étrange d’entendre dire ça, et pourtant c’est la vérité. Nous allons programmer dans un langage qui ne nous obéit pas ? En quelque sorte, oui.

Pas d’inquiétude ! Nous sommes là pour vous aider à ce passage difficile si c’est la première fois que vous le franchissez !

Tout d’abord, il faut vous placer de l’autre côté du miroir. Avant, vous étiez le patron, c’est à dire que vous ordonniez à la machine de vous calculer 1 + 1 et elle vous renvoyait fièrement 2. Maintenant, vous êtes un ouvrier. On vous donne l’autorisation de faire une tâche, vous dites comment faire à la machine. Concrètement, les « autorisations » sont des évènements. Vous avez le droit de faire ce que vous voulez, à moins que vous en ayez reçu l’autorisation. Voici un pseudo code qui montre que faire lorsque l’évènement « FaireLaCuisine » est appelé :

function FaireLaCuisine()
	organiserLaPaillasse()
	couperLesIngredients()
	assembler()
	chauffer()
	servir()
end

Vous comprenez bien que vous n’allez pas faire la cuisine une fois qu’on vous a embauché. Vous allez attendre que votre maître de maison vous en donne l’autorisation/l’ordre pour la faire ! Et bien c’est la même chose en Lua sur TI-Nspire. Tout est question d’évènements ! Dans notre exemple – en imaginant que FaireLaCuisine est un évènement de l’API – l’API appelle la fonction FaireLaCuisine si elle existe lorsque les conditions nécessaires sont réunies.

Donc, si on résume, tout code de notre part doit être mis dans des fonctions que la TI-Nspire va lancer. Mais comment savoir lesquelles ? Et bien c’est là que nous vous invitons à jeter un œil à cette liste des évènements. Lorsqu’un évènement est appelé, il lui est passé zéro ou plusieurs arguments que l’on peut réutiliser dans le corps de la fonction. Cela permet par exemple de savoir quelle touche est actuellement active, car lorsque l’évènement charIn() est appelé, on lui passe en plus une chaîne de caractère correspondant à cette touche. Par contre, lorsque l’évènement enterKey() est appelé, aucun argument n’est nécessaire, donc aucun n’est passé.

Avant, dans un langage quelconque n’étant pas évènementiel (BASIC, C, PHP, etc …), on programmait de cette manière :

-- Initialisation des constantes ici
...
 
k = 0
while k != 0 do
	k = getKey()
end
if k == 72 then
	-- quelque chose
end

Maintenant, cela ressemblera à ça :

-- Initialisation des constantes ici
...
 
function on.charIn(ch)
    if ch == "7" then
	-- quelque chose
    end
end

>> Partie 2

Couplage des événements

Retour à la partie 2

Cette partie va montrer comment sont déroulés les appels des évènements, ce qu’on appelle plus couramment des « couplages » de l’anglais link.

En fait, le script utilisateur est lancé avant tout le code maître (celui qui contrôle votre programme). Cela permet entre autre d’initialiser des valeurs. Seulement on ne peut ni évaluer d’expressions ni utiliser l’API fournie par la TI-Nspire (comme platform, var …) avant que le code maître soit lancé. Voici un résumé des appels :

  • Lancement de la bibliothèque string
  • Lancement de la bibliothèque math
  • Lancement du framework de la TI-Nspire (toolpalette, image, … )
  • Ouverture et lancement du script Lua utilisateur
  • Lancement de l’API var (store, recall, …)
  • Lancement de l’API platform (window, gc, …)
  • Liaison des évènements (pseudo code)
while(Exit)
	------- Some OS routines here
 
 	---- Begin of Event link
	buffer:captureDataFromKeyPad() 	--  (N) some underground routines to catch events
	if buffer.charInput ~= "" then 	 	--  (N)
		on.charIn(buffer.charInput)
		buffer.charInput= "" 	 	 	--  (N)
	end
	if buffer.arrowKey ~= "" then 	 	--  (N)
		on.arrowKey(buffer.arrowKey)
		buffer.arrowKey = "" 	 	 	--  (N)
	end
	----- etc ... 	
	if platform.window.isInvalidate then 
	 	local gc = platform.gc()	 	  	--  Create a new graphical context
	 	gc:begin()	 		 	   		-- initialize the graphical context
		on.paint(gc) 	  	 	  		-- save all the things we have to draw
	 	gc:finish()  	  	  	    		--  (N) draw all those things	
		platform.window.isInvalidate = false 	-- say that the window has been drawn
	end
	----- End of Event link
end

Note : Tous les commentaires préfixés par (N) sont là uniquement pour permettre de comprendre le sens de la routine. Ces fonctions n’existent pas vraiment.

Maintenant, nous pouvons comprendre comment tout cela est lié, uniquement par une boucle principale. Ceci peut vous aider à comprendre que ça ne sert à rien de faire une boucle vous-même, car de toute manière l’écran ne sera pas actualisé. Cela peut également vous permettre de voir quand est-ce que l’écran est dessiné. Dit plus clairement, nous ne pouvons pas utiliser ni gc ni platform.gc(), pour dessiner quelque chose à l’écran, si l’on se trouve en dehors de on.paint() (fonctions auxiliaires appelées depuis on.paint() exceptées bien sûr).

Voici un exemple simple de programme Lua qui affiche un message seulement lorsqu’une touche est active (et laisse donc l’écran blanc lorsqu’aucune touche n’est active) :

function on.paint(gc)
	if message then
	 	gc:setFont("sansserif", "r", 10)	-- initialize font drawing
	 	gc:drawString(message, 0, 0, "top")	-- display the message at (0, 0) coordinates
	 	message = nil 				-- erase the message
	 	timer.start(1) 	 	 	-- start a timer to exit on.paint() but keep in mind that we have to redraw the screen
	end
end
 
function on.timer()
	timer.stop()
	platform.window:invalidate()
end
 
function on.charIn(ch)
	message = "Hello World !"		-- store a message
	platform.window:invalidate()		-- force display
end

Lorsque l’on ouvre le document, le script est lu une première fois. Il initialise et écrase toutes les fonctions et variables globales avec celles que vous avez défini. Donc message vaut nil (valeur nulle). Lorsque l’évènement on.paint() est appelé, message vaut nil, donc rien ne se passe. Seulement, lorsque l’on appuie sur une touche, on.charIn() est appelé, message vaut maintenant « Hello world » et nous indiquons à platform que l’écran doit être rafraîchi. Donc au tour de boucle suivant, on.paint() est appelé encore une fois. message n’est pas nul, donc nous l’affichons tout en l’effaçant puis nous lançons un timer. Pourquoi ? Parce que si nous appelons platform.window:invalidate() juste ici, l’écran ne sera pas actualisé. Pourquoi (encore une fois) ? Regardez le pseudo-code juste de tout à l’heure. A la fin de la boucle l’OS considère l’écran comme actualisé. Donc l’utilisation d’un timer est nécessaire pour appeler manuellement on.paint() tout en étant sorti de ce dernier. Lorsque le timer a fini le décompte, on.timer() est appelé et nous forçons donc l’écran à s’actualiser. L’écran est donc actualisé mais il n’y a rien à dessiner car message est nul. Ainsi l’environnement graphique laisse l’écran blanc.

>> Partie 4

Classes d’objets

Retour à la partie 4

Dans cette partie nous allons étudier une notion assez pratique du Lua, celle de manipuler non plus des variables, mais des objets.

Si vous avez déjà étudié le Lua auparavant, vous devriez savoir que c’est un langage utilisant excessivement les tables indexées par clés. Cela veut dire que chaque élément d’un tableau (ou liste) est associé à une clé qui permet de la référencer. Par exemple la table suivante est indexée par des chaines de caractères :

tab = {"cle1"="valeur1", "cle2"="valeur2"}
print(tab["cle1"]) -- va afficher "valeur1"

En Lua, les clés comme les valeurs sont polymorphes, c’est à dire qu’on peut aussi bien stocker des chaines de caractères avec des tables ou encore des fonctions ou tout autre type de valeur :

tab = {
  [1]="exemple",
  "2"="de",
  [function() return 3 end]="table",
  [false]="polymorphe"
}
 
for k, v in pairs(tab) do
  print(k, v)
end

Affichera :

1                 exemple
2                 de
function: 10f6840 table
false             polymorphe

Même l’environnement global ( _G ) est une table !

C’est donc dans cette optique là que l’on peut manipuler le Lua sous forme de langage objet, avec chaque objet une table et chacun des éléments de cet objet une méthode ou une propriété. Par la suite, si l’on veut créer un nouvel objet, il suffira de copier la table « modèle » (concept des meta-table). Pour créer des classes d’objets hérités on procédera de la même manière, sauf qu’on modifiera certaines méthodes.

myObject = {
    nom="toto",
    age=21,
    ChangerNom=function(self, nom)
        if #nom > 1 then
            self.nom = nom
        end
    end,
    ChangerAge=function(self, age)
        if age > 1 then
            self.age = age
        end
    end
}

Et accéder très facilement à chacun des éléments :

print(myObject.age) -- affiche 21
myObject:ChangerAge(18) -- identique à myObject.ChangerAge(myObject, 18)
print(myObject.age) -- affiche 18

Il faut comprendre cependant qu’il n’y a pas de notion d’espaces publiques et privés comme en C++/C#/Java, car c’est simplement une représentation, pas une réelle implémentation.

De cette manière vous devez enfin comprendre pourquoi on écrit platform.window:width() et non une variable ressemblant à « GetWindowWidth() ». En effet, width() est une méthode de l’objet window qui est une propriété de l’objet platform. Pareil pour platform.gc() que l’on retrouve abrégé en gc. En réalité, gc est un objet et platform.gc() en est son constructeur. D’un côté, c’est donc la même chose (ils ont tous les deux les mêmes méthodes) mais de l’autre non (ils ont des propriétés quelque peu différentes).

Nous allons donc, pour illustrer ce principe, créer ensemble un script Lua qui nous permettra de créer une et une seule fois une classe Forme ayant plusieurs méthodes dont un constructeur permettant de créer plusieurs formes.

Dans l’API Lua de la TI-Nspire, on trouve une méthode « class() » qui ne provient pas du Lua. En réalité, cette méthode économise certaines lignes qui peuvent paraître incompréhensibles.

Ces lignes sont :

Account = {}
Account.__index = Account
 
function Account.create(balance)
    local acnt = {}             -- our new object
    setmetatable(acnt,Account)  -- make Account handle lookup
    acnt.balance = balance      -- initialize our object
    return acnt
end
 
function Account:withdraw(amount)
    self.balance = self.balance - amount
end
 
-- create and use an Account
acc = Account.create(1000)
acc:withdraw(100)

Source : http://lua-users.org/wiki/SimpleLuaClasses

Maintenant, la même chose en utilisant class() :

Account = class()
 
function Account:init(balance)
    self.balance = balance      -- initialize our object
end
 
function Account:withdraw(amount)
    self.balance = self.balance - amount
end
 
-- create and use an Account
acc = Account(1000)
acc:withdraw(100)

Avouez que cela est plus clair ?
Il est à noter également que init() est un constructeur. Le nom de ce constructeur est contraint par la méthode class() qui appelle ce dernier pour compléter la construction avec setmetatable().

Nous pouvons donc créer très facilement notre classe Forme :

Forme = class()
 
-- Constructeur
function Forme:init(x, y, image)
    self.xpos = x
    self.ypos = y
    self.pic = image
end
 
-- Dessiner la forme
function Forme:paint(gc)
    gc:drawImage(self.pic, self.xpos, self.ypos)
end
 
-- Repositionner l'objet en indiquant les nouvelles coordonnees
function Forme:setxy(newx, newy)
    if newx > xmin and newx < xmax and newy > ymin and newy < ymax then
        self.xpos = newx
        self.ypos = newy
   end
end
 
-- Repositionner l'objet en indiquant le deplacement a effectuer
function Forme:move(dx, dy)
    local newx,newy
    self:setxy(self.xpos + dx,self.ypos + dy)
end

Vous aurez surement remarqué l’utilisation de variables globales, xmin, ymin xmax et ymax.

xmin = 20
ymin = 30
xmax = 250
ymax = 200

Nous pouvons enfin jouer avec notre classe Forme ! Pour cela nous allons créer deux images et les convertir en TI.Image en utilisant l’outil préliminaire de développement :

carre_rouge = image.new("\010\000\000\000\010\000\000\000\000\000\000\000\020\000\000\000\016\000\001\000\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252")
smiley = image.new("\016\000\000\000\018\000\000\000\000\000\000\000 \000\000\000\016\000\001\000\156\243\156\243\156\243\189\247\213\222\235\193\136\181g\181\135\181g\181\137\185O\202\189\247\156\243\156\243\156\243\156\243\156\243{\239O\206\168\222F\243D\243d\247d\247#\243\004\235E\214\170\1858\231\156\243\156\243\156\243\213\222m\210\231\230d\247\132\251\195\255\131\255\131\255\131\255C\251\003\247\132\226\233\193r\210z\239\156\243\204\189\198\226\132\247\166\218\007\198\198\226\195\255\131\255#\243\231\197\007\198\002\251\131\230\135\185\246\222\212\218g\210D\243\166\218\166\218\197\226D\239\131\255\131\255C\247\165\218d\222\006\206\194\242\196\205O\206\170\185\005\239\131\255\197\226D\239\131\255\131\255\131\255\131\255C\251\034\255\002\255E\218\226\250b\234\167\193G\173C\247\131\255\131\255\130\218\001\198\162\222\131\255c\251\002\243\161\197\161\197\226\250\226\250\162\246\133\193f\181#\243C\251\131\255\194\193\194\156\002\202C\251\034\255\162\234\194\156\194\156\193\250\193\254\193\254\133\193g\181\003\247c\251\034\255\196\230e\218\196\234\034\255\034\255\226\242%\218%\218\226\250\226\250\161\254\133\193&\169\226\242\034\255\034\255\034\255\034\255\034\255\002\255\002\255\193\254\193\254\226\250\161\254\161\254\161\254d\189G\173\163\234\002\255\226\242\194\242\163\234\162\242\162\242\162\242\130\238\130\242B\242\130\242\162\246B\242\133\193\014\198E\214\194\242f\181\198\156\231\156\231\156\231\156\231\156\231\156\231\156\198\156\231\156B\242\227\213\235\197Y\235\136\185\130\238\193\254\132\189s\206\222\251\222\251\222\251\222\251{\239\198\156\130\242\129\254d\189\179\214\156\243\237\193\196\205\162\242\162\242\198\201)\165)\165)\165)\165H\173B\242\129\254\227\213\169\193\023\227\156\243z\239\137\185\034\226\162\246\130\242B\234\034\230\034\230B\234\034\234\129\254\130\242D\181\213\222\156\243\156\243\156\243\023\227\201\197\002\222\162\246\161\254\129\254\161\254\129\254\129\250\034\234d\189\147\214\156\243\156\243\156\243\156\243\156\243\246\222\011\206\196\205\002\222B\238\034\238\034\230\196\209\165\197\147\214{\239\156\243\156\243\156\243\189\247\156\243\156\243\023\227p\210\011\206\198\205\198\205\232\205O\210\147\214\156\243\156\243\156\243\156\243")
forme1 = Forme (xmin + 20, ymin + 20, smiley)
forme2 = Forme (xmax - 30, ymax - 30, carre_rouge)

Dorénavant il ne nous reste plus qu’à créer la structure principale en utilisant divers évènements afin de tester toutes les possibilités de la classe que nous venons de créer.

-- Affichage
function on.paint(gc)
    gc:setColorRGB(231, 231, 231)
    gc:fillRect(xmin, ymin, xmax-xmin+15, ymax-ymin+15)
    forme1:paint(gc)
    forme2:paint(gc)
    gc:setColorRGB(0, 0, 255)
    gc:setFont("sansserif" , "b", 11)
    x = gc:drawString("(" .. forme1.xpos..", ".. forme1.ypos .. ")", 50, 0, "top")
end
-- Permet de deplacer forme1 avec les fleches directionnelles
function on.arrowLeft()
    forme1:move(-5, 0)
end
 
function on.arrowRight()
    forme1:move(5, 0)
end
 
function on.arrowUp()
    forme1:move(0, -5)
end
 
function on.arrowDown()
    forme1:move(0, 5)
end
-- Permet de choisir l'emplacement de forme2 avec la souris
function on.mouseDown(wx, wy)
    forme2:setxy(wx, wy)
end

Et voilà le résultat !