Trim needless comments and fix the picker width
Remove comments that only restate the code, keeping the section banners and the notes that carry a reason, a measured fact or an external contract. ButtonOptions.width claimed to be a fixed width but only set minWidth, so the Graph and Texture pickers grew with the name they showed. It is now what it promises, with a separate minWidth for the buttons that have two forms. Narrow buttons shrink their padding, otherwise a 24px icon button had 8px left for its glyph and truncate dropped it entirely. __gm.labelsDropped() guards that class: the overflow guard only saw text that was too wide, never a label truncated to nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f16801aef3
commit
4d84231b9f
115 files changed
+707
-1564
No files matched your search
+3
-11
@@ -1,9 +1,6 @@
|
||||
import type { PropertyBag } from '../model/Properties';
|
||||
|
||||
/**
|
||||
* Port data types. `exec` carries control flow (Blueprint-style white arrows); the others
|
||||
* carry values. `any` accepts every value type (variables, logging).
|
||||
*/
|
||||
/** `exec` carries control flow, the others values; `any` accepts every value type. */
|
||||
export type PortType = 'exec' | 'number' | 'boolean' | 'string' | 'object' | 'any';
|
||||
|
||||
export interface PortRef {
|
||||
@@ -12,9 +9,8 @@ export interface PortRef {
|
||||
}
|
||||
|
||||
/**
|
||||
* One node of a behavior graph. `type` names a NodeDef in the registry; `params` holds
|
||||
* the node's own settings (e.g. which key) and inline values of unconnected data inputs,
|
||||
* both keyed by the param / port key.
|
||||
* `type` names a NodeDef in the registry; `params` holds both the node's own settings and the
|
||||
* inline values of unconnected data inputs, keyed by param or port key.
|
||||
*/
|
||||
export interface GraphNode {
|
||||
id: string;
|
||||
@@ -24,7 +20,6 @@ export interface GraphNode {
|
||||
params: PropertyBag;
|
||||
}
|
||||
|
||||
/** A connection from an output port to an input port. */
|
||||
export interface GraphEdge {
|
||||
id: string;
|
||||
from: PortRef;
|
||||
@@ -49,17 +44,14 @@ export function edgesInto(graph: Graph, nodeId: string, port: string): GraphEdge
|
||||
return graph.edges.filter((e) => e.to.node === nodeId && e.to.port === port);
|
||||
}
|
||||
|
||||
/** Edges leaving one output port. */
|
||||
export function edgesOutOf(graph: Graph, nodeId: string, port: string): GraphEdge[] {
|
||||
return graph.edges.filter((e) => e.from.node === nodeId && e.from.port === port);
|
||||
}
|
||||
|
||||
/** Every edge touching a node (used when removing it). */
|
||||
export function edgesOfNode(graph: Graph, nodeId: string): GraphEdge[] {
|
||||
return graph.edges.filter((e) => e.from.node === nodeId || e.to.node === nodeId);
|
||||
}
|
||||
|
||||
/** Whether a value may flow from an output of type `from` into an input of type `to`. */
|
||||
export function portsCompatible(from: PortType, to: PortType): boolean {
|
||||
if (from === 'exec' || to === 'exec') return from === to;
|
||||
return from === to || from === 'any' || to === 'any';
|
||||
|
||||
@@ -10,21 +10,17 @@ import type { Value } from './values';
|
||||
const MAX_STEPS_PER_FIRE = 1000;
|
||||
|
||||
export interface TraceListener {
|
||||
/** An exec edge was followed. */
|
||||
onEdge(edgeId: string): void;
|
||||
/** A node ran (exec) or was evaluated (data). */
|
||||
onNode(nodeId: string): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs one behavior graph for one instance. Blueprint semantics: an event node starts a
|
||||
* *pulse*; the interpreter follows exec edges, and whenever a node needs a data input it
|
||||
* pulls the value from the connected output – evaluating pure data nodes lazily and
|
||||
* reading exec nodes' outputs from the pulse cache. Per-node state (timers) lives here,
|
||||
* so every instance has its own.
|
||||
* Runs one behavior graph for one instance. An event node starts a *pulse*; the interpreter follows
|
||||
* exec edges and pulls data inputs from the connected output, evaluating pure data nodes lazily and
|
||||
* reading exec nodes' outputs from the pulse cache.
|
||||
*/
|
||||
export class GraphInterpreter {
|
||||
/** Per-node persistent state across pulses (timer accumulators etc.). */
|
||||
/** Per-node state that persists across pulses (timer accumulators etc.). */
|
||||
readonly nodeState = new Map<string, Record<string, number>>();
|
||||
private outputCache = new Map<string, Record<string, Value>>();
|
||||
private steps = 0;
|
||||
@@ -32,15 +28,11 @@ export class GraphInterpreter {
|
||||
|
||||
constructor(readonly graph: Graph) {}
|
||||
|
||||
/** Nodes of one event type, in graph order. */
|
||||
eventNodes(type: string): GraphNode[] {
|
||||
return this.graph.nodes.filter((n) => n.type === type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires every event node of `type` whose params satisfy `filter`, seeding its outputs with
|
||||
* `outputs` (e.g. `{ dt }`). Returns how many event nodes fired.
|
||||
*/
|
||||
/** Fires every event node of `type` passing `filter`, seeding its outputs with `outputs`. */
|
||||
fire(
|
||||
type: string,
|
||||
ctx: NodeContext,
|
||||
@@ -60,7 +52,6 @@ export class GraphInterpreter {
|
||||
return fired;
|
||||
}
|
||||
|
||||
/** Follows the exec edges leaving `port` of `node`, running each target in order. */
|
||||
private follow(node: GraphNode, port: string, ctx: NodeContext): void {
|
||||
for (const edge of edgesOutOf(this.graph, node.id, port)) {
|
||||
const target = this.node(edge.to.node);
|
||||
|
||||
@@ -3,12 +3,11 @@ import { sumAlongChain } from '../model/walk';
|
||||
import { enabledAlongChain } from '../properties/enabled';
|
||||
|
||||
/**
|
||||
* What a graph sees of a running entity. The runtime's Entity satisfies this shape; unit
|
||||
* tests use plain objects. Mutating `x`/`y`/`props` is how actions take effect.
|
||||
* What a graph sees of a running entity; the runtime's Entity satisfies this shape. Mutating
|
||||
* `x`/`y`/`props` is how actions take effect.
|
||||
*/
|
||||
export interface EntityHandle {
|
||||
id: string;
|
||||
/** Asset this entity was instantiated from (what `Is asset` compares against). */
|
||||
assetId: string;
|
||||
name: string;
|
||||
/** Position local to `parent` (world position for top-level entities). */
|
||||
@@ -16,11 +15,9 @@ export interface EntityHandle {
|
||||
y: number;
|
||||
z: number;
|
||||
props: PropertyBag;
|
||||
/** The entity this one is parented to; children move with their parent. */
|
||||
parent: EntityHandle | null;
|
||||
}
|
||||
|
||||
/** World position of a handle: its local position plus those of all ancestors. */
|
||||
export function worldPositionOf(handle: EntityHandle): { x: number; y: number; z: number } {
|
||||
return sumAlongChain(
|
||||
handle,
|
||||
@@ -29,7 +26,7 @@ export function worldPositionOf(handle: EntityHandle): { x: number; y: number; z
|
||||
);
|
||||
}
|
||||
|
||||
/** Whether a handle and every ancestor of it are switched on (see `properties/enabled.ts`). */
|
||||
/** Whether a handle and every ancestor of it are switched on. */
|
||||
export function isHandleEnabled(handle: EntityHandle): boolean {
|
||||
return enabledAlongChain(
|
||||
handle,
|
||||
@@ -38,11 +35,10 @@ export function isHandleEnabled(handle: EntityHandle): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
/** Where the mouse is (world units) and what it did this frame. */
|
||||
/** Position in world units. */
|
||||
export interface PointerState {
|
||||
x: number;
|
||||
y: number;
|
||||
/** Whether the pointer is over the game view at all. */
|
||||
inside: boolean;
|
||||
down: boolean;
|
||||
/** Went down / up during this frame. */
|
||||
@@ -51,45 +47,36 @@ export interface PointerState {
|
||||
}
|
||||
|
||||
/**
|
||||
* Services a graph may use while running. Implemented by the runtime (BehaviorSystem) and
|
||||
* by test doubles. Everything the graph does to the world goes through here, so the core
|
||||
* stays free of runtime imports.
|
||||
* Everything the graph does to the world goes through here, so the core stays free of runtime
|
||||
* imports. Implemented by the runtime (BehaviorSystem) and by test doubles.
|
||||
*/
|
||||
export interface NodeContext {
|
||||
/** The behavior entity itself. */
|
||||
self: EntityHandle;
|
||||
/** The behavior's parent object (the one the graph acts on), if any. */
|
||||
/** The behavior's parent object – the one the graph acts on. */
|
||||
owner: EntityHandle | null;
|
||||
/** Mutable variable state of this behavior instance. */
|
||||
variables: PropertyBag;
|
||||
/** Seconds since the world started and since the last tick. */
|
||||
time: number;
|
||||
dt: number;
|
||||
findByName(name: string): EntityHandle | null;
|
||||
isKeyDown(key: string): boolean;
|
||||
/** Mouse state of this frame, in world units. */
|
||||
pointer: PointerState;
|
||||
/**
|
||||
* Whether a world point lies on `target` – on its colliders when it has any, otherwise on
|
||||
* its own footprint or that of anything parented below it (so a button made of a container
|
||||
* with a sprite and a label is hit as a whole).
|
||||
* Whether a world point lies on `target`: on its colliders when it has any, otherwise on its own
|
||||
* footprint or that of anything parented below it.
|
||||
*/
|
||||
hitTest(target: EntityHandle, x: number, y: number): boolean;
|
||||
/**
|
||||
* Whether any collider of `a` overlaps any collider of `b` right now. A collider entity
|
||||
* stands for itself; any other entity stands for its direct collider children.
|
||||
*/
|
||||
/** A collider entity stands for itself; any other entity for its direct collider children. */
|
||||
isTouching(a: EntityHandle, b: EntityHandle): boolean;
|
||||
/**
|
||||
* Moves `target` by (dx, dy) in local units, stopping its solid colliders at other solid
|
||||
* colliders axis by axis (x first, then y) so it slides along walls. Reports which axes hit.
|
||||
* Moves `target` by (dx, dy) in local units, stopping solid colliders at other solid ones axis by
|
||||
* axis (x first, then y) so it slides along walls.
|
||||
*/
|
||||
moveAndSlide(target: EntityHandle, dx: number, dy: number): { blockedX: boolean; blockedY: boolean };
|
||||
log(message: string): void;
|
||||
/**
|
||||
* Places a new instance of an asset into the running scene and returns it. The position is
|
||||
* local to `parent` (world when none). Returns null when the asset does not exist or cannot
|
||||
* be placed (a scene). Spawned behaviors run their `On start` on the next tick.
|
||||
* The position is local to `parent` (world when none); null when the asset does not exist or
|
||||
* cannot be placed. A spawned behavior runs its `On start` on the next tick.
|
||||
*/
|
||||
spawn(
|
||||
assetId: string,
|
||||
|
||||
@@ -15,31 +15,25 @@ export interface PortDef {
|
||||
export type NodeCategory = 'event' | 'flow' | 'data' | 'variable' | 'action';
|
||||
|
||||
export interface RunResult {
|
||||
/** Values for the node's data outputs (cached for the rest of the pulse). */
|
||||
/** Cached for the rest of the pulse. */
|
||||
outputs?: Record<string, Value>;
|
||||
/** Exec output(s) to continue along; omitted = the node ends the flow. */
|
||||
next?: string | string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes one node type: its ports, its inline settings and how it runs. The editor
|
||||
* palette, port rendering, connection validation and the inspector fields are all derived
|
||||
* from this – adding a node type means adding one definition, no UI code.
|
||||
*/
|
||||
/** Palette, port rendering, connection validation and inspector fields are all derived from this. */
|
||||
export interface NodeDef {
|
||||
type: string;
|
||||
label: string;
|
||||
category: NodeCategory;
|
||||
/** Shown in the palette and the inspector. */
|
||||
description: string;
|
||||
inputs: PortDef[];
|
||||
outputs: PortDef[];
|
||||
/** Node settings that are not ports (key name, operator, variable). */
|
||||
params: PropertyDef[];
|
||||
/**
|
||||
* Executes the node. Pure data nodes (no exec pins) are evaluated on demand whenever an
|
||||
* input downstream needs their value; exec nodes run when the flow reaches them.
|
||||
* Event nodes have no `run`; the interpreter seeds their outputs when it fires them.
|
||||
* Pure data nodes are evaluated on demand when a downstream input needs their value; exec nodes
|
||||
* run when the flow reaches them. Event nodes have no `run` – the interpreter seeds their outputs.
|
||||
*/
|
||||
run?(ctx: NodeContext, inputs: Record<string, Value>, params: PropertyBag): RunResult;
|
||||
}
|
||||
@@ -74,12 +68,11 @@ export function hasExecInput(def: NodeDef): boolean {
|
||||
return def.inputs.some((p) => p.type === 'exec');
|
||||
}
|
||||
|
||||
/** Data inputs that may carry an inline value when unconnected (numbers, strings, booleans, any). */
|
||||
/** Data inputs that may carry an inline value while unconnected. */
|
||||
export function inlineInputs(def: NodeDef): PortDef[] {
|
||||
return def.inputs.filter((p) => p.type !== 'exec' && p.type !== 'object');
|
||||
}
|
||||
|
||||
/** A PropertyDef for editing the inline value of a port. */
|
||||
export function inlineInputDef(port: PortDef): PropertyDef {
|
||||
switch (port.type) {
|
||||
case 'boolean':
|
||||
|
||||
@@ -28,8 +28,8 @@ export const Compare: NodeDef = {
|
||||
},
|
||||
],
|
||||
run: (_ctx, inputs, params) => {
|
||||
// The fallback matches the param's declared default, so a node whose setting is missing
|
||||
// (an old document, a hand-written graph) behaves like a freshly created one.
|
||||
// The fallback matches the param's declared default, so a node with no setting behaves
|
||||
// like a freshly created one.
|
||||
const compare = OPERATORS[String(params.op)] ?? OPERATORS['<']!;
|
||||
return { outputs: { result: compare(asNumber(inputs.a), asNumber(inputs.b)) } };
|
||||
},
|
||||
|
||||
@@ -68,8 +68,8 @@ import { GetVariable } from './variable/GetVariable';
|
||||
import { SetVariable } from './variable/SetVariable';
|
||||
|
||||
/**
|
||||
* The built-in node set, one definition per file. Importing this module registers them;
|
||||
* the palette sorts by category and label, so the order here is documentation only.
|
||||
* Importing this module registers the built-in nodes. The palette sorts by category and label, so
|
||||
* the order here is documentation only.
|
||||
*/
|
||||
export const BUILT_IN_NODES: readonly NodeDef[] = [
|
||||
// events
|
||||
|
||||
@@ -12,7 +12,6 @@ export const str = (key: string, label = key): PortDef => ({ key, label, type: '
|
||||
export const obj = (key: string, label = key): PortDef => ({ key, label, type: 'object' });
|
||||
export const any = (key: string, label = key): PortDef => ({ key, label, type: 'any' });
|
||||
|
||||
/** The same port with an inline value it carries while unconnected. */
|
||||
export const withDefault = (port: PortDef, value: PropertyValue): PortDef => ({ ...port, default: value });
|
||||
|
||||
/** Outputs of the three collision events: the other body, its collider and the own collider. */
|
||||
@@ -36,15 +35,11 @@ export const variableParam: PropertyDef = {
|
||||
options: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* A reference to an asset the node acts on (spawns, compares against). No `assetTypes`: any
|
||||
* placeable asset is a valid answer, and restricting them would be guessing for the user.
|
||||
*/
|
||||
/** No `assetTypes`: any placeable asset is a valid answer here. */
|
||||
export const assetParam: PropertyDef = { key: 'asset', label: 'Asset', kind: 'asset', default: '' };
|
||||
|
||||
// --- node factories --------------------------------------------------------------------------
|
||||
|
||||
/** A data node computing one number from `a` and `b`. */
|
||||
export function binaryMath(type: string, description: string, fn: (a: number, b: number) => number): NodeDef {
|
||||
return {
|
||||
type,
|
||||
@@ -58,7 +53,6 @@ export function binaryMath(type: string, description: string, fn: (a: number, b:
|
||||
};
|
||||
}
|
||||
|
||||
/** A data node computing one number from `value`. */
|
||||
export function unaryMath(type: string, description: string, fn: (value: number) => number): NodeDef {
|
||||
return {
|
||||
type,
|
||||
@@ -72,7 +66,6 @@ export function unaryMath(type: string, description: string, fn: (value: number)
|
||||
};
|
||||
}
|
||||
|
||||
/** A data node combining the booleans `a` and `b`. */
|
||||
export function binaryLogic(
|
||||
type: string,
|
||||
description: string,
|
||||
|
||||
Reference in new issue
Block a user