Skip to main content

tilewright_source/
index.rs

1// Copyright 2026 Seraphina Studios
2// SPDX-License-Identifier: MIT
3
4//! The references of a world: what each one names, from where, and where
5//! in which file it is written.
6//!
7//! [`load_indexed_in`](crate::load_indexed_in) loads a tree as
8//! [`load_edited_in`](crate::load_edited_in) does and also returns a
9//! [`ReferenceIndex`] of every reference the loader checked or followed.
10//! The other loads record nothing.
11//!
12//! A reference is a value that names something else of the world by its
13//! identity: an object's prototype or render layer, a portal's plane and
14//! anchor, a value of a schema `reference` field, a zone's parent, a
15//! plane's default zone, a palette entry, a stairwell step's prototype,
16//! what a transitions document names, a schema field's definition type,
17//! a profile ground layer's render layer, and the paths and identities
18//! by which the manifest, the profile and the `defs/` layout tie files
19//! together (see [`Role`]). Values of `json` fields and `custom` objects
20//! are not looked into, so a name written there is not a reference here,
21//! and neither is anything a game's own code reads from them. Nothing in
22//! the format references a stairwell or an object uid, so neither is
23//! ever a [`Target`].
24//!
25//! A declaration that names another item is a reference: a definition's
26//! `type` names its definition type, and a chunk's `plane` names its
27//! plane. An item's own identity, such as a plane's `id` or a
28//! definition's `id`, is the item rather than a reference to it, so an
29//! item's own files and identity are found from the [`World`](crate::World)
30//! (its key, [`Chunk::path`](crate::Chunk::path), its plane's
31//! directory), not from the index.
32//!
33//! An indexed load records nothing once the load has a problem, since it
34//! then returns diagnostics and no index, and it refuses, with a
35//! `source.budget` diagnostic, a tree whose references, strings or holders
36//! would not fit the index's 32-bit numbering.
37
38use crate::schema::{FieldPath, FieldType, Schema};
39use std::{cmp::Ordering, collections::HashMap, fmt, ops::Range, sync::Arc};
40
41/// A chunk's key in [`Plane::chunks`](crate::Plane::chunks):
42/// `(layer, cx, cy)`.
43pub type ChunkKey = (i32, i64, i64);
44
45/// What a reference names.
46///
47/// Identities are as the source writes them. Anchors and zones belong to
48/// a plane, so they are qualified by the plane's ID; a portal that omits
49/// its plane names an anchor of its own plane (see
50/// [`Role::PortalAnchor`]).
51#[non_exhaustive]
52#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
53pub enum Target<'a> {
54    /// The definition `defs/<kind>/<id>.jsonc`.
55    Definition { kind: &'a str, id: &'a str },
56    /// A definition type the profile declares.
57    DefinitionType(&'a str),
58    /// A plane the manifest lists.
59    Plane(&'a str),
60    /// An anchor of a plane.
61    Anchor { plane: &'a str, id: &'a str },
62    /// A zone of a plane.
63    Zone { plane: &'a str, id: &'a str },
64    /// A render layer of the profile.
65    RenderLayer(&'a str),
66    /// A ground layer of the profile.
67    GroundLayer(&'a str),
68    /// A terrain of the profile's transitions document.
69    Terrain(&'a str),
70    /// A file, by its root-relative `/`-separated path, as the path that
71    /// names it resolves.
72    File(&'a str),
73}
74
75/// Which kind of reference it is: what holds it, and what it is for.
76#[non_exhaustive]
77#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
78pub enum Role<'a> {
79    /// The manifest's `profile` path, to the profile document.
80    ManifestProfile,
81    /// An entry of the manifest's `planes`, to a plane.
82    ManifestPlane,
83    /// A profile `ground_layers` entry, to the render layer it must be.
84    ProfileGroundLayer,
85    /// A schema path of the profile (a definition type's `schema`,
86    /// `object_fields` or `zone_properties`), to the schema document.
87    ProfileSchema,
88    /// The profile's `transitions` path, to the transitions document.
89    ProfileTransitions,
90    /// A `reference` field type of a schema, to its definition type.
91    /// `field` is the field's path in the schema: names joined by `.`,
92    /// with `[]` for a list's items, as in `loot.entries[].item`.
93    SchemaTarget { field: &'a str },
94    /// A definition's `type`, to the definition type its directory
95    /// names.
96    DefinitionType,
97    /// A value of a schema `reference` field: of a definition's `fields`,
98    /// an object's `fields` or a zone's `properties`. `field` is the
99    /// field's path in the schema, as for [`Role::SchemaTarget`]; the
100    /// value's span tells which list item holds it.
101    Field { field: &'a str },
102    /// A zone's `parent`.
103    ZoneParent,
104    /// A plane's `default_zone`.
105    DefaultZone,
106    /// An entry of a chunk's ground grid palette for the ground layer
107    /// `layer`, to the prototype it paints.
108    GroundPalette { layer: &'a str },
109    /// An entry of a chunk's zone grid palette.
110    ZonePalette,
111    /// A chunk's `plane`, which names the plane the chunk belongs to.
112    ChunkPlane,
113    /// An object's `proto`.
114    Proto,
115    /// An object's `layer`, to a render layer.
116    Layer,
117    /// A portal's `plane`.
118    PortalPlane,
119    /// A portal's `anchor`. When the portal omits its plane,
120    /// `implicit_plane` is `true`, the target's plane is the portal's own,
121    /// and the span is the anchor's: no `plane` is written.
122    PortalAnchor { implicit_plane: bool },
123    /// A stairwell step's `proto`.
124    StairwellStep,
125    /// The transitions document's terrain `layer`, to a ground layer.
126    TerrainLayer,
127    /// An entry of a terrain's `protos`.
128    TerrainProto,
129    /// A transition's `layer`, to the ground layer it draws into.
130    TransitionLayer,
131    /// An entry of a transition's `with`, to a terrain.
132    TransitionBlend,
133    /// A prototype of a transition's `tiles`.
134    TransitionTile,
135}
136
137/// What holds a reference.
138#[non_exhaustive]
139#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
140pub enum Holder<'a> {
141    /// `world.jsonc`.
142    Manifest,
143    /// The profile document.
144    Profile,
145    /// A schema document, by its root-relative path.
146    Schema { path: &'a str },
147    /// The profile's transitions document.
148    Transitions,
149    /// A definition.
150    Definition { kind: &'a str, id: &'a str },
151    /// A plane's `plane.jsonc`.
152    Plane { plane: &'a str },
153    /// A zone of a plane's `zones.jsonc`.
154    Zone { plane: &'a str, zone: &'a str },
155    /// A stairwell of a plane's `stairwells.jsonc`.
156    Stairwell { plane: &'a str, stairwell: &'a str },
157    /// A chunk itself: its grid palettes.
158    Chunk { plane: &'a str, chunk: ChunkKey },
159    /// An object of a chunk, inventory entries included. `ordinal` is the
160    /// object's position in [`Chunk::depth_first`](crate::Chunk::depth_first),
161    /// its index in the chunk.
162    Object {
163        plane: &'a str,
164        chunk: ChunkKey,
165        ordinal: usize,
166    },
167}
168
169/// The references of one load, interned, with lookups by target and by
170/// holder; see the [module documentation](self) for what is a reference.
171///
172/// Every reference is kept once. [`ReferenceIndex::iter`] and
173/// [`ReferenceIndex::to`] give references grouped by target, targets in
174/// a fixed order (by kind, then by their identities' bytes), and within a
175/// target in source order: by file path, then by span. [`ReferenceIndex::from`]
176/// gives a holder's references in source order. Both lookups are binary
177/// searches, so their cost does not grow with the references they skip.
178#[derive(Clone, Default, Eq, PartialEq)]
179pub struct ReferenceIndex {
180    /// Every string, sorted and unique, so that comparing two symbols
181    /// compares their strings.
182    names: Vec<Box<str>>,
183    /// Every holder, sorted.
184    holders: Vec<HolderEntry>,
185    /// Every reference, sorted by target.
186    records: Vec<Record>,
187    /// `records` positions, sorted by holder.
188    by_holder: Vec<u32>,
189}
190
191/// One reference of a [`ReferenceIndex`].
192#[derive(Clone, Copy)]
193pub struct Reference<'i> {
194    index: &'i ReferenceIndex,
195    record: &'i Record,
196}
197
198impl<'i> Reference<'i> {
199    /// What it names.
200    #[must_use]
201    pub fn target(&self) -> Target<'i> {
202        self.index.target(&self.record.target())
203    }
204
205    /// Which kind of reference it is.
206    #[must_use]
207    pub fn role(&self) -> Role<'i> {
208        self.index.role(self.record)
209    }
210
211    /// What holds it.
212    #[must_use]
213    pub fn holder(&self) -> Holder<'i> {
214        self.index.holder(self.record.holder, self.record.ordinal)
215    }
216
217    /// The root-relative path of the file it is written in.
218    #[must_use]
219    pub fn file(&self) -> &'i str {
220        self.index.holder_file(self.record.holder)
221    }
222
223    /// Its byte span in the file, as the file was read: the value that
224    /// names the target, quotes included. For a portal anchor whose
225    /// plane is implicit, it is the anchor's.
226    #[must_use]
227    pub fn span(&self) -> Range<usize> {
228        widen(self.record.start)..widen(self.record.end)
229    }
230}
231
232impl fmt::Debug for Reference<'_> {
233    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
234        formatter
235            .debug_struct("Reference")
236            .field("target", &self.target())
237            .field("role", &self.role())
238            .field("holder", &self.holder())
239            .field("file", &self.file())
240            .field("span", &self.span())
241            .finish()
242    }
243}
244
245impl fmt::Debug for ReferenceIndex {
246    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
247        formatter.debug_list().entries(self.iter()).finish()
248    }
249}
250
251impl ReferenceIndex {
252    /// How many references there are.
253    #[must_use]
254    pub fn len(&self) -> usize {
255        self.records.len()
256    }
257
258    /// Whether there are none.
259    #[must_use]
260    pub fn is_empty(&self) -> bool {
261        self.records.is_empty()
262    }
263
264    /// Every reference, grouped by target, each group in source order.
265    pub fn iter(&self) -> impl ExactSizeIterator<Item = Reference<'_>> + '_ {
266        self.records.iter().map(|record| Reference {
267            index: self,
268            record,
269        })
270    }
271
272    /// The references to `target`, in source order: none when nothing
273    /// names it, or when it does not exist.
274    pub fn to(&self, target: Target<'_>) -> impl ExactSizeIterator<Item = Reference<'_>> + '_ {
275        let found = self.target_key(target).map_or(&[][..], |key| {
276            let start = self.records.partition_point(|record| record.target() < key);
277            let length = self.records[start..].partition_point(|record| record.target() == key);
278            &self.records[start..start + length]
279        });
280        found.iter().map(|record| Reference {
281            index: self,
282            record,
283        })
284    }
285
286    /// The references `holder` holds, in source order. An object's are
287    /// its own, not its inventory's; a chunk's are its palettes', not its
288    /// objects'.
289    pub fn from(&self, holder: Holder<'_>) -> impl ExactSizeIterator<Item = Reference<'_>> + '_ {
290        let found = self
291            .holder_key(holder)
292            .map_or(&[][..], |(holder, ordinal)| {
293                let key = (holder, ordinal);
294                let at = |position: &u32| {
295                    let record = &self.records[widen(*position)];
296                    (record.holder, record.ordinal)
297                };
298                let start = self
299                    .by_holder
300                    .partition_point(|position| at(position) < key);
301                let length =
302                    self.by_holder[start..].partition_point(|position| at(position) == key);
303                &self.by_holder[start..start + length]
304            });
305        found.iter().map(|position| Reference {
306            index: self,
307            record: &self.records[widen(*position)],
308        })
309    }
310
311    /// The bytes the index holds, for measurement: its tables and its
312    /// strings.
313    #[cfg(test)]
314    pub(crate) fn heap_bytes(&self) -> usize {
315        self.names.capacity() * size_of::<Box<str>>()
316            + self.names.iter().map(|name| name.len()).sum::<usize>()
317            + self.holders.capacity() * size_of::<HolderEntry>()
318            + self.records.capacity() * size_of::<Record>()
319            + self.by_holder.capacity() * size_of::<u32>()
320    }
321
322    fn name(&self, symbol: u32) -> &str {
323        self.names.get(widen(symbol)).map_or("", |name| name)
324    }
325
326    fn symbol(&self, text: &str) -> Option<u32> {
327        let position = self
328            .names
329            .binary_search_by(|name| (**name).cmp(text))
330            .ok()?;
331        u32::try_from(position).ok()
332    }
333
334    fn target(&self, key: &TargetKey) -> Target<'_> {
335        let (a, b) = (self.name(key.a), self.name(key.b));
336        match key.kind {
337            TargetKind::Definition => Target::Definition { kind: a, id: b },
338            TargetKind::DefinitionType => Target::DefinitionType(a),
339            TargetKind::Plane => Target::Plane(a),
340            TargetKind::Anchor => Target::Anchor { plane: a, id: b },
341            TargetKind::Zone => Target::Zone { plane: a, id: b },
342            TargetKind::RenderLayer => Target::RenderLayer(a),
343            TargetKind::GroundLayer => Target::GroundLayer(a),
344            TargetKind::Terrain => Target::Terrain(a),
345            TargetKind::File => Target::File(a),
346        }
347    }
348
349    fn target_key(&self, target: Target<'_>) -> Option<TargetKey> {
350        let (kind, a, b) = split_target(target);
351        Some(TargetKey {
352            kind,
353            a: self.symbol(a)?,
354            b: if b.is_empty() { 0 } else { self.symbol(b)? },
355        })
356    }
357
358    fn role(&self, record: &Record) -> Role<'_> {
359        let argument = self.name(record.argument);
360        match record.role {
361            RoleKind::ManifestProfile => Role::ManifestProfile,
362            RoleKind::ManifestPlane => Role::ManifestPlane,
363            RoleKind::ProfileGroundLayer => Role::ProfileGroundLayer,
364            RoleKind::ProfileSchema => Role::ProfileSchema,
365            RoleKind::ProfileTransitions => Role::ProfileTransitions,
366            RoleKind::SchemaTarget => Role::SchemaTarget { field: argument },
367            RoleKind::DefinitionType => Role::DefinitionType,
368            RoleKind::Field => Role::Field { field: argument },
369            RoleKind::ZoneParent => Role::ZoneParent,
370            RoleKind::DefaultZone => Role::DefaultZone,
371            RoleKind::GroundPalette => Role::GroundPalette { layer: argument },
372            RoleKind::ZonePalette => Role::ZonePalette,
373            RoleKind::ChunkPlane => Role::ChunkPlane,
374            RoleKind::Proto => Role::Proto,
375            RoleKind::Layer => Role::Layer,
376            RoleKind::PortalPlane => Role::PortalPlane,
377            RoleKind::PortalAnchor => Role::PortalAnchor {
378                implicit_plane: record.argument != 0,
379            },
380            RoleKind::StairwellStep => Role::StairwellStep,
381            RoleKind::TerrainLayer => Role::TerrainLayer,
382            RoleKind::TerrainProto => Role::TerrainProto,
383            RoleKind::TransitionLayer => Role::TransitionLayer,
384            RoleKind::TransitionBlend => Role::TransitionBlend,
385            RoleKind::TransitionTile => Role::TransitionTile,
386        }
387    }
388
389    fn holder_entry(&self, holder: u32) -> Option<&HolderEntry> {
390        self.holders.get(widen(holder))
391    }
392
393    fn holder_file(&self, holder: u32) -> &str {
394        self.holder_entry(holder)
395            .map_or("", |entry| self.name(entry.file))
396    }
397
398    fn holder(&self, holder: u32, ordinal: u32) -> Holder<'_> {
399        let Some(entry) = self.holder_entry(holder) else {
400            return Holder::Manifest;
401        };
402        let (a, b) = (self.name(entry.key.a), self.name(entry.key.b));
403        match entry.key.kind {
404            HolderKind::Manifest => Holder::Manifest,
405            HolderKind::Profile => Holder::Profile,
406            HolderKind::Schema => Holder::Schema { path: a },
407            HolderKind::Transitions => Holder::Transitions,
408            HolderKind::Definition => Holder::Definition { kind: a, id: b },
409            HolderKind::Plane => Holder::Plane { plane: a },
410            HolderKind::Zone => Holder::Zone { plane: a, zone: b },
411            HolderKind::Stairwell => Holder::Stairwell {
412                plane: a,
413                stairwell: b,
414            },
415            HolderKind::Chunk if ordinal == NO_ORDINAL => Holder::Chunk {
416                plane: a,
417                chunk: entry.key.chunk,
418            },
419            HolderKind::Chunk => Holder::Object {
420                plane: a,
421                chunk: entry.key.chunk,
422                ordinal: widen(ordinal),
423            },
424        }
425    }
426
427    /// The position of `holder` in `holders`, and the ordinal its
428    /// references carry.
429    fn holder_key(&self, holder: Holder<'_>) -> Option<(u32, u32)> {
430        let (kind, a, b, chunk, ordinal) = split_holder(holder);
431        let ordinal = match ordinal {
432            Some(ordinal) => u32::try_from(ordinal).ok().filter(|o| *o != NO_ORDINAL)?,
433            None => NO_ORDINAL,
434        };
435        let key = HolderKey {
436            kind,
437            a: if a.is_empty() { 0 } else { self.symbol(a)? },
438            b: if b.is_empty() { 0 } else { self.symbol(b)? },
439            chunk,
440        };
441        let position = self
442            .holders
443            .binary_search_by(|entry| entry.key.cmp(&key))
444            .ok()?;
445        Some((u32::try_from(position).ok()?, ordinal))
446    }
447}
448
449/// `usize` from `u32`, which every supported platform holds.
450fn widen(value: u32) -> usize {
451    usize::try_from(value).unwrap_or(usize::MAX)
452}
453
454/// The ordinal of a reference a chunk holds itself, not one of its
455/// objects.
456const NO_ORDINAL: u32 = u32::MAX;
457
458#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
459enum TargetKind {
460    Definition,
461    DefinitionType,
462    Plane,
463    Anchor,
464    Zone,
465    RenderLayer,
466    GroundLayer,
467    Terrain,
468    File,
469}
470
471/// A target's kind and its one or two identities; the second is empty
472/// for kinds that have one.
473fn split_target(target: Target<'_>) -> (TargetKind, &str, &str) {
474    match target {
475        Target::Definition { kind, id } => (TargetKind::Definition, kind, id),
476        Target::DefinitionType(kind) => (TargetKind::DefinitionType, kind, ""),
477        Target::Plane(plane) => (TargetKind::Plane, plane, ""),
478        Target::Anchor { plane, id } => (TargetKind::Anchor, plane, id),
479        Target::Zone { plane, id } => (TargetKind::Zone, plane, id),
480        Target::RenderLayer(layer) => (TargetKind::RenderLayer, layer, ""),
481        Target::GroundLayer(layer) => (TargetKind::GroundLayer, layer, ""),
482        Target::Terrain(terrain) => (TargetKind::Terrain, terrain, ""),
483        Target::File(path) => (TargetKind::File, path, ""),
484    }
485}
486
487#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
488enum RoleKind {
489    ManifestProfile,
490    ManifestPlane,
491    ProfileGroundLayer,
492    ProfileSchema,
493    ProfileTransitions,
494    SchemaTarget,
495    DefinitionType,
496    Field,
497    ZoneParent,
498    DefaultZone,
499    GroundPalette,
500    ZonePalette,
501    ChunkPlane,
502    Proto,
503    Layer,
504    PortalPlane,
505    PortalAnchor,
506    StairwellStep,
507    TerrainLayer,
508    TerrainProto,
509    TransitionLayer,
510    TransitionBlend,
511    TransitionTile,
512}
513
514/// What a role carries: a string, a flag, or nothing.
515enum Argument<'a> {
516    None,
517    Text(&'a str),
518    Flag(bool),
519}
520
521fn split_role(role: Role<'_>) -> (RoleKind, Argument<'_>) {
522    match role {
523        Role::ManifestProfile => (RoleKind::ManifestProfile, Argument::None),
524        Role::ManifestPlane => (RoleKind::ManifestPlane, Argument::None),
525        Role::ProfileGroundLayer => (RoleKind::ProfileGroundLayer, Argument::None),
526        Role::ProfileSchema => (RoleKind::ProfileSchema, Argument::None),
527        Role::ProfileTransitions => (RoleKind::ProfileTransitions, Argument::None),
528        Role::SchemaTarget { field } => (RoleKind::SchemaTarget, Argument::Text(field)),
529        Role::DefinitionType => (RoleKind::DefinitionType, Argument::None),
530        Role::Field { field } => (RoleKind::Field, Argument::Text(field)),
531        Role::ZoneParent => (RoleKind::ZoneParent, Argument::None),
532        Role::DefaultZone => (RoleKind::DefaultZone, Argument::None),
533        Role::GroundPalette { layer } => (RoleKind::GroundPalette, Argument::Text(layer)),
534        Role::ZonePalette => (RoleKind::ZonePalette, Argument::None),
535        Role::ChunkPlane => (RoleKind::ChunkPlane, Argument::None),
536        Role::Proto => (RoleKind::Proto, Argument::None),
537        Role::Layer => (RoleKind::Layer, Argument::None),
538        Role::PortalPlane => (RoleKind::PortalPlane, Argument::None),
539        Role::PortalAnchor { implicit_plane } => {
540            (RoleKind::PortalAnchor, Argument::Flag(implicit_plane))
541        }
542        Role::StairwellStep => (RoleKind::StairwellStep, Argument::None),
543        Role::TerrainLayer => (RoleKind::TerrainLayer, Argument::None),
544        Role::TerrainProto => (RoleKind::TerrainProto, Argument::None),
545        Role::TransitionLayer => (RoleKind::TransitionLayer, Argument::None),
546        Role::TransitionBlend => (RoleKind::TransitionBlend, Argument::None),
547        Role::TransitionTile => (RoleKind::TransitionTile, Argument::None),
548    }
549}
550
551/// Whether a role's argument is a symbol, which interning renumbers.
552const fn argument_is_symbol(role: RoleKind) -> bool {
553    matches!(
554        role,
555        RoleKind::SchemaTarget | RoleKind::Field | RoleKind::GroundPalette
556    )
557}
558
559#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
560enum HolderKind {
561    Manifest,
562    Profile,
563    Schema,
564    Transitions,
565    Definition,
566    Plane,
567    Zone,
568    Stairwell,
569    /// A chunk, and its objects, which their ordinal tells apart.
570    Chunk,
571}
572
573/// A holder's kind, its identities (empty where it has fewer), its chunk
574/// key (zero where it has none) and, for an object, its ordinal.
575fn split_holder(holder: Holder<'_>) -> (HolderKind, &str, &str, ChunkKey, Option<usize>) {
576    match holder {
577        Holder::Manifest => (HolderKind::Manifest, "", "", (0, 0, 0), None),
578        Holder::Profile => (HolderKind::Profile, "", "", (0, 0, 0), None),
579        Holder::Schema { path } => (HolderKind::Schema, path, "", (0, 0, 0), None),
580        Holder::Transitions => (HolderKind::Transitions, "", "", (0, 0, 0), None),
581        Holder::Definition { kind, id } => (HolderKind::Definition, kind, id, (0, 0, 0), None),
582        Holder::Plane { plane } => (HolderKind::Plane, plane, "", (0, 0, 0), None),
583        Holder::Zone { plane, zone } => (HolderKind::Zone, plane, zone, (0, 0, 0), None),
584        Holder::Stairwell { plane, stairwell } => {
585            (HolderKind::Stairwell, plane, stairwell, (0, 0, 0), None)
586        }
587        Holder::Chunk { plane, chunk } => (HolderKind::Chunk, plane, "", chunk, None),
588        Holder::Object {
589            plane,
590            chunk,
591            ordinal,
592        } => (HolderKind::Chunk, plane, "", chunk, Some(ordinal)),
593    }
594}
595
596/// A target by kind and symbols; `b` is 0 for kinds with one identity.
597#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
598struct TargetKey {
599    kind: TargetKind,
600    a: u32,
601    b: u32,
602}
603
604#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
605struct HolderKey {
606    kind: HolderKind,
607    a: u32,
608    b: u32,
609    chunk: ChunkKey,
610}
611
612#[derive(Clone, Copy, Debug, Eq, PartialEq)]
613struct HolderEntry {
614    key: HolderKey,
615    /// The file the holder's references are written in.
616    file: u32,
617}
618
619/// One reference: 32 bytes. The target's parts are fields of their own,
620/// so that no padding comes between them.
621#[derive(Clone, Copy, Debug, Eq, PartialEq)]
622struct Record {
623    kind: TargetKind,
624    role: RoleKind,
625    a: u32,
626    b: u32,
627    /// A symbol, or a flag, by role: see [`argument_is_symbol`].
628    argument: u32,
629    /// A position in `holders`.
630    holder: u32,
631    /// The object's ordinal in its chunk, or [`NO_ORDINAL`].
632    ordinal: u32,
633    start: u32,
634    end: u32,
635}
636
637impl Record {
638    const fn target(&self) -> TargetKey {
639        TargetKey {
640            kind: self.kind,
641            a: self.a,
642            b: self.b,
643        }
644    }
645}
646
647/// One reference as the loader checks or follows it.
648#[derive(Clone, Copy, Debug)]
649pub(crate) struct Site<'a> {
650    pub target: Target<'a>,
651    pub role: Role<'a>,
652    pub holder: Holder<'a>,
653    /// The file it is written in, which its holder's references share.
654    pub file: &'a str,
655    pub start: usize,
656    pub end: usize,
657}
658
659/// Most references, distinct strings or holders an index holds: each is
660/// numbered by a `u32`, and `u32::MAX` is kept as a marker.
661const MAXIMUM_COUNT: u32 = u32::MAX - 1;
662
663#[cfg(test)]
664thread_local! {
665    /// References recorded on this thread, so tests can tell when a load
666    /// records and when it stops.
667    pub(crate) static RECORDED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
668    /// A lower count limit for the recorders a test makes on its thread.
669    pub(crate) static TEST_MAXIMUM_COUNT: std::cell::Cell<Option<u32>> =
670        const { std::cell::Cell::new(None) };
671}
672
673/// Collects the references of one load, interning their strings as it
674/// goes; [`Recorder::finish`] sorts them into a [`ReferenceIndex`]. A
675/// reference that would not fit the index's packed fields makes it
676/// refuse instead ([`Recorder::overflowed`]): nothing is ever cut short.
677pub(crate) struct Recorder {
678    symbols: HashMap<Box<str>, u32>,
679    names: Vec<Box<str>>,
680    holders: HashMap<HolderKey, u32>,
681    holder_entries: Vec<HolderEntry>,
682    records: Vec<Record>,
683    /// The interned field path of each `reference` field type a value had,
684    /// by the address of the schema that holds the type and of the type.
685    /// Each entry keeps its schema alive, so while the recorder lives no
686    /// other schema can take that address, and the types inside it, which
687    /// an `Arc` never moves or changes, keep theirs: a key cannot go stale.
688    fields: HashMap<(usize, usize), (Arc<Schema>, u32)>,
689    /// Most records, strings or holders.
690    maximum: u32,
691    /// Something did not fit, and nothing more is recorded.
692    overflowed: bool,
693}
694
695impl Default for Recorder {
696    fn default() -> Self {
697        #[cfg(test)]
698        let maximum = TEST_MAXIMUM_COUNT.get().unwrap_or(MAXIMUM_COUNT);
699        #[cfg(not(test))]
700        let maximum = MAXIMUM_COUNT;
701        // Symbol 0 is the empty string, which absent parts name, whatever
702        // is recorded first.
703        Self {
704            symbols: HashMap::from([(Box::from(""), 0)]),
705            names: vec![Box::from("")],
706            holders: HashMap::new(),
707            holder_entries: Vec::new(),
708            records: Vec::new(),
709            fields: HashMap::new(),
710            maximum,
711            overflowed: false,
712        }
713    }
714}
715
716/// `value` as a `u32` below `u32::MAX`, which is kept as a marker; `None`
717/// when it does not fit.
718pub(crate) fn packed(value: usize) -> Option<u32> {
719    u32::try_from(value)
720        .ok()
721        .filter(|value| *value <= MAXIMUM_COUNT)
722}
723
724/// `u32` from a count the recorder has kept within [`MAXIMUM_COUNT`].
725fn narrow(value: usize) -> u32 {
726    packed(value).unwrap_or(MAXIMUM_COUNT)
727}
728
729impl Recorder {
730    /// Whether a reference did not fit, so the index is refused.
731    pub const fn overflowed(&self) -> bool {
732        self.overflowed
733    }
734
735    /// `value` packed, or `None` after noting that it did not fit.
736    fn fit(&mut self, value: Option<u32>) -> Option<u32> {
737        if value.is_none() {
738            self.overflowed = true;
739        }
740        value
741    }
742
743    /// The next number of a table of `length` entries.
744    fn next(&mut self, length: usize) -> Option<u32> {
745        let maximum = self.maximum;
746        self.fit(packed(length).filter(|next| *next < maximum))
747    }
748
749    fn intern(&mut self, text: &str) -> Option<u32> {
750        if let Some(symbol) = self.symbols.get(text) {
751            return Some(*symbol);
752        }
753        let symbol = self.next(self.names.len())?;
754        self.names.push(text.into());
755        self.symbols.insert(text.into(), symbol);
756        Some(symbol)
757    }
758
759    /// Interns a part that may be empty, which symbol 0, the empty string,
760    /// stands for.
761    fn intern_part(&mut self, text: &str) -> Option<u32> {
762        if text.is_empty() {
763            Some(0)
764        } else {
765            self.intern(text)
766        }
767    }
768
769    pub fn record(&mut self, site: &Site<'_>) {
770        let _ = self.try_record(site, None);
771    }
772
773    /// Records a value of the `reference` field type `field_type` of
774    /// `schema`, at field path `path`, which is written out and interned
775    /// only the first time a value of that type is recorded. `site`'s role
776    /// is [`Role::Field`]; its path is taken from here.
777    pub fn record_field(
778        &mut self,
779        site: &Site<'_>,
780        (schema, field_type): (&Arc<Schema>, &FieldType),
781        path: &FieldPath<'_>,
782    ) {
783        if self.overflowed {
784            return;
785        }
786        let key = (
787            Arc::as_ptr(schema).addr(),
788            std::ptr::from_ref(field_type).addr(),
789        );
790        let symbol = if let Some((_, symbol)) = self.fields.get(&key) {
791            *symbol
792        } else {
793            let Some(symbol) = self.intern(&path.render()) else {
794                return;
795            };
796            self.fields.insert(key, (Arc::clone(schema), symbol));
797            symbol
798        };
799        let _ = self.try_record(site, Some(symbol));
800    }
801
802    /// Records `site`, with `argument` as its role's symbol when given.
803    /// `None` when it did not fit, which refuses the index.
804    fn try_record(&mut self, site: &Site<'_>, argument: Option<u32>) -> Option<()> {
805        if self.overflowed {
806            return None;
807        }
808        let (kind, a, b) = split_target(site.target);
809        let target = TargetKey {
810            kind,
811            a: self.intern(a)?,
812            b: self.intern_part(b)?,
813        };
814        let (role, text) = split_role(site.role);
815        let argument = match (argument, text) {
816            (Some(symbol), _) => symbol,
817            (None, Argument::None) => 0,
818            (None, Argument::Text(text)) => self.intern(text)?,
819            (None, Argument::Flag(flag)) => u32::from(flag),
820        };
821        let (holder_kind, holder_a, holder_b, chunk, ordinal) = split_holder(site.holder);
822        let key = HolderKey {
823            kind: holder_kind,
824            a: self.intern_part(holder_a)?,
825            b: self.intern_part(holder_b)?,
826            chunk,
827        };
828        let file = self.intern(site.file)?;
829        let holder = if let Some(holder) = self.holders.get(&key) {
830            *holder
831        } else {
832            let position = self.next(self.holder_entries.len())?;
833            self.holder_entries.push(HolderEntry { key, file });
834            self.holders.insert(key, position);
835            position
836        };
837        let ordinal = match ordinal {
838            None => NO_ORDINAL,
839            // An ordinal is below the marker, which means none.
840            Some(ordinal) => self.fit(packed(ordinal).filter(|o| *o < NO_ORDINAL))?,
841        };
842        let (start, end) = (packed(site.start), packed(site.end));
843        let (start, end) = (self.fit(start)?, self.fit(end)?);
844        self.next(self.records.len())?;
845        self.records.push(Record {
846            kind: target.kind,
847            a: target.a,
848            b: target.b,
849            role,
850            argument,
851            holder,
852            ordinal,
853            start,
854            end,
855        });
856        #[cfg(test)]
857        RECORDED.with(|recorded| recorded.set(recorded.get() + 1));
858        Some(())
859    }
860
861    /// The index: strings sorted, every symbol renumbered to match, and
862    /// the references sorted by target and by holder.
863    pub fn finish(self) -> ReferenceIndex {
864        let Self {
865            names,
866            holder_entries,
867            records,
868            ..
869        } = self;
870        let (names, renumbered) = sorted(names);
871        let symbol = |old: u32| renumbered.get(widen(old)).copied().unwrap_or(0);
872
873        let renumber_holder = |key: HolderKey| HolderKey {
874            a: symbol(key.a),
875            b: symbol(key.b),
876            ..key
877        };
878        let mut holders: Vec<(u32, HolderEntry)> = holder_entries
879            .into_iter()
880            .enumerate()
881            .map(|(old, entry)| {
882                (
883                    narrow(old),
884                    HolderEntry {
885                        key: renumber_holder(entry.key),
886                        file: symbol(entry.file),
887                    },
888                )
889            })
890            .collect();
891        holders.sort_by_key(|(_, entry)| entry.key);
892        let mut holder_position = vec![0; holders.len()];
893        for (new, (old, _)) in holders.iter().enumerate() {
894            holder_position[widen(*old)] = narrow(new);
895        }
896        let holders: Vec<HolderEntry> = holders.into_iter().map(|(_, entry)| entry).collect();
897
898        let mut records: Vec<Record> = records
899            .into_iter()
900            .map(|record| Record {
901                a: symbol(record.a),
902                b: symbol(record.b),
903                argument: if argument_is_symbol(record.role) {
904                    symbol(record.argument)
905                } else {
906                    record.argument
907                },
908                holder: holder_position
909                    .get(widen(record.holder))
910                    .copied()
911                    .unwrap_or(0),
912                ..record
913            })
914            .collect();
915        let file = |record: &Record| {
916            holders
917                .get(widen(record.holder))
918                .map_or(0, |entry| entry.file)
919        };
920        // Within a target: source order, then whatever else tells two
921        // references apart, so the order is total.
922        let source_order = |a: &Record, b: &Record| -> Ordering {
923            (
924                file(a),
925                a.start,
926                a.end,
927                a.role,
928                a.argument,
929                a.holder,
930                a.ordinal,
931            )
932                .cmp(&(
933                    file(b),
934                    b.start,
935                    b.end,
936                    b.role,
937                    b.argument,
938                    b.holder,
939                    b.ordinal,
940                ))
941        };
942        records.sort_by(|a, b| a.target().cmp(&b.target()).then_with(|| source_order(a, b)));
943        let mut by_holder: Vec<u32> = (0..narrow(records.len())).collect();
944        by_holder.sort_by(|a, b| {
945            let (a, b) = (&records[widen(*a)], &records[widen(*b)]);
946            (a.holder, a.ordinal)
947                .cmp(&(b.holder, b.ordinal))
948                .then_with(|| source_order(a, b))
949                .then_with(|| a.target().cmp(&b.target()))
950        });
951        // Recording grew the tables by doubling; the index keeps only what
952        // it holds.
953        records.shrink_to_fit();
954        ReferenceIndex {
955            names,
956            holders,
957            records,
958            by_holder,
959        }
960    }
961}
962
963/// `names` sorted, and the new position of each old one.
964fn sorted(names: Vec<Box<str>>) -> (Vec<Box<str>>, Vec<u32>) {
965    let mut order: Vec<u32> = (0..narrow(names.len())).collect();
966    order.sort_by(|a, b| names[widen(*a)].cmp(&names[widen(*b)]));
967    let mut renumbered = vec![0; names.len()];
968    for (new, old) in order.iter().enumerate() {
969        renumbered[widen(*old)] = narrow(new);
970    }
971    let mut slots: Vec<Option<Box<str>>> = names.into_iter().map(Some).collect();
972    let mut names: Vec<Box<str>> = order
973        .iter()
974        .map(|old| slots[widen(*old)].take().unwrap_or_default())
975        .collect();
976    names.shrink_to_fit();
977    (names, renumbered)
978}
979
980/// The diagnostic message for a reference that does not resolve. Each
981/// role words it as the loader always has.
982pub(crate) fn message(site: &Site<'_>) -> String {
983    match (site.role, site.target) {
984        (Role::ZonePalette, Target::Zone { id, .. }) => {
985            format!("zone \"{id}\" is not in this plane's zones.jsonc")
986        }
987        (Role::ZoneParent, Target::Zone { id: parent, .. }) => {
988            let zone = match site.holder {
989                Holder::Zone { zone, .. } => zone,
990                _ => "",
991            };
992            format!("zone \"{zone}\" has unknown parent \"{parent}\"")
993        }
994        (Role::DefaultZone, Target::Zone { id, .. }) => {
995            format!("default zone \"{id}\" is not in zones.jsonc")
996        }
997        (Role::ProfileGroundLayer, Target::RenderLayer(layer)) => {
998            format!("ground layer \"{layer}\" must be a render layer other than height or zone")
999        }
1000        (Role::SchemaTarget { field }, Target::DefinitionType(kind)) => {
1001            format!("field \"{field}\" references unknown definition type \"{kind}\"")
1002        }
1003        (_, Target::Definition { kind, id }) => format!("no {kind} definition \"{id}\""),
1004        (_, Target::Plane(plane)) => format!("no plane \"{plane}\""),
1005        (_, Target::Anchor { plane, id }) => format!("no anchor \"{id}\" in plane \"{plane}\""),
1006        (_, Target::RenderLayer(layer)) => {
1007            format!("\"{layer}\" is not a render layer of the profile")
1008        }
1009        (_, Target::GroundLayer(layer)) => {
1010            format!("\"{layer}\" is not a ground layer of the profile")
1011        }
1012        (_, Target::Terrain(terrain)) => format!("no terrain \"{terrain}\""),
1013        (_, Target::Zone { plane, id }) => format!("no zone \"{id}\" in plane \"{plane}\""),
1014        (_, Target::DefinitionType(kind)) => format!("no definition type \"{kind}\""),
1015        (_, Target::File(path)) => format!("no file {path}"),
1016    }
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021    use super::{Holder, Record, Recorder, Role, Site, Target};
1022
1023    fn site<'a>(target: Target<'a>, holder: Holder<'a>, file: &'a str, start: usize) -> Site<'a> {
1024        Site {
1025            target,
1026            role: Role::Proto,
1027            holder,
1028            file,
1029            start,
1030            end: start + 3,
1031        }
1032    }
1033
1034    #[test]
1035    fn a_value_is_packed_only_below_the_marker() {
1036        use super::packed;
1037        assert_eq!(packed(0), Some(0));
1038        let largest = usize::try_from(u32::MAX - 1).unwrap();
1039        assert_eq!(packed(largest), Some(u32::MAX - 1));
1040        assert_eq!(packed(largest + 1), None);
1041        assert_eq!(packed(usize::MAX), None);
1042    }
1043
1044    #[test]
1045    fn a_record_that_does_not_fit_refuses_the_index() {
1046        let mut recorder = Recorder::default();
1047        recorder.record(&Site {
1048            start: usize::MAX,
1049            ..site(Target::Plane("p"), Holder::Manifest, "world.jsonc", 0)
1050        });
1051        assert!(recorder.overflowed());
1052        // Nothing more is recorded once one did not fit.
1053        recorder.record(&site(
1054            Target::Plane("p"),
1055            Holder::Manifest,
1056            "world.jsonc",
1057            0,
1058        ));
1059        assert!(recorder.overflowed());
1060        assert_eq!(recorder.finish().len(), 0);
1061        let mut ordinal = Recorder::default();
1062        ordinal.record(&site(
1063            Target::Plane("p"),
1064            Holder::Object {
1065                plane: "p",
1066                chunk: (0, 0, 0),
1067                ordinal: usize::try_from(u32::MAX).unwrap(),
1068            },
1069            "c.jsonc",
1070            0,
1071        ));
1072        assert!(ordinal.overflowed());
1073    }
1074
1075    /// A field value recorded first must not take symbol 0, which absent
1076    /// parts name.
1077    #[test]
1078    fn a_field_recorded_first_keeps_symbol_zero_empty() {
1079        use crate::schema::{FieldPath, FieldType, Schema};
1080        use std::sync::Arc;
1081        let schema = Arc::new(Schema::new("s.jsonc".into()));
1082        let field_type = FieldType::Reference {
1083            target: "proto".into(),
1084        };
1085        let root = FieldPath::Root;
1086        let path = FieldPath::Field(&root, "f");
1087        let holder = Holder::Object {
1088            plane: "p",
1089            chunk: (0, 0, 0),
1090            ordinal: 0,
1091        };
1092        let mut recorder = Recorder::default();
1093        recorder.record_field(
1094            &Site {
1095                target: Target::Definition {
1096                    kind: "proto",
1097                    id: "x",
1098                },
1099                role: Role::Field { field: "" },
1100                holder,
1101                file: "c.jsonc",
1102                start: 1,
1103                end: 4,
1104            },
1105            (&schema, &field_type),
1106            &path,
1107        );
1108        recorder.record(&Site {
1109            target: Target::Plane("p"),
1110            role: Role::ChunkPlane,
1111            holder: Holder::Chunk {
1112                plane: "p",
1113                chunk: (0, 0, 0),
1114            },
1115            file: "c.jsonc",
1116            start: 10,
1117            end: 13,
1118        });
1119        let index = recorder.finish();
1120        let fields: Vec<Role<'_>> = index.from(holder).map(|found| found.role()).collect();
1121        assert_eq!(fields, [Role::Field { field: "f" }]);
1122        assert_eq!(index.to(Target::Plane("p")).count(), 1);
1123    }
1124
1125    #[test]
1126    fn a_record_is_32_bytes() {
1127        assert_eq!(size_of::<Record>(), 32);
1128    }
1129
1130    #[test]
1131    fn interning_sorts_strings_and_renumbers_every_symbol() {
1132        let mut recorder = Recorder::default();
1133        let chunk = |ordinal| Holder::Object {
1134            plane: "p",
1135            chunk: (0, 0, 0),
1136            ordinal,
1137        };
1138        // Recorded out of order, with strings first seen out of order.
1139        recorder.record(&site(
1140            Target::Definition {
1141                kind: "proto",
1142                id: "zeta",
1143            },
1144            chunk(1),
1145            "b.jsonc",
1146            40,
1147        ));
1148        recorder.record(&site(
1149            Target::Definition {
1150                kind: "proto",
1151                id: "alpha",
1152            },
1153            chunk(0),
1154            "b.jsonc",
1155            10,
1156        ));
1157        recorder.record(&site(
1158            Target::Definition {
1159                kind: "proto",
1160                id: "zeta",
1161            },
1162            Holder::Stairwell {
1163                plane: "p",
1164                stairwell: "s",
1165            },
1166            "a.jsonc",
1167            90,
1168        ));
1169        let index = recorder.finish();
1170        assert_eq!(index.len(), 3);
1171        let order: Vec<(Target<'_>, &str, usize)> = index
1172            .iter()
1173            .map(|reference| (reference.target(), reference.file(), reference.span().start))
1174            .collect();
1175        assert_eq!(
1176            order,
1177            [
1178                (
1179                    Target::Definition {
1180                        kind: "proto",
1181                        id: "alpha"
1182                    },
1183                    "b.jsonc",
1184                    10
1185                ),
1186                (
1187                    Target::Definition {
1188                        kind: "proto",
1189                        id: "zeta"
1190                    },
1191                    "a.jsonc",
1192                    90
1193                ),
1194                (
1195                    Target::Definition {
1196                        kind: "proto",
1197                        id: "zeta"
1198                    },
1199                    "b.jsonc",
1200                    40
1201                ),
1202            ]
1203        );
1204        let from: Vec<usize> = index
1205            .from(chunk(1))
1206            .map(|reference| reference.span().start)
1207            .collect();
1208        assert_eq!(from, [40]);
1209        assert_eq!(index.from(chunk(2)).len(), 0);
1210        assert_eq!(
1211            index
1212                .from(Holder::Chunk {
1213                    plane: "p",
1214                    chunk: (0, 0, 0)
1215                })
1216                .len(),
1217            0
1218        );
1219        assert_eq!(
1220            index
1221                .to(Target::Definition {
1222                    kind: "proto",
1223                    id: "missing"
1224                })
1225                .len(),
1226            0
1227        );
1228        // A string the index holds, as another kind of target.
1229        assert_eq!(index.to(Target::Plane("proto")).len(), 0);
1230    }
1231}