A new node takes the first of two values that is not empty (#205)
A graph that reads an optional value needed a chain of Equals and
Branch just to fall back on a default. The new node does that in one
step: it passes a through unless a is empty, in which case b comes
out. Both ports are any, so it works for numbers, text, lists,
structs and objects alike.
- `First not empty` sits in the data category with two any inputs
`a` and `b` and one any output `value`. Are both empty, b comes
out, so the second input doubles as the default.
- `isEmptyValue` in values.ts holds the rule as a pure function:
nothing connected, 0, an empty text, false, an empty list, and a
struct whose fields are all empty.
- The struct case is what covers a position: behavior graphs have no
vector type of their own, so (0, 0) arrives as a struct. `{x: 0,
y: 2}` therefore counts as set, `{x: 0, y: 0}` as empty.
Findings:
- A real 0 or false on a is passed over. That follows from counting
0 as empty and is written down in the issue.
- The output stays any. Ports that inherit their type from an input
do not exist in the graph; `Item at` has the same shape.
Tests:
- ./check.sh: 142 files, 2104 tests, exit 0 (was 2094).
- 10 new catalog cases: a set, a unconnected, 0, "", false, an empty
list, a position (0, 0), a struct with one field set, an object,
and both empty.
- Counter-check: with structs never empty and 0 a real value exactly
those two cases fail, the rest stays green. Originals restored and
compared with cmp, then rebuilt.
- No e2e check: single data nodes have no e2e coverage here, the
palette fills itself from BUILT_IN_NODES.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8c5846f956
commit
d98c2898b1
4 files changed
+52
No files matched your search
@@ -0,0 +1,14 @@
|
||||
import type { NodeDef } from '../../NodeRegistry';
|
||||
import { isEmptyValue } from '../../values';
|
||||
import { any } from '../shared';
|
||||
|
||||
export const FirstNotEmpty: NodeDef = {
|
||||
type: 'FirstNotEmpty',
|
||||
label: 'First not empty',
|
||||
category: 'data',
|
||||
description: 'a, unless it is empty (nothing, 0, "", false, an empty list), then b.',
|
||||
inputs: [any('a'), any('b')],
|
||||
outputs: [any('value')],
|
||||
params: [],
|
||||
run: (_ctx, inputs) => ({ outputs: { value: (isEmptyValue(inputs.a) ? inputs.b : inputs.a) ?? null } }),
|
||||
};
|
||||
@@ -34,6 +34,7 @@ import { Distance } from './data/Distance';
|
||||
import { Divide } from './data/Divide';
|
||||
import { Equals } from './data/Equals';
|
||||
import { FindText } from './data/FindText';
|
||||
import { FirstNotEmpty } from './data/FirstNotEmpty';
|
||||
import { Floor } from './data/Floor';
|
||||
import { GetPosition } from './data/GetPosition';
|
||||
import { IsAsset } from './data/IsAsset';
|
||||
@@ -192,6 +193,7 @@ export const BUILT_IN_NODES: readonly NodeDef[] = [
|
||||
FindText,
|
||||
Equals,
|
||||
Compare,
|
||||
FirstNotEmpty,
|
||||
And,
|
||||
Or,
|
||||
Not,
|
||||
|
||||
@@ -91,6 +91,18 @@ export function asString(value: Value | undefined): string {
|
||||
return textOf(isValueList(value) ? storedList(value) : value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a value is the empty one of its kind: no object, 0, '', false, an empty list, or a struct
|
||||
* whose fields are all empty – which is how a position (0, 0) arrives.
|
||||
*/
|
||||
export function isEmptyValue(value: Value | undefined): boolean {
|
||||
if (value === null || value === undefined) return true;
|
||||
if (isEntity(value)) return false;
|
||||
if (isValueList(value)) return value.length === 0;
|
||||
if (isStructValue(value)) return Object.values(value).every(isEmptyValue);
|
||||
return value === 0 || value === '' || value === false;
|
||||
}
|
||||
|
||||
export function asEntity(value: Value | undefined): EntityHandle | null {
|
||||
return isEntity(value) ? value : null;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user