d3-force-webgpu
Version:
Force-directed graph layout using velocity Verlet integration with WebGPU acceleration.
3 lines (2 loc) • 37.8 kB
JavaScript
// https://github.com/jamescarruthers/d3-force-webgpu v3.1.2 Copyright 2010-2021 Mike Bostock
!function(n,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("d3-quadtree"),require("d3-dispatch"),require("d3-timer")):"function"==typeof define&&define.amd?define(["exports","d3-quadtree","d3-dispatch","d3-timer"],e):e((n="undefined"!=typeof globalThis?globalThis:n||self).d3=n.d3||{},n.d3,n.d3,n.d3)}(this,(function(n,e,t,r){"use strict";function i(n){return function(){return n}}function a(n){return 1e-6*(n()-.5)}function o(n){return n.x+n.vx}function f(n){return n.y+n.vy}function s(n){return n.index}function u(n,e){var t=n.get(e);if(!t)throw new Error("node not found: "+e);return t}const l=4294967296;function d(){let n=1;return()=>(n=(1664525*n+1013904223)%l)/l}function c(n){return n.x}function h(n){return n.y}var g=Math.PI*(3-Math.sqrt(5));let y=null,p=null,x=null;async function v(){return y||(x||(x=(async()=>{if(!navigator.gpu)throw new Error("WebGPU not supported in this browser");if(p=await navigator.gpu.requestAdapter({powerPreference:"high-performance"}),!p)throw new Error("No WebGPU adapter found");const n={maxStorageBufferBindingSize:p.limits.maxStorageBufferBindingSize,maxBufferSize:p.limits.maxBufferSize,maxComputeWorkgroupsPerDimension:p.limits.maxComputeWorkgroupsPerDimension};return y=await p.requestDevice({requiredLimits:n}),y.lost.then((n=>{console.error("WebGPU device lost:",n.message),y=null,p=null,x=null})),y.onuncapturederror=n=>{console.error("WebGPU uncaptured error:",n.error)},y})(),x))}function m(){return"undefined"!=typeof navigator&&!!navigator.gpu}async function b(n,e,t,r){const i=n.createShaderModule({code:e}),a=await i.getCompilationInfo();if(a.messages.some((n=>"error"===n.type))){const n=a.messages.filter((n=>"error"===n.type));throw new Error("Shader compilation failed: "+n.map((n=>n.message)).join("\n"))}return n.createComputePipeline({layout:r?n.createPipelineLayout({bindGroupLayouts:[r]}):"auto",compute:{module:i,entryPoint:t}})}class w{constructor(n,e){this.device=n,this.nodeCount=e,this.isMapped=!1,this.floatsPerNode=8,this.bytesPerNode=4*this.floatsPerNode,this.bufferSize=this.nodeCount*this.bytesPerNode,this.storageBuffer=n.createBuffer({size:this.bufferSize,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST|GPUBufferUsage.COPY_SRC,label:"Node Storage Buffer"}),this.stagingBuffer=n.createBuffer({size:this.bufferSize,usage:GPUBufferUsage.MAP_READ|GPUBufferUsage.COPY_DST,label:"Node Staging Buffer"}),this.cpuData=new Float32Array(this.nodeCount*this.floatsPerNode)}uploadNodes(n){for(let e=0;e<n.length;e++){const t=n[e],r=e*this.floatsPerNode;this.cpuData[r+0]=t.x||0,this.cpuData[r+1]=t.y||0,this.cpuData[r+2]=t.vx||0,this.cpuData[r+3]=t.vy||0,this.cpuData[r+4]=null!=t.fx?t.fx:NaN,this.cpuData[r+5]=null!=t.fy?t.fy:NaN,this.cpuData[r+6]=null!=t._strength?t._strength:-30,this.cpuData[r+7]=null!=t.radius?t.radius:5}this.device.queue.writeBuffer(this.storageBuffer,0,this.cpuData)}async downloadNodes(n){if(this.isMapped)return void console.warn("Buffer already mapped, skipping download");const e=this.device.createCommandEncoder();e.copyBufferToBuffer(this.storageBuffer,0,this.stagingBuffer,0,this.bufferSize),this.device.queue.submit([e.finish()]),this.isMapped=!0;try{await this.stagingBuffer.mapAsync(GPUMapMode.READ);const e=new Float32Array(this.stagingBuffer.getMappedRange().slice(0));this.stagingBuffer.unmap();for(let t=0;t<n.length;t++){const r=t*this.floatsPerNode;n[t].x=e[r+0],n[t].y=e[r+1],n[t].vx=e[r+2],n[t].vy=e[r+3]}}finally{this.isMapped=!1}}async downloadVelocities(n){const e=this.device.createCommandEncoder();e.copyBufferToBuffer(this.storageBuffer,0,this.stagingBuffer,0,this.bufferSize),this.device.queue.submit([e.finish()]),await this.stagingBuffer.mapAsync(GPUMapMode.READ);const t=new Float32Array(this.stagingBuffer.getMappedRange().slice(0));this.stagingBuffer.unmap();for(let e=0;e<n.length;e++){const r=e*this.floatsPerNode;n[e].vx=t[r+2],n[e].vy=t[r+3]}}destroy(){this.storageBuffer.destroy(),this.stagingBuffer.destroy()}}class B{constructor(n,e){this.device=n,this.linkCount=e,this.floatsPerLink=8,this.bytesPerLink=4*this.floatsPerLink,this.bufferSize=Math.max(32,this.linkCount*this.bytesPerLink),this.storageBuffer=n.createBuffer({size:this.bufferSize,usage:GPUBufferUsage.STORAGE|GPUBufferUsage.COPY_DST,label:"Link Storage Buffer"}),this.cpuData=new Float32Array(this.linkCount*this.floatsPerLink)}uploadLinks(n,e,t,r){for(let i=0;i<n.length;i++){const a=n[i],o=i*this.floatsPerLink;this.cpuData[o+0]=a.source.index,this.cpuData[o+1]=a.target.index,this.cpuData[o+2]=e[i],this.cpuData[o+3]=t[i],this.cpuData[o+4]=r[i],this.cpuData[o+5]=0,this.cpuData[o+6]=0,this.cpuData[o+7]=0}this.device.queue.writeBuffer(this.storageBuffer,0,this.cpuData)}destroy(){this.storageBuffer.destroy()}}class k{constructor(n){this.device=n,this.bufferSize=96,this.storageBuffer=n.createBuffer({size:this.bufferSize,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST,label:"Simulation Params Buffer"}),this.arrayBuffer=new ArrayBuffer(96),this.floatView=new Float32Array(this.arrayBuffer),this.uintView=new Uint32Array(this.arrayBuffer)}update(n){this.floatView[0]=n.alpha||1,this.floatView[1]=n.velocityDecay||.6,this.uintView[2]=n.nodeCount||0,this.uintView[3]=n.linkCount||0,this.floatView[4]=n.centerX||0,this.floatView[5]=n.centerY||0,this.floatView[6]=n.centerStrength||1,this.floatView[7]=n.theta2||.81,this.floatView[8]=n.distanceMin2||1;const e=n.distanceMax2;this.floatView[9]=e===1/0||e>1e30?1e30:e,this.uintView[10]=n.iterations||1,this.floatView[11]=n.collisionRadius||5,this.floatView[12]=n.collisionStrength||1,this.uintView[13]=n.collisionIterations||1,this.floatView[14]=n.forceXTarget||0,this.floatView[15]=n.forceXStrength||.1,this.floatView[16]=n.forceYTarget||0,this.floatView[17]=n.forceYStrength||.1,this.floatView[18]=n.radialX||0,this.floatView[19]=n.radialY||0,this.floatView[20]=n.radialRadius||100,this.floatView[21]=n.radialStrength||.1,this.floatView[22]=0,this.floatView[23]=0,this.device.queue.writeBuffer(this.storageBuffer,0,this.arrayBuffer)}destroy(){this.storageBuffer.destroy()}}const S="\n// Many-Body Force Compute Shader\n// Implements N-body gravitational/repulsive force using tile-based algorithm\n\nstruct Node {\n x: f32,\n y: f32,\n vx: f32,\n vy: f32,\n fx: f32,\n fy: f32,\n strength: f32,\n radius: f32,\n}\n\nstruct Params {\n alpha: f32,\n velocityDecay: f32,\n nodeCount: u32,\n linkCount: u32,\n centerX: f32,\n centerY: f32,\n centerStrength: f32,\n theta2: f32,\n distanceMin2: f32,\n distanceMax2: f32,\n iterations: u32,\n collisionRadius: f32,\n collisionStrength: f32,\n collisionIterations: u32,\n forceXTarget: f32,\n forceXStrength: f32,\n forceYTarget: f32,\n forceYStrength: f32,\n radialX: f32,\n radialY: f32,\n radialRadius: f32,\n radialStrength: f32,\n _pad1: f32,\n _pad2: f32,\n}\n\n@group(0) @binding(0) var<storage, read_write> nodes: array<Node>;\n@group(0) @binding(1) var<uniform> params: Params;\n\nconst TILE_SIZE: u32 = 256u;\nvar<workgroup> tile: array<vec4<f32>, 256>;\n\nfn jiggle(seed: u32) -> f32 {\n let s = (seed * 1103515245u + 12345u) & 0x7fffffffu;\n return (f32(s) / f32(0x7fffffff) - 0.5) * 1e-6;\n}\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(global_invocation_id) global_id: vec3<u32>,\n @builtin(local_invocation_id) local_id: vec3<u32>,\n @builtin(workgroup_id) workgroup_id: vec3<u32>\n) {\n let i = global_id.x;\n let nodeCount = params.nodeCount;\n let isValid = i < nodeCount;\n\n // Load node data (use defaults for out-of-bounds threads)\n var myPos = vec2<f32>(0.0, 0.0);\n var myVx: f32 = 0.0;\n var myVy: f32 = 0.0;\n\n if (isValid) {\n let node = nodes[i];\n myPos = vec2<f32>(node.x, node.y);\n myVx = node.vx;\n myVy = node.vy;\n }\n\n var forceX: f32 = 0.0;\n var forceY: f32 = 0.0;\n\n let alpha = params.alpha;\n let distanceMin2 = params.distanceMin2;\n let distanceMax2 = params.distanceMax2;\n\n let numTiles = (nodeCount + TILE_SIZE - 1u) / TILE_SIZE;\n\n for (var t: u32 = 0u; t < numTiles; t++) {\n // All threads participate in loading the tile\n let tileIdx = t * TILE_SIZE + local_id.x;\n if (tileIdx < nodeCount) {\n let other = nodes[tileIdx];\n tile[local_id.x] = vec4<f32>(other.x, other.y, other.strength, 0.0);\n } else {\n tile[local_id.x] = vec4<f32>(0.0, 0.0, 0.0, 0.0);\n }\n\n // All threads must hit this barrier\n workgroupBarrier();\n\n // Only valid threads compute forces\n if (isValid) {\n let tileEnd = min(TILE_SIZE, nodeCount - t * TILE_SIZE);\n for (var j: u32 = 0u; j < tileEnd; j++) {\n let otherIdx = t * TILE_SIZE + j;\n if (otherIdx != i) {\n let other = tile[j];\n var dx = other.x - myPos.x;\n var dy = other.y - myPos.y;\n var l2 = dx * dx + dy * dy;\n\n if (l2 < distanceMax2) {\n if (dx == 0.0) {\n dx = jiggle(i * nodeCount + otherIdx);\n l2 += dx * dx;\n }\n if (dy == 0.0) {\n dy = jiggle(i * nodeCount + otherIdx + 1u);\n l2 += dy * dy;\n }\n\n if (l2 < distanceMin2) {\n l2 = sqrt(distanceMin2 * l2);\n }\n\n let strength = other.z;\n let force = strength * alpha / l2;\n\n forceX += dx * force;\n forceY += dy * force;\n }\n }\n }\n }\n\n // All threads must hit this barrier\n workgroupBarrier();\n }\n\n // Only valid threads write results\n if (isValid) {\n nodes[i].vx = myVx + forceX;\n nodes[i].vy = myVy + forceY;\n }\n}\n",_="\n// Link Force Compute Shader\n\nstruct Node {\n x: f32,\n y: f32,\n vx: f32,\n vy: f32,\n fx: f32,\n fy: f32,\n strength: f32,\n radius: f32,\n}\n\nstruct Link {\n sourceIdx: f32,\n targetIdx: f32,\n distance: f32,\n strength: f32,\n bias: f32,\n _pad1: f32,\n _pad2: f32,\n _pad3: f32,\n}\n\nstruct Params {\n alpha: f32,\n velocityDecay: f32,\n nodeCount: u32,\n linkCount: u32,\n centerX: f32,\n centerY: f32,\n centerStrength: f32,\n theta2: f32,\n distanceMin2: f32,\n distanceMax2: f32,\n iterations: u32,\n collisionRadius: f32,\n collisionStrength: f32,\n collisionIterations: u32,\n forceXTarget: f32,\n forceXStrength: f32,\n forceYTarget: f32,\n forceYStrength: f32,\n radialX: f32,\n radialY: f32,\n radialRadius: f32,\n radialStrength: f32,\n _pad1: f32,\n _pad2: f32,\n}\n\n@group(0) @binding(0) var<storage, read_write> nodes: array<Node>;\n@group(0) @binding(1) var<storage, read> links: array<Link>;\n@group(0) @binding(2) var<uniform> params: Params;\n\nfn jiggle(seed: u32) -> f32 {\n let s = (seed * 1103515245u + 12345u) & 0x7fffffffu;\n return (f32(s) / f32(0x7fffffff) - 0.5) * 1e-6;\n}\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(global_invocation_id) global_id: vec3<u32>\n) {\n let nodeIdx = global_id.x;\n let nodeCount = params.nodeCount;\n let linkCount = params.linkCount;\n let alpha = params.alpha;\n\n if (nodeIdx >= nodeCount) {\n return;\n }\n\n let node = nodes[nodeIdx];\n var dvx: f32 = 0.0;\n var dvy: f32 = 0.0;\n\n for (var i: u32 = 0u; i < linkCount; i++) {\n let link = links[i];\n let sourceIdx = u32(link.sourceIdx);\n let targetIdx = u32(link.targetIdx);\n\n if (sourceIdx != nodeIdx && targetIdx != nodeIdx) {\n continue;\n }\n\n let srcNode = nodes[sourceIdx];\n let dstNode = nodes[targetIdx];\n\n var dx = dstNode.x + dstNode.vx - srcNode.x - srcNode.vx;\n var dy = dstNode.y + dstNode.vy - srcNode.y - srcNode.vy;\n\n if (dx == 0.0 && dy == 0.0) {\n dx = jiggle(i * 2u);\n dy = jiggle(i * 2u + 1u);\n }\n\n let l = sqrt(dx * dx + dy * dy);\n let force = (l - link.distance) / l * alpha * link.strength;\n let fx = dx * force;\n let fy = dy * force;\n\n if (nodeIdx == targetIdx) {\n dvx -= fx * link.bias;\n dvy -= fy * link.bias;\n } else {\n dvx += fx * (1.0 - link.bias);\n dvy += fy * (1.0 - link.bias);\n }\n }\n\n nodes[nodeIdx].vx = node.vx + dvx;\n nodes[nodeIdx].vy = node.vy + dvy;\n}\n",P="\n// Position Integration Compute Shader\n\nstruct Node {\n x: f32,\n y: f32,\n vx: f32,\n vy: f32,\n fx: f32,\n fy: f32,\n strength: f32,\n radius: f32,\n}\n\nstruct Params {\n alpha: f32,\n velocityDecay: f32,\n nodeCount: u32,\n linkCount: u32,\n centerX: f32,\n centerY: f32,\n centerStrength: f32,\n theta2: f32,\n distanceMin2: f32,\n distanceMax2: f32,\n iterations: u32,\n collisionRadius: f32,\n collisionStrength: f32,\n collisionIterations: u32,\n forceXTarget: f32,\n forceXStrength: f32,\n forceYTarget: f32,\n forceYStrength: f32,\n radialX: f32,\n radialY: f32,\n radialRadius: f32,\n radialStrength: f32,\n _pad1: f32,\n _pad2: f32,\n}\n\n@group(0) @binding(0) var<storage, read_write> nodes: array<Node>;\n@group(0) @binding(1) var<uniform> params: Params;\n\nfn isNaN(v: f32) -> bool {\n return !(v == v);\n}\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(global_invocation_id) global_id: vec3<u32>\n) {\n let i = global_id.x;\n let nodeCount = params.nodeCount;\n\n if (i >= nodeCount) {\n return;\n }\n\n var node = nodes[i];\n let velocityDecay = params.velocityDecay;\n\n if (isNaN(node.fx)) {\n node.vx = node.vx * velocityDecay;\n node.x = node.x + node.vx;\n } else {\n node.x = node.fx;\n node.vx = 0.0;\n }\n\n if (isNaN(node.fy)) {\n node.vy = node.vy * velocityDecay;\n node.y = node.y + node.vy;\n } else {\n node.y = node.fy;\n node.vy = 0.0;\n }\n\n nodes[i] = node;\n}\n",M="\n// Collision Force Compute Shader\n\nstruct Node {\n x: f32,\n y: f32,\n vx: f32,\n vy: f32,\n fx: f32,\n fy: f32,\n strength: f32,\n radius: f32,\n}\n\nstruct Params {\n alpha: f32,\n velocityDecay: f32,\n nodeCount: u32,\n linkCount: u32,\n centerX: f32,\n centerY: f32,\n centerStrength: f32,\n theta2: f32,\n distanceMin2: f32,\n distanceMax2: f32,\n iterations: u32,\n collisionRadius: f32,\n collisionStrength: f32,\n collisionIterations: u32,\n forceXTarget: f32,\n forceXStrength: f32,\n forceYTarget: f32,\n forceYStrength: f32,\n radialX: f32,\n radialY: f32,\n radialRadius: f32,\n radialStrength: f32,\n _pad1: f32,\n _pad2: f32,\n}\n\n@group(0) @binding(0) var<storage, read_write> nodes: array<Node>;\n@group(0) @binding(1) var<uniform> params: Params;\n\nconst TILE_SIZE: u32 = 256u;\nvar<workgroup> tile: array<vec4<f32>, 256>;\n\nfn jiggle(seed: u32) -> f32 {\n let s = (seed * 1103515245u + 12345u) & 0x7fffffffu;\n return (f32(s) / f32(0x7fffffff) - 0.5) * 1e-6;\n}\n\n@compute @workgroup_size(256)\nfn main(\n @builtin(global_invocation_id) global_id: vec3<u32>,\n @builtin(local_invocation_id) local_id: vec3<u32>\n) {\n let i = global_id.x;\n let nodeCount = params.nodeCount;\n let isValid = i < nodeCount;\n\n // Load node data (use defaults for out-of-bounds threads)\n var myX: f32 = 0.0;\n var myY: f32 = 0.0;\n var myRadius: f32 = 0.0;\n var myVx: f32 = 0.0;\n var myVy: f32 = 0.0;\n\n if (isValid) {\n let node = nodes[i];\n myX = node.x;\n myY = node.y;\n myRadius = node.radius;\n myVx = node.vx;\n myVy = node.vy;\n }\n\n var dvx: f32 = 0.0;\n var dvy: f32 = 0.0;\n\n let strength = params.collisionStrength;\n let numTiles = (nodeCount + TILE_SIZE - 1u) / TILE_SIZE;\n\n for (var t: u32 = 0u; t < numTiles; t++) {\n // All threads participate in loading the tile\n let tileIdx = t * TILE_SIZE + local_id.x;\n if (tileIdx < nodeCount) {\n let other = nodes[tileIdx];\n tile[local_id.x] = vec4<f32>(other.x, other.y, other.radius, 0.0);\n } else {\n tile[local_id.x] = vec4<f32>(0.0, 0.0, 0.0, 0.0);\n }\n\n // All threads must hit this barrier\n workgroupBarrier();\n\n // Only valid threads compute collisions\n if (isValid) {\n let tileEnd = min(TILE_SIZE, nodeCount - t * TILE_SIZE);\n for (var j: u32 = 0u; j < tileEnd; j++) {\n let otherIdx = t * TILE_SIZE + j;\n if (otherIdx != i) {\n let other = tile[j];\n let otherRadius = other.z;\n let combinedRadius = myRadius + otherRadius;\n\n var dx = myX - other.x;\n var dy = myY - other.y;\n var l2 = dx * dx + dy * dy;\n let minDist2 = combinedRadius * combinedRadius;\n\n if (l2 < minDist2) {\n if (dx == 0.0) {\n dx = jiggle(min(i, otherIdx) * nodeCount + max(i, otherIdx));\n l2 += dx * dx;\n }\n if (dy == 0.0) {\n dy = jiggle(min(i, otherIdx) * nodeCount + max(i, otherIdx) + 1u);\n l2 += dy * dy;\n }\n\n let l = sqrt(l2);\n let overlap = combinedRadius - l;\n\n let totalRadius = myRadius + otherRadius;\n let myWeight = otherRadius / totalRadius;\n\n let impulse = overlap * strength * 0.5;\n let nx = dx / l;\n let ny = dy / l;\n\n dvx += nx * impulse * myWeight;\n dvy += ny * impulse * myWeight;\n }\n }\n }\n }\n\n // All threads must hit this barrier\n workgroupBarrier();\n }\n\n // Only valid threads write results\n if (isValid) {\n nodes[i].vx = myVx + dvx;\n nodes[i].vy = myVy + dvy;\n }\n}\n",C="\n// X-Positioning Force Compute Shader\n// Pushes nodes toward a target x position\n\nstruct Node {\n x: f32,\n y: f32,\n vx: f32,\n vy: f32,\n fx: f32,\n fy: f32,\n strength: f32,\n radius: f32,\n}\n\nstruct Params {\n alpha: f32,\n velocityDecay: f32,\n nodeCount: u32,\n linkCount: u32,\n centerX: f32,\n centerY: f32,\n centerStrength: f32,\n theta2: f32,\n distanceMin2: f32,\n distanceMax2: f32,\n iterations: u32,\n collisionRadius: f32,\n collisionStrength: f32,\n collisionIterations: u32,\n forceXTarget: f32,\n forceXStrength: f32,\n forceYTarget: f32,\n forceYStrength: f32,\n radialX: f32,\n radialY: f32,\n radialRadius: f32,\n radialStrength: f32,\n _pad1: f32,\n _pad2: f32,\n}\n\n@group(0) @binding(0) var<storage, read_write> nodes: array<Node>;\n@group(0) @binding(1) var<uniform> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) global_id: vec3<u32>) {\n let i = global_id.x;\n if (i >= params.nodeCount) {\n return;\n }\n\n let node = nodes[i];\n let alpha = params.alpha;\n let targetX = params.forceXTarget;\n let strength = params.forceXStrength;\n\n let dx = targetX - node.x;\n nodes[i].vx = node.vx + dx * strength * alpha;\n}\n",I="\n// Y-Positioning Force Compute Shader\n// Pushes nodes toward a target y position\n\nstruct Node {\n x: f32,\n y: f32,\n vx: f32,\n vy: f32,\n fx: f32,\n fy: f32,\n strength: f32,\n radius: f32,\n}\n\nstruct Params {\n alpha: f32,\n velocityDecay: f32,\n nodeCount: u32,\n linkCount: u32,\n centerX: f32,\n centerY: f32,\n centerStrength: f32,\n theta2: f32,\n distanceMin2: f32,\n distanceMax2: f32,\n iterations: u32,\n collisionRadius: f32,\n collisionStrength: f32,\n collisionIterations: u32,\n forceXTarget: f32,\n forceXStrength: f32,\n forceYTarget: f32,\n forceYStrength: f32,\n radialX: f32,\n radialY: f32,\n radialRadius: f32,\n radialStrength: f32,\n _pad1: f32,\n _pad2: f32,\n}\n\n@group(0) @binding(0) var<storage, read_write> nodes: array<Node>;\n@group(0) @binding(1) var<uniform> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) global_id: vec3<u32>) {\n let i = global_id.x;\n if (i >= params.nodeCount) {\n return;\n }\n\n let node = nodes[i];\n let alpha = params.alpha;\n let targetY = params.forceYTarget;\n let strength = params.forceYStrength;\n\n let dy = targetY - node.y;\n nodes[i].vy = node.vy + dy * strength * alpha;\n}\n",N="\n// Radial Force Compute Shader\n// Pushes nodes toward a target radius from a center point\n\nstruct Node {\n x: f32,\n y: f32,\n vx: f32,\n vy: f32,\n fx: f32,\n fy: f32,\n strength: f32,\n radius: f32,\n}\n\nstruct Params {\n alpha: f32,\n velocityDecay: f32,\n nodeCount: u32,\n linkCount: u32,\n centerX: f32,\n centerY: f32,\n centerStrength: f32,\n theta2: f32,\n distanceMin2: f32,\n distanceMax2: f32,\n iterations: u32,\n collisionRadius: f32,\n collisionStrength: f32,\n collisionIterations: u32,\n forceXTarget: f32,\n forceXStrength: f32,\n forceYTarget: f32,\n forceYStrength: f32,\n radialX: f32,\n radialY: f32,\n radialRadius: f32,\n radialStrength: f32,\n _pad1: f32,\n _pad2: f32,\n}\n\n@group(0) @binding(0) var<storage, read_write> nodes: array<Node>;\n@group(0) @binding(1) var<uniform> params: Params;\n\n@compute @workgroup_size(256)\nfn main(@builtin(global_invocation_id) global_id: vec3<u32>) {\n let i = global_id.x;\n if (i >= params.nodeCount) {\n return;\n }\n\n let node = nodes[i];\n let alpha = params.alpha;\n let cx = params.radialX;\n let cy = params.radialY;\n let targetRadius = params.radialRadius;\n let strength = params.radialStrength;\n\n let dx = node.x - cx;\n let dy = node.y - cy;\n let r = sqrt(dx * dx + dy * dy);\n\n if (r > 0.0) {\n let k = (targetRadius - r) * strength * alpha / r;\n nodes[i].vx = node.vx + dx * k;\n nodes[i].vy = node.vy + dy * k;\n }\n}\n";var Y=Math.PI*(3-Math.sqrt(5));n.checkWebGPUSupport=async function(){if(!m())return!1;try{return!!await navigator.gpu.requestAdapter()}catch(n){return!1}},n.forceCenter=function(n,e){var t,r=1;function i(){var i,a,o=t.length,f=0,s=0;for(i=0;i<o;++i)f+=(a=t[i]).x,s+=a.y;for(f=(f/o-n)*r,s=(s/o-e)*r,i=0;i<o;++i)(a=t[i]).x-=f,a.y-=s}return null==n&&(n=0),null==e&&(e=0),i.initialize=function(n){t=n},i.x=function(e){return arguments.length?(n=+e,i):n},i.y=function(n){return arguments.length?(e=+n,i):e},i.strength=function(n){return arguments.length?(r=+n,i):r},i},n.forceCollide=function(n){var t,r,s,u=1,l=1;function d(){for(var n,i,d,h,g,y,p,x=t.length,v=0;v<l;++v)for(i=e.quadtree(t,o,f).visitAfter(c),n=0;n<x;++n)d=t[n],y=r[d.index],p=y*y,h=d.x+d.vx,g=d.y+d.vy,i.visit(m);function m(n,e,t,r,i){var o=n.data,f=n.r,l=y+f;if(!o)return e>h+l||r<h-l||t>g+l||i<g-l;if(o.index>d.index){var c=h-o.x-o.vx,x=g-o.y-o.vy,v=c*c+x*x;v<l*l&&(0===c&&(v+=(c=a(s))*c),0===x&&(v+=(x=a(s))*x),v=(l-(v=Math.sqrt(v)))/v*u,d.vx+=(c*=v)*(l=(f*=f)/(p+f)),d.vy+=(x*=v)*l,o.vx-=c*(l=1-l),o.vy-=x*l)}}}function c(n){if(n.data)return n.r=r[n.data.index];for(var e=n.r=0;e<4;++e)n[e]&&n[e].r>n.r&&(n.r=n[e].r)}function h(){if(t){var e,i,a=t.length;for(r=new Array(a),e=0;e<a;++e)i=t[e],r[i.index]=+n(i,e,t)}}return"function"!=typeof n&&(n=i(null==n?1:+n)),d.initialize=function(n,e){t=n,s=e,h()},d.iterations=function(n){return arguments.length?(l=+n,d):l},d.strength=function(n){return arguments.length?(u=+n,d):u},d.radius=function(e){return arguments.length?(n="function"==typeof e?e:i(+e),h(),d):n},d},n.forceLink=function(n){var e,t,r,o,f,l,d=s,c=function(n){return 1/Math.min(o[n.source.index],o[n.target.index])},h=i(30),g=1;function y(r){for(var i=0,o=n.length;i<g;++i)for(var s,u,d,c,h,y,p,x=0;x<o;++x)u=(s=n[x]).source,c=(d=s.target).x+d.vx-u.x-u.vx||a(l),h=d.y+d.vy-u.y-u.vy||a(l),c*=y=((y=Math.sqrt(c*c+h*h))-t[x])/y*r*e[x],h*=y,d.vx-=c*(p=f[x]),d.vy-=h*p,u.vx+=c*(p=1-p),u.vy+=h*p}function p(){if(r){var i,a,s=r.length,l=n.length,c=new Map(r.map(((n,e)=>[d(n,e,r),n])));for(i=0,o=new Array(s);i<l;++i)(a=n[i]).index=i,"object"!=typeof a.source&&(a.source=u(c,a.source)),"object"!=typeof a.target&&(a.target=u(c,a.target)),o[a.source.index]=(o[a.source.index]||0)+1,o[a.target.index]=(o[a.target.index]||0)+1;for(i=0,f=new Array(l);i<l;++i)a=n[i],f[i]=o[a.source.index]/(o[a.source.index]+o[a.target.index]);e=new Array(l),x(),t=new Array(l),v()}}function x(){if(r)for(var t=0,i=n.length;t<i;++t)e[t]=+c(n[t],t,n)}function v(){if(r)for(var e=0,i=n.length;e<i;++e)t[e]=+h(n[e],e,n)}return null==n&&(n=[]),y.initialize=function(n,e){r=n,l=e,p()},y.links=function(e){return arguments.length?(n=e,p(),y):n},y.id=function(n){return arguments.length?(d=n,y):d},y.iterations=function(n){return arguments.length?(g=+n,y):g},y.strength=function(n){return arguments.length?(c="function"==typeof n?n:i(+n),x(),y):c},y.distance=function(n){return arguments.length?(h="function"==typeof n?n:i(+n),v(),y):h},y},n.forceManyBody=function(){var n,t,r,o,f,s=i(-30),u=1,l=1/0,d=.81;function g(r){var i,a=n.length,f=e.quadtree(n,c,h).visitAfter(p);for(o=r,i=0;i<a;++i)t=n[i],f.visit(x)}function y(){if(n){var e,t,r=n.length;for(f=new Array(r),e=0;e<r;++e)t=n[e],f[t.index]=+s(t,e,n)}}function p(n){var e,t,r,i,a,o=0,s=0;if(n.length){for(r=i=a=0;a<4;++a)(e=n[a])&&(t=Math.abs(e.value))&&(o+=e.value,s+=t,r+=t*e.x,i+=t*e.y);n.x=r/s,n.y=i/s}else{(e=n).x=e.data.x,e.y=e.data.y;do{o+=f[e.data.index]}while(e=e.next)}n.value=o}function x(n,e,i,s){if(!n.value)return!0;var c=n.x-t.x,h=n.y-t.y,g=s-e,y=c*c+h*h;if(g*g/d<y)return y<l&&(0===c&&(y+=(c=a(r))*c),0===h&&(y+=(h=a(r))*h),y<u&&(y=Math.sqrt(u*y)),t.vx+=c*n.value*o/y,t.vy+=h*n.value*o/y),!0;if(!(n.length||y>=l)){(n.data!==t||n.next)&&(0===c&&(y+=(c=a(r))*c),0===h&&(y+=(h=a(r))*h),y<u&&(y=Math.sqrt(u*y)));do{n.data!==t&&(g=f[n.data.index]*o/y,t.vx+=c*g,t.vy+=h*g)}while(n=n.next)}}return g.initialize=function(e,t){n=e,r=t,y()},g.strength=function(n){return arguments.length?(s="function"==typeof n?n:i(+n),y(),g):s},g.distanceMin=function(n){return arguments.length?(u=n*n,g):Math.sqrt(u)},g.distanceMax=function(n){return arguments.length?(l=n*n,g):Math.sqrt(l)},g.theta=function(n){return arguments.length?(d=n*n,g):Math.sqrt(d)},g},n.forceRadial=function(n,e,t){var r,a,o,f=i(.1);function s(n){for(var i=0,f=r.length;i<f;++i){var s=r[i],u=s.x-e||1e-6,l=s.y-t||1e-6,d=Math.sqrt(u*u+l*l),c=(o[i]-d)*a[i]*n/d;s.vx+=u*c,s.vy+=l*c}}function u(){if(r){var e,t=r.length;for(a=new Array(t),o=new Array(t),e=0;e<t;++e)o[e]=+n(r[e],e,r),a[e]=isNaN(o[e])?0:+f(r[e],e,r)}}return"function"!=typeof n&&(n=i(+n)),null==e&&(e=0),null==t&&(t=0),s.initialize=function(n){r=n,u()},s.strength=function(n){return arguments.length?(f="function"==typeof n?n:i(+n),u(),s):f},s.radius=function(e){return arguments.length?(n="function"==typeof e?e:i(+e),u(),s):n},s.x=function(n){return arguments.length?(e=+n,s):e},s.y=function(n){return arguments.length?(t=+n,s):t},s},n.forceSimulation=function(n){var e,i=1,a=.001,o=1-Math.pow(a,1/300),f=0,s=.6,u=new Map,l=r.timer(y),c=t.dispatch("tick","end"),h=d();function y(){p(),c.call("tick",e),i<a&&(l.stop(),c.call("end",e))}function p(t){var r,a,l=n.length;void 0===t&&(t=1);for(var d=0;d<t;++d)for(i+=(f-i)*o,u.forEach((function(n){n(i)})),r=0;r<l;++r)null==(a=n[r]).fx?a.x+=a.vx*=s:(a.x=a.fx,a.vx=0),null==a.fy?a.y+=a.vy*=s:(a.y=a.fy,a.vy=0);return e}function x(){for(var e,t=0,r=n.length;t<r;++t){if((e=n[t]).index=t,null!=e.fx&&(e.x=e.fx),null!=e.fy&&(e.y=e.fy),isNaN(e.x)||isNaN(e.y)){var i=10*Math.sqrt(.5+t),a=t*g;e.x=i*Math.cos(a),e.y=i*Math.sin(a)}(isNaN(e.vx)||isNaN(e.vy))&&(e.vx=e.vy=0)}}function v(e){return e.initialize&&e.initialize(n,h),e}return null==n&&(n=[]),x(),e={tick:p,restart:function(){return l.restart(y),e},stop:function(){return l.stop(),e},nodes:function(t){return arguments.length?(n=t,x(),u.forEach(v),e):n},alpha:function(n){return arguments.length?(i=+n,e):i},alphaMin:function(n){return arguments.length?(a=+n,e):a},alphaDecay:function(n){return arguments.length?(o=+n,e):+o},alphaTarget:function(n){return arguments.length?(f=+n,e):f},velocityDecay:function(n){return arguments.length?(s=1-n,e):1-s},randomSource:function(n){return arguments.length?(h=n,u.forEach(v),e):h},force:function(n,t){return arguments.length>1?(null==t?u.delete(n):u.set(n,v(t)),e):u.get(n)},find:function(e,t,r){var i,a,o,f,s,u=0,l=n.length;for(null==r?r=1/0:r*=r,u=0;u<l;++u)(o=(i=e-(f=n[u]).x)*i+(a=t-f.y)*a)<r&&(s=f,r=o);return s},on:function(n,t){return arguments.length>1?(c.on(n,t),e):c.on(n)}}},n.forceSimulationGPU=function(n){var e,i,a=1,o=.001,f=1-Math.pow(o,1/300),s=0,u=.6,l=new Map,c=r.timer(J),h=t.dispatch("tick","end"),g=d(),y=!1,p=null,x=null,m=null,X={},E={},G=null,D=!1,T=null,R={links:[],distances:[],strengths:[],bias:[]},V={enabled:!1,strength:-30,theta:.9,distanceMin:1,distanceMax:1/0},A={enabled:!1,x:0,y:0,strength:1},L={enabled:!1,radius:5,strength:1,iterations:1},z={enabled:!1,x:0,strength:.1},q={enabled:!1,y:0,strength:.1},U={enabled:!1,x:0,y:0,radius:100,strength:.1};async function W(){if(y)return!0;try{return p=await v(),await async function(){X.manyBody=await b(p,S,"main"),X.linkForce=await b(p,_,"main"),X.integrate=await b(p,P,"main"),X.collision=await b(p,M,"main"),X.forceX=await b(p,C,"main"),X.forceY=await b(p,I,"main"),X.radial=await b(p,N,"main")}(),y=!0,!0}catch(n){return console.warn("WebGPU initialization failed, falling back to CPU:",n),!1}}function j(){p&&0!==n.length&&(x&&x.destroy(),m&&m.destroy(),T&&T.destroy(),x=new w(p,n.length),m=new k(p),R.links.length>0&&(T=new B(p,R.links.length)),F())}function F(){p&&x&&(E.manyBody=p.createBindGroup({layout:X.manyBody.getBindGroupLayout(0),entries:[{binding:0,resource:{buffer:x.storageBuffer}},{binding:1,resource:{buffer:m.storageBuffer}}]}),E.integrate=p.createBindGroup({layout:X.integrate.getBindGroupLayout(0),entries:[{binding:0,resource:{buffer:x.storageBuffer}},{binding:1,resource:{buffer:m.storageBuffer}}]}),E.collision=p.createBindGroup({layout:X.collision.getBindGroupLayout(0),entries:[{binding:0,resource:{buffer:x.storageBuffer}},{binding:1,resource:{buffer:m.storageBuffer}}]}),T&&(E.link=p.createBindGroup({layout:X.linkForce.getBindGroupLayout(0),entries:[{binding:0,resource:{buffer:x.storageBuffer}},{binding:1,resource:{buffer:T.storageBuffer}},{binding:2,resource:{buffer:m.storageBuffer}}]})),E.forceX=p.createBindGroup({layout:X.forceX.getBindGroupLayout(0),entries:[{binding:0,resource:{buffer:x.storageBuffer}},{binding:1,resource:{buffer:m.storageBuffer}}]}),E.forceY=p.createBindGroup({layout:X.forceY.getBindGroupLayout(0),entries:[{binding:0,resource:{buffer:x.storageBuffer}},{binding:1,resource:{buffer:m.storageBuffer}}]}),E.radial=p.createBindGroup({layout:X.radial.getBindGroupLayout(0),entries:[{binding:0,resource:{buffer:x.storageBuffer}},{binding:1,resource:{buffer:m.storageBuffer}}]}))}async function Z(){a+=(s-a)*f,function(){if(x){for(let e=0;e<n.length;e++)n[e]._strength=V.strength,n[e].radius=n[e].radius||L.radius;x.uploadNodes(n),m.update({alpha:a,velocityDecay:u,nodeCount:n.length,linkCount:R.links.length,centerX:A.x,centerY:A.y,centerStrength:A.strength,theta2:V.theta*V.theta,distanceMin2:V.distanceMin*V.distanceMin,distanceMax2:V.distanceMax*V.distanceMax,iterations:1,collisionRadius:L.radius,collisionStrength:L.strength,collisionIterations:L.iterations,forceXTarget:z.x,forceXStrength:z.enabled?z.strength:0,forceYTarget:q.y,forceYStrength:q.enabled?q.strength:0,radialX:U.x,radialY:U.y,radialRadius:U.radius,radialStrength:U.enabled?U.strength:0}),T&&R.links.length>0&&T.uploadLinks(R.links,R.distances,R.strengths,R.bias)}}();const e=Math.ceil(n.length/256),t=p.createCommandEncoder(),r=t.beginComputePass();if(V.enabled&&E.manyBody&&(r.setPipeline(X.manyBody),r.setBindGroup(0,E.manyBody),r.dispatchWorkgroups(e)),R.links.length>0&&E.link&&(r.setPipeline(X.linkForce),r.setBindGroup(0,E.link),r.dispatchWorkgroups(e)),L.enabled&&E.collision)for(let n=0;n<L.iterations;n++)r.setPipeline(X.collision),r.setBindGroup(0,E.collision),r.dispatchWorkgroups(e);z.enabled&&E.forceX&&(r.setPipeline(X.forceX),r.setBindGroup(0,E.forceX),r.dispatchWorkgroups(e)),q.enabled&&E.forceY&&(r.setPipeline(X.forceY),r.setBindGroup(0,E.forceY),r.dispatchWorkgroups(e)),U.enabled&&E.radial&&(r.setPipeline(X.radial),r.setBindGroup(0,E.radial),r.dispatchWorkgroups(e)),r.setPipeline(X.integrate),r.setBindGroup(0,E.integrate),r.dispatchWorkgroups(e),r.end(),p.queue.submit([t.finish()]),await x.downloadNodes(n),A.enabled&&function(){let e=0,t=0;for(let r=0;r<n.length;r++)e+=n[r].x,t+=n[r].y;e=(e/n.length-A.x)*A.strength,t=(t/n.length-A.y)*A.strength;for(let r=0;r<n.length;r++)n[r].x-=e,n[r].y-=t}()}async function O(){if(!D){D=!0;try{for(;a>=o&&D&&y;)await Z(),h.call("tick",e);a<o&&(D=!1,h.call("end",e))}catch(n){console.error("GPU compute loop failed, falling back to CPU:",n),y=!1,D=!1}}}function H(t){var r,i,o=n.length;void 0===t&&(t=1);for(var d=0;d<t;++d)for(a+=(s-a)*f,l.forEach((function(n){n(a)})),r=0;r<o;++r)null==(i=n[r]).fx?i.x+=i.vx*=u:(i.x=i.fx,i.vx=0),null==i.fy?i.y+=i.vy*=u:(i.y=i.fy,i.vy=0);return e}function J(){K(),y||(h.call("tick",e),a<o&&(c.stop(),h.call("end",e)))}function K(n){return y?async function(n){y&&x&&p?D||O():H(n)}(n):H(n),e}function Q(){for(var e,t=0,r=n.length;t<r;++t){if((e=n[t]).index=t,null!=e.fx&&(e.x=e.fx),null!=e.fy&&(e.y=e.fy),isNaN(e.x)||isNaN(e.y)){var i=10*Math.sqrt(.5+t),a=t*Y;e.x=i*Math.cos(a),e.y=i*Math.sin(a)}(isNaN(e.vx)||isNaN(e.vy))&&(e.vx=e.vy=0)}}function $(e){return e.initialize&&e.initialize(n,g),e}return null==n&&(n=[]),i=new Promise((n=>{G=n})),Q(),W().then((async()=>{y&&(j(),await async function(){if(!p||!x||0===n.length)return;const e=Math.ceil(n.length/256),t=p.createCommandEncoder(),r=t.beginComputePass();E.manyBody&&(r.setPipeline(X.manyBody),r.setBindGroup(0,E.manyBody),r.dispatchWorkgroups(e)),E.collision&&(r.setPipeline(X.collision),r.setBindGroup(0,E.collision),r.dispatchWorkgroups(e)),E.forceX&&(r.setPipeline(X.forceX),r.setBindGroup(0,E.forceX),r.dispatchWorkgroups(e)),E.forceY&&(r.setPipeline(X.forceY),r.setBindGroup(0,E.forceY),r.dispatchWorkgroups(e)),E.radial&&(r.setPipeline(X.radial),r.setBindGroup(0,E.radial),r.dispatchWorkgroups(e)),r.setPipeline(X.integrate),r.setBindGroup(0,E.integrate),r.dispatchWorkgroups(e),r.end(),p.queue.submit([t.finish()]),await p.queue.onSubmittedWorkDone()}()),G(y)})),e={tick:K,restart:function(){return c.restart(J),y&&!D&&O(),e},stop:function(){return D=!1,c.stop(),e},nodes:function(t){return arguments.length?(n=t,Q(),l.forEach($),y&&j(),e):n},alpha:function(n){return arguments.length?(a=+n,e):a},alphaMin:function(n){return arguments.length?(o=+n,e):o},alphaDecay:function(n){return arguments.length?(f=+n,e):+f},alphaTarget:function(n){return arguments.length?(s=+n,e):s},velocityDecay:function(n){return arguments.length?(u=1-n,e):1-u},randomSource:function(n){return arguments.length?(g=n,l.forEach($),e):g},force:function(n,t){return arguments.length>1?(null==t?(l.delete(n),"charge"===n||"manyBody"===n?V.enabled=!1:"center"===n?A.enabled=!1:"collide"===n?L.enabled=!1:"link"===n?(R={links:[],distances:[],strengths:[],bias:[]},T&&(T.destroy(),T=null)):"x"===n?z.enabled=!1:"y"===n?q.enabled=!1:"radial"===n&&(U.enabled=!1)):(l.set(n,$(t)),nn(n,t)),e):l.get(n)},find:function(e,t,r){var i,a,o,f,s,u=0,l=n.length;for(null==r?r=1/0:r*=r,u=0;u<l;++u)(o=(i=e-(f=n[u]).x)*i+(a=t-f.y)*a)<r&&(s=f,r=o);return s},on:function(n,t){return arguments.length>1?(h.on(n,t),e):h.on(n)},isGPUEnabled:function(){return y},gpuReady:function(){return i},async initGPU(){const n=await W();return n&&j(),n}};function nn(e,t){if("charge"===e||"manyBody"===e){if(V.enabled=!0,t.strength){const n=t.strength();V.strength="function"==typeof n?-30:n}t.theta&&(V.theta=t.theta()),t.distanceMin&&(V.distanceMin=t.distanceMin()),t.distanceMax&&(V.distanceMax=t.distanceMax())}else if("center"===e)A.enabled=!0,t.x&&(A.x=t.x()),t.y&&(A.y=t.y()),t.strength&&(A.strength=t.strength());else if("collide"===e){if(L.enabled=!0,t.radius){const n=t.radius();L.radius="function"==typeof n?5:n}t.strength&&(L.strength=t.strength()),t.iterations&&(L.iterations=t.iterations())}else if("link"===e){if(t.links){const e=t.links();R.links=e,R.distances=[],R.strengths=[],R.bias=[];const r=t.distance?t.distance():()=>30,i=t.strength?t.strength():null,a=new Array(n.length).fill(0);for(const n of e)a[n.source.index]=(a[n.source.index]||0)+1,a[n.target.index]=(a[n.target.index]||0)+1;for(let n=0;n<e.length;n++){const t=e[n];R.distances.push("function"==typeof r?r(t,n,e):r),i?R.strengths.push("function"==typeof i?i(t,n,e):i):R.strengths.push(1/Math.min(a[t.source.index],a[t.target.index])),R.bias.push(a[t.source.index]/(a[t.source.index]+a[t.target.index]))}y&&e.length>0&&(T&&T.destroy(),T=new B(p,e.length),F())}}else if("x"===e){if(z.enabled=!0,t.x){const n=t.x();z.x="function"==typeof n?0:n}if(t.strength){const n=t.strength();z.strength="function"==typeof n?.1:n}}else if("y"===e){if(q.enabled=!0,t.y){const n=t.y();q.y="function"==typeof n?0:n}if(t.strength){const n=t.strength();q.strength="function"==typeof n?.1:n}}else if("radial"===e){if(U.enabled=!0,t.x&&(U.x=t.x()),t.y&&(U.y=t.y()),t.radius){const n=t.radius();U.radius="function"==typeof n?100:n}if(t.strength){const n=t.strength();U.strength="function"==typeof n?.1:n}}}},n.forceX=function(n){var e,t,r,a=i(.1);function o(n){for(var i,a=0,o=e.length;a<o;++a)(i=e[a]).vx+=(r[a]-i.x)*t[a]*n}function f(){if(e){var i,o=e.length;for(t=new Array(o),r=new Array(o),i=0;i<o;++i)t[i]=isNaN(r[i]=+n(e[i],i,e))?0:+a(e[i],i,e)}}return"function"!=typeof n&&(n=i(null==n?0:+n)),o.initialize=function(n){e=n,f()},o.strength=function(n){return arguments.length?(a="function"==typeof n?n:i(+n),f(),o):a},o.x=function(e){return arguments.length?(n="function"==typeof e?e:i(+e),f(),o):n},o},n.forceY=function(n){var e,t,r,a=i(.1);function o(n){for(var i,a=0,o=e.length;a<o;++a)(i=e[a]).vy+=(r[a]-i.y)*t[a]*n}function f(){if(e){var i,o=e.length;for(t=new Array(o),r=new Array(o),i=0;i<o;++i)t[i]=isNaN(r[i]=+n(e[i],i,e))?0:+a(e[i],i,e)}}return"function"!=typeof n&&(n=i(null==n?0:+n)),o.initialize=function(n){e=n,f()},o.strength=function(n){return arguments.length?(a="function"==typeof n?n:i(+n),f(),o):a},o.y=function(e){return arguments.length?(n="function"==typeof e?e:i(+e),f(),o):n},o},n.isWebGPUAvailable=m,Object.defineProperty(n,"__esModule",{value:!0})}));