Skip to content

refactor: Using $internal symbol for storing internals in plain sight #1182

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

Merged
merged 15 commits into from
Apr 25, 2025
Merged
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
2 changes: 2 additions & 0 deletions packages/typegpu/src/core/function/tgpuFn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type TgpuFnShellHeader<
Args extends AnyWgslData[] | Record<string, AnyWgslData>,
Return extends AnyWgslData | undefined = AnyWgslData | undefined,
> = {
readonly [$internal]: true;
readonly argTypes: Args;
readonly returnType: Return | undefined;
readonly isEntry: false;
Expand Down Expand Up @@ -122,6 +123,7 @@ export function fn<
Return extends AnyWgslData | undefined = undefined,
>(argTypes: Args, returnType?: Return): TgpuFnShell<Args, Return> {
const shell: TgpuFnShellHeader<Args, Return> = {
[$internal]: true,
argTypes,
returnType,
isEntry: false,
Expand Down
26 changes: 17 additions & 9 deletions packages/typegpu/src/core/pipeline/computePipeline.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { MissingBindGroupsError } from '../../errors.ts';
import type { TgpuNamable } from '../../namable.ts';
import { resolve } from '../../resolutionCtx.ts';
import { $internal } from '../../shared/symbols.ts';
import type {
TgpuBindGroup,
TgpuBindGroupLayout,
Expand All @@ -9,11 +10,16 @@ import type { TgpuComputeFn } from '../function/tgpuComputeFn.ts';
import type { ExperimentalTgpuRoot } from '../root/rootTypes.ts';
import type { TgpuSlot } from '../slot/slotTypes.ts';

interface ComputePipelineInternals {
readonly rawPipeline: GPUComputePipeline;
}

// ----------
// Public API
// ----------

export interface TgpuComputePipeline extends TgpuNamable {
readonly [$internal]: ComputePipelineInternals;
readonly resourceType: 'compute-pipeline';
readonly label: string | undefined;

Expand All @@ -29,10 +35,6 @@ export interface TgpuComputePipeline extends TgpuNamable {
): void;
}

export interface INTERNAL_TgpuComputePipeline {
readonly rawPipeline: GPUComputePipeline;
}

export function INTERNAL_createComputePipeline(
branch: ExperimentalTgpuRoot,
slotBindings: [TgpuSlot<unknown>, unknown][],
Expand All @@ -47,7 +49,8 @@ export function INTERNAL_createComputePipeline(
export function isComputePipeline(
value: unknown,
): value is TgpuComputePipeline {
return (value as TgpuComputePipeline)?.resourceType === 'compute-pipeline';
const maybe = value as TgpuComputePipeline | undefined;
return maybe?.resourceType === 'compute-pipeline' && !!maybe[$internal];
}

// --------------
Expand All @@ -64,15 +67,20 @@ type Memo = {
catchall: [number, TgpuBindGroup] | null;
};

class TgpuComputePipelineImpl
implements TgpuComputePipeline, INTERNAL_TgpuComputePipeline
{
class TgpuComputePipelineImpl implements TgpuComputePipeline {
public readonly [$internal]: ComputePipelineInternals;
public readonly resourceType = 'compute-pipeline';

constructor(
private readonly _core: ComputePipelineCore,
private readonly _priors: TgpuComputePipelinePriors,
) {}
) {
this[$internal] = {
get rawPipeline() {
return _core.unwrap().pipeline;
},
};
}

get label(): string | undefined {
return this._core.label;
Expand Down
84 changes: 48 additions & 36 deletions packages/typegpu/src/core/pipeline/renderPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from '../../errors.ts';
import type { TgpuNamable } from '../../namable.ts';
import { resolve } from '../../resolutionCtx.ts';
import { $internal } from '../../shared/symbols.ts';
import type { AnyVertexAttribs } from '../../shared/vertexFormat.ts';
import {
type TgpuBindGroup,
Expand All @@ -29,12 +30,18 @@ import {
import { connectAttachmentToShader } from './connectAttachmentToShader.ts';
import { connectTargetsToShader } from './connectTargetsToShader.ts';

interface RenderPipelineInternals {
readonly core: RenderPipelineCore;
readonly priors: TgpuRenderPipelinePriors;
}

// ----------
// Public API
// ----------

export interface TgpuRenderPipeline<Output extends IOLayout = IOLayout>
extends TgpuNamable {
readonly [$internal]: RenderPipelineInternals;
readonly resourceType: 'render-pipeline';
readonly label: string | undefined;

Expand Down Expand Up @@ -63,11 +70,6 @@ export interface TgpuRenderPipeline<Output extends IOLayout = IOLayout>
): void;
}

export interface INTERNAL_TgpuRenderPipeline {
readonly core: RenderPipelineCore;
readonly priors: TgpuRenderPipelinePriors;
}

export type FragmentOutToTargets<T extends IOLayout> = T extends IOData
? GPUColorTargetState
: T extends Record<string, unknown>
Expand Down Expand Up @@ -200,7 +202,8 @@ export function INTERNAL_createRenderPipeline(
}

export function isRenderPipeline(value: unknown): value is TgpuRenderPipeline {
return (value as TgpuRenderPipeline)?.resourceType === 'render-pipeline';
const maybe = value as TgpuRenderPipeline | undefined;
return maybe?.resourceType === 'render-pipeline' && !!maybe[$internal];
}

// --------------
Expand All @@ -224,22 +227,23 @@ type Memo = {
catchall: [number, TgpuBindGroup] | null;
};

class TgpuRenderPipelineImpl
implements TgpuRenderPipeline, INTERNAL_TgpuRenderPipeline
{
class TgpuRenderPipelineImpl implements TgpuRenderPipeline {
public readonly [$internal]: RenderPipelineInternals;
public readonly resourceType = 'render-pipeline';

constructor(
public readonly core: RenderPipelineCore,
public readonly priors: TgpuRenderPipelinePriors,
) {}
constructor(core: RenderPipelineCore, priors: TgpuRenderPipelinePriors) {
this[$internal] = {
core,
priors,
};
}

get label() {
return this.core.label;
return this[$internal].core.label;
}

$name(label?: string | undefined): this {
this.core.label = label;
this[$internal].core.label = label;
return this;
}

Expand All @@ -255,21 +259,23 @@ class TgpuRenderPipelineImpl
definition: TgpuVertexLayout | TgpuBindGroupLayout,
resource: (TgpuBuffer<AnyWgslData> & VertexFlag) | TgpuBindGroup,
): TgpuRenderPipeline {
const internals = this[$internal];

if (isBindGroupLayout(definition)) {
return new TgpuRenderPipelineImpl(this.core, {
...this.priors,
return new TgpuRenderPipelineImpl(internals.core, {
...internals.priors,
bindGroupLayoutMap: new Map([
...(this.priors.bindGroupLayoutMap ?? []),
...(internals.priors.bindGroupLayoutMap ?? []),
[definition, resource as TgpuBindGroup],
]),
});
}

if (isVertexLayout(definition)) {
return new TgpuRenderPipelineImpl(this.core, {
...this.priors,
return new TgpuRenderPipelineImpl(internals.core, {
...internals.priors,
vertexLayoutMap: new Map([
...(this.priors.vertexLayoutMap ?? []),
...(internals.priors.vertexLayoutMap ?? []),
[definition, resource as TgpuBuffer<AnyWgslData> & VertexFlag],
]),
});
Expand All @@ -281,17 +287,21 @@ class TgpuRenderPipelineImpl
withColorAttachment(
attachment: AnyFragmentColorAttachment,
): TgpuRenderPipeline {
return new TgpuRenderPipelineImpl(this.core, {
...this.priors,
const internals = this[$internal];

return new TgpuRenderPipelineImpl(internals.core, {
...internals.priors,
colorAttachment: attachment,
});
}

withDepthStencilAttachment(
attachment: DepthStencilAttachment,
): TgpuRenderPipeline {
return new TgpuRenderPipelineImpl(this.core, {
...this.priors,
const internals = this[$internal];

return new TgpuRenderPipelineImpl(internals.core, {
...internals.priors,
depthStencilAttachment: attachment,
});
}
Expand All @@ -302,12 +312,14 @@ class TgpuRenderPipelineImpl
firstVertex?: number,
firstInstance?: number,
): void {
const memo = this.core.unwrap();
const { branch, fragmentFn } = this.core.options;
const internals = this[$internal];

const memo = internals.core.unwrap();
const { branch, fragmentFn } = internals.core.options;

const colorAttachments = connectAttachmentToShader(
fragmentFn.shell.targets,
this.priors.colorAttachment ?? {},
internals.priors.colorAttachment ?? {},
).map((attachment) => {
if (isTexture(attachment.view)) {
return {
Expand All @@ -323,12 +335,12 @@ class TgpuRenderPipelineImpl
colorAttachments,
};

if (this.core.label !== undefined) {
renderPassDescriptor.label = this.core.label;
if (internals.core.label !== undefined) {
renderPassDescriptor.label = internals.core.label;
}

if (this.priors.depthStencilAttachment !== undefined) {
const attachment = this.priors.depthStencilAttachment;
if (internals.priors.depthStencilAttachment !== undefined) {
const attachment = internals.priors.depthStencilAttachment;
if (isTexture(attachment.view)) {
renderPassDescriptor.depthStencilAttachment = {
...attachment,
Expand All @@ -352,19 +364,19 @@ class TgpuRenderPipelineImpl
pass.setBindGroup(idx, branch.unwrap(memo.catchall[1]));
missingBindGroups.delete(layout);
} else {
const bindGroup = this.priors.bindGroupLayoutMap?.get(layout);
const bindGroup = internals.priors.bindGroupLayoutMap?.get(layout);
if (bindGroup !== undefined) {
missingBindGroups.delete(layout);
pass.setBindGroup(idx, branch.unwrap(bindGroup));
}
}
});

const missingVertexLayouts = new Set(this.core.usedVertexLayouts);
const missingVertexLayouts = new Set(internals.core.usedVertexLayouts);

const usedVertexLayouts = this.core.usedVertexLayouts;
const usedVertexLayouts = internals.core.usedVertexLayouts;
usedVertexLayouts.forEach((vertexLayout, idx) => {
const buffer = this.priors.vertexLayoutMap?.get(vertexLayout);
const buffer = internals.priors.vertexLayoutMap?.get(vertexLayout);
if (buffer) {
missingVertexLayouts.delete(vertexLayout);
pass.setVertexBuffer(idx, branch.unwrap(buffer));
Expand Down
Loading