/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2023-2024 Tony Garnock-Jones import { Actor, Ref, Facet, LocalAction, Turn, assertionFrom } from './actor'; import type { Handle, Assertable } from './actor'; import type { Field } from './dataflow'; import * as Refl from '../gen/mirror'; import { Observe } from '../gen/dataspace'; import * as P from './pattern'; import { ActorSpace } from './space'; import { assertionFacetObserver, Dataspace } from './dataspace'; import { stringify, isEmbedded, KeyedDictionary } from '@preserves/core'; export interface Reflectable { asRef(): Ref; } /// A mirror reflects a "focus" object from a "source" space into an "image" in a *different* /// "target" space. /// /// From the point of view of the target space, the source space is a Plain Old JavaScript /// program. In all other situations (DOM events, network activity etc.) explicit entry to /// facet turns is required to operate within the target space. Mirrors should be the same. /// export class Mirror { readonly imageFacet!: Facet; private focusType!: Field; constructor ( public readonly focus: Ref, private readonly image: Ref, public readonly focusSpace: ActorSpace, t: Turn, ) { let installed = false; t.facet(() => { (this as any).imageFacet = t.activeFacet; this.focusType = t.field(Refl.TypeName.entity(), 'focusType'); if ((this.focus.target.data !== this.focusSpace) && (this.focus.relay.actor.space !== this.focusSpace)) { this.focusType.value = Refl.TypeName.external(); } else { this.focus.target.setMirror?.(this); installed = true; } t.assert(this.image, Refl.Facet({ thing: this.focus, facet: this.focus.relay.asRef() })); t.assertDataflow(() => ({ target: this.image, assertion: Refl.Type({ thing: this.focus, type: this.focusType.value }), })); }); if (installed) this.imageFacet.onStop(() => this.focus.target.setMirror?.(null)); } setFocusType(n: Refl.TypeName) { this.turn(() => this.focusType.value = n); } turn(a: LocalAction) { const t = Turn.active; (t && t.activeFacet === this.imageFacet) ? a() : Turn.for(this.imageFacet, a); } constProp(prop: Assertable): void { this.turn(() => { const attribute = assertionFrom(prop); Turn.active.assert(this.image, Refl.Attribute({ thing: this.focus, attribute })); }); } setProp( c: { [K in Name]?: Handle }, n: Name, prop: Assertable | undefined, ): void { this.turn(() => { const a = prop ? Refl.Attribute({ thing: this.focus, attribute: assertionFrom(prop) }) : void 0; const newHandle = Turn.active.replace(this.image, c[n], a); (newHandle === void 0) ? delete c[n] : c[n] = newHandle; }); } } export function spawnMirror(sourceSpace: ActorSpace, targetSpace = new ActorSpace()): Ref { let image!: Ref; Actor.boot(() => { const t = Turn.active; image = t.ref(new Dataspace({ tracer: (event, a, _ds, sig) => console.log('DS', event, stringify(a), sig), })); t.assert(image, Observe({ pattern: P.rec(Symbol.for('reflect'), P.bind()), observer: t.ref(assertionFacetObserver(a => { if (!Array.isArray(a)) return; const [thing] = a; if (!isEmbedded(thing)) return; new Mirror(thing, image, sourceSpace, Turn.active); })), })); }, void 0, targetSpace); return image; } export function _asRef( x: { _reflectableRef?: Ref, setMirror(mirror: Mirror | null): void }, facet: Facet, ): Ref { if (x._reflectableRef === void 0) { x._reflectableRef = new Ref(facet, { data: x, setMirror: m => x.setMirror(m) }); } return x._reflectableRef; }