@@ -129,15 +129,16 @@ const makeProcessLogic: <
129129 return yield * Model . normalizeSnapshotEffect ( machine , entry . snapshot )
130130 }
131131 const planned = yield * internalPlanner . planInitial ( machine , ...entry . args )
132- const runtime = internalPlanner . makeLiveRuntime < Machine . EventOf < Events > , Machine . EmitOf < Emits > > (
133- machine ,
134- scope
135- )
136132 yield * internalPlanner . runCommands (
137133 planned . commands ,
138134 scope
139135 )
140- yield * internalPlanner . runEmittedEvents ( planned . emittedEvents , runtime )
136+ if ( planned . emittedEvents . length > 0 ) {
137+ yield * internalPlanner . runEmittedEvents (
138+ planned . emittedEvents ,
139+ internalPlanner . makeLiveRuntime < Machine . EventOf < Events > , Machine . EmitOf < Emits > > ( machine , scope )
140+ )
141+ }
141142 return planned . state
142143 } ) ,
143144 scope
@@ -157,64 +158,60 @@ const makeProcessLogic: <
157158 )
158159 }
159160
160- const liveRuntime = internalPlanner . makeLiveRuntime < Machine . EventOf < Events > , Machine . EmitOf < Emits > > (
161- machine ,
162- context
163- )
164-
165161 if ( ! hasInvokes ) {
166162 // A queued batch is produced entirely by this worker, so its
167163 // configuration is already validated. Drop both caches before
168164 // blocking again so idle machines retain only the public snapshot.
165+ // Keeping the loop in this generator avoids a suspended generator
166+ // per iteration; every iteration still crosses Effect boundaries,
167+ // so the Effect scheduler remains responsible for cooperative yield.
169168 let configuration : Model . ActiveConfiguration | undefined
170169 let pendingEvent : Option . Option < Machine . EventOf < Events > > = Option . none ( )
171170 let pollEvent : Effect . Effect < Option . Option < Machine . EventOf < Events > > > | undefined
172- yield * Effect . whileLoop ( {
173- while : ( ) => terminal === undefined ,
174- body : ( ) =>
175- Effect . gen ( function * ( ) {
176- const event = Option . isSome ( pendingEvent ) ? pendingEvent . value : yield * receive
177- pendingEvent = Option . none ( )
178- let planned
179- try {
180- planned = internalPlanner . planConfiguration (
181- machine ,
182- configuration ?? Model . normalizeConfigurationSync ( machine , current ) ,
183- event
184- )
185- } catch ( error ) {
186- if ( error instanceof InfiniteTransitionError || error instanceof MachineSchemaDecodeError ) {
187- return yield * error
188- }
189- throw error
190- }
191- configuration = planned . next
171+ let liveRuntime : Runtime < Machine . EventOf < Events > , Machine . EmitOf < Emits > > | undefined
172+ while ( terminal === undefined ) {
173+ const event = Option . isSome ( pendingEvent ) ? pendingEvent . value : yield * receive
174+ pendingEvent = Option . none ( )
175+ let planned
176+ try {
177+ planned = internalPlanner . planConfiguration (
178+ machine ,
179+ configuration ?? Model . normalizeConfigurationSync ( machine , current ) ,
180+ event
181+ )
182+ } catch ( error ) {
183+ if ( error instanceof InfiniteTransitionError || error instanceof MachineSchemaDecodeError ) {
184+ return yield * error
185+ }
186+ throw error
187+ }
188+ configuration = planned . next
192189
193- if ( planned . microsteps . length > 0 ) {
194- const next = Model . snapshotFromConfiguration < States > ( machine , planned . next )
195- yield * internalPlanner . runCommands ( planned . commands , context )
196- yield * setState ( next )
197- current = next
198- yield * internalPlanner . runEmittedEvents (
199- planned . emittedEvents as ReadonlyArray < Machine . EmitOf < Emits > > ,
200- liveRuntime
201- )
190+ if ( planned . microsteps . length > 0 ) {
191+ const next = Model . snapshotFromConfiguration < States > ( machine , planned . next )
192+ yield * internalPlanner . runCommands ( planned . commands , context )
193+ yield * setState ( next )
194+ current = next
195+ if ( planned . emittedEvents . length > 0 ) {
196+ yield * internalPlanner . runEmittedEvents (
197+ planned . emittedEvents as ReadonlyArray < Machine . EmitOf < Emits > > ,
198+ liveRuntime ??= internalPlanner . makeLiveRuntime ( machine , context )
199+ )
200+ }
202201
203- if ( planned . done ) {
204- terminal = { output : planned . output as Output }
205- }
206- }
202+ if ( planned . done ) {
203+ terminal = { output : planned . output as Output }
204+ }
205+ }
207206
208- if ( terminal === undefined ) {
209- pendingEvent = yield * ( pollEvent ??= Queue . poll ( mailbox ) )
210- if ( Option . isNone ( pendingEvent ) ) {
211- configuration = undefined
212- pollEvent = undefined
213- }
214- }
215- } ) ,
216- step : ( ) => undefined
217- } )
207+ if ( terminal === undefined ) {
208+ pendingEvent = yield * ( pollEvent ??= Queue . poll ( mailbox ) )
209+ if ( Option . isNone ( pendingEvent ) ) {
210+ configuration = undefined
211+ pollEvent = undefined
212+ }
213+ }
214+ }
218215
219216 if ( terminal === undefined ) {
220217 return yield * Effect . die (
@@ -414,73 +411,71 @@ const makeProcessLogic: <
414411 configuration = undefined
415412 let pendingEvent : Option . Option < Machine . EventOf < Events > > = Option . none ( )
416413 let pollEvent : Effect . Effect < Option . Option < Machine . EventOf < Events > > > | undefined
414+ let liveRuntime : Runtime < Machine . EventOf < Events > , Machine . EmitOf < Emits > > | undefined
417415
418- yield * Effect . whileLoop ( {
419- while : ( ) => terminal === undefined ,
420- body : ( ) =>
421- Effect . gen ( function * ( ) {
422- const event = Option . isSome ( pendingEvent ) ? pendingEvent . value : yield * receive
423- pendingEvent = Option . none ( )
424- let planned
425- try {
426- planned = internalPlanner . planConfiguration (
427- machine ,
428- configuration ?? Model . normalizeConfigurationSync ( machine , current ) ,
429- event
430- )
431- } catch ( error ) {
432- if ( error instanceof InfiniteTransitionError || error instanceof MachineSchemaDecodeError ) {
433- return yield * error
416+ // Match the compact non-invoke loop while retaining state-scoped
417+ // child lifecycle work at the same ordered Effect boundaries.
418+ while ( terminal === undefined ) {
419+ const event = Option . isSome ( pendingEvent ) ? pendingEvent . value : yield * receive
420+ pendingEvent = Option . none ( )
421+ let planned
422+ try {
423+ planned = internalPlanner . planConfiguration (
424+ machine ,
425+ configuration ?? Model . normalizeConfigurationSync ( machine , current ) ,
426+ event
427+ )
428+ } catch ( error ) {
429+ if ( error instanceof InfiniteTransitionError || error instanceof MachineSchemaDecodeError ) {
430+ return yield * error
431+ }
432+ throw error
433+ }
434+ configuration = planned . next
435+ if ( planned . microsteps . length > 0 ) {
436+ const changed = planned . microsteps . some ( ( step ) => step . changed )
437+ const exitPaths = planned . microsteps . flatMap ( ( step ) => step . exitPaths )
438+ const entryEvents = new Map < string , Machine . LifecycleEvent < Events > > ( )
439+ for ( const step of planned . microsteps ) {
440+ if ( step . changed ) {
441+ for ( const path of step . entryPaths ) {
442+ entryEvents . set ( path , step . event as Machine . LifecycleEvent < Events > )
434443 }
435- throw error
436444 }
437- configuration = planned . next
438- if ( planned . microsteps . length > 0 ) {
439- const changed = planned . microsteps . some ( ( step ) => step . changed )
440- const exitPaths = planned . microsteps . flatMap ( ( step ) => step . exitPaths )
441- const entryEvents = new Map < string , Machine . LifecycleEvent < Events > > ( )
442- for ( const step of planned . microsteps ) {
443- if ( step . changed ) {
444- for ( const path of step . entryPaths ) {
445- entryEvents . set ( path , step . event as Machine . LifecycleEvent < Events > )
446- }
447- }
448- }
445+ }
449446
450- const next = Model . snapshotFromConfiguration < States > ( machine , planned . next )
451- yield * internalPlanner . runCommands ( planned . commands , context )
452- if ( changed ) {
453- yield * stopInvokes ( exitPaths )
454- }
455- yield * setState ( next )
456- current = next
457- yield * internalPlanner . runEmittedEvents (
458- planned . emittedEvents as ReadonlyArray < Machine . EmitOf < Emits > > ,
459- liveRuntime
460- )
447+ const next = Model . snapshotFromConfiguration < States > ( machine , planned . next )
448+ yield * internalPlanner . runCommands ( planned . commands , context )
449+ if ( changed ) {
450+ yield * stopInvokes ( exitPaths )
451+ }
452+ yield * setState ( next )
453+ current = next
454+ if ( planned . emittedEvents . length > 0 ) {
455+ yield * internalPlanner . runEmittedEvents (
456+ planned . emittedEvents as ReadonlyArray < Machine . EmitOf < Emits > > ,
457+ liveRuntime ??= internalPlanner . makeLiveRuntime ( machine , context )
458+ )
459+ }
461460
462- if ( planned . done ) {
463- terminal = { output : planned . output as Output }
464- yield * stopAllInvokes
465- } else {
466- if ( changed ) {
467- for ( const [ path , entryEvent ] of entryEvents ) {
468- yield * startInvokes ( planned . next , [ path ] , entryEvent )
469- }
470- }
471- }
461+ if ( planned . done ) {
462+ terminal = { output : planned . output as Output }
463+ yield * stopAllInvokes
464+ } else if ( changed ) {
465+ for ( const [ path , entryEvent ] of entryEvents ) {
466+ yield * startInvokes ( planned . next , [ path ] , entryEvent )
472467 }
468+ }
469+ }
473470
474- if ( terminal === undefined ) {
475- pendingEvent = yield * ( pollEvent ??= Queue . poll ( mailbox ) )
476- if ( Option . isNone ( pendingEvent ) ) {
477- configuration = undefined
478- pollEvent = undefined
479- }
480- }
481- } ) ,
482- step : ( ) => undefined
483- } )
471+ if ( terminal === undefined ) {
472+ pendingEvent = yield * ( pollEvent ??= Queue . poll ( mailbox ) )
473+ if ( Option . isNone ( pendingEvent ) ) {
474+ configuration = undefined
475+ pollEvent = undefined
476+ }
477+ }
478+ }
484479
485480 if ( terminal === undefined ) {
486481 return yield * Effect . die (
0 commit comments