typegpu
Version:
A thin layer between JS and WebGPU/WGSL that improves development experience and allows for faster iteration.
140 lines (139 loc) • 5.41 kB
JavaScript
import { stitch } from "../core/resolve/stitch.js";
import { WgslTypeError } from "../errors.js";
import { setName } from "../shared/meta.js";
import { $gpuCallable, $internal, $ownSnippet, $resolve } from "../shared/symbols.js";
import { UnknownData } from "./dataTypes.js";
import { createPtrFromOrigin, explicitFrom } from "./ptr.js";
import { isAlias, snip, withDataType } from "./snippet.js";
import { isNaturallyEphemeral, isPtr } from "./wgslTypes.js";
export const _ref = (() => {
const impl = ((value) => INTERNAL_createRef(value));
setName(impl, 'ref');
impl.toString = () => 'ref';
impl[$internal] = true;
impl[$gpuCallable] = {
call(ctx, [value]) {
if (value.origin === 'argument') {
throw new WgslTypeError(stitch `d.ref(${value}) is illegal, cannot take a reference of an argument. Copy the value first, and take a reference of the copy.`);
}
if (value.origin === 'constant-immutable-def' || value.origin === 'runtime-immutable-def') {
const typeStr = ctx.resolve(value.dataType).value;
throw new WgslTypeError(stitch `d.ref(${value}) is illegal, cannot take a reference to a constant.
-----
- Try 'd.ref(${typeStr}(${value}));' instead to create a new referencable value.
-----`);
}
if (isAlias(value) && isNaturallyEphemeral(value.dataType)) {
const typeStr = ctx.resolve(value.dataType).value;
throw new WgslTypeError(stitch `d.ref(${value}) is illegal, cannot take a reference to a scalar value.
-----
- Try 'd.ref(${typeStr}(${value}));' instead to create a new referencable scalar.
-----`);
}
if (isPtr(value.dataType)) {
// This can happen if we take a reference of an *implicit* pointer, one
// made by assigning a reference to a `const`.
return withDataType(explicitFrom(value.dataType), value);
}
/**
* Pointer type only exists if the ref was created from a reference (buttery-butter).
*
* @example
* ```ts
* const life = ref(42); // created from a value
* const boid = ref(layout.$.boids[0]); // created from a reference
* ```
*/
const ptrType = createPtrFromOrigin(value.origin, value.dataType);
return snip(new RefOperator(value, ptrType), ptrType ?? UnknownData,
/* origin */ 'runtime', value.possibleSideEffects);
},
};
return impl;
})();
export function isRef(value) {
return value?.[$internal]?.type === 'ref';
}
// --------------
// Implementation
// --------------
export function INTERNAL_createRef(value) {
const target = {
[$internal]: { type: 'ref' },
get $() {
return value;
},
set $(newValue) {
if (newValue && typeof newValue === 'object') {
// Setting an object means updating the properties of the original object.
// e.g.: foo.$ = Boid();
for (const key of Object.keys(newValue)) {
value[key] = newValue[key];
}
}
else {
value = newValue;
}
},
};
if (value === undefined || value === null) {
throw new Error('Cannot create a ref from undefined or null');
}
if (typeof value === 'object') {
return new Proxy(target, {
get(target, prop) {
if (prop in target) {
return target[prop];
}
return value[prop];
},
set(_target, prop, propValue) {
if (prop === $internal) {
return false;
}
if (prop === '$') {
return Reflect.set(target, prop, propValue);
}
return Reflect.set(value, prop, propValue);
},
});
}
return target;
}
/**
* The result of calling `d.ref(...)`. The code responsible for
* generating shader code can check if the value of a snippet is
* an instance of `RefOperator`, and act accordingly.
*/
export class RefOperator {
[$internal];
snippet;
#ptrType;
constructor(snippet, ptrType) {
this[$internal] = true;
this.snippet = snippet;
this.#ptrType = ptrType;
}
get [$ownSnippet]() {
if (!this.#ptrType) {
throw new Error(stitch `Cannot take a reference of ${this.snippet}`);
}
return snip(this, this.#ptrType, this.snippet.origin, this.snippet.possibleSideEffects);
}
[$resolve]() {
if (!this.#ptrType) {
throw new Error(stitch `Cannot take a reference of ${this.snippet}`);
}
return snip(stitch `(&${this.snippet})`, this.#ptrType, this.snippet.origin, this.snippet.possibleSideEffects);
}
}
export function derefSnippet(snippet) {
if (!isPtr(snippet.dataType)) {
return snippet;
}
const innerType = snippet.dataType.inner;
if (snippet.value instanceof RefOperator) {
return snip(stitch `${snippet.value.snippet}`, innerType, snippet.origin, snippet.possibleSideEffects);
}
return snip(stitch `(*${snippet})`, innerType, snippet.origin, snippet.possibleSideEffects);
}