Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return proxy from .value properties #795

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 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 @@ -103,10 +95,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)).bind(this),
toString: (() => `.value:${this.label ?? '<unnamed>'}`).bind(this),
},
valueProxyHandler,
) as Infer<TData>;
}
}

export class TgpuLaidOutBufferImpl<
TData extends BaseWgslData,
TUsage extends BindableBufferUsage,
Expand Down Expand Up @@ -146,7 +144,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)).bind(this),
toString: (() => `.value:${this.label ?? '<unnamed>'}`).bind(this),
},
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)).bind(this),
toString: (() => `.value:${this.label ?? '<unnamed>'}`).bind(this),
},
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)).bind(this),
toString: (() => `.value:${this.label ?? '<unnamed>'}`).bind(this),
},
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 All @@ -23,6 +24,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 @@ -32,7 +34,7 @@ function createDerived<T>(compute: () => T): TgpuDerived<T> {
);
}

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

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

'~compute'() {
const ctx = getResolutionCtx();
if (!ctx) {
Expand All @@ -76,7 +80,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>;

with<TValue>(slot: TgpuSlot<TValue>, value: Eventual<TValue>): TgpuDerived<T>;

Expand All @@ -35,6 +37,7 @@ export interface TgpuAccessor<T extends AnyWgslData = AnyWgslData>
extends TgpuNamable,
Labelled {
readonly resourceType: 'accessor';
'~repr': Infer<T>;

readonly schema: T;
readonly defaultValue:
Expand Down
41 changes: 41 additions & 0 deletions packages/typegpu/src/core/valueProxyUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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);
}

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)).bind(this),
toString: (() => `.value:${this.label ?? '<unnamed>'}`).bind(this),
},
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 @@ -122,15 +122,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 @@ -166,3 +171,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';
}
48 changes: 48 additions & 0 deletions packages/typegpu/tests/accessor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,52 @@ describe('tgpu.accessor', () => {
]),
);
});

it('resolves in tgsl functions, using .value', ({ root }) => {
const colorAccessorValue = tgpu['~unstable'].accessor(d.vec3f, RED);
const colorAccessorUsage = tgpu['~unstable'].accessor(
d.vec3f,
unstable_asUniform(
root.createBuffer(d.vec3f, RED).$usage('uniform').$name('colorUniform'),
),
);

const colorAccessorFn = tgpu['~unstable'].accessor(
d.vec3f,
tgpu['~unstable']
.fn([], d.vec3f)
.does(() => RED)
.$name('getColor'),
);

const main = tgpu['~unstable']
.fn([])
.does(() => {
const color = colorAccessorValue.value;
const color2 = colorAccessorUsage.value;
const color3 = colorAccessorFn.value;
})
.$name('main');

const resolved = tgpu.resolve({
externals: { main },
names: 'strict',
});

expect(parse(resolved)).toEqual(
parse(/* wgsl */ `
@group(0) @binding(0) var<uniform> colorUniform: vec3f;

fn getColor() -> vec3f {
return vec3f(1, 0, 0);
}

fn main() {
var color = vec3f(1, 0, 0);
var color2 = colorUniform;
var color3 = getColor();
}
`),
);
});
});
Loading
Loading