1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
import type {
CityContent,
CityId,
InstitutionContent,
InstitutionContentId,
RegionContent,
RegionId
} from '@hollowdark/content-system/regions/schema'
import type { ContentManifest } from '@hollowdark/content-system/manifest/schema'
import { loadChunk } from '@hollowdark/content-system/loader/loader'
/**
* The in-memory content registry. Populated once at session start by
* walking the manifest and loading every chunk into typed maps. All
* simulation-time reads go through here — synchronous, typed, fast.
*/
export class ContentRegistry {
readonly #regions = new Map<RegionId, RegionContent>()
readonly #cities = new Map<CityId, CityContent>()
readonly #institutions = new Map<InstitutionContentId, InstitutionContent>()
get regions(): ReadonlyMap<RegionId, RegionContent> {
return this.#regions
}
get cities(): ReadonlyMap<CityId, CityContent> {
return this.#cities
}
get institutions(): ReadonlyMap<InstitutionContentId, InstitutionContent> {
return this.#institutions
}
addRegion(region: RegionContent): void {
this.#regions.set(region.id, region)
}
addCity(city: CityContent): void {
this.#cities.set(city.id, city)
}
addInstitution(institution: InstitutionContent): void {
this.#institutions.set(institution.id, institution)
}
region(id: RegionId): RegionContent {
const value = this.#regions.get(id)
if (value === undefined) throw new Error(`Unknown region: ${id}`)
return value
}
city(id: CityId): CityContent {
const value = this.#cities.get(id)
if (value === undefined) throw new Error(`Unknown city: ${id}`)
return value
}
institution(id: InstitutionContentId): InstitutionContent {
const value = this.#institutions.get(id)
if (value === undefined) throw new Error(`Unknown institution: ${id}`)
return value
}
}
/** Per-chunk callback used to advance a progress bar as loading proceeds. */
export type ChunkProgressCallback = (loaded: number, total: number) => void
/**
* Populate a fresh registry from the supplied manifest. Fetches every
* chunk whose id begins with a world-content prefix; ignores unknown
* prefixes so the registry can grow without breaking older clients.
* Chunks load in parallel; the callback reports completion order, not
* start order.
*/
export async function populateFromManifest(
manifest: ContentManifest,
baseUrl: string,
onProgress?: ChunkProgressCallback
): Promise<ContentRegistry> {
const registry = new ContentRegistry()
const entries = Object.entries(manifest.chunks)
const total = entries.length
let loaded = 0
await Promise.all(
entries.map(async ([chunkId]) => {
const { data } = await loadChunk(chunkId, manifest, baseUrl)
if (chunkId.startsWith('world/regions/')) {
registry.addRegion(data as unknown as RegionContent)
} else if (chunkId.startsWith('world/cities/')) {
registry.addCity(data as unknown as CityContent)
} else if (chunkId.startsWith('world/institutions/')) {
registry.addInstitution(data as unknown as InstitutionContent)
}
loaded += 1
onProgress?.(loaded, total)
})
)
return registry
}
|