J'ai vu quelques mentions de cela sur Stack Overflow, mais regarder Wikipédia (la page pertinente a depuis été supprimée) et une démo de dialogue dynamique MFC n'ont rien fait pour m'éclairer. Quelqu'un peut-il expliquer cela? Apprendre un concept fondamentalement différent sonne bien.
Sur la base des réponses: je pense que je ressens mieux. Je suppose que je n'ai tout simplement pas regardé le code source suffisamment attentivement la première fois. J'ai des sentiments mitigés sur l'exécution différentielle à ce stade. D'une part, cela peut faciliter considérablement certaines tâches. D'un autre côté, le faire fonctionner (c'est-à-dire le configurer dans la langue de votre choix) n'est pas facile (je suis sûr que ce le serait si je le comprenais mieux) ... bien que je suppose que la boîte à outils pour cela ne doit être effectuée qu'une seule fois, puis développée si nécessaire. Je pense que pour vraiment le comprendre, je devrai probablement essayer de l'implémenter dans une autre langue.
Oh, Brian, j'aurais aimé avoir vu ta question plus tôt. Comme c'est à peu près mon "invention" (pour le meilleur ou pour le pire), je pourrais peut-être aider.
Inséré: L'explication la plus courte possible que je puisse faire est que si l'exécution normale est comme lancer une balle en l'air et l'attraper, alors l'exécution différentielle est comme jongler.
L'explication de @ windfinder est différente de la mienne, et c'est OK. Cette technique n'est pas facile à comprendre, et il m'a fallu environ 20 ans (de temps en temps) pour trouver des explications qui fonctionnent. Permettez-moi de lui donner un autre coup ici:
Nous comprenons tous l'idée simple d'un ordinateur qui progresse dans un programme, prend des branches conditionnelles basées sur les données d'entrée et fait des choses. (Supposons que nous ne traitions qu'avec du code structuré simple sans retour ni retour.) Ce code contient des séquences d'instructions, des conditions structurées de base, des boucles simples et des appels de sous-programme. (Oubliez les fonctions renvoyant des valeurs pour l'instant.)
Imaginez maintenant deux ordinateurs exécutant ce même code en phase de verrouillage l'un avec l'autre, et capables de comparer les notes. L'ordinateur 1 s'exécute avec les données d'entrée A et l'ordinateur 2 s'exécute avec les données d'entrée B. Ils s'exécutent pas à pas côte à côte. S'ils arrivent à une déclaration conditionnelle comme IF (test) .... ENDIF, et s'ils ont une différence d'opinion sur la véracité du test, alors celui qui dit le test si faux saute à ENDIF et attend autour sa sœur à rattraper. (C'est pourquoi le code est structuré, nous savons donc que la sœur finira par arriver à ENDIF.)
Étant donné que les deux ordinateurs peuvent communiquer entre eux, ils peuvent comparer les notes et donner une explication détaillée de la différence entre les deux ensembles de données d'entrée et les historiques d'exécution.
Bien sûr, en exécution différentielle (DE), cela se fait avec un ordinateur, en simulant deux.
MAINTENANT, supposons que vous ne disposiez que d'un seul ensemble de données d'entrée, mais vous voulez voir comment elles ont changé de l'heure 1 à l'heure 2. Supposons que le programme que vous exécutez est un sérialiseur/désérialiseur. Lorsque vous exécutez, vous sérialisez (écrivez) les données actuelles et désérialisez (lisez) les données passées (qui ont été écrites la dernière fois que vous avez fait cela). Maintenant, vous pouvez facilement voir quelles sont les différences entre ce que les données étaient la dernière fois et ce qu'elles sont cette fois.
Le fichier dans lequel vous écrivez et l'ancien fichier que vous lisez ensemble constituent une file d'attente ou FIFO (premier entré, premier sorti), mais ce n'est pas un concept très profond.
Cela m'est venu à l'esprit alors que je travaillais sur un projet graphique, où l'utilisateur pouvait construire de petites routines de processeur d'affichage appelées "symboles" qui pouvaient être assemblées en de plus grandes routines pour peindre des choses comme des diagrammes de tuyaux, réservoirs, vannes, des trucs comme ça. Nous voulions que les diagrammes soient "dynamiques" dans le sens où ils pourraient se mettre à jour progressivement sans avoir à redessiner l'intégralité du diagramme. (Le matériel était lent par rapport aux normes d'aujourd'hui.) J'ai réalisé que (par exemple) une routine pour dessiner une barre d'un graphique à barres pouvait se souvenir de son ancienne hauteur et se mettre à jour progressivement.
Cela ressemble à OOP, non? Cependant, plutôt que de "créer" un "objet", je pourrais profiter de la prévisibilité de la séquence d'exécution de la procédure de diagramme. Je pourrais écrire la hauteur de la barre dans un flux d'octets séquentiel. Ensuite, pour mettre à jour l'image, je pourrais simplement exécuter la procédure dans un mode où il lit séquentiellement ses anciens paramètres pendant qu'il écrit les nouveaux paramètres afin d'être prêt pour la prochaine passe de mise à jour.
Cela semble stupidement évident et semble rompre dès que la procédure contient un conditionnel, car alors le nouveau flux et l'ancien flux seraient désynchronisés. Mais il m'est alors apparu que s'ils sérialisaient également la valeur booléenne du test conditionnel, ils pourraient revenir en synchronisation. Il m'a fallu un certain temps pour me convaincre, puis prouver, que cela fonctionnerait toujours, à condition qu'une règle simple (la "règle du mode d'effacement") soit suivie.
Le résultat net est que l'utilisateur pourrait concevoir ces "symboles dynamiques" et les assembler en diagrammes plus grands, sans jamais avoir à se soucier de la façon dont ils se mettraient à jour dynamiquement, quelle que soit la complexité ou la variation structurelle de l'affichage.
À cette époque, je devais m'inquiéter des interférences entre les objets visuels, afin que l'effacement d'un n'endommage pas les autres. Cependant, maintenant j'utilise la technique avec les contrôles Windows et je laisse Windows s'occuper des problèmes de rendu.
Alors qu'est-ce que cela permet? Cela signifie que je peux créer une boîte de dialogue en écrivant une procédure pour peindre les contrôles, et je n'ai pas à me soucier de me souvenir des objets de contrôle ou de les mettre à jour progressivement, ou de les faire apparaître/disparaître/se déplacer selon les conditions. Le résultat est un code source de boîte de dialogue beaucoup plus petit et plus simple, d'environ un ordre de grandeur, et des choses comme la disposition dynamique ou la modification du nombre de contrôles ou le fait d'avoir des tableaux ou des grilles de contrôles sont triviaux. De plus, un contrôle tel qu'un champ d'édition peut être lié de manière triviale aux données d'application qu'il modifie, et il sera toujours prouvablement correct, et je n'ai jamais à gérer ses événements. La mise dans un champ d'édition pour une variable de chaîne d'application est une édition d'une ligne.
Ce que j'ai trouvé le plus difficile à expliquer, c'est qu'il faut penser différemment les logiciels. Les programmeurs sont si fermement attachés à la vue action-objet du logiciel qu'ils veulent savoir quels sont les objets, quelles sont les classes, comment "construisent-ils" l'affichage, et comment gèrent-ils les événements, qu'il en faut une cerise bombe pour les faire exploser. Ce que j'essaie de transmettre, c'est que ce qui compte vraiment, c'est que devez-vous dire? Imaginez que vous construisez un langage spécifique au domaine (DSL) où tout ce que vous devez faire est de lui dire "I voulez éditer la variable A ici, la variable B là-bas et la variable C là-bas "et elle s'en occuperait comme par magie. Par exemple, dans Win32, il existe ce "langage de ressources" pour définir les boîtes de dialogue. C'est une DSL parfaitement bonne, sauf qu'elle ne va pas assez loin. Il ne "vit" pas dans le langage procédural principal, ne gère pas les événements pour vous, ni ne contient de boucles/conditions/sous-programmes. Mais cela signifie bien, et Dynamic Dialogs essaie de terminer le travail.
Ainsi, les différents modes de pensée sont: pour écrire un programme, vous devez d'abord trouver (ou inventer) une DSL appropriée, et coder autant de votre programme en ce que possible. Soit il traite tous les objets et actions qui n'existent que pour l'implémentation.
Si vous voulez vraiment comprendre l'exécution différentielle et l'utiliser, il y a quelques problèmes délicats qui peuvent vous trébucher. Je l'ai codé une fois dans LISP macros, où ces bits délicats pourraient être manipulés pour vous, mais dans les langages "normaux", il nécessite une certaine discipline de programmeur pour éviter les pièges .
Désolé d'être si long. Si je n'ai pas de sens, je vous serais reconnaissant de le signaler et je peux essayer de le réparer.
Ajoutée:
Dans Java Swing , il existe un exemple de programme appelé TextInputDemo. Il s'agit d'un dialogue statique, prenant 270 lignes (sans compter la liste des 50 états). Dans Dynamic Dialogs (dans MFC), il s'agit d'environ 60 lignes:
#define NSTATE (sizeof(states)/sizeof(states[0]))
CString sStreet;
CString sCity;
int iState;
CString sZip;
CString sWholeAddress;
void SetAddress(){
CString sTemp = states[iState];
int len = sTemp.GetLength();
sWholeAddress.Format("%s\r\n%s %s %s", sStreet, sCity, sTemp.Mid(len-3, 2), sZip);
}
void ClearAddress(){
sWholeAddress = sStreet = sCity = sZip = "";
}
void CDDDemoDlg::deContentsTextInputDemo(){
int gy0 = P(gy);
P(www = Width()*2/3);
deStartHorizontal();
deStatic(100, 20, "Street Address:");
deEdit(www - 100, 20, &sStreet);
deEndHorizontal(20);
deStartHorizontal();
deStatic(100, 20, "City:");
deEdit(www - 100, 20, &sCity);
deEndHorizontal(20);
deStartHorizontal();
deStatic(100, 20, "State:");
deStatic(www - 100 - 20 - 20, 20, states[iState]);
if (deButton(20, 20, "<")){
iState = (iState+NSTATE - 1) % NSTATE;
DD_THROW;
}
if (deButton(20, 20, ">")){
iState = (iState+NSTATE + 1) % NSTATE;
DD_THROW;
}
deEndHorizontal(20);
deStartHorizontal();
deStatic(100, 20, "Zip:");
deEdit(www - 100, 20, &sZip);
deEndHorizontal(20);
deStartHorizontal();
P(gx += 100);
if (deButton((www-100)/2, 20, "Set Address")){
SetAddress();
DD_THROW;
}
if (deButton((www-100)/2, 20, "Clear Address")){
ClearAddress();
DD_THROW;
}
deEndHorizontal(20);
P((gx = www, gy = gy0));
deStatic(P(Width() - gx), 20*5, (sWholeAddress != "" ? sWholeAddress : "No address set."));
}
Ajoutée:
Voici un exemple de code pour modifier un tableau de patients hospitalisés dans environ 40 lignes de code. Les lignes 1 à 6 définissent la "base de données". Les lignes 10 à 23 définissent le contenu global de l'interface utilisateur. Les lignes 30 à 48 définissent les commandes permettant de modifier le dossier d'un seul patient. Notez que la forme du programme ne tient pratiquement aucun compte des événements dans le temps, comme si tout ce qu'il avait à faire était de créer l'affichage une fois. Ensuite, si des sujets sont ajoutés ou supprimés ou si d'autres changements structurels ont lieu, il est simplement réexécuté, comme s'il était recréé à partir de zéro, sauf que DE provoque la mise à jour incrémentielle à la place. L'avantage est que vous, le programmeur, n'avez pas à prêter attention ni à écrire de code pour effectuer les mises à jour incrémentielles de l'interface utilisateur, et elles sont garanties correctes. Il peut sembler que cette réexécution serait un problème de performances, mais ce n'est pas le cas, car la mise à jour des contrôles qui n'ont pas besoin d'être modifiés prend de l'ordre de dizaines de nanosecondes.
1 class Patient {public:
2 String name;
3 double age;
4 bool smoker; // smoker only relevant if age >= 50
5 };
6 vector< Patient* > patients;
10 void deContents(){ int i;
11 // First, have a label
12 deLabel(200, 20, “Patient name, age, smoker:”);
13 // For each patient, have a row of controls
14 FOR(i=0, i<patients.Count(), i++)
15 deEditOnePatient( P( patients[i] ) );
16 END
17 // Have a button to add a patient
18 if (deButton(50, 20, “Add”)){
19 // When the button is clicked add the patient
20 patients.Add(new Patient);
21 DD_THROW;
22 }
23 }
30 void deEditOnePatient(Patient* p){
31 // Determine field widths
32 int w = (Width()-50)/3;
33 // Controls are laid out horizontally
34 deStartHorizontal();
35 // Have a button to remove this patient
36 if (deButton(50, 20, “Remove”)){
37 patients.Remove(p);
37 DD_THROW;
39 }
40 // Edit fields for name and age
41 deEdit(w, 20, P(&p->name));
42 deEdit(w, 20, P(&p->age));
43 // If age >= 50 have a checkbox for smoker boolean
44 IF(p->age >= 50)
45 deCheckBox(w, 20, “Smoker?”, P(&p->smoker));
46 END
47 deEndHorizontal(20);
48 }
Ajouté: Brian a posé une bonne question, et je pensais que la réponse appartenait au texte principal ici:
@Mike: Je ne sais pas exactement ce que fait réellement l'instruction "if (deButton (50, 20," Add ")) {". À quoi sert la fonction deButton? De plus, vos boucles FOR/END utilisent-elles une sorte de macro ou quelque chose? - Brian.
@Brian: Oui, les instructions FOR/END et IF sont des macros. Le projet SourceForge a une implémentation complète. deButton maintient un contrôle de bouton. Lorsqu'une action d'entrée utilisateur a lieu, le code est exécuté en mode "événement de contrôle", dans lequel deButton détecte qu'il a été pressé et signifie qu'il a été pressé en retournant TRUE. Ainsi, le "if (deButton (...)) {... code d'action ...} est un moyen d'attacher un code d'action au bouton, sans avoir à créer une fermeture ou à écrire un gestionnaire d'événements. Le DD_THROW est un manière de terminer le passage lorsque l'action est effectuée car l'action peut avoir modifié les données d'application, il n'est donc pas valide de continuer le passage de "l'événement de contrôle" à travers la routine. Si vous comparez cela à l'écriture de gestionnaires d'événements, cela vous évite de les écrire, et il vous permet d'avoir un certain nombre de contrôles.
Ajouté: Désolé, je devrais expliquer ce que je veux dire par le mot "maintient". Lorsque la procédure est exécutée pour la première fois (en mode SHOW), deButton crée un contrôle bouton et se souvient de son identifiant dans le FIFO. Lors des passes suivantes (en mode UPDATE), deButton obtient l'identifiant du FIFO, le modifie si nécessaire et le remet dans le FIFO. En mode ERASE, il le lit à partir du FIFO, le détruit et ne le remet pas, ce qui le "récupère". Ainsi, l'appel deButton gère toute la durée de vie du contrôle, en le maintenant en accord avec les données d'application, c'est pourquoi je dis qu'il le "maintient".
Le quatrième mode est ÉVÉNEMENT (ou CONTRÔLE). Lorsque l'utilisateur tape un caractère ou clique sur un bouton, cet événement est capturé et enregistré, puis la procédure deContents est exécutée en mode ÉVÉNEMENT. deButton obtient l'id de son contrôle bouton du FIFO et demande si c'est le contrôle qui a été cliqué. S'il l'était, il retourne TRUE pour que le code d'action puisse être exécuté. Sinon, il renvoie simplement FALSE. D'un autre côté, deEdit(..., &myStringVar)
détecte si l'événement lui était destiné et, le cas échéant, le transmet au contrôle d'édition, puis copie le contenu du contrôle d'édition dans myStringVar. Entre cela et traitement UPDATE normal, myStringVar est toujours égal au contenu du contrôle d'édition. C'est ainsi que la "liaison" est effectuée. La même idée s'applique aux barres de défilement, aux zones de liste, aux zones de liste déroulante et à tout type de contrôle qui vous permet de modifier les données d'application.
Voici un lien vers mon édition Wikipedia: http://en.wikipedia.org/wiki/User:MikeDunlavey/Difex_Article
L'exécution différentielle est une stratégie pour modifier le flux de votre code en fonction d'événements externes. Cela se fait généralement en manipulant une structure de données quelconque pour faire la chronique des changements. Ceci est principalement utilisé dans les interfaces utilisateur graphiques, mais est également utilisé pour des choses comme la sérialisation, où vous fusionnez les modifications dans un "état" existant.
Le flux de base est le suivant:
Start loop:
for each element in the datastructure:
if element has changed from oldDatastructure:
copy element from datastructure to oldDatastructure
execute corresponding subroutine (display the new button in your GUI, for example)
End loop:
Allow the states of the datastructure to change (such as having the user do some input in the GUI)
Les avantages en sont quelques-uns. Premièrement, c'est la séparation de l'exécution de vos modifications et la manipulation réelle des données de support. Ce qui est bien pour plusieurs processeurs. Deuxièmement, il fournit une méthode à faible bande passante pour communiquer les changements dans votre programme.
Pensez au fonctionnement d'un moniteur:
Il est mis à jour à 60 Hz - 60 fois par seconde. Flicker scintillement scintillement 60 fois, mais vos yeux sont lents et ne peuvent pas vraiment dire. Le moniteur affiche tout ce qui se trouve dans le tampon de sortie; il fait simplement glisser ces données tous les 1/60e de seconde, peu importe ce que vous faites.
Maintenant, pourquoi voudriez-vous que votre programme mette à jour le tampon entier 60 fois par seconde si l'image ne change pas si souvent? Que faire si vous ne modifiez qu'un pixel de l'image, devez-vous réécrire la totalité du tampon?
Il s'agit d'une abstraction de l'idée de base: vous souhaitez modifier le tampon de sortie en fonction des informations que vous souhaitez afficher à l'écran. Vous voulez économiser autant de temps CPU et de temps d'écriture de tampon que possible, afin de ne pas modifier les parties du tampon qui n'ont pas besoin d'être modifiées pour la prochaine capture d'écran.
Le moniteur est distinct de votre ordinateur et de la logique (programmes). Il lit à partir du tampon de sortie à n'importe quel rythme, il met à jour l'écran. Nous voulons que notre ordinateur cesse de se synchroniser et de se redessiner inutilement. Nous pouvons résoudre ce problème en modifiant notre façon de travailler avec le tampon, ce qui peut se faire de différentes manières. Sa technique implémente une file d'attente FIFO en retard - elle contient ce que nous venons d'envoyer au tampon. La file d'attente retardée FIFO ne contient pas de données de pixels, il contient des "primitives de forme" (qui peuvent être des pixels dans votre application, mais aussi des lignes, des rectangles, des choses faciles à dessiner car ce ne sont que des formes, aucune donnée inutile n'est autorisée).
Vous voulez donc dessiner/effacer des choses de l'écran? Aucun problème. Sur la base du contenu de la file d'attente FIFO je sais à quoi ressemble le moniteur en ce moment. Je compare la sortie souhaitée (pour effacer ou dessiner de nouvelles primitives) avec la FIFO file d'attente et ne changer que les valeurs qui doivent être modifiées/mises à jour. C'est l'étape qui lui donne le nom d'évaluation différentielle.
Deux façons distinctes dans lequel j'apprécie ceci:
The First: Mike Dunlavey utilise une extension d'instruction conditionnelle. La file d'attente FIFO contient de nombreuses informations ("l'état précédent" ou les informations actuelles sur le moniteur ou le dispositif d'interrogation basé sur le temps). Tout ce que vous avez à ajouter est l'état que vous souhaitez apparaîtra à l'écran ensuite.
Un bit conditionnel est ajouté à chaque emplacement pouvant contenir une primitive dans la file d'attente FIFO.
0 means erase
1 means draw
Cependant, nous avons l'état précédent:
Was 0, now 0: don't do anything;
Was 0, now 1: add it to the buffer (draw it);
Was 1, now 1: don't do anything;
Was 1, now 0: erase it from the buffer (erase it from the screen);
C'est élégant, car lorsque vous mettez à jour quelque chose, vous n'avez vraiment besoin que de savoir quelles primitives vous voulez dessiner à l'écran - cette comparaison va savoir si elle doit effacer une primitive ou l'ajouter/la conserver dans/dans le tampon.
The Second: Ceci n'est qu'un exemple, et je pense que ce à quoi Mike veut vraiment en venir est quelque chose qui devrait être fondamental dans la conception de tous les projets: Réduisez la complexité (informatique) de la conception en écrivant le plus des opérations de calcul intensif comme de la nourriture informatique ou aussi proche que possible. Respectez le timing naturel des appareils.
Une méthode de redessinage pour dessiner tout l'écran est incroyablement coûteuse, et il existe d'autres applications où ces informations sont incroyablement précieuses.
Nous ne "déplaçons" jamais des objets sur l'écran. Le "déplacement" est une opération coûteuse si nous voulons imiter l'action physique du "déplacement" lorsque nous concevons du code pour quelque chose comme un écran d'ordinateur. Au lieu de cela, les objets clignotent simplement sur le moniteur. Chaque fois qu'un objet se déplace, c'est maintenant un nouvel ensemble de primitives et l'ancien ensemble de primitives clignote.
Chaque fois que le moniteur tire du tampon, nous avons des entrées qui ressemblent à
Draw bit primitive_description
0 Rect(0,0,5,5);
1 Circ(0,0,2);
1 Line(0,1,2,5);
Un objet n'interagit jamais avec l'écran (ou le dispositif d'interrogation sensible au facteur temps). Nous pouvons le gérer plus intelligemment qu'un objet ne le fera lorsqu'il demandera avidement de mettre à jour tout l'écran juste pour montrer un changement spécifique à lui-même.
Disons que nous avons une liste de toutes les primitives graphiques possibles que notre programme est capable de générer, et que nous lions chaque primitive à un ensemble d'instructions conditionnelles
if (iWantGreenCircle && iWantBigCircle && iWantOutlineOnMyCircle) ...
Bien sûr, il s'agit d'une abstraction et, vraiment, l'ensemble des conditions qui représente une primitive particulière activée/désactivée pourrait être grand (peut-être des centaines de drapeaux qui doivent tous être évalués comme vrais).
Si nous exécutons le programme, nous pouvons dessiner à l'écran essentiellement au même rythme auquel nous pouvons évaluer toutes ces conditions. (Pire cas: combien de temps faut-il pour évaluer le plus grand ensemble d'instructions conditionnelles.)
Maintenant, pour n'importe quel état du programme, nous pouvons simplement évaluer toutes les conditions et les afficher sur l'écran rapide comme l'éclair! (Nous connaissons nos primitives de forme et leurs instructions if dépendantes.)
Ce serait comme acheter un jeu graphiquement intense. Ce n'est qu'au lieu de l'installer sur votre disque dur et de l'exécuter via votre processeur que vous achetez une toute nouvelle carte qui contient l'intégralité du jeu et prend en entrée: souris, clavier et prend en sortie: moniteur. Évaluation conditionnelle incroyablement condensée (comme la forme la plus fondamentale d'un conditionnel est les portes logiques sur les cartes de circuits imprimés). Ce serait, bien sûr, très réactif, mais il n'offre presque aucun support pour corriger les bugs, car la conception de la carte entière change lorsque vous effectuez un petit changement de conception (car la "conception" est si éloignée de la nature de la carte de circuit imprimé). ). Au détriment de la flexibilité et de la clarté dans la façon dont nous représentons les données en interne, nous avons gagné en "réactivité" importante parce que nous ne faisons plus de "réflexion" sur l'ordinateur; tout est juste réflexe pour la carte de circuit imprimé en fonction des entrées.
La leçon, si je comprends bien, est de diviser le travail de telle sorte que vous donnez à chaque partie du système (pas nécessairement seulement un ordinateur et un moniteur) quelque chose qu'il peut bien faire. La "pensée informatique" peut se faire en termes de concepts comme les objets ... Le cerveau de l'ordinateur essaiera volontiers de réfléchir à tout cela pour vous, mais vous pouvez simplifier la tâche beaucoup si vous êtes en mesure de laisser l'ordinateur réfléchir. termes de data_update et conditional_evals. Nos abstractions humaines de concepts en code sont idéalistes et, dans le cas de programmes internes, dessinent des méthodes un peu trop idéalistes. Lorsque tout ce que vous voulez est un résultat (tableau de pixels avec des valeurs de couleur correctes) et que vous avez une machine qui peut facilement cracher un tableau grand tous les 1/60ème de seconde, essayez d'éliminer autant de pensée fleurie que possible du cerveau de l'ordinateur afin que vous puissiez vous concentrer sur ce que vous voulez vraiment: synchroniser vos mises à jour graphiques avec vos entrées (rapides) et le comportement naturel du moniteur.
Comment cette carte est-elle associée à d'autres applications? J'aimerais entendre d'autres exemples, mais je suis sûr qu'il y en a beaucoup. Je pense que tout ce qui fournit une "fenêtre" en temps réel sur l'état de vos informations (état variable ou quelque chose comme une base de données ... un moniteur n'est qu'une fenêtre sur votre tampon d'affichage) peut bénéficier de ces informations.
Je trouve ce concept très similaire aux machines d'état de l'électronique numérique classique. Surtout ceux qui se souviennent de leur sortie précédente.
Une machine dont la prochaine sortie dépend de l'entrée actuelle et de la sortie précédente selon (VOTRE CODE ICI). Cette entrée actuelle n'est rien d'autre que la sortie précédente + (USER, INTERACT HERE).
Remplissez une surface avec de telles machines et elle sera interactive pour l'utilisateur et représentera en même temps une couche de données modifiables. Mais à ce stade, il restera stupide, reflétant simplement l'interaction de l'utilisateur avec les données sous-jacentes.
Ensuite, interconnectez les machines sur votre surface, laissez-les partager des notes, selon (VOTRE CODE ICI), et maintenant nous le rendons intelligent. Il deviendra un système informatique interactif.
Il vous suffit donc de fournir votre logique à deux endroits dans le modèle ci-dessus; le reste est pris en charge par la conception de la machine elle-même. Voilà à quoi ça sert.