Skip to content

Commit 77bd69d

Browse files
committed
Use an onRebind callback instead of transpiler tricks
1 parent 5555f13 commit 77bd69d

2 files changed

Lines changed: 22 additions & 70 deletions

File tree

src/strands/ir_builders.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,28 @@ export function createStructArrayElementProxy(strandsContext, bufferNode, indexN
689689
});
690690
const id = DAG.getOrCreateNode(dag, nodeData);
691691
CFG.recordInBasicBlock(cfg, cfg.currentBlock, id);
692-
return createStrandsNode(id, field.dim, strandsContext);
692+
// When a swizzle assignment fires (e.g. buf[i].vel.y *= -1), onRebind
693+
// receives the new vector ID and writes it back to the buffer field,
694+
// equivalent to buf[i].vel = newVec.
695+
const onRebind = (newFieldID) => {
696+
const accessData = DAG.createNodeData({
697+
nodeType: NodeType.OPERATION,
698+
opCode: OpCode.Binary.ARRAY_ACCESS,
699+
dependsOn: [bufferNode.id, index.id],
700+
dimension: field.dim,
701+
baseType: BaseType.FLOAT,
702+
identifier: field.name,
703+
});
704+
const accessID = DAG.getOrCreateNode(dag, accessData);
705+
const assignData = DAG.createNodeData({
706+
nodeType: NodeType.ASSIGNMENT,
707+
dependsOn: [accessID, newFieldID],
708+
phiBlocks: [],
709+
});
710+
const assignID = DAG.getOrCreateNode(dag, assignData);
711+
CFG.recordInBasicBlock(cfg, cfg.currentBlock, assignID);
712+
};
713+
return createStrandsNode(id, field.dim, strandsContext, onRebind);
693714
},
694715
set(val) {
695716
// Create access node as assignment target (field name in identifier)

src/strands/strands_transpiler.js

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,75 +1247,6 @@ const ASTCallbacks = {
12471247
delete node.update;
12481248
},
12491249

1250-
// Swizzle assignments on storage fields like data[i].velocity.y *= -1 don't work
1251-
// as plain assignments because data.get(i).velocity returns a StrandsNode without
1252-
// an onRebind callback, so setting .y on it has no effect on the buffer.
1253-
//
1254-
// We detect this case here (ancestor walk is post-order, so by the time this runs
1255-
// data[i] has already been converted to data.get(i) by the MemberExpression visitor).
1256-
// buildPropertyPath returns null when it hits a non-Identifier object (like a .get()
1257-
// call), which distinguishes this from struct-field swizzles like inputs.position.x
1258-
// where buildPropertyPath returns a non-null path and the phi-node mechanism handles it.
1259-
//
1260-
// We rewrite fieldExpr.swizzle = rhs into a read-modify-write:
1261-
// let __tmp = fieldExpr
1262-
// __tmp.swizzle = rhs swizzleTrap.set mutates __tmp.id
1263-
// fieldExpr = __tmp proxy setter writes back to the buffer
1264-
ExpressionStatement(node, state, ancestors) {
1265-
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) {
1266-
return;
1267-
}
1268-
const assign = node.expression;
1269-
if (assign?.type !== 'AssignmentExpression') return;
1270-
const left = assign.left;
1271-
if (left.type !== 'MemberExpression' || left.computed) return;
1272-
const propName = left.property.name;
1273-
if (!propName || !isSwizzle(propName)) return;
1274-
const fieldExpr = left.object;
1275-
// A plain identifier (e.g. myVec.y = 5) is handled directly by swizzleTrap.set.
1276-
if (fieldExpr.type === 'Identifier') return;
1277-
// A simple dotted path (e.g. inputs.position.x) is handled by the phi-node mechanism.
1278-
if (buildPropertyPath(fieldExpr) !== null) return;
1279-
1280-
const tmpName = `__swizzle_tmp_${blockVarCounter++}`;
1281-
node.type = 'BlockStatement';
1282-
node.body = [
1283-
{
1284-
type: 'VariableDeclaration',
1285-
declarations: [{
1286-
type: 'VariableDeclarator',
1287-
id: { type: 'Identifier', name: tmpName },
1288-
init: JSON.parse(JSON.stringify(fieldExpr)),
1289-
}],
1290-
kind: 'let',
1291-
},
1292-
{
1293-
type: 'ExpressionStatement',
1294-
expression: {
1295-
type: 'AssignmentExpression',
1296-
operator: '=',
1297-
left: {
1298-
type: 'MemberExpression',
1299-
object: { type: 'Identifier', name: tmpName },
1300-
property: { type: 'Identifier', name: propName },
1301-
computed: false,
1302-
},
1303-
right: assign.right,
1304-
},
1305-
},
1306-
{
1307-
type: 'ExpressionStatement',
1308-
expression: {
1309-
type: 'AssignmentExpression',
1310-
operator: '=',
1311-
left: JSON.parse(JSON.stringify(fieldExpr)),
1312-
right: { type: 'Identifier', name: tmpName },
1313-
},
1314-
},
1315-
];
1316-
delete node.expression;
1317-
},
1318-
13191250
// Helper method to replace identifier references in AST nodes
13201251
replaceIdentifierReferences(node, oldName, newName) {
13211252
if (!node || typeof node !== 'object') return node;

0 commit comments

Comments
 (0)