Skip to content

Commit 1602e74

Browse files
authored
fix: Rework how schemas propagate usage validity (#1547)
* Rework how schemas propagate usage validity * Fix types errors * Lenient for generic cases * Fix tests * Verify buffer shorthands as well * Back to square one * Fix index validity checking * Fix invalid inference in buffer shorthands * Made Unstruct wider, just like WgslStruct * Not using deprecated types internally * Update buffer shorthand defs * Cleanup * Unused import * Fix merge issue * No reason to prefix in case of decorated * Review fixes
1 parent c8f2cd0 commit 1602e74

26 files changed

+618
-261
lines changed

packages/typegpu/src/core/buffer/buffer.ts

Lines changed: 32 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,25 @@ import { getCompiledWriterForSchema } from '../../data/compiledIO.ts';
33
import { readData, writeData } from '../../data/dataIO.ts';
44
import { getWriteInstructions } from '../../data/partialIO.ts';
55
import { sizeOf } from '../../data/sizeOf.ts';
6-
import type { BaseData, WgslTypeLiteral } from '../../data/wgslTypes.ts';
6+
import type { BaseData } from '../../data/wgslTypes.ts';
77
import { isWgslData } from '../../data/wgslTypes.ts';
88
import type { StorageFlag } from '../../extension.ts';
99
import type { TgpuNamable } from '../../shared/meta.ts';
1010
import { getName, setName } from '../../shared/meta.ts';
11-
import type { Infer, InferPartial, MemIdentity } from '../../shared/repr.ts';
11+
import type {
12+
Infer,
13+
InferPartial,
14+
IsValidIndexSchema,
15+
IsValidStorageSchema,
16+
IsValidUniformSchema,
17+
IsValidVertexSchema,
18+
MemIdentity,
19+
} from '../../shared/repr.ts';
1220
import { $internal } from '../../shared/symbols.ts';
13-
import type { UnionToIntersection } from '../../shared/utilityTypes.ts';
21+
import type {
22+
Prettify,
23+
UnionToIntersection,
24+
} from '../../shared/utilityTypes.ts';
1425
import { isGPUBuffer } from '../../types.ts';
1526
import type { ExperimentalTgpuRoot } from '../root/rootTypes.ts';
1627
import {
@@ -23,7 +34,6 @@ import {
2334
type TgpuFixedBufferUsage,
2435
} from './bufferUsage.ts';
2536
import type { AnyData } from '../../data/dataTypes.ts';
26-
import type { Undecorate } from '../../data/decorateUtils.ts';
2737

2838
// ----------
2939
// Public API
@@ -75,15 +85,18 @@ const usageToUsageConstructor = {
7585
readonly: asReadonly,
7686
};
7787

78-
type IsIndexCompatible<TData extends BaseData> = Undecorate<TData> extends {
79-
readonly type: 'array';
80-
readonly elementType: infer TElement;
81-
}
82-
? TElement extends BaseData
83-
? Undecorate<TElement> extends { readonly type: 'u32' | 'u16' } ? true
84-
: false
85-
: false
86-
: false;
88+
/**
89+
* Done as an object to later Prettify it
90+
*/
91+
type InnerValidUsagesFor<T> = {
92+
usage:
93+
| (IsValidStorageSchema<T> extends true ? 'storage' : never)
94+
| (IsValidUniformSchema<T> extends true ? 'uniform' : never)
95+
| (IsValidVertexSchema<T> extends true ? 'vertex' : never)
96+
| (IsValidIndexSchema<T> extends true ? 'index' : never);
97+
};
98+
99+
export type ValidUsagesFor<T> = InnerValidUsagesFor<T>['usage'];
87100

88101
export interface TgpuBuffer<TData extends BaseData> extends TgpuNamable {
89102
readonly [$internal]: true;
@@ -99,7 +112,12 @@ export interface TgpuBuffer<TData extends BaseData> extends TgpuNamable {
99112
usableAsVertex: boolean;
100113
usableAsIndex: boolean;
101114

102-
$usage<T extends RestrictUsages<TData>>(
115+
$usage<
116+
T extends [
117+
Prettify<InnerValidUsagesFor<TData>>['usage'],
118+
...Prettify<InnerValidUsagesFor<TData>>['usage'][],
119+
],
120+
>(
103121
...usages: T
104122
): this & UnionToIntersection<LiteralToUsageType<T[number]>>;
105123
$addFlags(flags: GPUBufferUsageFlags): this;
@@ -152,30 +170,6 @@ export function isUsableAsIndex<T extends TgpuBuffer<AnyData>>(
152170
// --------------
153171
const endianness = getSystemEndianness();
154172

155-
type IsArrayOfU32<TData extends BaseData> = Undecorate<TData> extends {
156-
readonly type: 'array';
157-
readonly elementType: infer TElement;
158-
}
159-
? TElement extends BaseData
160-
? Undecorate<TElement> extends { readonly type: 'u32' } ? true
161-
: false
162-
: false
163-
: false;
164-
165-
type IsWgslLiteral<TData extends BaseData> = TData extends {
166-
readonly type: WgslTypeLiteral;
167-
} ? true
168-
: false;
169-
170-
type RestrictUsages<TData extends BaseData> = string extends TData['type']
171-
? ('uniform' | 'storage' | 'vertex' | 'index')[]
172-
: IsIndexCompatible<TData> extends true
173-
? IsArrayOfU32<TData> extends true
174-
? ('uniform' | 'storage' | 'vertex' | 'index')[]
175-
: ['index']
176-
: IsWgslLiteral<TData> extends true ? ('uniform' | 'storage' | 'vertex')[]
177-
: ['vertex'];
178-
179173
class TgpuBufferImpl<TData extends AnyData> implements TgpuBuffer<TData> {
180174
public readonly [$internal] = true;
181175
public readonly resourceType = 'buffer';

packages/typegpu/src/core/buffer/bufferUsage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ export class TgpuLaidOutBufferImpl<
235235

236236
ctx.addDeclaration(
237237
`@group(${group}) @binding(${this._membership.idx}) var<${usage}> ${id}: ${
238-
ctx.resolve(this.dataType as AnyWgslData)
238+
ctx.resolve(this.dataType)
239239
};`,
240240
);
241241

packages/typegpu/src/core/function/fnCore.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import { undecorate } from '../../data/decorateUtils.ts';
44
import type { AnyData } from '../../data/dataTypes.ts';
55
import { snip } from '../../data/snippet.ts';
66
import {
7-
type AnyWgslStruct,
87
isWgslData,
98
isWgslStruct,
109
Void,
10+
type WgslStruct,
1111
} from '../../data/wgslTypes.ts';
1212
import { MissingLinksError } from '../../errors.ts';
1313
import { getMetaData, getName, setName } from '../../shared/meta.ts';
@@ -182,7 +182,7 @@ export function createFnCore(
182182
alias,
183183
snip(
184184
`_arg_${i}.${name}`,
185-
(argTypes[i] as AnyWgslStruct)
185+
(argTypes[i] as WgslStruct)
186186
.propTypes[name],
187187
),
188188
])

packages/typegpu/src/core/resolve/resolveData.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ function resolveStruct(ctx: ResolutionCtx, struct: WgslStruct) {
129129
ctx.addDeclaration(`\
130130
struct ${id} {
131131
${
132-
Object.entries(struct.propTypes)
132+
Object.entries(struct.propTypes as Record<string, BaseData>)
133133
.map((prop) => resolveStructProperty(ctx, prop))
134134
.join('')
135135
}\
@@ -159,7 +159,7 @@ function resolveUnstruct(ctx: ResolutionCtx, unstruct: Unstruct) {
159159
ctx.addDeclaration(`\
160160
struct ${id} {
161161
${
162-
Object.entries(unstruct.propTypes)
162+
Object.entries(unstruct.propTypes as Record<string, BaseData>)
163163
.map((prop) =>
164164
isAttribute(prop[1])
165165
? resolveStructProperty(ctx, [

packages/typegpu/src/core/root/rootTypes.ts

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import type { AnyComputeBuiltin, OmitBuiltins } from '../../builtin.ts';
22
import type { TgpuQuerySet } from '../../core/querySet/querySet.ts';
3+
import type { AnyData, Disarray } from '../../data/dataTypes.ts';
34
import type { UndecorateRecord } from '../../data/decorateUtils.ts';
4-
import type { AnyData, Disarray, HasNestedType } from '../../data/dataTypes.ts';
55
import type { AnyWgslData, U16, U32, WgslArray } from '../../data/wgslTypes.ts';
66
import type { NameRegistry } from '../../nameRegistry.ts';
7-
import type { Infer } from '../../shared/repr.ts';
7+
import type {
8+
ExtractInvalidSchemaError,
9+
Infer,
10+
IsValidBufferSchema,
11+
IsValidStorageSchema,
12+
IsValidUniformSchema,
13+
} from '../../shared/repr.ts';
814
import type {
915
Mutable,
1016
OmitProps,
@@ -387,16 +393,20 @@ export interface RenderPass {
387393
): undefined;
388394
}
389395

390-
type ValidateSchema<TData extends AnyData> = HasNestedType<
391-
[TData],
392-
'bool'
393-
> extends true ? 'Error: Bool is not host-shareable, use U32 or I32 instead'
394-
: HasNestedType<[TData], 'u16'> extends true ? TData extends {
395-
type: 'array';
396-
elementType: { type: 'u16' };
397-
} ? TData
398-
: 'Error: U16 is only usable inside arrays for index buffers'
399-
: TData;
396+
export type ValidateBufferSchema<TData extends AnyData> =
397+
IsValidBufferSchema<TData> extends false
398+
? ExtractInvalidSchemaError<TData, '(Error) '>
399+
: TData;
400+
401+
export type ValidateStorageSchema<TData extends AnyData> =
402+
IsValidStorageSchema<TData> extends false
403+
? ExtractInvalidSchemaError<TData, '(Error) '>
404+
: TData;
405+
406+
export type ValidateUniformSchema<TData extends AnyData> =
407+
IsValidUniformSchema<TData> extends false
408+
? ExtractInvalidSchemaError<TData, '(Error) '>
409+
: TData;
400410

401411
export interface TgpuRoot extends Unwrapper {
402412
/**
@@ -414,8 +424,9 @@ export interface TgpuRoot extends Unwrapper {
414424
* @param initial The initial value of the buffer. (optional)
415425
*/
416426
createBuffer<TData extends AnyData>(
417-
typeSchema: ValidateSchema<TData>,
418-
initial?: Infer<TData> | undefined,
427+
typeSchema: ValidateBufferSchema<TData>,
428+
// NoInfer is there to infer the schema type just based on the first parameter
429+
initial?: Infer<NoInfer<TData>> | undefined,
419430
): TgpuBuffer<TData>;
420431

421432
/**
@@ -428,7 +439,7 @@ export interface TgpuRoot extends Unwrapper {
428439
* @param gpuBuffer A vanilla WebGPU buffer.
429440
*/
430441
createBuffer<TData extends AnyData>(
431-
typeSchema: ValidateSchema<TData>,
442+
typeSchema: ValidateBufferSchema<TData>,
432443
gpuBuffer: GPUBuffer,
433444
): TgpuBuffer<TData>;
434445

@@ -441,8 +452,9 @@ export interface TgpuRoot extends Unwrapper {
441452
* @param initial The initial value of the buffer. (optional)
442453
*/
443454
createUniform<TData extends AnyWgslData>(
444-
typeSchema: TData,
445-
initial?: Infer<TData>,
455+
typeSchema: ValidateUniformSchema<TData>,
456+
// NoInfer is there to infer the schema type just based on the first parameter
457+
initial?: Infer<NoInfer<TData>>,
446458
): TgpuUniform<TData>;
447459

448460
/**
@@ -454,7 +466,7 @@ export interface TgpuRoot extends Unwrapper {
454466
* @param gpuBuffer A vanilla WebGPU buffer.
455467
*/
456468
createUniform<TData extends AnyWgslData>(
457-
typeSchema: TData,
469+
typeSchema: ValidateUniformSchema<TData>,
458470
gpuBuffer: GPUBuffer,
459471
): TgpuUniform<TData>;
460472

@@ -467,8 +479,9 @@ export interface TgpuRoot extends Unwrapper {
467479
* @param initial The initial value of the buffer. (optional)
468480
*/
469481
createMutable<TData extends AnyWgslData>(
470-
typeSchema: TData,
471-
initial?: Infer<TData>,
482+
typeSchema: ValidateStorageSchema<TData>,
483+
// NoInfer is there to infer the schema type just based on the first parameter
484+
initial?: Infer<NoInfer<TData>>,
472485
): TgpuMutable<TData>;
473486

474487
/**
@@ -480,7 +493,7 @@ export interface TgpuRoot extends Unwrapper {
480493
* @param gpuBuffer A vanilla WebGPU buffer.
481494
*/
482495
createMutable<TData extends AnyWgslData>(
483-
typeSchema: TData,
496+
typeSchema: ValidateStorageSchema<TData>,
484497
gpuBuffer: GPUBuffer,
485498
): TgpuMutable<TData>;
486499

@@ -493,8 +506,9 @@ export interface TgpuRoot extends Unwrapper {
493506
* @param initial The initial value of the buffer. (optional)
494507
*/
495508
createReadonly<TData extends AnyWgslData>(
496-
typeSchema: TData,
497-
initial?: Infer<TData>,
509+
typeSchema: ValidateStorageSchema<TData>,
510+
// NoInfer is there to infer the schema type just based on the first parameter
511+
initial?: Infer<NoInfer<TData>>,
498512
): TgpuReadonly<TData>;
499513

500514
/**
@@ -506,7 +520,7 @@ export interface TgpuRoot extends Unwrapper {
506520
* @param gpuBuffer A vanilla WebGPU buffer.
507521
*/
508522
createReadonly<TData extends AnyWgslData>(
509-
typeSchema: TData,
523+
typeSchema: ValidateStorageSchema<TData>,
510524
gpuBuffer: GPUBuffer,
511525
): TgpuReadonly<TData>;
512526

packages/typegpu/src/core/vertexLayout/vertexAttribute.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
import type {
2-
AnyUnstruct,
32
Disarray,
43
LooseDecorated,
4+
Unstruct,
55
} from '../../data/dataTypes.ts';
6-
import type {
7-
AnyWgslStruct,
8-
Decorated,
9-
WgslArray,
10-
} from '../../data/wgslTypes.ts';
6+
import type { Decorated, WgslArray, WgslStruct } from '../../data/wgslTypes.ts';
117
import type {
128
KindToAcceptedAttribMap,
139
KindToDefaultFormatMap,
@@ -23,8 +19,7 @@ import type {
2319
* - WgslStruct<{ a: Vec3f, b: unorm8x2 }>
2420
* - WgslStruct<{ nested: WgslStruct<{ a: Vec3f }> }>
2521
*/
26-
export type DataToContainedAttribs<T> = T extends AnyWgslStruct | AnyUnstruct
27-
? {
22+
export type DataToContainedAttribs<T> = T extends WgslStruct | Unstruct ? {
2823
[Key in keyof T['propTypes']]: DataToContainedAttribs<
2924
T['propTypes'][Key]
3025
>;

packages/typegpu/src/core/vertexLayout/vertexLayout.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,9 @@ function dataToContainedAttribs<
8989
if (isWgslStruct(data)) {
9090
let memberOffset = offset;
9191

92+
const propTypes = data.propTypes as Record<string, BaseData>;
9293
return Object.fromEntries(
93-
Object.entries(data.propTypes).map(([key, value]) => {
94+
Object.entries(propTypes).map(([key, value]) => {
9495
memberOffset = roundUp(memberOffset, alignmentOf(value));
9596
const attrib = [
9697
key,
@@ -111,8 +112,9 @@ function dataToContainedAttribs<
111112
if (isUnstruct(data)) {
112113
let memberOffset = offset;
113114

115+
const propTypes = data.propTypes as Record<string, BaseData>;
114116
return Object.fromEntries(
115-
Object.entries(data.propTypes).map(([key, value]) => {
117+
Object.entries(propTypes).map(([key, value]) => {
116118
memberOffset = roundUp(memberOffset, customAlignmentOf(value));
117119
const attrib = [
118120
key,

packages/typegpu/src/data/alignmentOf.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ function computeAlignment(data: object): number {
4545
}
4646

4747
if (isWgslStruct(data)) {
48-
return Object.values(data.propTypes)
48+
return Object.values(data.propTypes as Record<string, BaseData>)
4949
.map(alignmentOf)
5050
.reduce((a, b) => (a > b ? a : b));
5151
}
@@ -56,7 +56,8 @@ function computeAlignment(data: object): number {
5656

5757
if (isUnstruct(data)) {
5858
// A loose struct is aligned to its first property.
59-
const firstProp = Object.values(data.propTypes)[0];
59+
const firstProp =
60+
Object.values(data.propTypes as Record<string, BaseData>)[0];
6061
return firstProp ? (getCustomAlignment(firstProp) ?? 1) : 1;
6162
}
6263

@@ -80,7 +81,8 @@ function computeAlignment(data: object): number {
8081
function computeCustomAlignment(data: BaseData): number {
8182
if (isUnstruct(data)) {
8283
// A loose struct is aligned to its first property.
83-
const firstProp = Object.values(data.propTypes)[0];
84+
const firstProp =
85+
Object.values(data.propTypes as Record<string, BaseData>)[0];
8486
return firstProp ? customAlignmentOf(firstProp) : 1;
8587
}
8688

packages/typegpu/src/data/atomic.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import type { Infer, MemIdentity } from '../shared/repr.ts';
22
import { $internal } from '../shared/symbols.ts';
3-
import type { $gpuRepr, $memIdent, $repr } from '../shared/symbols.ts';
3+
import type {
4+
$gpuRepr,
5+
$memIdent,
6+
$repr,
7+
$validStorageSchema,
8+
$validUniformSchema,
9+
$validVertexSchema,
10+
} from '../shared/symbols.ts';
411
import type { Atomic, atomicI32, atomicU32, I32, U32 } from './wgslTypes.ts';
512

613
// ----------
@@ -34,6 +41,9 @@ class AtomicImpl<TSchema extends U32 | I32> implements Atomic<TSchema> {
3441
declare readonly [$repr]: Infer<TSchema>;
3542
declare readonly [$memIdent]: MemIdentity<TSchema>;
3643
declare readonly [$gpuRepr]: TSchema extends U32 ? atomicU32 : atomicI32;
44+
declare readonly [$validStorageSchema]: true;
45+
declare readonly [$validUniformSchema]: true;
46+
declare readonly [$validVertexSchema]: true;
3747
// ---
3848

3949
constructor(public readonly inner: TSchema) {}

0 commit comments

Comments
 (0)