mudlet-map-renderer 2.6.0 → 2.6.1
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/{SkeletonMapReader-B6PzEWGP.js → SkeletonMapReader-ZPwsqemZ.js} +14 -3
- package/dist/SkeletonMapReader-ZPwsqemZ.js.map +1 -0
- package/dist/bigmap/Skeleton.d.ts +2 -0
- package/dist/bigmap/SkeletonMapReader.d.ts +7 -1
- package/dist/bigmap.mjs +14 -13
- package/dist/bigmap.mjs.map +1 -1
- package/dist/binary/loadMudletMap.d.ts +13 -0
- package/dist/binary.mjs +36 -35
- package/dist/binary.mjs.map +1 -1
- package/dist/export/exportViewport.d.ts +25 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +506 -476
- package/dist/index.mjs.map +1 -1
- package/dist/reader/HashLookup.d.ts +17 -0
- package/package.json +1 -1
- package/dist/SkeletonMapReader-B6PzEWGP.js.map +0 -1
|
@@ -146,7 +146,7 @@ function s(e, t, n) {
|
|
|
146
146
|
}
|
|
147
147
|
var c = class {
|
|
148
148
|
constructor(e) {
|
|
149
|
-
this.viewportAware = !0, this.viewport = o, this.version = 0, this.planeCache = /* @__PURE__ */ new Map(), this.areaCache = /* @__PURE__ */ new Map(), this.rgbCache = /* @__PURE__ */ new Map(), this.visibleCache = /* @__PURE__ */ new Map(), this.detail = /* @__PURE__ */ new Map(), this.userDataMap = /* @__PURE__ */ new Map(), this.sk = e;
|
|
149
|
+
this.viewportAware = !0, this.hashLookupCapable = !0, this.viewport = o, this.version = 0, this.planeCache = /* @__PURE__ */ new Map(), this.areaCache = /* @__PURE__ */ new Map(), this.rgbCache = /* @__PURE__ */ new Map(), this.visibleCache = /* @__PURE__ */ new Map(), this.detail = /* @__PURE__ */ new Map(), this.userDataMap = /* @__PURE__ */ new Map(), this.sk = e;
|
|
150
150
|
let t = e.y;
|
|
151
151
|
for (let n = 0; n < e.count; n++) t[n] = -t[n];
|
|
152
152
|
for (let t of e.userData ?? []) this.userDataMap.set(t.id, t.data);
|
|
@@ -155,6 +155,9 @@ var c = class {
|
|
|
155
155
|
skeleton() {
|
|
156
156
|
return this.sk;
|
|
157
157
|
}
|
|
158
|
+
getRoomIdByHash(e) {
|
|
159
|
+
return this.sk.hashToId?.[e];
|
|
160
|
+
}
|
|
158
161
|
setViewport(e) {
|
|
159
162
|
let t = this.viewport;
|
|
160
163
|
e.minX === t.minX && e.maxX === t.maxX && e.minY === t.minY && e.maxY === t.maxY || (this.viewport = { ...e }, this.visibleCache.clear(), this.version++);
|
|
@@ -221,6 +224,14 @@ var c = class {
|
|
|
221
224
|
}
|
|
222
225
|
return t;
|
|
223
226
|
}
|
|
227
|
+
hashFor(e) {
|
|
228
|
+
if (!this.sk.hashToId) return "";
|
|
229
|
+
if (!this.idToHashIndex) {
|
|
230
|
+
this.idToHashIndex = /* @__PURE__ */ new Map();
|
|
231
|
+
for (let e in this.sk.hashToId) this.idToHashIndex.set(this.sk.hashToId[e], e);
|
|
232
|
+
}
|
|
233
|
+
return this.idToHashIndex.get(e) ?? "";
|
|
234
|
+
}
|
|
224
235
|
makeRoom(e) {
|
|
225
236
|
let n = this.sk, r = this.detail.get(n.id[e]);
|
|
226
237
|
if (r) return r;
|
|
@@ -245,7 +256,7 @@ var c = class {
|
|
|
245
256
|
userData: this.userDataMap.get(n.id[e]) ?? {},
|
|
246
257
|
customLines: {},
|
|
247
258
|
stubs: [],
|
|
248
|
-
hash:
|
|
259
|
+
hash: this.hashFor(n.id[e]),
|
|
249
260
|
env: n.env[e],
|
|
250
261
|
exits: i,
|
|
251
262
|
doors: {},
|
|
@@ -337,4 +348,4 @@ var c = class {
|
|
|
337
348
|
//#endregion
|
|
338
349
|
export { t as a, i, n, a as r, c as t };
|
|
339
350
|
|
|
340
|
-
//# sourceMappingURL=SkeletonMapReader-
|
|
351
|
+
//# sourceMappingURL=SkeletonMapReader-ZPwsqemZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SkeletonMapReader-ZPwsqemZ.js","names":[],"sources":["../src/bigmap/Skeleton.ts","../src/bigmap/PlaneIndex.ts","../src/bigmap/SkeletonMapReader.ts"],"sourcesContent":["/**\n * Compact, fully-resident skeleton of an entire map: one slot per room across\n * parallel typed arrays. This is what lets a multi-million-room map stay in\n * memory — positions/env/area/id as flat columns, no per-room object graph.\n *\n * @public seam — this is the stable contract between skeleton producers and\n * {@link SkeletonMapReader}. Producers include {@link buildSkeleton} (eager,\n * over an already-parsed map) and out-of-process sources such as a Web Worker\n * streaming a Mudlet `.dat` (see `demo/streaming/streaming-worker.ts`). New\n * fields must be optional.\n *\n * Coordinates are RAW Mudlet map space: `y` is stored exactly as it appears in\n * the map data. `SkeletonMapReader` converts to renderer space (negated y)\n * once at construction — producers never need to know renderer conventions.\n *\n * Array ownership transfers to the consumer: `SkeletonMapReader` mutates the\n * arrays in place (the renderer-space conversion). Producers hand over freshly\n * built (or structured-clone-transferred) arrays and must not reuse them.\n */\nexport interface MapSkeleton {\n count: number;\n x: Int32Array;\n y: Int32Array;\n z: Int32Array;\n area: Int32Array;\n env: Int32Array;\n id: Int32Array;\n /** 12 cardinal exit targets per room (row-major: room i → exits[i*12 + d]); -1 = none. */\n exits: Int32Array;\n areaNames: Record<number, string>;\n /** Areas drawn as adjacency grids — cardinal exit lines are suppressed for these. */\n areaGridMode: Record<number, boolean>;\n customEnvColors: Record<number, {r: number; g: number; b: number}>;\n /** Per-room name in slot order (parallel to the typed arrays). */\n names?: string[];\n /** userData only for rooms that have any (sparse). */\n userData?: {id: number; data: Record<string, string>}[];\n /**\n * Fully-materialised rooms that carry visual detail (custom lines, symbols,\n * special exits, doors, stubs, exit locks). The bulk of the map stays\n * skeleton-only; these override the synthesised room for their id.\n * Raw map space, like the arrays.\n */\n detailRooms?: MapData.Room[];\n labels?: MapData.Label[];\n /** Content-hash (`MapData.Room.hash`) → room id, for every room that has one. */\n hashToId?: Record<string, number>;\n}\n\n/**\n * Cardinal exit slots per room, in packing order: room i's exit in direction\n * `SKELETON_DIRS[d]` lives at `exits[i * 12 + d]`.\n */\nexport const SKELETON_DIRS: readonly MapData.direction[] = [\n \"north\", \"northeast\", \"east\", \"southeast\", \"south\", \"southwest\",\n \"west\", \"northwest\", \"up\", \"down\", \"in\", \"out\",\n];\n","import type {ViewportBounds} from \"../types/Settings\";\nimport type {MapSkeleton} from \"./Skeleton\";\n\n/**\n * A (area, z) plane's room slots bucketed into a uniform grid via counting\n * sort. Queries touch only the cells a viewport overlaps, so visiting the\n * rooms in view is O(occupied cells + hits) instead of O(plane).\n */\nexport interface PlaneIndex {\n /** Skeleton slot indices of every room on this plane. */\n indices: Int32Array;\n minX: number;\n minY: number;\n /** Cell size in map units. */\n cs: number;\n cols: number;\n rows: number;\n /** Prefix sums: cell c's members are order[cellStart[c] .. cellStart[c+1]). */\n cellStart: Int32Array;\n order: Int32Array;\n /** Full plane bounds (same coordinate space as the skeleton). */\n bounds: ViewportBounds;\n}\n\n/**\n * Bucket every room of one (area, z) plane into a uniform grid. The cell size\n * targets a 128×128 grid over the plane's extent — coarse enough that the\n * cellStart array stays small, fine enough that a viewport query skips the\n * overwhelming majority of a dense plane.\n */\nexport function buildPlaneIndex(sk: MapSkeleton, areaId: number, z: number): PlaneIndex {\n const tmp: number[] = [];\n let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\n for (let i = 0; i < sk.count; i++) {\n if (sk.area[i] !== areaId || sk.z[i] !== z) continue;\n tmp.push(i);\n const x = sk.x[i], y = sk.y[i];\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n }\n const indices = Int32Array.from(tmp);\n if (indices.length === 0) {\n return {indices, minX: 0, minY: 0, cs: 1, cols: 1, rows: 1,\n cellStart: new Int32Array(2), order: indices,\n bounds: {minX: 0, maxX: 0, minY: 0, maxY: 0}};\n }\n\n const extent = Math.max(maxX - minX, maxY - minY, 1);\n const cs = Math.max(1, Math.ceil(extent / 128));\n const cols = Math.floor((maxX - minX) / cs) + 1;\n const rows = Math.floor((maxY - minY) / cs) + 1;\n const cellOf = (i: number) =>\n Math.floor((sk.y[i] - minY) / cs) * cols + Math.floor((sk.x[i] - minX) / cs);\n\n const counts = new Int32Array(cols * rows);\n for (let k = 0; k < indices.length; k++) counts[cellOf(indices[k])]++;\n const cellStart = new Int32Array(cols * rows + 1);\n for (let c = 0; c < cols * rows; c++) cellStart[c + 1] = cellStart[c] + counts[c];\n const cursor = cellStart.slice(0, cols * rows);\n const order = new Int32Array(indices.length);\n for (let k = 0; k < indices.length; k++) {\n const i = indices[k];\n const c = cellOf(i);\n order[cursor[c]++] = i;\n }\n\n return {indices, minX, minY, cs, cols, rows, cellStart, order, bounds: {minX, maxX, minY, maxY}};\n}\n\nfunction cellRange(p: PlaneIndex, b: ViewportBounds):\n {cx0: number; cx1: number; cy0: number; cy1: number} | null {\n const cx0 = Math.max(0, Math.floor((b.minX - p.minX) / p.cs));\n const cx1 = Math.min(p.cols - 1, Math.floor((b.maxX - p.minX) / p.cs));\n const cy0 = Math.max(0, Math.floor((b.minY - p.minY) / p.cs));\n const cy1 = Math.min(p.rows - 1, Math.floor((b.maxY - p.minY) / p.cs));\n return cx1 < cx0 || cy1 < cy0 ? null : {cx0, cx1, cy0, cy1};\n}\n\n/**\n * Visit the skeleton slot of every room whose centre lies inside `b` — exact\n * (each candidate is bounds-tested), uncapped. This is the hot path for both\n * room materialisation and raster painting; it never allocates.\n */\nexport function forEachInBounds(\n sk: MapSkeleton, p: PlaneIndex, b: ViewportBounds, fn: (i: number) => void,\n): void {\n const r = cellRange(p, b);\n if (!r) return;\n const {cols, cellStart, order} = p;\n for (let cy = r.cy0; cy <= r.cy1; cy++) {\n const base = cy * cols;\n for (let cx = r.cx0; cx <= r.cx1; cx++) {\n const c = base + cx;\n for (let q = cellStart[c]; q < cellStart[c + 1]; q++) {\n const i = order[q];\n if (sk.x[i] >= b.minX && sk.x[i] <= b.maxX &&\n sk.y[i] >= b.minY && sk.y[i] <= b.maxY) fn(i);\n }\n }\n }\n}\n\n/**\n * Cheap UPPER BOUND on the rooms inside `b`: sums the occupancy of every cell\n * the bounds overlap without per-room tests. Cells at the edge contribute\n * rooms that are actually outside — do not use this where exactness matters\n * (an earlier LOD used it as an exact count and flipped modes spuriously).\n */\nexport function countInBounds(p: PlaneIndex, b: ViewportBounds): number {\n const r = cellRange(p, b);\n if (!r) return 0;\n let total = 0;\n for (let cy = r.cy0; cy <= r.cy1; cy++) {\n const base = cy * p.cols;\n for (let cx = r.cx0; cx <= r.cx1; cx++) {\n const c = base + cx;\n total += p.cellStart[c + 1] - p.cellStart[c];\n }\n }\n return total;\n}\n","import type {IMapReader} from \"../reader/MapReader\";\nimport {type IArea, pairLinkExits} from \"../reader/Area\";\nimport type {IPlane} from \"../reader/Plane\";\nimport type IExit from \"../reader/Exit\";\nimport type {ViewportDataSource} from \"../reader/ViewportDataSource\";\nimport type {HashLookupCapable} from \"../reader/HashLookup\";\nimport type {ViewportBounds} from \"../types/Settings\";\nimport type {MapSkeleton} from \"./Skeleton\";\nimport {SKELETON_DIRS} from \"./Skeleton\";\nimport type {PlaneIndex} from \"./PlaneIndex\";\nimport {buildPlaneIndex, countInBounds, forEachInBounds} from \"./PlaneIndex\";\n\nconst INFINITE_VIEWPORT: ViewportBounds = {\n minX: -Infinity, maxX: Infinity, minY: -Infinity, maxY: Infinity,\n};\n\nfunction hslToRgb(h: number, s: number, l: number): [number, number, number] {\n const c = (1 - Math.abs(2 * l - 1)) * s;\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1));\n const m = l - c / 2;\n let r = 0, g = 0, b = 0;\n if (h < 60) [r, g, b] = [c, x, 0];\n else if (h < 120) [r, g, b] = [x, c, 0];\n else if (h < 180) [r, g, b] = [0, c, x];\n else if (h < 240) [r, g, b] = [0, x, c];\n else if (h < 300) [r, g, b] = [x, 0, c];\n else [r, g, b] = [c, 0, x];\n return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)];\n}\n\n/**\n * {@link IMapReader} over a {@link MapSkeleton}: rooms are synthesised on\n * demand from compact typed-array columns instead of held as an object graph,\n * so maps far too large for the concrete `MapReader` stay renderable.\n *\n * It is also a {@link ViewportDataSource}: planes materialise only the rooms\n * inside the current viewport. The interactive backend detects this and pushes\n * padded camera bounds before every build (rebuild-on-pan); standalone use\n * (e.g. `SvgExporter`) works too — the default viewport is infinite, so\n * everything materialises unless {@link setViewport} narrows it.\n *\n * Construction takes ownership of the skeleton: coordinates are converted to\n * renderer space (y negated, matching `MapReader`) in place.\n */\nexport default class SkeletonMapReader implements IMapReader, ViewportDataSource, HashLookupCapable {\n readonly viewportAware = true as const;\n readonly hashLookupCapable = true as const;\n\n private readonly sk: MapSkeleton;\n private viewport: ViewportBounds = INFINITE_VIEWPORT;\n private version = 0;\n\n private readonly planeCache = new Map<string, PlaneIndex>();\n private readonly areaCache = new Map<number, SkeletonArea>();\n private readonly rgbCache = new Map<number, [number, number, number]>();\n private idIndex?: Map<number, number>;\n private idToHashIndex?: Map<number, string>;\n // Per-viewport materialisation cache so getRooms() and getLinkExits()\n // share one synthesized set within a single scene build.\n private readonly visibleCache = new Map<string, MapData.Room[]>();\n\n /** Fully-converted rooms with visual detail, keyed by id (override the skeleton synth). */\n private readonly detail = new Map<number, MapData.Room>();\n private readonly userDataMap = new Map<number, Record<string, string>>();\n\n constructor(skeleton: MapSkeleton) {\n this.sk = skeleton;\n // To renderer space, once: MapReader stores rooms as y = -room.y.\n const y = skeleton.y;\n for (let i = 0; i < skeleton.count; i++) y[i] = -y[i];\n for (const u of skeleton.userData ?? []) this.userDataMap.set(u.id, u.data);\n for (const r of skeleton.detailRooms ?? []) {\n r.y = -r.y;\n if (this.isGrid(r.area)) {\n // gridMode suppresses cardinal exit visuals; keep special exits / lines / symbol.\n r.exits = {} as Record<MapData.direction, number>;\n r.stubs = [];\n }\n this.detail.set(r.id, r);\n }\n }\n\n skeleton(): MapSkeleton {\n return this.sk;\n }\n\n // --- HashLookupCapable ---\n\n getRoomIdByHash(hash: string): number | undefined {\n return this.sk.hashToId?.[hash];\n }\n\n // --- ViewportDataSource ---\n\n setViewport(bounds: ViewportBounds): void {\n const v = this.viewport;\n if (bounds.minX === v.minX && bounds.maxX === v.maxX &&\n bounds.minY === v.minY && bounds.maxY === v.maxY) return;\n this.viewport = {...bounds};\n this.visibleCache.clear();\n this.version++;\n }\n\n getViewport(): ViewportBounds {\n return this.viewport;\n }\n\n getPlaneRoomCount(areaId: number, z: number): number {\n return this.planeIndex(areaId, z).indices.length;\n }\n\n estimateVisibleCount(areaId: number, z: number, bounds: ViewportBounds): number {\n return countInBounds(this.planeIndex(areaId, z), bounds);\n }\n\n forEachInBounds(\n areaId: number, z: number, bounds: ViewportBounds,\n fn: (x: number, y: number, envId: number) => void,\n ): void {\n const sk = this.sk;\n forEachInBounds(sk, this.planeIndex(areaId, z), bounds,\n i => fn(sk.x[i], sk.y[i], sk.env[i]));\n }\n\n // --- IMapReader ---\n\n getArea(areaId: number): IArea {\n let a = this.areaCache.get(areaId);\n if (!a) {\n a = new SkeletonArea(areaId, this);\n this.areaCache.set(areaId, a);\n }\n return a;\n }\n\n getAreas(): IArea[] {\n return [...new Set(Array.from(this.sk.area))].map(id => this.getArea(id));\n }\n\n getRooms(): MapData.Room[] {\n // Never materialise all rooms — nothing in the hot path needs it.\n return [];\n }\n\n getRoom(roomId: number): MapData.Room {\n const d = this.detail.get(roomId);\n if (d) return d;\n if (!this.idIndex) {\n this.idIndex = new Map();\n for (let i = 0; i < this.sk.count; i++) this.idIndex.set(this.sk.id[i], i);\n }\n const i = this.idIndex.get(roomId);\n return i === undefined ? (undefined as unknown as MapData.Room) : this.makeRoom(i);\n }\n\n getColorValue(envId: number): string {\n const [r, g, b] = this.rgb(envId);\n return `rgb(${r},${g},${b})`;\n }\n\n getSymbolColor(envId: number, opacity?: number): string {\n const [r, g, b] = this.rgb(envId);\n const lum = (Math.max(r, g, b) + Math.min(r, g, b)) / 2 / 255;\n // Format matches MapReader.getSymbolColor exactly (incl. the space).\n const c = lum > 0.41 ? '25,25,25' : '225,255,255';\n const o = Math.min(Math.max(opacity ?? 1, 0), 1);\n return o !== 1 ? `rgba(${c}, ${o})` : `rgba(${c})`;\n }\n\n // --- internals shared with SkeletonArea / SkeletonPlane ---\n\n getReaderVersion(): number {\n return this.version;\n }\n\n labelsFor(areaId: number, z: number): MapData.Label[] {\n return (this.sk.labels ?? []).filter(l => l.areaId === areaId && l.Z === z);\n }\n\n isGrid(areaId: number): boolean {\n return !!this.sk.areaGridMode[areaId];\n }\n\n private rgb(envId: number): [number, number, number] {\n let v = this.rgbCache.get(envId);\n if (!v) {\n const c = this.sk.customEnvColors[envId];\n v = c ? [c.r, c.g, c.b] : hslToRgb(((envId * 2654435761) >>> 0) % 360, 0.5, 0.55);\n this.rgbCache.set(envId, v);\n }\n return v;\n }\n\n /** Reverse of `sk.hashToId` (hash -> id), built lazily and once, same pattern as `idIndex`. */\n private hashFor(id: number): string {\n if (!this.sk.hashToId) return '';\n if (!this.idToHashIndex) {\n this.idToHashIndex = new Map();\n for (const hash in this.sk.hashToId) this.idToHashIndex.set(this.sk.hashToId[hash], hash);\n }\n return this.idToHashIndex.get(id) ?? '';\n }\n\n private makeRoom(i: number): MapData.Room {\n const sk = this.sk;\n const d = this.detail.get(sk.id[i]);\n if (d) return d; // full detail (special exits, custom lines, symbol, …)\n // Exits are only meaningful for non-grid areas; grid areas draw rooms as\n // adjacency with no exit lines (and would otherwise emit millions).\n const exits: Record<string, number> = {};\n if (!this.isGrid(sk.area[i])) {\n const base = i * 12;\n for (let dir = 0; dir < 12; dir++) {\n const t = sk.exits[base + dir];\n if (t !== -1) exits[SKELETON_DIRS[dir]] = t;\n }\n }\n return {\n id: sk.id[i],\n area: sk.area[i],\n x: sk.x[i],\n y: sk.y[i],\n z: sk.z[i],\n areaId: String(sk.area[i]),\n weight: 1,\n roomChar: '',\n name: sk.names?.[i] ?? '',\n userData: this.userDataMap.get(sk.id[i]) ?? {},\n customLines: {},\n stubs: [],\n hash: this.hashFor(sk.id[i]),\n env: sk.env[i],\n exits: exits as Record<MapData.direction, number>,\n doors: {},\n specialExits: {},\n };\n }\n\n planeIndex(areaId: number, z: number): PlaneIndex {\n const key = `${areaId}:${z}`;\n let p = this.planeCache.get(key);\n if (!p) {\n p = buildPlaneIndex(this.sk, areaId, z);\n this.planeCache.set(key, p);\n }\n return p;\n }\n\n /**\n * Every room of the plane inside the current viewport — exact, uncapped\n * (the backend's LOD budget bounds how many this can be in vector mode),\n * memoised until the viewport changes.\n */\n visibleRooms(areaId: number, z: number): MapData.Room[] {\n const key = `${areaId}:${z}`;\n let rooms = this.visibleCache.get(key);\n if (!rooms) {\n const collected: MapData.Room[] = [];\n forEachInBounds(this.sk, this.planeIndex(areaId, z), this.viewport,\n i => collected.push(this.makeRoom(i)));\n rooms = collected;\n this.visibleCache.set(key, rooms);\n }\n return rooms;\n }\n}\n\nclass SkeletonArea implements IArea {\n private readonly planeCache = new Map<number, SkeletonPlane>();\n\n constructor(\n private readonly areaId: number,\n private readonly reader: SkeletonMapReader,\n ) {}\n\n getAreaName(): string {\n return this.reader.skeleton().areaNames[this.areaId] ?? `#${this.areaId}`;\n }\n getAreaId(): number {\n return this.areaId;\n }\n getVersion(): number {\n // Viewport-dependent content: the reader bumps its version on every\n // setViewport() with new bounds, which marks all cached-area state stale.\n return this.reader.getReaderVersion();\n }\n getPlane(zIndex: number): IPlane {\n let p = this.planeCache.get(zIndex);\n if (!p) {\n p = new SkeletonPlane(this.areaId, zIndex, this.reader);\n this.planeCache.set(zIndex, p);\n }\n return p;\n }\n getPlanes(): IPlane[] {\n return this.getZLevels().map(z => this.getPlane(z));\n }\n getZLevels(): number[] {\n const sk = this.reader.skeleton();\n const zs = new Set<number>();\n for (let i = 0; i < sk.count; i++) if (sk.area[i] === this.areaId) zs.add(sk.z[i]);\n return [...zs].sort((a, b) => a - b);\n }\n getRooms(): MapData.Room[] {\n return [];\n }\n getFullBounds(): {minX: number; maxX: number; minY: number; maxY: number} {\n const zs = this.getZLevels();\n let b = {minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity};\n for (const z of zs) {\n const pb = this.reader.planeIndex(this.areaId, z).bounds;\n b = {\n minX: Math.min(b.minX, pb.minX), maxX: Math.max(b.maxX, pb.maxX),\n minY: Math.min(b.minY, pb.minY), maxY: Math.max(b.maxY, pb.maxY),\n };\n }\n return b;\n }\n getLinkExits(zIndex: number): IExit[] {\n // Grid areas are adjacency grids — Mudlet draws no exit lines for them\n // (and a dense grid would emit millions).\n if (this.reader.isGrid(this.areaId)) return [];\n const rooms = this.reader.visibleRooms(this.areaId, zIndex);\n if (rooms.length === 0) return [];\n // Pair exits directly over the visible rooms (no throwaway Area/Plane —\n // that also grouped-by-z and computed bounds we never use, pure waste\n // since visibleRooms is already single-z). Exits to off-screen/other-area\n // rooms become one-way and still draw — the pipeline resolves the far\n // endpoint through reader.getRoom().\n return Array.from(pairLinkExits(rooms).values())\n .filter(e => e.zIndex.includes(zIndex));\n }\n}\n\nclass SkeletonPlane implements IPlane {\n constructor(\n private readonly areaId: number,\n private readonly z: number,\n private readonly reader: SkeletonMapReader,\n ) {}\n\n getRooms(): MapData.Room[] {\n return this.reader.visibleRooms(this.areaId, this.z);\n }\n getLabels(): MapData.Label[] {\n return this.reader.labelsFor(this.areaId, this.z);\n }\n getBounds(): {minX: number; maxX: number; minY: number; maxY: number} {\n // Full plane bounds (NOT the viewport) so fitArea frames the whole map.\n // Labels extend the bounds exactly like the concrete Plane's do.\n const b = {...this.reader.planeIndex(this.areaId, this.z).bounds};\n for (const label of this.reader.labelsFor(this.areaId, this.z)) {\n const lx = label.X;\n const ly = -label.Y;\n b.minX = Math.min(b.minX, lx);\n b.maxX = Math.max(b.maxX, lx + label.Width);\n b.minY = Math.min(b.minY, ly);\n b.maxY = Math.max(b.maxY, ly + label.Height);\n }\n return b;\n }\n}\n"],"mappings":"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"}
|
|
@@ -50,6 +50,8 @@ export interface MapSkeleton {
|
|
|
50
50
|
*/
|
|
51
51
|
detailRooms?: MapData.Room[];
|
|
52
52
|
labels?: MapData.Label[];
|
|
53
|
+
/** Content-hash (`MapData.Room.hash`) → room id, for every room that has one. */
|
|
54
|
+
hashToId?: Record<string, number>;
|
|
53
55
|
}
|
|
54
56
|
/**
|
|
55
57
|
* Cardinal exit slots per room, in packing order: room i's exit in direction
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IMapReader } from '../reader/MapReader';
|
|
2
2
|
import { IArea } from '../reader/Area';
|
|
3
3
|
import { ViewportDataSource } from '../reader/ViewportDataSource';
|
|
4
|
+
import { HashLookupCapable } from '../reader/HashLookup';
|
|
4
5
|
import { ViewportBounds } from '../types/Settings';
|
|
5
6
|
import { MapSkeleton } from './Skeleton';
|
|
6
7
|
import { PlaneIndex } from './PlaneIndex';
|
|
@@ -18,8 +19,9 @@ import { PlaneIndex } from './PlaneIndex';
|
|
|
18
19
|
* Construction takes ownership of the skeleton: coordinates are converted to
|
|
19
20
|
* renderer space (y negated, matching `MapReader`) in place.
|
|
20
21
|
*/
|
|
21
|
-
export default class SkeletonMapReader implements IMapReader, ViewportDataSource {
|
|
22
|
+
export default class SkeletonMapReader implements IMapReader, ViewportDataSource, HashLookupCapable {
|
|
22
23
|
readonly viewportAware: true;
|
|
24
|
+
readonly hashLookupCapable: true;
|
|
23
25
|
private readonly sk;
|
|
24
26
|
private viewport;
|
|
25
27
|
private version;
|
|
@@ -27,12 +29,14 @@ export default class SkeletonMapReader implements IMapReader, ViewportDataSource
|
|
|
27
29
|
private readonly areaCache;
|
|
28
30
|
private readonly rgbCache;
|
|
29
31
|
private idIndex?;
|
|
32
|
+
private idToHashIndex?;
|
|
30
33
|
private readonly visibleCache;
|
|
31
34
|
/** Fully-converted rooms with visual detail, keyed by id (override the skeleton synth). */
|
|
32
35
|
private readonly detail;
|
|
33
36
|
private readonly userDataMap;
|
|
34
37
|
constructor(skeleton: MapSkeleton);
|
|
35
38
|
skeleton(): MapSkeleton;
|
|
39
|
+
getRoomIdByHash(hash: string): number | undefined;
|
|
36
40
|
setViewport(bounds: ViewportBounds): void;
|
|
37
41
|
getViewport(): ViewportBounds;
|
|
38
42
|
getPlaneRoomCount(areaId: number, z: number): number;
|
|
@@ -48,6 +52,8 @@ export default class SkeletonMapReader implements IMapReader, ViewportDataSource
|
|
|
48
52
|
labelsFor(areaId: number, z: number): MapData.Label[];
|
|
49
53
|
isGrid(areaId: number): boolean;
|
|
50
54
|
private rgb;
|
|
55
|
+
/** Reverse of `sk.hashToId` (hash -> id), built lazily and once, same pattern as `idIndex`. */
|
|
56
|
+
private hashFor;
|
|
51
57
|
private makeRoom;
|
|
52
58
|
planeIndex(areaId: number, z: number): PlaneIndex;
|
|
53
59
|
/**
|
package/dist/bigmap.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as e, i as t, n, r, t as i } from "./SkeletonMapReader-
|
|
1
|
+
import { a as e, i as t, n, r, t as i } from "./SkeletonMapReader-ZPwsqemZ.js";
|
|
2
2
|
//#region src/bigmap/buildSkeleton.ts
|
|
3
3
|
var a = (e) => !!e && Object.keys(e).length > 0;
|
|
4
4
|
function o(e) {
|
|
@@ -7,7 +7,7 @@ function o(e) {
|
|
|
7
7
|
function s(t, n = [], r = {}) {
|
|
8
8
|
let i = r.isDetailRoom ?? o, s = 0;
|
|
9
9
|
for (let e of t) s += e.rooms.length;
|
|
10
|
-
let c = new Int32Array(s), l = new Int32Array(s), u = new Int32Array(s), d = new Int32Array(s), f = new Int32Array(s), p = new Int32Array(s), m = new Int32Array(s * 12).fill(-1), h = Array(s), g = [], _ = [], v = [], y = {}, b = 0;
|
|
10
|
+
let c = new Int32Array(s), l = new Int32Array(s), u = new Int32Array(s), d = new Int32Array(s), f = new Int32Array(s), p = new Int32Array(s), m = new Int32Array(s * 12).fill(-1), h = Array(s), g = [], _ = [], v = [], y = {}, b = {}, x = 0;
|
|
11
11
|
for (let n of t) {
|
|
12
12
|
let t = parseInt(n.areaId);
|
|
13
13
|
y[t] = n.areaName;
|
|
@@ -16,26 +16,26 @@ function s(t, n = [], r = {}) {
|
|
|
16
16
|
areaId: e.areaId ?? t
|
|
17
17
|
});
|
|
18
18
|
for (let t of n.rooms) {
|
|
19
|
-
c[
|
|
20
|
-
let n =
|
|
19
|
+
c[x] = t.x, l[x] = t.y, u[x] = t.z, d[x] = t.area, f[x] = t.env, p[x] = t.id;
|
|
20
|
+
let n = x * 12;
|
|
21
21
|
for (let r = 0; r < 12; r++) {
|
|
22
22
|
let i = t.exits?.[e[r]];
|
|
23
23
|
i !== void 0 && (m[n + r] = i);
|
|
24
24
|
}
|
|
25
|
-
h[
|
|
25
|
+
h[x] = t.name ?? "", a(t.userData) && g.push({
|
|
26
26
|
id: t.id,
|
|
27
27
|
data: t.userData
|
|
28
|
-
}), i(t) && _.push({ ...t }),
|
|
28
|
+
}), t.hash && (b[t.hash] = t.id), i(t) && _.push({ ...t }), x++;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
let
|
|
32
|
-
for (let e of n)
|
|
31
|
+
let S = {};
|
|
32
|
+
for (let e of n) S[e.envId] = {
|
|
33
33
|
r: e.colors[0],
|
|
34
34
|
g: e.colors[1],
|
|
35
35
|
b: e.colors[2]
|
|
36
36
|
};
|
|
37
|
-
let
|
|
38
|
-
for (let e of r.gridAreas ?? [])
|
|
37
|
+
let C = {};
|
|
38
|
+
for (let e of r.gridAreas ?? []) C[e] = !0;
|
|
39
39
|
return {
|
|
40
40
|
count: s,
|
|
41
41
|
x: c,
|
|
@@ -46,12 +46,13 @@ function s(t, n = [], r = {}) {
|
|
|
46
46
|
id: p,
|
|
47
47
|
exits: m,
|
|
48
48
|
areaNames: y,
|
|
49
|
-
areaGridMode:
|
|
50
|
-
customEnvColors:
|
|
49
|
+
areaGridMode: C,
|
|
50
|
+
customEnvColors: S,
|
|
51
51
|
names: h,
|
|
52
52
|
userData: g,
|
|
53
53
|
detailRooms: _,
|
|
54
|
-
labels: v
|
|
54
|
+
labels: v,
|
|
55
|
+
hashToId: b
|
|
55
56
|
};
|
|
56
57
|
}
|
|
57
58
|
//#endregion
|
package/dist/bigmap.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bigmap.mjs","names":[],"sources":["../src/bigmap/buildSkeleton.ts"],"sourcesContent":["import type {MapSkeleton} from \"./Skeleton\";\nimport {SKELETON_DIRS} from \"./Skeleton\";\n\nconst hasKeys = (o: Record<string, unknown> | undefined | null): boolean =>\n !!o && Object.keys(o).length > 0;\n\n/**\n * Default detail promotion: a room is materialised in full only if it carries\n * visual detail the skeleton columns can't describe. Everything else (plain\n * terrain) renders from the compact columns alone.\n */\nexport function hasVisualDetail(room: MapData.Room): boolean {\n return !!room.roomChar || hasKeys(room.customLines) || hasKeys(room.specialExits) ||\n (room.stubs?.length ?? 0) > 0 || hasKeys(room.doors) || (room.exitLocks?.length ?? 0) > 0;\n}\n\nexport interface BuildSkeletonOptions {\n /** Override which rooms are promoted to full-detail (default: {@link hasVisualDetail}). */\n isDetailRoom?: (room: MapData.Room) => boolean;\n /** Area ids to mark as adjacency grids (cardinal exit lines suppressed). */\n gridAreas?: number[];\n}\n\n/**\n * Eagerly build a {@link MapSkeleton} from an already-parsed map — the same\n * `(map, envs)` inputs the concrete `MapReader` constructor takes. Use this\n * when the map parses fine but is too dense to render as one Konva scene;\n * pair the result with `SkeletonMapReader` and `settings.lodEnabled`.\n *\n * The output is raw map space (room `y` copied verbatim). Detail rooms are\n * cloned, so the input map data is never mutated.\n */\nexport function buildSkeleton(\n map: MapData.Map,\n envs: MapData.Env[] = [],\n options: BuildSkeletonOptions = {},\n): MapSkeleton {\n const isDetail = options.isDetailRoom ?? hasVisualDetail;\n\n let count = 0;\n for (const area of map) count += area.rooms.length;\n\n const x = new Int32Array(count), y = new Int32Array(count), z = new Int32Array(count);\n const areaCol = new Int32Array(count), env = new Int32Array(count), id = new Int32Array(count);\n const exits = new Int32Array(count * 12).fill(-1);\n const names: string[] = new Array(count);\n const userData: {id: number; data: Record<string, string>}[] = [];\n const detailRooms: MapData.Room[] = [];\n const labels: MapData.Label[] = [];\n const areaNames: Record<number, string> = {};\n\n let i = 0;\n for (const area of map) {\n const areaId = parseInt(area.areaId);\n areaNames[areaId] = area.areaName;\n for (const label of area.labels ?? []) {\n labels.push({...label, areaId: label.areaId ?? areaId});\n }\n for (const room of area.rooms) {\n x[i] = room.x;\n y[i] = room.y;\n z[i] = room.z;\n areaCol[i] = room.area;\n env[i] = room.env;\n id[i] = room.id;\n const base = i * 12;\n for (let d = 0; d < 12; d++) {\n const t = room.exits?.[SKELETON_DIRS[d]];\n if (t !== undefined) exits[base + d] = t;\n }\n names[i] = room.name ?? \"\";\n if (hasKeys(room.userData)) userData.push({id: room.id, data: room.userData});\n if (isDetail(room)) detailRooms.push({...room});\n i++;\n }\n }\n\n const customEnvColors: Record<number, {r: number; g: number; b: number}> = {};\n for (const e of envs) {\n customEnvColors[e.envId] = {r: e.colors[0], g: e.colors[1], b: e.colors[2]};\n }\n\n const areaGridMode: Record<number, boolean> = {};\n for (const a of options.gridAreas ?? []) areaGridMode[a] = true;\n\n return {\n count, x, y, z, area: areaCol, env, id, exits,\n areaNames, areaGridMode, customEnvColors,\n names, userData, detailRooms, labels,\n };\n}\n"],"mappings":";;AAGA,IAAM,KAAW,MACb,CAAC,CAAC,KAAK,OAAO,KAAK,EAAE,CAAC,SAAS;AAOnC,SAAgB,EAAgB,GAA6B;AACzD,QAAO,CAAC,CAAC,EAAK,YAAY,EAAQ,EAAK,YAAY,IAAI,EAAQ,EAAK,aAAa,KAC5E,EAAK,OAAO,UAAU,KAAK,KAAK,EAAQ,EAAK,MAAM,KAAK,EAAK,WAAW,UAAU,KAAK;;AAmBhG,SAAgB,EACZ,GACA,IAAsB,EAAE,EACxB,IAAgC,EAAE,EACvB;CACX,IAAM,IAAW,EAAQ,gBAAgB,GAErC,IAAQ;AACZ,MAAK,IAAM,KAAQ,EAAK,MAAS,EAAK,MAAM;CAE5C,IAAM,IAAI,IAAI,WAAW,EAAM,EAAE,IAAI,IAAI,WAAW,EAAM,EAAE,IAAI,IAAI,WAAW,EAAM,EAC/E,IAAU,IAAI,WAAW,EAAM,EAAE,IAAM,IAAI,WAAW,EAAM,EAAE,IAAK,IAAI,WAAW,EAAM,EACxF,IAAQ,IAAI,WAAW,IAAQ,GAAG,CAAC,KAAK,GAAG,EAC3C,IAAsB,MAAM,EAAM,EAClC,IAAyD,EAAE,EAC3D,IAA8B,EAAE,EAChC,IAA0B,EAAE,EAC5B,IAAoC,EAAE,
|
|
1
|
+
{"version":3,"file":"bigmap.mjs","names":[],"sources":["../src/bigmap/buildSkeleton.ts"],"sourcesContent":["import type {MapSkeleton} from \"./Skeleton\";\nimport {SKELETON_DIRS} from \"./Skeleton\";\n\nconst hasKeys = (o: Record<string, unknown> | undefined | null): boolean =>\n !!o && Object.keys(o).length > 0;\n\n/**\n * Default detail promotion: a room is materialised in full only if it carries\n * visual detail the skeleton columns can't describe. Everything else (plain\n * terrain) renders from the compact columns alone.\n */\nexport function hasVisualDetail(room: MapData.Room): boolean {\n return !!room.roomChar || hasKeys(room.customLines) || hasKeys(room.specialExits) ||\n (room.stubs?.length ?? 0) > 0 || hasKeys(room.doors) || (room.exitLocks?.length ?? 0) > 0;\n}\n\nexport interface BuildSkeletonOptions {\n /** Override which rooms are promoted to full-detail (default: {@link hasVisualDetail}). */\n isDetailRoom?: (room: MapData.Room) => boolean;\n /** Area ids to mark as adjacency grids (cardinal exit lines suppressed). */\n gridAreas?: number[];\n}\n\n/**\n * Eagerly build a {@link MapSkeleton} from an already-parsed map — the same\n * `(map, envs)` inputs the concrete `MapReader` constructor takes. Use this\n * when the map parses fine but is too dense to render as one Konva scene;\n * pair the result with `SkeletonMapReader` and `settings.lodEnabled`.\n *\n * The output is raw map space (room `y` copied verbatim). Detail rooms are\n * cloned, so the input map data is never mutated.\n */\nexport function buildSkeleton(\n map: MapData.Map,\n envs: MapData.Env[] = [],\n options: BuildSkeletonOptions = {},\n): MapSkeleton {\n const isDetail = options.isDetailRoom ?? hasVisualDetail;\n\n let count = 0;\n for (const area of map) count += area.rooms.length;\n\n const x = new Int32Array(count), y = new Int32Array(count), z = new Int32Array(count);\n const areaCol = new Int32Array(count), env = new Int32Array(count), id = new Int32Array(count);\n const exits = new Int32Array(count * 12).fill(-1);\n const names: string[] = new Array(count);\n const userData: {id: number; data: Record<string, string>}[] = [];\n const detailRooms: MapData.Room[] = [];\n const labels: MapData.Label[] = [];\n const areaNames: Record<number, string> = {};\n const hashToId: Record<string, number> = {};\n\n let i = 0;\n for (const area of map) {\n const areaId = parseInt(area.areaId);\n areaNames[areaId] = area.areaName;\n for (const label of area.labels ?? []) {\n labels.push({...label, areaId: label.areaId ?? areaId});\n }\n for (const room of area.rooms) {\n x[i] = room.x;\n y[i] = room.y;\n z[i] = room.z;\n areaCol[i] = room.area;\n env[i] = room.env;\n id[i] = room.id;\n const base = i * 12;\n for (let d = 0; d < 12; d++) {\n const t = room.exits?.[SKELETON_DIRS[d]];\n if (t !== undefined) exits[base + d] = t;\n }\n names[i] = room.name ?? \"\";\n if (hasKeys(room.userData)) userData.push({id: room.id, data: room.userData});\n if (room.hash) hashToId[room.hash] = room.id;\n if (isDetail(room)) detailRooms.push({...room});\n i++;\n }\n }\n\n const customEnvColors: Record<number, {r: number; g: number; b: number}> = {};\n for (const e of envs) {\n customEnvColors[e.envId] = {r: e.colors[0], g: e.colors[1], b: e.colors[2]};\n }\n\n const areaGridMode: Record<number, boolean> = {};\n for (const a of options.gridAreas ?? []) areaGridMode[a] = true;\n\n return {\n count, x, y, z, area: areaCol, env, id, exits,\n areaNames, areaGridMode, customEnvColors,\n names, userData, detailRooms, labels, hashToId,\n };\n}\n"],"mappings":";;AAGA,IAAM,KAAW,MACb,CAAC,CAAC,KAAK,OAAO,KAAK,EAAE,CAAC,SAAS;AAOnC,SAAgB,EAAgB,GAA6B;AACzD,QAAO,CAAC,CAAC,EAAK,YAAY,EAAQ,EAAK,YAAY,IAAI,EAAQ,EAAK,aAAa,KAC5E,EAAK,OAAO,UAAU,KAAK,KAAK,EAAQ,EAAK,MAAM,KAAK,EAAK,WAAW,UAAU,KAAK;;AAmBhG,SAAgB,EACZ,GACA,IAAsB,EAAE,EACxB,IAAgC,EAAE,EACvB;CACX,IAAM,IAAW,EAAQ,gBAAgB,GAErC,IAAQ;AACZ,MAAK,IAAM,KAAQ,EAAK,MAAS,EAAK,MAAM;CAE5C,IAAM,IAAI,IAAI,WAAW,EAAM,EAAE,IAAI,IAAI,WAAW,EAAM,EAAE,IAAI,IAAI,WAAW,EAAM,EAC/E,IAAU,IAAI,WAAW,EAAM,EAAE,IAAM,IAAI,WAAW,EAAM,EAAE,IAAK,IAAI,WAAW,EAAM,EACxF,IAAQ,IAAI,WAAW,IAAQ,GAAG,CAAC,KAAK,GAAG,EAC3C,IAAsB,MAAM,EAAM,EAClC,IAAyD,EAAE,EAC3D,IAA8B,EAAE,EAChC,IAA0B,EAAE,EAC5B,IAAoC,EAAE,EACtC,IAAmC,EAAE,EAEvC,IAAI;AACR,MAAK,IAAM,KAAQ,GAAK;EACpB,IAAM,IAAS,SAAS,EAAK,OAAO;AACpC,IAAU,KAAU,EAAK;AACzB,OAAK,IAAM,KAAS,EAAK,UAAU,EAAE,CACjC,GAAO,KAAK;GAAC,GAAG;GAAO,QAAQ,EAAM,UAAU;GAAO,CAAC;AAE3D,OAAK,IAAM,KAAQ,EAAK,OAAO;AAM3B,GALA,EAAE,KAAK,EAAK,GACZ,EAAE,KAAK,EAAK,GACZ,EAAE,KAAK,EAAK,GACZ,EAAQ,KAAK,EAAK,MAClB,EAAI,KAAK,EAAK,KACd,EAAG,KAAK,EAAK;GACb,IAAM,IAAO,IAAI;AACjB,QAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;IACzB,IAAM,IAAI,EAAK,QAAQ,EAAc;AACrC,IAAI,MAAM,KAAA,MAAW,EAAM,IAAO,KAAK;;AAM3C,GAJA,EAAM,KAAK,EAAK,QAAQ,IACpB,EAAQ,EAAK,SAAS,IAAE,EAAS,KAAK;IAAC,IAAI,EAAK;IAAI,MAAM,EAAK;IAAS,CAAC,EACzE,EAAK,SAAM,EAAS,EAAK,QAAQ,EAAK,KACtC,EAAS,EAAK,IAAE,EAAY,KAAK,EAAC,GAAG,GAAK,CAAC,EAC/C;;;CAIR,IAAM,IAAqE,EAAE;AAC7E,MAAK,IAAM,KAAK,EACZ,GAAgB,EAAE,SAAS;EAAC,GAAG,EAAE,OAAO;EAAI,GAAG,EAAE,OAAO;EAAI,GAAG,EAAE,OAAO;EAAG;CAG/E,IAAM,IAAwC,EAAE;AAChD,MAAK,IAAM,KAAK,EAAQ,aAAa,EAAE,CAAE,GAAa,KAAK;AAE3D,QAAO;EACH;EAAO;EAAG;EAAG;EAAG,MAAM;EAAS;EAAK;EAAI;EACxC;EAAW;EAAc;EACzB;EAAO;EAAU;EAAa;EAAQ;EACzC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MudletRoom } from 'mudlet-map-binary-reader';
|
|
1
2
|
import { IMapReader } from '../reader/MapReader';
|
|
2
3
|
import { MapSkeleton } from '../bigmap/Skeleton';
|
|
3
4
|
export type LoadMode = "auto" | "plain" | "streaming";
|
|
@@ -14,6 +15,18 @@ export interface LoadMudletMapOptions {
|
|
|
14
15
|
threshold?: number;
|
|
15
16
|
/** Invoked periodically (every 200,000 rooms) — the streaming path only; the full parse has no per-room hook. */
|
|
16
17
|
onProgress?: (roomsRead: number, total: number) => void;
|
|
18
|
+
/**
|
|
19
|
+
* Streaming path only (the full parse always materialises every field —
|
|
20
|
+
* there's no skeleton to promote into). Overrides which rooms are
|
|
21
|
+
* promoted to a fully materialised `MapData.Room` in `MapSkeleton.detailRooms`;
|
|
22
|
+
* default `hasExtraDetail` (doors/customLines/specialExits/stubs/exitLocks/
|
|
23
|
+
* non-default weight/exitWeights — fields the compact columns can't encode
|
|
24
|
+
* at all). Pass `() => true` to promote every room — correct at any size,
|
|
25
|
+
* but memory-equivalent to a full `MapReader` object graph for the room
|
|
26
|
+
* data itself, so it only makes sense when the map's total room count
|
|
27
|
+
* doesn't actually need the streaming path's memory savings.
|
|
28
|
+
*/
|
|
29
|
+
isDetailRoom?: (room: MudletRoom) => boolean;
|
|
17
30
|
}
|
|
18
31
|
/**
|
|
19
32
|
* Portable, structured-clone/transfer-safe parse result — no live reader yet
|
package/dist/binary.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as e } from "./MapReader-BeVNpm6y.js";
|
|
2
|
-
import { t } from "./SkeletonMapReader-
|
|
2
|
+
import { t } from "./SkeletonMapReader-ZPwsqemZ.js";
|
|
3
3
|
import { convertLabel as n, convertRoom as r, readMapFromBuffer as i, readerExport as a, streamRooms as o } from "mudlet-map-binary-reader";
|
|
4
4
|
//#region src/binary/convert.ts
|
|
5
5
|
function s(e, t, n = "") {
|
|
@@ -149,64 +149,65 @@ function _(e) {
|
|
|
149
149
|
envs: n
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
|
-
function v(e, t, i, a) {
|
|
153
|
-
let
|
|
154
|
-
for (let e in t.mpRoomDbHashToRoomId)
|
|
155
|
-
let
|
|
152
|
+
function v(e, t, i, a, l = u) {
|
|
153
|
+
let d = new Int32Array(i), p = new Int32Array(i), m = new Int32Array(i), _ = new Int32Array(i), v = new Int32Array(i), y = new Int32Array(i), b = new Int32Array(i * 12).fill(-1), x = Array(i), S = [], C = [], w = {};
|
|
154
|
+
for (let e in t.mpRoomDbHashToRoomId) w[t.mpRoomDbHashToRoomId[e]] = e;
|
|
155
|
+
let T = () => typeof performance < "u" ? performance.now() : Date.now(), E = T(), D = 0;
|
|
156
156
|
o(e, (e, t) => {
|
|
157
|
-
|
|
158
|
-
let n =
|
|
159
|
-
for (let e = 0; e < 12; e++)
|
|
160
|
-
if (
|
|
157
|
+
d[D] = t.x, p[D] = t.y, m[D] = t.z, _[D] = t.area, v[D] = t.environment, y[D] = e;
|
|
158
|
+
let n = D * 12;
|
|
159
|
+
for (let e = 0; e < 12; e++) b[n + e] = t[f[e]];
|
|
160
|
+
if (x[D] = t.name ?? "", g(t.userData) && S.push({
|
|
161
161
|
id: e,
|
|
162
162
|
data: t.userData
|
|
163
|
-
}),
|
|
164
|
-
let n = r(e, t,
|
|
165
|
-
|
|
163
|
+
}), l(t)) {
|
|
164
|
+
let n = r(e, t, w[e]);
|
|
165
|
+
C.push(s(n, String(t.area), w[e]));
|
|
166
166
|
}
|
|
167
|
-
if (
|
|
168
|
-
let e =
|
|
169
|
-
e -
|
|
167
|
+
if (D++, a) {
|
|
168
|
+
let e = T();
|
|
169
|
+
e - E >= 80 && (E = e, a(D, i));
|
|
170
170
|
}
|
|
171
|
-
}), a?.(
|
|
172
|
-
let
|
|
171
|
+
}), a?.(D, i);
|
|
172
|
+
let O = {};
|
|
173
173
|
for (let e in t.mCustomEnvColors ?? {}) {
|
|
174
174
|
let n = t.mCustomEnvColors[e];
|
|
175
|
-
|
|
175
|
+
O[e] = {
|
|
176
176
|
r: n.r,
|
|
177
177
|
g: n.g,
|
|
178
178
|
b: n.b
|
|
179
179
|
};
|
|
180
180
|
}
|
|
181
|
-
let
|
|
181
|
+
let k = [];
|
|
182
182
|
for (let e in t.labels ?? {}) {
|
|
183
183
|
let r = Number(e);
|
|
184
|
-
for (let e of t.labels[r])
|
|
184
|
+
for (let e of t.labels[r]) k.push(c(n(e), r));
|
|
185
185
|
}
|
|
186
186
|
return {
|
|
187
187
|
kind: "skeleton",
|
|
188
188
|
skeleton: {
|
|
189
|
-
count:
|
|
190
|
-
x:
|
|
191
|
-
y:
|
|
192
|
-
z:
|
|
193
|
-
area:
|
|
194
|
-
env:
|
|
195
|
-
id:
|
|
196
|
-
exits:
|
|
189
|
+
count: D,
|
|
190
|
+
x: d,
|
|
191
|
+
y: p,
|
|
192
|
+
z: m,
|
|
193
|
+
area: _,
|
|
194
|
+
env: v,
|
|
195
|
+
id: y,
|
|
196
|
+
exits: b,
|
|
197
197
|
areaNames: t.areaNames ?? {},
|
|
198
198
|
areaGridMode: h(t),
|
|
199
|
-
customEnvColors:
|
|
200
|
-
names:
|
|
201
|
-
userData:
|
|
202
|
-
detailRooms:
|
|
203
|
-
labels:
|
|
199
|
+
customEnvColors: O,
|
|
200
|
+
names: x,
|
|
201
|
+
userData: S,
|
|
202
|
+
detailRooms: C,
|
|
203
|
+
labels: k,
|
|
204
|
+
hashToId: t.mpRoomDbHashToRoomId ?? {}
|
|
204
205
|
}
|
|
205
206
|
};
|
|
206
207
|
}
|
|
207
208
|
function y(e, t = {}) {
|
|
208
|
-
let { mode: n = "auto", threshold: r = 5e4, onProgress: i } = t, { header:
|
|
209
|
-
return (n === "auto" ?
|
|
209
|
+
let { mode: n = "auto", threshold: r = 5e4, onProgress: i, isDetailRoom: a } = t, { header: o, total: s } = m(e);
|
|
210
|
+
return (n === "auto" ? s > r ? "streaming" : "plain" : n) === "plain" ? _(e) : v(e, o, s, i, a);
|
|
210
211
|
}
|
|
211
212
|
function b(n) {
|
|
212
213
|
return n.kind === "plain" ? new e(n.map, n.envs) : new t(n.skeleton);
|
package/dist/binary.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"binary.mjs","names":[],"sources":["../src/binary/convert.ts","../src/binary/BinaryMapReader.ts","../src/binary/loadMudletMap.ts"],"sourcesContent":["import type {RendererRoom, RendererLabel, MudletRoom} from \"mudlet-map-binary-reader\";\n\n/**\n * RendererRoom -> MapData.Room: fills the fields RendererRoom leaves optional\n * (`env`/`roomChar` are only set upstream when truthy; `hash` is resolved\n * separately from the header's `mpRoomDbHashToRoomId` index since streamRooms\n * doesn't hydrate it per-room). `customLines` colour drops `alpha` upstream —\n * SpecialExitStyle only ever reads r/g/b, so 255 (opaque) is a safe filler,\n * never a real behavioural difference.\n */\nexport function toMapRoom(r: RendererRoom, areaId: string, hash = \"\"): MapData.Room {\n const customLines: Record<string, MapData.Line> = {};\n for (const key in r.customLines) {\n const line = r.customLines[key];\n customLines[key] = {\n points: line.points,\n attributes: {\n color: {alpha: 255, r: line.attributes.color.r, g: line.attributes.color.g, b: line.attributes.color.b},\n style: line.attributes.style,\n arrow: line.attributes.arrow,\n },\n };\n }\n return {\n id: r.id,\n area: r.area,\n x: r.x,\n y: r.y,\n z: r.z,\n areaId,\n weight: r.weight,\n roomChar: r.roomChar ?? \"\",\n name: r.name,\n userData: r.userData,\n customLines,\n stubs: r.stubs,\n hash: r.hash ?? hash,\n env: r.env ?? 0,\n exits: r.exits as Record<MapData.direction, number>,\n // Mudlet door states are always 1|2|3 at runtime; MudletRoom types them as plain number.\n doors: r.doors as Record<string, 1 | 2 | 3>,\n specialExits: r.specialExits,\n exitLocks: r.exitLocks,\n exitWeights: r.exitWeights,\n mSpecialExitLocks: r.mSpecialExitLocks,\n };\n}\n\n/** RendererLabel -> MapData.Label; areaId/labelId are stamped from the enclosing header.labels[areaId] key when omitted. */\nexport function toMapLabel(l: RendererLabel, fallbackAreaId: number): MapData.Label {\n return {\n labelId: l.labelId ?? l.id,\n areaId: l.areaId ?? fallbackAreaId,\n pixMap: l.pixMap || undefined,\n X: l.X, Y: l.Y, Z: l.Z, Width: l.Width, Height: l.Height, Text: l.Text,\n FgColor: l.FgColor, BgColor: l.BgColor,\n noScaling: l.noScaling, showOnTop: l.showOnTop,\n };\n}\n\nconst hasKeys = (o: Record<string, unknown> | undefined | null): boolean => !!o && Object.keys(o).length > 0;\n\n/**\n * A room carries non-default metadata the skeleton's compact columns can't\n * encode (or that a plain visual-detail check would silently drop —\n * `weight`, `exitWeights`) and must be promoted to a fully materialised\n * MapData.Room.\n */\nexport function hasExtraDetail(room: MudletRoom): boolean {\n return !!room.symbol || hasKeys(room.customLines) || hasKeys(room.mSpecialExits) ||\n (room.stubs?.length ?? 0) > 0 || hasKeys(room.doors) || (room.exitLocks?.length ?? 0) > 0 ||\n room.weight !== 1 || hasKeys(room.exitWeights);\n}\n","import {\n type MudletMap,\n readerExport,\n readMapFromBuffer,\n} from \"mudlet-map-binary-reader\";\nimport MapReader, {type IMapReader} from \"../reader/MapReader\";\nimport type {IArea} from \"../reader/Area\";\nimport {toMapRoom, toMapLabel} from \"./convert\";\n\n/**\n * {@link IMapReader} implementation backed by a parsed Mudlet binary map.\n *\n * Convenience wrapper around the existing {@link MapReader} that converts a\n * {@link MudletMap} (the in-memory binary model from\n * `mudlet-map-binary-reader`) into the renderer's `{mapData, colors}` shape\n * via `readerExport()`, then forwards every `IMapReader` call to the wrapped\n * reader.\n *\n * Useful for downstream apps (the upstream Mudix client is one) that already\n * own binary `.dat` bytes and want a single object they can hand to\n * {@link MapRenderer} without staging a separate JSON export step.\n *\n * `mudlet-map-binary-reader` is a `peerDependency`; consumers who do not use\n * binary maps never pay the bundle cost.\n */\nexport default class BinaryMapReader implements IMapReader {\n private readonly reader: MapReader;\n\n /** Build directly from a parsed {@link MudletMap}. */\n constructor(model: MudletMap) {\n const {mapData, colors} = readerExport(model);\n const map: MapData.Map = mapData.map(a => ({\n areaName: a.areaName,\n areaId: a.areaId,\n rooms: a.rooms.map(r => toMapRoom(r, a.areaId, r.hash)),\n labels: a.labels.map(l => toMapLabel(l, parseInt(a.areaId))),\n }));\n this.reader = new MapReader(map, colors);\n }\n\n /**\n * Parse the given binary map buffer and construct a reader from it.\n * Accepts any `Uint8Array`: in Node pass `fs.readFileSync(path)`; in the\n * browser pass `new Uint8Array(await file.arrayBuffer())`. The underlying\n * `mudlet-map-binary-reader` (>=1.0.0) is browser-safe and needs no\n * Node `Buffer` polyfill.\n */\n static fromBuffer(buf: Parameters<typeof readMapFromBuffer>[0]): BinaryMapReader {\n return new BinaryMapReader(readMapFromBuffer(buf));\n }\n\n // --- IMapReader forwarding ---\n\n getArea(areaId: number): IArea {\n return this.reader.getArea(areaId);\n }\n\n getAreas(): IArea[] {\n return this.reader.getAreas();\n }\n\n getRooms(): MapData.Room[] {\n return this.reader.getRooms();\n }\n\n getRoom(roomId: number): MapData.Room {\n return this.reader.getRoom(roomId);\n }\n\n getColorValue(envId: number): string {\n return this.reader.getColorValue(envId);\n }\n\n getSymbolColor(envId: number, opacity?: number): string {\n return this.reader.getSymbolColor(envId, opacity);\n }\n}\n","import {streamRooms, convertRoom, convertLabel, readMapFromBuffer, readerExport} from \"mudlet-map-binary-reader\";\nimport type {MudletMapHeader} from \"mudlet-map-binary-reader\";\nimport MapReader, {type IMapReader} from \"../reader/MapReader\";\nimport SkeletonMapReader from \"../bigmap/SkeletonMapReader\";\nimport type {MapSkeleton} from \"../bigmap/Skeleton\";\nimport {toMapRoom, toMapLabel, hasExtraDetail} from \"./convert\";\n\n// Cardinal exit fields on MudletRoom, in the order packed into the skeleton\n// (12 slots per room; -1 = no exit).\nconst DIRS = [\n \"north\", \"northeast\", \"east\", \"southeast\", \"south\", \"southwest\",\n \"west\", \"northwest\", \"up\", \"down\", \"in\", \"out\",\n] as const;\n\nexport type LoadMode = \"auto\" | \"plain\" | \"streaming\";\n\nexport interface LoadMudletMapOptions {\n /** 'auto' (default) picks 'plain' or 'streaming' from the header's total room count vs. {@link threshold}. */\n mode?: LoadMode;\n /**\n * Total-room-count cutoff for 'auto' (default 50,000). Below it: a full\n * parse (every field, no skeleton overhead — a `MapSkeleton` has no\n * upside for a map small enough to hold as an object graph). Above it:\n * stream straight into a `MapSkeleton` — the full parsed map and the\n * skeleton are never both resident in memory at once.\n */\n threshold?: number;\n /** Invoked periodically (every 200,000 rooms) — the streaming path only; the full parse has no per-room hook. */\n onProgress?: (roomsRead: number, total: number) => void;\n}\n\n/**\n * Portable, structured-clone/transfer-safe parse result — no live reader yet\n * (see {@link readerFromLoadedMap}). Splitting parse from materialisation\n * lets a Web Worker run the (potentially expensive) parse and hand back only\n * the result; the skeleton kind's typed arrays should be passed as\n * transferables (`[skeleton.x.buffer, skeleton.y.buffer, ...]`).\n */\nexport type LoadedMudletMap =\n | {kind: \"plain\"; map: MapData.Map; envs: MapData.Env[]}\n | {kind: \"skeleton\"; skeleton: MapSkeleton};\n\nclass HeaderAbort extends Error {}\n\n/** Reads only the header (areas/labels/colours), aborting before the — potentially huge — rooms blob. */\nfunction peekHeader(bytes: Uint8Array): {header: MudletMapHeader; total: number} {\n let result: {header: MudletMapHeader; total: number} | undefined;\n try {\n streamRooms(bytes, () => {\n throw new HeaderAbort();\n }, (hdr) => {\n let total = 0;\n for (const k in hdr.areas ?? {}) total += hdr.areas[k as unknown as number].rooms.length;\n result = {header: hdr, total};\n throw new HeaderAbort();\n });\n } catch (e) {\n if (!(e instanceof HeaderAbort)) throw e;\n }\n if (!result) throw new Error(\"failed to decode map header\");\n return result;\n}\n\nfunction buildAreaGridMode(header: MudletMapHeader): Record<number, boolean> {\n const areaGridMode: Record<number, boolean> = {};\n for (const k in header.areas ?? {}) {\n areaGridMode[k as unknown as number] = header.areas[k as unknown as number].gridMode;\n }\n return areaGridMode;\n}\n\nconst hasKeys = (o: Record<string, unknown> | undefined | null): boolean => !!o && Object.keys(o).length > 0;\n\nfunction parsePlain(bytes: Uint8Array): {kind: \"plain\"; map: MapData.Map; envs: MapData.Env[]} {\n const model = readMapFromBuffer(bytes);\n const {mapData, colors} = readerExport(model);\n const map: MapData.Map = mapData.map(a => ({\n areaName: a.areaName,\n areaId: a.areaId,\n // readerExport already resolves each room's hash — pass-through, no reverse lookup needed here.\n rooms: a.rooms.map(r => toMapRoom(r, a.areaId, r.hash)),\n labels: a.labels.map(l => toMapLabel(l, parseInt(a.areaId))),\n }));\n return {kind: \"plain\", map, envs: colors};\n}\n\nfunction parseStreaming(\n bytes: Uint8Array, header: MudletMapHeader, total: number,\n onProgress?: (roomsRead: number, total: number) => void,\n): {kind: \"skeleton\"; skeleton: MapSkeleton} {\n const x = new Int32Array(total), y = new Int32Array(total), z = new Int32Array(total);\n const area = new Int32Array(total), env = new Int32Array(total), id = new Int32Array(total);\n const exits = new Int32Array(total * 12).fill(-1);\n const names: string[] = new Array(total);\n const userData: {id: number; data: Record<string, string>}[] = [];\n const detailRooms: MapData.Room[] = [];\n\n const roomIdToHash: Record<number, string> = {};\n for (const hash in header.mpRoomDbHashToRoomId) {\n roomIdToHash[header.mpRoomDbHashToRoomId[hash]] = hash;\n }\n\n // Time-based throttle (not every-N-rooms): gives smooth, frequent updates\n // regardless of map size or per-room cost, instead of a handful of big\n // jumps on a huge map or none at all on a small one.\n const now = () => (typeof performance !== \"undefined\" ? performance.now() : Date.now());\n let lastReport = now();\n let i = 0;\n streamRooms(bytes, (roomId, room) => {\n x[i] = room.x;\n y[i] = room.y; // RAW map space — SkeletonMapReader converts to renderer space\n z[i] = room.z;\n area[i] = room.area;\n env[i] = room.environment;\n id[i] = roomId;\n const base = i * 12;\n for (let d = 0; d < 12; d++) exits[base + d] = room[DIRS[d]] as number;\n\n // Name + userData for EVERY room (compact columns), so any room — drawn\n // or looked up by id — is complete, without promoting it to a heavy object.\n names[i] = room.name ?? \"\";\n if (hasKeys(room.userData)) userData.push({id: roomId, data: room.userData});\n\n if (hasExtraDetail(room)) {\n const rr = convertRoom(roomId, room, roomIdToHash[roomId]);\n detailRooms.push(toMapRoom(rr, String(room.area), roomIdToHash[roomId]));\n }\n i++;\n if (onProgress) {\n const t = now();\n if (t - lastReport >= 80) {\n lastReport = t;\n onProgress(i, total);\n }\n }\n });\n onProgress?.(i, total); // final tick — guarantees a 100% callback even on a fast/small stream\n\n const customEnvColors: Record<number, {r: number; g: number; b: number}> = {};\n for (const k in header.mCustomEnvColors ?? {}) {\n const c = header.mCustomEnvColors[k as unknown as number];\n customEnvColors[k as unknown as number] = {r: c.r, g: c.g, b: c.b};\n }\n\n const labels: MapData.Label[] = [];\n for (const k in header.labels ?? {}) {\n const areaId = Number(k);\n for (const lb of header.labels[areaId]) {\n labels.push(toMapLabel(convertLabel(lb), areaId));\n }\n }\n\n return {\n kind: \"skeleton\",\n skeleton: {\n count: i, x, y, z, area, env, id, exits,\n areaNames: header.areaNames ?? {}, areaGridMode: buildAreaGridMode(header), customEnvColors,\n names, userData, detailRooms, labels,\n },\n };\n}\n\n/**\n * Parse a Mudlet binary `.dat` buffer, picking the loading strategy from its\n * total room count (see {@link LoadMudletMapOptions}). Returns portable data\n * only — call {@link readerFromLoadedMap} to get a live `IMapReader`, or use\n * {@link loadMudletMap} to do both in one call.\n */\nexport function parseMudletMap(bytes: Uint8Array, options: LoadMudletMapOptions = {}): LoadedMudletMap {\n const {mode = \"auto\", threshold = 50_000, onProgress} = options;\n const {header, total} = peekHeader(bytes);\n const effectiveMode = mode === \"auto\" ? (total > threshold ? \"streaming\" : \"plain\") : mode;\n return effectiveMode === \"plain\" ? parsePlain(bytes) : parseStreaming(bytes, header, total, onProgress);\n}\n\n/** Build the live {@link IMapReader} for data produced by {@link parseMudletMap}. */\nexport function readerFromLoadedMap(loaded: LoadedMudletMap): IMapReader {\n return loaded.kind === \"plain\" ? new MapReader(loaded.map, loaded.envs) : new SkeletonMapReader(loaded.skeleton);\n}\n\n/**\n * Convenience: parse and materialise in one call, on the current thread.\n * `mudlet-map-binary-reader` is a peer dependency; consumers who don't call\n * this never pay the bundle cost. For very large maps, prefer running\n * {@link parseMudletMap} in a Web Worker and calling {@link readerFromLoadedMap}\n * with its result on the main thread.\n */\nexport function loadMudletMap(bytes: Uint8Array, options?: LoadMudletMapOptions): IMapReader {\n return readerFromLoadedMap(parseMudletMap(bytes, options));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"binary.mjs","names":[],"sources":["../src/binary/convert.ts","../src/binary/BinaryMapReader.ts","../src/binary/loadMudletMap.ts"],"sourcesContent":["import type {RendererRoom, RendererLabel, MudletRoom} from \"mudlet-map-binary-reader\";\n\n/**\n * RendererRoom -> MapData.Room: fills the fields RendererRoom leaves optional\n * (`env`/`roomChar` are only set upstream when truthy; `hash` is resolved\n * separately from the header's `mpRoomDbHashToRoomId` index since streamRooms\n * doesn't hydrate it per-room). `customLines` colour drops `alpha` upstream —\n * SpecialExitStyle only ever reads r/g/b, so 255 (opaque) is a safe filler,\n * never a real behavioural difference.\n */\nexport function toMapRoom(r: RendererRoom, areaId: string, hash = \"\"): MapData.Room {\n const customLines: Record<string, MapData.Line> = {};\n for (const key in r.customLines) {\n const line = r.customLines[key];\n customLines[key] = {\n points: line.points,\n attributes: {\n color: {alpha: 255, r: line.attributes.color.r, g: line.attributes.color.g, b: line.attributes.color.b},\n style: line.attributes.style,\n arrow: line.attributes.arrow,\n },\n };\n }\n return {\n id: r.id,\n area: r.area,\n x: r.x,\n y: r.y,\n z: r.z,\n areaId,\n weight: r.weight,\n roomChar: r.roomChar ?? \"\",\n name: r.name,\n userData: r.userData,\n customLines,\n stubs: r.stubs,\n hash: r.hash ?? hash,\n env: r.env ?? 0,\n exits: r.exits as Record<MapData.direction, number>,\n // Mudlet door states are always 1|2|3 at runtime; MudletRoom types them as plain number.\n doors: r.doors as Record<string, 1 | 2 | 3>,\n specialExits: r.specialExits,\n exitLocks: r.exitLocks,\n exitWeights: r.exitWeights,\n mSpecialExitLocks: r.mSpecialExitLocks,\n };\n}\n\n/** RendererLabel -> MapData.Label; areaId/labelId are stamped from the enclosing header.labels[areaId] key when omitted. */\nexport function toMapLabel(l: RendererLabel, fallbackAreaId: number): MapData.Label {\n return {\n labelId: l.labelId ?? l.id,\n areaId: l.areaId ?? fallbackAreaId,\n pixMap: l.pixMap || undefined,\n X: l.X, Y: l.Y, Z: l.Z, Width: l.Width, Height: l.Height, Text: l.Text,\n FgColor: l.FgColor, BgColor: l.BgColor,\n noScaling: l.noScaling, showOnTop: l.showOnTop,\n };\n}\n\nconst hasKeys = (o: Record<string, unknown> | undefined | null): boolean => !!o && Object.keys(o).length > 0;\n\n/**\n * A room carries non-default metadata the skeleton's compact columns can't\n * encode (or that a plain visual-detail check would silently drop —\n * `weight`, `exitWeights`) and must be promoted to a fully materialised\n * MapData.Room.\n */\nexport function hasExtraDetail(room: MudletRoom): boolean {\n return !!room.symbol || hasKeys(room.customLines) || hasKeys(room.mSpecialExits) ||\n (room.stubs?.length ?? 0) > 0 || hasKeys(room.doors) || (room.exitLocks?.length ?? 0) > 0 ||\n room.weight !== 1 || hasKeys(room.exitWeights);\n}\n","import {\n type MudletMap,\n readerExport,\n readMapFromBuffer,\n} from \"mudlet-map-binary-reader\";\nimport MapReader, {type IMapReader} from \"../reader/MapReader\";\nimport type {IArea} from \"../reader/Area\";\nimport {toMapRoom, toMapLabel} from \"./convert\";\n\n/**\n * {@link IMapReader} implementation backed by a parsed Mudlet binary map.\n *\n * Convenience wrapper around the existing {@link MapReader} that converts a\n * {@link MudletMap} (the in-memory binary model from\n * `mudlet-map-binary-reader`) into the renderer's `{mapData, colors}` shape\n * via `readerExport()`, then forwards every `IMapReader` call to the wrapped\n * reader.\n *\n * Useful for downstream apps (the upstream Mudix client is one) that already\n * own binary `.dat` bytes and want a single object they can hand to\n * {@link MapRenderer} without staging a separate JSON export step.\n *\n * `mudlet-map-binary-reader` is a `peerDependency`; consumers who do not use\n * binary maps never pay the bundle cost.\n */\nexport default class BinaryMapReader implements IMapReader {\n private readonly reader: MapReader;\n\n /** Build directly from a parsed {@link MudletMap}. */\n constructor(model: MudletMap) {\n const {mapData, colors} = readerExport(model);\n const map: MapData.Map = mapData.map(a => ({\n areaName: a.areaName,\n areaId: a.areaId,\n rooms: a.rooms.map(r => toMapRoom(r, a.areaId, r.hash)),\n labels: a.labels.map(l => toMapLabel(l, parseInt(a.areaId))),\n }));\n this.reader = new MapReader(map, colors);\n }\n\n /**\n * Parse the given binary map buffer and construct a reader from it.\n * Accepts any `Uint8Array`: in Node pass `fs.readFileSync(path)`; in the\n * browser pass `new Uint8Array(await file.arrayBuffer())`. The underlying\n * `mudlet-map-binary-reader` (>=1.0.0) is browser-safe and needs no\n * Node `Buffer` polyfill.\n */\n static fromBuffer(buf: Parameters<typeof readMapFromBuffer>[0]): BinaryMapReader {\n return new BinaryMapReader(readMapFromBuffer(buf));\n }\n\n // --- IMapReader forwarding ---\n\n getArea(areaId: number): IArea {\n return this.reader.getArea(areaId);\n }\n\n getAreas(): IArea[] {\n return this.reader.getAreas();\n }\n\n getRooms(): MapData.Room[] {\n return this.reader.getRooms();\n }\n\n getRoom(roomId: number): MapData.Room {\n return this.reader.getRoom(roomId);\n }\n\n getColorValue(envId: number): string {\n return this.reader.getColorValue(envId);\n }\n\n getSymbolColor(envId: number, opacity?: number): string {\n return this.reader.getSymbolColor(envId, opacity);\n }\n}\n","import {streamRooms, convertRoom, convertLabel, readMapFromBuffer, readerExport} from \"mudlet-map-binary-reader\";\nimport type {MudletMapHeader, MudletRoom} from \"mudlet-map-binary-reader\";\nimport MapReader, {type IMapReader} from \"../reader/MapReader\";\nimport SkeletonMapReader from \"../bigmap/SkeletonMapReader\";\nimport type {MapSkeleton} from \"../bigmap/Skeleton\";\nimport {toMapRoom, toMapLabel, hasExtraDetail} from \"./convert\";\n\n// Cardinal exit fields on MudletRoom, in the order packed into the skeleton\n// (12 slots per room; -1 = no exit).\nconst DIRS = [\n \"north\", \"northeast\", \"east\", \"southeast\", \"south\", \"southwest\",\n \"west\", \"northwest\", \"up\", \"down\", \"in\", \"out\",\n] as const;\n\nexport type LoadMode = \"auto\" | \"plain\" | \"streaming\";\n\nexport interface LoadMudletMapOptions {\n /** 'auto' (default) picks 'plain' or 'streaming' from the header's total room count vs. {@link threshold}. */\n mode?: LoadMode;\n /**\n * Total-room-count cutoff for 'auto' (default 50,000). Below it: a full\n * parse (every field, no skeleton overhead — a `MapSkeleton` has no\n * upside for a map small enough to hold as an object graph). Above it:\n * stream straight into a `MapSkeleton` — the full parsed map and the\n * skeleton are never both resident in memory at once.\n */\n threshold?: number;\n /** Invoked periodically (every 200,000 rooms) — the streaming path only; the full parse has no per-room hook. */\n onProgress?: (roomsRead: number, total: number) => void;\n /**\n * Streaming path only (the full parse always materialises every field —\n * there's no skeleton to promote into). Overrides which rooms are\n * promoted to a fully materialised `MapData.Room` in `MapSkeleton.detailRooms`;\n * default `hasExtraDetail` (doors/customLines/specialExits/stubs/exitLocks/\n * non-default weight/exitWeights — fields the compact columns can't encode\n * at all). Pass `() => true` to promote every room — correct at any size,\n * but memory-equivalent to a full `MapReader` object graph for the room\n * data itself, so it only makes sense when the map's total room count\n * doesn't actually need the streaming path's memory savings.\n */\n isDetailRoom?: (room: MudletRoom) => boolean;\n}\n\n/**\n * Portable, structured-clone/transfer-safe parse result — no live reader yet\n * (see {@link readerFromLoadedMap}). Splitting parse from materialisation\n * lets a Web Worker run the (potentially expensive) parse and hand back only\n * the result; the skeleton kind's typed arrays should be passed as\n * transferables (`[skeleton.x.buffer, skeleton.y.buffer, ...]`).\n */\nexport type LoadedMudletMap =\n | {kind: \"plain\"; map: MapData.Map; envs: MapData.Env[]}\n | {kind: \"skeleton\"; skeleton: MapSkeleton};\n\nclass HeaderAbort extends Error {}\n\n/** Reads only the header (areas/labels/colours), aborting before the — potentially huge — rooms blob. */\nfunction peekHeader(bytes: Uint8Array): {header: MudletMapHeader; total: number} {\n let result: {header: MudletMapHeader; total: number} | undefined;\n try {\n streamRooms(bytes, () => {\n throw new HeaderAbort();\n }, (hdr) => {\n let total = 0;\n for (const k in hdr.areas ?? {}) total += hdr.areas[k as unknown as number].rooms.length;\n result = {header: hdr, total};\n throw new HeaderAbort();\n });\n } catch (e) {\n if (!(e instanceof HeaderAbort)) throw e;\n }\n if (!result) throw new Error(\"failed to decode map header\");\n return result;\n}\n\nfunction buildAreaGridMode(header: MudletMapHeader): Record<number, boolean> {\n const areaGridMode: Record<number, boolean> = {};\n for (const k in header.areas ?? {}) {\n areaGridMode[k as unknown as number] = header.areas[k as unknown as number].gridMode;\n }\n return areaGridMode;\n}\n\nconst hasKeys = (o: Record<string, unknown> | undefined | null): boolean => !!o && Object.keys(o).length > 0;\n\nfunction parsePlain(bytes: Uint8Array): {kind: \"plain\"; map: MapData.Map; envs: MapData.Env[]} {\n const model = readMapFromBuffer(bytes);\n const {mapData, colors} = readerExport(model);\n const map: MapData.Map = mapData.map(a => ({\n areaName: a.areaName,\n areaId: a.areaId,\n // readerExport already resolves each room's hash — pass-through, no reverse lookup needed here.\n rooms: a.rooms.map(r => toMapRoom(r, a.areaId, r.hash)),\n labels: a.labels.map(l => toMapLabel(l, parseInt(a.areaId))),\n }));\n return {kind: \"plain\", map, envs: colors};\n}\n\nfunction parseStreaming(\n bytes: Uint8Array, header: MudletMapHeader, total: number,\n onProgress?: (roomsRead: number, total: number) => void,\n isDetailRoom: (room: MudletRoom) => boolean = hasExtraDetail,\n): {kind: \"skeleton\"; skeleton: MapSkeleton} {\n const x = new Int32Array(total), y = new Int32Array(total), z = new Int32Array(total);\n const area = new Int32Array(total), env = new Int32Array(total), id = new Int32Array(total);\n const exits = new Int32Array(total * 12).fill(-1);\n const names: string[] = new Array(total);\n const userData: {id: number; data: Record<string, string>}[] = [];\n const detailRooms: MapData.Room[] = [];\n\n const roomIdToHash: Record<number, string> = {};\n for (const hash in header.mpRoomDbHashToRoomId) {\n roomIdToHash[header.mpRoomDbHashToRoomId[hash]] = hash;\n }\n\n // Time-based throttle (not every-N-rooms): gives smooth, frequent updates\n // regardless of map size or per-room cost, instead of a handful of big\n // jumps on a huge map or none at all on a small one.\n const now = () => (typeof performance !== \"undefined\" ? performance.now() : Date.now());\n let lastReport = now();\n let i = 0;\n streamRooms(bytes, (roomId, room) => {\n x[i] = room.x;\n y[i] = room.y; // RAW map space — SkeletonMapReader converts to renderer space\n z[i] = room.z;\n area[i] = room.area;\n env[i] = room.environment;\n id[i] = roomId;\n const base = i * 12;\n for (let d = 0; d < 12; d++) exits[base + d] = room[DIRS[d]] as number;\n\n // Name + userData for EVERY room (compact columns), so any room — drawn\n // or looked up by id — is complete, without promoting it to a heavy object.\n names[i] = room.name ?? \"\";\n if (hasKeys(room.userData)) userData.push({id: roomId, data: room.userData});\n\n if (isDetailRoom(room)) {\n const rr = convertRoom(roomId, room, roomIdToHash[roomId]);\n detailRooms.push(toMapRoom(rr, String(room.area), roomIdToHash[roomId]));\n }\n i++;\n if (onProgress) {\n const t = now();\n if (t - lastReport >= 80) {\n lastReport = t;\n onProgress(i, total);\n }\n }\n });\n onProgress?.(i, total); // final tick — guarantees a 100% callback even on a fast/small stream\n\n const customEnvColors: Record<number, {r: number; g: number; b: number}> = {};\n for (const k in header.mCustomEnvColors ?? {}) {\n const c = header.mCustomEnvColors[k as unknown as number];\n customEnvColors[k as unknown as number] = {r: c.r, g: c.g, b: c.b};\n }\n\n const labels: MapData.Label[] = [];\n for (const k in header.labels ?? {}) {\n const areaId = Number(k);\n for (const lb of header.labels[areaId]) {\n labels.push(toMapLabel(convertLabel(lb), areaId));\n }\n }\n\n return {\n kind: \"skeleton\",\n skeleton: {\n count: i, x, y, z, area, env, id, exits,\n areaNames: header.areaNames ?? {}, areaGridMode: buildAreaGridMode(header), customEnvColors,\n names, userData, detailRooms, labels,\n // Already hash -> roomId, exactly the shape SkeletonMapReader wants —\n // no inversion needed (roomIdToHash above is the id -> hash direction,\n // built separately for tagging detailRooms with their hash).\n hashToId: header.mpRoomDbHashToRoomId ?? {},\n },\n };\n}\n\n/**\n * Parse a Mudlet binary `.dat` buffer, picking the loading strategy from its\n * total room count (see {@link LoadMudletMapOptions}). Returns portable data\n * only — call {@link readerFromLoadedMap} to get a live `IMapReader`, or use\n * {@link loadMudletMap} to do both in one call.\n */\nexport function parseMudletMap(bytes: Uint8Array, options: LoadMudletMapOptions = {}): LoadedMudletMap {\n const {mode = \"auto\", threshold = 50_000, onProgress, isDetailRoom} = options;\n const {header, total} = peekHeader(bytes);\n const effectiveMode = mode === \"auto\" ? (total > threshold ? \"streaming\" : \"plain\") : mode;\n return effectiveMode === \"plain\" ? parsePlain(bytes) : parseStreaming(bytes, header, total, onProgress, isDetailRoom);\n}\n\n/** Build the live {@link IMapReader} for data produced by {@link parseMudletMap}. */\nexport function readerFromLoadedMap(loaded: LoadedMudletMap): IMapReader {\n return loaded.kind === \"plain\" ? new MapReader(loaded.map, loaded.envs) : new SkeletonMapReader(loaded.skeleton);\n}\n\n/**\n * Convenience: parse and materialise in one call, on the current thread.\n * `mudlet-map-binary-reader` is a peer dependency; consumers who don't call\n * this never pay the bundle cost. For very large maps, prefer running\n * {@link parseMudletMap} in a Web Worker and calling {@link readerFromLoadedMap}\n * with its result on the main thread.\n */\nexport function loadMudletMap(bytes: Uint8Array, options?: LoadMudletMapOptions): IMapReader {\n return readerFromLoadedMap(parseMudletMap(bytes, options));\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { IMapReader } from '../reader/MapReader';
|
|
2
|
+
/**
|
|
3
|
+
* Headless exporters (`CanvasExporter`, `SvgExporter`) rebuild their scene
|
|
4
|
+
* directly from `state.mapReader`, independent of the live interactive
|
|
5
|
+
* camera. For a viewport-virtualized reader (`SkeletonMapReader`) that reader
|
|
6
|
+
* only materialises rooms inside whatever viewport was LAST pushed onto it —
|
|
7
|
+
* normally by the interactive backend, whose viewport push is itself
|
|
8
|
+
* deferred to a `requestAnimationFrame` (see `KonvaRenderBackend.refresh()`),
|
|
9
|
+
* so an export that runs synchronously right after a camera change (e.g. a
|
|
10
|
+
* `fitArea()` call, as `MapController.renderArea()` does before generating a
|
|
11
|
+
* preview thumbnail) can race it and capture a stale, wrong window instead
|
|
12
|
+
* of the region it was actually asked to export.
|
|
13
|
+
*
|
|
14
|
+
* Pushes a viewport covering exactly `bounds` (world space, the same shape
|
|
15
|
+
* `computeExportBounds` returns) before the caller builds its scene, and
|
|
16
|
+
* returns a restore function that puts back whatever viewport was active
|
|
17
|
+
* before — so the export never leaves the shared reader (and the interactive
|
|
18
|
+
* backend's next incremental refresh) in a different state than it found it.
|
|
19
|
+
*/
|
|
20
|
+
export declare function pushExportViewport(mapReader: IMapReader, bounds: {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
w: number;
|
|
24
|
+
h: number;
|
|
25
|
+
}): () => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -60,6 +60,8 @@ export type { IPlane } from './reader/Plane';
|
|
|
60
60
|
export type { IExit, Kind as ExitKind } from './reader/Exit';
|
|
61
61
|
export { isViewportDataSource } from './reader/ViewportDataSource';
|
|
62
62
|
export type { ViewportDataSource } from './reader/ViewportDataSource';
|
|
63
|
+
export { isHashLookupCapable } from './reader/HashLookup';
|
|
64
|
+
export type { HashLookupCapable } from './reader/HashLookup';
|
|
63
65
|
export { shouldUseRaster, computeLodMode } from './rendering/lod/lodDecision';
|
|
64
66
|
export type { LodDecisionInput, LodModeInput, LodMode } from './rendering/lod/lodDecision';
|
|
65
67
|
export type { RoomLens, ExitTreatment } from './lens/RoomLens';
|