use crate::schema::{FieldPath, FieldType, Schema};
use std::{cmp::Ordering, collections::HashMap, fmt, ops::Range, sync::Arc};
pub type ChunkKey = (i32, i64, i64);
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Target<'a> {
Definition { kind: &'a str, id: &'a str },
DefinitionType(&'a str),
Plane(&'a str),
Anchor { plane: &'a str, id: &'a str },
Zone { plane: &'a str, id: &'a str },
RenderLayer(&'a str),
GroundLayer(&'a str),
Terrain(&'a str),
File(&'a str),
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Role<'a> {
ManifestProfile,
ManifestPlane,
ProfileGroundLayer,
ProfileSchema,
ProfileTransitions,
SchemaTarget { field: &'a str },
DefinitionType,
Field { field: &'a str },
ZoneParent,
DefaultZone,
GroundPalette { layer: &'a str },
ZonePalette,
ChunkPlane,
Proto,
Layer,
PortalPlane,
PortalAnchor { implicit_plane: bool },
StairwellStep,
TerrainLayer,
TerrainProto,
TransitionLayer,
TransitionBlend,
TransitionTile,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Holder<'a> {
Manifest,
Profile,
Schema { path: &'a str },
Transitions,
Definition { kind: &'a str, id: &'a str },
Plane { plane: &'a str },
Zone { plane: &'a str, zone: &'a str },
Stairwell { plane: &'a str, stairwell: &'a str },
Chunk { plane: &'a str, chunk: ChunkKey },
Object {
plane: &'a str,
chunk: ChunkKey,
ordinal: usize,
},
}
#[derive(Clone, Default, Eq, PartialEq)]
pub struct ReferenceIndex {
names: Vec<Box<str>>,
holders: Vec<HolderEntry>,
records: Vec<Record>,
by_holder: Vec<u32>,
}
#[derive(Clone, Copy)]
pub struct Reference<'i> {
index: &'i ReferenceIndex,
record: &'i Record,
}
impl<'i> Reference<'i> {
#[must_use]
pub fn target(&self) -> Target<'i> {
self.index.target(&self.record.target())
}
#[must_use]
pub fn role(&self) -> Role<'i> {
self.index.role(self.record)
}
#[must_use]
pub fn holder(&self) -> Holder<'i> {
self.index.holder(self.record.holder, self.record.ordinal)
}
#[must_use]
pub fn file(&self) -> &'i str {
self.index.holder_file(self.record.holder)
}
#[must_use]
pub fn span(&self) -> Range<usize> {
widen(self.record.start)..widen(self.record.end)
}
}
impl fmt::Debug for Reference<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Reference")
.field("target", &self.target())
.field("role", &self.role())
.field("holder", &self.holder())
.field("file", &self.file())
.field("span", &self.span())
.finish()
}
}
impl fmt::Debug for ReferenceIndex {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.debug_list().entries(self.iter()).finish()
}
}
impl ReferenceIndex {
#[must_use]
pub fn len(&self) -> usize {
self.records.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.records.is_empty()
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = Reference<'_>> + '_ {
self.records.iter().map(|record| Reference {
index: self,
record,
})
}
pub fn to(&self, target: Target<'_>) -> impl ExactSizeIterator<Item = Reference<'_>> + '_ {
let found = self.target_key(target).map_or(&[][..], |key| {
let start = self.records.partition_point(|record| record.target() < key);
let length = self.records[start..].partition_point(|record| record.target() == key);
&self.records[start..start + length]
});
found.iter().map(|record| Reference {
index: self,
record,
})
}
pub fn from(&self, holder: Holder<'_>) -> impl ExactSizeIterator<Item = Reference<'_>> + '_ {
let found = self
.holder_key(holder)
.map_or(&[][..], |(holder, ordinal)| {
let key = (holder, ordinal);
let at = |position: &u32| {
let record = &self.records[widen(*position)];
(record.holder, record.ordinal)
};
let start = self
.by_holder
.partition_point(|position| at(position) < key);
let length =
self.by_holder[start..].partition_point(|position| at(position) == key);
&self.by_holder[start..start + length]
});
found.iter().map(|position| Reference {
index: self,
record: &self.records[widen(*position)],
})
}
#[cfg(test)]
pub(crate) fn heap_bytes(&self) -> usize {
self.names.capacity() * size_of::<Box<str>>()
+ self.names.iter().map(|name| name.len()).sum::<usize>()
+ self.holders.capacity() * size_of::<HolderEntry>()
+ self.records.capacity() * size_of::<Record>()
+ self.by_holder.capacity() * size_of::<u32>()
}
fn name(&self, symbol: u32) -> &str {
self.names.get(widen(symbol)).map_or("", |name| name)
}
fn symbol(&self, text: &str) -> Option<u32> {
let position = self
.names
.binary_search_by(|name| (**name).cmp(text))
.ok()?;
u32::try_from(position).ok()
}
fn target(&self, key: &TargetKey) -> Target<'_> {
let (a, b) = (self.name(key.a), self.name(key.b));
match key.kind {
TargetKind::Definition => Target::Definition { kind: a, id: b },
TargetKind::DefinitionType => Target::DefinitionType(a),
TargetKind::Plane => Target::Plane(a),
TargetKind::Anchor => Target::Anchor { plane: a, id: b },
TargetKind::Zone => Target::Zone { plane: a, id: b },
TargetKind::RenderLayer => Target::RenderLayer(a),
TargetKind::GroundLayer => Target::GroundLayer(a),
TargetKind::Terrain => Target::Terrain(a),
TargetKind::File => Target::File(a),
}
}
fn target_key(&self, target: Target<'_>) -> Option<TargetKey> {
let (kind, a, b) = split_target(target);
Some(TargetKey {
kind,
a: self.symbol(a)?,
b: if b.is_empty() { 0 } else { self.symbol(b)? },
})
}
fn role(&self, record: &Record) -> Role<'_> {
let argument = self.name(record.argument);
match record.role {
RoleKind::ManifestProfile => Role::ManifestProfile,
RoleKind::ManifestPlane => Role::ManifestPlane,
RoleKind::ProfileGroundLayer => Role::ProfileGroundLayer,
RoleKind::ProfileSchema => Role::ProfileSchema,
RoleKind::ProfileTransitions => Role::ProfileTransitions,
RoleKind::SchemaTarget => Role::SchemaTarget { field: argument },
RoleKind::DefinitionType => Role::DefinitionType,
RoleKind::Field => Role::Field { field: argument },
RoleKind::ZoneParent => Role::ZoneParent,
RoleKind::DefaultZone => Role::DefaultZone,
RoleKind::GroundPalette => Role::GroundPalette { layer: argument },
RoleKind::ZonePalette => Role::ZonePalette,
RoleKind::ChunkPlane => Role::ChunkPlane,
RoleKind::Proto => Role::Proto,
RoleKind::Layer => Role::Layer,
RoleKind::PortalPlane => Role::PortalPlane,
RoleKind::PortalAnchor => Role::PortalAnchor {
implicit_plane: record.argument != 0,
},
RoleKind::StairwellStep => Role::StairwellStep,
RoleKind::TerrainLayer => Role::TerrainLayer,
RoleKind::TerrainProto => Role::TerrainProto,
RoleKind::TransitionLayer => Role::TransitionLayer,
RoleKind::TransitionBlend => Role::TransitionBlend,
RoleKind::TransitionTile => Role::TransitionTile,
}
}
fn holder_entry(&self, holder: u32) -> Option<&HolderEntry> {
self.holders.get(widen(holder))
}
fn holder_file(&self, holder: u32) -> &str {
self.holder_entry(holder)
.map_or("", |entry| self.name(entry.file))
}
fn holder(&self, holder: u32, ordinal: u32) -> Holder<'_> {
let Some(entry) = self.holder_entry(holder) else {
return Holder::Manifest;
};
let (a, b) = (self.name(entry.key.a), self.name(entry.key.b));
match entry.key.kind {
HolderKind::Manifest => Holder::Manifest,
HolderKind::Profile => Holder::Profile,
HolderKind::Schema => Holder::Schema { path: a },
HolderKind::Transitions => Holder::Transitions,
HolderKind::Definition => Holder::Definition { kind: a, id: b },
HolderKind::Plane => Holder::Plane { plane: a },
HolderKind::Zone => Holder::Zone { plane: a, zone: b },
HolderKind::Stairwell => Holder::Stairwell {
plane: a,
stairwell: b,
},
HolderKind::Chunk if ordinal == NO_ORDINAL => Holder::Chunk {
plane: a,
chunk: entry.key.chunk,
},
HolderKind::Chunk => Holder::Object {
plane: a,
chunk: entry.key.chunk,
ordinal: widen(ordinal),
},
}
}
fn holder_key(&self, holder: Holder<'_>) -> Option<(u32, u32)> {
let (kind, a, b, chunk, ordinal) = split_holder(holder);
let ordinal = match ordinal {
Some(ordinal) => u32::try_from(ordinal).ok().filter(|o| *o != NO_ORDINAL)?,
None => NO_ORDINAL,
};
let key = HolderKey {
kind,
a: if a.is_empty() { 0 } else { self.symbol(a)? },
b: if b.is_empty() { 0 } else { self.symbol(b)? },
chunk,
};
let position = self
.holders
.binary_search_by(|entry| entry.key.cmp(&key))
.ok()?;
Some((u32::try_from(position).ok()?, ordinal))
}
}
fn widen(value: u32) -> usize {
usize::try_from(value).unwrap_or(usize::MAX)
}
const NO_ORDINAL: u32 = u32::MAX;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
enum TargetKind {
Definition,
DefinitionType,
Plane,
Anchor,
Zone,
RenderLayer,
GroundLayer,
Terrain,
File,
}
fn split_target(target: Target<'_>) -> (TargetKind, &str, &str) {
match target {
Target::Definition { kind, id } => (TargetKind::Definition, kind, id),
Target::DefinitionType(kind) => (TargetKind::DefinitionType, kind, ""),
Target::Plane(plane) => (TargetKind::Plane, plane, ""),
Target::Anchor { plane, id } => (TargetKind::Anchor, plane, id),
Target::Zone { plane, id } => (TargetKind::Zone, plane, id),
Target::RenderLayer(layer) => (TargetKind::RenderLayer, layer, ""),
Target::GroundLayer(layer) => (TargetKind::GroundLayer, layer, ""),
Target::Terrain(terrain) => (TargetKind::Terrain, terrain, ""),
Target::File(path) => (TargetKind::File, path, ""),
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
enum RoleKind {
ManifestProfile,
ManifestPlane,
ProfileGroundLayer,
ProfileSchema,
ProfileTransitions,
SchemaTarget,
DefinitionType,
Field,
ZoneParent,
DefaultZone,
GroundPalette,
ZonePalette,
ChunkPlane,
Proto,
Layer,
PortalPlane,
PortalAnchor,
StairwellStep,
TerrainLayer,
TerrainProto,
TransitionLayer,
TransitionBlend,
TransitionTile,
}
enum Argument<'a> {
None,
Text(&'a str),
Flag(bool),
}
fn split_role(role: Role<'_>) -> (RoleKind, Argument<'_>) {
match role {
Role::ManifestProfile => (RoleKind::ManifestProfile, Argument::None),
Role::ManifestPlane => (RoleKind::ManifestPlane, Argument::None),
Role::ProfileGroundLayer => (RoleKind::ProfileGroundLayer, Argument::None),
Role::ProfileSchema => (RoleKind::ProfileSchema, Argument::None),
Role::ProfileTransitions => (RoleKind::ProfileTransitions, Argument::None),
Role::SchemaTarget { field } => (RoleKind::SchemaTarget, Argument::Text(field)),
Role::DefinitionType => (RoleKind::DefinitionType, Argument::None),
Role::Field { field } => (RoleKind::Field, Argument::Text(field)),
Role::ZoneParent => (RoleKind::ZoneParent, Argument::None),
Role::DefaultZone => (RoleKind::DefaultZone, Argument::None),
Role::GroundPalette { layer } => (RoleKind::GroundPalette, Argument::Text(layer)),
Role::ZonePalette => (RoleKind::ZonePalette, Argument::None),
Role::ChunkPlane => (RoleKind::ChunkPlane, Argument::None),
Role::Proto => (RoleKind::Proto, Argument::None),
Role::Layer => (RoleKind::Layer, Argument::None),
Role::PortalPlane => (RoleKind::PortalPlane, Argument::None),
Role::PortalAnchor { implicit_plane } => {
(RoleKind::PortalAnchor, Argument::Flag(implicit_plane))
}
Role::StairwellStep => (RoleKind::StairwellStep, Argument::None),
Role::TerrainLayer => (RoleKind::TerrainLayer, Argument::None),
Role::TerrainProto => (RoleKind::TerrainProto, Argument::None),
Role::TransitionLayer => (RoleKind::TransitionLayer, Argument::None),
Role::TransitionBlend => (RoleKind::TransitionBlend, Argument::None),
Role::TransitionTile => (RoleKind::TransitionTile, Argument::None),
}
}
const fn argument_is_symbol(role: RoleKind) -> bool {
matches!(
role,
RoleKind::SchemaTarget | RoleKind::Field | RoleKind::GroundPalette
)
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
enum HolderKind {
Manifest,
Profile,
Schema,
Transitions,
Definition,
Plane,
Zone,
Stairwell,
Chunk,
}
fn split_holder(holder: Holder<'_>) -> (HolderKind, &str, &str, ChunkKey, Option<usize>) {
match holder {
Holder::Manifest => (HolderKind::Manifest, "", "", (0, 0, 0), None),
Holder::Profile => (HolderKind::Profile, "", "", (0, 0, 0), None),
Holder::Schema { path } => (HolderKind::Schema, path, "", (0, 0, 0), None),
Holder::Transitions => (HolderKind::Transitions, "", "", (0, 0, 0), None),
Holder::Definition { kind, id } => (HolderKind::Definition, kind, id, (0, 0, 0), None),
Holder::Plane { plane } => (HolderKind::Plane, plane, "", (0, 0, 0), None),
Holder::Zone { plane, zone } => (HolderKind::Zone, plane, zone, (0, 0, 0), None),
Holder::Stairwell { plane, stairwell } => {
(HolderKind::Stairwell, plane, stairwell, (0, 0, 0), None)
}
Holder::Chunk { plane, chunk } => (HolderKind::Chunk, plane, "", chunk, None),
Holder::Object {
plane,
chunk,
ordinal,
} => (HolderKind::Chunk, plane, "", chunk, Some(ordinal)),
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
struct TargetKey {
kind: TargetKind,
a: u32,
b: u32,
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
struct HolderKey {
kind: HolderKind,
a: u32,
b: u32,
chunk: ChunkKey,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct HolderEntry {
key: HolderKey,
file: u32,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct Record {
kind: TargetKind,
role: RoleKind,
a: u32,
b: u32,
argument: u32,
holder: u32,
ordinal: u32,
start: u32,
end: u32,
}
impl Record {
const fn target(&self) -> TargetKey {
TargetKey {
kind: self.kind,
a: self.a,
b: self.b,
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Site<'a> {
pub target: Target<'a>,
pub role: Role<'a>,
pub holder: Holder<'a>,
pub file: &'a str,
pub start: usize,
pub end: usize,
}
const MAXIMUM_COUNT: u32 = u32::MAX - 1;
#[cfg(test)]
thread_local! {
pub(crate) static RECORDED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
pub(crate) static TEST_MAXIMUM_COUNT: std::cell::Cell<Option<u32>> =
const { std::cell::Cell::new(None) };
}
pub(crate) struct Recorder {
symbols: HashMap<Box<str>, u32>,
names: Vec<Box<str>>,
holders: HashMap<HolderKey, u32>,
holder_entries: Vec<HolderEntry>,
records: Vec<Record>,
fields: HashMap<(usize, usize), (Arc<Schema>, u32)>,
maximum: u32,
overflowed: bool,
}
impl Default for Recorder {
fn default() -> Self {
#[cfg(test)]
let maximum = TEST_MAXIMUM_COUNT.get().unwrap_or(MAXIMUM_COUNT);
#[cfg(not(test))]
let maximum = MAXIMUM_COUNT;
Self {
symbols: HashMap::from([(Box::from(""), 0)]),
names: vec![Box::from("")],
holders: HashMap::new(),
holder_entries: Vec::new(),
records: Vec::new(),
fields: HashMap::new(),
maximum,
overflowed: false,
}
}
}
pub(crate) fn packed(value: usize) -> Option<u32> {
u32::try_from(value)
.ok()
.filter(|value| *value <= MAXIMUM_COUNT)
}
fn narrow(value: usize) -> u32 {
packed(value).unwrap_or(MAXIMUM_COUNT)
}
impl Recorder {
pub const fn overflowed(&self) -> bool {
self.overflowed
}
fn fit(&mut self, value: Option<u32>) -> Option<u32> {
if value.is_none() {
self.overflowed = true;
}
value
}
fn next(&mut self, length: usize) -> Option<u32> {
let maximum = self.maximum;
self.fit(packed(length).filter(|next| *next < maximum))
}
fn intern(&mut self, text: &str) -> Option<u32> {
if let Some(symbol) = self.symbols.get(text) {
return Some(*symbol);
}
let symbol = self.next(self.names.len())?;
self.names.push(text.into());
self.symbols.insert(text.into(), symbol);
Some(symbol)
}
fn intern_part(&mut self, text: &str) -> Option<u32> {
if text.is_empty() {
Some(0)
} else {
self.intern(text)
}
}
pub fn record(&mut self, site: &Site<'_>) {
let _ = self.try_record(site, None);
}
pub fn record_field(
&mut self,
site: &Site<'_>,
(schema, field_type): (&Arc<Schema>, &FieldType),
path: &FieldPath<'_>,
) {
if self.overflowed {
return;
}
let key = (
Arc::as_ptr(schema).addr(),
std::ptr::from_ref(field_type).addr(),
);
let symbol = if let Some((_, symbol)) = self.fields.get(&key) {
*symbol
} else {
let Some(symbol) = self.intern(&path.render()) else {
return;
};
self.fields.insert(key, (Arc::clone(schema), symbol));
symbol
};
let _ = self.try_record(site, Some(symbol));
}
fn try_record(&mut self, site: &Site<'_>, argument: Option<u32>) -> Option<()> {
if self.overflowed {
return None;
}
let (kind, a, b) = split_target(site.target);
let target = TargetKey {
kind,
a: self.intern(a)?,
b: self.intern_part(b)?,
};
let (role, text) = split_role(site.role);
let argument = match (argument, text) {
(Some(symbol), _) => symbol,
(None, Argument::None) => 0,
(None, Argument::Text(text)) => self.intern(text)?,
(None, Argument::Flag(flag)) => u32::from(flag),
};
let (holder_kind, holder_a, holder_b, chunk, ordinal) = split_holder(site.holder);
let key = HolderKey {
kind: holder_kind,
a: self.intern_part(holder_a)?,
b: self.intern_part(holder_b)?,
chunk,
};
let file = self.intern(site.file)?;
let holder = if let Some(holder) = self.holders.get(&key) {
*holder
} else {
let position = self.next(self.holder_entries.len())?;
self.holder_entries.push(HolderEntry { key, file });
self.holders.insert(key, position);
position
};
let ordinal = match ordinal {
None => NO_ORDINAL,
Some(ordinal) => self.fit(packed(ordinal).filter(|o| *o < NO_ORDINAL))?,
};
let (start, end) = (packed(site.start), packed(site.end));
let (start, end) = (self.fit(start)?, self.fit(end)?);
self.next(self.records.len())?;
self.records.push(Record {
kind: target.kind,
a: target.a,
b: target.b,
role,
argument,
holder,
ordinal,
start,
end,
});
#[cfg(test)]
RECORDED.with(|recorded| recorded.set(recorded.get() + 1));
Some(())
}
pub fn finish(self) -> ReferenceIndex {
let Self {
names,
holder_entries,
records,
..
} = self;
let (names, renumbered) = sorted(names);
let symbol = |old: u32| renumbered.get(widen(old)).copied().unwrap_or(0);
let renumber_holder = |key: HolderKey| HolderKey {
a: symbol(key.a),
b: symbol(key.b),
..key
};
let mut holders: Vec<(u32, HolderEntry)> = holder_entries
.into_iter()
.enumerate()
.map(|(old, entry)| {
(
narrow(old),
HolderEntry {
key: renumber_holder(entry.key),
file: symbol(entry.file),
},
)
})
.collect();
holders.sort_by_key(|(_, entry)| entry.key);
let mut holder_position = vec![0; holders.len()];
for (new, (old, _)) in holders.iter().enumerate() {
holder_position[widen(*old)] = narrow(new);
}
let holders: Vec<HolderEntry> = holders.into_iter().map(|(_, entry)| entry).collect();
let mut records: Vec<Record> = records
.into_iter()
.map(|record| Record {
a: symbol(record.a),
b: symbol(record.b),
argument: if argument_is_symbol(record.role) {
symbol(record.argument)
} else {
record.argument
},
holder: holder_position
.get(widen(record.holder))
.copied()
.unwrap_or(0),
..record
})
.collect();
let file = |record: &Record| {
holders
.get(widen(record.holder))
.map_or(0, |entry| entry.file)
};
let source_order = |a: &Record, b: &Record| -> Ordering {
(
file(a),
a.start,
a.end,
a.role,
a.argument,
a.holder,
a.ordinal,
)
.cmp(&(
file(b),
b.start,
b.end,
b.role,
b.argument,
b.holder,
b.ordinal,
))
};
records.sort_by(|a, b| a.target().cmp(&b.target()).then_with(|| source_order(a, b)));
let mut by_holder: Vec<u32> = (0..narrow(records.len())).collect();
by_holder.sort_by(|a, b| {
let (a, b) = (&records[widen(*a)], &records[widen(*b)]);
(a.holder, a.ordinal)
.cmp(&(b.holder, b.ordinal))
.then_with(|| source_order(a, b))
.then_with(|| a.target().cmp(&b.target()))
});
records.shrink_to_fit();
ReferenceIndex {
names,
holders,
records,
by_holder,
}
}
}
fn sorted(names: Vec<Box<str>>) -> (Vec<Box<str>>, Vec<u32>) {
let mut order: Vec<u32> = (0..narrow(names.len())).collect();
order.sort_by(|a, b| names[widen(*a)].cmp(&names[widen(*b)]));
let mut renumbered = vec![0; names.len()];
for (new, old) in order.iter().enumerate() {
renumbered[widen(*old)] = narrow(new);
}
let mut slots: Vec<Option<Box<str>>> = names.into_iter().map(Some).collect();
let mut names: Vec<Box<str>> = order
.iter()
.map(|old| slots[widen(*old)].take().unwrap_or_default())
.collect();
names.shrink_to_fit();
(names, renumbered)
}
pub(crate) fn message(site: &Site<'_>) -> String {
match (site.role, site.target) {
(Role::ZonePalette, Target::Zone { id, .. }) => {
format!("zone \"{id}\" is not in this plane's zones.jsonc")
}
(Role::ZoneParent, Target::Zone { id: parent, .. }) => {
let zone = match site.holder {
Holder::Zone { zone, .. } => zone,
_ => "",
};
format!("zone \"{zone}\" has unknown parent \"{parent}\"")
}
(Role::DefaultZone, Target::Zone { id, .. }) => {
format!("default zone \"{id}\" is not in zones.jsonc")
}
(Role::ProfileGroundLayer, Target::RenderLayer(layer)) => {
format!("ground layer \"{layer}\" must be a render layer other than height or zone")
}
(Role::SchemaTarget { field }, Target::DefinitionType(kind)) => {
format!("field \"{field}\" references unknown definition type \"{kind}\"")
}
(_, Target::Definition { kind, id }) => format!("no {kind} definition \"{id}\""),
(_, Target::Plane(plane)) => format!("no plane \"{plane}\""),
(_, Target::Anchor { plane, id }) => format!("no anchor \"{id}\" in plane \"{plane}\""),
(_, Target::RenderLayer(layer)) => {
format!("\"{layer}\" is not a render layer of the profile")
}
(_, Target::GroundLayer(layer)) => {
format!("\"{layer}\" is not a ground layer of the profile")
}
(_, Target::Terrain(terrain)) => format!("no terrain \"{terrain}\""),
(_, Target::Zone { plane, id }) => format!("no zone \"{id}\" in plane \"{plane}\""),
(_, Target::DefinitionType(kind)) => format!("no definition type \"{kind}\""),
(_, Target::File(path)) => format!("no file {path}"),
}
}
#[cfg(test)]
mod tests {
use super::{Holder, Record, Recorder, Role, Site, Target};
fn site<'a>(target: Target<'a>, holder: Holder<'a>, file: &'a str, start: usize) -> Site<'a> {
Site {
target,
role: Role::Proto,
holder,
file,
start,
end: start + 3,
}
}
#[test]
fn a_value_is_packed_only_below_the_marker() {
use super::packed;
assert_eq!(packed(0), Some(0));
let largest = usize::try_from(u32::MAX - 1).unwrap();
assert_eq!(packed(largest), Some(u32::MAX - 1));
assert_eq!(packed(largest + 1), None);
assert_eq!(packed(usize::MAX), None);
}
#[test]
fn a_record_that_does_not_fit_refuses_the_index() {
let mut recorder = Recorder::default();
recorder.record(&Site {
start: usize::MAX,
..site(Target::Plane("p"), Holder::Manifest, "world.jsonc", 0)
});
assert!(recorder.overflowed());
recorder.record(&site(
Target::Plane("p"),
Holder::Manifest,
"world.jsonc",
0,
));
assert!(recorder.overflowed());
assert_eq!(recorder.finish().len(), 0);
let mut ordinal = Recorder::default();
ordinal.record(&site(
Target::Plane("p"),
Holder::Object {
plane: "p",
chunk: (0, 0, 0),
ordinal: usize::try_from(u32::MAX).unwrap(),
},
"c.jsonc",
0,
));
assert!(ordinal.overflowed());
}
#[test]
fn a_field_recorded_first_keeps_symbol_zero_empty() {
use crate::schema::{FieldPath, FieldType, Schema};
use std::sync::Arc;
let schema = Arc::new(Schema::new("s.jsonc".into()));
let field_type = FieldType::Reference {
target: "proto".into(),
};
let root = FieldPath::Root;
let path = FieldPath::Field(&root, "f");
let holder = Holder::Object {
plane: "p",
chunk: (0, 0, 0),
ordinal: 0,
};
let mut recorder = Recorder::default();
recorder.record_field(
&Site {
target: Target::Definition {
kind: "proto",
id: "x",
},
role: Role::Field { field: "" },
holder,
file: "c.jsonc",
start: 1,
end: 4,
},
(&schema, &field_type),
&path,
);
recorder.record(&Site {
target: Target::Plane("p"),
role: Role::ChunkPlane,
holder: Holder::Chunk {
plane: "p",
chunk: (0, 0, 0),
},
file: "c.jsonc",
start: 10,
end: 13,
});
let index = recorder.finish();
let fields: Vec<Role<'_>> = index.from(holder).map(|found| found.role()).collect();
assert_eq!(fields, [Role::Field { field: "f" }]);
assert_eq!(index.to(Target::Plane("p")).count(), 1);
}
#[test]
fn a_record_is_32_bytes() {
assert_eq!(size_of::<Record>(), 32);
}
#[test]
fn interning_sorts_strings_and_renumbers_every_symbol() {
let mut recorder = Recorder::default();
let chunk = |ordinal| Holder::Object {
plane: "p",
chunk: (0, 0, 0),
ordinal,
};
recorder.record(&site(
Target::Definition {
kind: "proto",
id: "zeta",
},
chunk(1),
"b.jsonc",
40,
));
recorder.record(&site(
Target::Definition {
kind: "proto",
id: "alpha",
},
chunk(0),
"b.jsonc",
10,
));
recorder.record(&site(
Target::Definition {
kind: "proto",
id: "zeta",
},
Holder::Stairwell {
plane: "p",
stairwell: "s",
},
"a.jsonc",
90,
));
let index = recorder.finish();
assert_eq!(index.len(), 3);
let order: Vec<(Target<'_>, &str, usize)> = index
.iter()
.map(|reference| (reference.target(), reference.file(), reference.span().start))
.collect();
assert_eq!(
order,
[
(
Target::Definition {
kind: "proto",
id: "alpha"
},
"b.jsonc",
10
),
(
Target::Definition {
kind: "proto",
id: "zeta"
},
"a.jsonc",
90
),
(
Target::Definition {
kind: "proto",
id: "zeta"
},
"b.jsonc",
40
),
]
);
let from: Vec<usize> = index
.from(chunk(1))
.map(|reference| reference.span().start)
.collect();
assert_eq!(from, [40]);
assert_eq!(index.from(chunk(2)).len(), 0);
assert_eq!(
index
.from(Holder::Chunk {
plane: "p",
chunk: (0, 0, 0)
})
.len(),
0
);
assert_eq!(
index
.to(Target::Definition {
kind: "proto",
id: "missing"
})
.len(),
0
);
assert_eq!(index.to(Target::Plane("proto")).len(), 0);
}
}