s3m-three 0.3.4 → 0.3.6
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/dist/browser/assets/s3mWorker-hZY5KqWF.js +1 -0
- package/dist/browser/s3m-three.iife.ext-three.js +26 -20
- package/dist/browser/s3m-three.iife.js +26 -20
- package/dist/core/TextureDecode.d.ts +18 -0
- package/dist/core/TextureDecode.d.ts.map +1 -0
- package/dist/core/TextureDecode.js +130 -0
- package/dist/core/TextureDecode.js.map +1 -0
- package/dist/core/TextureFormat.d.ts +22 -2
- package/dist/core/TextureFormat.d.ts.map +1 -1
- package/dist/core/TextureFormat.js +22 -2
- package/dist/core/TextureFormat.js.map +1 -1
- package/dist/render/DatasetManager.d.ts +2 -0
- package/dist/render/DatasetManager.d.ts.map +1 -1
- package/dist/render/DatasetManager.js +1 -0
- package/dist/render/DatasetManager.js.map +1 -1
- package/dist/render/FeatureStyleManager.d.ts +4 -2
- package/dist/render/FeatureStyleManager.d.ts.map +1 -1
- package/dist/render/FeatureStyleManager.js +33 -1
- package/dist/render/FeatureStyleManager.js.map +1 -1
- package/dist/render/MergeVertices.d.ts +17 -0
- package/dist/render/MergeVertices.d.ts.map +1 -0
- package/dist/render/MergeVertices.js +50 -0
- package/dist/render/MergeVertices.js.map +1 -0
- package/dist/render/S3MViewer.d.ts +2 -0
- package/dist/render/S3MViewer.d.ts.map +1 -1
- package/dist/render/S3MViewer.js +9 -4
- package/dist/render/S3MViewer.js.map +1 -1
- package/dist/render/StyleMaterial.d.ts.map +1 -1
- package/dist/render/StyleMaterial.js +6 -0
- package/dist/render/StyleMaterial.js.map +1 -1
- package/dist/render/TextureFactory.d.ts +8 -19
- package/dist/render/TextureFactory.d.ts.map +1 -1
- package/dist/render/TextureFactory.js +39 -116
- package/dist/render/TextureFactory.js.map +1 -1
- package/dist/tileset/S3MTileset.d.ts +17 -0
- package/dist/tileset/S3MTileset.d.ts.map +1 -1
- package/dist/tileset/S3MTileset.js +32 -1
- package/dist/tileset/S3MTileset.js.map +1 -1
- package/dist/worker/S3MWorkerClient.d.ts +40 -0
- package/dist/worker/S3MWorkerClient.d.ts.map +1 -0
- package/dist/worker/S3MWorkerClient.js +120 -0
- package/dist/worker/S3MWorkerClient.js.map +1 -0
- package/dist/worker/s3mWorker.d.ts +2 -0
- package/dist/worker/s3mWorker.d.ts.map +1 -0
- package/dist/worker/s3mWorker.js +48 -0
- package/dist/worker/s3mWorker.js.map +1 -0
- package/dist/worker/transferables.d.ts +14 -0
- package/dist/worker/transferables.d.ts.map +1 -0
- package/dist/worker/transferables.js +61 -0
- package/dist/worker/transferables.js.map +1 -0
- package/docs/API.md +4 -0
- package/package.json +1 -1
- package/src/core/TextureDecode.ts +158 -0
- package/src/core/TextureFormat.ts +23 -2
- package/src/render/DatasetManager.ts +3 -0
- package/src/render/FeatureStyleManager.ts +31 -2
- package/src/render/MergeVertices.ts +56 -0
- package/src/render/S3MViewer.ts +12 -4
- package/src/render/StyleMaterial.ts +6 -0
- package/src/render/TextureFactory.ts +43 -122
- package/src/tileset/S3MTileset.ts +39 -1
- package/src/worker/S3MWorkerClient.ts +150 -0
- package/src/worker/s3mWorker.ts +58 -0
- package/src/worker/transferables.ts +58 -0
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* 命中阈值就请求 childTile,实现渐进式细化。
|
|
8
8
|
*/
|
|
9
9
|
import { parseS3MB } from '../core/S3MBParser.js';
|
|
10
|
+
import { getSharedS3MWorker, S3MWorkerClient, WorkerDeadError } from '../worker/S3MWorkerClient.js';
|
|
10
11
|
import {
|
|
11
12
|
composeLeafAtlas,
|
|
12
13
|
expandTexblockRequestNames,
|
|
@@ -235,6 +236,15 @@ export interface S3MTilesetOptions {
|
|
|
235
236
|
/** 原始图像/子图集缓存上限(LRU),默认 128 */
|
|
236
237
|
maxImageCache?: number;
|
|
237
238
|
buildTile?: BuildTileFn | null;
|
|
239
|
+
/**
|
|
240
|
+
* 是否用 Web Worker 解析经典 `.s3mb` 瓦片(默认开)。
|
|
241
|
+
*
|
|
242
|
+
* Worker 里跑 parseS3MB 全链路(inflate / Draco / MeshOpt 内联 wasm 解码),
|
|
243
|
+
* 主线程只做 fetch 与几何组装,消除加载阶段的解析卡顿。
|
|
244
|
+
* Worker 创建失败或崩溃时自动**永久**降级回主线程解析,行为与关闭时完全一致。
|
|
245
|
+
* S3MBLOCK 路径(图集补齐与主线程缓存强耦合)暂不进 Worker,仍主线程解析。
|
|
246
|
+
*/
|
|
247
|
+
useWorker?: boolean;
|
|
238
248
|
uniforms?: StyleUniforms | null;
|
|
239
249
|
onTile?: (node: TileNode) => void;
|
|
240
250
|
/** 超过 maxTiles 被淘汰时回调,调用方要自己把场景里的对象和拾取表清掉 */
|
|
@@ -330,6 +340,8 @@ export class S3MTileset {
|
|
|
330
340
|
private isS3MV1 = false;
|
|
331
341
|
private _buildTile: BuildTileFn | null;
|
|
332
342
|
private readonly _uniforms: StyleUniforms | null;
|
|
343
|
+
/** 经典 .s3mb 的 Worker 解析客户端;undefined = 尚未尝试创建,null = 不可用/已降级 */
|
|
344
|
+
private _workerClient: S3MWorkerClient | null | undefined; private readonly _useWorker: boolean;
|
|
333
345
|
private _queue: string[] = [];
|
|
334
346
|
private _active = 0;
|
|
335
347
|
|
|
@@ -346,6 +358,7 @@ export class S3MTileset {
|
|
|
346
358
|
this._buildTile = options.buildTile ?? null;
|
|
347
359
|
// 着色器共享 uniform,由 S3MLayer 持有并传进来
|
|
348
360
|
this._uniforms = options.uniforms ?? null;
|
|
361
|
+
this._useWorker = options.useWorker !== false;
|
|
349
362
|
|
|
350
363
|
// 并发与总量限制:iServer 公开服务上别把人家打爆
|
|
351
364
|
this.maxConcurrent = options.maxConcurrent ?? 4;
|
|
@@ -677,6 +690,30 @@ export class S3MTileset {
|
|
|
677
690
|
return this._buildTile;
|
|
678
691
|
}
|
|
679
692
|
|
|
693
|
+
/**
|
|
694
|
+
* 经典 `.s3mb` 瓦片解析:优先走 Worker(把 inflate / Draco / MeshOpt 的 CPU 密集
|
|
695
|
+
* 解码搬离主线程),Worker 不可用或已崩溃时主线程兜底,两条路径输出完全一致。
|
|
696
|
+
*/
|
|
697
|
+
private async _parseClassicTile(buffer: ArrayBuffer): Promise<ParsedTile> {
|
|
698
|
+
if (this._useWorker && this._workerClient === undefined) {
|
|
699
|
+
// 与纹理解码共用进程级单例,避免每个数据集各起一个 Worker
|
|
700
|
+
this._workerClient = getSharedS3MWorker();
|
|
701
|
+
}
|
|
702
|
+
if (this._workerClient) {
|
|
703
|
+
try {
|
|
704
|
+
return await this._workerClient.parseTile(buffer);
|
|
705
|
+
} catch (e) {
|
|
706
|
+
if (e instanceof WorkerDeadError) {
|
|
707
|
+
// 脚本加载失败 / 崩溃属于环境问题,永久退回主线程;瓦片本身的问题照常抛
|
|
708
|
+
this._workerClient = null;
|
|
709
|
+
} else {
|
|
710
|
+
throw e;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return parseS3MB(buffer);
|
|
715
|
+
}
|
|
716
|
+
|
|
680
717
|
/**
|
|
681
718
|
* 记住一个块文件里解析出来的全部 LOD 组。
|
|
682
719
|
*
|
|
@@ -970,7 +1007,7 @@ export class S3MTileset {
|
|
|
970
1007
|
groupNames = set.names;
|
|
971
1008
|
tile = set.tiles[groupIndex] ?? set.tiles[0]!;
|
|
972
1009
|
} else {
|
|
973
|
-
tile = await
|
|
1010
|
+
tile = await this._parseClassicTile(buffer);
|
|
974
1011
|
}
|
|
975
1012
|
}
|
|
976
1013
|
if (isBlock) {
|
|
@@ -1269,6 +1306,7 @@ export class S3MTileset {
|
|
|
1269
1306
|
}
|
|
1270
1307
|
|
|
1271
1308
|
dispose(): void {
|
|
1309
|
+
this._workerClient?.dispose();
|
|
1272
1310
|
for (const node of this.nodes.values()) {
|
|
1273
1311
|
node.group.traverse((o) => {
|
|
1274
1312
|
const mesh = o as {
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 解析 Worker 的主线程客户端。
|
|
3
|
+
*
|
|
4
|
+
* 生命周期约定(由 S3MTileset 使用):
|
|
5
|
+
* - `create()` 返回 null = 环境不可用(无 Worker / 脚本同步创建失败),调用方直接走主线程解析;
|
|
6
|
+
* - `parseTile()` 抛 `WorkerDeadError` = Worker 脚本加载失败或已崩溃,调用方应**永久**
|
|
7
|
+
* 退回主线程解析(不要再给这个 client 发活,也别让每块瓦片都白白失败两轮重试);
|
|
8
|
+
* - 抛普通 Error = Worker 活着、但解析本身失败(网络字节坏了 / S3M1 容器永久错误等),
|
|
9
|
+
* 语义与主线程 `parseS3MB` 抛错完全一致,原样向上传。
|
|
10
|
+
*/
|
|
11
|
+
import type { ParsedTile, S3MTexture } from '../core/types.js';
|
|
12
|
+
import type { DecodedTextureData } from '../core/TextureDecode.js';
|
|
13
|
+
|
|
14
|
+
/** Worker 崩溃 / 不可用 —— 与「瓦片数据本身有问题」区分开,调用方据此决定降级还是重试 */
|
|
15
|
+
export class WorkerDeadError extends Error {
|
|
16
|
+
constructor(message: string) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'WorkerDeadError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface ParseOkResponse {
|
|
23
|
+
id: number;
|
|
24
|
+
ok: true;
|
|
25
|
+
tile: ParsedTile;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface TexOkResponse {
|
|
29
|
+
id: number;
|
|
30
|
+
ok: true;
|
|
31
|
+
data: DecodedTextureData;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface ErrResponse {
|
|
35
|
+
id: number;
|
|
36
|
+
ok: false;
|
|
37
|
+
message: string;
|
|
38
|
+
permanent: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type Response = ParseOkResponse | TexOkResponse | ErrResponse;
|
|
42
|
+
|
|
43
|
+
export class S3MWorkerClient {
|
|
44
|
+
private readonly _worker: Worker;
|
|
45
|
+
private _seq = 0;
|
|
46
|
+
private readonly _pending = new Map<number, { resolve: (v: unknown) => void; reject: (err: unknown) => void }>();
|
|
47
|
+
/** true = 已崩溃 / 已终止,后续请求直接拒绝 */
|
|
48
|
+
dead = false;
|
|
49
|
+
|
|
50
|
+
/** 创建失败返回 null(不抛),调用方据此走主线程解析 */
|
|
51
|
+
static create(): S3MWorkerClient | null {
|
|
52
|
+
if (typeof Worker === 'undefined') return null;
|
|
53
|
+
try {
|
|
54
|
+
// 路径锚定本模块的编译产物(dist/worker/s3mWorker.js);
|
|
55
|
+
// Vite 打包时会把这个 pattern 转成独立 worker 资产,IIFE 直引场景由死锁降级兜底
|
|
56
|
+
const worker = new Worker(new URL('./s3mWorker.js', import.meta.url), { type: 'module' });
|
|
57
|
+
return new S3MWorkerClient(worker);
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private constructor(worker: Worker) {
|
|
64
|
+
this._worker = worker;
|
|
65
|
+
worker.onmessage = (e: MessageEvent<Response>) => {
|
|
66
|
+
const msg = e.data;
|
|
67
|
+
const waiter = this._pending.get(msg.id);
|
|
68
|
+
if (!waiter) return;
|
|
69
|
+
this._pending.delete(msg.id);
|
|
70
|
+
if (!msg.ok) {
|
|
71
|
+
// 还原主线程 parseS3MB 的错误形状:permanent 标记驱动瓦片级的「永久失败」分支
|
|
72
|
+
const err = new Error(msg.message);
|
|
73
|
+
(err as { permanent?: boolean }).permanent = msg.permanent;
|
|
74
|
+
waiter.reject(err);
|
|
75
|
+
} else if ('tile' in msg) {
|
|
76
|
+
waiter.resolve(msg.tile);
|
|
77
|
+
} else {
|
|
78
|
+
waiter.resolve(msg.data);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
worker.onerror = () => this._die('s3m 解析 Worker 加载失败/崩溃');
|
|
82
|
+
worker.onmessageerror = () => this._die('s3m 解析 Worker 消息无法反序列化');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** 解析一块瓦片。buffer 会被 structured clone(不 transfer),主线程保留原 buffer 以便崩溃后降级重解 */
|
|
86
|
+
parseTile(buffer: ArrayBuffer): Promise<ParsedTile> {
|
|
87
|
+
if (this.dead) return Promise.reject(new WorkerDeadError('s3m 解析 Worker 已终止'));
|
|
88
|
+
const id = ++this._seq;
|
|
89
|
+
return new Promise<ParsedTile>((resolve, reject) => {
|
|
90
|
+
this._pending.set(id, { resolve: resolve as (v: unknown) => void, reject });
|
|
91
|
+
this._worker.postMessage({ type: 'parse', id, buffer });
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 解码一张纹理(DXT / CRN / WEBP / RGB 系)。tex.data 走 structured clone,主线程保留原数据 */
|
|
96
|
+
decodeTexture(tex: S3MTexture): Promise<DecodedTextureData> {
|
|
97
|
+
if (this.dead) return Promise.reject(new WorkerDeadError('s3m 解析 Worker 已终止'));
|
|
98
|
+
const id = ++this._seq;
|
|
99
|
+
return new Promise<DecodedTextureData>((resolve, reject) => {
|
|
100
|
+
this._pending.set(id, { resolve: resolve as (v: unknown) => void, reject });
|
|
101
|
+
this._worker.postMessage({ type: 'tex', id, tex });
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
dispose(): void {
|
|
106
|
+
this._die('dispose');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private _die(reason: string): void {
|
|
110
|
+
if (this.dead) return;
|
|
111
|
+
this.dead = true;
|
|
112
|
+
try {
|
|
113
|
+
this._worker.terminate();
|
|
114
|
+
} catch {
|
|
115
|
+
/* 已死的 worker terminate 不会再抛 */
|
|
116
|
+
}
|
|
117
|
+
const err = new WorkerDeadError(`${reason},退回主线程解析`);
|
|
118
|
+
for (const waiter of this._pending.values()) waiter.reject(err);
|
|
119
|
+
this._pending.clear();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* ------------------------------------------------------------------ 共享单例 */
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 进程级共享的解析 Worker(解析 + 纹理解码共用一个线程,避免每个数据集 /
|
|
127
|
+
* 每个渲染层各起一个 Worker 把内存和调度打爆)。懒创建,崩溃后返回 null。
|
|
128
|
+
*/
|
|
129
|
+
let sharedClient: S3MWorkerClient | null | undefined;
|
|
130
|
+
|
|
131
|
+
export function getSharedS3MWorker(): S3MWorkerClient | null {
|
|
132
|
+
if (sharedClient === undefined) sharedClient = S3MWorkerClient.create();
|
|
133
|
+
if (sharedClient && sharedClient.dead) return null;
|
|
134
|
+
return sharedClient;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 纹理解码的「Worker 优先」入口,供 TextureFactory.setTextureOffThreadDecode 装配。
|
|
139
|
+
* 永不 reject:Worker 不可用 / 崩溃 / 单张解码失败都返回 null,调用方主线程兜底
|
|
140
|
+
* (解码失败本身在 decodeTextureData 里也不抛错,走 crn(failed) 之类的标记)。
|
|
141
|
+
*/
|
|
142
|
+
export async function decodeTextureWithWorker(tex: S3MTexture): Promise<DecodedTextureData | null> {
|
|
143
|
+
const client = getSharedS3MWorker();
|
|
144
|
+
if (!client) return null;
|
|
145
|
+
try {
|
|
146
|
+
return await client.decodeTexture(tex);
|
|
147
|
+
} catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3M 解析 / 纹理解码 Worker 入口。
|
|
3
|
+
*
|
|
4
|
+
* 职责:把 CPU 密集的解码搬离主线程——
|
|
5
|
+
* - parse:`.s3mb`(含 S3M 1.x / Draco / MeshOpt 内联 wasm 解码)整块解析;
|
|
6
|
+
* - tex: 纹理解码(DXT 解块 / CRN wasm 转码 / WEBP createImageBitmap),
|
|
7
|
+
* 与主线程共用 core/TextureDecode.ts 的同一份实现。
|
|
8
|
+
* 抓取(fetch)仍由 S3MTileset 在主线程做——重试、退避、content-type 检查这些
|
|
9
|
+
* 语义都留在原处不动,Worker 只收字节、吐纯数据结果。
|
|
10
|
+
*
|
|
11
|
+
* 协议:
|
|
12
|
+
* { type:'parse', id, buffer } → { id, ok:true, tile } | { id, ok:false, message, permanent }
|
|
13
|
+
* { type:'tex', id, tex } → { id, ok:true, data } | { id, ok:false, message }
|
|
14
|
+
*
|
|
15
|
+
* 线程安全:两个入口都无模块级可变状态;每次调用用独立的 reader / 解码器实例。
|
|
16
|
+
*/
|
|
17
|
+
import { parseS3MB } from '../core/S3MBParser.js';
|
|
18
|
+
import { decodeTextureData } from '../core/TextureDecode.js';
|
|
19
|
+
import type { ParsedTile, S3MTexture } from '../core/types.js';
|
|
20
|
+
import { collectTransferables } from './transferables.js';
|
|
21
|
+
|
|
22
|
+
type ParseRequest = { type: 'parse'; id: number; buffer: ArrayBuffer };
|
|
23
|
+
type TexRequest = { type: 'tex'; id: number; tex: S3MTexture };
|
|
24
|
+
type Request = ParseRequest | TexRequest;
|
|
25
|
+
|
|
26
|
+
type ParseResponse =
|
|
27
|
+
| { id: number; ok: true; tile: ParsedTile }
|
|
28
|
+
| { id: number; ok: false; message: string; permanent: boolean };
|
|
29
|
+
|
|
30
|
+
type TexResponse = { id: number; ok: true; data: Awaited<ReturnType<typeof decodeTextureData>> };
|
|
31
|
+
|
|
32
|
+
const ctx = self as unknown as Worker;
|
|
33
|
+
|
|
34
|
+
/** 纹理解码响应的传输清单:RGBA 像素 buffer + WEBP 的 ImageBitmap 都走零拷贝 */
|
|
35
|
+
function texTransferList(data: Awaited<ReturnType<typeof decodeTextureData>>): ArrayBuffer[] {
|
|
36
|
+
const list = collectTransferables(data);
|
|
37
|
+
if (data.bitmap) list.push(data.bitmap as unknown as ArrayBuffer);
|
|
38
|
+
return list;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
ctx.onmessage = async (e: MessageEvent<Request>) => {
|
|
42
|
+
const msg = e.data;
|
|
43
|
+
const { id } = msg;
|
|
44
|
+
try {
|
|
45
|
+
if (msg.type === 'parse') {
|
|
46
|
+
const tile = await parseS3MB(msg.buffer);
|
|
47
|
+
// 响应整体过线(零拷贝);请求 buffer 是主线程 structured clone 过来的副本,无需回传
|
|
48
|
+
ctx.postMessage({ id, ok: true, tile } satisfies ParseResponse, collectTransferables(tile));
|
|
49
|
+
} else {
|
|
50
|
+
const data = await decodeTextureData(msg.tex);
|
|
51
|
+
ctx.postMessage({ id, ok: true, data } satisfies TexResponse, texTransferList(data));
|
|
52
|
+
}
|
|
53
|
+
} catch (err) {
|
|
54
|
+
const permanent = (err as { permanent?: boolean } | null)?.permanent === true;
|
|
55
|
+
const message = String((err as Error | null)?.message ?? err);
|
|
56
|
+
ctx.postMessage({ id, ok: false, message, permanent });
|
|
57
|
+
}
|
|
58
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 传输清单收集:递归扫出对象图里所有可 Transfer 的 ArrayBuffer(去重)。
|
|
3
|
+
*
|
|
4
|
+
* 用于 Worker 往返的零拷贝:postMessage 的 transferList 传这个清单后,
|
|
5
|
+
* 线程间共享同一块内存(structured clone 只拷贝「壳」,不拷字节)。
|
|
6
|
+
*
|
|
7
|
+
* 安全性约定:**要么全转,要么不转** —— 同一块 ArrayBuffer 上可能挂着多个视图
|
|
8
|
+
* (positions / normals 常是同一块顶点缓冲的 subarray),transferList 里出现它一次
|
|
9
|
+
* 之后所有视图共享的底层数据整体过线,视图本身仍然有效;但如果只转一半视图、
|
|
10
|
+
* 另一半留在原地,留在原地的会因 detach 变成空数据。所以只允许「把整个对象图
|
|
11
|
+
* 的 buffer 全部收集」或「完全不收集」两种用法,不支持挑着转。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** 递归深度上限。ParsedTile 的层级是固定的(tile → geoPackage → geode → package),远用不到 12 层 */
|
|
15
|
+
const MAX_DEPTH = 12;
|
|
16
|
+
|
|
17
|
+
export function collectTransferables(root: unknown): ArrayBuffer[] {
|
|
18
|
+
const buffers = new Set<ArrayBuffer>();
|
|
19
|
+
const seen = new Set<object>();
|
|
20
|
+
|
|
21
|
+
const walk = (value: unknown, depth: number): void => {
|
|
22
|
+
if (value == null || depth > MAX_DEPTH) return;
|
|
23
|
+
if (ArrayBuffer.isView(value)) {
|
|
24
|
+
const b = (value as ArrayBufferView).buffer;
|
|
25
|
+
if (b instanceof ArrayBuffer) buffers.add(b);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (value instanceof ArrayBuffer) {
|
|
29
|
+
buffers.add(value);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (typeof value !== 'object') return;
|
|
33
|
+
const obj = value as object;
|
|
34
|
+
if (seen.has(obj)) return;
|
|
35
|
+
seen.add(obj);
|
|
36
|
+
if (Array.isArray(obj)) {
|
|
37
|
+
for (const item of obj) walk(item, depth + 1);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (obj instanceof Map) {
|
|
41
|
+
for (const [k, v] of obj) {
|
|
42
|
+
walk(k, depth + 1);
|
|
43
|
+
walk(v, depth + 1);
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (obj instanceof Set) {
|
|
48
|
+
for (const item of obj) walk(item, depth + 1);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
for (const key of Object.keys(obj)) {
|
|
52
|
+
walk((obj as Record<string, unknown>)[key], depth + 1);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
walk(root, 0);
|
|
57
|
+
return [...buffers];
|
|
58
|
+
}
|