Système de formules
Vue d’ensemble
Le système de formules permet à un étudiant de calculer automatiquement la valeur d'une cellule à partir d'autres cellules numériques d'un tableau de module. Une formule est une liste de références signées : chaque référence est ajoutée ou soustraite du total.
Le calcul est effectué dans Unity, tandis que la définition de la formule est persistée côté backend. La valeur calculée reste stockée dans DataDrivenInputData.value; la structure de la formule est stockée séparément dans DataDrivenInputData.formula.
1. Architecture générale
flowchart LR
USER[Étudiant] --> EDITOR[DataDrivenTableEditorControl]
EDITOR --> TABLE[DataDrivenTableControl]
TABLE --> CELL[InputCellControl]
TABLE --> FORMULA[DataDrivenInputFormulaData]
FORMULA --> REF[DataDrivenInputFormulaReferenceData]
TABLE --> CALC[Calcul + graphe de dépendances]
CALC --> VALUE[DataDrivenInputData.value]
VALUE --> CELL
TABLE --> CTRL[TeamByModuleController]
CTRL --> CMD[save_student_module_input_formula]
CMD --> BACK[Backend Node.js / BrainCloud]
BACK --> STORE[(teamsModules)]
BACK --> EVENT[team_module_input_formula_changed]
EVENT --> CTRL
CTRL --> TABLE
2. Modèle de données
Une cellule peut contenir une formule optionnelle. La formule contient une liste de références; chaque référence identifie complètement une cellule et contient son signe.
classDiagram
class DataDrivenInputData {
string value
Dictionary options
DataDrivenInputFormulaData formula
}
class DataDrivenInputFormulaData {
List references
Serialize()
Deserialize()
Clone()
}
class DataDrivenInputFormulaReferenceData {
string pageUid
string tableUid
string sectionUid
string rowUid
string inputUid
int sign
}
DataDrivenInputData --> DataDrivenInputFormulaData
DataDrivenInputFormulaData --> "0..*" DataDrivenInputFormulaReferenceData
Adresse complète d’une référence
Un inputUid seul n'est pas suffisant, puisque le même input peut exister dans plusieurs sections ou rangées. Une référence conserve donc pageUid, tableUid, sectionUid, rowUid, inputUid et sign. Le signe vaut 1 pour additionner et -1 pour soustraire.
3. Création d’une formule dans l’interface
sequenceDiagram
actor U as Étudiant
participant E as DataDrivenTableEditorControl
participant T as DataDrivenTableControl
participant C as InputCellControl
U->>C: Sélectionne la cellule résultat
U->>E: Active le mode formule
E->>T: onFormulaModeValueChanged(true)
T->>T: signe initial = PLUS
T->>C: met les cellules compatibles en mode sélection
U->>E: choisit + ou -
E->>T: onFormulaReferenceSignChanged(sign)
U->>C: clique une cellule référence
C->>T: onSelected(inputUid)
T->>T: SelectFormulaReference(...)
T->>T: ajoute / change / retire la référence
T->>C: outline vert (+) ou rouge (-)
U->>E: désactive le mode formule / Enter
E->>T: onFormulaModeValueChanged(false)
T->>T: sauvegarde la formule
Comportement lors d’un clic sur une référence
- si la référence n'existe pas, elle est ajoutée;
- si elle existe avec l'autre signe, son signe est remplacé;
- si elle existe déjà avec le même signe, elle est retirée;
- la cellule résultat ne peut pas se référencer elle-même;
- seules les cellules numériques compatibles sont sélectionnables en mode formule;
- une cellule qui dépend directement de la cellule résultat est rendue non sélectionnable afin d'éviter une dépendance circulaire directe.
Les références positives sont affichées avec un contour vert et les références négatives avec un contour rouge via InputCellControl.SetFormulaOutline().
4. Calcul d’une formule
flowchart TD
F[DataDrivenInputFormulaData] --> LOOP[Pour chaque référence]
LOOP --> GET[TryGetInputData]
GET --> PARSE[Parse inputData.value en double]
PARSE -->|échec| ZERO[Valeur = 0]
PARSE --> MOD{value_modifier présent ?}
ZERO --> MOD
MOD -->|Oui| APPLY[referenceValue *= valueModifier]
MOD -->|Non| SIGN
APPLY --> SIGN[Appliquer sign +1 / -1]
SIGN --> SUM[Ajouter au total]
SUM --> LOOP
LOOP -->|fin| VALUE[Écrire le résultat dans inputData.value]
VALUE --> UI[Mettre à jour InputCellControl]
VALUE --> TOTAL[Mettre à jour total row / journal si applicable]
Les valeurs impossibles à parser sont traitées comme 0. Si l'option serveur value_modifier est présente sur une cellule référencée, elle est appliquée avant le signe de la référence.
5. Propagation des dépendances
Le système construit un index inverse des dépendances dans _formulaListenersByReference. La clé d'une cellule est construite sous la forme sectionUid|rowUid|inputUid.
flowchart LR
A[Cellule A change] --> LOOKUP[_formulaListenersByReference A]
LOOKUP --> B[Formule B dépend de A]
LOOKUP --> C[Formule C dépend de A]
B --> RB[RecalculateFormula B]
C --> RC[RecalculateFormula C]
RB -->|valeur changée| NEXTB[Notifier les formules dépendantes de B]
RC -->|valeur changée| NEXTC[Notifier les formules dépendantes de C]
NEXTB --> D[Formule D]
NEXTC --> E[Formule E]
La propagation est récursive : lorsqu'une formule change de valeur, les formules qui dépendent d'elle sont recalculées à leur tour. Un HashSet de clés déjà visitées empêche la propagation de boucler indéfiniment pendant un même cycle de notification.
Initialisation
Lors du chargement ou d'un refresh important, InitializeFormulasByReference() reconstruit entièrement l'index inverse, puis RefreshFormulasByReferenceAndRecalculateAll() recalcule toutes les cellules possédant une formule.
6. Sauvegarde et synchronisation backend
sequenceDiagram
participant T as DataDrivenTableControl
participant C as TeamByModuleController
participant B as Backend Node.js
participant DB as teamsModules
participant M as Messages équipe/prof
T->>C: SaveTeamModuleInputFormula(..., formula)
C->>B: custom command save_student_module_input_formula
B->>B: localise page/table/section/row/input
alt formule avec références
B->>DB: inputData.formula = formula
else formule vide
B->>DB: delete inputData.formula
end
B->>B: updateFormulaReferences(...)
B->>DB: saveDocumentData
B->>M: team_module_input_formula_changed
M-->>C: message temps réel
C-->>T: OnTeamModuleInputFormulaChanged(...)
T->>T: reconstruit listeners + recalcule
Le backend persiste uniquement la définition de la formule. Si la liste de références est vide, la propriété formula est supprimée de l'input.
7. Sections et rangées dynamiques
Une section ou une rangée temporaire peut recevoir un nouvel UID lors de sa création côté serveur. Dans ce cas, les références de formules existantes doivent suivre le nouvel identifiant.
utilsByModule.updateFormulaReferences() parcourt les inputs de la table, des sections et des rangées et remplace les anciens sectionUid/rowUid par les UID persistés.
Lorsqu'une rangée ou une section est supprimée, les références qui pointent vers celle-ci sont retirées. Si une formule ne possède ensuite plus aucune référence, la formule est supprimée.
8. Activation et tutoriel
Le système est activé par la préférence has_formulas. Lorsque cette option est désactivée, les contrôles de formule ne sont pas affichés.
Deux séquences de tutoriel existent : ModuleFormulaTutorial introduit l'activation du mode formule, et ModuleFormulaReferencesTutorial explique les boutons + et - et la sélection des références.
Fichiers principaux
Assets/Src/UI/SimByModule/DataDrivenTableControl.cs— logique principale, calcul et dépendances.Assets/Src/UI/SimByModule/DataDrivenTableEditorControl.cs— toggle formule et boutons + / -.Assets/Src/UI/SimByModule/UI/InputCellControl.cs— interaction cellule et contours visuels.Assets/Src/Game/Data/ByModule/DataDrivenInputData.cs— valeur calculée et formule optionnelle.Assets/Src/Game/Data/ByModule/DataDrivenInputFormulaData.cs— liste des références.Assets/Src/Game/Data/ByModule/DataDrivenInputFormulaReferenceData.cs— adresse complète et signe.Assets/Src/UI/SimByModule/Controllers/TeamByModuleController.cs— sauvegarde et réception de l'événement.Assets/Src/UI/SimByModule/Tutorial/ModuleFormulaTutorial.cs— tutoriels.master/node.js/.../utils_custom_command_by_module.js— persistance backend.master/node.js/.../byModule_utils.js— mise à jour/suppression des références lors des changements d'UID.
Points d’attention
- Le résultat numérique est dans
value; la formule ne remplace pas ce champ. - Une formule doit utiliser l'adresse complète de la cellule, pas seulement
inputUid. - Toute modification d'UID dynamique doit mettre à jour les références persistées.
- Lorsqu'une cellule référencée change, il faut notifier les dépendances pour maintenir les formules en cascade.
- Lorsqu'une formule est modifiée depuis un autre client, l'événement temps réel doit reconstruire ses listeners avant le recalcul.
No comments to display
No comments to display