Skip to content
Open
17 changes: 0 additions & 17 deletions packages/typegpu/src/data/dataTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import type { Snippet } from './snippet.ts';
import type { PackedData } from './vertexFormatData.ts';
import * as wgsl from './wgslTypes.ts';
import type { WgslComparisonSampler, WgslSampler } from './sampler.ts';
import type { ResolutionCtx } from '../types.ts';
import type { BaseData } from './wgslTypes.ts';

/**
Expand Down Expand Up @@ -237,22 +236,6 @@ export type AnyConcreteData = Exclude<
export const UnknownData = Symbol('UNKNOWN');
export type UnknownData = typeof UnknownData;

export class InfixDispatch {
readonly name: string;
readonly lhs: Snippet;
readonly operator: (ctx: ResolutionCtx, args: [lhs: Snippet, rhs: Snippet]) => Snippet;

constructor(
name: string,
lhs: Snippet,
operator: (ctx: ResolutionCtx, args: [lhs: Snippet, rhs: Snippet]) => Snippet,
) {
this.name = name;
this.lhs = lhs;
this.operator = operator;
}
}

export class MatrixColumnsAccess {
readonly matrix: Snippet;

Expand Down
45 changes: 18 additions & 27 deletions packages/typegpu/src/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,39 @@
// NOTE: This is a barrel file, internal files should not import things from this file

import { Operator } from 'tsover-runtime';
import { type InfixOperator, infixOperators } from '../tgsl/accessProp.ts';
import { type InfixOperatorName, infixOperators } from '../tgsl/accessProp.ts';
import { MatBase } from './matrix.ts';
import { VecBase } from './vectorImpl.ts';
import { infixDispatch } from '../tgsl/infixDispatch.ts';

function assignInfixOperator<T extends typeof VecBase | typeof MatBase>(
object: T,
operator: InfixOperator,
base: T,
operator: InfixOperatorName,
operatorSymbol: symbol,
) {
// oxlint-disable-next-line typescript/no-explicit-any -- anything is possible
const proto = object.prototype as any;
const opImpl = infixOperators[operator] as (lhs: unknown, rhs: unknown) => unknown;
const opImpl = infixOperators[operator];

proto[operator] = function (this: unknown, other: unknown): unknown {
return opImpl(this, other);
};
Object.defineProperty(base.prototype, operatorSymbol, {
value: opImpl,
});

proto[operatorSymbol] = (lhs: unknown, rhs: unknown): unknown => {
return opImpl(lhs, rhs);
};
Object.defineProperty(base.prototype, operator, {
get() {
return infixDispatch(this, opImpl);
},
Comment on lines +24 to +27
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's avoidable, and vectors are already not performant in JS anyways

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could technically branch on whether or not we're generating a shader, return what it is currently when we are, otherwise return a function that is the same across instances and actually uses 'this'

});
}

assignInfixOperator(VecBase, 'add', Operator.plus);
assignInfixOperator(MatBase, 'add', Operator.plus);
assignInfixOperator(VecBase, 'sub', Operator.minus);
assignInfixOperator(MatBase, 'sub', Operator.minus);
assignInfixOperator(VecBase, 'mul', Operator.star);
assignInfixOperator(MatBase, 'mul', Operator.star);
assignInfixOperator(VecBase, 'div', Operator.slash);
assignInfixOperator(VecBase, 'mod', Operator.percent);
assignInfixOperator(MatBase, 'add', Operator.plus);
assignInfixOperator(MatBase, 'sub', Operator.minus);
assignInfixOperator(MatBase, 'mul', Operator.star);

// bitShift does not yet have tsover operator symbol
{
// oxlint-disable-next-line typescript/no-explicit-any -- anything is possible
const proto = VecBase.prototype as any;
proto.bitShiftLeft = function (this: unknown, other: unknown) {
return (infixOperators.bitShiftLeft as (a: unknown, b: unknown) => unknown)(this, other);
};
proto.bitShiftRight = function (this: unknown, other: unknown) {
return (infixOperators.bitShiftRight as (a: unknown, b: unknown) => unknown)(this, other);
};
}
assignInfixOperator(VecBase, 'bitShiftLeft', Symbol()); // bitShift does not yet have tsover operator symbol
assignInfixOperator(VecBase, 'bitShiftRight', Symbol()); // bitShift does not yet have tsover operator symbol

export { bool, f16, f32, i32, u16, u32 } from './numeric.ts';
export {
Expand Down
36 changes: 12 additions & 24 deletions packages/typegpu/src/tgsl/accessProp.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { stitch } from '../core/resolve/stitch.ts';
import { AutoStruct } from '../data/autoStruct.ts';
import { EntryInputRouter } from '../core/function/entryInputRouter.ts';
import {
InfixDispatch,
isUnstruct,
MatrixColumnsAccess,
undecorate,
UnknownData,
} from '../data/dataTypes.ts';
import { isUnstruct, MatrixColumnsAccess, undecorate, UnknownData } from '../data/dataTypes.ts';
import { abstractInt, bool, f16, f32, i32, u32 } from '../data/numeric.ts';
import { derefSnippet } from '../data/ref.ts';
import { isEphemeralSnippet, isSnippet, snip, type Snippet } from '../data/snippet.ts';
Expand Down Expand Up @@ -37,10 +31,10 @@ import {
isWgslArray,
isWgslStruct,
} from '../data/wgslTypes.ts';
import { $gpuCallable } from '../shared/symbols.ts';
import { add, bitShiftLeft, bitShiftRight, div, mod, mul, sub } from '../std/operators.ts';
import { isKnownAtComptime } from '../types.ts';
import { coerceToSnippet } from './generationHelpers.ts';
import { infixDispatch } from './infixDispatch.ts';

const infixKinds = [
'vec2f',
Expand Down Expand Up @@ -70,7 +64,8 @@ export const infixOperators = {
bitShiftRight,
} as const;

export type InfixOperator = keyof typeof infixOperators;
export type InfixOperatorName = keyof typeof infixOperators;
export type InfixOperator = (typeof infixOperators)[InfixOperatorName];

type SwizzleableType = 'f' | 'h' | 'i' | 'u' | 'b';
type SwizzleLength = 1 | 2 | 3 | 4;
Expand Down Expand Up @@ -110,12 +105,8 @@ const swizzleLenToType: Record<SwizzleableType, Record<SwizzleLength, BaseData>>

export function accessProp(target: Snippet, propName: string): Snippet | undefined {
if (infixKinds.includes((target.dataType as BaseData).type) && propName in infixOperators) {
const operator = infixOperators[propName as InfixOperator];
return snip(
new InfixDispatch(propName, target, operator[$gpuCallable].call.bind(operator)),
UnknownData,
/* origin */ target.origin,
);
const operator = infixOperators[propName as InfixOperatorName];
return snip(infixDispatch(target, operator), UnknownData, /* origin */ target.origin);
}

if (isWgslArray(target.dataType) && propName === 'length') {
Expand Down Expand Up @@ -191,15 +182,12 @@ export function accessProp(target: Snippet, propName: string): Snippet | undefin
}

const propLength = propName.length;
if (isVec(target.dataType) && propLength >= 1 && propLength <= 4) {
const isXYZW = /^[xyzw]+$/.test(propName);
const isRGBA = /^[rgba]+$/.test(propName);

if (!isXYZW && !isRGBA) {
// Not a valid swizzle
return undefined;
}

if (
isVec(target.dataType) &&
propLength >= 1 &&
propLength <= 4 &&
/^[xyzw]+$|^[rgba]+$/.test(propName)
) {
Comment thread
aleksanderkatan marked this conversation as resolved.
const swizzleTypeChar = target.dataType.type.includes('bool')
? 'b'
: (target.dataType.type[4] as SwizzleableType);
Expand Down
52 changes: 52 additions & 0 deletions packages/typegpu/src/tgsl/infixDispatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { isSnippet, type Snippet } from '../data/snippet.ts';
import type { AnyMatInstance, AnyNumericVecInstance } from '../data/wgslTypes.ts';
import { $internal, isMarkedInternal } from '../shared/symbols.ts';
import type { InfixOperator } from './accessProp.ts';

type Numeric = number | AnyNumericVecInstance | AnyMatInstance;

/**
* In wgslGenerator, the lhs may either be Numeric or Snippet,
* and InfixDispatch is recognized by the $internal symbol.
* InfixDispatch is not called in wgslGenerator.
* @example
* const dispatch = d.vec2u(1).mul;
* const fn = () => {
* 'use gpu';
* dispatch(2); // lhs is Numeric
* d.vec2u(1).mul(2) // lhs is a snippet
* }
*
* In JS, the lhs is always numeric, and InfixDispatch is callable.
* @example
* const dispatch = d.vec2u(1).mul;
* dispatch(2);
*/
export interface InfixDispatch {
[$internal]: true;
type: 'infix-dispatch';
readonly lhs: Snippet | Numeric;
readonly operator: InfixOperator;
(other: Numeric): Numeric;
}

export function infixDispatch(lhs: Snippet | Numeric, operator: InfixOperator): InfixDispatch {
const callable = (other: Numeric | Snippet) => {
if (isSnippet(lhs)) {
throw new Error('Unexpected snippet lhs in JS infix operator.');
}
// operator will perform all necessary type checks
return operator(lhs as never, other as never);
};
const infix = Object.assign(callable, {
[$internal]: true as const,
type: 'infix-dispatch' as const,
lhs,
operator,
});
return infix;
}

export function isInfixDispatch(o: unknown): o is InfixDispatch {
return isMarkedInternal(o) && (o as InfixDispatch)?.type === 'infix-dispatch';
}
12 changes: 7 additions & 5 deletions packages/typegpu/src/tgsl/wgslGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as tinyest from 'tinyest';
import { stitch } from '../core/resolve/stitch.ts';
import { arrayOf } from '../data/array.ts';
import { type AnyData, InfixDispatch, isLooseData, UnknownData, unptr } from '../data/dataTypes.ts';
import { type AnyData, isLooseData, UnknownData, unptr } from '../data/dataTypes.ts';
import { bool, i32, u32 } from '../data/numeric.ts';
import { vec2u, vec3u, vec4u } from '../data/vector.ts';
import {
Expand Down Expand Up @@ -46,6 +46,7 @@ import * as forOfUtils from './forOfUtils.ts';
import { isTgpuRange } from '../std/range.ts';
import type { FunctionDefinitionOptions } from './shaderGenerator_members.ts';
import { getAttributesString } from '../data/attributes.ts';
import { isInfixDispatch } from './infixDispatch.ts';

const { NodeTypeCatalog: NODE } = tinyest;

Expand Down Expand Up @@ -645,15 +646,16 @@ ${this.ctx.pre}}`;
);
}

if (callee.value instanceof InfixDispatch) {
// Infix operator dispatch.
if (isInfixDispatch(callee.value)) {
if (!argNodes[0]) {
throw new WgslTypeError(
`An infix operator '${callee.value.name}' was called without any arguments`,
`An infix operator '${getName(callee.value.operator)}' was called without any arguments`,
);
}
const lhs = coerceToSnippet(callee.value.lhs);
const rhs = this._expression(argNodes[0]);
return callee.value.operator(this.ctx, [callee.value.lhs, rhs]);
const callable = callee.value.operator[$gpuCallable];
return callable.call(this.ctx, [lhs, rhs]);
}

if (isGPUCallable(callee.value)) {
Expand Down
2 changes: 1 addition & 1 deletion packages/typegpu/tests/swizzleMixedValidation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('Mixed swizzle validation', () => {
return mixed;
};

// The resolution should fail because accessProp returns undefined for mixed swizzles
// The resolution should fail because accessProp won't match any prop and will return undefined
expect(() => tgpu.resolve([main])).toThrowErrorMatchingInlineSnapshot(`
[Error: Resolution of the following tree failed:
- <root>
Expand Down
Loading
Loading