@@ -11,15 +11,13 @@ import * as Deferred from "effect/Deferred"
1111import * as Effect from "effect/Effect"
1212import * as Exit from "effect/Exit"
1313import * as Fiber from "effect/Fiber"
14- import * as HashMap from "effect/HashMap"
1514import * as MutableRef from "effect/MutableRef"
1615import * as Option from "effect/Option"
1716import * as PubSub from "effect/PubSub"
1817import * as Queue from "effect/Queue"
1918import * as Ref from "effect/Ref"
2019import * as Scope from "effect/Scope"
2120import * as Stream from "effect/Stream"
22- import * as SubscriptionRef from "effect/SubscriptionRef"
2321import * as SynchronizedRef from "effect/SynchronizedRef"
2422import type * as Take from "effect/Take"
2523import { ChildAlreadyExistsError , StoppedError } from "./machineErrors.js"
@@ -56,12 +54,15 @@ export const compiledProcess: unique symbol = Symbol.for("effect/Machine/compile
5654interface ChildRegistrySnapshot {
5755 readonly closed : boolean
5856 readonly revision : number
59- readonly children : HashMap . HashMap < ChildKey , ChildEntry >
57+ readonly children : ReadonlyMap < ChildKey , ChildEntry >
6058}
6159
62- interface ChildRegistry extends ChildRegistrySnapshot {
63- readonly changes : SubscriptionRef . SubscriptionRef < ChildRegistrySnapshot > | undefined
64- readonly scope : Scope . Closeable | undefined
60+ interface ChildRegistry {
61+ closed : boolean
62+ revision : number
63+ readonly children : Map < ChildKey , ChildEntry >
64+ changes : PubSub . PubSub < ChildRegistrySnapshot > | undefined
65+ scope : Scope . Closeable | undefined
6566}
6667
6768export type RuntimeSnapshot < State , Error = never , Output = never > =
@@ -385,33 +386,43 @@ const makeChildRuntime = (
385386 self : ProcessAddress < any > ,
386387 options : StartInternalOptions
387388) : Effect . Effect < ChildRuntime > =>
388- Effect . gen ( function * ( ) {
389- const registry = yield * SynchronizedRef . make < ChildRegistry > ( {
389+ Effect . sync ( ( ) => {
390+ // Child-registry decisions are synchronous and every access below runs in
391+ // one Effect.sync / Effect.suspend step. Keep the unobserved representation
392+ // compact; a replay PubSub is installed only when childChanges is used.
393+ const registry : ChildRegistry = {
390394 closed : false ,
391395 revision : 0 ,
392- children : HashMap . empty ( ) ,
396+ children : new Map ( ) ,
393397 changes : undefined ,
394398 scope : undefined
395- } )
399+ }
396400
397401 const snapshot = ( registry : ChildRegistry ) : ChildRegistrySnapshot => ( {
398402 closed : registry . closed ,
399403 revision : registry . revision ,
400- children : registry . children
404+ children : new Map ( registry . children )
401405 } )
402406
407+ const publishRegistryChange = ( ) : void => {
408+ if ( registry . changes !== undefined ) {
409+ PubSub . publishUnsafe ( registry . changes , snapshot ( registry ) )
410+ }
411+ }
412+
403413 const close = < A , E > ( _exit : Exit . Exit < A , E > ) : Effect . Effect < void > =>
404- SynchronizedRef . modify ( registry , ( current ) => {
405- if ( current . closed ) {
406- return [ undefined , current ] as const
414+ Effect . sync ( ( ) => {
415+ if ( registry . closed ) {
416+ return undefined
407417 }
408- if ( current . scope === undefined ) {
409- return [ undefined , { ...current , closed : true } ] as const
418+ registry . closed = true
419+ if ( registry . scope === undefined ) {
420+ return undefined
410421 }
411- const children = Array . from ( HashMap . values ( current . children ) ) . flatMap ( ( entry ) =>
422+ const children = Array . from ( registry . children . values ( ) ) . flatMap ( ( entry ) =>
412423 entry . _tag === "Started" ? [ entry . ref ] : [ ]
413424 )
414- return [ { children, scope : current . scope } , { ... current , closed : true } ] as const
425+ return { children, scope : registry . scope }
415426 } ) . pipe (
416427 Effect . flatMap ( ( resources ) =>
417428 resources === undefined
@@ -426,57 +437,48 @@ const makeChildRuntime = (
426437 )
427438 )
428439
429- const getOrCreateScope : Effect . Effect < Scope . Closeable | undefined > = SynchronizedRef . modifyEffect (
430- registry ,
431- ( current ) => {
432- if ( current . closed || current . scope !== undefined ) {
433- return Effect . succeed ( [ current . scope , current ] as const )
434- }
435- return Scope . make ( "parallel" ) . pipe (
436- Effect . map ( ( scope ) => [ scope , { ...current , scope } ] as const )
437- )
440+ const getOrCreateScope : Effect . Effect < Scope . Closeable | undefined > = Effect . sync ( ( ) => {
441+ if ( registry . closed ) {
442+ return undefined
438443 }
439- )
444+ if ( registry . scope === undefined ) {
445+ registry . scope = Scope . makeUnsafe ( "parallel" )
446+ }
447+ return registry . scope
448+ } )
440449
441450 const reserve = (
442451 key : ChildKey ,
443452 token : symbol
444453 ) : Effect . Effect < boolean , ChildAlreadyExistsError > =>
445- SynchronizedRef . modifyEffect ( registry , ( current ) => {
446- if ( current . closed ) {
447- return Effect . succeed ( [ false , current ] as const )
454+ Effect . suspend ( ( ) => {
455+ if ( registry . closed ) {
456+ return Effect . succeed ( false )
448457 }
449- if ( typeof key === "string" && HashMap . has ( current . children , key ) ) {
458+ if ( typeof key === "string" && registry . children . has ( key ) ) {
450459 return Effect . fail ( new ChildAlreadyExistsError ( { id : key } ) )
451460 }
452- return Effect . succeed (
453- [
454- true ,
455- { ...current , children : HashMap . set ( current . children , key , { _tag : "Starting" , token } ) }
456- ] as const
457- )
461+ registry . children . set ( key , { _tag : "Starting" , token } )
462+ return Effect . succeed ( true )
458463 } )
459464
460465 const unregister = (
461466 key : ChildKey ,
462467 token : symbol
463468 ) : Effect . Effect < void > =>
464- SynchronizedRef . modifyEffect ( registry , ( current ) => {
465- const entry = HashMap . get ( current . children , key )
466- if ( Option . isNone ( entry ) || entry . value . token !== token ) {
467- return Effect . succeed ( [ undefined , current ] as const )
469+ Effect . sync ( ( ) => {
470+ const entry = registry . children . get ( key )
471+ if ( entry === undefined || entry . token !== token ) {
472+ return
468473 }
469474 const observable = typeof key === "string"
470- const next = {
471- ...current ,
472- revision : observable ? current . revision + 1 : current . revision ,
473- children : HashMap . remove ( current . children , key )
475+ if ( observable ) {
476+ registry . revision += 1
477+ }
478+ registry . children . delete ( key )
479+ if ( observable ) {
480+ publishRegistryChange ( )
474481 }
475- return observable && next . changes !== undefined
476- ? SubscriptionRef . set ( next . changes , snapshot ( next ) ) . pipe (
477- Effect . as ( [ undefined , next ] as const )
478- )
479- : Effect . succeed ( [ undefined , next ] as const )
480482 } )
481483
482484 const register = (
@@ -485,31 +487,26 @@ const makeChildRuntime = (
485487 ref : MachineRef < any , any , any , any > ,
486488 descriptor : ChildDescriptor | undefined
487489 ) : Effect . Effect < boolean > =>
488- SynchronizedRef . modifyEffect ( registry , ( current ) => {
489- const entry = HashMap . get ( current . children , key )
490+ Effect . sync ( ( ) => {
491+ const entry = registry . children . get ( key )
490492 if (
491- current . closed ||
492- Option . isNone ( entry ) ||
493- entry . value . _tag !== "Starting" ||
494- entry . value . token !== token
493+ registry . closed ||
494+ entry === undefined ||
495+ entry . _tag !== "Starting" ||
496+ entry . token !== token
495497 ) {
496- return Effect . succeed ( [ false , current ] as const )
498+ return false
497499 }
498500 const observable = typeof key === "string"
499- const next = {
500- ...current ,
501- revision : observable ? current . revision + 1 : current . revision ,
502- children : HashMap . set (
503- HashMap . remove ( current . children , key ) ,
504- key ,
505- { _tag : "Started" , token, descriptor, ref }
506- )
501+ if ( observable ) {
502+ registry . revision += 1
507503 }
508- return observable && next . changes !== undefined
509- ? SubscriptionRef . set ( next . changes , snapshot ( next ) ) . pipe (
510- Effect . as ( [ true , next ] as const )
511- )
512- : Effect . succeed ( [ true , next ] as const )
504+ registry . children . delete ( key )
505+ registry . children . set ( key , { _tag : "Started" , token, descriptor, ref } )
506+ if ( observable ) {
507+ publishRegistryChange ( )
508+ }
509+ return true
513510 } )
514511
515512 const matches = (
@@ -524,31 +521,47 @@ const makeChildRuntime = (
524521
525522 const get : ChildRuntime [ "get" ] = ( child ) => {
526523 const id = typeof child === "string" ? child : child . id
527- return SynchronizedRef . get ( registry ) . pipe (
528- Effect . map ( ( registry ) => {
529- if ( registry . closed ) {
530- return Option . none ( )
531- }
532- const entry = HashMap . get ( registry . children , id )
533- return Option . isSome ( entry ) && matches ( entry . value , child )
534- ? Option . some ( entry . value . ref )
535- : Option . none ( )
536- } )
537- )
524+ return Effect . sync ( ( ) => {
525+ if ( registry . closed ) {
526+ return Option . none ( )
527+ }
528+ const entry = registry . children . get ( id )
529+ return entry !== undefined && matches ( entry , child )
530+ ? Option . some ( entry . ref )
531+ : Option . none ( )
532+ } )
538533 }
539534
540535 const changes : ChildRuntime [ "changes" ] = ( child ) => {
541536 const id = typeof child === "string" ? child : child . id
542537 return Stream . unwrap (
543- SynchronizedRef . modifyEffect ( registry , ( current ) => {
544- if ( current . closed ) {
545- return Effect . succeed ( [ undefined , current ] as const )
538+ Effect . suspend ( ( ) => {
539+ if ( registry . closed ) {
540+ return Effect . succeed ( undefined )
546541 }
547- if ( current . changes !== undefined ) {
548- return Effect . succeed ( [ current . changes , current ] as const )
542+ if ( registry . changes !== undefined ) {
543+ return Effect . succeed ( registry . changes )
549544 }
550- return SubscriptionRef . make ( snapshot ( current ) ) . pipe (
551- Effect . map ( ( changes ) => [ changes , { ...current , changes } ] as const )
545+ return PubSub . unbounded < ChildRegistrySnapshot > ( { replay : 1 } ) . pipe (
546+ Effect . flatMap ( ( candidate ) =>
547+ Effect . sync ( ( ) => {
548+ if ( registry . closed ) {
549+ return [ undefined , true ] as const
550+ }
551+ if ( registry . changes !== undefined ) {
552+ return [ registry . changes , true ] as const
553+ }
554+ registry . changes = candidate
555+ PubSub . publishUnsafe ( candidate , snapshot ( registry ) )
556+ return [ candidate , false ] as const
557+ } ) . pipe (
558+ Effect . flatMap ( ( [ changes , discardCandidate ] ) =>
559+ discardCandidate
560+ ? PubSub . shutdown ( candidate ) . pipe ( Effect . as ( changes ) )
561+ : Effect . succeed ( changes )
562+ )
563+ )
564+ )
552565 )
553566 } ) . pipe (
554567 Effect . flatMap ( ( changes ) => {
@@ -559,45 +572,41 @@ const makeChildRuntime = (
559572 if ( registry . closed ) {
560573 return Option . none ( )
561574 }
562- const entry = HashMap . get ( registry . children , id )
563- return Option . isSome ( entry ) && matches ( entry . value , child )
564- ? Option . some ( entry . value . ref )
575+ const entry = registry . children . get ( id )
576+ return entry !== undefined && matches ( entry , child )
577+ ? Option . some ( entry . ref )
565578 : Option . none ( )
566579 }
567- return Effect . succeed ( SubscriptionRef . changes ( changes ) . pipe ( Stream . map ( select ) ) )
580+ return Effect . succeed ( Stream . fromPubSub ( changes ) . pipe ( Stream . map ( select ) ) )
568581 } )
569582 )
570583 )
571584 }
572585
573586 const sendTo = ( child : ChildSelector , event : unknown ) : Effect . Effect < void , StoppedError > => {
574587 const id = typeof child === "string" ? child : child . id
575- return SynchronizedRef . get ( registry ) . pipe (
576- Effect . flatMap ( ( registry ) => {
577- if ( registry . closed ) {
578- return Effect . void
579- }
580- const entry = HashMap . get ( registry . children , id )
581- return Option . isSome ( entry ) && matches ( entry . value , child )
582- ? entry . value . ref . send ( event )
583- : Effect . void
584- } )
585- )
588+ return Effect . suspend ( ( ) => {
589+ if ( registry . closed ) {
590+ return Effect . void
591+ }
592+ const entry = registry . children . get ( id )
593+ return entry !== undefined && matches ( entry , child )
594+ ? entry . ref . send ( event )
595+ : Effect . void
596+ } )
586597 }
587598
588599 const stop = ( child : ChildSelector ) : Effect . Effect < void > => {
589600 const id = typeof child === "string" ? child : child . id
590- return SynchronizedRef . get ( registry ) . pipe (
591- Effect . flatMap ( ( registry ) => {
592- if ( registry . closed ) {
593- return Effect . void
594- }
595- const entry = HashMap . get ( registry . children , id )
596- return Option . isSome ( entry ) && matches ( entry . value , child )
597- ? entry . value . ref . stop
598- : Effect . void
599- } )
600- )
601+ return Effect . suspend ( ( ) => {
602+ if ( registry . closed ) {
603+ return Effect . void
604+ }
605+ const entry = registry . children . get ( id )
606+ return entry !== undefined && matches ( entry , child )
607+ ? entry . ref . stop
608+ : Effect . void
609+ } )
601610 }
602611
603612 function spawn < ChildState , ChildEvent , ChildError , ChildRequirements , ChildOutput , ChildInitialError = never > (
0 commit comments