Finally can run the UI
This commit is contained in:
parent
115b457173
commit
49ef33f113
3 changed files with 95 additions and 159 deletions
|
|
@ -11,6 +11,7 @@ import { PriorityQueue } from "./priority_queue"
|
|||
export type SignalRuntime = {
|
||||
// Named signals (there are also nameless ones - and the distinction matters for garbage collection)
|
||||
store: Map<SignalName, SignalRuntime.DAGNode>,
|
||||
// TODO: consider tracking root nodes (and even the signal-closures with 0 bindings). Would be useful to build a global visualization of the DAG.
|
||||
};
|
||||
|
||||
export type ExternalObserver = (state: Value) => void;
|
||||
|
|
@ -18,6 +19,7 @@ export type UnsubscribeCapability = () => void;
|
|||
|
||||
export namespace SignalRuntime {
|
||||
// TODO: This is a terrible name. Looking for a new name... Just don't call it `Node`, which clashes with the builtin ts `Node` type all the time.
|
||||
// TODO: consider naming this `SignalId` actually...
|
||||
export type DAGNode = {
|
||||
signalName?: SignalName,
|
||||
signal: SignalValue,
|
||||
|
|
@ -53,7 +55,7 @@ export function externalSubscribe(node: SignalRuntime.DAGNode, observer: Externa
|
|||
}
|
||||
|
||||
export function getNode(program: Program, signalName: SignalName): SignalRuntime.DAGNode {
|
||||
const maybeNode = program.signal_runtimeNew.store.get(signalName);
|
||||
const maybeNode = program.signal_runtime.store.get(signalName);
|
||||
if (maybeNode === undefined) {
|
||||
// TODO: Make this into a proper error
|
||||
throw Error(`Signal '${signalName}' not found!`);
|
||||
|
|
@ -63,19 +65,19 @@ export function getNode(program: Program, signalName: SignalName): SignalRuntime
|
|||
}
|
||||
|
||||
export function spawnSignal(program: Program, signalName: SignalName, expr: SignalExpr): SignalRuntime.DAGNode {
|
||||
const maybeNode = program.signal_runtimeNew.store.get(signalName);
|
||||
const maybeNode = program.signal_runtime.store.get(signalName);
|
||||
if (maybeNode !== undefined) {
|
||||
// TODO: Make this into a proper error
|
||||
throw Error(`Attempt to spawn a signal '${signalName}' that already exists`);
|
||||
}
|
||||
const node = eval_signal_expression(program, Env.nil(), expr);
|
||||
node.signalName = signalName;
|
||||
program.signal_runtimeNew.store.set(signalName, node);
|
||||
program.signal_runtime.store.set(signalName, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
export function spawnSource(program: Program, signalName: SignalName, initValue: Value): [SignalRuntime.DAGNode, (value: Value) => void] {
|
||||
const maybeNode = program.signal_runtimeNew.store.get(signalName);
|
||||
const maybeNode = program.signal_runtime.store.get(signalName);
|
||||
if (maybeNode !== undefined) {
|
||||
// TODO: Make this into a proper error
|
||||
throw Error(`Attempt to spawn a signal '${signalName}' that already exists`);
|
||||
|
|
@ -90,7 +92,7 @@ export function spawnSource(program: Program, signalName: SignalName, initValue:
|
|||
externalOutputs: [],
|
||||
currentValue: initValue,
|
||||
};
|
||||
program.signal_runtimeNew.store.set(signalName, node);
|
||||
program.signal_runtime.store.set(signalName, node);
|
||||
|
||||
|
||||
function setValue(value: Value) {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import { eval_signal_expression, makeTickSignal, signal, Signal, SignalId, SignalRuntime } from "./eval/signal";
|
||||
import { ThrownRuntimeError } from "./eval/error";
|
||||
import { Env, Value } from "./eval/value";
|
||||
import { Expr, FunctionName, SignalName, ProductPattern, SignalExpr } from "./expr";
|
||||
import { installPrimitives } from "./primitive";
|
||||
import { eval_expr } from "./eval/evaluator";
|
||||
import { SignalRuntime as SignalRuntimeNew} from "./eval/signalValue"
|
||||
import { eval_signal_expression, spawnTick, spawnSignal, SignalRuntime, getNode, spawnSource } from "./eval/signalValue"
|
||||
|
||||
export type Timestamp = number;
|
||||
|
||||
type SignalId = SignalRuntime.DAGNode;
|
||||
|
||||
export type Program = {
|
||||
function_definitions: Map<FunctionName, FunctionDefinition>,
|
||||
function_definition_order: FunctionName[],
|
||||
|
|
@ -15,9 +16,6 @@ export type Program = {
|
|||
signal_definitions: Map<SignalName, SignalDefinition>,
|
||||
signal_definition_order: SignalName[],
|
||||
|
||||
signal_runtimeNew: SignalRuntimeNew,
|
||||
|
||||
// TODO: Get rid of the old Runtime
|
||||
signal_runtime: SignalRuntime,
|
||||
};
|
||||
|
||||
|
|
@ -74,7 +72,7 @@ type CellDefinition = {
|
|||
|
||||
body: Expr,
|
||||
|
||||
signalId?: SignalId,
|
||||
cell?: [SignalId, (value: Value) => void],
|
||||
|
||||
// Metadata
|
||||
createdAt: Timestamp,
|
||||
|
|
@ -82,7 +80,7 @@ type CellDefinition = {
|
|||
}
|
||||
|
||||
type PrimitiveSignalDefinition = {
|
||||
name: FunctionName,
|
||||
name: SignalName,
|
||||
signalId: SignalId,
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +127,6 @@ export namespace Program {
|
|||
function_definitions: new Map(),
|
||||
function_definition_order: [],
|
||||
|
||||
signal_runtimeNew: SignalRuntimeNew.make(),
|
||||
signal_runtime: SignalRuntime.make(),
|
||||
signal_definitions: new Map(),
|
||||
signal_definition_order: [],
|
||||
|
|
@ -167,11 +164,11 @@ export namespace Program {
|
|||
}
|
||||
|
||||
function install_primitive_signals(program: Program) {
|
||||
install_primitive_signal(program, "tick",makeTickSignal(1000));
|
||||
const signalId = spawnTick(program, "tick", 1000);
|
||||
install_primitive_signal(program, "tick", signalId);
|
||||
}
|
||||
|
||||
function install_primitive_signal(program: Program, name: SignalName, signal: Signal<Value>) {
|
||||
const signalId = attachNewSignal(program, signal);
|
||||
function install_primitive_signal(program: Program, name: SignalName, signalId: SignalId) {
|
||||
const def: SignalDefinition = {
|
||||
tag: "primitive",
|
||||
def: { name, signalId }
|
||||
|
|
@ -180,19 +177,14 @@ export namespace Program {
|
|||
}
|
||||
|
||||
// may throw `ThrownRuntimeError`. This is used during initialization.
|
||||
export function get_or_create_signal(program: Program, name: SignalName): Signal<Value> {
|
||||
export function get_or_create_signal(program: Program, name: SignalName): SignalId {
|
||||
const sigDef = lookup_signal_definition(program, name);
|
||||
|
||||
switch (sigDef.tag) {
|
||||
case "user": {
|
||||
const def = sigDef.def;
|
||||
if (def.signalId !== undefined) {
|
||||
const signal = SignalRuntime.getSignal(program.signal_runtime, def.signalId);
|
||||
if (signal === undefined) {
|
||||
throw ThrownRuntimeError.error({ tag: "SignalDefinitionHasSignalIdWithoutSignal", name, });
|
||||
} else {
|
||||
return signal;
|
||||
}
|
||||
return def.signalId;
|
||||
}
|
||||
|
||||
// We need to create the signal for the first time.
|
||||
|
|
@ -202,90 +194,28 @@ export namespace Program {
|
|||
def.is_initializing = true;
|
||||
|
||||
try {
|
||||
const newSignal = eval_signal_expression(program, Env.nil(), def.body);
|
||||
const newId = attachNewSignal(program, newSignal);
|
||||
program.signal_runtime.store.set(newId, newSignal);
|
||||
const newId = spawnSignal(program, name, def.body);
|
||||
def.signalId = newId;
|
||||
|
||||
return newSignal;
|
||||
return newId;
|
||||
} finally {
|
||||
def.is_initializing = false;
|
||||
}
|
||||
}
|
||||
case "cell": {
|
||||
const def = sigDef.def;
|
||||
if (def.signalId !== undefined) {
|
||||
const signal = SignalRuntime.getSignal(program.signal_runtime, def.signalId);
|
||||
if (signal === undefined) {
|
||||
throw ThrownRuntimeError.error({ tag: "SignalDefinitionHasSignalIdWithoutSignal", name, });
|
||||
} else {
|
||||
return signal;
|
||||
}
|
||||
if (def.cell !== undefined) {
|
||||
return def.cell[0];
|
||||
}
|
||||
|
||||
// We need to create the cell-signal for the first time.
|
||||
const initialValue = eval_expr(program, Env.nil(), def.body);
|
||||
const sig = signal(initialValue);
|
||||
const id = attachNewSignal(program, sig);
|
||||
def.signalId = id;
|
||||
return sig;
|
||||
const cell = spawnSource(program, name, initialValue)
|
||||
def.cell = cell;
|
||||
return cell[0];
|
||||
}
|
||||
case "primitive": {
|
||||
const def = sigDef.def;
|
||||
const signal = SignalRuntime.getSignal(program.signal_runtime, def.signalId);
|
||||
if (signal === undefined) {
|
||||
throw ThrownRuntimeError.error({ tag: "SignalDefinitionHasSignalIdWithoutSignal", name, });
|
||||
} else {
|
||||
return signal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function attachNewSignal(program: Program, signal: Signal<Value>): SignalId {
|
||||
const newId = SignalRuntime.generateSignalId(program.signal_runtime);
|
||||
program.signal_runtime.store.set(newId, signal);
|
||||
return newId;
|
||||
}
|
||||
|
||||
// TODO: Is this necessary? Maybe `get_or_create_signal` is sufficient for all cases.
|
||||
// may throw `ThrownRuntimeError`. This is used by evaluator.
|
||||
export function lookup_signal(program: Program, name: SignalName): Signal<Value> {
|
||||
const sigDef = lookup_signal_definition(program, name);
|
||||
|
||||
switch (sigDef.tag) {
|
||||
case "user": {
|
||||
const def = sigDef.def;
|
||||
if (def.signalId === undefined) {
|
||||
throw ThrownRuntimeError.error({ tag: "SignalLookupFailure", name, });
|
||||
}
|
||||
const signal = SignalRuntime.getSignal(program.signal_runtime, def.signalId);
|
||||
if (signal === undefined){
|
||||
throw ThrownRuntimeError.error({ tag: "SignalLookupFailure", name, });
|
||||
} else {
|
||||
return signal;
|
||||
}
|
||||
}
|
||||
case "cell": {
|
||||
const def = sigDef.def;
|
||||
if (def.signalId === undefined) {
|
||||
throw ThrownRuntimeError.error({ tag: "SignalLookupFailure", name, });
|
||||
}
|
||||
const signal = SignalRuntime.getSignal(program.signal_runtime, def.signalId);
|
||||
if (signal === undefined){
|
||||
throw ThrownRuntimeError.error({ tag: "SignalLookupFailure", name, });
|
||||
} else {
|
||||
return signal;
|
||||
}
|
||||
}
|
||||
case "primitive": {
|
||||
const def = sigDef.def;
|
||||
const signal = SignalRuntime.getSignal(program.signal_runtime, def.signalId);
|
||||
if (signal === undefined) {
|
||||
throw ThrownRuntimeError.error({ tag: "SignalDefinitionHasSignalIdWithoutSignal", name, });
|
||||
} else {
|
||||
return signal;
|
||||
}
|
||||
return sigDef.def.signalId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -431,7 +361,7 @@ export namespace Program {
|
|||
raw_body: string,
|
||||
}
|
||||
|
||||
export function updateSignal(
|
||||
export function updateSignal(
|
||||
program: Program,
|
||||
name: SignalName,
|
||||
{ body, raw_body }: UpdateSignal
|
||||
|
|
@ -468,32 +398,33 @@ export function updateSignal(
|
|||
return Result.ok(undefined);
|
||||
}
|
||||
|
||||
export function deleteSignal(program: Program, name: SignalName): Result<void> {
|
||||
const existingEntry = program.signal_definitions.get(name);
|
||||
// TODO: This needs careful thought about transitive dependencies of a signal
|
||||
// export function deleteSignal(program: Program, name: SignalName): Result<void> {
|
||||
// const existingEntry = program.signal_definitions.get(name);
|
||||
|
||||
if (!existingEntry) {
|
||||
return Result.error({ tag: "SignalNotFound", name } as any);
|
||||
}
|
||||
// if (!existingEntry) {
|
||||
// return Result.error({ tag: "SignalNotFound", name } as any);
|
||||
// }
|
||||
|
||||
if (existingEntry.tag === "primitive") {
|
||||
return Result.error({ tag: "CannotDeletePrimitiveSignal", name } as any);
|
||||
}
|
||||
// if (existingEntry.tag === "primitive") {
|
||||
// return Result.error({ tag: "CannotDeletePrimitiveSignal", name } as any);
|
||||
// }
|
||||
|
||||
program.signal_definitions.delete(name);
|
||||
// program.signal_definitions.delete(name);
|
||||
|
||||
const orderIndex = program.signal_definition_order.indexOf(name);
|
||||
if (orderIndex !== -1) {
|
||||
program.signal_definition_order.splice(orderIndex, 1);
|
||||
}
|
||||
// const orderIndex = program.signal_definition_order.indexOf(name);
|
||||
// if (orderIndex !== -1) {
|
||||
// program.signal_definition_order.splice(orderIndex, 1);
|
||||
// }
|
||||
|
||||
// TODO:
|
||||
// Note: The old signal instance still exists in program.signal_runtime.store
|
||||
// We technically leak memory here unless we also remove it from the runtime store.
|
||||
// However, since other signals might still depend on that ID,
|
||||
// leaving it is actually safer for now to prevent crashes.
|
||||
// // TODO:
|
||||
// // Note: The old signal instance still exists in program.signal_runtime.store
|
||||
// // We technically leak memory here unless we also remove it from the runtime store.
|
||||
// // However, since other signals might still depend on that ID,
|
||||
// // leaving it is actually safer for now to prevent crashes.
|
||||
|
||||
return Result.ok(undefined);
|
||||
}
|
||||
// return Result.ok(undefined);
|
||||
// }
|
||||
|
||||
// === Cells ===
|
||||
export type CreateCell = {
|
||||
|
|
@ -502,34 +433,35 @@ export function updateSignal(
|
|||
raw_body: string,
|
||||
}
|
||||
|
||||
// TODO: refactor pending
|
||||
export function registerCell(
|
||||
program: Program,
|
||||
{ name, body, raw_body }: CreateCell
|
||||
): Result<void> {
|
||||
|
||||
if (program.signal_definitions.has(name)) {
|
||||
return Result.error({ tag: "DuplicateSignalName", name });
|
||||
}
|
||||
// if (program.signal_definitions.has(name)) {
|
||||
// return Result.error({ tag: "DuplicateSignalName", name });
|
||||
// }
|
||||
|
||||
const now: Timestamp = Date.now();
|
||||
// const now: Timestamp = Date.now();
|
||||
|
||||
// TODO: MAY THROW RuntimeError. Should probably switch to `eval_start` - and extend the `Program.Error` with runtime errors.
|
||||
const initialValue = eval_expr(program, Env.nil(), body);
|
||||
// // TODO: MAY THROW RuntimeError. Should probably switch to `eval_start` - and extend the `Program.Error` with runtime errors.
|
||||
// const initialValue = eval_expr(program, Env.nil(), body);
|
||||
|
||||
const sig = signal(initialValue);
|
||||
const signalId = attachNewSignal(program, sig);
|
||||
// const sig = signal(initialValue);
|
||||
// const signalId = attachNewSignal(program, sig);
|
||||
|
||||
const newCell: CellDefinition = {
|
||||
name,
|
||||
raw_body,
|
||||
body,
|
||||
signalId,
|
||||
createdAt: now,
|
||||
lastModifiedAt: now,
|
||||
};
|
||||
// const newCell: CellDefinition = {
|
||||
// name,
|
||||
// raw_body,
|
||||
// body,
|
||||
// signalId,
|
||||
// createdAt: now,
|
||||
// lastModifiedAt: now,
|
||||
// };
|
||||
|
||||
program.signal_definitions.set(name, { tag: "cell", def: newCell });
|
||||
program.signal_definition_order.push(name);
|
||||
// program.signal_definitions.set(name, { tag: "cell", def: newCell });
|
||||
// program.signal_definition_order.push(name);
|
||||
|
||||
return Result.ok(undefined);
|
||||
}
|
||||
|
|
@ -539,44 +471,45 @@ export function updateSignal(
|
|||
raw_body: string,
|
||||
}
|
||||
|
||||
// TODO: refactor pending
|
||||
export function updateCell(
|
||||
program: Program,
|
||||
name: SignalName,
|
||||
{ body, raw_body }: UpdateCell
|
||||
): Result<void> {
|
||||
const existingEntry = program.signal_definitions.get(name);
|
||||
// const existingEntry = program.signal_definitions.get(name);
|
||||
|
||||
if (!existingEntry) {
|
||||
return Result.error({ tag: "SignalNotFound", name } as any);
|
||||
}
|
||||
// if (!existingEntry) {
|
||||
// return Result.error({ tag: "SignalNotFound", name } as any);
|
||||
// }
|
||||
|
||||
// Ensure we are editing a Cell
|
||||
if (existingEntry.tag !== "cell") {
|
||||
return Result.error({ tag: "CannotEditCell", name } as any);
|
||||
}
|
||||
// // Ensure we are editing a Cell
|
||||
// if (existingEntry.tag !== "cell") {
|
||||
// return Result.error({ tag: "CannotEditCell", name } as any);
|
||||
// }
|
||||
|
||||
const def = existingEntry.def;
|
||||
// const def = existingEntry.def;
|
||||
|
||||
// TODO: MAY THROW RuntimeError. Should probably switch to `eval_start` - and extend the `Program.Error` with runtime errors.
|
||||
const newValue = eval_expr(program, Env.nil(), body);
|
||||
// // TODO: MAY THROW RuntimeError. Should probably switch to `eval_start` - and extend the `Program.Error` with runtime errors.
|
||||
// const newValue = eval_expr(program, Env.nil(), body);
|
||||
|
||||
// Find the existing runtime signal
|
||||
if (def.signalId === undefined) {
|
||||
// This should theoretically not happen for cells since we initialize them eagerly,
|
||||
// but good to be safe.
|
||||
return Result.error({ tag: "SignalDefinitionHasSignalIdWithoutSignal", name } as any);
|
||||
}
|
||||
// // Find the existing runtime signal
|
||||
// if (def.signalId === undefined) {
|
||||
// // This should theoretically not happen for cells since we initialize them eagerly,
|
||||
// // but good to be safe.
|
||||
// return Result.error({ tag: "SignalDefinitionHasSignalIdWithoutSignal", name } as any);
|
||||
// }
|
||||
|
||||
const sig = SignalRuntime.getSignal(program.signal_runtime, def.signalId);
|
||||
if (!sig) {
|
||||
return Result.error({ tag: "SignalDefinitionHasSignalIdWithoutSignal", name } as any);
|
||||
}
|
||||
// const sig = SignalRuntime.getSignal(program.signal_runtime, def.signalId);
|
||||
// if (!sig) {
|
||||
// return Result.error({ tag: "SignalDefinitionHasSignalIdWithoutSignal", name } as any);
|
||||
// }
|
||||
|
||||
sig.set(() => newValue);
|
||||
// sig.set(() => newValue);
|
||||
|
||||
def.body = body;
|
||||
def.raw_body = raw_body;
|
||||
def.lastModifiedAt = Date.now();
|
||||
// def.body = body;
|
||||
// def.raw_body = raw_body;
|
||||
// def.lastModifiedAt = Date.now();
|
||||
|
||||
return Result.ok(undefined);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { updateDigith } from "src/ui/Scrowl/scrowlStore";
|
|||
import { DigithError } from "src/ui/Digith/DigithError";
|
||||
import { Value } from "src/lang/eval/value";
|
||||
import { Val } from "src/ui/Component/Value";
|
||||
import { externalSubscribe } from "src/lang/eval/signalValue";
|
||||
|
||||
type Input = {
|
||||
raw_body: string,
|
||||
|
|
@ -50,9 +51,9 @@ export function SignalDigith(props: { signal: Digith.Signal }) {
|
|||
const signal = Program.get_or_create_signal(program, props.signal.name);
|
||||
|
||||
// TODO: Not sure about this one. Setting a signal in `setValue` is discouraged.
|
||||
setTimeout(() => setValue(signal.read()), 0);
|
||||
setTimeout(() => setValue(signal.currentValue), 0);
|
||||
|
||||
const cancel = signal.subscribe((newValue) => {
|
||||
const cancel = externalSubscribe(signal, (newValue) => {
|
||||
setValue(newValue);
|
||||
});
|
||||
onCleanup(cancel);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue