Let a behavior be a node in another graph
A behavior that has a Start and an End node can now be dropped into any other graph as a node of its own. What is declared on the start node becomes the inputs of that node, what hangs on the end node its outputs; the port list is derived from the sub graph, not copied, so declaring a port in one graph grows the node in the other without touching it. The node carries the behavior's name and the palette lists behaviors by name, so a call is one grab instead of a node plus a dropdown. An embedded behavior has no object and therefore no place for variables: every call starts on the asset defaults and communicates only through its ports. That is the conservative direction - state can still be added later, and shared state already has a home in a behavior object in the scene. Node ids are unique per graph only, so each call gets its own pulse cache; without that, two graphs would collide. Renaming a port used to silently drop the value typed on the calling node. It is carried along now, project wide, like the edges.
This commit is contained in:
1 parent
64e034ffb9
commit
6702ff8abe
31 files changed
+1402
-107
No files matched your search
@@ -20,6 +20,8 @@ interface Case {
|
||||
next?: string | string[] | null;
|
||||
/** Expected loop request; `null` asserts the node does not loop. */
|
||||
repeat?: RunResult['repeat'] | null;
|
||||
/** Expected nested-behavior call; `null` asserts the node calls nothing. */
|
||||
call?: RunResult['call'] | null;
|
||||
/** Assertions on what the node did to the context or to the handles it was given. */
|
||||
after?(ctx: FakeContext): void;
|
||||
}
|
||||
@@ -45,6 +47,28 @@ const CASES: Record<string, Case[]> = {
|
||||
{ name: '"false" as text is false', inputs: { condition: 'false' }, next: 'else' },
|
||||
],
|
||||
Sequence: [{ name: 'runs all three outputs in order', next: ['a', 'b', 'c'] }],
|
||||
BehaviorEnd: [
|
||||
{
|
||||
name: 'hands its inputs back out, which is where the caller reads the result',
|
||||
inputs: { hit: true, damage: 7 },
|
||||
outputs: { hit: true, damage: 7 },
|
||||
next: null,
|
||||
},
|
||||
],
|
||||
RunBehavior: [
|
||||
{
|
||||
name: 'asks for the named behavior to run, passing its inputs on',
|
||||
inputs: { power: 3 },
|
||||
params: { behavior: 'b1' },
|
||||
call: { behaviorId: 'b1', inputs: { power: 3 } },
|
||||
next: 'exec',
|
||||
},
|
||||
{
|
||||
name: 'without a behavior chosen it still continues, calling nothing that exists',
|
||||
call: { behaviorId: '', inputs: {} },
|
||||
next: 'exec',
|
||||
},
|
||||
],
|
||||
Repeat: [
|
||||
{
|
||||
name: 'asks for as many body rounds as it was given, then continues at done',
|
||||
@@ -534,6 +558,7 @@ function eqNamesakes(): Record<string, Value> {
|
||||
* seeds their outputs. The value names the test that drives each one.
|
||||
*/
|
||||
const EVENT_NODES: Record<string, string> = {
|
||||
BehaviorStart: 'nested-behavior.test.ts',
|
||||
OnStart: 'graph.test.ts',
|
||||
OnUpdate: 'graph.test.ts',
|
||||
OnKeyDown: 'behavior-events.test.ts',
|
||||
@@ -590,6 +615,7 @@ for (const [type, cases] of Object.entries(CASES)) {
|
||||
if (c.outputs) expect(result.outputs ?? {}).toEqual(c.outputs);
|
||||
if (c.next !== undefined) expect(result.next ?? null).toEqual(c.next);
|
||||
if (c.repeat !== undefined) expect(result.repeat ?? null).toEqual(c.repeat);
|
||||
if (c.call !== undefined) expect(result.call ?? null).toEqual(c.call);
|
||||
c.after?.(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user