Skip to content

Commit d222e01

Browse files
Migrate examples to machine event constructors (#109)
1 parent d471d42 commit d222e01

16 files changed

Lines changed: 180 additions & 133 deletions

File tree

examples/platformer/src/machine.test.ts

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { describe, expect, it } from "vitest"
55
import { makeTextRenderer } from "../../../test/machine/visualization/text.ts"
66
import {
77
airJumpMode,
8+
CharacterEvents,
89
CharacterMachine,
910
type CharacterSnapshot,
1011
Event,
@@ -100,37 +101,50 @@ const laws = [
100101
resumeRestoresDeepHistory
101102
]
102103

104+
// Exploration scenarios retain decoded events for trace inspection.
105+
const EventValue = {
106+
Resume: () => Machine.event(CharacterMachine, Event.cases.Resume),
107+
Pause: (fields: { readonly at: number }) => Machine.event(CharacterMachine, Event.cases.Pause, fields),
108+
Reset: () => Machine.event(CharacterMachine, Event.cases.Reset),
109+
JumpPressed: (fields: { readonly at: number; readonly y: number; readonly wall: -1 | 0 | 1 }) =>
110+
Machine.event(CharacterMachine, Event.cases.JumpPressed, fields),
111+
Landed: (fields: { readonly impact: number; readonly axis: -1 | 0 | 1; readonly at: number }) =>
112+
Machine.event(CharacterMachine, Event.cases.Landed, fields),
113+
ApexReached: (fields: { readonly y: number }) => Machine.event(CharacterMachine, Event.cases.ApexReached, fields),
114+
DownPressed: (fields: { readonly at: number }) => Machine.event(CharacterMachine, Event.cases.DownPressed, fields)
115+
}
116+
103117
const explorationEvents = ({ snapshot }: MachineTest.ExplorationStateContext<typeof CharacterMachine>) => {
104118
const locomotion = snapshot.states.locomotion.state
105119
if (locomotion.path === "Character.locomotion.Paused") {
106-
return [Event.cases.Resume.make({})]
120+
return [EventValue.Resume()]
107121
}
108122

109-
const pauseAndReset = [Event.cases.Pause.make({ at: 70 }), Event.cases.Reset.make({})]
123+
const pauseAndReset = [EventValue.Pause({ at: 70 }), EventValue.Reset()]
110124
if (locomotion.state.path === "Character.locomotion.Playing.Grounded") {
111125
return [
112-
Event.cases.JumpPressed.make({ at: 20, y: 100, wall: -1 }),
113-
Event.cases.JumpPressed.make({ at: 20, y: 100, wall: 0 }),
114-
Event.cases.JumpPressed.make({ at: 20, y: 100, wall: 1 }),
126+
EventValue.JumpPressed({ at: 20, y: 100, wall: -1 }),
127+
EventValue.JumpPressed({ at: 20, y: 100, wall: 0 }),
128+
EventValue.JumpPressed({ at: 20, y: 100, wall: 1 }),
115129
...pauseAndReset
116130
]
117131
}
118132

119133
const motion = locomotion.state.states.motion.state
120134
const airborne = [
121-
Event.cases.JumpPressed.make({ at: 30, y: 80, wall: -1 }),
122-
Event.cases.JumpPressed.make({ at: 30, y: 80, wall: 1 }),
123-
Event.cases.Landed.make({ impact: 12, axis: 0, at: 60 }),
135+
EventValue.JumpPressed({ at: 30, y: 80, wall: -1 }),
136+
EventValue.JumpPressed({ at: 30, y: 80, wall: 1 }),
137+
EventValue.Landed({ impact: 12, axis: 0, at: 60 }),
124138
...pauseAndReset
125139
]
126140
return motion.path === "Character.locomotion.Playing.Airborne.motion.Jumping"
127141
? [
128-
Event.cases.ApexReached.make({ y: 50 }),
129-
Event.cases.DownPressed.make({ at: 40 }),
142+
EventValue.ApexReached({ y: 50 }),
143+
EventValue.DownPressed({ at: 40 }),
130144
...airborne
131145
]
132146
: motion.path === "Character.locomotion.Playing.Airborne.motion.Falling"
133-
? [Event.cases.DownPressed.make({ at: 40 }), ...airborne]
147+
? [EventValue.DownPressed({ at: 40 }), ...airborne]
134148
: airborne
135149
}
136150

@@ -166,20 +180,20 @@ describe("platformer history integration", () => {
166180
const ducking = yield* Machine.plan(
167181
CharacterMachine,
168182
initial.state,
169-
Event.cases.DownPressed.make({ at: 10 })
183+
CharacterEvents.DownPressed({ at: 10 })
170184
)
171185
const beforePause = playingSnapshot(ducking.next)
172186

173187
const paused = yield* Machine.plan(
174188
CharacterMachine,
175189
ducking.next,
176-
Event.cases.Pause.make({ at: 20 })
190+
CharacterEvents.Pause({ at: 20 })
177191
)
178192
const pausedLocomotion = paused.next.states.locomotion.state
179193
expect(pausedLocomotion.path).toBe("Character.locomotion.Paused")
180194
expect(pausedLocomotion.value).toEqual({ _tag: "Paused", pausedAt: 20 })
181195

182-
const resumed = yield* Machine.plan(CharacterMachine, paused.next, Event.cases.Resume.make({}))
196+
const resumed = yield* Machine.plan(CharacterMachine, paused.next, CharacterEvents.Resume())
183197
expect(playingSnapshot(resumed.next)).toEqual(beforePause)
184198
expect(resumed.next.states.facing.state.path).toBe("Character.facing.Right")
185199
expect(resumed.next.states.contact.state.path).toBe("Character.contact.NoWall")
@@ -192,19 +206,19 @@ describe("platformer history integration", () => {
192206
const airborne = yield* Machine.plan(
193207
CharacterMachine,
194208
initial.state,
195-
Event.cases.JumpPressed.make({ at: 100, y: 207, wall: -1 })
209+
CharacterEvents.JumpPressed({ at: 100, y: 207, wall: -1 })
196210
)
197211
const falling = yield* Machine.plan(
198212
CharacterMachine,
199213
airborne.next,
200-
Event.cases.ApexReached.make({ y: 91 })
214+
CharacterEvents.ApexReached({ y: 91 })
201215
)
202216
const beforePause = playingSnapshot(falling.next)
203217

204218
const paused = yield* Machine.plan(
205219
CharacterMachine,
206220
falling.next,
207-
Event.cases.Pause.make({ at: 180 })
221+
CharacterEvents.Pause({ at: 180 })
208222
)
209223
expect(paused.next.history?.["Character.locomotion.Playing.resume"]?.active).toEqual([
210224
"Character",
@@ -217,7 +231,7 @@ describe("platformer history integration", () => {
217231
"Character.locomotion.Playing.Airborne.airJump.AirJumpGroundLock"
218232
])
219233

220-
const resumed = yield* Machine.plan(CharacterMachine, paused.next, Event.cases.Resume.make({}))
234+
const resumed = yield* Machine.plan(CharacterMachine, paused.next, CharacterEvents.Resume())
221235
const restored = playingSnapshot(resumed.next)
222236
expect(restored).toEqual(beforePause)
223237

examples/platformer/src/machine.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,18 @@ const initialCharacter = () =>
121121
.contact.from((contact) => contact.NoWall.from())
122122
)
123123

124-
export const CharacterMachine = Machine.make({
124+
const definition = Machine.make({
125125
id: "PlatformerCharacter",
126126
states: CharacterStates.states,
127127
events: [Event],
128128
internalEvents: [InternalEvent],
129129
initial: initialCharacter
130-
}).handle({
130+
})
131+
132+
export const CharacterEvents = Machine.events(definition)
133+
const InternalEvents = Machine.internalEvents(definition)
134+
135+
export const CharacterMachine = definition.handle({
131136
Character: {
132137
on: {
133138
Reset: {
@@ -223,7 +228,7 @@ export const CharacterMachine = Machine.make({
223228
}
224229
},
225230
Landing: {
226-
invoke: Machine.after("140 millis", InternalEvent.cases.LandingSettled.make({}), {
231+
invoke: Machine.after("140 millis", InternalEvents.LandingSettled(), {
227232
id: "landing-settle"
228233
}),
229234
on: {
@@ -254,8 +259,8 @@ export const CharacterMachine = Machine.make({
254259
const push = awayFrom(event.wall)
255260
enqueue.raise(
256261
push === 0
257-
? InternalEvent.cases.TryAirJump.make({ at: event.at })
258-
: InternalEvent.cases.WallJump.make({ at: event.at, push })
262+
? InternalEvents.TryAirJump({ at: event.at })
263+
: InternalEvents.WallJump({ at: event.at, push })
259264
)
260265
}
261266
},
@@ -319,7 +324,7 @@ export const CharacterMachine = Machine.make({
319324
},
320325
states: {
321326
AirJumpGroundLock: {
322-
invoke: Machine.after("120 millis", InternalEvent.cases.AirJumpUnlocked.make({}), {
327+
invoke: Machine.after("120 millis", InternalEvents.AirJumpUnlocked(), {
323328
id: "ground-air-jump-unlock"
324329
}),
325330
on: {
@@ -330,7 +335,7 @@ export const CharacterMachine = Machine.make({
330335
}
331336
},
332337
AirJumpWallLock: {
333-
invoke: Machine.after("240 millis", InternalEvent.cases.AirJumpUnlocked.make({}), {
338+
invoke: Machine.after("240 millis", InternalEvents.AirJumpUnlocked(), {
334339
id: "wall-air-jump-unlock"
335340
}),
336341
on: {
@@ -345,7 +350,7 @@ export const CharacterMachine = Machine.make({
345350
TryAirJump: {
346351
targets: ["Character.locomotion.Playing.Airborne.airJump.AirJumpSpent"],
347352
transition: ({ event, target }, enqueue) => {
348-
enqueue.raise(InternalEvent.cases.DoubleJump.make({ at: event.at }))
353+
enqueue.raise(InternalEvents.DoubleJump({ at: event.at }))
349354
return target.local.AirJumpSpent.from()
350355
}
351356
}

examples/playground/src/examples/examples.test.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@ import { Effect } from "effect"
55
import { MicrowaveEvent, MicrowaveMachine } from "./microwave/machine.ts"
66
import { TrafficLightMachine } from "./traffic-light/machine.ts"
77
import { TurnstileEvent, TurnstileMachine } from "./turnstile/machine.ts"
8-
import { SharedMachine, SharedMachineEvent } from "./worker-tabs/machine.ts"
8+
import { SharedMachine, SharedTransportEvents } from "./worker-tabs/machine.ts"
99

1010
describe("playground machines", () => {
1111
it.effect("accepts only the command enabled by the current turnstile state", () =>
1212
Effect.gen(function*() {
1313
const trace = yield* MachineTest.run(TurnstileMachine, {
1414
events: [
15-
TurnstileEvent.cases.GatePushed.make({}),
16-
TurnstileEvent.cases.CoinInserted.make({}),
17-
TurnstileEvent.cases.CoinInserted.make({}),
18-
TurnstileEvent.cases.GatePushed.make({})
15+
Machine.event(TurnstileMachine, TurnstileEvent.cases.GatePushed),
16+
Machine.event(TurnstileMachine, TurnstileEvent.cases.CoinInserted),
17+
Machine.event(TurnstileMachine, TurnstileEvent.cases.CoinInserted),
18+
Machine.event(TurnstileMachine, TurnstileEvent.cases.GatePushed)
1919
]
2020
})
2121

@@ -46,11 +46,11 @@ describe("playground machines", () => {
4646
Effect.gen(function*() {
4747
const trace = yield* MachineTest.run(MicrowaveMachine, {
4848
events: [
49-
MicrowaveEvent.cases.PowerPressed.make({}),
50-
MicrowaveEvent.cases.DoorOpened.make({}),
51-
MicrowaveEvent.cases.PowerPressed.make({}),
52-
MicrowaveEvent.cases.DoorClosed.make({}),
53-
MicrowaveEvent.cases.PowerPressed.make({})
49+
Machine.event(MicrowaveMachine, MicrowaveEvent.cases.PowerPressed),
50+
Machine.event(MicrowaveMachine, MicrowaveEvent.cases.DoorOpened),
51+
Machine.event(MicrowaveMachine, MicrowaveEvent.cases.PowerPressed),
52+
Machine.event(MicrowaveMachine, MicrowaveEvent.cases.DoorClosed),
53+
Machine.event(MicrowaveMachine, MicrowaveEvent.cases.PowerPressed)
5454
]
5555
})
5656

@@ -67,9 +67,9 @@ describe("playground machines", () => {
6767
Effect.gen(function*() {
6868
const trace = yield* MachineTest.run(SharedMachine, {
6969
events: [
70-
SharedMachineEvent.cases.Started.make({}),
71-
SharedMachineEvent.cases.Incremented.make({}),
72-
SharedMachineEvent.cases.Synchronized.make({ active: false, count: 12 })
70+
SharedTransportEvents.Started(),
71+
SharedTransportEvents.Incremented(),
72+
SharedTransportEvents.Synchronized({ active: false, count: 12 })
7373
]
7474
})
7575

examples/playground/src/examples/media-player/MediaPlayerPage.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Match } from "effect"
33
import { useCallback, useEffect, useState } from "react"
44
import { ExamplePage } from "../../components/ExamplePage.tsx"
55
import { mediaPlayerAtom, mediaPlayerViewAtom, registerMediaPlayerElement } from "./atoms.ts"
6-
import { MediaPlayerEvent } from "./schemas.ts"
6+
import { MediaPlayerEvents } from "./definition.ts"
77

88
interface AudioSource {
99
readonly name: string
@@ -75,7 +75,7 @@ export function MediaPlayerPage() {
7575
if (file === undefined) return
7676
const next = { name: file.name, url: URL.createObjectURL(file) }
7777
setSource(next)
78-
send(MediaPlayerEvent.cases.SourceSelected.make({ url: next.url }))
78+
send(MediaPlayerEvents.SourceSelected({ url: next.url }))
7979
}}
8080
/>
8181
</label>
@@ -86,16 +86,16 @@ export function MediaPlayerPage() {
8686
ref={registerAudioElement}
8787
className="media-audio-element"
8888
preload="auto"
89-
onWaiting={() => send(MediaPlayerEvent.cases.MediaWaiting.make({}))}
90-
onCanPlay={() => send(MediaPlayerEvent.cases.MediaCanPlay.make({}))}
89+
onWaiting={() => send(MediaPlayerEvents.MediaWaiting())}
90+
onCanPlay={() => send(MediaPlayerEvents.MediaCanPlay())}
9191
onError={({ currentTarget }) =>
92-
send(MediaPlayerEvent.cases.MediaFailed.make({
92+
send(MediaPlayerEvents.MediaFailed({
9393
message: currentTarget.error?.message ?? "The selected audio file could not be loaded"
9494
}))}
9595
onTimeUpdate={({ currentTarget }) =>
96-
send(MediaPlayerEvent.cases.TimeUpdated.make({ currentTime: currentTarget.currentTime }))}
96+
send(MediaPlayerEvents.TimeUpdated({ currentTime: currentTarget.currentTime }))}
9797
onEnded={({ currentTarget }) =>
98-
send(MediaPlayerEvent.cases.PlaybackEnded.make({ currentTime: currentTarget.currentTime }))}
98+
send(MediaPlayerEvents.PlaybackEnded({ currentTime: currentTarget.currentTime }))}
9999
/>
100100

101101
<div className="media-player-layout">
@@ -116,21 +116,21 @@ export function MediaPlayerPage() {
116116
<button
117117
type="button"
118118
disabled={!canPlay || source === undefined}
119-
onClick={() => send(MediaPlayerEvent.cases.PlayRequested.make({}))}
119+
onClick={() => send(MediaPlayerEvents.PlayRequested())}
120120
>
121121
Play
122122
</button>
123123
<button
124124
type="button"
125125
disabled={!canPause}
126-
onClick={() => send(MediaPlayerEvent.cases.PauseRequested.make({}))}
126+
onClick={() => send(MediaPlayerEvents.PauseRequested())}
127127
>
128128
Pause
129129
</button>
130130
<button
131131
type="button"
132132
disabled={!canRestart}
133-
onClick={() => send(MediaPlayerEvent.cases.RestartRequested.make({}))}
133+
onClick={() => send(MediaPlayerEvents.RestartRequested())}
134134
>
135135
Restart
136136
</button>
@@ -164,7 +164,7 @@ export function MediaPlayerPage() {
164164
step="0.01"
165165
value={settings.volume}
166166
onChange={({ currentTarget }) =>
167-
send(MediaPlayerEvent.cases.VolumeChanged.make({
167+
send(MediaPlayerEvents.VolumeChanged({
168168
volume: currentTarget.valueAsNumber
169169
}))}
170170
/>
@@ -177,8 +177,8 @@ export function MediaPlayerPage() {
177177
onChange={({ currentTarget }) =>
178178
send(
179179
currentTarget.checked
180-
? MediaPlayerEvent.cases.MuteRequested.make({})
181-
: MediaPlayerEvent.cases.UnmuteRequested.make({})
180+
? MediaPlayerEvents.MuteRequested()
181+
: MediaPlayerEvents.UnmuteRequested()
182182
)}
183183
/>
184184
</label>
@@ -187,7 +187,7 @@ export function MediaPlayerPage() {
187187
<select
188188
value={settings.playbackRate}
189189
onChange={({ currentTarget }) =>
190-
send(MediaPlayerEvent.cases.PlaybackRateChanged.make({
190+
send(MediaPlayerEvents.PlaybackRateChanged({
191191
playbackRate: Number(currentTarget.value)
192192
}))}
193193
>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Machine } from "@typeonce/effect-machine"
2+
import { initialAudioSettings, MediaPlayerEvent, MediaPlayerInternalEvent, MediaPlayerStates } from "./schemas.ts"
3+
4+
const initialPlayer = () =>
5+
MediaPlayerStates.initial.Player.from((player) =>
6+
player
7+
.transport.from((transport) => transport.Empty.from())
8+
.settings.from((settings) =>
9+
settings.Audible.from({
10+
volume: initialAudioSettings.volume,
11+
playbackRate: initialAudioSettings.playbackRate
12+
})
13+
)
14+
)
15+
16+
export const MediaPlayerDefinition = Machine.make({
17+
id: "MediaPlayer",
18+
states: MediaPlayerStates.states,
19+
events: [MediaPlayerEvent],
20+
internalEvents: [MediaPlayerInternalEvent],
21+
initial: initialPlayer
22+
})
23+
24+
export const MediaPlayerEvents = Machine.events(MediaPlayerDefinition)
25+
export const MediaPlayerInternalEvents = Machine.internalEvents(MediaPlayerDefinition)

0 commit comments

Comments
 (0)