Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d2d8a83
basic for ... of ... loop compilation
cieplypolar Dec 14, 2025
a2bb186
naming convention fix
cieplypolar Dec 15, 2025
edfaea7
refactor, var vs let, support for vectors
cieplypolar Dec 16, 2025
4909740
Merge branch 'main' into feat/for-of-loop
cieplypolar Dec 16, 2025
f8ac4b8
biome fixes
cieplypolar Dec 16, 2025
8be4dd7
for of in some examples
cieplypolar Dec 16, 2025
d74b998
updated wgsl tests
cieplypolar Dec 16, 2025
934bacf
cleanup
cieplypolar Dec 16, 2025
38bee47
minor test cleanup
cieplypolar Dec 17, 2025
581f7d2
Taking a reference to the iterated value if it's a non-ephemeral element
iwoplaza Dec 18, 2025
5e8cfe0
Revert example change
iwoplaza Dec 18, 2025
c386c45
Free test!
Dec 18, 2025
9b0da3d
more review changes + vector type guard impr
cieplypolar Dec 18, 2025
17c34dc
review changes
cieplypolar Dec 20, 2025
70d515d
Merge branch 'main' into feat/for-of-loop
cieplypolar Dec 20, 2025
41a0165
nested forOf test
cieplypolar Dec 21, 2025
3990078
Merge branch 'feat/for-of-loop' of github.com:software-mansion/TypeGP…
cieplypolar Dec 21, 2025
e47a2cc
the last test, I promise
cieplypolar Dec 21, 2025
80e60bd
test cleanup
cieplypolar Dec 22, 2025
26e0f98
I lied
cieplypolar Dec 22, 2025
3b90675
Merge branch 'main' into feat/for-of-loop
cieplypolar Dec 23, 2025
46ef92a
merge main
cieplypolar Dec 23, 2025
a374551
Merge branch 'main' into feat/for-of-loop
cieplypolar Jan 8, 2026
cd6c194
Merge branch 'main' into feat/for-of-loop
cieplypolar Jan 9, 2026
57bfbae
Merge branch 'main' into feat/for-of-loop
cieplypolar Jan 12, 2026
b6d8b3b
Merge branch 'main' into feat/for-of-loop
cieplypolar Jan 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ const fragmentMain = tgpu['~unstable'].fragmentFn({
const diskRadius = shadowParams.$.diskRadius;

let visibilityAcc = 0.0;
for (let i = 0; i < PCF_SAMPLES; i++) {
const o = samplesUniform.$[i].xy.mul(diskRadius);
for (const sample of samplesUniform.$) {
const o = sample.xy.mul(diskRadius);

const sampleDir = dir
.add(right.mul(o.x))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,7 @@ const time = root.createUniform(d.f32);

const isInsideObstacle = (x: number, y: number): boolean => {
'use gpu';
for (let obsIdx = 0; obsIdx < MAX_OBSTACLES; obsIdx++) {
const obs = obstacles.$[obsIdx];

for (const obs of obstacles.$) {
if (obs.enabled === 0) {
continue;
}
Expand Down Expand Up @@ -158,8 +156,7 @@ const computeVelocity = (x: number, y: number): d.v2f => {
];
let dirChoiceCount = 1;

for (let i = 0; i < 4; i++) {
const offset = neighborOffsets[i];
for (const offset of neighborOffsets) {
const neighborDensity = getCell(x + offset.x, y + offset.y);
const cost = neighborDensity.z + d.f32(offset.y) * gravityCost;

Expand Down
14 changes: 8 additions & 6 deletions packages/tinyest-for-wgsl/src/parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,12 +236,7 @@ const Transpilers: Partial<
}

if (node.kind === 'const') {
if (init === undefined) {
throw new Error(
'Did not provide initial value in `const` declaration.',
);
}
return [NODE.const, id, init];
return init !== undefined ? [NODE.const, id, init] : [NODE.const, id];
}

return init !== undefined ? [NODE.let, id, init] : [NODE.let, id];
Expand Down Expand Up @@ -314,6 +309,13 @@ const Transpilers: Partial<
return [NODE.while, condition, body];
},

ForOfStatement(ctx, node) {
const loopVar = transpile(ctx, node.left) as tinyest.Const | tinyest.Let;
const iterable = transpile(ctx, node.right) as tinyest.Expression;
const body = transpile(ctx, node.body) as tinyest.Statement;
return [NODE.forOf, loopVar, iterable, body];
},

ContinueStatement() {
return [NODE.continue];
},
Expand Down
23 changes: 17 additions & 6 deletions packages/tinyest/src/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const NodeTypeCatalog = {
while: 15,
continue: 16,
break: 17,
forOf: 18,

// rare
arrayExpr: 100,
Expand Down Expand Up @@ -72,11 +73,13 @@ export type Let =
/**
* Represents a const statement
*/
export type Const = readonly [
type: NodeTypeCatalog['const'],
identifier: string,
value: Expression,
];
export type Const =
| readonly [type: NodeTypeCatalog['const'], identifier: string]
| readonly [
type: NodeTypeCatalog['const'],
identifier: string,
value: Expression,
];

export type For = readonly [
type: NodeTypeCatalog['for'],
Expand All @@ -96,6 +99,13 @@ export type Continue = readonly [type: NodeTypeCatalog['continue']];

export type Break = readonly [type: NodeTypeCatalog['break']];

export type ForOf = readonly [
type: NodeTypeCatalog['forOf'],
left: Const | Let,
right: Expression,
body: Statement,
];

/**
* A union type of all statements
*/
Expand All @@ -109,7 +119,8 @@ export type Statement =
| For
| While
| Continue
| Break;
| Break
| ForOf;

//
// Expression
Expand Down
102 changes: 102 additions & 0 deletions packages/typegpu/src/tgsl/wgslGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,108 @@ ${this.ctx.pre}else ${alternate}`;
return `${this.ctx.pre}while (${conditionStr}) ${bodyStr}`;
}

if (statement[0] === NODE.forOf) {
const [_, loopVar, iterable, body] = statement;
const iterableSnippet = this.expression(iterable);

if (isEphemeralSnippet(iterableSnippet)) {
throw new Error(
'`for ... of ...` loops only support iterables stored in variables',
);
}

// if user defines a variable named 'i', it will be scoped to a new block,
// shadowing our 'i'
const index = this.ctx.makeNameValid('i');

const elementSnippet = accessIndex(
iterableSnippet,
snip(index, u32, 'runtime'),
);
if (!elementSnippet) {
throw new WgslTypeError(
'`for ... of ...` loops only support array or vector iterables',
);
}

const iterableDataType = iterableSnippet.dataType;
let elementCount: number;
let elementType = elementSnippet?.dataType;
if (wgsl.isWgslArray(iterableDataType)) {
elementCount = iterableDataType.elementCount;
} else if (wgsl.isVec(iterableDataType)) {
elementCount = Number(iterableDataType.type.match(/\d/));
} else {
throw new WgslTypeError(
'`for ... of ...` loops only support array or vector iterables',
);
}

if (loopVar[0] !== NODE.const) {
throw new WgslTypeError(
'Only `for (const ... of ... )` loops are supported',
);
}

// If it's ephemeral, it's a value that cannot change. If it's a reference, we take
// an implicit pointer to it
let loopVarKind = 'let';
const loopVarName = this.ctx.makeNameValid(loopVar[1]);

if (!isEphemeralSnippet(elementSnippet)) {
if (elementSnippet.origin === 'constant-tgpu-const-ref') {
loopVarKind = 'const';
} else if (elementSnippet.origin === 'runtime-tgpu-const-ref') {
loopVarKind = 'let';
} else {
loopVarKind = 'let';
if (!wgsl.isPtr(elementType)) {
const ptrType = createPtrFromOrigin(
elementSnippet.origin,
concretize(elementType as wgsl.AnyWgslData) as wgsl.StorableData,
);
invariant(
ptrType !== undefined,
`Creating pointer type from origin ${elementSnippet.origin}`,
);
elementType = ptrType;
}

elementType = implicitFrom(elementType);
}
}

const loopVarSnippet = snip(
loopVarName,
elementType,
elementSnippet.origin,
);
this.ctx.defineVariable(loopVarName, loopVarSnippet);

const iterableStr = this.ctx.resolve(
iterableSnippet.value,
iterableDataType,
).value;

const forStr =
`${this.ctx.pre}for (var ${index} = 0; ${index} < ${elementCount}; ${index}++) {`;

this.ctx.indent();

const loopVarDeclStr =
stitch`${this.ctx.pre}${loopVarKind} ${loopVarName} = ${
tryConvertSnippet(elementSnippet, elementType as AnyData, false)
};`;

const bodyStr = `${this.ctx.pre}${
this.block(blockifySingleStatement(body))
}`;

this.ctx.dedent();

return stitch`${forStr}\n${loopVarDeclStr}\n${bodyStr}\n${this.ctx.pre}}`;
}

if (statement[0] === NODE.continue) {
return `${this.ctx.pre}continue;`;
}
Expand Down
Loading