minecraft-renderer 0.1.54 → 0.1.56
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/mesher.js +54 -54
- package/dist/mesher.js.map +3 -3
- package/dist/mesherWasm.js +46 -46
- package/dist/minecraft-renderer.js +61 -61
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +972 -972
- package/package.json +1 -1
- package/src/lib/buildWorkerMcDataIndexes.test.ts +48 -0
- package/src/lib/buildWorkerMcDataIndexes.ts +121 -0
- package/src/lib/items.ts +4 -3
- package/src/lib/utils.ts +1 -1
- package/src/lib/workerMessageSanitize.test.ts +14 -0
- package/src/lib/workerMessageSanitize.ts +48 -0
- package/src/lib/workerProxy.restore.test.ts +29 -0
- package/src/lib/workerProxy.ts +110 -36
- package/src/lib/worldrendererCommon.ts +25 -0
- package/src/resourcesManager/resourcesManager.ts +97 -11
- package/src/resourcesManager/resourcesManager.worker.test.ts +61 -0
- package/src/three/appShared.ts +1 -1
- package/src/three/chunkMeshManager.ts +4 -3
- package/src/three/entities.ts +27 -19
- package/src/three/entity/EntityMesh.ts +4 -10
- package/src/three/graphicsBackendBase.ts +25 -15
- package/src/three/graphicsBackendOffThread.ts +27 -3
- package/src/three/hand.ts +3 -3
- package/src/three/itemMesh.ts +20 -7
- package/src/three/menuBackground/assetUrl.ts +15 -0
- package/src/three/menuBackground/classic.ts +11 -5
- package/src/three/menuBackground/futuristic.ts +3 -0
- package/src/three/threeJsMedia.ts +31 -4
- package/src/three/threeJsUtils.ts +11 -0
- package/src/three/threeWorker.ts +12 -11
- package/src/three/worldRendererThree.ts +7 -0
- package/src/worldView/worldView.ts +2 -1
package/package.json
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { augmentWorkerMcData } from './buildWorkerMcDataIndexes'
|
|
4
|
+
|
|
5
|
+
describe('augmentWorkerMcData', () => {
|
|
6
|
+
it('is idempotent when mcData is augmented twice (menu then world)', () => {
|
|
7
|
+
const mcData: Record<string, unknown> = {
|
|
8
|
+
entities: [{ name: 'bat', id: 1, type: 'mob' }],
|
|
9
|
+
blocks: [{ name: 'stone', id: 3, minStateId: 48, maxStateId: 63, defaultState: 48 }],
|
|
10
|
+
}
|
|
11
|
+
augmentWorkerMcData(mcData)
|
|
12
|
+
expect(() => augmentWorkerMcData(mcData)).not.toThrow()
|
|
13
|
+
expect(mcData.__workerIndexesBuilt).toBe(true)
|
|
14
|
+
expect(Array.isArray(mcData.blocksArray)).toBe(true)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('keeps *Array sources after indexing (esbuild mc-data plugin reads blocksArray)', () => {
|
|
18
|
+
const mcData: Record<string, unknown> = {
|
|
19
|
+
blocks: [{ name: 'stone', id: 3, minStateId: 48, maxStateId: 63, defaultState: 48 }],
|
|
20
|
+
}
|
|
21
|
+
augmentWorkerMcData(mcData)
|
|
22
|
+
expect(Array.isArray(mcData.blocksArray)).toBe(true)
|
|
23
|
+
expect((mcData.blocksArray as unknown[]).length).toBe(1)
|
|
24
|
+
expect(Array.isArray(mcData.blocks)).toBe(false)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('coerces valtio-style dense objects into arrays', () => {
|
|
28
|
+
const mcData: Record<string, unknown> = {
|
|
29
|
+
entities: { 0: { name: 'bat', id: 1, type: 'mob' } },
|
|
30
|
+
}
|
|
31
|
+
augmentWorkerMcData(mcData)
|
|
32
|
+
expect((mcData.entitiesByName as Record<string, { name: string }>).bat?.name).toBe('bat')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('builds entitiesByName and items indexes from arrays', () => {
|
|
36
|
+
const mcData: Record<string, unknown> = {
|
|
37
|
+
entities: [{ name: 'bat', id: 1, type: 'mob' }],
|
|
38
|
+
items: [{ name: 'dirt', id: 2 }],
|
|
39
|
+
blocks: [{ name: 'stone', id: 3, minStateId: 48, maxStateId: 63, defaultState: 48 }],
|
|
40
|
+
}
|
|
41
|
+
augmentWorkerMcData(mcData)
|
|
42
|
+
expect((mcData.entitiesByName as Record<string, { name: string }>).bat?.name).toBe('bat')
|
|
43
|
+
expect((mcData.entities as Record<number, { id: number }>)[1]?.id).toBe(1)
|
|
44
|
+
expect((mcData.itemsByName as Record<string, unknown>).dirt).toBeDefined()
|
|
45
|
+
expect((mcData.items as Record<number, unknown>)[2]).toBeDefined()
|
|
46
|
+
expect((mcData.blocksByStateId as Record<number, unknown>)[48]).toBeDefined()
|
|
47
|
+
})
|
|
48
|
+
})
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
type McElement = Record<string, unknown>
|
|
3
|
+
|
|
4
|
+
function coerceDenseArray (value: unknown): McElement[] | undefined {
|
|
5
|
+
if (Array.isArray(value)) {
|
|
6
|
+
return value as McElement[]
|
|
7
|
+
}
|
|
8
|
+
if (!value || typeof value !== 'object') {
|
|
9
|
+
return undefined
|
|
10
|
+
}
|
|
11
|
+
const record = value as Record<string, McElement>
|
|
12
|
+
const keys = Object.keys(record).filter((k) => /^\d+$/.test(k)).map(Number).sort((a, b) => a - b)
|
|
13
|
+
if (!keys.length || keys[0] !== 0) {
|
|
14
|
+
return undefined
|
|
15
|
+
}
|
|
16
|
+
for (let i = 0; i < keys.length; i++) {
|
|
17
|
+
if (keys[i] !== i) {
|
|
18
|
+
return undefined
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return keys.map((k) => record[String(k)])
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function buildIndexFromArray<T extends McElement> (
|
|
25
|
+
array: T[],
|
|
26
|
+
field: keyof T
|
|
27
|
+
): Record<string | number, T> {
|
|
28
|
+
if (!Array.isArray(array)) {
|
|
29
|
+
console.warn('[augmentWorkerMcData] buildIndexFromArray expected array, got', typeof array)
|
|
30
|
+
return {}
|
|
31
|
+
}
|
|
32
|
+
return array.reduce<Record<string | number, T>>((index, element) => {
|
|
33
|
+
index[element[field] as string | number] = element
|
|
34
|
+
return index
|
|
35
|
+
}, {})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildIndexFromArrayWithRanges<T extends McElement> (
|
|
39
|
+
array: T[],
|
|
40
|
+
minField: keyof T,
|
|
41
|
+
maxField: keyof T
|
|
42
|
+
): Record<number, T> {
|
|
43
|
+
if (!Array.isArray(array)) {
|
|
44
|
+
console.warn('[augmentWorkerMcData] buildIndexFromArrayWithRanges expected array, got', typeof array)
|
|
45
|
+
return {}
|
|
46
|
+
}
|
|
47
|
+
return array.reduce<Record<number, T>>((index, element) => {
|
|
48
|
+
const min = element[minField] as number
|
|
49
|
+
const max = element[maxField] as number
|
|
50
|
+
for (let i = min; i <= max; i++) {
|
|
51
|
+
index[i] = element
|
|
52
|
+
}
|
|
53
|
+
return index
|
|
54
|
+
}, {})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function ensureBlockStateIds (blocks: McElement[]) {
|
|
58
|
+
if (!blocks.length) return
|
|
59
|
+
if ('minStateId' in blocks[0] && 'defaultState' in blocks[0]) return
|
|
60
|
+
for (const block of blocks) {
|
|
61
|
+
const id = block.id as number
|
|
62
|
+
block.minStateId = id << 4
|
|
63
|
+
block.maxStateId = (block.minStateId as number) + 15
|
|
64
|
+
block.defaultState = block.minStateId
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getSourceArray (
|
|
69
|
+
mcData: Record<string, unknown>,
|
|
70
|
+
arrayKey: string,
|
|
71
|
+
rawKey: string
|
|
72
|
+
): McElement[] | undefined {
|
|
73
|
+
const fromArrayKey = coerceDenseArray(mcData[arrayKey])
|
|
74
|
+
if (fromArrayKey?.length) {
|
|
75
|
+
return fromArrayKey
|
|
76
|
+
}
|
|
77
|
+
const raw = coerceDenseArray(mcData[rawKey])
|
|
78
|
+
if (raw?.length) {
|
|
79
|
+
return raw
|
|
80
|
+
}
|
|
81
|
+
return undefined
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function augmentWorkerMcData (mcData: Record<string, unknown>) {
|
|
85
|
+
if (mcData.__workerIndexesBuilt) {
|
|
86
|
+
return mcData
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const blocks = getSourceArray(mcData, 'blocksArray', 'blocks')
|
|
90
|
+
if (blocks?.length) {
|
|
91
|
+
ensureBlockStateIds(blocks)
|
|
92
|
+
mcData.blocksArray = blocks
|
|
93
|
+
mcData.blocks = buildIndexFromArray(blocks, 'id')
|
|
94
|
+
mcData.blocksByName = buildIndexFromArray(blocks, 'name')
|
|
95
|
+
mcData.blocksByStateId = buildIndexFromArrayWithRanges(blocks, 'minStateId', 'maxStateId')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const items = getSourceArray(mcData, 'itemsArray', 'items')
|
|
99
|
+
if (items?.length) {
|
|
100
|
+
mcData.itemsArray = items
|
|
101
|
+
mcData.itemsByName = buildIndexFromArray(items, 'name')
|
|
102
|
+
mcData.items = buildIndexFromArray(items, 'id')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const entities = getSourceArray(mcData, 'entitiesArray', 'entities')
|
|
106
|
+
if (entities?.length) {
|
|
107
|
+
mcData.entitiesArray = entities
|
|
108
|
+
mcData.entitiesByName = buildIndexFromArray(entities, 'name')
|
|
109
|
+
mcData.entities = buildIndexFromArray(entities, 'id')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const biomes = getSourceArray(mcData, 'biomesArray', 'biomes')
|
|
113
|
+
if (biomes?.length) {
|
|
114
|
+
mcData.biomesArray = biomes
|
|
115
|
+
mcData.biomes = buildIndexFromArray(biomes, 'id')
|
|
116
|
+
mcData.biomesByName = buildIndexFromArray(biomes, 'name')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
mcData.__workerIndexesBuilt = true
|
|
120
|
+
return mcData
|
|
121
|
+
}
|
package/src/lib/items.ts
CHANGED
|
@@ -4,7 +4,7 @@ import nbt from 'prismarine-nbt'
|
|
|
4
4
|
import { fromFormattedString } from '@xmcl/text-component'
|
|
5
5
|
import { getItemSelector } from '../playerState/playerState'
|
|
6
6
|
import { getItemDefinition } from 'mc-assets/dist/itemDefinitions'
|
|
7
|
-
import { ResourcesManagerCommon } from '../resourcesManager'
|
|
7
|
+
import { getItemsDefinitionsStoreForRender, ResourcesManagerCommon } from '../resourcesManager'
|
|
8
8
|
import { ItemSpecificContextProperties } from '../playerState/types'
|
|
9
9
|
import { PlayerStateRenderer } from '../playerState/playerState'
|
|
10
10
|
|
|
@@ -131,9 +131,10 @@ export const getItemModelName = (item: GeneralInputItem, specificProps: ItemSpec
|
|
|
131
131
|
const itemSelector = getItemSelector(playerState, {
|
|
132
132
|
...specificProps
|
|
133
133
|
})
|
|
134
|
-
const
|
|
134
|
+
const resources = resourcesManager.currentResources!
|
|
135
|
+
const modelFromDef = getItemDefinition(getItemsDefinitionsStoreForRender(resources), {
|
|
135
136
|
name: itemModelName,
|
|
136
|
-
version:
|
|
137
|
+
version: resources.version,
|
|
137
138
|
properties: itemSelector
|
|
138
139
|
})?.model
|
|
139
140
|
const model = (modelFromDef === 'minecraft:special' ? undefined : modelFromDef) ?? itemModelName
|
package/src/lib/utils.ts
CHANGED
|
@@ -42,7 +42,7 @@ const detectFullOffscreenCanvasSupport = () => {
|
|
|
42
42
|
const hasFullOffscreenCanvasSupport = detectFullOffscreenCanvasSupport()
|
|
43
43
|
|
|
44
44
|
export const createCanvas = (width: number, height: number): OffscreenCanvas => {
|
|
45
|
-
if (hasFullOffscreenCanvasSupport) {
|
|
45
|
+
if (hasFullOffscreenCanvasSupport || typeof document === 'undefined') {
|
|
46
46
|
return new OffscreenCanvas(width, height)
|
|
47
47
|
}
|
|
48
48
|
const canvas = document.createElement('canvas')
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { sanitizeForWorkerPostMessage } from './workerMessageSanitize'
|
|
4
|
+
|
|
5
|
+
describe('sanitizeForWorkerPostMessage', () => {
|
|
6
|
+
it('drops functions such as debug loggers', () => {
|
|
7
|
+
const debug = Object.assign(() => {}, { enabled: false })
|
|
8
|
+
const entity = { id: 1, name: 'zombie', debug, position: { x: 1, y: 2, z: 3 } }
|
|
9
|
+
const sanitized = sanitizeForWorkerPostMessage(entity) as Record<string, unknown>
|
|
10
|
+
expect(sanitized.debug).toBeUndefined()
|
|
11
|
+
expect(sanitized.id).toBe(1)
|
|
12
|
+
expect(() => structuredClone(sanitized)).not.toThrow()
|
|
13
|
+
})
|
|
14
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Strip non–structured-clone values before Worker.postMessage (e.g. mineflayer `debug` on entities).
|
|
4
|
+
*/
|
|
5
|
+
export function sanitizeForWorkerPostMessage (value: unknown, depth = 0): unknown {
|
|
6
|
+
if (depth > 16) return undefined
|
|
7
|
+
if (value === null || value === undefined) return value
|
|
8
|
+
|
|
9
|
+
const t = typeof value
|
|
10
|
+
if (t === 'function' || t === 'symbol') return undefined
|
|
11
|
+
if (t === 'bigint') return value.toString()
|
|
12
|
+
if (t !== 'object') return value
|
|
13
|
+
|
|
14
|
+
if (value instanceof ArrayBuffer) return value
|
|
15
|
+
if (ArrayBuffer.isView(value)) {
|
|
16
|
+
const view = value as ArrayBufferView
|
|
17
|
+
return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength)
|
|
18
|
+
}
|
|
19
|
+
if (value instanceof Date) return value.toISOString()
|
|
20
|
+
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
return value
|
|
23
|
+
.map((entry) => sanitizeForWorkerPostMessage(entry, depth + 1))
|
|
24
|
+
.filter((entry) => entry !== undefined)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const record = value as Record<string, unknown>
|
|
28
|
+
if (
|
|
29
|
+
typeof record.x === 'number'
|
|
30
|
+
&& typeof record.y === 'number'
|
|
31
|
+
&& typeof record.z === 'number'
|
|
32
|
+
&& !('w' in record)
|
|
33
|
+
) {
|
|
34
|
+
return { x: record.x, y: record.y, z: record.z }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const out: Record<string, unknown> = {}
|
|
38
|
+
for (const key of Object.keys(record)) {
|
|
39
|
+
if (key === '_client' || key === '_events' || key === '_eventsCount') continue
|
|
40
|
+
const sanitized = sanitizeForWorkerPostMessage(record[key], depth + 1)
|
|
41
|
+
if (sanitized !== undefined) out[key] = sanitized
|
|
42
|
+
}
|
|
43
|
+
return out
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function sanitizeWorkerEventArgs (args: unknown[]): unknown[] {
|
|
47
|
+
return args.map((arg) => sanitizeForWorkerPostMessage(arg))
|
|
48
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { restoreTransferred } from './workerProxy'
|
|
4
|
+
|
|
5
|
+
describe('restoreTransferred Set/Map', () => {
|
|
6
|
+
const worker = null as unknown as Worker
|
|
7
|
+
|
|
8
|
+
it('restores Set from __setValues', () => {
|
|
9
|
+
const out = restoreTransferred({ __restorer: 'Set', __setValues: ['a', 'b'] }, [], worker, false)
|
|
10
|
+
expect(out).toEqual(new Set(['a', 'b']))
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('does not throw when legacy values is a function (Map.values collision)', () => {
|
|
14
|
+
const map = new Map([['k', 1]])
|
|
15
|
+
const out = restoreTransferred({ __restorer: 'Set', values: map.values.bind(map) }, [], worker, false)
|
|
16
|
+
expect(out).toEqual(new Set())
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('restores Map from __mapEntries', () => {
|
|
20
|
+
const out = restoreTransferred({ __restorer: 'Map', __mapEntries: [['a', 1]] }, [], worker, false)
|
|
21
|
+
expect(out).toEqual(new Map([['a', 1]]))
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('does not throw when legacy entries is a function', () => {
|
|
25
|
+
const m = new Map()
|
|
26
|
+
const out = restoreTransferred({ __restorer: 'Map', entries: m.entries.bind(m) }, [], worker, false)
|
|
27
|
+
expect(out).toEqual(new Map())
|
|
28
|
+
})
|
|
29
|
+
})
|
package/src/lib/workerProxy.ts
CHANGED
|
@@ -117,6 +117,10 @@ const getSyncId = () => {
|
|
|
117
117
|
return Math.random().toString(36).slice(2, 15) + Math.random().toString(36).slice(2, 15)
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
const applySyncPatch = (target: any, patch: any, worker: Worker) => {
|
|
121
|
+
Object.assign(target, restoreTransferred(patch, [], worker, false))
|
|
122
|
+
}
|
|
123
|
+
|
|
120
124
|
const setupObjectSync = (obj: any, originalObj: any, worker: Worker, isValtio: boolean, debugKey: string) => {
|
|
121
125
|
if (!obj['__syncToWorker'] && !obj['__syncFromWorker'] && !isValtio) return
|
|
122
126
|
|
|
@@ -141,13 +145,45 @@ const setupObjectSync = (obj: any, originalObj: any, worker: Worker, isValtio: b
|
|
|
141
145
|
worker.addEventListener('message', (event: any) => {
|
|
142
146
|
if (event.data.type === 'sync' && event.data.syncId === syncId) {
|
|
143
147
|
currentWorkerSyncStats.fromWorker++
|
|
144
|
-
|
|
148
|
+
applySyncPatch(originalObj, event.data.value, worker)
|
|
145
149
|
}
|
|
146
150
|
})
|
|
147
151
|
}
|
|
148
152
|
}
|
|
149
153
|
|
|
154
|
+
const serializeMapForTransfer = (map: Map<unknown, unknown>) => ({
|
|
155
|
+
__restorer: 'Map',
|
|
156
|
+
__mapEntries: Array.from(map.entries()),
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const serializeSetForTransfer = (set: Set<unknown>) => ({
|
|
160
|
+
__restorer: 'Set',
|
|
161
|
+
__setValues: [...set],
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
const isSetLike = (value: unknown): value is Set<unknown> => {
|
|
165
|
+
return value instanceof Set || Object.prototype.toString.call(value) === '[object Set]'
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const isMapLike = (value: unknown): value is Map<unknown, unknown> => {
|
|
169
|
+
return value instanceof Map || Object.prototype.toString.call(value) === '[object Map]'
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const iterableFromPlainObject = (obj: Record<string, unknown>) => {
|
|
173
|
+
return Object.keys(obj)
|
|
174
|
+
.filter(k => !k.startsWith('__'))
|
|
175
|
+
.sort((a, b) => Number(a) - Number(b))
|
|
176
|
+
.map(k => obj[k])
|
|
177
|
+
}
|
|
178
|
+
|
|
150
179
|
const cloneValtioObject = (obj: any) => {
|
|
180
|
+
if (isMapLike(obj)) {
|
|
181
|
+
return serializeMapForTransfer(obj)
|
|
182
|
+
}
|
|
183
|
+
if (isSetLike(obj)) {
|
|
184
|
+
return serializeSetForTransfer(obj)
|
|
185
|
+
}
|
|
186
|
+
|
|
151
187
|
if (getVersion(obj) === undefined) {
|
|
152
188
|
return obj
|
|
153
189
|
}
|
|
@@ -179,16 +215,19 @@ export const deepPrepareForTransfer = (obj: any, worker: Worker, autoRemoveMetho
|
|
|
179
215
|
continue
|
|
180
216
|
}
|
|
181
217
|
|
|
182
|
-
// print a warning for Date, RegExp,
|
|
183
|
-
if (obj[key] instanceof Date || obj[key] instanceof RegExp || obj[key] instanceof
|
|
218
|
+
// print a warning for Date, RegExp, WeakMap, WeakSet
|
|
219
|
+
if (obj[key] instanceof Date || obj[key] instanceof RegExp || obj[key] instanceof WeakMap || obj[key] instanceof WeakSet) {
|
|
184
220
|
console.warn(`Warning: ${key} is a ${typeof obj[key]}, which is not supported for transfer.`)
|
|
185
221
|
}
|
|
186
222
|
|
|
187
223
|
// default restorers main -> worker
|
|
224
|
+
if (isMapLike(obj[key])) {
|
|
225
|
+
newObj[key] = serializeMapForTransfer(obj[key])
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
188
228
|
// Set (only primitive values)
|
|
189
|
-
if (obj[key]
|
|
190
|
-
newObj[key] =
|
|
191
|
-
newObj[key]['__restorer'] = 'Set'
|
|
229
|
+
if (isSetLike(obj[key])) {
|
|
230
|
+
newObj[key] = serializeSetForTransfer(obj[key])
|
|
192
231
|
continue
|
|
193
232
|
}
|
|
194
233
|
if (obj[key] instanceof Vec3) {
|
|
@@ -253,7 +292,7 @@ const receiveSyncedObject = (obj: any, worker: Worker, debugKey: string) => {
|
|
|
253
292
|
if (obj['__syncToWorker']) {
|
|
254
293
|
worker.addEventListener('message', (event: any) => {
|
|
255
294
|
if (event.data.type === 'sync' && event.data.syncId === syncId) {
|
|
256
|
-
|
|
295
|
+
applySyncPatch(obj, event.data.value, worker)
|
|
257
296
|
}
|
|
258
297
|
})
|
|
259
298
|
}
|
|
@@ -275,10 +314,36 @@ const receiveSyncedObject = (obj: any, worker: Worker, debugKey: string) => {
|
|
|
275
314
|
}
|
|
276
315
|
|
|
277
316
|
const defaultRestorers = [
|
|
317
|
+
{
|
|
318
|
+
restorerName: 'Map',
|
|
319
|
+
restoreTransferred(obj, _worker: Worker) {
|
|
320
|
+
if (Array.isArray(obj)) {
|
|
321
|
+
return new Map(obj)
|
|
322
|
+
}
|
|
323
|
+
const raw = obj.__mapEntries ?? obj.entries
|
|
324
|
+
if (Array.isArray(raw)) {
|
|
325
|
+
return new Map(raw)
|
|
326
|
+
}
|
|
327
|
+
if (raw != null && typeof raw === 'object' && typeof raw !== 'function') {
|
|
328
|
+
return new Map(Object.entries(raw as Record<string, unknown>))
|
|
329
|
+
}
|
|
330
|
+
return new Map()
|
|
331
|
+
}
|
|
332
|
+
},
|
|
278
333
|
{
|
|
279
334
|
restorerName: 'Set',
|
|
280
|
-
restoreTransferred(obj,
|
|
281
|
-
|
|
335
|
+
restoreTransferred(obj, _worker: Worker) {
|
|
336
|
+
if (Array.isArray(obj)) {
|
|
337
|
+
return new Set(obj)
|
|
338
|
+
}
|
|
339
|
+
const raw = obj.__setValues ?? obj.values
|
|
340
|
+
if (Array.isArray(raw)) {
|
|
341
|
+
return new Set(raw)
|
|
342
|
+
}
|
|
343
|
+
if (raw != null && typeof raw === 'object' && typeof raw !== 'function') {
|
|
344
|
+
return new Set(iterableFromPlainObject(raw as Record<string, unknown>))
|
|
345
|
+
}
|
|
346
|
+
return new Set()
|
|
282
347
|
}
|
|
283
348
|
},
|
|
284
349
|
{
|
|
@@ -296,41 +361,50 @@ export const addDefaultRestorer = (restorer: { restorerName: string, restoreTran
|
|
|
296
361
|
export const restoreTransferred = (obj: any, restorersArg: any[], worker: Worker, errorHandler: ((error: Error) => void) | boolean = true) => {
|
|
297
362
|
const restorers = [...defaultRestorers, ...restorersArg]
|
|
298
363
|
|
|
299
|
-
|
|
300
|
-
if (
|
|
301
|
-
|
|
364
|
+
const restoreValue = (value: any, debugKey: string): any => {
|
|
365
|
+
if (value == null || typeof value !== 'object') {
|
|
366
|
+
return value
|
|
367
|
+
}
|
|
302
368
|
|
|
303
|
-
|
|
304
|
-
|
|
369
|
+
if (value['__restorer']) {
|
|
370
|
+
const restorer = restorers.find(r => {
|
|
371
|
+
return r.restorerName ? r.restorerName === value['__restorer'] : r.name === value['__restorer']
|
|
372
|
+
})
|
|
373
|
+
if (restorer) {
|
|
374
|
+
return restorer.restoreTransferred(value, worker)
|
|
305
375
|
}
|
|
306
|
-
|
|
307
|
-
if (
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
obj[key] = restorer.restoreTransferred(obj[key], worker)
|
|
314
|
-
} else {
|
|
315
|
-
const error = new Error(`Restorer ${obj[key]['__restorer']} not found`)
|
|
316
|
-
if (typeof errorHandler === 'function') {
|
|
317
|
-
errorHandler(error)
|
|
318
|
-
} else if (errorHandler) {
|
|
319
|
-
throw error
|
|
320
|
-
} else {
|
|
321
|
-
console.error(error)
|
|
322
|
-
}
|
|
323
|
-
}
|
|
376
|
+
const error = new Error(`Restorer ${value['__restorer']} not found`)
|
|
377
|
+
if (typeof errorHandler === 'function') {
|
|
378
|
+
errorHandler(error)
|
|
379
|
+
} else if (errorHandler) {
|
|
380
|
+
throw error
|
|
381
|
+
} else {
|
|
382
|
+
console.error(error)
|
|
324
383
|
}
|
|
384
|
+
return value
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (Array.isArray(value)) {
|
|
388
|
+
return value.map((item, index) => restoreValue(item, `${debugKey}[${index}]`))
|
|
389
|
+
}
|
|
325
390
|
|
|
326
|
-
|
|
327
|
-
|
|
391
|
+
for (const key in value) {
|
|
392
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) continue
|
|
393
|
+
const child = value[key]
|
|
394
|
+
if (child != null && typeof child === 'object') {
|
|
395
|
+
value[key] = restoreValue(child, `${debugKey}.${key}`)
|
|
328
396
|
}
|
|
397
|
+
}
|
|
329
398
|
|
|
330
|
-
|
|
399
|
+
if (value['__valtio']) {
|
|
400
|
+
value = proxy(value)
|
|
331
401
|
}
|
|
402
|
+
|
|
403
|
+
receiveSyncedObject(value, worker, debugKey)
|
|
404
|
+
return value
|
|
332
405
|
}
|
|
333
|
-
|
|
406
|
+
|
|
407
|
+
return restoreValue(obj, 'root')
|
|
334
408
|
}
|
|
335
409
|
|
|
336
410
|
// const workerProxy = createWorkerProxy({
|
|
@@ -1295,3 +1295,28 @@ export const meshersSendMcData = (workers: Worker[], version: string, mcDataKeys
|
|
|
1295
1295
|
worker.postMessage({ type: 'mcData', mcData })
|
|
1296
1296
|
}
|
|
1297
1297
|
}
|
|
1298
|
+
|
|
1299
|
+
/** Wait for worker `mcDataApplied` after {@link meshersSendMcData}. */
|
|
1300
|
+
export const meshersSendMcDataAwait = (
|
|
1301
|
+
workers: Worker[],
|
|
1302
|
+
version: string,
|
|
1303
|
+
mcDataKeys = dynamicMcDataFiles,
|
|
1304
|
+
mcDataFull: IndexedData,
|
|
1305
|
+
timeoutMs = 10_000
|
|
1306
|
+
): Promise<void> => {
|
|
1307
|
+
return Promise.all(workers.map(worker => new Promise<void>((resolve, reject) => {
|
|
1308
|
+
const timeout = setTimeout(() => {
|
|
1309
|
+
worker.removeEventListener('message', handler as EventListener)
|
|
1310
|
+
reject(new Error(`mcData transfer timeout (${timeoutMs}ms)`))
|
|
1311
|
+
}, timeoutMs)
|
|
1312
|
+
const handler = ({ data }: MessageEvent) => {
|
|
1313
|
+
if (data?.type === 'mcDataApplied') {
|
|
1314
|
+
clearTimeout(timeout)
|
|
1315
|
+
worker.removeEventListener('message', handler as EventListener)
|
|
1316
|
+
resolve()
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
worker.addEventListener('message', handler as EventListener)
|
|
1320
|
+
meshersSendMcData([worker], version, mcDataKeys, mcDataFull)
|
|
1321
|
+
}))).then(() => undefined)
|
|
1322
|
+
}
|