From 2f7b9edb426831b924a18da6bfa7b8e3dda0e9b2 Mon Sep 17 00:00:00 2001 From: SandroMaglione Date: Fri, 14 Aug 2026 19:56:53 +0200 Subject: [PATCH] Migrate examples to machine event constructors --- examples/platformer/src/machine.test.ts | 50 ++++++++++++------- examples/platformer/src/machine.ts | 21 +++++--- .../playground/src/examples/examples.test.ts | 26 +++++----- .../examples/media-player/MediaPlayerPage.tsx | 28 +++++------ .../src/examples/media-player/definition.ts | 25 ++++++++++ .../src/examples/media-player/invocations.ts | 19 +++---- .../src/examples/media-player/machine.test.ts | 35 +++++++------ .../src/examples/media-player/machine.ts | 30 ++--------- .../src/examples/microwave/MicrowavePage.tsx | 8 ++- .../src/examples/microwave/machine.ts | 12 +++-- .../traffic-light/TrafficLightPage.tsx | 4 +- .../src/examples/traffic-light/machine.ts | 12 +++-- .../src/examples/turnstile/TurnstilePage.tsx | 6 +-- .../src/examples/turnstile/machine.ts | 8 ++- .../examples/worker-tabs/WorkerTabsPage.tsx | 12 ++--- .../src/examples/worker-tabs/machine.ts | 17 ++++++- 16 files changed, 180 insertions(+), 133 deletions(-) create mode 100644 examples/playground/src/examples/media-player/definition.ts diff --git a/examples/platformer/src/machine.test.ts b/examples/platformer/src/machine.test.ts index 5e71b5d..cb40cc0 100644 --- a/examples/platformer/src/machine.test.ts +++ b/examples/platformer/src/machine.test.ts @@ -5,6 +5,7 @@ import { describe, expect, it } from "vitest" import { makeTextRenderer } from "../../../test/machine/visualization/text.ts" import { airJumpMode, + CharacterEvents, CharacterMachine, type CharacterSnapshot, Event, @@ -100,37 +101,50 @@ const laws = [ resumeRestoresDeepHistory ] +// Exploration scenarios retain decoded events for trace inspection. +const EventValue = { + Resume: () => Machine.event(CharacterMachine, Event.cases.Resume), + Pause: (fields: { readonly at: number }) => Machine.event(CharacterMachine, Event.cases.Pause, fields), + Reset: () => Machine.event(CharacterMachine, Event.cases.Reset), + JumpPressed: (fields: { readonly at: number; readonly y: number; readonly wall: -1 | 0 | 1 }) => + Machine.event(CharacterMachine, Event.cases.JumpPressed, fields), + Landed: (fields: { readonly impact: number; readonly axis: -1 | 0 | 1; readonly at: number }) => + Machine.event(CharacterMachine, Event.cases.Landed, fields), + ApexReached: (fields: { readonly y: number }) => Machine.event(CharacterMachine, Event.cases.ApexReached, fields), + DownPressed: (fields: { readonly at: number }) => Machine.event(CharacterMachine, Event.cases.DownPressed, fields) +} + const explorationEvents = ({ snapshot }: MachineTest.ExplorationStateContext) => { const locomotion = snapshot.states.locomotion.state if (locomotion.path === "Character.locomotion.Paused") { - return [Event.cases.Resume.make({})] + return [EventValue.Resume()] } - const pauseAndReset = [Event.cases.Pause.make({ at: 70 }), Event.cases.Reset.make({})] + const pauseAndReset = [EventValue.Pause({ at: 70 }), EventValue.Reset()] if (locomotion.state.path === "Character.locomotion.Playing.Grounded") { return [ - Event.cases.JumpPressed.make({ at: 20, y: 100, wall: -1 }), - Event.cases.JumpPressed.make({ at: 20, y: 100, wall: 0 }), - Event.cases.JumpPressed.make({ at: 20, y: 100, wall: 1 }), + EventValue.JumpPressed({ at: 20, y: 100, wall: -1 }), + EventValue.JumpPressed({ at: 20, y: 100, wall: 0 }), + EventValue.JumpPressed({ at: 20, y: 100, wall: 1 }), ...pauseAndReset ] } const motion = locomotion.state.states.motion.state const airborne = [ - Event.cases.JumpPressed.make({ at: 30, y: 80, wall: -1 }), - Event.cases.JumpPressed.make({ at: 30, y: 80, wall: 1 }), - Event.cases.Landed.make({ impact: 12, axis: 0, at: 60 }), + EventValue.JumpPressed({ at: 30, y: 80, wall: -1 }), + EventValue.JumpPressed({ at: 30, y: 80, wall: 1 }), + EventValue.Landed({ impact: 12, axis: 0, at: 60 }), ...pauseAndReset ] return motion.path === "Character.locomotion.Playing.Airborne.motion.Jumping" ? [ - Event.cases.ApexReached.make({ y: 50 }), - Event.cases.DownPressed.make({ at: 40 }), + EventValue.ApexReached({ y: 50 }), + EventValue.DownPressed({ at: 40 }), ...airborne ] : motion.path === "Character.locomotion.Playing.Airborne.motion.Falling" - ? [Event.cases.DownPressed.make({ at: 40 }), ...airborne] + ? [EventValue.DownPressed({ at: 40 }), ...airborne] : airborne } @@ -166,20 +180,20 @@ describe("platformer history integration", () => { const ducking = yield* Machine.plan( CharacterMachine, initial.state, - Event.cases.DownPressed.make({ at: 10 }) + CharacterEvents.DownPressed({ at: 10 }) ) const beforePause = playingSnapshot(ducking.next) const paused = yield* Machine.plan( CharacterMachine, ducking.next, - Event.cases.Pause.make({ at: 20 }) + CharacterEvents.Pause({ at: 20 }) ) const pausedLocomotion = paused.next.states.locomotion.state expect(pausedLocomotion.path).toBe("Character.locomotion.Paused") expect(pausedLocomotion.value).toEqual({ _tag: "Paused", pausedAt: 20 }) - const resumed = yield* Machine.plan(CharacterMachine, paused.next, Event.cases.Resume.make({})) + const resumed = yield* Machine.plan(CharacterMachine, paused.next, CharacterEvents.Resume()) expect(playingSnapshot(resumed.next)).toEqual(beforePause) expect(resumed.next.states.facing.state.path).toBe("Character.facing.Right") expect(resumed.next.states.contact.state.path).toBe("Character.contact.NoWall") @@ -192,19 +206,19 @@ describe("platformer history integration", () => { const airborne = yield* Machine.plan( CharacterMachine, initial.state, - Event.cases.JumpPressed.make({ at: 100, y: 207, wall: -1 }) + CharacterEvents.JumpPressed({ at: 100, y: 207, wall: -1 }) ) const falling = yield* Machine.plan( CharacterMachine, airborne.next, - Event.cases.ApexReached.make({ y: 91 }) + CharacterEvents.ApexReached({ y: 91 }) ) const beforePause = playingSnapshot(falling.next) const paused = yield* Machine.plan( CharacterMachine, falling.next, - Event.cases.Pause.make({ at: 180 }) + CharacterEvents.Pause({ at: 180 }) ) expect(paused.next.history?.["Character.locomotion.Playing.resume"]?.active).toEqual([ "Character", @@ -217,7 +231,7 @@ describe("platformer history integration", () => { "Character.locomotion.Playing.Airborne.airJump.AirJumpGroundLock" ]) - const resumed = yield* Machine.plan(CharacterMachine, paused.next, Event.cases.Resume.make({})) + const resumed = yield* Machine.plan(CharacterMachine, paused.next, CharacterEvents.Resume()) const restored = playingSnapshot(resumed.next) expect(restored).toEqual(beforePause) diff --git a/examples/platformer/src/machine.ts b/examples/platformer/src/machine.ts index 851c66b..12b21c0 100644 --- a/examples/platformer/src/machine.ts +++ b/examples/platformer/src/machine.ts @@ -121,13 +121,18 @@ const initialCharacter = () => .contact.from((contact) => contact.NoWall.from()) ) -export const CharacterMachine = Machine.make({ +const definition = Machine.make({ id: "PlatformerCharacter", states: CharacterStates.states, events: [Event], internalEvents: [InternalEvent], initial: initialCharacter -}).handle({ +}) + +export const CharacterEvents = Machine.events(definition) +const InternalEvents = Machine.internalEvents(definition) + +export const CharacterMachine = definition.handle({ Character: { on: { Reset: { @@ -223,7 +228,7 @@ export const CharacterMachine = Machine.make({ } }, Landing: { - invoke: Machine.after("140 millis", InternalEvent.cases.LandingSettled.make({}), { + invoke: Machine.after("140 millis", InternalEvents.LandingSettled(), { id: "landing-settle" }), on: { @@ -254,8 +259,8 @@ export const CharacterMachine = Machine.make({ const push = awayFrom(event.wall) enqueue.raise( push === 0 - ? InternalEvent.cases.TryAirJump.make({ at: event.at }) - : InternalEvent.cases.WallJump.make({ at: event.at, push }) + ? InternalEvents.TryAirJump({ at: event.at }) + : InternalEvents.WallJump({ at: event.at, push }) ) } }, @@ -319,7 +324,7 @@ export const CharacterMachine = Machine.make({ }, states: { AirJumpGroundLock: { - invoke: Machine.after("120 millis", InternalEvent.cases.AirJumpUnlocked.make({}), { + invoke: Machine.after("120 millis", InternalEvents.AirJumpUnlocked(), { id: "ground-air-jump-unlock" }), on: { @@ -330,7 +335,7 @@ export const CharacterMachine = Machine.make({ } }, AirJumpWallLock: { - invoke: Machine.after("240 millis", InternalEvent.cases.AirJumpUnlocked.make({}), { + invoke: Machine.after("240 millis", InternalEvents.AirJumpUnlocked(), { id: "wall-air-jump-unlock" }), on: { @@ -345,7 +350,7 @@ export const CharacterMachine = Machine.make({ TryAirJump: { targets: ["Character.locomotion.Playing.Airborne.airJump.AirJumpSpent"], transition: ({ event, target }, enqueue) => { - enqueue.raise(InternalEvent.cases.DoubleJump.make({ at: event.at })) + enqueue.raise(InternalEvents.DoubleJump({ at: event.at })) return target.local.AirJumpSpent.from() } } diff --git a/examples/playground/src/examples/examples.test.ts b/examples/playground/src/examples/examples.test.ts index 22150c9..72971a3 100644 --- a/examples/playground/src/examples/examples.test.ts +++ b/examples/playground/src/examples/examples.test.ts @@ -5,17 +5,17 @@ import { Effect } from "effect" import { MicrowaveEvent, MicrowaveMachine } from "./microwave/machine.ts" import { TrafficLightMachine } from "./traffic-light/machine.ts" import { TurnstileEvent, TurnstileMachine } from "./turnstile/machine.ts" -import { SharedMachine, SharedMachineEvent } from "./worker-tabs/machine.ts" +import { SharedMachine, SharedTransportEvents } from "./worker-tabs/machine.ts" describe("playground machines", () => { it.effect("accepts only the command enabled by the current turnstile state", () => Effect.gen(function*() { const trace = yield* MachineTest.run(TurnstileMachine, { events: [ - TurnstileEvent.cases.GatePushed.make({}), - TurnstileEvent.cases.CoinInserted.make({}), - TurnstileEvent.cases.CoinInserted.make({}), - TurnstileEvent.cases.GatePushed.make({}) + Machine.event(TurnstileMachine, TurnstileEvent.cases.GatePushed), + Machine.event(TurnstileMachine, TurnstileEvent.cases.CoinInserted), + Machine.event(TurnstileMachine, TurnstileEvent.cases.CoinInserted), + Machine.event(TurnstileMachine, TurnstileEvent.cases.GatePushed) ] }) @@ -46,11 +46,11 @@ describe("playground machines", () => { Effect.gen(function*() { const trace = yield* MachineTest.run(MicrowaveMachine, { events: [ - MicrowaveEvent.cases.PowerPressed.make({}), - MicrowaveEvent.cases.DoorOpened.make({}), - MicrowaveEvent.cases.PowerPressed.make({}), - MicrowaveEvent.cases.DoorClosed.make({}), - MicrowaveEvent.cases.PowerPressed.make({}) + Machine.event(MicrowaveMachine, MicrowaveEvent.cases.PowerPressed), + Machine.event(MicrowaveMachine, MicrowaveEvent.cases.DoorOpened), + Machine.event(MicrowaveMachine, MicrowaveEvent.cases.PowerPressed), + Machine.event(MicrowaveMachine, MicrowaveEvent.cases.DoorClosed), + Machine.event(MicrowaveMachine, MicrowaveEvent.cases.PowerPressed) ] }) @@ -67,9 +67,9 @@ describe("playground machines", () => { Effect.gen(function*() { const trace = yield* MachineTest.run(SharedMachine, { events: [ - SharedMachineEvent.cases.Started.make({}), - SharedMachineEvent.cases.Incremented.make({}), - SharedMachineEvent.cases.Synchronized.make({ active: false, count: 12 }) + SharedTransportEvents.Started(), + SharedTransportEvents.Incremented(), + SharedTransportEvents.Synchronized({ active: false, count: 12 }) ] }) diff --git a/examples/playground/src/examples/media-player/MediaPlayerPage.tsx b/examples/playground/src/examples/media-player/MediaPlayerPage.tsx index 8ccb719..2167d88 100644 --- a/examples/playground/src/examples/media-player/MediaPlayerPage.tsx +++ b/examples/playground/src/examples/media-player/MediaPlayerPage.tsx @@ -3,7 +3,7 @@ import { Match } from "effect" import { useCallback, useEffect, useState } from "react" import { ExamplePage } from "../../components/ExamplePage.tsx" import { mediaPlayerAtom, mediaPlayerViewAtom, registerMediaPlayerElement } from "./atoms.ts" -import { MediaPlayerEvent } from "./schemas.ts" +import { MediaPlayerEvents } from "./definition.ts" interface AudioSource { readonly name: string @@ -75,7 +75,7 @@ export function MediaPlayerPage() { if (file === undefined) return const next = { name: file.name, url: URL.createObjectURL(file) } setSource(next) - send(MediaPlayerEvent.cases.SourceSelected.make({ url: next.url })) + send(MediaPlayerEvents.SourceSelected({ url: next.url })) }} /> @@ -86,16 +86,16 @@ export function MediaPlayerPage() { ref={registerAudioElement} className="media-audio-element" preload="auto" - onWaiting={() => send(MediaPlayerEvent.cases.MediaWaiting.make({}))} - onCanPlay={() => send(MediaPlayerEvent.cases.MediaCanPlay.make({}))} + onWaiting={() => send(MediaPlayerEvents.MediaWaiting())} + onCanPlay={() => send(MediaPlayerEvents.MediaCanPlay())} onError={({ currentTarget }) => - send(MediaPlayerEvent.cases.MediaFailed.make({ + send(MediaPlayerEvents.MediaFailed({ message: currentTarget.error?.message ?? "The selected audio file could not be loaded" }))} onTimeUpdate={({ currentTarget }) => - send(MediaPlayerEvent.cases.TimeUpdated.make({ currentTime: currentTarget.currentTime }))} + send(MediaPlayerEvents.TimeUpdated({ currentTime: currentTarget.currentTime }))} onEnded={({ currentTarget }) => - send(MediaPlayerEvent.cases.PlaybackEnded.make({ currentTime: currentTarget.currentTime }))} + send(MediaPlayerEvents.PlaybackEnded({ currentTime: currentTarget.currentTime }))} />
@@ -116,21 +116,21 @@ export function MediaPlayerPage() { @@ -164,7 +164,7 @@ export function MediaPlayerPage() { step="0.01" value={settings.volume} onChange={({ currentTarget }) => - send(MediaPlayerEvent.cases.VolumeChanged.make({ + send(MediaPlayerEvents.VolumeChanged({ volume: currentTarget.valueAsNumber }))} /> @@ -177,8 +177,8 @@ export function MediaPlayerPage() { onChange={({ currentTarget }) => send( currentTarget.checked - ? MediaPlayerEvent.cases.MuteRequested.make({}) - : MediaPlayerEvent.cases.UnmuteRequested.make({}) + ? MediaPlayerEvents.MuteRequested() + : MediaPlayerEvents.UnmuteRequested() )} /> @@ -187,7 +187,7 @@ export function MediaPlayerPage() {