import type { InputConnectionEval } from './inputType' import { writable, type Writable } from 'svelte/store' import { deepEqual } from 'fast-equals' import sum from 'hash-sum' export interface Subscriber { id?: string next(v: T, force?: boolean): void } export interface Observable { subscribe(x: Subscriber, previousValue: T): () => void } export interface Output extends Observable { set(x: T, force?: boolean): void peak(): T | any | undefined } export interface Input extends Subscriber { peak(): T | any | undefined } export type World = { outputsById: Record>> connect: ( connection: InputConnectionEval, next: (x: T, force?: boolean) => void, id: string, previousValue: any ) => Input stateId: Writable newOutput: (id: string, name: string, previousValue: T) => Output } export function buildWorld(context: Record): Writable { const newWorld = buildObservableWorld() const stateId = writable(0) let writableWorld: Writable | undefined = undefined const outputsById: Record>> = { ctx: Object.fromEntries( Object.entries(context).map(([k, v]) => { return [k, newWorld.newOutput('ctx', k, stateId, v)] }) ), state: {} } function newOutput(id: string, name: string, previousValue: T) { if (outputsById[id]?.[name]) { return outputsById[id][name] } let o = newWorld.newOutput(id, name, stateId, previousValue) if (!outputsById[id]) { outputsById[id] = {} } outputsById[id][name] = o writableWorld?.update((x) => x) return o } stateId.update((x) => x + 1) writableWorld = writable({ outputsById, connect: newWorld.connect, stateId, newOutput }) return writableWorld } export function buildObservableWorld() { const observables: Record> = {} function connect( connection: InputConnectionEval, next: (x: T) => void, id: string, previousValue: T ): Input { // console.log('connect', connection, id, previousValue) if (!connection) { return { id, peak: () => undefined, next: () => {} } } const input = cachedInput(next, id) const { componentId, id: idc } = connection let obs = observables[`${componentId}.${idc}`] if (!obs) { if (componentId != 'row' && componentId != 'iter') { console.warn('Observable at ' + componentId + '.' + idc + ' not found') } return { peak: () => undefined, next: () => {} } } obs.subscribe(input, previousValue) return input } function newOutput( id: string, name: string, state: Writable, previousValue: T ): Output { const output = settableOutput(state, previousValue) observables[`${id}.${name}`] = output return output } return { connect, newOutput } } export function cachedInput(nextParam: (x: T, force?: boolean) => void, id?: string): Input { let value: T | undefined = undefined function peak(): T | undefined { return value } function next(x: T, force?: boolean): void { value = x nextParam(x, force) } return { id, peak, next } } export function settableOutput(state: Writable, previousValue: T): Output { let value: T | undefined = previousValue const subscribers: Subscriber[] = [] function subscribe(x: Subscriber, npreviousValue: any) { // console.log('subscribe', x, npreviousValue) let currentSubscriber = subscribers.findIndex((y) => y === x || (y.id && y.id === x.id)) if (currentSubscriber == -1) { subscribers.push(x) } else { subscribers[currentSubscriber] = x } // Send the current value to the new subscriber if it already exists if (value !== undefined && !deepEqual(value, npreviousValue)) { x.next(value, false) } // return a callback to unsubscribe return () => { const index = subscribers.findIndex((y) => y === x || (y.id && y.id === x.id)) if (index !== -1) { subscribers.splice(index, 1) } } } let lastHash: any = undefined function set(x: T, force: boolean = false) { let newHash = typeof x === 'object' ? sum(x) : x if (lastHash != newHash || force) { lastHash = newHash state.update((x) => x + 1) if (typeof x === 'object') { if (Array.isArray(x)) { value = [...x] as any } else { value = Object.assign({}, x) } } else { value = x } // console.log('set', value, force, subscribers) subscribers.forEach((x) => x.next(value!, force)) } } function peak(): T | undefined { return value } return { subscribe, set, peak } }