PHP et Lua, le couple parfait

EDIT: Cet article est un repost de l’ancienne version du blog. Et puis, depuis, l’extension PHP a changé, elle n’utilise plus expose_function() mais une autre fonction (néanmoins le comportement reste similaire, il suffit de se référer à la documentation pour voir les différences).

Parfait, parfait, j »exagère un peu. Mais il faut avouer que c »est bien fun.

Bon alors on va remettre les choses en place pour les deux qui dorment au fond :
Lua (Lune en portugais), c »est un langage de programmation créé par 3 personnes de l »université de Rio de Janeiro. Il est libre, et a été conçu dans le but d »être très léger, ce qui fait que son interpréteur pèse environ 100 Ko (comparé à PHP qui pèse 15 Mo…). Il est tellement compact que certains petits jeux indépendants ont décidé de l »utiliser comme moteur de scripts : WoW, Garry »s mod, Far Cry… Et il a même été porté sur la Nintendo DS (avec MicroLua DS, fait par un français) et la PSP. L »article Wikipédia sur Lua vous en dira plus sur le sujet.

Maintenant, venons en au faits, c »est à dire l »interaction entre PHP et Lua. Eh ouais, parce que, avant d »avoir été un langage interprété en standalone, il a été écrit sous forme d »une bibliothèque C, afin de rendre l »interaction avec les programmes C plus facile (pour créer un système d »extension simplement par exemple, comme sur WoW). Par la suite, quelques développeurs d »extensions PHP qui passaient par là on eu l »idée de développer une extension PHP pour utiliser Lua depuis notre site web ou, comme moi, depuis un programme, et c »est de là qu »on est arrivé à PHP-Lua. Bref, c »est fini pour la petite histoire, on passe à l »installation.

Première étape : Installation de l »extension

Néanmoins, il faut savoir que l »extension officielle est en alpha, et donc considérée comme non-stable. Cependant, je travaille pour ma part sur un fork de cette extension, disponible ici. Ce fork est plus à jour que la version officielle de PHP-lua (le dernier commit date de 6 jours au moment ou j »écris), et un peu plus évolué et propose quelques fonctionnalités en plus dont j »ai eu besoin.
Je vous conseille de faire un clonage du dépôt Git de phplua, plutôt que de télécharger l »archive qu »il propose, le dépôt étant bien évidemment plus à jour.

Pour la compilation, rien de plus simple (penser à avoir installé Lua au préalable est une bonne idée aussi) :

phpize
./configure
make
make install

Mais, comme je tourne sur un système Debian, ça ne marche pas aussi simplement. En effet, Debian appelle la bibliothèque Lua d »une autre façon que les autres, ce qui implique de changer un peu le Makefile avant de lancer make. J »ai dû par conséquent aller chercher dans le Makefile la variable LUA_SHARED_LIBADD, pour remplacer sa valeur par :

LUA_SHARED_LIBADD = -llua5.1

Et ensuite, l »extension a compilé correctement. Si vous avez essayé et que vous n »y arrivez pas, je vous propose quand même ma build, réalisée sur Debian, avec Lua 5.1 d »installé : c »est ici. Copiez-le dans /usr/lib/php5/le dossier de votre version/ et ajoutez « extension=lua.so » à votre php.ini et c »est bon !

Une fois que l »extension a été installée, il me reste plus qu »à tester son fonctionnement. Pour ça, un tout bête script :

<?php

$lua = new lua();
$lua->evaluate(''print("Hello World !\\n")'');

Et là, magie ! Un « Hello World » apparaît, signalant que tout fonctionne bien.
Bon, maintenant, on va passer au coeur du sujet : faire des trucs marrant en Lua avec PHP.

On commence simple : Exécution d »un script Lua

Au tout début, j »ai pas fait des trucs hyper évolués, j »ai juste essayé d »exécuter un script Lua tout banal. Fallait que je me fasse un peu au concept de programmer sur 2 langages en même temps et tout. Bref.
Donc après avoir fait le programme de test que j »ai écrit quelques lignes plus haut, qui évalue une chaîne écrite en Lua, et qui affiche via Lua un Hello World. Très puissant comme programme…
J »ai un peu corsé le jeu donc en mettant le script dans un fichier à part, et en rajoutant quelques trucs :

Fichier Lua :

fp = io.open(''tmpfile'', ''a+'')

msg = io.stdin:read()

for i = 0, 100 do
fp:write(msg.."\\n")
end

fp:close()

Fichier PHP :

<?php

$lua = new lua();
$lua->evaluatefile(''test.lua'');

Bon, c »est vrai que c »est pas beaucoup plus compliqué que l »autre script, mais ça m »a permis de tester un certain truc de Lua qui aurait pu ne pas marcher avec PHP, c »est à dire la lecture à partir de l »entrée clavier (c »est la fonction io.stdin:read() ). Concrètement, ce programme prend en entrée une chaîne de caractères et l »écrit 100 fois dans le fichier tmpfile. Enfin, rien de bien excitant. On peut donc passer à la suite, parce que ce que j »ai fait, on peut le faire avec un shell_exec( »lua test.lua »), et donc que pour l »instant ici cette extension est un peu inutile.

Le début des péripéties : Appel d »une fonction Lua depuis PHP

Ensuite, j »ai décidé de donner enfin une utilité à cette extension, en appelant des fonctions Lua depuis PHP. A première vue, ça peut avoir l »air de ne pas servir à grand chose, mais mine de rien c »est utile. Mais on le verra un peu plus bas. Pour l »instant, un exemple simple d »appel de fonction Lua depuis PHP, avec la méthode call_function() :

Fichier Lua :

function copyLines(msg)
fp = io.open(''tmpfile'', ''a+'')

for i = 0, 100 do
fp:write(msg.."\\n")
end

fp:close()
end

function flushTmpFile()
fp = io.open(''tmpfile'', ''w+'')
fp:close()
end

Fichier PHP :

 

<?php

$lua = new lua();
$lua->evaluatefile(''test.lua'');
$lua->call_function(''flushTmpFile'', array());
$lua->call_function(''copyLines'', array(''some creepy messages...''));

Donc avec ça, on commence à devenir utile, en appelant des fonctions Lua depuis PHP, ce qui nous permet de faire des trucs plutôt fun, comme par exemple de faire des programmes PHP scriptables en Lua. Ou encore de pouvoir recharger tout le contexte programmé en Lua sans avoir à redémarrer le programme (il suffit de détruire et de rdéclarer l »objet). Mais, bon, pour pouvoir profiter à fond de ça, il va encore falloir rajouter quelque chose.

On inverse et on recommence !

Après que j »aie réussi à lancer des fonctions Lua depuis PHP, j »ai décidé d »aller plus loin en voyant comment lancer des fonctions PHP depuis Lua. En réalité, c »est pas vraiment plus compliqué que de lancer des fonctions Lua depuis PHP.
Pour pouvoir le faire, il suffit de dire à l »objet Lua que la fonction « truc » en PHP correspond à la fonction « machin » en Lua. Et ensuite, il suffit juste d »appeler la fonction Lua, et l »extension fait le reste (les paramètres Lua sont les mêmes qu »en PHP).

Note : Cette fonctionnalité n »est pas disponible dans la version PECL de l »extension, elle a été ajoutée uniquement dans la version forkée. Une raison de plus de passer sur cette extension :)

Bon, au final, ça rend un truc comme ça :

Fichier Lua :

money = getMoney()
print(money.."\\n")
setMoney(125)
money = getMoney()
print(money.."\\n")

Fichier PHP :

<?php

$money = 0;

function moneySetValue($val)
{
 global $money;
 $money = $val;
}

function moneyGetValue()
{
 global $money;
 return $money;
}

$lua = new lua();

$lua->expose_function("setMoney", ''moneySetValue'');
$lua->expose_function("getMoney", ''moneyGetValue'');

$lua->evaluatefile(''test.lua'');

Et comme résultat, on le voit bien, il change la valeur de la variable PHP. C »est super.
Et après avoir vu tout ça, j »ai décidé de mettre tout ça en application dans un truc assez sympa.

Synthèse du tout : Système d »events Lua/PHP

Comme j »aime pas mal le concept de la programmation évènementielle, j »ai voulu faire un système de gestion d »events pour Lua, pour faire par exemple un système de plugins rechargeables dynamiquement. Un truc bien utile quoi.
Le tout consiste en une classe :

<?php
/* * Lua Events Class * * Copyright 2011 Yohann Lorant <yohann.lorant@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * */
class LuaEvent
{
private $_lua;
private $_events;

public function __construct($files)
{
$this->_files = $files;
$this->reload();
}

public function reload()
{
$this->flushLua();
$this->loadLua($this->_files);
$this->initLua();
}

public function initLua()
{
$this->_lua->call_function(''__init'', array());
}

public function loadLua($files)
{
foreach($files as $file)
{
if(is_file($file))
$this->_lua->evaluatefile($file);
}

return TRUE;
}

public function flushLua()
{
$this->_events = array();
$this->_lua = new lua();

$this->_lua->expose_function(''addEvent'', array($this, ''addEvent''));
$this->_lua->expose_function(''delEvent'', array($this, ''delEvent''));
}

public function addEvent($event, $callback)
{
if(!isset($this->_events[$event]))
$this->_events[$event] = array();

$this->_events[$event][] = $callback;

return TRUE;
}

public function delEvent($event, $callback)
{
if(!isset($this->_events[$event]))
return FALSE;

if(!$search = array_search($callback, $this->_events[$event]))
return FALSE;

unset($this->_events[$event][$search]);

if(empty($this->_events[$event]))
unset($this->_events[$event]);
}

public function execEvents($cmd)
{
if(isset($this->_events[$cmd[0]]))
{
foreach($this->_events[$cmd[0]] as $ev)
@$this->_lua->call_function($ev, array($cmd));
}
}
}

Et pour tester ça, j »ai utilisé un petit script PHP qui reproduit un shell et appelle les fonctions Lua associées aux events Lua déclarés dans la fonction __init() (en Lua) :

<?php
$files = array(''test.lua'');

$events = new LuaEvent($files);

$continue = 1;
while($continue)
{
 echo "> ";
 $cmd = trim(fgets(STDIN));
 $cmd = explode('' '', $cmd);
 $events->execEvents($cmd);
 if($cmd[0] == ''quit'')
 $continue = 0;
 elseif($cmd[0] == ''reload'')
 $events->reload();
 echo "\\n";
}

Et le fichier test.lua associé, qui contient nos commandes (bien sûr, comme les commandes sont complètement désolidarisées du PHP, vous pouvez écrire le vôtre avec vos propres commandes, ça marchera) :

function __init()
addEvent(''hello'', ''cmdHello'')
addEvent(''quit'', ''cmdQuit'')
 print(''Init done.''.."\\n")
end

function cmdHello(cmd)
 print(''Oh, Hello ''..cmd[1]..''. It\\''s been a loooong time...''.."\\n")
end

function cmdQuit(cmd)
 print(''You know what ? You have won. Just go.''.."\\n")
end

Et lorsque l »on exécute, oh magie !

[yohann@Scruffy:~/Programmes]$ php testlua.php
Init done.
> hello john
Oh, Hello john. It''s been a loooong time...
> reload
Init done.
> hello
I''ve been very very busy being dead... Since you murdered me.
> quit
You know what ? You have won. Just go.
[yohann@Scruffy:~/Programmes]$

Conclusion

Voilà, cet article (plutôt long) sur PHP et Lua arrive à sa fin. Grâce à ça, j »ai pu commencer à développer un MUD en tant que plugin de mon serveur IRC, qui utilise des scripts Lua pour gérer les events du jeu, ainsi que pour créer des nouvelles commandes et donc permettre au programmeur du MUD de customiser ton jeu sans toucher au moteur en PHP (mais bon, il manque encore plein de trucs, il est absolument pas utilisable en l »état).

Bref, avant de finir, je vais faire un petit HS en vous parlant d »un artiste que vous devriez écouter : c »est allemand, ça mixe de la musique 8-bit avec de la guitare, et ça s »appelle Pornophonique. Je vous conseille vraiment d »écouter, c »est un super groupe, et ils font de la bonne musique. En plus, c »est de la musique libre, donc gratuite à écouter. Que du bon quoi. Si vous voulez en savoir plus, rendez-vous sur leur site officiel.

A plus les gens.

Leave a Reply