Edit the graph during play and watch values and flow run through it
The graph was only locked by five edit-mode guards in GraphCanvas. The running game reads the graph by reference each frame, so a new node, edge, changed value, disconnect or undo takes effect from the next frame. Stop discards these edits like everything else changed in play. A new On start node does not fire for a behavior that has already started. During play every data output shows the value it last produced, in the highlight colour and refreshed ten times a second. Values are kept per graph, because node ids are only unique within one; with several instances the most recent value wins. The Variables node reads the live variable bag, so only its own outputs are kept, as a snapshot. Each time an exec edge fires, a bead runs along it in 0.3 s, at most one every 0.15 s per edge, following the same curve the edge is drawn with. Beads run on game time and freeze while paused; the game logic is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
495756b500
commit
656d61f187
17 files changed
+542
-37
No files matched your search
@@ -10,12 +10,14 @@ import {
|
||||
foldMarkLabel,
|
||||
foldMarkRadius,
|
||||
} from './folding';
|
||||
import { curvePoint, curveReach } from './beads';
|
||||
import { type NodeView, portColor } from './NodeView';
|
||||
|
||||
/** How far a folded edge's stub reaches out of its port. */
|
||||
const FOLD_STUB = 16;
|
||||
const FOLD_MARK_RADIUS = 5;
|
||||
const FOLD_LABEL_WIDTH = 96;
|
||||
const BEAD_RADIUS = 4.5;
|
||||
|
||||
/** One port's hidden links as drawn: what they stand in for, and where they can be clicked. */
|
||||
export interface FoldMark {
|
||||
@@ -31,6 +33,7 @@ export interface FoldMark {
|
||||
/** The curves between nodes and the marks of the hidden ones, in graph coordinates. */
|
||||
export class EdgeLayer extends Container {
|
||||
private readonly curves = new Graphics();
|
||||
private readonly beads = new Graphics();
|
||||
private readonly chips = new Graphics();
|
||||
/** Texts naming the far node of each folded edge; pooled, since a redraw rebuilds them all. */
|
||||
private readonly labelLayer = new Container();
|
||||
@@ -40,10 +43,11 @@ export class EdgeLayer extends Container {
|
||||
private marks: FoldMark[] = [];
|
||||
/** Edges the last draw really stroked as a curve – a hidden one is missing from it. */
|
||||
private readonly stroked = new Set<string>();
|
||||
private beadsDrawn: { edgeId: string; progress: number; x: number; y: number }[] = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.addChild(this.curves, this.chips, this.labelLayer);
|
||||
this.addChild(this.curves, this.beads, this.chips, this.labelLayer);
|
||||
}
|
||||
|
||||
draw(
|
||||
@@ -51,14 +55,17 @@ export class EdgeLayer extends Container {
|
||||
views: ReadonlyMap<string, NodeView>,
|
||||
isHot: (edgeId: string) => boolean,
|
||||
selected: Set<string>,
|
||||
beads: (edgeId: string) => readonly number[] = () => [],
|
||||
): void {
|
||||
this.curves.clear();
|
||||
this.beads.clear();
|
||||
this.beadsDrawn = [];
|
||||
this.chips.clear();
|
||||
this.marks = [];
|
||||
this.ends = [];
|
||||
this.labelCount = 0;
|
||||
this.stroked.clear();
|
||||
for (const edge of edges) this.drawEdge(edge, views, isHot(edge.id), selected);
|
||||
for (const edge of edges) this.drawEdge(edge, views, isHot(edge.id), selected, beads(edge.id));
|
||||
// After the curves, so no line drawn later crosses a mark that stands for a hidden one.
|
||||
this.drawFoldMarks();
|
||||
for (let i = this.labelCount; i < this.labels.length; i++) this.labels[i]!.visible = false;
|
||||
@@ -76,11 +83,16 @@ export class EdgeLayer extends Container {
|
||||
return this.stroked.has(edgeId);
|
||||
}
|
||||
|
||||
drawnBeads(): readonly { edgeId: string; progress: number; x: number; y: number }[] {
|
||||
return this.beadsDrawn;
|
||||
}
|
||||
|
||||
private drawEdge(
|
||||
edge: GraphEdge,
|
||||
views: ReadonlyMap<string, NodeView>,
|
||||
hot: boolean,
|
||||
selected: Set<string>,
|
||||
beads: readonly number[],
|
||||
): void {
|
||||
const from = views.get(edge.from.node);
|
||||
const to = views.get(edge.to.node);
|
||||
@@ -106,6 +118,15 @@ export class EdgeLayer extends Container {
|
||||
alpha: (type === 'exec' || hot ? 1 : 0.85) * (edge.folded ? 0.6 : 1),
|
||||
});
|
||||
this.stroked.add(edge.id);
|
||||
for (const progress of beads) {
|
||||
const { x, y } = curvePoint(start.x, start.y, end.x, end.y, progress);
|
||||
// Light with a dark rim: a bead in the highlight colour would vanish on the edge it lights up.
|
||||
this.beads
|
||||
.circle(x, y, BEAD_RADIUS)
|
||||
.fill(Theme.colors.text)
|
||||
.stroke({ width: 1.5, color: Theme.colors.graphBackground });
|
||||
this.beadsDrawn.push({ edgeId: edge.id, progress, x, y });
|
||||
}
|
||||
}
|
||||
|
||||
private addFoldEnd(
|
||||
@@ -167,6 +188,6 @@ export class EdgeLayer extends Container {
|
||||
|
||||
/** Horizontal S-curve between two points. */
|
||||
export function bezier(g: Graphics, x1: number, y1: number, x2: number, y2: number): void {
|
||||
const dx = Math.max(40, Math.abs(x2 - x1) * 0.5);
|
||||
const dx = curveReach(x1, x2);
|
||||
g.moveTo(x1, y1).bezierCurveTo(x1 + dx, y1, x2 - dx, y2, x2, y2);
|
||||
}
|
||||
Reference in new issue
Block a user