Skip to content
Draft
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
17 changes: 17 additions & 0 deletions apps/typegpu-docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,23 @@ const DEV = import.meta.env.DEV;
export default defineConfig({
site: 'https://docs.swmansion.com',
base: 'TypeGPU',
server: {
// Required for '@rolldown/browser' to work in dev mode.
// Since the service worker is hosted on the /TypeGPU path,
// fetches from /@fs/ fail due to CORS. This fixes that.
headers: {
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin',
},
},
vite: {
define: {
// Required for '@rolldown/browser' to work.
'process.env.NODE_DEBUG_NATIVE': '""',
},
optimizeDeps: {
exclude: ['@rolldown/browser'],
},
// Allowing query params, for invalidation
plugins: [
wasm(),
Expand All @@ -42,6 +58,7 @@ export default defineConfig({
ssr: {
noExternal: [
'wgsl-wasm-transpiler-bundler',
'@rolldown/browser',
],
},
},
Expand Down
1 change: 1 addition & 0 deletions apps/typegpu-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@monaco-editor/react": "^4.7.0",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-slider": "^1.3.5",
"@rolldown/browser": "1.0.0-beta.32",
"@stackblitz/sdk": "^1.11.0",
"@tailwindcss/vite": "^4.1.11",
"@typegpu/color": "workspace:*",
Expand Down
175 changes: 175 additions & 0 deletions apps/typegpu-docs/public/coi-serviceworker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */

// This service worker is responsible for intercepting fetch requests to
// assets hosted on the same origin, and attaching CORS headers that
// allow SharedArrayBuffer to function (required by @rolldown/browser).

let coepCredentialless = false;
if (typeof window === 'undefined') {
self.addEventListener('install', () => self.skipWaiting());
self.addEventListener(
'activate',
(event) => event.waitUntil(self.clients.claim()),
);

self.addEventListener('message', (ev) => {
if (!ev.data) {
return;
}

if (ev.data.type === 'deregister') {
self.registration
.unregister()
.then(() => {
return self.clients.matchAll();
})
.then((clients) => {
for (const client of clients) {
client.navigate(client.url);
}
});
} else if (ev.data.type === 'coepCredentialless') {
coepCredentialless = ev.data.value;
}
});

self.addEventListener('fetch', (event) => {
const r = event.request;
if (r.cache === 'only-if-cached' && r.mode !== 'same-origin') {
return;
}

const request = (coepCredentialless && r.mode === 'no-cors')
? new Request(r, {
credentials: 'omit',
})
: r;
event.respondWith(
fetch(request)
.then((response) => {
if (response.status === 0) {
return response;
}

const newHeaders = new Headers(response.headers);
newHeaders.set(
'Cross-Origin-Embedder-Policy',
coepCredentialless ? 'credentialless' : 'require-corp',
);
if (!coepCredentialless) {
newHeaders.set('Cross-Origin-Resource-Policy', 'cross-origin');
}
newHeaders.set('Cross-Origin-Opener-Policy', 'same-origin');

return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders,
});
})
.catch((e) => console.error(e)),
);
});
} else {
(() => {
const reloadedBySelf = window.sessionStorage.getItem('coiReloadedBySelf');
window.sessionStorage.removeItem('coiReloadedBySelf');
const coepDegrading = reloadedBySelf === 'coepdegrade';

// You can customize the behavior of this script through a global `coi` variable.
const coi = {
shouldRegister: () => !reloadedBySelf,
shouldDeregister: () => false,
coepCredentialless: () => true,
coepDegrade: () => true,
doReload: () => window.location.reload(),
quiet: false,
...window.coi,
};

const n = navigator;
const controlling = !!n.serviceWorker && !!n.serviceWorker.controller;

// Record the failure if the page is served by serviceWorker.
if (controlling && !window.crossOriginIsolated) {
window.sessionStorage.setItem('coiCoepHasFailed', 'true');
}
const coepHasFailed = window.sessionStorage.getItem('coiCoepHasFailed');

if (controlling) {
// Reload only on the first failure.
const reloadToDegrade = coi.coepDegrade() && !(
coepDegrading || window.crossOriginIsolated
);
n.serviceWorker.controller.postMessage({
type: 'coepCredentialless',
value: (reloadToDegrade || coepHasFailed && coi.coepDegrade())
? false
: coi.coepCredentialless(),
});
if (reloadToDegrade) {
!coi.quiet && console.log('Reloading page to degrade COEP.');
window.sessionStorage.setItem('coiReloadedBySelf', 'coepdegrade');
coi.doReload('coepdegrade');
}

if (coi.shouldDeregister()) {
n.serviceWorker.controller.postMessage({ type: 'deregister' });
}
}

// If we're already coi: do nothing. Perhaps it's due to this script doing its job, or COOP/COEP are
// already set from the origin server. Also if the browser has no notion of crossOriginIsolated, just give up here.
if (window.crossOriginIsolated !== false || !coi.shouldRegister()) return;

if (!window.isSecureContext) {
!coi.quiet &&
console.log(
'COOP/COEP Service Worker not registered, a secure context is required.',
);
return;
}

// In some environments (e.g. Firefox private mode) this won't be available
if (!n.serviceWorker) {
!coi.quiet &&
console.error(
'COOP/COEP Service Worker not registered, perhaps due to private mode.',
);
return;
}

n.serviceWorker.register(window.document.currentScript.src).then(
(registration) => {
!coi.quiet &&
console.log(
'COOP/COEP Service Worker registered',
registration.scope,
);

registration.addEventListener('updatefound', () => {
!coi.quiet &&
console.log(
'Reloading page to make use of updated COOP/COEP Service Worker.',
);
window.sessionStorage.setItem('coiReloadedBySelf', 'updatefound');
coi.doReload();
});

// If the registration is active, but it's not controlling the page
if (registration.active && !n.serviceWorker.controller) {
!coi.quiet &&
console.log(
'Reloading page to make use of COOP/COEP Service Worker.',
);
window.sessionStorage.setItem('coiReloadedBySelf', 'notcontrolling');
coi.doReload();
}
},
(err) => {
!coi.quiet &&
console.error('COOP/COEP Service Worker failed to register:', err);
},
);
})();
}
2 changes: 1 addition & 1 deletion apps/typegpu-docs/src/components/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function handleEditorWillMount(monaco: Monaco) {
entries(SANDBOX_MODULES),
map(([key, moduleDef]) => {
if ('reroute' in moduleDef.typeDef) {
return [key, moduleDef.typeDef.reroute] as const;
return [key, [moduleDef.typeDef.reroute]] as [string, string[]];
}
return null;
}),
Expand Down
12 changes: 3 additions & 9 deletions apps/typegpu-docs/src/components/translator/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ fn fs_main() -> @location(0) vec4<f32> {

export const DEFAULT_TGSL = `import tgpu from 'typegpu';
import * as d from 'typegpu/data';
import * as std from 'typegpu/std';

const Particle = d.struct({
position: d.vec3f,
Expand All @@ -42,14 +41,9 @@ const layout = tgpu.bindGroupLayout({

export const updateParicle = tgpu.fn([Particle, d.vec3f, d.f32], Particle)(
(particle, gravity, deltaTime) => {
const newVelocity = std.mul(
particle.velocity,
std.mul(gravity, deltaTime),
);
const newPosition = std.add(
particle.position,
std.mul(newVelocity, deltaTime),
);
const newVelocity = particle.velocity.mul(gravity).mul(deltaTime);
const newPosition = particle.position.add(newVelocity.mul(deltaTime));

return Particle({
position: newPosition,
velocity: newVelocity,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function setupMonacoEditor(monaco: Monaco) {
entries(SANDBOX_MODULES),
map(([key, moduleDef]) => {
if ('reroute' in moduleDef.typeDef) {
return [key, moduleDef.typeDef.reroute] as const;
return [key, [moduleDef.typeDef.reroute]] as [string, string[]];
}
return null;
}),
Expand Down
99 changes: 99 additions & 0 deletions apps/typegpu-docs/src/components/translator/lib/rolldown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import type { InputOptions, OutputOptions } from '@rolldown/browser';
import { join } from 'pathe';

export interface BundleResult {
output: Record<string, string | Uint8Array>;
warnings?: string[] | undefined;
}

export interface SourceFile {
filename: string;
code: string;
isEntry?: boolean;
}

export type FileMap = Record<
string,
{
content: string;
} | {
reroute: string;
} | undefined
>;

export async function bundle(
files: FileMap,
entries: string[],
config: InputOptions & { output?: OutputOptions | undefined } = {},
): Promise<BundleResult> {
const rolldown = await import('@rolldown/browser');

const warnings: string[] = [];

const inputOptions: InputOptions = {
input: entries,
cwd: '/',
onLog(level, log, logger) {
if (level === 'warn') {
warnings.push(String(log));
} else {
logger(level, log);
}
},
...config,
plugins: [
// Virtual file system plugin
{
name: 'virtual-fs',
resolveId(source, importer) {
const id = source[0] === '.'
? join(importer || '/', '..', source)
: source;

if (files[id] && 'reroute' in files[id]) {
// Rerouting
return files[id].reroute;
}

return id;
},
load(id) {
if (!files[id]) {
return;
}

if ('reroute' in files[id]) {
// Reroutes are supposed to be resolved in `resolveId`
throw new Error(`Unresolved reroute for ${id}`);
}

return files[id].content;
},
},
...(Array.isArray(config?.plugins)
? config.plugins
: [config?.plugins].filter(Boolean)),
],
};

const outputOptions: OutputOptions = {
format: 'esm',
...config?.output,
};

const bundle = await rolldown.rolldown(inputOptions);
const result = await bundle.generate(outputOptions);

const output = Object.fromEntries(
result.output.map((chunk) =>
chunk.type === 'chunk'
? [chunk.fileName, chunk.code]
: [chunk.fileName, chunk.source]
),
);

return {
output,
warnings,
};
}
Loading
Loading