@graphty/graphty-element 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/ai.js +4 -4
- package/dist/catalog.js +5 -5
- package/dist/chunks/{AiManager-D22fxCNb.js → AiManager-DWwQiBQu.js} +5 -5
- package/dist/chunks/{Algorithm-BhwPK-aS.js → Algorithm-RQ629NLb.js} +210 -92
- package/dist/chunks/{DataSource-7S-7S3M1.js → DataSource-DEg3igzS.js} +3 -3
- package/dist/chunks/{GraphSession-DwlThkoy.js → GraphSession-CtqyVNsw.js} +2518 -2239
- package/dist/chunks/{GraphtyError-B3eKs4yg.js → GraphtyError-BwcnblTH.js} +10 -8
- package/dist/chunks/GraphtyLogger-5KEttFUo.js +752 -0
- package/dist/chunks/{NodeStyle-CS7dj20m.js → NodeStyle-Cup9O1lu.js} +2 -1
- package/dist/chunks/{VoiceInputAdapter-BrCQEMf0.js → VoiceInputAdapter-mD0x3z7t.js} +1 -1
- package/dist/chunks/{XRPivotCameraController-BE1pfwMC.js → XRPivotCameraController-Clil9NhV.js} +2 -2
- package/dist/chunks/{cameras-D5LX3ttm.js → cameras-DFXNYBze.js} +2 -2
- package/dist/chunks/{capability-check-MH9salUj.js → capability-check-dXwQ_z5B.js} +1 -1
- package/dist/chunks/{detect-5cVtGFHg.js → detect-CqN0mC6u.js} +3 -3
- package/dist/chunks/{format-detection-Bos-Q387.js → format-detection-C8vbCSlS.js} +1 -1
- package/dist/chunks/{index-CqZp3iwq.js → index-2VIpMJZP.js} +1576 -1211
- package/dist/chunks/{paletteRegistry-BSjlD98O.js → paletteRegistry-NrWOKT-A.js} +17 -17
- package/dist/chunks/{registry-DU-e49Y2.js → registry-CSba5QGJ.js} +1 -1
- package/dist/chunks/{scales-DPUY-fFk.js → scales-BUS7NWy2.js} +2206 -1357
- package/dist/custom-elements.json +1 -1
- package/dist/extend.js +26 -27
- package/dist/graphty-catalog.json +7 -4
- package/dist/graphty.bundle.js +36288 -34368
- package/dist/graphty.js +66 -64
- package/dist/index.d.ts +1 -1
- package/dist/logging.js +2 -2
- package/dist/schema.js +1 -1
- package/dist/session.d.ts +2 -1
- package/dist/session.js +37 -33
- package/dist/src/Edge.d.ts +1 -1
- package/dist/src/Graph.d.ts +41 -7
- package/dist/src/Node.d.ts +1 -1
- package/dist/src/acceleration/AccelerationController.d.ts +27 -0
- package/dist/src/acceleration/index.d.ts +1 -1
- package/dist/src/acceleration/narrow.d.ts +56 -0
- package/dist/src/acceleration/types.d.ts +88 -7
- package/dist/src/algorithms/Algorithm.d.ts +96 -2
- package/dist/src/algorithms/BFSAlgorithm.d.ts +9 -0
- package/dist/src/algorithms/DijkstraAlgorithm.d.ts +2 -8
- package/dist/src/algorithms/PageRankAlgorithm.d.ts +8 -0
- package/dist/src/algorithms/utils/graphUtils.d.ts +13 -0
- package/dist/src/cameras/OrbitCameraController.d.ts +1 -0
- package/dist/src/catalog/layouts.d.ts +13 -1
- package/dist/src/config/GraphBehavior.d.ts +13 -0
- package/dist/src/config/GraphStyle.d.ts +1 -1
- package/dist/src/config/StyleTemplate.d.ts +4 -0
- package/dist/src/data/CSVDataSource.d.ts +1 -0
- package/dist/src/data/csv-variant-detection.d.ts +11 -0
- package/dist/src/errors/codes.d.ts +14 -1
- package/dist/src/events.d.ts +17 -4
- package/dist/src/graphty-element.d.ts +48 -3
- package/dist/src/layout/ForceAtlas2LayoutEngine.d.ts +28 -45
- package/dist/src/layout/LayoutEngine.d.ts +7 -7
- package/dist/src/layout/SimulationLayoutEngine.d.ts +458 -0
- package/dist/src/layout/SpringElectricalLayoutEngine.d.ts +32 -0
- package/dist/src/layout/SpringLayoutEngine.d.ts +22 -32
- package/dist/src/managers/EventManager.d.ts +7 -0
- package/dist/src/managers/GraphContext.d.ts +6 -0
- package/dist/src/managers/LayoutManager.d.ts +98 -4
- package/dist/src/managers/RenderManager.d.ts +7 -0
- package/dist/src/managers/UpdateManager.d.ts +1 -1
- package/dist/src/session/catalog.d.ts +2 -2
- package/dist/src/session/query.d.ts +83 -0
- package/dist/src/session/runs/types.d.ts +10 -4
- package/dist/src/session/selection/targets.d.ts +20 -3
- package/dist/src/session/styles/StylesApi.d.ts +18 -0
- package/dist/src/session/styles/legend.d.ts +9 -0
- package/dist/src/session/styles/predicate.d.ts +11 -0
- package/dist/src/session/styles/sources.d.ts +16 -0
- package/dist/src/session/types.d.ts +38 -2
- package/dist/src/testing/fakeAccelerator.d.ts +323 -0
- package/dist/webgpu.d.ts +23 -1
- package/dist/webgpu.js +70 -32
- package/package.json +15 -11
- package/dist/chunks/GraphtyLogger-DoYeIghs.js +0 -609
- package/dist/chunks/types-Dwm9waL2.js +0 -7
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A deterministic stand-in for a real accelerator, for tests and for stories.
|
|
3
|
+
*
|
|
4
|
+
* The element decides "GPU or CPU" once per piece of work and then drives whatever it attached
|
|
5
|
+
* through the same two seams: `LayoutSimulation` for a layout and the accelerator's algorithm
|
|
6
|
+
* members for a run. Exercising either seam against a real GPU makes a test depend on a device,
|
|
7
|
+
* on a driver and on how many frames the browser happened to render. This fake stands in for
|
|
8
|
+
* the device: it implements the seams, it moves nodes by an exact amount per landed batch, and
|
|
9
|
+
* it counts what it was asked to do.
|
|
10
|
+
*
|
|
11
|
+
* What a landed batch COMPUTES is a choice, because a unit test and a story want opposite things
|
|
12
|
+
* of it. A test wants an answer it can assert on to the digit, so by default a batch moves every
|
|
13
|
+
* unfixed node `moveBy * iterations` along x and settling is counted in landed batches rather
|
|
14
|
+
* than in elapsed time: a node's coordinate after a settle is an exact multiple of `moveBy`
|
|
15
|
+
* whatever the frame rate.
|
|
16
|
+
*
|
|
17
|
+
* A STORY WANTS A PICTURE, AND THAT UNIFORM TRANSLATION HAS NONE. Every unfixed node moves the
|
|
18
|
+
* same distance in the same direction, and the element frames the camera on whatever it is
|
|
19
|
+
* given, so the arrangement on screen is the seed scatter no matter how many batches land --
|
|
20
|
+
* which makes a story named after a layout draw a hairball and a baseline of it mean nothing.
|
|
21
|
+
* `computes: "layout"` is for that case: the batch runs the CPU simulation of the layout that was
|
|
22
|
+
* asked for, so the picture is that layout's own arrangement, reached through the accelerator
|
|
23
|
+
* seam and deterministic under the layout's seed.
|
|
24
|
+
*
|
|
25
|
+
* Everything a test observes lives on the ACCELERATOR, aggregated over every simulation it
|
|
26
|
+
* created ({@link FakeAccelerator.calls}), so a test never has to reach for the simulation to
|
|
27
|
+
* find out what happened; `simulations` is there for the rare case that needs identity.
|
|
28
|
+
*
|
|
29
|
+
* This module is reachable from no entry point: it imports the seam, the CPU simulations it can
|
|
30
|
+
* stand a device in front of, and the error model, and it ships in the package the way the tests
|
|
31
|
+
* do.
|
|
32
|
+
* @module testing/fakeAccelerator
|
|
33
|
+
*/
|
|
34
|
+
import type { IndexedPageRankOptions, LabelResultLike, PageRankResultLike } from "@graphty/algorithms";
|
|
35
|
+
import { type F32, type GraphSnapshot, type NodeMask } from "@graphty/graph-format";
|
|
36
|
+
import { type ForceAtlas2Options, type FruchtermanReingoldOptions, type LayoutSimulation, type SimulationOptions, type SimulationType, type SpringElectricalOptions } from "@graphty/layout";
|
|
37
|
+
import type { AccelerationPrecision, GraphAccelerator } from "../acceleration/types";
|
|
38
|
+
import { type GraphtyErrorCode } from "../errors";
|
|
39
|
+
/**
|
|
40
|
+
* What the fake has been asked to do, counted across every simulation one accelerator created.
|
|
41
|
+
*
|
|
42
|
+
* Each algorithm and layout member counts CALLS of that name. The three simulation counters are
|
|
43
|
+
* about batches rather than calls, because the seam coalesces: `step` counts SUBMISSIONS, so a
|
|
44
|
+
* call that landed on a pending promise instead of submitting its own batch does not appear
|
|
45
|
+
* here; `resolved` counts the batches that have landed; `reheat` counts reheats. `release` lists
|
|
46
|
+
* the snapshots in call order rather than counting them, because which snapshot was released is
|
|
47
|
+
* the thing a release test is about.
|
|
48
|
+
*
|
|
49
|
+
* Not exported: it is reachable as `FakeAccelerator["calls"]`, and a name nothing imports is a
|
|
50
|
+
* name knip reports. Export it the day a caller needs to spell it.
|
|
51
|
+
*/
|
|
52
|
+
interface FakeAcceleratorCalls {
|
|
53
|
+
/** How many `forceAtlas2()` simulations were built. */
|
|
54
|
+
forceAtlas2: number;
|
|
55
|
+
/** How many `fruchtermanReingold()` simulations were built. */
|
|
56
|
+
fruchtermanReingold: number;
|
|
57
|
+
/** How many `springElectrical()` simulations were built. */
|
|
58
|
+
springElectrical: number;
|
|
59
|
+
/** How many PageRank runs were asked for. */
|
|
60
|
+
pageRank: number;
|
|
61
|
+
/** How many connected-component runs were asked for. */
|
|
62
|
+
connectedComponents: number;
|
|
63
|
+
/** How many weakly-connected-component runs were asked for. */
|
|
64
|
+
weaklyConnectedComponents: number;
|
|
65
|
+
/** How many times the ACCELERATOR was disposed; a simulation's own dispose is not counted. */
|
|
66
|
+
dispose: number;
|
|
67
|
+
/** The snapshots handed to `release()`, in call order. */
|
|
68
|
+
release: GraphSnapshot[];
|
|
69
|
+
/** How many times a simulation was loaded. */
|
|
70
|
+
load: number;
|
|
71
|
+
/** How many batches were SUBMITTED; a coalesced `step()` does not count. */
|
|
72
|
+
step: number;
|
|
73
|
+
/** How many batches have landed. */
|
|
74
|
+
resolved: number;
|
|
75
|
+
/** How many times a simulation was reheated. */
|
|
76
|
+
reheat: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* How to build a fake accelerator.
|
|
80
|
+
*
|
|
81
|
+
* Not exported, for the reason above: `Parameters<typeof createFakeAccelerator>[0]` spells it.
|
|
82
|
+
*/
|
|
83
|
+
interface FakeAcceleratorOptions {
|
|
84
|
+
/** The name it reports in diagnostics. Default `"fake"`. */
|
|
85
|
+
readonly name?: string;
|
|
86
|
+
/** The arithmetic it claims. Default `"f32"`, the precision a GPU reports. */
|
|
87
|
+
readonly precision?: AccelerationPrecision;
|
|
88
|
+
/** Landed batches after which a simulation reports `settled`. Default 10. */
|
|
89
|
+
readonly settleAfter?: number;
|
|
90
|
+
/** Scene units a landed batch moves one unfixed node along x, per iteration. Default 1. */
|
|
91
|
+
readonly moveBy?: number;
|
|
92
|
+
/** Batches a simulation accepts before coalescing onto the oldest. Default 2. */
|
|
93
|
+
readonly maxInFlight?: number;
|
|
94
|
+
/**
|
|
95
|
+
* What a landed batch computes: the exact translation, or the layout that was asked for.
|
|
96
|
+
*
|
|
97
|
+
* `"translation"` is the default and is what every assertion in this package is written
|
|
98
|
+
* against. `"layout"` runs the CPU simulation of the layout the caller built -- ForceAtlas2
|
|
99
|
+
* for `forceAtlas2()`, Fruchterman-Reingold for `fruchtermanReingold()` -- so the arrangement
|
|
100
|
+
* is that layout's own and a story drawing it draws the thing its name promises. `moveBy` and
|
|
101
|
+
* `settleAfter` do not apply to it: the simulation settles on its own terms.
|
|
102
|
+
*/
|
|
103
|
+
readonly computes?: "translation" | "layout";
|
|
104
|
+
/** A device-loss promise, for the recovery paths. Absent means this backend cannot lose one. */
|
|
105
|
+
readonly lost?: Promise<{
|
|
106
|
+
reason: string;
|
|
107
|
+
}>;
|
|
108
|
+
/**
|
|
109
|
+
* What the accelerator's self-check answers when the element asks it, at attach, to prove it
|
|
110
|
+
* computes correctly.
|
|
111
|
+
*
|
|
112
|
+
* Absent -- the default -- gives it no `verify` member at all, which is the shape of a
|
|
113
|
+
* backend that cannot check itself and is what every accelerator looks like today. `"ok"`
|
|
114
|
+
* gives it one that resolves. A code gives it one that rejects with that code, which is how a
|
|
115
|
+
* test stands in for a device that computes wrong answers without owning one:
|
|
116
|
+
* `verify: "E_DEVICE_INCORRECT"`.
|
|
117
|
+
*/
|
|
118
|
+
readonly verify?: "ok" | GraphtyErrorCode;
|
|
119
|
+
/**
|
|
120
|
+
* Members to add to the accelerator, or to replace on it.
|
|
121
|
+
*
|
|
122
|
+
* The element feature-tests a member before it uses it, so setting one to `undefined` is how
|
|
123
|
+
* a test builds an accelerator that does not implement a capability.
|
|
124
|
+
*/
|
|
125
|
+
readonly members?: Readonly<Record<string, unknown>>;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The simulation a {@link FakeAccelerator} hands out: the GPU shape, without a GPU.
|
|
129
|
+
*
|
|
130
|
+
* It is the GPU shape in the two ways the element's frame loop cares about. `step()` returns a
|
|
131
|
+
* promise, so the bridge's fire-and-forget path is what a test exercises rather than the
|
|
132
|
+
* synchronous CPU path. And a simulation that already has `maxInFlight` batches in flight
|
|
133
|
+
* returns the OLDEST pending promise instead of submitting another, which is how a real GPU
|
|
134
|
+
* simulation keeps a frame loop from queueing work faster than the device retires it.
|
|
135
|
+
*
|
|
136
|
+
* What it computes depends on `computes`. By default a landed batch adds `moveBy * iterations`
|
|
137
|
+
* to the x coordinate of every node that is neither fixed nor being dragged and leaves y and z
|
|
138
|
+
* alone, which is exact and assertable and, on screen, invisible. Under `computes: "layout"` a
|
|
139
|
+
* batch runs the CPU simulation of the layout this one was built for instead, and every call this
|
|
140
|
+
* class takes -- `load`, `setFixed`, `setPosition`, `reheat`, `settled` -- is that simulation's.
|
|
141
|
+
*/
|
|
142
|
+
export declare class FakeSimulation implements LayoutSimulation {
|
|
143
|
+
#private;
|
|
144
|
+
/** The options this simulation was created with. */
|
|
145
|
+
readonly options: SimulationOptions;
|
|
146
|
+
/**
|
|
147
|
+
* Builds a simulation. The accelerator does this; a test reaches it through `fake.forceAtlas2()`.
|
|
148
|
+
* @param calls - The accelerator's counter bag, shared by every simulation it creates.
|
|
149
|
+
* @param defaults - The accelerator's own movement and settling defaults.
|
|
150
|
+
* @param defaults.moveBy - Scene units a landed batch moves one unfixed node, per iteration.
|
|
151
|
+
* @param defaults.settleAfter - Landed batches after which the simulation reports `settled`.
|
|
152
|
+
* @param defaults.maxInFlight - Batches accepted before a step coalesces onto the oldest.
|
|
153
|
+
* @param defaults.computes - Whether a batch translates the nodes or runs the real layout.
|
|
154
|
+
* @param type - Which layout this simulation is, which is what `computes: "layout"` runs.
|
|
155
|
+
* @param options - The simulation options the caller passed to the layout member.
|
|
156
|
+
*/
|
|
157
|
+
constructor(calls: FakeAcceleratorCalls, defaults: {
|
|
158
|
+
moveBy: number;
|
|
159
|
+
settleAfter: number;
|
|
160
|
+
maxInFlight: number;
|
|
161
|
+
computes: "translation" | "layout";
|
|
162
|
+
}, type: SimulationType, options?: SimulationOptions);
|
|
163
|
+
/**
|
|
164
|
+
* The snapshot the last `load()` was given, or null before the first one.
|
|
165
|
+
* @returns The snapshot.
|
|
166
|
+
*/
|
|
167
|
+
get snapshot(): GraphSnapshot | null;
|
|
168
|
+
/**
|
|
169
|
+
* The position array the last `load()` was given, written in place by every landed batch.
|
|
170
|
+
* @returns The array.
|
|
171
|
+
*/
|
|
172
|
+
get positions(): F32 | null;
|
|
173
|
+
/**
|
|
174
|
+
* How many batches have landed since the last `reheat()`.
|
|
175
|
+
* @returns The count.
|
|
176
|
+
*/
|
|
177
|
+
get iterationsDone(): number;
|
|
178
|
+
/**
|
|
179
|
+
* True once `settleAfter` batches have landed; false again after a `reheat()`.
|
|
180
|
+
* @returns Whether it has settled.
|
|
181
|
+
*/
|
|
182
|
+
get settled(): boolean;
|
|
183
|
+
/**
|
|
184
|
+
* How many batches are in flight on this simulation.
|
|
185
|
+
* @returns The count.
|
|
186
|
+
*/
|
|
187
|
+
get pending(): number;
|
|
188
|
+
/**
|
|
189
|
+
* True once `dispose()` has run.
|
|
190
|
+
* @returns Whether it was disposed.
|
|
191
|
+
*/
|
|
192
|
+
get disposed(): boolean;
|
|
193
|
+
/**
|
|
194
|
+
* Adopts a snapshot and the owner's position array, which every later batch writes in place.
|
|
195
|
+
*
|
|
196
|
+
* Rows no layout has placed read as NaN, which is the element's "not placed yet"; they are
|
|
197
|
+
* seeded to the origin here, as a real simulation seeds them, so a landed batch moves a
|
|
198
|
+
* number rather than turning NaN into NaN. Only the snapshot's OWN rows are seeded: the
|
|
199
|
+
* element's position array outlives any one snapshot and its spare rows belong to nodes this
|
|
200
|
+
* graph does not have yet, whose NaN means "not placed" and must not become the origin.
|
|
201
|
+
* @param snapshot - The graph to lay out.
|
|
202
|
+
* @param positions - The owner's stride-3 scene-unit array.
|
|
203
|
+
*/
|
|
204
|
+
load(snapshot: GraphSnapshot, positions: F32): void;
|
|
205
|
+
/**
|
|
206
|
+
* Submits one batch of `iterations`, or coalesces onto the oldest one already in flight.
|
|
207
|
+
* @param iterations - Iterations this batch computes. Defaults to the simulation's own.
|
|
208
|
+
* @returns The batch. Coalesced calls share the promise they landed on.
|
|
209
|
+
*/
|
|
210
|
+
step(iterations?: number): Promise<void>;
|
|
211
|
+
/**
|
|
212
|
+
* Fixes the nodes whose bit is set; a fixed node is not moved by a landed batch.
|
|
213
|
+
* @param mask - The bitmask, copied rather than held.
|
|
214
|
+
*/
|
|
215
|
+
setFixed(mask: NodeMask): void;
|
|
216
|
+
/**
|
|
217
|
+
* Places one node now, and keeps the next landed batch from moving it.
|
|
218
|
+
*
|
|
219
|
+
* That is what a drag needs: the pointer owns the node until the batch that was in flight
|
|
220
|
+
* when the drag began has landed, and a batch computed before the drag must not slide the
|
|
221
|
+
* node back.
|
|
222
|
+
* @param index - The node's dense row.
|
|
223
|
+
* @param x - The scene-unit x.
|
|
224
|
+
* @param y - The scene-unit y.
|
|
225
|
+
* @param z - The scene-unit z.
|
|
226
|
+
*/
|
|
227
|
+
setPosition(index: number, x: number, y: number, z: number): void;
|
|
228
|
+
/** Starts the settle count again, so a settled simulation moves once more. */
|
|
229
|
+
reheat(): void;
|
|
230
|
+
/**
|
|
231
|
+
* Makes the NEXT submitted batch reject with this code.
|
|
232
|
+
* @param code - The code the rejection carries.
|
|
233
|
+
*/
|
|
234
|
+
fail(code: GraphtyErrorCode): void;
|
|
235
|
+
/**
|
|
236
|
+
* Awaits every batch in flight on this simulation.
|
|
237
|
+
*
|
|
238
|
+
* A snapshot of the array, awaited once: looping until nothing is in flight never returns while
|
|
239
|
+
* something keeps submitting, which a story or a browser test with the frame loop running does.
|
|
240
|
+
*/
|
|
241
|
+
flush(): Promise<void>;
|
|
242
|
+
/** Releases it. Every later call throws `E_DISPOSED`. */
|
|
243
|
+
dispose(): void;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* The fake accelerator itself: the layout seam, three algorithm members, residency and the
|
|
247
|
+
* counters a test reads.
|
|
248
|
+
*/
|
|
249
|
+
export interface FakeAccelerator extends GraphAccelerator {
|
|
250
|
+
/** What the layout package's `LayoutAccelerator` calls itself. */
|
|
251
|
+
readonly kind: string;
|
|
252
|
+
/** Everything the fake was asked to do, aggregated over every simulation it created. */
|
|
253
|
+
readonly calls: FakeAcceleratorCalls;
|
|
254
|
+
/** The simulations it has created, oldest first, for the cases that need identity. */
|
|
255
|
+
readonly simulations: readonly FakeSimulation[];
|
|
256
|
+
/** How many batches are in flight over every simulation. */
|
|
257
|
+
readonly pending: number;
|
|
258
|
+
/**
|
|
259
|
+
* Builds a ForceAtlas2 simulation.
|
|
260
|
+
* @param options - The simulation options.
|
|
261
|
+
* @returns The simulation, also pushed onto `simulations`.
|
|
262
|
+
*/
|
|
263
|
+
forceAtlas2(options?: ForceAtlas2Options): FakeSimulation;
|
|
264
|
+
/**
|
|
265
|
+
* Builds a Fruchterman-Reingold simulation.
|
|
266
|
+
* @param options - The simulation options.
|
|
267
|
+
* @returns The simulation, also pushed onto `simulations`.
|
|
268
|
+
*/
|
|
269
|
+
fruchtermanReingold(options?: FruchtermanReingoldOptions): FakeSimulation;
|
|
270
|
+
/**
|
|
271
|
+
* Builds a spring-electrical simulation, which has no CPU implementation.
|
|
272
|
+
* @param options - The simulation options.
|
|
273
|
+
* @returns The simulation, also pushed onto `simulations`.
|
|
274
|
+
*/
|
|
275
|
+
springElectrical(options?: SpringElectricalOptions): FakeSimulation;
|
|
276
|
+
/**
|
|
277
|
+
* Scores every node equally, so a caller sees a result shape without a computation.
|
|
278
|
+
* @param snapshot - The graph.
|
|
279
|
+
* @param options - Ignored.
|
|
280
|
+
* @returns Scores of `1 / n`, converged after one iteration.
|
|
281
|
+
*/
|
|
282
|
+
pageRank(snapshot: GraphSnapshot, options?: IndexedPageRankOptions): Promise<PageRankResultLike>;
|
|
283
|
+
/**
|
|
284
|
+
* Puts every node in one component.
|
|
285
|
+
* @param snapshot - The graph.
|
|
286
|
+
* @returns One label, one group.
|
|
287
|
+
*/
|
|
288
|
+
connectedComponents(snapshot: GraphSnapshot): Promise<LabelResultLike>;
|
|
289
|
+
/**
|
|
290
|
+
* Puts every node in one component, ignoring direction.
|
|
291
|
+
* @param snapshot - The graph.
|
|
292
|
+
* @returns One label, one group.
|
|
293
|
+
*/
|
|
294
|
+
weaklyConnectedComponents(snapshot: GraphSnapshot): Promise<LabelResultLike>;
|
|
295
|
+
/**
|
|
296
|
+
* Records a snapshot as released. A real accelerator frees its device buffers here.
|
|
297
|
+
* @param snapshot - The snapshot whose residency is dropped.
|
|
298
|
+
*/
|
|
299
|
+
release(snapshot: GraphSnapshot): void;
|
|
300
|
+
/** Records that the accelerator was disposed. */
|
|
301
|
+
dispose(): void;
|
|
302
|
+
/** Awaits every batch in flight over every simulation. */
|
|
303
|
+
flush(): Promise<void>;
|
|
304
|
+
/**
|
|
305
|
+
* Makes the next submitted batch of every live simulation reject.
|
|
306
|
+
* @param code - The code the rejection carries; `E_DEVICE_LOST` for the loss paths.
|
|
307
|
+
*/
|
|
308
|
+
fail(code: GraphtyErrorCode): void;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Builds a deterministic fake accelerator.
|
|
312
|
+
* @param options - Its name, precision, movement, settling, coalescing and extra members.
|
|
313
|
+
* @returns The accelerator, with every member an own property so a feature test finds it.
|
|
314
|
+
* @example
|
|
315
|
+
* ```ts
|
|
316
|
+
* const fake = createFakeAccelerator({ moveBy: 2, settleAfter: 3 });
|
|
317
|
+
* element.session.setAccelerator(fake);
|
|
318
|
+
* await fake.flush();
|
|
319
|
+
* assert.strictEqual(fake.calls.step, fake.calls.resolved);
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
export declare function createFakeAccelerator(options?: FakeAcceleratorOptions): FakeAccelerator;
|
|
323
|
+
export {};
|
package/dist/webgpu.d.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* Importing this module registers a WebGPU accelerator factory with the element and does
|
|
10
10
|
* nothing else. After that line the element probes for an adapter, requests a context, builds
|
|
11
11
|
* the accelerator, attaches it, watches for device loss, applies the `acceleration.minNodes`
|
|
12
|
-
* threshold and publishes what it found. A
|
|
12
|
+
* threshold and publishes what it found. A software adapter is refused under `auto` and
|
|
13
|
+
* accepted under `required` (`acceptSoftware`). A consumer writes no probe, no construction, no
|
|
13
14
|
* injection and no device-loss code, because every one of those would be the same code in every
|
|
14
15
|
* application that wanted a GPU.
|
|
15
16
|
*
|
|
@@ -24,6 +25,27 @@
|
|
|
24
25
|
* and the rest stay on this side of the boundary; what crosses is a {@link GraphAccelerator},
|
|
25
26
|
* which is plain names and functions, and failures arrive as codes on a `GraphtyError`.
|
|
26
27
|
*
|
|
28
|
+
* ## A GPU that answers, and answers wrongly
|
|
29
|
+
*
|
|
30
|
+
* An adapter being present is not the same as an adapter being right. The software renderer that
|
|
31
|
+
* ships with Windows miscomputes shaders that pass a value across a workgroup barrier, so every
|
|
32
|
+
* multi-workgroup prefix sum -- and the sort, the histogram and the grid layout tier above one --
|
|
33
|
+
* comes back wrong, with plausible numbers and no error anywhere.
|
|
34
|
+
*
|
|
35
|
+
* So the accelerator this file builds implements `verify()`, and the element calls it at ATTACH,
|
|
36
|
+
* before any of the graph goes near the device: {@link checkDeviceComputes} runs the peer's own
|
|
37
|
+
* self-check, a real prefix sum of known numbers scanned through the shipped primitive and
|
|
38
|
+
* checked on the host, and turns a wrong word into `E_DEVICE_INCORRECT`. The element then
|
|
39
|
+
* reports acceleration unavailable with that code and draws the graph on the CPU. The peer
|
|
40
|
+
* memoises the result per device, so the 14 to 20 milliseconds are paid once and the accelerated
|
|
41
|
+
* runs that follow re-use the answer.
|
|
42
|
+
*
|
|
43
|
+
* That is detection, not a fallback: the decision is made before any accelerated work starts.
|
|
44
|
+
* The peer also guards its own compute entry points, so a device that somehow gets past the
|
|
45
|
+
* check still refuses rather than returning wrong numbers -- and the element lets go of it and
|
|
46
|
+
* republishes rather than finishing that work anywhere else. Recorded in
|
|
47
|
+
* `docs/decisions/device-computes-incorrectly.md`.
|
|
48
|
+
*
|
|
27
49
|
* ## Detection is not a fallback
|
|
28
50
|
*
|
|
29
51
|
* Finding out, before anything runs, that this host has no WebGPU and letting the element take
|
package/dist/webgpu.js
CHANGED
|
@@ -1,71 +1,109 @@
|
|
|
1
|
-
import { EXACT_MAX_NODES as
|
|
2
|
-
import { probeBrowserWebGpu as
|
|
3
|
-
import { r as
|
|
4
|
-
import { G as
|
|
5
|
-
const a = "webgpu-graph-algorithms",
|
|
6
|
-
function
|
|
7
|
-
for (const [
|
|
8
|
-
|
|
1
|
+
import { EXACT_MAX_NODES as n, createAccelerator as p, verifyDevice as u } from "@graphty/webgpu-graph-algorithms";
|
|
2
|
+
import { probeBrowserWebGpu as l, requestGpuContext as d } from "@graphty/webgpu-graph-algorithms/browser";
|
|
3
|
+
import { r as h } from "./chunks/registry-CSba5QGJ.js";
|
|
4
|
+
import { G as s } from "./chunks/GraphtyError-BwcnblTH.js";
|
|
5
|
+
const a = "webgpu-graph-algorithms", w = /* @__PURE__ */ new Set(["kind", "ctx", "options", "dispose", "verify"]);
|
|
6
|
+
function b(o, e) {
|
|
7
|
+
for (const [r, t] of Object.entries(o))
|
|
8
|
+
w.has(r) || typeof t != "function" || (e[r] = t.bind(o));
|
|
9
9
|
}
|
|
10
|
-
function
|
|
10
|
+
function m(o, e) {
|
|
11
11
|
const t = {
|
|
12
12
|
E_NO_WEBGPU: typeof globalThis.isSecureContext == "boolean" && !globalThis.isSecureContext ? "WebGPU requires a secure context (https or localhost)" : "this browser has no WebGPU",
|
|
13
13
|
E_NO_ADAPTER: "WebGPU is present but no graphics adapter would answer",
|
|
14
14
|
E_SOFTWARE_ONLY: "the only WebGPU adapter here is a software renderer, which is slower than the CPU path"
|
|
15
15
|
};
|
|
16
|
-
return new
|
|
16
|
+
return new s({
|
|
17
17
|
code: o,
|
|
18
18
|
message: t[o],
|
|
19
19
|
source: "acceleration",
|
|
20
20
|
recoverable: !1,
|
|
21
|
-
details:
|
|
21
|
+
details: e === null ? { accelerator: a } : { accelerator: a, probe: e }
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
|
-
function
|
|
25
|
-
const
|
|
24
|
+
async function f(o) {
|
|
25
|
+
const e = await u(o), { mismatch: r } = e;
|
|
26
|
+
if (r === null)
|
|
27
|
+
return;
|
|
28
|
+
const t = r.poison ? `${r.where} was never written at all` : `${r.where} came back as ${String(r.actual)} where ${String(r.expected)} was required`;
|
|
29
|
+
throw new s({
|
|
30
|
+
code: "E_DEVICE_INCORRECT",
|
|
31
|
+
message: `this GPU computes multi-workgroup shaders incorrectly: a prefix sum of ${String(e.count)} known numbers came back wrong -- ${t}. The graph is being computed on the processor instead, because every number this device produced would be unreliable`,
|
|
32
|
+
source: "acceleration",
|
|
33
|
+
recoverable: !1,
|
|
34
|
+
details: {
|
|
35
|
+
accelerator: a,
|
|
36
|
+
check: e.check,
|
|
37
|
+
where: r.where,
|
|
38
|
+
expected: r.expected,
|
|
39
|
+
actual: r.actual,
|
|
40
|
+
poison: r.poison,
|
|
41
|
+
count: e.count,
|
|
42
|
+
blocks: e.blocks,
|
|
43
|
+
workgroupSize: e.workgroupSize,
|
|
44
|
+
ms: e.ms,
|
|
45
|
+
adapter: {
|
|
46
|
+
vendor: e.vendor,
|
|
47
|
+
architecture: e.architecture,
|
|
48
|
+
description: e.description
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function g(o, e) {
|
|
54
|
+
const { caps: r } = o, t = {
|
|
26
55
|
name: a,
|
|
27
56
|
backend: "webgpu",
|
|
28
57
|
device: {
|
|
29
|
-
vendor:
|
|
30
|
-
architecture:
|
|
31
|
-
description:
|
|
58
|
+
vendor: r.vendor,
|
|
59
|
+
architecture: r.architecture,
|
|
60
|
+
description: r.description === "" ? r.device : r.description
|
|
32
61
|
},
|
|
33
62
|
// Every GPU kernel in the peer computes in single precision, which is why a run that
|
|
34
63
|
// used one is labelled f32 and the same run on the CPU path is labelled f64.
|
|
35
64
|
precision: "f32",
|
|
36
65
|
// The element watches this. A device the element itself destroyed also resolves it, and
|
|
37
66
|
// the controller ignores a loss reported for an accelerator it has already released.
|
|
38
|
-
lost: o.lost.then((
|
|
67
|
+
lost: o.lost.then((c) => ({ reason: c.message === "" ? c.reason : c.message })),
|
|
68
|
+
// The element calls this once, before it attaches this accelerator and before any of the
|
|
69
|
+
// graph reaches the device. `NOT_FORWARDED` is what keeps the forwarding pass below from
|
|
70
|
+
// replacing it.
|
|
71
|
+
verify: () => f(o),
|
|
39
72
|
dispose: () => {
|
|
40
|
-
|
|
73
|
+
e.dispose();
|
|
41
74
|
}
|
|
42
75
|
};
|
|
43
|
-
return
|
|
76
|
+
return b(e, t), t;
|
|
44
77
|
}
|
|
45
78
|
async function E(o) {
|
|
46
|
-
const { exactMaxNodes:
|
|
47
|
-
if (
|
|
48
|
-
throw new
|
|
79
|
+
const { exactMaxNodes: e } = o ?? {};
|
|
80
|
+
if (e !== void 0 && e > n)
|
|
81
|
+
throw new s({
|
|
49
82
|
code: "E_TOO_LARGE",
|
|
50
|
-
message: `the WebGPU accelerator computes exactly up to ${String(
|
|
83
|
+
message: `the WebGPU accelerator computes exactly up to ${String(n)} nodes, not ${String(e)}`,
|
|
51
84
|
source: "acceleration",
|
|
52
|
-
details: { accelerator: a, exactMaxNodes:
|
|
85
|
+
details: { accelerator: a, exactMaxNodes: e, limit: n }
|
|
53
86
|
});
|
|
54
|
-
const
|
|
55
|
-
if (!
|
|
56
|
-
throw
|
|
57
|
-
const
|
|
87
|
+
const r = !(o?.acceptSoftware ?? !1), t = await l({ rejectSoftware: r });
|
|
88
|
+
if (!t.ok || t.code !== "OK")
|
|
89
|
+
throw m(t.code === "OK" ? "E_NO_ADAPTER" : t.code, t.reason);
|
|
90
|
+
const c = await d(
|
|
91
|
+
t.adapter === null ? { rejectSoftware: r } : { adapter: t.adapter, rejectSoftware: r }
|
|
92
|
+
);
|
|
58
93
|
try {
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
|
|
94
|
+
return g(
|
|
95
|
+
c,
|
|
96
|
+
p(c, e === void 0 ? void 0 : { layout: { exactMaxNodes: e } })
|
|
97
|
+
);
|
|
98
|
+
} catch (i) {
|
|
99
|
+
throw c.dispose(), s.wrap(i, {
|
|
62
100
|
code: "E_INTERNAL",
|
|
63
101
|
source: "acceleration",
|
|
64
102
|
details: { accelerator: a }
|
|
65
103
|
});
|
|
66
104
|
}
|
|
67
105
|
}
|
|
68
|
-
|
|
106
|
+
h({
|
|
69
107
|
name: a,
|
|
70
108
|
backend: "webgpu",
|
|
71
109
|
factory: E
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphty/graphty-element",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "A Web Component library for 3D/2D graph visualization built with Lit and Babylon.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"customElements": "./dist/custom-elements.json",
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"./dist/graphty.bundle.js",
|
|
61
61
|
"./dist/webgpu.js",
|
|
62
62
|
"./dist/chunks/*.js",
|
|
63
|
+
"./webgpu.ts",
|
|
63
64
|
"./src/algorithms/index.ts",
|
|
64
65
|
"./src/data/index.ts",
|
|
65
66
|
"./src/layout/index.ts",
|
|
@@ -112,6 +113,7 @@
|
|
|
112
113
|
"@types/hammerjs": "^2.0.46",
|
|
113
114
|
"@types/jmespath": "^0.15.2",
|
|
114
115
|
"@types/lodash": "^4.17.19",
|
|
116
|
+
"@types/papaparse": "^5.5.0",
|
|
115
117
|
"@types/toposort": "^2.0.7",
|
|
116
118
|
"ai": "^5.0.104",
|
|
117
119
|
"encrypt-storage": "^2.14.7",
|
|
@@ -119,6 +121,8 @@
|
|
|
119
121
|
"fast-check": "^4.2.0",
|
|
120
122
|
"iwer": "^2.1.1",
|
|
121
123
|
"lit": "^3.3.1",
|
|
124
|
+
"lodash": "^4.17.21",
|
|
125
|
+
"ngraph.random": "^1.2.0",
|
|
122
126
|
"pngjs": "^7.0.0",
|
|
123
127
|
"storybook": "^9.1.20",
|
|
124
128
|
"typedoc": "^0.28.15",
|
|
@@ -128,7 +132,9 @@
|
|
|
128
132
|
"vite-plugin-cem": "^0.8.2",
|
|
129
133
|
"vite-plugin-eslint": "^1.8.1",
|
|
130
134
|
"vitepress": "^1.6.3",
|
|
131
|
-
"vitest": "^3.2.4"
|
|
135
|
+
"vitest": "^3.2.4",
|
|
136
|
+
"@graphty/remote-logger": "^1.3.6",
|
|
137
|
+
"@graphty/webgpu-graph-algorithms": "^0.6.3"
|
|
132
138
|
},
|
|
133
139
|
"peerDependencies": {
|
|
134
140
|
"@ai-sdk/anthropic": "^2.0.50",
|
|
@@ -136,11 +142,11 @@
|
|
|
136
142
|
"@ai-sdk/openai": "^2.0.74",
|
|
137
143
|
"@babylonjs/core": "^8.0.0",
|
|
138
144
|
"@graphty/graph-format": "^1.0.0",
|
|
139
|
-
"@graphty/webgpu-graph-algorithms": "^0.2.0",
|
|
140
145
|
"@mlc-ai/web-llm": ">=0.2.0",
|
|
141
146
|
"ai": "^5.0.104",
|
|
142
147
|
"encrypt-storage": "^2.14.7",
|
|
143
|
-
"lit": "^3.0.0"
|
|
148
|
+
"lit": "^3.0.0",
|
|
149
|
+
"@graphty/webgpu-graph-algorithms": "^0.6.3"
|
|
144
150
|
},
|
|
145
151
|
"peerDependenciesMeta": {
|
|
146
152
|
"@ai-sdk/anthropic": {
|
|
@@ -167,23 +173,20 @@
|
|
|
167
173
|
},
|
|
168
174
|
"dependencies": {
|
|
169
175
|
"@jsonhero/schema-infer": "^0.1.5",
|
|
170
|
-
"@types/papaparse": "^5.5.0",
|
|
171
176
|
"colorjs.io": "^0.5.2",
|
|
172
177
|
"d3-force-3d": "^3.0.6",
|
|
173
178
|
"fast-xml-parser": "^5.3.1",
|
|
174
179
|
"hammerjs": "^2.0.8",
|
|
175
180
|
"jmespath": "^0.16.0",
|
|
176
|
-
"lodash": "^4.17.21",
|
|
177
181
|
"ngraph.forcelayout": "^3.3.1",
|
|
178
182
|
"ngraph.graph": "^20.0.1",
|
|
179
183
|
"p-queue": "^8.1.1",
|
|
180
184
|
"papaparse": "^5.5.3",
|
|
181
185
|
"toposort": "^2.0.2",
|
|
182
186
|
"zod": "^3.25.28",
|
|
183
|
-
"@graphty/
|
|
184
|
-
"@graphty/
|
|
185
|
-
"@graphty/layout": "^1.
|
|
186
|
-
"@graphty/remote-logger": "^1.3.4"
|
|
187
|
+
"@graphty/algorithms": "^2.0.2",
|
|
188
|
+
"@graphty/graph-format": "^1.0.4",
|
|
189
|
+
"@graphty/layout": "^1.9.0"
|
|
187
190
|
},
|
|
188
191
|
"overrides": {
|
|
189
192
|
"storybook": "$storybook"
|
|
@@ -241,7 +244,8 @@
|
|
|
241
244
|
"coverage:shard:storybook:4": "COVERAGE_DIR=.coverage-parts/storybook-4 vitest run --project=storybook --shard=4/4 --coverage",
|
|
242
245
|
"coverage:fast": "vitest run --project=default --coverage",
|
|
243
246
|
"coverage:preview": "npx serve coverage -p ${PORT:?start it through servherd, which sets PORT}",
|
|
244
|
-
"lint": "eslint && tsc --noEmit",
|
|
247
|
+
"lint": "eslint && tsc --noEmit && npm run typecheck:strict-consumer",
|
|
248
|
+
"typecheck:strict-consumer": "tsc -p tsconfig.strict-consumer.json",
|
|
245
249
|
"lint:fix": "eslint --fix",
|
|
246
250
|
"lint:knip": "cd .. && ./tools/run-knip.sh --workspace graphty-element",
|
|
247
251
|
"dev": "vite --force --mode development --port ${PORT:?start it through servherd, which sets PORT} --strictPort",
|