Skip to content

Instantly share code, notes, and snippets.

@nin-jin
Last active January 26, 2023 13:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nin-jin/8a6b98a97e034339b938a433821084fb to your computer and use it in GitHub Desktop.
Save nin-jin/8a6b98a97e034339b938a433821084fb to your computer and use it in GitHub Desktop.
0.5KB optimal error-proof finegrained lazy pull reactivity with suspense and scheduler
export class Atom< Input = never, Output = unknown > extends Object {
static Track = null as null | Atom< any, any >
static Index = 0
static Timer = 0
static _plan: any
static plan() {
if( this._plan ) return
this._plan = requestAnimationFrame( ()=> {
this._plan = null
this.sync()
} )
}
static queue = [] as Atom< any, any >[]
static sync() {
for( const root of this.queue ) root.fresh()
this.queue.length = 0
}
constructor(
name: string,
public task: ( next?: Input )=> Output,
equals = Object.is
) {
super()
this[ Symbol.toStringTag ] = name
}
hold = undefined as undefined | Output | Promise< any > | Error
cond = 'πŸ”΄' as 'πŸ”΄' | 'πŸ”΅' | '🟒'
time = -1
pubs = [] as Atom< any, any >[]
subs = [] as WeakRef< Atom< any, any > >[]
sync(): Awaited< Output > {
if( Atom.Track ) {
if( this.cond === 'πŸ”΅' ) throw new Error( 'Cycle' )
Atom.Track.pubs[ Atom.Index ++ ] = this
this.subs.push( new WeakRef( Atom.Track ) )
}
this.fresh()
if( this.hold instanceof Error ) throw this.hold
if( this.hold instanceof Promise ) throw this.hold
return this.hold as any
}
async async(): Promise< Awaited< Output > > {
while( true ) {
this.fresh()
if( this.hold instanceof Error ) throw this.hold
if( this.hold instanceof Promise ) await this.hold
return this.hold
}
}
fresh() {
if( this.cond !== 'πŸ”΄' ) return this.time
for( const pub of this.pubs ) {
if( this.time < pub.fresh() ) return this.refresh()
}
if( this.pubs.length === 0 ) return this.refresh()
this.cond = '🟒'
return this.time
}
refresh() {
const track = Atom.Track
const index = Atom.Index
try {
Atom.Track = this
Atom.Index = 0
this.cond = 'πŸ”΅'
let val = this.task() as Output | Promise<any>
if( val instanceof Promise ) {
const put = this.put.bind( this )
val = val.then( put, put )
}
this.put( val )
while( Atom.Index < this.pubs.length ) this.pubs.pop()
} catch( cause: any ) {
if( cause instanceof Promise ) {
cause = cause.finally( this.stale.bind( this ) )
}
this.put( cause )
} finally {
Atom.Track = track
Atom.Index = index
}
return this.time
}
set( next: Input ) {
this.put( this.task( next ) )
}
put( next: undefined | Output | Promise< any > | Error ) {
if( this.hold !== next && !equals( this.hold, next ) ) {
this.time = ++ Atom.Timer
if( this.cond !== 'πŸ”΅' ) this.notify()
}
this.cond = '🟒'
this.hold = next
}
stale() {
if( this.cond === 'πŸ”΄' ) return
this.cond = 'πŸ”΄'
this.notify()
}
notify() {
if( this.subs.length ) {
for( const sub of this.subs ) sub.deref()?.stale()
this.subs.length = 0
} else {
Atom.plan()
Atom.queue.push( this )
}
}
}
let res = []
const numbers = Array.from( { length: 5 }, ( _, i )=> i )
const fib = n => n < 2 ? 1
: fib( n - 1 ) + fib( n - 2 )
const hard = ( n, l )=> n + fib(16)
const A = new Atom( 'A', n => n ?? 0 )
const B = new Atom( 'B', n => n ?? 0 )
const C = new Atom( 'C', () => A.sync()%2 + B.sync()%2 )
const D = new Atom( 'D', () => numbers.map( i => ({ x: i + A.sync()%2 - B.sync()%2 }) ), $mol_compare_deep )
const E = new Atom( 'E', () => hard( C.sync() + A.sync() + D.sync()[0].x, 'E' ) )
const F = new Atom( 'F', () => hard( D.sync()[2].x || B.sync(), 'F' ) )
const G = new Atom( 'G', () => C.sync() + ( C.sync() || E.sync()%2 ) + D.sync()[4].x + F.sync() )
const H = new Atom( 'H', () => res.push( hard( G.sync(), 'H' ) ) )
const I = new Atom( 'I', () => res.push( G.sync() ) )
const J = new Atom( 'J', () => res.push( hard( F.sync(), 'J' ) ) )
H.sync(); I.sync(); J.sync()
B.set(1); A.set(1+0*2); Atom.sync() // H
A.set(2+0*2); B.set(2); Atom.sync() // EH
$mol_assert_like( res, [
3201, 1604, 3196,
3204, 1607,
3201, 1604,
] )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment