# 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]

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

<Accordions>
  <Accordion title="call" id="call-a-label">
    To call a `label`, use the `narration.call` function.
    This function has the following parameters:

    * `label`: the `label` to be called
    * `props`: the properties to pass to the `label`. The interface corresponds to <DynamicLink href="/start/labels#steplabelprops">`StepLabelProps`</DynamicLink>.

    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` returns the <DynamicLink href="/start/labels#all-steps-result">result of the first `step` of the called `label`</DynamicLink>.

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

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

    If you execute `narration.call` inside a `step`, you should return the result of `narration.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" id="jump-to-a-label">
    To jump to a `label`, use the `narration.jump` function.
    This function has the following parameters:

    * `label`: the `label` to jump to
    * `props`: the properties to pass to the `label`. The interface corresponds to <DynamicLink href="/start/labels#steplabelprops">`StepLabelProps`</DynamicLink>.

    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` returns the <DynamicLink href="/start/labels#all-steps-result">result of the first `step` of the called `label`</DynamicLink>.

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

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

    If you execute `narration.jump` inside a `step`, you should return the result of `narration.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_game" id="start-game">
    To start the game, use the `Game.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]

<Callout title="UI screen" type="info">
  You can find the example of the "continue" button in the <DynamicLink href="/start/interface-examples#go-next">interface examples</DynamicLink> section.
</Callout>

To execute the next `step`, use the `narration.continue()` function.
This function has the following parameters:

* `props`: the properties to pass to the `label`. The interface corresponds to <DynamicLink href="/start/labels#steplabelprops">`StepLabelProps`</DynamicLink>.

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

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

`narration.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="call_continue_inside_step" id="call-continue-inside-a-step">
    If you call `narration.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_can_go_next_step" id="can-go-next">
    You can use the `narration.canContinue` property to check if the next `step` can be executed.

    `narration.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]

<Callout title="UI screen" type="info">
  You can find the example of the go back button in the <DynamicLink href="/start/interface-examples#go-back">interface examples</DynamicLink> section.
</Callout>

At every `step`, the system saves the current state of the game. To go back to the previous `step`, use the `stepHistory.back()` function.

You must pass a function `navigate: (path: string) => void` as a parameter. This function will be called with the <DynamicLink href="/start/start/interface-navigate">URL Path or Route</DynamicLink> of the previous `step`, so you can <DynamicLink href="/start/interface-navigate">navigate to the previous UI screen</DynamicLink>.

```ts title="React Router Dom"
import { stepHistory } from "@drincs/pixi-vn";
import { useNavigate } from "react-router-dom";

const navigate = useNavigate();

if (stepHistory.canGoBack) {
    stepHistory.back(navigate).then(() => {
        // ...
    });
}
```

<Accordions>
  <Accordion title="can_go_back" id="can-go-back">
    You can use the `stepHistory.canGoBack` property to check if going back is possible.

    `stepHistory.canGoBack` is `false` when there are no `steps` in the history to restore.

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

    function BackButton() {
        return (
            <button
                disabled={!stepHistory.canGoBack}
                onClick={() => {
                    stepHistory.back({});
                }}
            >
                Back
            </button>
        );
    }
    ```
  </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()`.

    ```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.closeCurrentLabel()` function.

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

    narration.closeCurrentLabel();
    ```
  </Accordion>

  <Accordion title="close_all_labels" id="close-all-labels">
    To close all `labels`, use the `narration.closeAllLabels()` function.\
    &#x2A;*If you call this function and do not call any `label` afterwards, the <DynamicLink href="/start/other-narrative-features#how-manage-the-end-of-the-game">game will end</DynamicLink>.**

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

    narration.closeAllLabels();
    ```
  </Accordion>
</Accordions>

Other features [#other-features]

<Accordions>
  <Accordion title="rollback_rollforward" id="rollback-rollforward">
    <Callout title="Templates" type="info">
      In all templates, this implementation is already included.
    </Callout>

    If you execute multiple `stepHistory.back()` and `narration.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()` and `narration.continue()` functions.

    ```tsx title="hooks/useWheelActions.tsx"
    import { stepHistory, StepLabelProps } from "@drincs/pixi-vn";
    import { narration } from "@drincs/pixi-vn/narration";
    import { useQueryClient } from "@tanstack/react-query";
    import { throttle } from "es-toolkit";
    import { useCallback, useEffect, useRef } from "react";
    import useStepStore from "../stores/useStepStore";
    import useGameProps from "./useGameProps";
    import { INTERFACE_DATA_USE_QUEY_KEY } from "./useQueryInterface";

    export function useWheelActions({
        throttleMs = 300,
        minDelta = 20,
    }: {
        throttleMs?: number;
        minDelta?: number;
    } = {}) {
        const pendingAsync = useRef(0);
        const setLoading = useStepStore((state) => state.setLoading);
        const queryClient = useQueryClient();
        const gameProps = useGameProps();

        const runAsync = async (
            fn: (props: StepLabelProps) => Promise<unknown>,
        ) => {
            try {
                pendingAsync.current += 1;
                setLoading(pendingAsync.current > 0);
                await fn(gameProps);
            } finally {
                pendingAsync.current -= 1;
                setLoading(pendingAsync.current > 0);
                if (pendingAsync.current === 0) {
                    queryClient.invalidateQueries({
                        queryKey: [INTERFACE_DATA_USE_QUEY_KEY],
                    });
                }
            }
        };

        const handleWheel = useCallback(
            throttle(async (event: WheelEvent) => {
                // 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));
                }
            }, throttleMs),
            [throttleMs, minDelta],
        );

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

            return () => {
                window.removeEventListener("wheel", handleWheel);
                handleWheel.cancel();
            };
        }, [handleWheel]);
    }
    ```
  </Accordion>
</Accordions>
