Compare commits

...

1 Commits

15 changed files with 194 additions and 185 deletions

View File

@ -194,7 +194,7 @@ ${joinItems(sa.captureBinders.map(binderTypeGuard(t)), '\n')}
});
x(ctx.parser.atStatement, (s, t) => {
return t`(((${ctx.argDecl(t, 'currentSyndicateTarget', '__SYNDICATE__.Ref')}) => {${walk(s.body)}})(${walk(s.target)}));`;
return t`(((${ctx.argDecl(t, 'currentSyndicateTarget', '__SYNDICATE__.Cap')}) => {${walk(s.body)}})(${walk(s.target)}));`;
});
x(ctx.parser.createExpression, (s, t) => {
@ -280,7 +280,7 @@ ${joinItems(sa.captureBinders.map(binderTypeGuard(t)), '\n')}
const fns = JSON.stringify(s.fields.map(f => f.id.text));
const formatBinder = (b: Binder) => t`${[b.id]}: ${b.type ?? '__SYNDICATE__.AnyValue'}`;
const fs = ctx.typescript
? t`<{${commaJoin(s.fields.map(formatBinder))}}, __SYNDICATE__.Ref>`
? t`<{${commaJoin(s.fields.map(formatBinder))}}, __SYNDICATE__.Cap>`
: '';
return t`const ${[s.label]} = __SYNDICATE__.Record.makeConstructor${fs}()(${maybeWalk(s.wireName) ?? l}, ${fns});`;
});

View File

@ -9,7 +9,11 @@ import { ActionDescription, StructuredTask, TaskAction } from './task.js';
import { randomId } from './randomid.js';
import * as Q from '../gen/queuedTasks.js';
export type AnyValue = Value<Ref>;
export interface Cap {
toRef(): Ref | undefined;
}
export type AnyValue = Value<Cap>;
//---------------------------------------------------------------------------
@ -17,7 +21,7 @@ if ('stackTraceLimit' in Error) {
Error.stackTraceLimit = Infinity;
}
export type Assertion = Value<Ref>;
export type Assertion = Value<Cap>;
export type Handle = number;
export type ExitReason = null | { ok: true } | { ok: false, err: unknown };
export type LocalAction = () => void;
@ -29,19 +33,17 @@ export interface Entity {
assert(assertion: Assertion, handle: Handle): void;
retract(handle: Handle): void;
message(body: Assertion): void;
sync(peer: Ref): void;
sync(peer: Cap): void;
data?: unknown;
}
export type Cap = Ref;
export interface Ref {
readonly relay: Facet;
readonly target: Partial<Entity>;
readonly attenuation?: Caveat[];
}
export class RefImpl implements Ref {
export class RefImpl implements Ref, Cap {
readonly relay: Facet;
readonly target: Partial<Entity>;
readonly attenuation?: Caveat[];
@ -64,16 +66,24 @@ export class RefImpl implements Ref {
if ('sync' in this.target) sig = sig + 'S';
return `${this.relay.idChain()}<${sig}>${entityRepr}`;
}
toRef(): Ref | undefined {
return this;
}
}
//---------------------------------------------------------------------------
export function isCap(v: any): v is Cap {
return 'toRef' in v && typeof(v.toRef) === 'function';
}
export function isRef(v: any): v is Ref {
return 'relay' in v && v.relay instanceof Facet && 'target' in v;
}
export function toRef(_v: any): Ref | undefined {
return isRef(_v) ? _v : void 0;
return isCap(_v) ? _v.toRef() : void 0;
}
export function assertionFrom(a: Assertable): Assertion {
@ -84,7 +94,7 @@ export function assertionFrom(a: Assertable): Assertion {
}
}
type OutboundAssertion = { handle: Handle, peer: Ref, established: boolean };
type OutboundAssertion = { handle: Handle, peer: Cap, established: boolean };
type OutboundMap = Map<Handle, OutboundAssertion>;
let nextActorId = 0;
@ -205,7 +215,7 @@ export class Facet {
_halfLink(other: Facet): void {
const h = nextHandle++;
const e = { handle: h, peer: { relay: other, target: new StopOnRetract() }, established: true };
const e = { handle: h, peer: new RefImpl(other, new StopOnRetract()), established: true };
this.outbound.set(h, e);
}
@ -265,7 +275,7 @@ export class StopOnRetract implements Partial<Entity> {
data = STOP_ON_RETRACT;
}
export function _sync_impl(e: Partial<Entity>, peer: Ref): void {
export function _sync_impl(e: Partial<Entity>, peer: Cap): void {
e.sync ? e.sync!(peer) : Turn.active.message(peer, true);
}
@ -279,7 +289,7 @@ export class Turn {
return Turn.active.activeFacet;
}
static ref<T extends Partial<Entity>>(e: T): Ref {
static ref<T extends Partial<Entity>>(e: T): Cap {
return Turn.active.ref(e);
}
@ -324,7 +334,7 @@ export class Turn {
this._activeFacet = saved;
}
ref<T extends Partial<Entity>>(e: T): Ref {
ref<T extends Partial<Entity>>(e: T): Cap {
return new RefImpl(this.activeFacet, e);
}
@ -359,7 +369,7 @@ export class Turn {
// ^ we trust initialAssertions, so can use `!` safely
const newActor = Actor.__unsafeNew(this.activeFacet.actor.space, newOutbound);
const detail: Q.OptionalAny<Ref> = 'detail' in bootProc ? Q.OptionalAny.some(bootProc.detail) : Q.OptionalAny.none();
const detail: Q.OptionalAny<Cap> = 'detail' in bootProc ? Q.OptionalAny.some(bootProc.detail) : Q.OptionalAny.none();
const spawningFacet = this.activeFacet;
this.enqueue(spawningFacet,
() => {
@ -370,7 +380,7 @@ export class Turn {
});
},
() => {
const a = new KeyedSet<number, Ref>();
const a = new KeyedSet<number, Cap>();
initialAssertions.forEach(h => a.add(h));
return Q.ActionDescription.spawnActor({ detail, initialAssertions: a });
});
@ -423,11 +433,11 @@ export class Turn {
}
assertDataflow(assertionFunction: () => {
target: Ref | undefined,
target: Cap | undefined,
assertion: Assertable | undefined
}) {
let handle: Handle | undefined = void 0;
let target: Ref | undefined = void 0;
let target: Cap | undefined = void 0;
let assertion: Assertable | undefined = void 0;
this.dataflow(() => {
let {target: nextTarget, assertion: nextAssertion} = assertionFunction();
@ -439,17 +449,19 @@ export class Turn {
});
}
assert(ref: Ref, assertion: Assertable): Handle {
assert(cap: Cap, assertion: Assertable): Handle {
const h = nextHandle++;
this._assert(ref, assertion, h);
this._assert(cap, assertion, h);
return h;
}
_assert(ref: Ref, assertable: Assertable, h: Handle) {
_assert(cap: Cap, assertable: Assertable, h: Handle) {
const ref = cap.toRef();
if (ref === void 0) return;
const assertion = assertionFrom(assertable);
const a = runRewrites(ref.attenuation, assertion);
if (a !== null) {
const e = { handle: h, peer: ref, established: false };
const e = { handle: h, peer: cap, established: false };
this.activeFacet.outbound.set(h, e);
this.enqueue(ref.relay,
() => {
@ -457,7 +469,7 @@ export class Turn {
ref.target.assert?.(a, h);
},
() => Q.ActionDescription.assert({
target: ref,
target: cap,
handle: h,
assertion,
}));
@ -472,21 +484,22 @@ export class Turn {
}
}
replace(ref: Ref | undefined, h: Handle | undefined, assertion: Assertable | undefined): Handle | undefined {
const newHandle = (assertion === void 0 || ref === void 0)
replace(cap: Cap | undefined, h: Handle | undefined, assertion: Assertable | undefined): Handle | undefined {
const newHandle = (assertion === void 0 || cap === void 0)
? void 0
: this.assert(ref, assertion);
: this.assert(cap, assertion);
this.retract(h);
return newHandle;
}
_retract(e: OutboundAssertion): void {
this.activeFacet.outbound.delete(e.handle);
this.enqueue(e.peer.relay,
const ref = e.peer.toRef()!;
this.enqueue(ref.relay,
() => {
if (e.established) {
e.established = false;
e.peer.target.retract?.(e.handle);
ref.target.retract?.(e.handle);
}
},
() => Q.ActionDescription.retract({
@ -495,27 +508,34 @@ export class Turn {
}));
}
sync(ref: Ref): Promise<void> {
return new Promise(resolve => this._sync(ref, this.ref({ message() { resolve() } })));
sync(cap: Cap): Promise<void> {
return new Promise(resolve => this._sync(cap, this.ref({ message() { resolve() } })));
}
_sync(ref: Ref, peer: Ref): void {
this.enqueue(ref.relay,
() => _sync_impl(ref.target, peer),
() => Q.ActionDescription.sync({
target: ref,
callback: peer,
}));
_sync(cap: Cap, peer: Cap): void {
const ref = cap.toRef();
if (ref === void 0) {
this.message(peer, true);
} else {
this.enqueue(ref.relay,
() => _sync_impl(ref.target, peer),
() => Q.ActionDescription.sync({
target: cap,
callback: peer,
}));
}
}
message(ref: Ref, assertable: Assertable): void {
message(cap: Cap, assertable: Assertable): void {
const ref = cap.toRef();
if (ref === void 0) return;
const assertion = assertionFrom(assertable);
const a = runRewrites(ref.attenuation, assertion);
if (a !== null) {
this.enqueue(ref.relay,
() => ref.target.message?.(assertion),
() => Q.ActionDescription.message({
target: ref,
target: cap,
assertion,
}));
}
@ -579,3 +599,9 @@ function stopIfInertAfter(a: LocalAction): LocalAction {
Q.ActionDescription.inertCheck);
};
}
export const INERT_CAP: Cap & Ref = {
relay: Actor.boot(() => Turn.active.stop()).root,
target: {},
toRef(): Ref | undefined { return this; },
};

View File

@ -3,7 +3,7 @@
import { IdentityMap, KeyedDictionary, stringify } from '@preserves/core';
import { Index, IndexObserver } from './skeleton.js';
import { Actor, AnyValue, Assertion, Entity, Facet, Handle, LocalAction, Ref, Turn } from './actor.js';
import { Actor, AnyValue, Assertion, Cap, Entity, Facet, Handle, LocalAction, Turn } from './actor.js';
import { Observe, toObserve } from '../gen/dataspace.js';
import * as P from '../gen/dataspacePatterns.js';
@ -16,10 +16,10 @@ export type DataspaceOptions = {
};
export class DataspaceObserver implements IndexObserver<Turn> {
readonly captureMap = new KeyedDictionary<Array<AnyValue>, Handle, Ref>();
readonly captureMap = new KeyedDictionary<Array<AnyValue>, Handle, Cap>();
constructor(
public readonly target: Ref,
public readonly target: Cap,
) {}
onAssert(captures: Assertion[], t: Turn) {
@ -49,7 +49,7 @@ export class Dataspace implements Partial<Entity> {
readonly options: DataspaceOptions;
readonly index = new Index();
readonly handleMap = new IdentityMap<Handle, Assertion>();
readonly observerMap = new IdentityMap<Ref, DataspaceObserver>();
readonly observerMap = new IdentityMap<Cap, DataspaceObserver>();
readonly data = this;
constructor(options?: DataspaceOptions) {
@ -95,7 +95,7 @@ export class Dataspace implements Partial<Entity> {
this.index.deliverMessage(v, Turn.active);
}
static boot(bootProc: (ds: Ref) => void, options?: DataspaceOptions): Actor {
static boot(bootProc: (ds: Cap) => void, options?: DataspaceOptions): Actor {
return Actor.boot(() => {
Turn.activeFacet.preventInertCheck();
const ds = Turn.active.ref(new Dataspace(options));
@ -137,6 +137,6 @@ export function assertionFacetObserver(f: (a: Assertion) => void, inertOk: boole
};
}
export function assertObserve(ds: Ref, pattern: P.Pattern, e: Partial<Entity>): Handle {
export function assertObserve(ds: Cap, pattern: P.Pattern, e: Partial<Entity>): Handle {
return Turn.active.assert(ds, Observe({ pattern, observer: Turn.ref(e) }));
}

View File

@ -2,7 +2,7 @@
/// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { canonicalString, Dictionary, is, Record, RecordConstructorInfo, Value } from '@preserves/core';
import { AnyValue, Ref } from './actor.js';
import { AnyValue, Cap } from './actor.js';
import * as P from '../gen/dataspacePatterns.js';
export type Path = Array<AnyValue>;
@ -217,7 +217,7 @@ export function drop_lit(p: P.Pattern): AnyValue | null {
case 'DCompound':
switch (p.value._variant) {
case 'rec': {
const v = [] as unknown as Record<AnyValue, AnyValue[], Ref>;
const v = [] as unknown as Record<AnyValue, AnyValue[], Cap>;
v.label = p.value.label;
p.value.fields.forEach(tt => v.push(walk(tt)));
return v;
@ -225,7 +225,7 @@ export function drop_lit(p: P.Pattern): AnyValue | null {
case 'arr':
return p.value.items.map(walk);
case 'dict': {
const v = new Dictionary<Ref, AnyValue>();
const v = new Dictionary<Cap, AnyValue>();
p.value.entries.forEach((pp, key) => v.set(key, walk(pp)));
return v;
}
@ -253,5 +253,5 @@ export function arr(... patterns: P.Pattern[]): P.Pattern {
}
export function dict(... entries: [AnyValue, P.Pattern][]): P.Pattern {
return P.Pattern.DCompound(P.DCompound.dict(new Dictionary<Ref, P.Pattern>(entries)));
return P.Pattern.DCompound(P.DCompound.dict(new Dictionary<Cap, P.Pattern>(entries)));
}

View File

@ -1,14 +1,14 @@
/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2021-2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { AnyValue, Ref } from './actor.js';
import { AnyValue, Cap } from './actor.js';
import { Pattern, toPattern } from '../gen/dataspacePatterns.js';
import * as P from './pattern.js';
import { RecordConstructorInfo, is, Record } from '@preserves/core';
import { Meta, Type, GenType, SchemaDefinition } from '@preserves/schema';
export type QuasiValueConstructorInfo =
| { constructorInfo: RecordConstructorInfo<AnyValue, Ref> }
| { constructorInfo: RecordConstructorInfo<AnyValue, Cap> }
| { schema(): SchemaDefinition }
| { quasiValue(... args: QuasiValue[]): QuasiValue }
;

View File

@ -1,9 +1,9 @@
/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { Turn } from "./actor.js";
import { INERT_CAP, Turn } from "./actor.js";
import { Bytes, Dictionary, DoubleFloat, embed, IdentityMap, is, isEmbedded, Record, SingleFloat, Tuple, stringify } from "@preserves/core";
import type { Assertion, Handle, Ref } from "./actor.js";
import type { Assertion, Cap, Handle, Ref } from "./actor.js";
import type { SturdyValue } from "../transport/sturdy.js";
import {
@ -68,7 +68,7 @@ export function match(p: Pattern, v: Assertion): Bindings | null {
return is(p.value.value, v);
case 'PCompound': switch (p.value._variant) {
case 'rec': {
if (!Record.isRecord<Assertion, Tuple<Assertion>, Ref>(v)) return false;
if (!Record.isRecord<Assertion, Tuple<Assertion>, Cap>(v)) return false;
if (!is(p.value.label, v.label)) return false;
if (p.value.fields.length !== v.length) return false;
let index = 0;
@ -88,7 +88,7 @@ export function match(p: Pattern, v: Assertion): Bindings | null {
return true;
}
case 'dict':{
if (!Dictionary.isDictionary<Ref, Assertion>(v)) return false;
if (!Dictionary.isDictionary<Cap, Assertion>(v)) return false;
for (const [key, pp] of p.value.entries.entries()) {
const vv = v.get(key);
if (vv === void 0) return false;
@ -115,7 +115,7 @@ export function instantiate(t: Template, b: Bindings): Assertion {
throw new Error(`Attempt to attenuate non-capability: ${stringify(v)}`);
}
const r = v.embeddedValue;
return embed(attenuate(r, ... t.value.attenuation));
return embed(attenuate(r, ... t.value.attenuation) ?? INERT_CAP);
}
case 'TRef': {
const n = t.value.binding;
@ -128,7 +128,7 @@ export function instantiate(t: Template, b: Bindings): Assertion {
case 'TCompound':
switch (t.value._variant) {
case 'rec': {
const v = [] as unknown as Record<Assertion, Assertion[], Ref>;
const v = [] as unknown as Record<Assertion, Assertion[], Cap>;
v.label = t.value.label;
t.value.fields.forEach(tt => v.push(walk(tt)));
return v;
@ -136,7 +136,7 @@ export function instantiate(t: Template, b: Bindings): Assertion {
case 'arr':
return t.value.items.map(walk);
case 'dict': {
const v = new Dictionary<Ref, Assertion>();
const v = new Dictionary<Cap, Assertion>();
t.value.entries.forEach((tt, key) => v.set(key, walk(tt)));
return v;
}
@ -193,13 +193,20 @@ export function rfilter(... patterns: Pattern[]): Caveat {
return ps.length === 1 ? Caveat.Rewrite(ps[0]) : Caveat.Alts(Alts(ps));
}
export function attenuate(ref: Ref, ... a: Caveat[]): Ref {
if (a.length === 0) return ref;
return { ... ref, attenuation: [... (ref.attenuation ?? []), ... a] };
export function attenuate(cap: Cap, ... a: Caveat[]): Cap | undefined {
if (a.length === 0) return cap;
const ref = cap.toRef();
if (ref === void 0) return void 0;
const newCap: Cap & Ref = {
... ref,
attenuation: [... (ref.attenuation ?? []), ... a],
toRef(): Ref | undefined { return this; }
};
return newCap;
}
export function forwarder(ref: Ref): { proxy: Ref, revoker: Ref } {
let underlying: Ref | null = ref;
export function forwarder(cap: Cap): { proxy: Cap, revoker: Cap } {
let underlying: Cap | null = cap;
let handleMap = new IdentityMap<Handle, Handle>();
let proxy = Turn.ref({
assert(assertion: Assertion, handle: Handle): void {
@ -215,7 +222,7 @@ export function forwarder(ref: Ref): { proxy: Ref, revoker: Ref } {
if (underlying === null) return;
Turn.active.message(underlying, body);
},
sync(peer: Ref): void {
sync(peer: Cap): void {
if (underlying === null) return;
Turn.active._sync(underlying, peer);
},

View File

@ -2,7 +2,7 @@
/// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { Set, Dictionary, KeyedDictionary, IdentitySet, stringify, Value } from '@preserves/core';
import { AnyValue, Assertion, Ref } from './actor.js';
import { AnyValue, Assertion, Cap } from './actor.js';
import { Bag, ChangeDescription } from './bag.js';
import * as Stack from './stack.js';
import * as P from '../gen/dataspacePatterns.js';
@ -27,7 +27,7 @@ export interface IndexObserver<T> {
}
export class Index<T> {
readonly allAssertions: Bag<Ref> = new Bag();
readonly allAssertions: Bag<Cap> = new Bag();
readonly root: Node<T> = new Node(new Continuation(new Set()));
addObserver(pattern: P.Pattern, observer: IndexObserver<T>, parameter: T) {
@ -54,7 +54,7 @@ export class Index<T> {
}
let observerGroup = leaf.observerGroups.get(capturePaths);
if (!observerGroup) {
const cachedCaptures = new Bag<Ref, Array<AnyValue>>();
const cachedCaptures = new Bag<Cap, Array<AnyValue>>();
leaf.cachedAssertions.forEach((a) =>
cachedCaptures._items.update(projectPaths(a, capturePaths), n => n! + 1, 0));
observerGroup = new ObserverGroup(cachedCaptures);
@ -156,7 +156,7 @@ type Selector = [number, AnyValue];
class Node<T> {
readonly continuation: Continuation<T>;
readonly edges: KeyedDictionary<Selector, { [shape: string]: Node<T> }, Ref> = new KeyedDictionary();
readonly edges: KeyedDictionary<Selector, { [shape: string]: Node<T> }, Cap> = new KeyedDictionary();
constructor(continuation: Continuation<T>) {
this.continuation = continuation;
@ -266,10 +266,10 @@ class Node<T> {
}
class Continuation<T> {
readonly cachedAssertions: Set<Ref>;
readonly leafMap: KeyedDictionary<Array<Path>, Dictionary<Ref, Leaf<T>>, Ref> = new KeyedDictionary();
readonly cachedAssertions: Set<Cap>;
readonly leafMap: KeyedDictionary<Array<Path>, Dictionary<Cap, Leaf<T>>, Cap> = new KeyedDictionary();
constructor(cachedAssertions: Set<Ref>) {
constructor(cachedAssertions: Set<Cap>) {
this.cachedAssertions = cachedAssertions;
}
@ -285,8 +285,8 @@ class Continuation<T> {
}
class Leaf<T> {
readonly cachedAssertions: Set<Ref> = new Set();
readonly observerGroups: KeyedDictionary<Array<Path>, ObserverGroup<T>, Ref> = new KeyedDictionary();
readonly cachedAssertions: Set<Cap> = new Set();
readonly observerGroups: KeyedDictionary<Array<Path>, ObserverGroup<T>, Cap> = new KeyedDictionary();
isEmpty(): boolean {
return this.cachedAssertions.size === 0 && this.observerGroups.size === 0;
@ -301,10 +301,10 @@ class Leaf<T> {
}
class ObserverGroup<T> {
readonly cachedCaptures: Bag<Ref, Array<AnyValue>>;
readonly cachedCaptures: Bag<Cap, Array<AnyValue>>;
readonly observers = new IdentitySet<IndexObserver<T>>();
constructor(cachedCaptures: Bag<Ref, Array<AnyValue>>) {
constructor(cachedCaptures: Bag<Cap, Array<AnyValue>>) {
this.cachedCaptures = cachedCaptures;
}

View File

@ -1,7 +1,7 @@
/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { Actor, Assertion, Entity, Facet, Handle, Ref, Turn } from '../runtime/actor.js';
import { Assertion, Cap, Entity, Facet, Handle, INERT_CAP, Turn } from '../runtime/actor.js';
import { BytesLike, Decoder, Dictionary, embed, encode, IdentityMap, mapEmbeddeds, stringify, underlying, Value } from '@preserves/core';
import * as IO from '../gen/protocol.js';
import { wireRefEmbeddedType } from './protocol.js';
@ -16,25 +16,25 @@ export class WireSymbol {
constructor (
public side: Membrane,
public oid: IO.Oid,
public ref: Ref,
public cap: Cap,
) {}
drop(): void {
this.count--;
if (this.count === 0) {
this.side.byOid.delete(this.oid);
this.side.byRef.delete(this.ref);
this.side.byCap.delete(this.cap);
}
}
};
export class SyncPeerEntity implements Entity {
readonly relay: Relay;
readonly peer: Ref;
readonly peer: Cap;
readonly handleMap = new IdentityMap<Handle, Handle>();
pins: Array<WireSymbol> = [];
constructor(relay: Relay, peer: Ref) {
constructor(relay: Relay, peer: Cap) {
this.relay = relay;
this.peer = peer;
}
@ -54,7 +54,7 @@ export class SyncPeerEntity implements Entity {
Turn.active.message(this.peer, body);
}
sync(peer: Ref): void {
sync(peer: Cap): void {
Turn.active._sync(this.peer, peer);
}
}
@ -88,7 +88,7 @@ export class RelayEntity implements Entity {
this.send(IO.Event.Message(IO.Message(this.relay.register(null, body, null))));
}
sync(peer: Ref): void {
sync(peer: Cap): void {
const peerEntity = new SyncPeerEntity(this.relay, peer);
this.relay.grabImportedOid(this.oid, peerEntity.pins);
const ior = this.relay.rewriteRefOut(Turn.ref(peerEntity), false, peerEntity.pins);
@ -96,11 +96,11 @@ export class RelayEntity implements Entity {
}
}
export type WhichTable = "byOid" | "byRef";
export type WhichTable = "byOid" | "byCap";
export class Membrane {
readonly byOid = new IdentityMap<IO.Oid, WireSymbol>();
readonly byRef = new IdentityMap<Ref, WireSymbol>();
readonly byCap = new IdentityMap<Cap, WireSymbol>();
grab<Table extends WhichTable>(table: Table,
key: Parameters<Membrane[Table]['get']>[0],
@ -118,7 +118,7 @@ export class Membrane {
if (e === void 0) {
if (f === null) return null;
e = f();
this.byRef.set(e.ref, e);
this.byCap.set(e.cap, e);
this.byOid.set(e.oid, e);
}
if (!transient) e.count++;
@ -126,11 +126,6 @@ export class Membrane {
}
}
export const INERT_REF: Ref = {
relay: Actor.boot(() => Turn.active.stop()).root,
target: {},
};
export type PacketWriter = (bs: Uint8Array) => void;
export interface RelayOptions {
@ -139,13 +134,13 @@ export interface RelayOptions {
debug?: boolean;
trustPeer?: boolean;
initialOid?: IO.Oid;
initialRef?: Ref;
initialCap?: Cap;
nextLocalOid?: IO.Oid;
}
export class Relay {
readonly facet: Facet;
readonly selfRef: Ref;
readonly selfCap: Cap;
readonly w: PacketWriter;
readonly inboundAssertions = new IdentityMap<Handle, {
localHandle: Handle,
@ -154,7 +149,7 @@ export class Relay {
readonly outboundAssertions = new IdentityMap<Handle, Array<WireSymbol>>();
readonly exported = new Membrane();
readonly imported = new Membrane();
readonly peer: Ref | null;
readonly peer: Cap | null;
nextLocalOid: IO.Oid = 0;
pendingTurn: IO.Turn<WireRef> = [];
debug: boolean;
@ -167,7 +162,7 @@ export class Relay {
constructor(options: RelayOptions) {
this.facet = Turn.activeFacet;
this.selfRef = Turn.ref(this);
this.selfCap = Turn.ref(this);
this.w = options.packetWriter;
this.debug = options.debug ?? false;
this.trustPeer = options.trustPeer ?? true;
@ -175,8 +170,8 @@ export class Relay {
this.facet.preventInertCheck();
options.setup(this);
if (options.initialRef !== void 0) {
this.rewriteRefOut(options.initialRef, false, []);
if (options.initialCap !== void 0) {
this.rewriteRefOut(options.initialCap, false, []);
}
this.peer = (options.initialOid !== void 0)
@ -216,59 +211,62 @@ export class Relay {
pins.push(e);
}
grabExportedOid(oid: IO.Oid, pins: Array<WireSymbol>): Ref {
grabExportedOid(oid: IO.Oid, pins: Array<WireSymbol>): Cap {
const e = this.exported.grab("byOid", oid, false, null);
if (e === null) return INERT_REF;
if (e === null) return INERT_CAP;
pins.push(e);
return e.ref;
return e.cap;
}
rewriteRefOut(r: Ref, transient: boolean, pins: Array<WireSymbol>): WireRef {
if (r.target instanceof RelayEntity && r.target.relay === this) {
if (r.attenuation === void 0 || r.attenuation.length === 0) {
// No extra conditions on this reference since it was sent to us.
this.grabImportedOid(r.target.oid, pins);
return WireRef.yours({ oid: r.target.oid, attenuation: [] });
} else {
// This reference has been attenuated since it was sent to us.
// Do we trust the peer to enforce such attenuation on our behalf?
if (this.trustPeer) {
rewriteRefOut(c: Cap, transient: boolean, pins: Array<WireSymbol>): WireRef {
const r = c.toRef();
if (r !== void 0) {
if (r.target instanceof RelayEntity && r.target.relay === this) {
if (r.attenuation === void 0 || r.attenuation.length === 0) {
// No extra conditions on this reference since it was sent to us.
this.grabImportedOid(r.target.oid, pins);
return WireRef.yours({ oid: r.target.oid, attenuation: r.attenuation });
return WireRef.yours({ oid: r.target.oid, attenuation: [] });
} else {
// fall through: treat the attenuated ref as a local ref, and re-export it.
// This reference has been attenuated since it was sent to us.
// Do we trust the peer to enforce such attenuation on our behalf?
if (this.trustPeer) {
this.grabImportedOid(r.target.oid, pins);
return WireRef.yours({ oid: r.target.oid, attenuation: r.attenuation });
} else {
// fall through: treat the attenuated ref as a local ref, and re-export it.
}
}
}
}
const e = this.exported.grab(
"byRef", r, transient, () => {
"byCap", c, transient, () => {
if (transient) throw new Error("Cannot send transient reference");
return new WireSymbol(this.exported, this.nextLocalOid++, r);
return new WireSymbol(this.exported, this.nextLocalOid++, c);
});
pins.push(e);
return WireRef.mine(e.oid);
}
rewriteRefIn(n: WireRef, pins: Array<WireSymbol>): Ref {
rewriteRefIn(n: WireRef, pins: Array<WireSymbol>): Cap {
switch (n._variant) {
case 'yours': {
const e = this.exported.grab("byOid", n.oid, false, null);
if (e === null) {
return INERT_REF;
return INERT_CAP;
} else {
pins.push(e);
const r = e.ref;
const c = e.cap;
if (n.attenuation.length === 0) {
return r;
return c;
} else {
type AttenuatedRef = Ref & { __attenuations?: Dictionary<any, Ref> };
const ar = r as AttenuatedRef;
if (ar.__attenuations === void 0) {
ar.__attenuations = new Dictionary();
type AttenuatedCap = Cap & { __attenuations?: Dictionary<any, Cap> };
const ac = c as AttenuatedCap;
if (ac.__attenuations === void 0) {
ac.__attenuations = new Dictionary();
}
return ar.__attenuations.getOrSet(n.attenuation.map(fromCaveat), () =>
attenuate(r, ... n.attenuation));
return ac.__attenuations.getOrSet(n.attenuation.map(fromCaveat), () =>
attenuate(c, ... n.attenuation) ?? INERT_CAP);
}
}
}
@ -276,7 +274,7 @@ export class Relay {
const e = this.imported.grab("byOid", n.oid, false, () =>
new WireSymbol(this.imported, n.oid, Turn.ref(new RelayEntity(this, n.oid))));
pins.push(e);
return e.ref;
return e.cap;
}
}
}
@ -294,7 +292,7 @@ export class Relay {
send(remoteOid: IO.Oid, m: IO.Event<WireRef>): void {
if (this.pendingTurn.length === 0) {
Turn.active.message(this.selfRef, FLUSH);
Turn.active.message(this.selfCap, FLUSH);
}
this.pendingTurn.push(IO.TurnEvent({ oid: remoteOid, event: m }));
}
@ -343,8 +341,8 @@ export class Relay {
case 'Message': {
const [a, pins] = this.rewriteIn(m.value.body);
if (pins.length > 0) throw new Error("Cannot receive transient reference");
const r = this.exported.byOid.get(localOid)?.ref;
if (r) Turn.active.message(r, a);
const c = this.exported.byOid.get(localOid)?.cap;
if (c) Turn.active.message(c, a);
break;
}
case 'Sync': {

View File

@ -1,22 +0,0 @@
((typescript-mode
. ((eval
. (progn
;; For TIDE:
(setq tide-tsserver-executable
(concat
(let ((d (dir-locals-find-file ".")))
(if (stringp d) d (car d)))
"node_modules/typescript/lib/tsserver.js"))
;; For LSP:
(require 'lsp-javascript)
(let ((node-modules (concat
(let ((d (dir-locals-find-file ".")))
(if (stringp d) d (car d)))
"node_modules/")))
(lsp-dependency 'typescript-language-server
`(:system ,(concat node-modules
"typescript-language-server/lib/cli.mjs")))
(lsp-dependency 'typescript
`(:system ,(concat node-modules
"typescript/lib/tsserver.js")))))
))))

View File

@ -1,7 +1,7 @@
/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { Dataspace, Ref, Sturdy, Reader, Schemas, Embedded, randomId, fromJS } from "@syndicate-lang/core";
import { Dataspace, Cap, Sturdy, Reader, Schemas, Embedded, randomId, fromJS } from "@syndicate-lang/core";
import html from "@syndicate-lang/html";
import wsRelay from "@syndicate-lang/ws-relay";
import { ExampleDefinition } from './gen/example';
@ -15,7 +15,7 @@ export function main() {
});
}
function bootApp(ds: Ref) {
function bootApp(ds: Cap) {
spawn named 'app' {
at ds {
/*
@ -26,11 +26,11 @@ function bootApp(ds: Ref) {
const this_instance = randomId(16);
const route = G.Route<Ref>({
const route = G.Route<Cap>({
"transports": [fromJS(Schemas.transportAddress.WebSocket(
`ws://${document.location.hostname}:9001/`))],
"pathSteps": [G.asPathStep(fromJS(Sturdy.asSturdyRef(
new Reader<Ref>(
new Reader<Cap>(
'<ref {oid: "syndicate" sig: #[acowDB2/oI+6aSEC3YIxGg==]}>').next())))],
});

View File

@ -1,7 +1,7 @@
/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { Bytes, Observe, Ref, Turn, stringify } from "@syndicate-lang/core";
import { Bytes, Observe, Cap, Turn, stringify } from "@syndicate-lang/core";
import { QuasiValue as Q } from "@syndicate-lang/core";
import { service } from '@syndicate-lang/service';
import { Contents, File, asConfig, Config, Encoding, toEncoding } from './gen/fs.js';
@ -20,7 +20,7 @@ export function main(_argv: string[]) {
});
}
export function serve(config: Config<Ref>) {
export function serve(config: Config<Cap>) {
at config.core.dataspace {
during Observe({
"pattern": :pattern File({
@ -38,7 +38,7 @@ export function serve(config: Config<Ref>) {
}
}
function trackFile(config: Config<Ref>, relativePath: string, encoding: Encoding) {
function trackFile(config: Config<Cap>, relativePath: string, encoding: Encoding) {
const facet = Turn.activeFacet;
const absolutePath = path.resolve(config.core.path, relativePath);

View File

@ -1,7 +1,7 @@
/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { randomId, Observe, FlexMap, embed, Embedded, Ref, Turn, AnyValue } from "@syndicate-lang/core";
import { randomId, Observe, FlexMap, embed, Embedded, Cap, Turn, AnyValue } from "@syndicate-lang/core";
import { QuasiValue as Q } from "@syndicate-lang/core";
import * as P from "./protocol";
@ -11,7 +11,7 @@ export type UIFragmentRecord = ReturnType<typeof P.UIFragment>;
import { HtmlFragments } from "./html";
export * from "./html";
export function boot(ds: Ref) {
export function boot(ds: Cap) {
spawnGlobalEventFactory(ds);
spawnWindowEventFactory(ds);
spawnUIFragmentFactory(ds);
@ -34,7 +34,7 @@ export function newFragmentId() {
//---------------------------------------------------------------------------
export function spawnGlobalEventFactory(ds: Ref) {
export function spawnGlobalEventFactory(ds: Cap) {
spawn named 'GlobalEventFactory' {
at ds {
during Observe({
@ -68,7 +68,7 @@ export function spawnGlobalEventFactory(ds: Ref) {
}
}
export function spawnWindowEventFactory(ds: Ref) {
export function spawnWindowEventFactory(ds: Cap) {
spawn named 'WindowEventFactory' {
at ds {
during Observe({
@ -111,7 +111,7 @@ function isNodeOrderKey(x: any): x is NodeOrderKey {
type HandlerClosure = (event: Event) => void;
function spawnUIFragmentFactory(ds: Ref) {
function spawnUIFragmentFactory(ds: Cap) {
type RegistrationKey = [string, string]; // [selector, eventType]
spawn named 'UIFragmentFactory' {
@ -149,7 +149,7 @@ function spawnUIFragmentFactory(ds: Ref) {
removeNodes();
selector = newSelector;
html = (newHtml as Embedded<Ref>).embeddedValue.target.data as ChildNode[];
html = (newHtml as Embedded<Cap>).embeddedValue.toRef()!.target.data as ChildNode[];
orderBy = newOrderBy;
anchorNodes = (selector !== null) ? selectorMatch(document.body, selector) : [];
@ -320,7 +320,7 @@ function configureNode(n: ChildNode) {
//---------------------------------------------------------------------------
function spawnUIAttributeFactory(ds: Ref) {
function spawnUIAttributeFactory(ds: Cap) {
spawn named 'UIAttributeFactory' {
at ds {
during P.UIAttribute($selector: string, $attribute: string, $value) =>
@ -331,7 +331,7 @@ function spawnUIAttributeFactory(ds: Ref) {
}
}
function spawnUIPropertyFactory(ds: Ref) {
function spawnUIPropertyFactory(ds: Cap) {
spawn named 'UIPropertyFactory' {
at ds {
during P.UIProperty($selector: string, $property: string, $value) =>
@ -417,7 +417,7 @@ function splitClassValue(v: string | null): Array<string> {
//---------------------------------------------------------------------------
function spawnUIChangeablePropertyFactory(ds: Ref) {
function spawnUIChangeablePropertyFactory(ds: Cap) {
spawn named 'UIChangeablePropertyFactory' {
at ds {
during Observe({
@ -513,7 +513,7 @@ export class Anchor {
//---------------------------------------------------------------------------
function spawnLocationHashTracker(ds: Ref) {
function spawnLocationHashTracker(ds: Cap) {
spawn named 'LocationHashTracker' {
at ds {
field hashValue: string = '/';
@ -542,7 +542,7 @@ function spawnLocationHashTracker(ds: Ref) {
//---------------------------------------------------------------------------
function spawnAttributeUpdater(ds: Ref) {
function spawnAttributeUpdater(ds: Cap) {
spawn named 'AttributeUpdater' {
at ds {
on message P.SetAttribute($s: string, $k: string, $v: string) =>

View File

@ -25,7 +25,7 @@ export function service(handler: (args: AnyValue) => void, options?: ServiceOpti
process.stdin.on('close', () => facet.turn(() => { stop {} }));
process.stdin.on('end', () => facet.turn(() => { stop {} }));
},
initialRef: Turn.ref(assertionFacetObserver(handler)),
initialCap: Turn.ref(assertionFacetObserver(handler)),
}, options ?? {}));
});
}

View File

@ -1,7 +1,7 @@
/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { preserves, DoubleFloat, Observe, floatValue, Turn, Ref, Schemas } from "@syndicate-lang/core";
import { preserves, Cap, DoubleFloat, Observe, floatValue, Turn, Schemas } from "@syndicate-lang/core";
import { QuasiValue as Q } from "@syndicate-lang/core";
export message type PeriodicTick(interval: DoubleFloat);
@ -9,7 +9,7 @@ export message type PeriodicTick(interval: DoubleFloat);
export const LaterThan = Schemas.timer.LaterThan;
export type LaterThan = Schemas.timer.LaterThan;
export function sleep(ds: Ref, seconds: number, cb: () => void): void {
export function sleep(ds: Cap, seconds: number, cb: () => void): void {
react {
at ds {
const deadline = +(new Date()) / 1000.0 + seconds;
@ -20,7 +20,7 @@ export function sleep(ds: Ref, seconds: number, cb: () => void): void {
}
}
export function boot(ds: Ref) {
export function boot(ds: Cap) {
spawn named 'timer/PeriodicTick' {
at ds {
during Observe({

View File

@ -4,12 +4,12 @@
import {
Assertion,
Bytes,
Cap,
Dataflow,
Embedded,
IdentitySet,
Observe,
QuasiValue as Q,
Ref,
Relay,
Schemas,
Supervisor,
@ -29,11 +29,11 @@ import * as SaltyCrypto from 'salty-crypto';
type TransportState = {
addr: T.WebSocket,
control: Ref,
peer: Ref,
control: Cap,
peer: Cap,
};
export function boot(ds: Ref, debug: boolean = false) {
export function boot(ds: Cap, debug: boolean = false) {
spawn named 'transportConnector' {
at ds {
during Observe({ "pattern": :pattern G.TransportConnection({
@ -86,7 +86,7 @@ export function boot(ds: Ref, debug: boolean = false) {
});
console.log('succeed', addr.url);
at ds {
assert G.TransportConnection<Ref>({
assert G.TransportConnection<Cap>({
"addr": fromJS(addr),
"control": create controlEntity,
"resolved": G.Resolved.accepted(relay.peer!),
@ -98,7 +98,7 @@ export function boot(ds: Ref, debug: boolean = false) {
final = true;
console.log('fail', addr.url, detail);
at ds {
assert G.TransportConnection<Ref>({
assert G.TransportConnection<Cap>({
"addr": fromJS(addr),
"control": create controlEntity,
"resolved": G.Resolved.Rejected(G.Rejected(detail)),
@ -152,7 +152,7 @@ export function boot(ds: Ref, debug: boolean = false) {
}
});
field best: TransportState | null = null;
field rootPeer: Ref | null = null;
field rootPeer: Cap | null = null;
dataflow {
best.value = null;
for (const c of candidates.value) {
@ -163,7 +163,7 @@ export function boot(ds: Ref, debug: boolean = false) {
}
resolve(() => rootPeer.value, route.pathSteps, (r) => {
console.log('leaf', best.value?.addr?.url);
assert G.ResolvePath<Ref>({
assert G.ResolvePath<Cap>({
"route": route,
"addr": fromJS(best.value!.addr),
"control": best.value!.control,
@ -175,7 +175,7 @@ export function boot(ds: Ref, debug: boolean = false) {
}
function resolve(
e: () => Ref | null,
e: () => Cap | null,
steps: G.PathStep[],
k: (r: () => G.Resolved | null) => void,
) {
@ -221,7 +221,7 @@ export function boot(ds: Ref, debug: boolean = false) {
const detail0 = Q.drop_lit(detailPatValue, N.toNoisePathStepDetail);
if (!detail0) return;
const spec: N.NoiseSpec<Ref> = detail0;
const spec: N.NoiseSpec<Cap> = detail0;
const algorithms = SaltyCrypto.Noise_25519_ChaChaPoly_BLAKE2s;
const protocol =
@ -247,7 +247,7 @@ export function boot(ds: Ref, debug: boolean = false) {
});
let transportState: SaltyCrypto.TransportState | null = null;
let responderSession: Ref | null = null;
let responderSession: Cap | null = null;
let relay: Relay.Relay | null = null;
function maybeTransition(s: SaltyCrypto.TransportState | null) {
@ -273,7 +273,7 @@ export function boot(ds: Ref, debug: boolean = false) {
},
initialOid: 0,
}).peer!;
assert G.ResolvedPathStep<Ref>({
assert G.ResolvedPathStep<Cap>({
"origin": origin,
"pathStep": G.PathStep({
"stepType": N.$noise,
@ -307,7 +307,7 @@ export function boot(ds: Ref, debug: boolean = false) {
case "Rejected":
stop {
at ds {
assert G.ResolvedPathStep<Ref>({
assert G.ResolvedPathStep<Cap>({
"origin": origin,
"pathStep": G.PathStep({
"stepType": N.$noise,
@ -374,7 +374,7 @@ export function boot(ds: Ref, debug: boolean = false) {
const response = G.toResolved(e);
if (!response) return;
at ds {
assert G.ResolvedPathStep<Ref>({
assert G.ResolvedPathStep<Cap>({
"origin": origin,
"pathStep": G.PathStep({
"stepType": S.$ref,