Make a call node say why it does nothing, and easier to place
A behavior node that cannot run looked exactly like a healthy one: no start node, no behavior picked, a deleted behavior or a self-call were all silently skipped. The call still had to be forgiving at runtime -- a dangling reference must not abort a game -- so the fix is diagnosis, not an exception: describeProblem() names the one reason, the node draws it in red under its title, and the inspector repeats it where you pick the behavior. A missing end node is deliberately not a problem: a call is always synchronous, and the end node only decides which values come back. Two ways in: a behavior dragged from the asset panel lands in the graph under the pointer, and double-clicking a call node opens the graph it runs. Both reuse machinery that already existed -- the asset panel already starts the drag, and list views already report double clicks. The e2e module needed its assets at the head of the asset list: after 18 modules the list is long enough that a newly appended row scrolls out of view, and the press then lands on the panel behind it, starting no drag at all. That looked exactly like a rejected drop and cost two full runs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1d8e6ebea0
commit
c6f907b4e0
12 files changed
+427
-24
No files matched your search
@@ -74,6 +74,8 @@ export class NodeView extends Widget {
|
||||
private readonly ports = new Graphics();
|
||||
private readonly texts: Text[] = [];
|
||||
private paramLines: string[] = [];
|
||||
/** Why this node can do nothing; see `callProblem`. */
|
||||
private problem: string | null = null;
|
||||
selected = false;
|
||||
hot = false;
|
||||
/** Port a running edge drag would connect to, drawn with a ring so the drop is predictable. */
|
||||
@@ -111,8 +113,10 @@ export class NodeView extends Widget {
|
||||
asset: BehaviorAsset | null,
|
||||
project: Project | null,
|
||||
def: NodeDef,
|
||||
problem: string | null = null,
|
||||
): void {
|
||||
this.def = def;
|
||||
this.problem = problem;
|
||||
this.paramLines = this.def.params
|
||||
.map((p) => ({
|
||||
label: p.label,
|
||||
@@ -127,8 +131,7 @@ export class NodeView extends Widget {
|
||||
.filter((p) => edgesInto(graph, node.id, p.key).length > 0)
|
||||
.map((p) => p.key),
|
||||
);
|
||||
const height =
|
||||
TITLE_HEIGHT + PAD + this.paramLines.length * ROW_HEIGHT + this.rowCount() * ROW_HEIGHT + PAD;
|
||||
const height = this.headerHeight() + this.rowCount() * ROW_HEIGHT + PAD;
|
||||
this.rebuildTexts();
|
||||
this.resize(NODE_WIDTH, height);
|
||||
this.position.set(node.x, node.y);
|
||||
@@ -138,6 +141,17 @@ export class NodeView extends Widget {
|
||||
return Math.max(this.def.inputs.length, this.def.outputs.length);
|
||||
}
|
||||
|
||||
/** The warning line as drawn – checks read what the node really shows, not a recomputation. */
|
||||
get problemText(): string | null {
|
||||
return this.problem;
|
||||
}
|
||||
|
||||
/** Everything above the first port row. The problem line sits in here, so ports move down with it. */
|
||||
private headerHeight(): number {
|
||||
const lines = this.paramLines.length + (this.problem ? 1 : 0);
|
||||
return TITLE_HEIGHT + PAD + lines * ROW_HEIGHT;
|
||||
}
|
||||
|
||||
/** Local position of a port's centre. */
|
||||
portPosition(key: string, isOutput: boolean): { x: number; y: number } | null {
|
||||
const list = isOutput ? this.def.outputs : this.def.inputs;
|
||||
@@ -147,7 +161,7 @@ export class NodeView extends Widget {
|
||||
}
|
||||
|
||||
private rowY(index: number): number {
|
||||
return TITLE_HEIGHT + PAD + this.paramLines.length * ROW_HEIGHT + index * ROW_HEIGHT + ROW_HEIGHT / 2;
|
||||
return this.headerHeight() + index * ROW_HEIGHT + ROW_HEIGHT / 2;
|
||||
}
|
||||
|
||||
/** Every port with its local centre – inputs on the left edge, outputs on the right. */
|
||||
@@ -190,6 +204,10 @@ export class NodeView extends Widget {
|
||||
add(line, 'small', PAD + 2, y + 2);
|
||||
y += ROW_HEIGHT;
|
||||
}
|
||||
if (this.problem) {
|
||||
add(this.problem, 'warning', PAD + 2, y + 2);
|
||||
y += ROW_HEIGHT;
|
||||
}
|
||||
for (let i = 0; i < this.rowCount(); i++) {
|
||||
const input = this.def.inputs[i];
|
||||
const output = this.def.outputs[i];
|
||||
@@ -230,10 +248,10 @@ export class NodeView extends Widget {
|
||||
this.box
|
||||
.rect(0, TITLE_HEIGHT - Theme.radius, this.w, Theme.radius)
|
||||
.fill(categoryColor(this.def.category));
|
||||
const border = this.hot ? c.hot : this.selected ? c.nodeSelected : c.nodeBorder;
|
||||
const border = this.hot ? c.hot : this.selected ? c.nodeSelected : this.problem ? c.danger : c.nodeBorder;
|
||||
this.box
|
||||
.roundRect(0.5, 0.5, this.w - 1, this.h - 1, Theme.radius)
|
||||
.stroke({ width: this.selected || this.hot ? 2 : 1, color: border });
|
||||
.stroke({ width: this.selected || this.hot || this.problem ? 2 : 1, color: border });
|
||||
|
||||
this.ports.clear();
|
||||
const r = PORT_RADIUS;
|
||||
|
||||
Reference in new issue
Block a user