Skip to content

Commit

Permalink
Return proxy from .value properties (#795)
Browse files Browse the repository at this point in the history
  • Loading branch information
mhawryluk authored Feb 4, 2025
1 parent 6689eee commit 077388e
Show file tree
Hide file tree
Showing 16 changed files with 686 additions and 37 deletions.
37 changes: 23 additions & 14 deletions packages/typegpu/src/core/buffer/bufferUsage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
ResolutionCtx,
SelfResolvable,
} from '../../types';
import { valueProxyHandler } from '../valueProxyUtils';
import { type TgpuBuffer, type Uniform, isUsableAsUniform } from './buffer';

// ----------
Expand Down Expand Up @@ -37,15 +38,6 @@ export interface TgpuBufferReadonly<TData extends BaseWgslData>
export interface TgpuBufferMutable<TData extends BaseWgslData>
extends TgpuBufferUsage<TData, 'mutable'> {}

export function isBufferUsage<
T extends
| TgpuBufferUniform<BaseWgslData>
| TgpuBufferReadonly<BaseWgslData>
| TgpuBufferMutable<BaseWgslData>,
>(value: T | unknown): value is T {
return (value as T)?.resourceType === 'buffer-usage';
}

// --------------
// Implementation
// --------------
Expand Down Expand Up @@ -89,7 +81,9 @@ class TgpuFixedBufferImpl<
const usage = usageToVarTemplateMap[this.usage];

ctx.addDeclaration(
`@group(${group}) @binding(${binding}) var<${usage}> ${id}: ${ctx.resolve(this.buffer.dataType)};`,
`@group(${group}) @binding(${binding}) var<${usage}> ${id}: ${ctx.resolve(
this.buffer.dataType,
)};`,
);

return id;
Expand All @@ -103,10 +97,16 @@ class TgpuFixedBufferImpl<
if (!inGPUMode()) {
throw new Error(`Cannot access buffer's value directly in JS.`);
}
return this as Infer<TData>;

return new Proxy(
{
'~resolve': (ctx: ResolutionCtx) => ctx.resolve(this),
toString: () => `.value:${this.label ?? '<unnamed>'}`,
},
valueProxyHandler,
) as Infer<TData>;
}
}

export class TgpuLaidOutBufferImpl<
TData extends BaseWgslData,
TUsage extends BindableBufferUsage,
Expand All @@ -132,7 +132,9 @@ export class TgpuLaidOutBufferImpl<
const usage = usageToVarTemplateMap[this.usage];

ctx.addDeclaration(
`@group(${group}) @binding(${this._membership.idx}) var<${usage}> ${id}: ${ctx.resolve(this.dataType as AnyWgslData)};`,
`@group(${group}) @binding(${
this._membership.idx
}) var<${usage}> ${id}: ${ctx.resolve(this.dataType as AnyWgslData)};`,
);

return id;
Expand All @@ -146,7 +148,14 @@ export class TgpuLaidOutBufferImpl<
if (!inGPUMode()) {
throw new Error(`Cannot access buffer's value directly in JS.`);
}
return this as Infer<TData>;

return new Proxy(
{
'~resolve': (ctx: ResolutionCtx) => ctx.resolve(this),
toString: () => `.value:${this.label ?? '<unnamed>'}`,
},
valueProxyHandler,
) as Infer<TData>;
}
}

Expand Down
10 changes: 9 additions & 1 deletion packages/typegpu/src/core/constant/tgpuConstant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { inGPUMode } from '../../gpuMode';
import type { TgpuNamable } from '../../namable';
import type { Infer } from '../../shared/repr';
import type { ResolutionCtx, SelfResolvable } from '../../types';
import { valueProxyHandler } from '../valueProxyUtils';
import type { Exotic } from './../../data/exotic';

// ----------
Expand Down Expand Up @@ -65,6 +66,13 @@ class TgpuConstImpl<TDataType extends AnyWgslData>
if (!inGPUMode()) {
return this._value;
}
return this as Infer<TDataType>;

return new Proxy(
{
'~resolve': (ctx: ResolutionCtx) => ctx.resolve(this),
toString: () => `.value:${this.label ?? '<unnamed>'}`,
},
valueProxyHandler,
) as Infer<TDataType>;
}
}
18 changes: 15 additions & 3 deletions packages/typegpu/src/core/slot/accessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import type { AnyWgslData } from '../../data';
import type { Exotic } from '../../data/exotic';
import { getResolutionCtx } from '../../gpuMode';
import type { Infer } from '../../shared/repr';
import type { ResolutionCtx, SelfResolvable } from '../../types';
import { type TgpuBufferUsage, isBufferUsage } from '../buffer/bufferUsage';
import {
type ResolutionCtx,
type SelfResolvable,
isBufferUsage,
} from '../../types';
import type { TgpuBufferUsage } from '../buffer/bufferUsage';
import { type TgpuFn, isTgpuFn } from '../function/tgpuFn';
import { valueProxyHandler } from '../valueProxyUtils';
import { slot } from './slot';
import type { TgpuAccessor, TgpuSlot } from './slotTypes';

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

Expand Down Expand Up @@ -62,7 +68,13 @@ export class TgpuAccessorImpl<T extends AnyWgslData>
);
}

return this as unknown as Infer<T>;
return new Proxy(
{
'~resolve': (ctx: ResolutionCtx) => ctx.resolve(this),
toString: () => `.value:${this.label ?? '<unnamed>'}`,
},
valueProxyHandler,
) as Infer<T>;
}

'~resolve'(ctx: ResolutionCtx): string {
Expand Down
8 changes: 6 additions & 2 deletions packages/typegpu/src/core/slot/derived.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getResolutionCtx } from '../../gpuMode';
import type { Infer } from '../../shared/repr';
import { unwrapProxy } from '../valueProxyUtils';
import type {
Eventual,
SlotValuePair,
Expand Down Expand Up @@ -27,6 +28,7 @@ function createDerived<T>(compute: () => T): TgpuDerived<T> {
const result = {
resourceType: 'derived' as const,
'~compute': compute,
'~repr': undefined as Infer<T>,

get value(): Infer<T> {
const ctx = getResolutionCtx();
Expand All @@ -36,7 +38,7 @@ function createDerived<T>(compute: () => T): TgpuDerived<T> {
);
}

return ctx.unwrap(this) as Infer<T>;
return unwrapProxy(ctx.unwrap(this));
},

with<TValue>(
Expand All @@ -60,6 +62,8 @@ function createBoundDerived<T>(
): TgpuDerived<T> {
const result = {
resourceType: 'derived' as const,
'~repr': undefined as Infer<T>,

'~compute'() {
throw new Error(
`'~compute' should never be read on bound derived items.`,
Expand All @@ -78,7 +82,7 @@ function createBoundDerived<T>(
);
}

return ctx.unwrap(this) as Infer<T>;
return unwrapProxy(ctx.unwrap(this));
},

with<TValue>(
Expand Down
4 changes: 3 additions & 1 deletion packages/typegpu/src/core/slot/slot.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getResolutionCtx } from '../../gpuMode';
import type { Infer } from '../../shared/repr';
import { unwrapProxy } from '../valueProxyUtils';
import type { TgpuSlot } from './slotTypes';

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

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

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

return ctx.unwrap(this) as Infer<T>;
return unwrapProxy(ctx.unwrap(this));
}
}
3 changes: 3 additions & 0 deletions packages/typegpu/src/core/slot/slotTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { TgpuBufferUsage } from './../buffer/bufferUsage';

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

readonly defaultValue: T | undefined;

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

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

readonly schema: T;
readonly defaultValue:
Expand Down
45 changes: 45 additions & 0 deletions packages/typegpu/src/core/valueProxyUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
type Labelled,
type ResolutionCtx,
type SelfResolvable,
isBufferUsage,
} from '../types';
import { isAccessor, isDerived, isSlot } from './slot/slotTypes';

export const valueProxyHandler: ProxyHandler<SelfResolvable & Labelled> = {
get(target, prop) {
if (prop in target) {
return Reflect.get(target, prop);
}

if (prop === '~providing') {
return undefined;
}

return new Proxy(
{
'~resolve': (ctx: ResolutionCtx) =>
`${ctx.resolve(target)}.${String(prop)}`,

toString: () =>
`.value(...).${String(prop)}:${target.label ?? '<unnamed>'}`,
},
valueProxyHandler,
);
},
};

export function unwrapProxy<T>(value: unknown): T {
let unwrapped = value;

while (
isSlot(unwrapped) ||
isDerived(unwrapped) ||
isAccessor(unwrapped) ||
isBufferUsage(unwrapped)
) {
unwrapped = unwrapped.value;
}

return unwrapped as T;
}
10 changes: 9 additions & 1 deletion packages/typegpu/src/core/variable/tgpuVariable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { inGPUMode } from '../../gpuMode';
import type { TgpuNamable } from '../../namable';
import type { Infer } from '../../shared/repr';
import type { ResolutionCtx, SelfResolvable } from '../../types';
import { valueProxyHandler } from '../valueProxyUtils';

// ----------
// Public API
Expand Down Expand Up @@ -92,6 +93,13 @@ class TgpuVarImpl<TScope extends VariableScope, TDataType extends AnyWgslData>
if (!inGPUMode()) {
throw new Error(`Cannot access tgpu.var's value directly in JS.`);
}
return this as Infer<TDataType>;

return new Proxy(
{
'~resolve': (ctx: ResolutionCtx) => ctx.resolve(this),
toString: () => `.value:${this.label ?? '<unnamed>'}`,
},
valueProxyHandler,
) as Infer<TDataType>;
}
}
9 changes: 0 additions & 9 deletions packages/typegpu/src/smol/wgslGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,6 @@ function generateExpression(
}

if (isWgsl(target.value)) {
// NOTE: Temporary solution, assuming that access to `.value` of resolvables should always resolve to just the target.
if (propertyStr === 'value') {
return {
value: resolveRes(ctx, target),
// TODO: Infer data type
dataType: UnknownData,
};
}

return {
// biome-ignore lint/suspicious/noExplicitAny: <sorry TypeScript>
value: (target.value as any)[propertyStr],
Expand Down
16 changes: 15 additions & 1 deletion packages/typegpu/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { Block } from 'tinyest';
import type { TgpuBufferUsage } from './core/buffer/bufferUsage';
import type {
TgpuBufferMutable,
TgpuBufferReadonly,
TgpuBufferUniform,
TgpuBufferUsage,
} from './core/buffer/bufferUsage';
import type { TgpuConst } from './core/constant/tgpuConstant';
import type { TgpuDeclare } from './core/declare/tgpuDeclare';
import type { TgpuComputeFn } from './core/function/tgpuComputeFn';
Expand Down Expand Up @@ -168,3 +173,12 @@ export function isGPUBuffer(value: unknown): value is GPUBuffer {
'mapAsync' in value
);
}

export function isBufferUsage<
T extends
| TgpuBufferUniform<BaseWgslData>
| TgpuBufferReadonly<BaseWgslData>
| TgpuBufferMutable<BaseWgslData>,
>(value: T | unknown): value is T {
return (value as T)?.resourceType === 'buffer-usage';
}
Loading

0 comments on commit 077388e

Please sign in to comment.