Skip to content

Commit 077388e

Browse files
authored
Return proxy from .value properties (#795)
1 parent 6689eee commit 077388e

16 files changed

+686
-37
lines changed

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

+23-14
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
ResolutionCtx,
99
SelfResolvable,
1010
} from '../../types';
11+
import { valueProxyHandler } from '../valueProxyUtils';
1112
import { type TgpuBuffer, type Uniform, isUsableAsUniform } from './buffer';
1213

1314
// ----------
@@ -37,15 +38,6 @@ export interface TgpuBufferReadonly<TData extends BaseWgslData>
3738
export interface TgpuBufferMutable<TData extends BaseWgslData>
3839
extends TgpuBufferUsage<TData, 'mutable'> {}
3940

40-
export function isBufferUsage<
41-
T extends
42-
| TgpuBufferUniform<BaseWgslData>
43-
| TgpuBufferReadonly<BaseWgslData>
44-
| TgpuBufferMutable<BaseWgslData>,
45-
>(value: T | unknown): value is T {
46-
return (value as T)?.resourceType === 'buffer-usage';
47-
}
48-
4941
// --------------
5042
// Implementation
5143
// --------------
@@ -89,7 +81,9 @@ class TgpuFixedBufferImpl<
8981
const usage = usageToVarTemplateMap[this.usage];
9082

9183
ctx.addDeclaration(
92-
`@group(${group}) @binding(${binding}) var<${usage}> ${id}: ${ctx.resolve(this.buffer.dataType)};`,
84+
`@group(${group}) @binding(${binding}) var<${usage}> ${id}: ${ctx.resolve(
85+
this.buffer.dataType,
86+
)};`,
9387
);
9488

9589
return id;
@@ -103,10 +97,16 @@ class TgpuFixedBufferImpl<
10397
if (!inGPUMode()) {
10498
throw new Error(`Cannot access buffer's value directly in JS.`);
10599
}
106-
return this as Infer<TData>;
100+
101+
return new Proxy(
102+
{
103+
'~resolve': (ctx: ResolutionCtx) => ctx.resolve(this),
104+
toString: () => `.value:${this.label ?? '<unnamed>'}`,
105+
},
106+
valueProxyHandler,
107+
) as Infer<TData>;
107108
}
108109
}
109-
110110
export class TgpuLaidOutBufferImpl<
111111
TData extends BaseWgslData,
112112
TUsage extends BindableBufferUsage,
@@ -132,7 +132,9 @@ export class TgpuLaidOutBufferImpl<
132132
const usage = usageToVarTemplateMap[this.usage];
133133

134134
ctx.addDeclaration(
135-
`@group(${group}) @binding(${this._membership.idx}) var<${usage}> ${id}: ${ctx.resolve(this.dataType as AnyWgslData)};`,
135+
`@group(${group}) @binding(${
136+
this._membership.idx
137+
}) var<${usage}> ${id}: ${ctx.resolve(this.dataType as AnyWgslData)};`,
136138
);
137139

138140
return id;
@@ -146,7 +148,14 @@ export class TgpuLaidOutBufferImpl<
146148
if (!inGPUMode()) {
147149
throw new Error(`Cannot access buffer's value directly in JS.`);
148150
}
149-
return this as Infer<TData>;
151+
152+
return new Proxy(
153+
{
154+
'~resolve': (ctx: ResolutionCtx) => ctx.resolve(this),
155+
toString: () => `.value:${this.label ?? '<unnamed>'}`,
156+
},
157+
valueProxyHandler,
158+
) as Infer<TData>;
150159
}
151160
}
152161

packages/typegpu/src/core/constant/tgpuConstant.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { inGPUMode } from '../../gpuMode';
33
import type { TgpuNamable } from '../../namable';
44
import type { Infer } from '../../shared/repr';
55
import type { ResolutionCtx, SelfResolvable } from '../../types';
6+
import { valueProxyHandler } from '../valueProxyUtils';
67
import type { Exotic } from './../../data/exotic';
78

89
// ----------
@@ -65,6 +66,13 @@ class TgpuConstImpl<TDataType extends AnyWgslData>
6566
if (!inGPUMode()) {
6667
return this._value;
6768
}
68-
return this as Infer<TDataType>;
69+
70+
return new Proxy(
71+
{
72+
'~resolve': (ctx: ResolutionCtx) => ctx.resolve(this),
73+
toString: () => `.value:${this.label ?? '<unnamed>'}`,
74+
},
75+
valueProxyHandler,
76+
) as Infer<TDataType>;
6977
}
7078
}

packages/typegpu/src/core/slot/accessor.ts

+15-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@ import type { AnyWgslData } from '../../data';
22
import type { Exotic } from '../../data/exotic';
33
import { getResolutionCtx } from '../../gpuMode';
44
import type { Infer } from '../../shared/repr';
5-
import type { ResolutionCtx, SelfResolvable } from '../../types';
6-
import { type TgpuBufferUsage, isBufferUsage } from '../buffer/bufferUsage';
5+
import {
6+
type ResolutionCtx,
7+
type SelfResolvable,
8+
isBufferUsage,
9+
} from '../../types';
10+
import type { TgpuBufferUsage } from '../buffer/bufferUsage';
711
import { type TgpuFn, isTgpuFn } from '../function/tgpuFn';
12+
import { valueProxyHandler } from '../valueProxyUtils';
813
import { slot } from './slot';
914
import type { TgpuAccessor, TgpuSlot } from './slotTypes';
1015

@@ -30,6 +35,7 @@ export class TgpuAccessorImpl<T extends AnyWgslData>
3035
implements TgpuAccessor<T>, SelfResolvable
3136
{
3237
readonly resourceType = 'accessor';
38+
'~repr' = undefined as Infer<T>;
3339
public label?: string | undefined;
3440
public slot: TgpuSlot<TgpuFn<[], T> | TgpuBufferUsage<T> | Infer<T>>;
3541

@@ -62,7 +68,13 @@ export class TgpuAccessorImpl<T extends AnyWgslData>
6268
);
6369
}
6470

65-
return this as unknown as Infer<T>;
71+
return new Proxy(
72+
{
73+
'~resolve': (ctx: ResolutionCtx) => ctx.resolve(this),
74+
toString: () => `.value:${this.label ?? '<unnamed>'}`,
75+
},
76+
valueProxyHandler,
77+
) as Infer<T>;
6678
}
6779

6880
'~resolve'(ctx: ResolutionCtx): string {

packages/typegpu/src/core/slot/derived.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getResolutionCtx } from '../../gpuMode';
22
import type { Infer } from '../../shared/repr';
3+
import { unwrapProxy } from '../valueProxyUtils';
34
import type {
45
Eventual,
56
SlotValuePair,
@@ -27,6 +28,7 @@ function createDerived<T>(compute: () => T): TgpuDerived<T> {
2728
const result = {
2829
resourceType: 'derived' as const,
2930
'~compute': compute,
31+
'~repr': undefined as Infer<T>,
3032

3133
get value(): Infer<T> {
3234
const ctx = getResolutionCtx();
@@ -36,7 +38,7 @@ function createDerived<T>(compute: () => T): TgpuDerived<T> {
3638
);
3739
}
3840

39-
return ctx.unwrap(this) as Infer<T>;
41+
return unwrapProxy(ctx.unwrap(this));
4042
},
4143

4244
with<TValue>(
@@ -60,6 +62,8 @@ function createBoundDerived<T>(
6062
): TgpuDerived<T> {
6163
const result = {
6264
resourceType: 'derived' as const,
65+
'~repr': undefined as Infer<T>,
66+
6367
'~compute'() {
6468
throw new Error(
6569
`'~compute' should never be read on bound derived items.`,
@@ -78,7 +82,7 @@ function createBoundDerived<T>(
7882
);
7983
}
8084

81-
return ctx.unwrap(this) as Infer<T>;
85+
return unwrapProxy(ctx.unwrap(this));
8286
},
8387

8488
with<TValue>(

packages/typegpu/src/core/slot/slot.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getResolutionCtx } from '../../gpuMode';
22
import type { Infer } from '../../shared/repr';
3+
import { unwrapProxy } from '../valueProxyUtils';
34
import type { TgpuSlot } from './slotTypes';
45

56
// ----------
@@ -17,6 +18,7 @@ export function slot<T>(defaultValue?: T): TgpuSlot<T> {
1718
class TgpuSlotImpl<T> implements TgpuSlot<T> {
1819
readonly resourceType = 'slot';
1920
public label?: string | undefined;
21+
'~repr' = undefined as Infer<T>;
2022

2123
constructor(public defaultValue: T | undefined = undefined) {}
2224

@@ -39,6 +41,6 @@ class TgpuSlotImpl<T> implements TgpuSlot<T> {
3941
throw new Error(`Cannot access tgpu.slot's value outside of resolution.`);
4042
}
4143

42-
return ctx.unwrap(this) as Infer<T>;
44+
return unwrapProxy(ctx.unwrap(this));
4345
}
4446
}

packages/typegpu/src/core/slot/slotTypes.ts

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { TgpuBufferUsage } from './../buffer/bufferUsage';
77

88
export interface TgpuSlot<T> extends TgpuNamable, Labelled {
99
readonly resourceType: 'slot';
10+
'~repr': Infer<T>;
1011

1112
readonly defaultValue: T | undefined;
1213

@@ -22,6 +23,7 @@ export interface TgpuSlot<T> extends TgpuNamable, Labelled {
2223
export interface TgpuDerived<T> {
2324
readonly resourceType: 'derived';
2425
readonly value: Infer<T>;
26+
'~repr': Infer<T>;
2527
readonly '~providing'?: Providing | undefined;
2628

2729
with<TValue>(slot: TgpuSlot<TValue>, value: Eventual<TValue>): TgpuDerived<T>;
@@ -36,6 +38,7 @@ export interface TgpuAccessor<T extends AnyWgslData = AnyWgslData>
3638
extends TgpuNamable,
3739
Labelled {
3840
readonly resourceType: 'accessor';
41+
'~repr': Infer<T>;
3942

4043
readonly schema: T;
4144
readonly defaultValue:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {
2+
type Labelled,
3+
type ResolutionCtx,
4+
type SelfResolvable,
5+
isBufferUsage,
6+
} from '../types';
7+
import { isAccessor, isDerived, isSlot } from './slot/slotTypes';
8+
9+
export const valueProxyHandler: ProxyHandler<SelfResolvable & Labelled> = {
10+
get(target, prop) {
11+
if (prop in target) {
12+
return Reflect.get(target, prop);
13+
}
14+
15+
if (prop === '~providing') {
16+
return undefined;
17+
}
18+
19+
return new Proxy(
20+
{
21+
'~resolve': (ctx: ResolutionCtx) =>
22+
`${ctx.resolve(target)}.${String(prop)}`,
23+
24+
toString: () =>
25+
`.value(...).${String(prop)}:${target.label ?? '<unnamed>'}`,
26+
},
27+
valueProxyHandler,
28+
);
29+
},
30+
};
31+
32+
export function unwrapProxy<T>(value: unknown): T {
33+
let unwrapped = value;
34+
35+
while (
36+
isSlot(unwrapped) ||
37+
isDerived(unwrapped) ||
38+
isAccessor(unwrapped) ||
39+
isBufferUsage(unwrapped)
40+
) {
41+
unwrapped = unwrapped.value;
42+
}
43+
44+
return unwrapped as T;
45+
}

packages/typegpu/src/core/variable/tgpuVariable.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { inGPUMode } from '../../gpuMode';
44
import type { TgpuNamable } from '../../namable';
55
import type { Infer } from '../../shared/repr';
66
import type { ResolutionCtx, SelfResolvable } from '../../types';
7+
import { valueProxyHandler } from '../valueProxyUtils';
78

89
// ----------
910
// Public API
@@ -92,6 +93,13 @@ class TgpuVarImpl<TScope extends VariableScope, TDataType extends AnyWgslData>
9293
if (!inGPUMode()) {
9394
throw new Error(`Cannot access tgpu.var's value directly in JS.`);
9495
}
95-
return this as Infer<TDataType>;
96+
97+
return new Proxy(
98+
{
99+
'~resolve': (ctx: ResolutionCtx) => ctx.resolve(this),
100+
toString: () => `.value:${this.label ?? '<unnamed>'}`,
101+
},
102+
valueProxyHandler,
103+
) as Infer<TDataType>;
96104
}
97105
}

packages/typegpu/src/smol/wgslGenerator.ts

-9
Original file line numberDiff line numberDiff line change
@@ -123,15 +123,6 @@ function generateExpression(
123123
}
124124

125125
if (isWgsl(target.value)) {
126-
// NOTE: Temporary solution, assuming that access to `.value` of resolvables should always resolve to just the target.
127-
if (propertyStr === 'value') {
128-
return {
129-
value: resolveRes(ctx, target),
130-
// TODO: Infer data type
131-
dataType: UnknownData,
132-
};
133-
}
134-
135126
return {
136127
// biome-ignore lint/suspicious/noExplicitAny: <sorry TypeScript>
137128
value: (target.value as any)[propertyStr],

packages/typegpu/src/types.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import type { Block } from 'tinyest';
2-
import type { TgpuBufferUsage } from './core/buffer/bufferUsage';
2+
import type {
3+
TgpuBufferMutable,
4+
TgpuBufferReadonly,
5+
TgpuBufferUniform,
6+
TgpuBufferUsage,
7+
} from './core/buffer/bufferUsage';
38
import type { TgpuConst } from './core/constant/tgpuConstant';
49
import type { TgpuDeclare } from './core/declare/tgpuDeclare';
510
import type { TgpuComputeFn } from './core/function/tgpuComputeFn';
@@ -168,3 +173,12 @@ export function isGPUBuffer(value: unknown): value is GPUBuffer {
168173
'mapAsync' in value
169174
);
170175
}
176+
177+
export function isBufferUsage<
178+
T extends
179+
| TgpuBufferUniform<BaseWgslData>
180+
| TgpuBufferReadonly<BaseWgslData>
181+
| TgpuBufferMutable<BaseWgslData>,
182+
>(value: T | unknown): value is T {
183+
return (value as T)?.resourceType === 'buffer-usage';
184+
}

0 commit comments

Comments
 (0)