121 lines
4.4 KiB
TypeScript
121 lines
4.4 KiB
TypeScript
/// SPDX-License-Identifier: GPL-3.0-or-later
|
|
/// SPDX-FileCopyrightText: Copyright © 2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
|
|
|
import { Actor, Ref, RefImpl, 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 '../gen/dataspacePatterns';
|
|
import { ActorSpace } from './space';
|
|
import { assertionFacetObserver, Dataspace } from './dataspace';
|
|
import { stringify, isEmbedded } 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<Refl.TypeName>;
|
|
|
|
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<Name extends string>(
|
|
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.Pattern.DCompound(P.DCompound.rec({
|
|
label: Symbol.for('reflect'),
|
|
fields: [P.Pattern.DBind(P.DBind(P.Pattern.DDiscard(P.DDiscard())))],
|
|
})),
|
|
observer: t.ref(assertionFacetObserver(a => {
|
|
if (!Array.isArray(a)) return;
|
|
const [thing_embedded] = a;
|
|
if (!isEmbedded(thing_embedded)) return;
|
|
const thing = thing_embedded.embeddedValue;
|
|
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 RefImpl(facet, { data: x, setMirror: m => x.setMirror(m) });
|
|
}
|
|
return x._reflectableRef;
|
|
}
|