Skip to content

Context

svelte-context is a useful feature from svelte especially when a shared state requires some inputs/types to create. Here it shows the structure of context of sample game /apps/lines. As showed before, setContext() is called at entry level component. For example, apps/lines/src/routes/+page.svelte or apps/lines/src/stories/ComponentsGame.stories.svelte. It sets four major contexts from the packages by this:

// context.ts - Example of setContext in apps

export const setContext = () => {
  setContextEventEmitter<EmitterEvent>({ eventEmitter });
  setContextXstate({ stateXstate, stateXstateDerived });
  setContextLayout({ stateLayout, stateLayoutDerived });
  setContextApp({ stateApp });
};

Different apps and packages require different contexts.

below

ContextEventEmitter

eventEmitter is created by packages/utils-event-emitter/src/createEventEmitter.ts. We have covered eventEmitter in the previous content.

ContextLayout

stateLayout and stateLayoutDerived are created by packages/utils-layout/src/createLayout.svelte.ts. It provides canvasSizes, canvasRatio, layoutType and so on. Because we have a setting resizeTo: window for PIXI.Application, we use the sizes of window from svelte-reactivity as canvasSizes.

For html, the tags will auto-flow by default. However, in the canvas/pixijs we need to set positions manually to avoid overlapping. The importance of LayoutContext is that it provides us the values of boundaries (canvasSizes), device type based on the dimensions (layoutType) and so on. For example:

  • Set a pixi-svelte component to the left edge of the canvas:
  • <Component x={0} />
  • Set a pixi-svelte component to the right edge of the canvas:
  • <Component x={context.stateLayoutDerived.canvasSizes().width} anchor={{ x: 1: y: 0 }} />
  • It works when <App /> is the parent of the component, otherwise it will be determined by its parent <Container />.
  • The reason why we set anchor is because that the drawing is always go from top-left to bottom-right in pixijs.
// createLayout.svelte.ts

import { innerWidth, innerHeight } from 'svelte/reactivity/window';

...

const stateLayout = $state({
  showLoadingScreen: true,
});

const stateLayoutDerived = {
  canvasSizes,
  canvasRatio,
  canvasRatioType,
  canvasSizeType,
  layoutType,
  isStacked,
  mainLayout,
  normalBackgroundLayout,
  portraitBackgroundLayout,
};

ContextXstate

stateXstate and stateXstateDerived are created by packages/utils-xstate/src/createXstateUtils.svelte.ts. It provides a few functions to check the state of finite state machine, also known as gameActor, which is created by packages/utils-xstate/src/createGameActor.svelte.ts.

// createXstateUtils.svelte.ts

import { matchesState, type StateValue } from 'xstate';

...

const stateXstate = $state({
  value: '' as StateValue,
});

const matchesXstate = (state: string) => matchesState(state, stateXstate.value);

const stateXstateDerived = {
  matchesXstate,
  isRendering: () => matchesXstate(STATE_RENDERING),
  isIdle: () => matchesXstate(STATE_IDLE),
  isBetting: () => matchesXstate(STATE_BET),
  isAutoBetting: () => matchesXstate(STATE_AUTOBET),
  isResumingBet: () => matchesXstate(STATE_RESUME_BET),
  isForcingResult: () => matchesXstate(STATE_FORCE_RESULT),
  isPlaying: () => !matchesXstate(STATE_RENDERING) && !matchesXstate(STATE_IDLE),
};

gameActor: To avoid using massive "if-else" conditions in the code, we use npm/xstate to create a finite state machine to handle the complicated logic and states of betting. It provides a few pre-defined mechanics like one-off bet, autoBet with a count down, resumeBet to continue an unfinished bet and so on.

// createGameActor.svelte.ts

import { setup, createActor } from 'xstate';

...

const gameMachine = setup({
  actors: {
    bet: intermediateMachines.bet,
    autoBet: intermediateMachines.autoBet,
    resumeBet: intermediateMachines.resumeBet,
    forceResult: intermediateMachines.forceResult,
  },
}).createMachine({
  initial: 'rendering',
  states: {
    [STATE_RENDERING]: stateRendering,
    [STATE_IDLE]: stateIdle,
    [STATE_BET]: stateBet,
    [STATE_AUTOBET]: stateAutoBet,
    [STATE_RESUME_BET]: stateResumeBet,
    [STATE_FORCE_RESULT]: stateForceResult,
  },
});

const gameActor = createActor(gameMachine);

This is highly useful when it comes to the interactions with UI, for example disable the bet button when the a game is playing.

// BetButton.svelte - Example of interaction between xstate and UI

<script lang="ts">
  import { getContext } from '../context';

  const context = getContext();
</script>

<SimpleUiButton disabled={context.stateXstateDerived.isPlaying()} />

AppContext

stateApp is created by packages/pixi-svelte/src/lib/createApp.svelte.ts. loadedAssets contains the static images, animations and sound data that is processed by PIXI.Assets.load with stateApp.assets. loadedAssets can be digested by pixi-svelte components directly as showed in pixi-svelte component \<Sprite /\>(/packages/pixi-svelte/src/lib/components/Sprite.svelte).

// createApp.svelte.ts

const stateApp = $state({
  reset,
  assets,
  loaded: false,
  loadingProgress: 0,
  loadedAssets: {} as LoadedAssets,
  pixiApplication: undefined as PIXI.Application | undefined,
});