@webqit/quantum-js
Version:
Runtime extension to JavaScript that let's us do Imperative Reactive Programming (IRP) in the very language.
94 lines (84 loc) • 3.42 kB
JavaScript
/**
* @import
*/
import Observer from '@webqit/observer';
import { _isTypeObject } from '../util.js';
import EventTarget from './EventTarget.js';
export default class Signal extends EventTarget {
subscribers = new Set;
signals = new Map;
constructor( context, type, state ) {
super();
this.context = context;
this.context?.once( () => this.abort() );
this.once( () => this.watchMode( false ) );
this.type = type;
this.state = state;
}
get name() { return [ ...this.context?.signals.keys() || [] ].find( k => this.context.signals.get( k ) === this ); }
signal( name, type = 'prop' ) {
let signal = this.signals.get( name );
if ( !signal ) {
// Initialization
signal = new Signal(
this,
type,
type === 'object' ? name : ( _isTypeObject( this.state ) ? Observer.get( this.state, name ) : undefined )
);
this.signals.set( name, signal );
// Self-start
if ( this.signals.size === 1 ) { this.watchMode(); }
// Cleanup
signal.once( () => {
// On being killed, auto-delete
this.signals.delete( name );
// On empty self-kill
if ( !this.signals.size ) { this.watchMode( false ); }
} );
}
return signal;
}
subscribe( autorun ) {
this.subscribers.add( autorun );
autorun.once( () => {
// Cleanup
this.subscribers.delete( autorun );
// On empty self-kill
if ( !this.subscribers.size ) { this.abort(); }
} );
}
watchMode( flag = true ) {
this.mutationsWatch?.abort();
if ( !flag || !this.signals.size || !_isTypeObject( this.state ) ) return;
this.mutationsWatch = Observer.observe( this.state, mutations => {
// Find subscribers and organize them by target runtime
const groupings = { map: new Map, add( autoruns, mutation ) {
for ( const autorun of autoruns ) {
if ( autorun.spec.beforeSchedule?.( mutation ) === false ) continue;
if ( !this.map.has( autorun.runtime ) ) { this.map.set( autorun.runtime, new Set ); }
this.map.get( autorun.runtime ).add( autorun );
}
} };
for ( const mutation of mutations ) {
const signal = this.signals.get( mutation.key );
if ( !signal ) continue;
groupings.add( signal.subscribers, mutation );
signal.refresh( mutation.value );
}
// Dispatch to runtimes...
const runtimesMap = !groupings.map.size ? groupings.map : [ ...groupings.map ].sort( ( a, b ) => a.$serial > b.$serial ? -1 : 1 );
//const runtimesMap = groupings.map;
for ( const [ runtime, autoruns ] of runtimesMap ) {
if ( runtime.state === 'aborted' ) continue;
runtime.schedule( ...autoruns );
}
}, { recursions: 'force-sync' } );
}
refresh( newState ) {
this.state = newState;
for ( const [ name, signal ] of this.signals ) {
signal.refresh( Observer.get( this.state ?? {}, name ) );
}
this.watchMode();
}
}