Résumé
Ce document est l'objet d'un projet de fin d'année de Maîtrise Informatique 2000-2001 à l'UNSA. La finalité du sujet traité est, d'une part, d'évaluer les apports de la programmation réactive dans le développement d'un jeu, et d'autre part, d'estimer les avantages fournis par son implémentation Scheme. Ceci en utilisant le moteur réactif Senior, développé par J.Demaria à l'INRIA pour le compilateur Scheme Bigloo. Dans le cahier des charges, document conçut en début de projet, il avait été précisé que le jeu retenue était un jeu de combat en 3D. Mais, après mures réflexions, il s'est avéré qu'un tel type de jeu allait faire perdre trop de temps avec des détails informatiques trop éloignés du sujet. Il a donc été décidé d'opter pour un jeu de billard (toujours en 3D), plus simple visuellement, ce qui permettrait de passer plus de temps sur la partie réactive. L'élaboration de ce jeu à donné lieu, en parallèle, à l'ajout d'une couche multimédia à l'environnement de programmation Bigloo.
La programmation réactive est une alternative à la programmation par threads. Il existe une grande différence entre ces deux types d'approche car la programmation par thread ne permet pas à l'utilisateur de gérer le partage du temps utilisé par chaqu'un d'eux, contrairement aux systèmes réactifs qui de plus possèdent différentes propriétés:
ils font évoluer, sans utiliser de threads, un ensemble d'entités en concurrence.
ils possèdent une horloge interne sur laquelle sont synchronisées toutes les entités (notion d'instant commun).
ils permettent aux entités de communiquer grâce à des événements diffusés instantanément.
ils proposent des opérateurs de préemption sur les entités.
Il existe plusieurs langages, notamment Java, Scheme ou encore C, pour lesquels des bibliothèques qui implementent un noyau permettant d'utiliser la programmation réactive existent. Le département Mimosa de l'INRIA, en collaboration avec l'école des Mines et France Télécom, a développé un moteur réactif baptisé Junior (Boussinot, 2000). Ce moteur est actuellement porté en Scheme sous le nom de Senior (Demaria, 2001), pour le compilateur Bigloo. Ce projet étant réalisé en Scheme, la bibliothèque réactive ici utilisée est Senior. Cette librairie, encore en développement, fournit toutes les constructions indispensables à la programmation réactive. Elle possède en outre d'autre instructions permettant une intégration totale à la sémantique de Scheme, en terme de fonctionnalités et de syntaxe. En effet les instructions réactives comme if&, let&, letrec&, ou encore l'appel de fonctions réactive (instruction funcall&), permettent au programmeur Scheme de se familiariser immédiatement avec ce langage. Les différentes instructions utilisés ci-dessous font partie de l'API Senior.
Afin de coordonner ces entités ou instructions, le programmeur dispose d'une machine réactive. Deux instructions fondamentales sont liées à une machine réactive:
l'ajout d'instructions réactives
(add& ma-machine instr1 . L) |
le déclenchement de l'exécution d'un instant
(react& ma-machine) |
L'insertion de cette fonction dans une boucle permettra donc d'effectuer le traitement du programme réactif (ensemble d'instructions réactives) qui lui aura préalablement été ajouté. La machine réactive gère donc l'exécution et la concurrence de toutes les instructions qui lui ont été rajoutée. Ainsi, par opposition aux threads, la programmation réactive permet la gestion totale des instructions à effectuer à chaque instant.
Il existe deux groupes d'instructions réactives bien distincts:
les instructions liées à la communication et à l'organisation de la concurrence entre les instructions.
la mise en parallèle d'instructions
(par& (atom& (print "instr1"))
(atom& (print "instr2"))) |
Cette instruction permet la mise en parallèle d'instructions réactives, ce qui rappèle fortement le mécanisme de thread.
![]() | l'instruction atom& permet l'insertion de code Scheme traditionnel dans du code réactif. |
la mise en séquence d'instructions
(seq& (atom& (print "instr1"))
(atom& (print "instr2"))) |
Cette instruction, par opposition à l'instruction par& permet la mise en séquence d'instructions réactive.
la communication et la synchronisation entre les instructions réactives
(par& (seq& (atom& (print "instr1"))
(generate& 'print-1-done))
(seq& (await& 'print-1-done)
(atom& (print "terminée")))) |
L'instruction await& permet l'attente d'un événement réactif, alors que l'instruction generate& insere un événement réactif dans la machine. Il est ainsi possible de gérer la synchronisation entre les instructions. D'autre construction comme scan& permettent en plus d'établir une communication entre les instructions. En effet scan& permet non seulement d'attendre un événement réactif, mais aussi de récupérer une valeur associée à l'événement. L'instruction generate& permet, dans un argument optionnel, d'associer cette valeur. A la différence de l'instruction await&, l'instruction scan& exécute son code, pour chacun des événements présent au cours de l'instant.
(par& (scan& 'print-event (print (the-values&)))
(generate& 'print-event 1)
(generate& 'print-event 2)
(generate& 'another-event 3)) |
Le résultat de cette instruction sera
bash$> 1
bash$> 2
soit l'inverse...
(the-values&) est donc l'objet Scheme associé à l'événement lors du generate&. Il existe une variante de l'instruction generate&, qui permet, depuis l'extérieur d'une instruction réactive d'insérer un événement réactif dans la machine. C'est l'instruction generate-in-the-machine&.
les instructions permettant le contrôle de boucle et de condition
la forme if réactive
(loop&
(if& 'clicked
(atom& (print "clicked"))
(atom& (print "not clicked")))) |
Cette forme permet le traitement d'un if-then-else classique entre instructions réactives.
la structure de boucle de base
La construction loop& permet de boucler sur un ensemble d'instructions réactives. Par exemple l'instruction: (loop& (atom& (print "toto"))) ne se terminera jamais et provoquera une boucle infinie. L'effet souhaité serait qu'à chaque fois que l'on fait réagir la machine, alors il s'affiche "toto" à l'écran. Afin d'obtenir ce résultat, il faut utiliser l'instruction stop&. Elle a pour effet de stopper l'instruction pour l'instant en cours, et de reprendre son exécution à l'instant suivant. Ainsi le code suivant à bien l'effet souhaité
(loop& (atom& (print "toto"))
(stop&)) |
![]() | ici il n'y a pas besoin de mettre en séquence les instructions atom& et stop&, car la forme loop& effectue cette mise en séquence par défaut. |
la forme until& permet l'exécution d'une instruction donnée jusqu'à ce qu'une condition réactive soit vérifiés. Un exemple simple permet d'illustrer cette forme
(until& 'clicked
(seq& (atom& (print "begin"))
(loop& (atom& (print "wait click"))))
(atom& (print "click appears"))) |
Le résultat de l'exécution de cette instruction sera
bash$> begin
bash$> wait click
bash$> ...
bash$> wait click
bash$> click appears
Ici, l'événement 'clicked sera par exemple généré depuis l'extérieur de la machine réactive de la manière suivante: (generate-in-the-machine la-machine 'clicked) Ainsi, lors du prochain react&, l'événement 'clicked sera présent au cours de l'instant, et l'instruction until& terminera son exécution.
| Suivant | ||
| Analyse des besoins du projet |