Skip to content

Lifecycle & Validation

Lifecycle complet

Ordre d'exécution pour chaque block

  1. Cleanup du block précédent — La fonction de cleanup retournée par le handler du block précédent s'exécute au moment de la transition (quand next() est appelé)
  2. onValidateNextBlock — Validation avant exécution
  3. onBeforeBlock — Pré-traitement (doit appeler resolve() pour continuer)
  4. Handler de type (Tier 2 puis Tier 1)

Events de scène

ts
engine.onSceneEnter(({ scene, context }) => {
  // Called when handle.start() is executed
});

engine.onSceneExit(({ scene, context }) => {
  // Called when the scene ends (naturally or via cancel)
});
csharp
engine.OnSceneEnter(args => {
    // Called when handle.Start() is executed
});

engine.OnSceneExit(args => {
    // Called when the scene ends (naturally or via cancel)
});
cpp
engine.onSceneEnter([](auto* scene, auto*) {
    // Called when handle->start() is executed
});

engine.onSceneExit([](auto* scene, auto*) {
    // Called when the scene ends (naturally or via cancel)
});
gdscript
engine.on_scene_enter(func(args):
    pass # Called when handle.start() is executed
)

engine.on_scene_exit(func(args):
    pass # Called when the scene ends (naturally or via cancel)
)

onValidateNextBlock

Intercepte chaque transition de block pour validation. Le handler reçoit le personnage résolu du prochain block (nextContext) et du block précédent (fromContext) :

ts
engine.onValidateNextBlock(({ nextBlock, fromBlock, nextContext, fromContext }) => {
  return { valid: true };
});

engine.onInvalidateBlock(({ scene, reason }) => {
  console.error('Invalid block:', reason);
  scene.cancel();
});
csharp
engine.OnValidateNextBlock(args => {
    // args.NextContext.Character, args.FromContext?.Character
    return new ValidationResult { Valid = true };
});

engine.OnInvalidateBlock(args => {
    Console.Error.WriteLine($"Invalid block: {args.Reason}");
    args.Scene.Cancel();
});
cpp
engine.onValidateNextBlock([](const auto& args) {
    // args.nextContext.character, args.fromContext.character (check args.hasFromContext)
    return ValidationResult{true};
});

engine.onInvalidateBlock([](auto* scene, const auto& reason) {
    std::cerr << "Invalid block: " << reason << "\n";
    scene->cancel();
});
gdscript
engine.on_validate_next_block(func(args):
    # args["nextContext"]["character"], args["fromContext"]["character"]
    return {"valid": true}
)

engine.on_invalidate_block(func(args):
    printerr("Invalid block: %s" % args["reason"])
    args["scene"].cancel()
)

Character Gating

Utilisez nextContext.character pour contrôler quels blocks peuvent s'exécuter selon l'état du jeu :

ts
// Block if the character is stunned
engine.onValidateNextBlock(({ nextContext }) => {
  const { character } = nextContext;
  if (!character) return { valid: false, reason: 'no_character' };
  if (game.characterHasStatus(character, 'stunned'))
    return { valid: false, reason: 'character_stunned' };
  return { valid: true };
});
csharp
engine.OnValidateNextBlock(args => {
    var character = args.NextContext.Character;
    if (character == null)
        return ValidationResult.Fail("no_character");
    if (game.CharacterHasStatus(character, "stunned"))
        return ValidationResult.Fail("character_stunned");
    return ValidationResult.Ok();
});
cpp
engine.onValidateNextBlock([&game](const auto& args) {
    auto* character = args.nextContext.character;
    if (!character) return ValidationResult{false, "no_character"};
    if (game.characterHasStatus(character, "stunned"))
        return ValidationResult{false, "character_stunned"};
    return ValidationResult{true};
});
gdscript
engine.on_validate_next_block(func(args):
    var character = args["nextContext"]["character"]
    if character == null:
        return {"valid": false, "reason": "no_character"}
    if game.character_has_status(character, "stunned"):
        return {"valid": false, "reason": "character_stunned"}
    return {"valid": true}
)

Utilisez fromContext.character pour valider les transitions entre personnages (ex: relations, cooldowns). fromContext est null pour le premier block d'une scène.

onBeforeBlock

Appelé avant chaque block. resolve() doit être appelé pour continuer :

ts
engine.onBeforeBlock(({ block, resolve }) => {
  const delay = block.nativeProperties?.delay;
  if (delay) {
    setTimeout(resolve, delay * 1000);
  } else {
    resolve();
  }
});
csharp
engine.OnBeforeBlock(args => {
    var delay = args.Block.NativeProperties?.Delay;
    if (delay.HasValue)
    {
        // use your engine's delay system (coroutine, DOTween, Invoke, etc.)
        DelayThenCall((float)delay.Value, args.Resolve);
    }
    else
    {
        args.Resolve();
    }
});
cpp
engine.onBeforeBlock([](const auto& args) {
    auto delay = args.block->nativeProperties
        ? args.block->nativeProperties->delay : std::nullopt;
    if (delay.has_value()) {
        // use your engine's timer system (FTimerManager, SDL_AddTimer, etc.)
        scheduleDelay(delay.value(), [&args]() { args.resolve(); });
    } else {
        args.resolve();
    }
});
gdscript
engine.on_before_block(func(args):
    var delay = args["block"].get("nativeProperties", {}).get("delay", 0)
    if delay > 0:
        await get_tree().create_timer(delay).timeout
    args["resolve"].call()
)

Fonctions de cleanup

Un handler peut retourner une fonction de cleanup, appelée quand le block est quitté :

ts
engine.onDialog(({ block, next }) => {
  const element = showDialogUI(block);

  // next() is called later — by player input, timer, etc.

  return () => {
    element.remove(); // called when the engine moves to the next block
  };
});
csharp
engine.OnDialog(args => {
    var element = ShowDialogUI(args.Block);

    // next() is called later — by player input, timer, etc.

    return () => element.SetActive(false);
});
cpp
engine.onDialog([](auto*, auto* block, auto* ctx, auto next) -> CleanupFn {
    auto* element = showDialogUI(block);

    // next() is called later — by player input, timer, etc.

    return [element]() { element->remove(); };
});
gdscript
engine.on_dialog(func(args):
    var element = show_dialog_ui(args["block"])

    # next is called later — by player input, timer, etc.

    return func(): element.queue_free()
)

Error Boundaries

Chaque appel de handler est encapsulé dans un try/catch. Si un handler throw :

  • L'erreur est silencieuse — elle n'est pas loguée ni re-throw. Si votre scène se termine de façon inattendue, vérifiez vos handlers.
  • Pour le main track : la scène se termine proprement
  • Pour les async tracks : seul le track affecté est terminé — les autres tracks et le flow principal continuent

C'est compatible cross-language (try/catch en TS, C#, C++, GDScript).

cancel()

Appeler scene.cancel() déclenche cette séquence :

  1. Tous les async tracks sont annulés
  2. La fonction de cleanup du block courant est exécutée
  3. Le handler onSceneExit est appelé
  4. La scène est marquée comme terminée
ts
engine.onInvalidateBlock(({ scene, reason }) => {
  console.error('Validation failed:', reason);
  scene.cancel();
});
csharp
engine.OnInvalidateBlock(args => {
    Console.Error.WriteLine($"Validation failed: {args.Reason}");
    args.Scene.Cancel();
});
cpp
engine.onInvalidateBlock([](auto* scene, const auto& reason) {
    std::cerr << "Validation failed: " << reason << "\n";
    scene->cancel();
});
gdscript
engine.on_invalidate_block(func(args):
    printerr("Validation failed: %s" % args["reason"])
    args["scene"].cancel()
)

NativeProperties

Propriétés d'exécution qui contrôlent comment un block est dispatché par le engine :

ChampTypeDescription
isAsyncboolean?Exécuter sur un track async parallèle
delaynumber?Délai avant exécution (consommé par onBeforeBlock)
timeoutnumber?Timeout d'exécution
portPerCharacterboolean?Un port de sortie par personnage dans metadata
skipIfMissingActorboolean?Sauter le block si l'acteur référencé est absent
debugboolean?Flag de debug pour l'éditeur
waitForBlocksstring[]?UUIDs de blocks qui doivent avoir été visités avant que ce block puisse progresser
waitInputboolean?Flag passif pour contrôle explicite de l'input joueur

Référence visuelle

Flow d'exécution des blocks

Flow de Character Gating