# Manage narration flow with labels (/start/labels-flow)



The narration flow is managed by functions such as `call`, `jump`, `continue`, `back`, and `closeLabel`, all available in the `narration` object.

## Run a label [#run-a-label]

<CalloutContainer type="info">
  <CalloutTitle>
    *ink*
  </CalloutTitle>

  <CalloutDescription>
    You can use this method with the *ink* syntax. See more <DynamicLink href="/ink/labels#run-a-knot">here</DynamicLink>.
  </CalloutDescription>
</CalloutContainer>

<Accordions>
  <Accordion title="`call` a `label`" id="call-a-label">
    To call a `label`, use the [`narration.call`](/jsdoc/pixi-vn/index/interfaces/NarrationManagerInterface#call) function.

    ```ts
    import { narration } from "@drincs/pixi-vn";

    narration.call(startLabel, {});
    ```

    When you call a `label`, the first `step` of that `label` is executed. If another `label` was running before the call, the remaining `steps` of that previous `label` will resume after the called `label` finishes.

    For example, if the game is running `step` 5 of `label` A and you **call** `label` B, after all `steps` of `label` B are executed, the game continues with `step` 6 of `label` A.

    [`narration.call`](/jsdoc/pixi-vn/index/interfaces/NarrationManagerInterface#call) returns the <DynamicLink href="/start/labels#step-results">result</DynamicLink> of the first `step` of the called `label`.

    ***

    If you execute [`narration.call`](/jsdoc/pixi-vn/index/interfaces/NarrationManagerInterface#call) inside a `step`, you should return the result of [`narration.call`](/jsdoc/pixi-vn/index/interfaces/NarrationManagerInterface#call) and use `await`. This ensures the history is managed correctly.

    ```ts title="content/labels/start.label.ts"
    import { narration, newLabel } from "@drincs/pixi-vn";

    export const startLabel = newLabel("start", [
        async (props) => {
            return await narration.call(TestLabel, props);
        },
        async (props) => await narration.call(TestLabel, props),
    ]);
    ```
  </Accordion>

  <Accordion title="`jump` a `label`" id="jump-to-a-label">
    To jump to a `label`, use the [`narration.jump`](/jsdoc/pixi-vn/index/interfaces/NarrationManagerInterface#jump) function.

    ```ts
    import { narration } from "@drincs/pixi-vn";

    narration.jump(startLabel, {});
    ```

    When you jump to a `label`, the current `label`'s `steps` are stopped and the new `label`'s `steps` begin.

    For example, if the game is running `step` 5 of `label` A, and you **call** `label` B, but then **jump** to `label` C, after `label` C finishes, the game continues with `step` 6 of `label` A. When you jump to `label` C, `label` B is closed.

    [`narration.jump`](/jsdoc/pixi-vn/index/interfaces/NarrationManagerInterface#jump) returns the <DynamicLink href="/start/labels#all-steps-result">result of the first `step` of the called `label`</DynamicLink>.

    ***

    If you execute [`narration.jump`](/jsdoc/pixi-vn/index/interfaces/NarrationManagerInterface#jump) inside a `step`, you should return the result of [`narration.jump`](/jsdoc/pixi-vn/index/interfaces/NarrationManagerInterface#jump) and use `await`. This ensures the first `step` of the new `label` is awaited.

    ```ts title="content/labels/start.label.ts"
    import { narration } from "@drincs/pixi-vn";

    export const startLabel = newLabel("start", [
        async (props) => {
            return await narration.jump(TestLabel, props);
        },
        async (props) => await narration.jump(TestLabel, props),
    ]);
    ```
  </Accordion>

  <Accordion title="Start the game" id="start-game">
    To start the game, use the [`Game.start`](/jsdoc/pixi-vn/index/namespaces/Game/functions/start) function. This function is equivalent to calling a `label`, but it is recommended to use it for a cleaner startup.

    ```ts title="content/labels/start.label.ts"
    import { Game } from "@drincs/pixi-vn";

    Game.start(startLabel, gameProps);
    ```
  </Accordion>
</Accordions>

## Continue and go back in steps [#continue-and-go-back-in-steps]

### Continue [#continue]

<CalloutContainer type="info">
  <CalloutTitle>
    UI screen
  </CalloutTitle>

  <CalloutDescription>
    You can find the example of "continue by clicking the screen" in the <DynamicLink href="/start/interface-examples#go-next">interface examples</DynamicLink> section.
  </CalloutDescription>
</CalloutContainer>

To execute the next `step`, use the [`narration.continue`](/jsdoc/pixi-vn/index/interfaces/NarrationManagerInterface#continue) function.

```ts
import { narration } from "@drincs/pixi-vn";

await narration.continue({});
```

[`narration.continue`](/jsdoc/pixi-vn/index/interfaces/NarrationManagerInterface#continue) is asynchronous, so you can use `.then` to, for example, disable a "Next" button until the `step` is complete.

```ts
import { narration } from "@drincs/pixi-vn";

// disable next button
narration.continue({}).then((result) => {
    // enable next button
});
```

<Accordions>
  <Accordion title="Execute continue inside a `step`" id="call-continue-inside-a-step">
    If you call [`narration.continue`](/jsdoc/pixi-vn/index/interfaces/NarrationManagerInterface#continue) inside a `step`, the "continue" request will be queued and executed when the queue is empty.

    ```ts title="content/labels/start.label.ts"
    import { narration, newLabel } from "@drincs/pixi-vn";

    export const startLabel = newLabel("start", [
        async (props) => {
            narration.continue(props);
        },
    ]);
    ```

    Here is an example to illustrate how the queue works:

    ```ts title="content/labels/start.label.ts"
    import { narration, newLabel } from "@drincs/pixi-vn";

    export const startLabel = newLabel("start", [
        async (props) => {
            await narration.call(label2, props);
            console.log(1);
        },
        () => {
            console.log(3);
        },
    ]);

    const label2 = newLabel("label_02", [
        async (props) => {
            await narration.continue(props);
            console.log(2);
        },
    ]);
    ```

    In this example, the output will be `2`, `1`, `3`. The steps are:

    1. `await narration.call(label2, props)` calls label2 and awaits its first `step`. (1 item in the queue)
    2. The first `step` of label2 executes `await narration.continue(props)`, but the continue request is queued because the queue is not empty. (2 items in the queue)
    3. `console.log(2)` runs, finishing the first `step` of label2. (1 item in the queue)
    4. `console.log(1)` runs, finishing the first `step` of startLabel. (0 items in the queue)
    5. Now the continue request is executed, and the second `step` of startLabel runs. (1 item in the queue)
    6. `console.log(3)` runs, finishing the second `step` of startLabel. (0 items in the queue)
  </Accordion>

  <Accordion title="Check if can go to the next `step`" id="can-go-next">
    You can use the [`narration.canContinue`](/jsdoc/pixi-vn/index/interfaces/NarrationManagerInterface#cancontinue) property to check if the next `step` can be executed.

    [`narration.canContinue`](/jsdoc/pixi-vn/index/interfaces/NarrationManagerInterface#cancontinue) is `false` when:

    * A `step` is running
    * The player must <DynamicLink href="/start/choices">make a choice</DynamicLink>
    * The player must <DynamicLink href="/start/input">enter a value</DynamicLink>

    ```tsx title="components/NextButton.tsx"
    import { narration } from "@drincs/pixi-vn";

    function NextButton() {
        return (
            <button
                disabled={!narration.canContinue}
                onClick={() => {
                    narration.continue({});
                }}
            >
                Next
            </button>
        );
    }
    ```
  </Accordion>
</Accordions>

### Go back [#go-back]

<CalloutContainer type="info">
  <CalloutTitle>
    UI screen
  </CalloutTitle>

  <CalloutDescription>
    You can find the example of the go back button in the <DynamicLink href="/start/interface-examples#go-back">interface examples</DynamicLink> section.
  </CalloutDescription>
</CalloutContainer>

At every `step`, the system saves the current state of the game. To go back to the previous `step`, use the [`stepHistory.back`](/jsdoc/pixi-vn/index/interfaces/HistoryManagerInterface#back) function.

<Accordions>
  <Accordion title="Check if can go back" id="can-go-back">
    You can use the [`stepHistory.canGoBack`](/jsdoc/pixi-vn/index/interfaces/HistoryManagerInterface#cangoback) property to check if going back is possible.

    [`stepHistory.canGoBack`](/jsdoc/pixi-vn/index/interfaces/HistoryManagerInterface#cangoback) is `false` when there are no `steps` in the history to restore.

    ```ts
    import { stepHistory } from "@drincs/pixi-vn";

    if (stepHistory.canGoBack) {
        stepHistory.back();
    }
    ```
  </Accordion>

  <Accordion title="Block the possibility of going back" id="block-the-possibility-of-going-back">
    You can block the "go back" by calling [`stepHistory.blockGoBack`](/jsdoc/pixi-vn/index/interfaces/HistoryManagerInterface#blockgoback).

    ```typescript
    import { stepHistory } from "@drincs/pixi-vn";

    stepHistory.blockGoBack();
    ```
  </Accordion>
</Accordions>

## Close labels [#close-labels]

<Accordions>
  <Accordion title="Close current label" id="close-current-label">
    To close the current `label`, use the [`narration.labels.closeCurrent`](/jsdoc/pixi-vn/index/interfaces/NarrationLabelsInterface#closecurrent) function.

    ```typescript
    import { narration } from "@drincs/pixi-vn";

    narration.labels.closeCurrent();
    ```
  </Accordion>

  <Accordion title="Close all labels" id="close-all-labels">
    To close all `labels`, use the [`narration.labels.closeAll`](/jsdoc/pixi-vn/index/interfaces/NarrationLabelsInterface#closeall) function. **If you call this function and do not call any `label` afterwards, the game will end**

    ```typescript
    import { narration } from "@drincs/pixi-vn";

    narration.labels.closeAll();
    ```
  </Accordion>
</Accordions>

## Other features [#other-features]

<Accordions>
  <Accordion title="Rollback and Rollforward" id="rollback-rollforward">
    <CalloutContainer type="info">
      <CalloutTitle>
        Templates
      </CalloutTitle>

      <CalloutDescription>
        In all templates, this implementation is already included.
      </CalloutDescription>
    </CalloutContainer>

    If you execute multiple [`stepHistory.back`](/jsdoc/pixi-vn/index/interfaces/HistoryManagerInterface#back) and [`narration.continue`](/jsdoc/pixi-vn/index/interfaces/NarrationManagerInterface#continue) requests synchronously, the system will internally queue them and calculate the delta to execute only the necessary processes.

    So you can implement rollback and rollforward features using the [`stepHistory.back`](/jsdoc/pixi-vn/index/interfaces/HistoryManagerInterface#back) and [`narration.continue`](/jsdoc/pixi-vn/index/interfaces/NarrationManagerInterface#continue) functions.

    ```tsx title="lib/hooks/quick-tools-hooks.ts"
    import { HTML_CANVAS_LAYER_NAME, HTML_UI_LAYER_NAME } from "@/constans";
    import { useGameProps } from "@/lib/hooks/props-hooks";
    import { GameStatus } from "@/lib/stores/game-status-store";
    import { hasScrollableParent } from "@/lib/utils/scroll-utils";
    import { type StepLabelProps, stepHistory } from "@drincs/pixi-vn";
    import { narration } from "@drincs/pixi-vn/narration";
    import { useThrottler } from "@tanstack/react-pacer";
    import { useEffect, useRef } from "react";

    function isInsideRoot(target: EventTarget | null, selector: string): boolean {
        if (!(target instanceof HTMLElement)) return false;
        return target.closest(`#${selector}`) !== null;
    }

    export function useWheelActions({
        throttleMs = 300,
        minDelta = 20,
    }: {
        throttleMs?: number;
        minDelta?: number;
    } = {}) {
        const pendingAsync = useRef(0);
        const gameProps = useGameProps();

        const runAsync = async (
            fn: (props: StepLabelProps) => Promise<unknown>,
        ) => {
            try {
                pendingAsync.current += 1;
                GameStatus.setLoading(pendingAsync.current > 0);
                await fn(gameProps);
            } finally {
                pendingAsync.current -= 1;
                GameStatus.setLoading(pendingAsync.current > 0);
                if (pendingAsync.current === 0) {
                    gameProps.invalidateInterfaceData();
                }
            }
        };

        const handleWheel = useThrottler(
            async (event: WheelEvent) => {
                if (
                    !(
                        isInsideRoot(event.target, HTML_UI_LAYER_NAME) ||
                        isInsideRoot(event.target, HTML_CANVAS_LAYER_NAME)
                    )
                )
                    return;
                if (hasScrollableParent(event.target)) return;

                // blocca lo scroll nativo
                event.preventDefault();

                const { deltaY } = event;

                // ignora micro-movimenti del trackpad
                if (Math.abs(deltaY) < minDelta) return;

                if (deltaY < 0) {
                    // ⬆️ Scroll up
                    await runAsync(narration.continue.bind(narration));
                }

                if (deltaY > 0) {
                    // ⬇️ Scroll down
                    await runAsync(stepHistory.back.bind(stepHistory));
                }
            },
            { wait: throttleMs },
        );

        useEffect(() => {
            window.addEventListener("wheel", handleWheel.maybeExecute, {
                passive: false,
            });

            return () => {
                window.removeEventListener("wheel", handleWheel.maybeExecute);
            };
        }, [handleWheel.maybeExecute]);

        return null;
    }
    ```
  </Accordion>
</Accordions>
