char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
b2fc926
main
1import { 2Array , 3ArrayPrototypePush as push , 4ArrayPrototypeSplice as splice , 5Map , 6MapPrototypeDelete as drop , 7MapPrototypeGet as get , 8MapPrototypeSet as set , 9TypeError , 10uncurryThis , 11} from "ext:core/primordials" ; 12import { markPromiseHandled , safePromise } from "ext:core/promises" ; 13import { op_signal_bind , op_signal_poll , op_signal_unbind } from "ext:core/ops" ; 14import { array , required , usv } from "ext:web/idl.js" ; 15import { result } from "ext:file/errors.js" ; 16import { reportErrorValue } from "ext:web/events.js" ; 17 18const indexOf = uncurryThis ( Array . prototype . indexOf ); 19const slice = uncurryThis ( Array . prototype . slice ); 20type SignalListener = () => void ; 21const bindings = new Map < string , SignalListener []>(); 22let pumping = false ; 23// Signals with at least one listener. A bind racing the pump's final 24// poll restarts the loop below instead of stranding its delivery. 25let boundSignals = 0 ; 26 27/** Delivers each bound signal to its listeners in binding order. */ 28async function pump () :Promise < void > { 29try { 30while ( true ) { 31const name = result ( await safePromise ( op_signal_poll ())); 32// The isolate binds nothing anymore: end, releasing the event loop. 33if ( name === null ) return ; 34const listeners = get ( bindings , name ); 35if ( listeners === undefined ) continue ; 36const snapshot = slice ( listeners , 0 ); 37for ( let i = 0 ; i < snapshot . length ; i ++ ) { 38// A listener removed mid-dispatch stops firing, like EventTarget. 39// Copied to a local first: calling it off the array would set 40// `this` to the array. 41const listener = snapshot [ i ]; 42if ( indexOf ( listeners , listener ) === - 1 ) continue ; 43try { 44listener (); 45} catch ( error ) { 46reportErrorValue ( error ); 47} 48} 49} 50} finally { 51pumping = false ; 52if ( boundSignals > 0 ) ensurePump (); 53} 54} 55function ensurePump () :void { 56if ( pumping ) return ; 57pumping = true ; 58markPromiseHandled ( pump ()); 59} 60const addSignalListener = { 61addSignalListener ( signal :string , listener :SignalListener ) :void { 62required ( arguments . length , 2 ); 63const name = usv ( signal ); 64if ( typeof listener !== "function" ) { 65throw new TypeError ( "Signal listener must be a function" ); 66} 67let listeners = get ( bindings , name ); 68if ( listeners !== undefined && indexOf ( listeners , listener ) !== - 1 ) { 69return ; 70} 71if ( listeners === undefined ) { 72// Binding once per signal keeps the Rust binding count (one per 73// isolate per signal) balanced with unbind-on-empty below. 74result ( op_signal_bind ( name )); 75listeners = array (); 76set ( bindings , name , listeners ); 77boundSignals ++ ; 78} 79push ( listeners , listener ); 80ensurePump (); 81}, 82}. addSignalListener ; 83const removeSignalListener = { 84removeSignalListener ( signal :string , listener :SignalListener ) :void { 85required ( arguments . length , 2 ); 86const name = usv ( signal ); 87const listeners = get ( bindings , name ); 88if ( listeners === undefined ) return ; 89const at = indexOf ( listeners , listener ); 90if ( at === - 1 ) return ; 91splice ( listeners , at , 1 ); 92if ( listeners . length === 0 ) { 93drop ( bindings , name ); 94boundSignals -- ; 95op_signal_unbind ( name ); 96} 97}, 98}. removeSignalListener ; 99export { addSignalListener , removeSignalListener };