Aller au contenu
SheetForge

Le noyau de création — construire une seconde surface de création

Avancé. Pour les auteurs d'assets/d'outils qui veulent construire leur propre interface de création (par exemple, un canevas de graphe à nœuds) par-dessus le moteur de SheetForge. Les équipes de jeu qui utilisent le Data Studio n'ont pas besoin de cette page.

La fenêtre de création n'est pas le moteur. Le Data Studio — et l'application web à ses côtés — sont des consommateurs d'un noyau de création indépendant de toute fenêtre.

Tout ce qu'ils font passe par des types publics qu'une troisième surface peut piloter de la même façon : préparation, validation, orchestration de la répercussion, limites d'annulation, réimport. Deux surfaces le font déjà, ce qui est la preuve pratique que la couture est réelle plutôt qu'aspirationnelle.

Avant de construire toute une surface, vérifiez si un point d'extension couvre déjà le besoin. Un plugin peut ajouter des verbes, des panneaux, des badges et des widgets de cellule à la fenêtre livrée sans posséder de fenêtre du tout, décrits comme des données afin qu'ils se dessinent dans l'éditeur et dans le navigateur — voir Création de plugins §4.16. Cette page est pour le cas où vous voulez votre propre canevas.

Un assembly de test de simulation de consommateur (SheetForge.Tests.Consumer, sans accès InternalsVisibleTo au Core ou à l'Editor) implémente une surface de création virtuelle de bout en bout contre la seule API publique. Si un membre dont il a besoin était internal, cet assembly ne compilerait pas (CS0122). Il sert donc de spécification exécutable pour la surface décrite ci-dessous.

Le moteur à trois objets

┌─────────────────────┐     ┌──────────────────────────┐     ┌───────────────┐
│  AuthoringSession   │────▶│   AuthoringDispatcher    │────▶│ BaselineStore │
│  (staging state)    │     │   .Reflect()             │     │ (round-trip   │
│                     │     │   (the full cycle)       │     │  snapshots)   │
└─────────────────────┘     └────────────┬─────────────┘     └───────────────┘
                                         │ binds
                            ┌────────────▼─────────────┐
                            │ AuthoringDispatchCallbacks│
                            │ (view concerns — YOUR UI) │
                            └──────────────────────────┘

AuthoringSession — l'état de préparation

Une simple classe [Serializable], volontairement pas un ScriptableObject. Conservez-la dans un champ [SerializeField] de votre EditorWindow, et vous obtenez gratuitement des instantanés Undo natifs d'Unity et une survie au rechargement de domaine — le même mécanisme qui se cache derrière le Ctrl+Z des fenêtres livrées.

Elle possède tout l'état préparé :

  • modifications de cellule (Edits), nouvelles lignes (NewRows), opérations de structure (StructOps) ;
  • réorganisations par onglet (Reorders), renommages d'onglet (TabRenames) ;
  • ancres de baseline, modifications isolées.

Au-dessus de cet état, elle expose l'API de mutation/interrogation :

  • SetStaged(...) — prépare une modification de cellule. Les modifications portent une adresse logique (onglet · RecordId · champ) ; l'ordinal de ligne physique est un cache dérivé, résolu à nouveau juste avant la répercussion.
  • ResolveBaselineEdits(provider) — réancre toutes les modifications par rapport à la baseline actuelle. Les modifications résolubles avancent. Les trois cas irrésolubles (renommage externe / suppression externe / conflit de clé) sont déplacés vers IsolatedEdits : exclus de la répercussion, signalés par un badge, jamais abandonnés silencieusement, jamais bloquants pour la session.
  • Surface de lecture de la baseline : TabNames, TryGetBaselineTable(tab, out SheetTable) — accès au schéma typé (TypeToken, @desc, @overlap) sans avoir à toucher vous-même au parseur.
  • EffectiveStructOps() / PendingStructCount() — la vue composée et canonique des opérations de structure.
  • Les hooks de remappage (RemapFieldName / RemapRecordId / RemapTab) gardent l'état préparé cohérent à travers les renommages.
  • LastProjectionResult met en cache la dernière projection.

AuthoringDispatcher — l'orchestration de la répercussion

var dispatcher = new AuthoringDispatcher(session, callbacks, baselineStore);
dispatcher.Reflect();   // the entire cycle, one call

Reflect() exécute le cycle complet, dans l'ordre :

  • validation préalable
  • répercussion par source — écritures chirurgicales pour le local, réécriture sûre pour Google, votre propre cible pour les fournisseurs personnalisés
  • nettoyage de l'état conservé
  • la limite de confirmation ClearUndo
  • réimport automatique avec rapport

De plus :

  • BuildProjectionResult() — une projection sans effet de bord de l'état préparé actuel sous forme d'ImportResult (valider comme si la répercussion avait eu lieu). Utilisez-la pour des badges d'erreur en direct.
  • Session / Callbacks / Baselines publics — les fournisseurs de source personnalisés les utilisent pour assembler leurs cibles de répercussion.

AuthoringDispatchCallbacks — le contrat de votre interface

Un ensemble de 13 délégués généraux que le dispatcher appelle pour chaque préoccupation de vue : ResolveSettings, des boîtes de dialogue de confirmation (ConfirmKeyRenames, ConfirmTabRenames, …), RenderReport (un Action<ImportReport> — tolérant au null, purement observationnel), PushApprover, TriggerReimport, ClearUndo, Rebuild, et ainsi de suite.

Les 14 délégués de boîte de dialogue propres aux sources intégrées Local/Google vivent dans un ensemble séparé et optionnel, BuiltInSourceDialogs, qu'une surface externe ou un fournisseur n'a jamais besoin de lier.

Les fenêtres livrées lient des valeurs par défaut qui affichent des boîtes de dialogue ; votre canevas lie les siennes (ou des no-op). Le moteur ne dessine jamais lui-même d'interface.

Matière de graphe

Pour une projection « nœud = enregistrement, arête = référence ∪ déclaration » :

  • ReferenceScanner (Core) — la source unique de vérité pour énumérer les occurrences de référence à travers toutes les tables : scalaires, éléments de liste, valeurs par défaut explicites. C'est la même énumération que celle utilisée par le validateur de référence, si bien que votre graphe et la validation concordent par construction. Scan(tables) / ScanTable / ScanField / IsReferenceField.
  • IEdgeContributor / EdgeSpec / EdgeContributorRegistry (Core) — les plugins de domaine déclarent les arêtes que le scanner ne peut pas voir (à l'intérieur de valeurs de type personnalisé, liens de colonne type, arêtes d'enregistrement avec un enregistrement de charge utile). Collectez-les via PluginRegistry.BuildEdgeContributors de l'Editor.
  • ReferenceIndex / RecordEdge (Core) — l'instantané assemblé sur lequel tourne le propre canevas du Data Studio : Build(...) fusionne une fois les références scannées avec les arêtes des contributeurs, puis OutEdges / InEdges / InCount répondent en O(1) par enregistrement. Liste complète des membres dans la Référence de l'API.
  • IRecordCanvasAugmenter / CanvasAugmentBuilder (Core) — le contrat de surcharge par onglet, si vous voulez que des packs de domaine étendent votre canevas de la même façon qu'ils étendent celui du Studio (nœuds virtuels, arêtes supplémentaires, indices de calque et d'affichage).
  • ProjectionErrorMapper (Editor, pur) — fait correspondre la coordonnée physique d'une erreur de projection (onglet/ligne/champ) à une adresse logique (onglet/RecordId/champ), afin que vous puissiez épingler les badges d'erreur aux nœuds plutôt qu'à des numéros de ligne.

Éléments de support

TypeÀ quoi votre surface s'en sert
ImportEventsDeux bus, tous deux des contrats publics. ImportCompleted (ImportCompletedArgs : Tabs · BakeFolder) se déclenche quand la chaîne automatique est allée jusqu'au bout du bake, si bien qu'un abonné peut lire les assets bakés. BaselineUpdated (BaselineUpdatedArgs : Tabs · Quarantined) se déclenche chaque fois qu'un instantané de feuille a été enregistré — y compris une exécution qui a échoué à la validation — ce à quoi une surface s'abonne si elle veut montrer les feuilles en échec et laisser les gens les corriger. Abonnez-vous aux deux si votre vue montre à la fois des feuilles et des valeurs bakées ; désabonnez-vous symétriquement dans OnDisable.
IPipelineObserverSi ce qui a besoin de savoir est un plugin plutôt qu'une fenêtre, c'est le chemin le plus léger : enregistrez un observateur et recevez un PipelineRunView immuable à la fin de chaque cycle d'import, sans aucune dépendance à l'éditeur — cela fonctionne aussi dans l'hôte navigateur. Voir Création de plugins §4.17.
RecordIdMinterSuggère des id pour les nouveaux enregistrements — détection de préfixe + unification sans collision. Une API de suggestion, volontairement pas une numérotation automatique.
EphemeralSoApplyPrévisualise temporairement les valeurs préparées sur les SO bake (le réimport les restaure). Applique le sous-ensemble calculable ; renvoie des raisons de saut pour les colonnes en attente et les échecs de parsing. Plus rien dans l'UI livrée ne le pilote, si bien qu'une surface qui veut cette prévisualisation possède elle-même le bouton pour cela.
KeyRenamePlannerPlanifie les renommages de clé (3 étapes : extraction / propagation / chirurgie), de la même façon que les fenêtres livrées. Les confirmations de renommage d'onglet passent plutôt par le callback public ConfirmTabRenames.
SourceProviderRegistryRésout le fournisseur de source actif de la même façon que l'interface des paramètres.

Règles de base imposées par le noyau (et que vous héritez)

  • La feuille reste canonique — votre surface prépare et répercute ; elle n'écrit jamais dans les SO.
  • Valider puis répercuterReflect() n'écrit rien si la validation préalable échoue.
  • Aucune perte silencieuse — les modifications irrésolubles s'isolent avec une raison ; les confirmations passent par vos callbacks.
  • L'annulation s'intègre nativement — conservez la session dans un champ sérialisé et enregistrez des instantanés d'annulation sur votre fenêtre ; ClearUndo marque la limite de répercussion.
  • Indépendant du domaine — le noyau ne contient aucun vocabulaire de domaine (testé par un garde-fou). Votre domaine arrive via les contrats de plugin, pas via des modifications du noyau.

Pages associées