Skip to main content

shuyi_assets/
format.rs

1//! Formats are plugins (ADR-0007, ADR-0019). An **asset kind** is a plain
2//! type ([`AssetKind`]): an image's size, a palette, a dialogue file's
3//! scenes. A **format** is a value that reads one kind from the bytes of
4//! one file ([`Format`]). [`Formats`] holds the formats of every kind; the
5//! engine's standard ones are there from the start
6//! ([`Formats::standard`]), and a game registers its own kinds and formats
7//! at start-up. Code that uses an asset asks for its kind by id
8//! ([`Formats::load`]) and never sees which file it came from.
9//!
10//! The rules of the registry:
11//!
12//! - **Lookup.** For each file, the formats of the kind whose
13//!   [`sniff`](Format::sniff) says yes are tried first, in the order they
14//!   were registered, then the one that claims the file's extension. The
15//!   first that decodes the file gives the asset; when none does, the
16//!   errors are the first one's.
17//! - **Conflicts.** Within one kind, two formats may not have the same id
18//!   or claim the same extension: [`Formats::register`] refuses the second
19//!   with a message naming both, and [`Formats::replace`] puts it in place
20//!   of the first. Different kinds may claim one extension (every RON
21//!   file is `.ron`).
22//! - **Limits.** A file bigger than its format's
23//!   [`max_bytes`](Format::max_bytes), or than the registry's own
24//!   [`Limits`], is refused before it is decoded.
25//! - **One file.** A format reads one file, and the files it names
26//!   through [`DecodeCx`]. A check against another kind of asset (a scene
27//!   against the game's characters, a cinematic against its music) runs
28//!   after, on the assets loaded.
29//!
30//! A format may read settings for its file (ADR-0057,
31//! [`import_settings`]): a section of the
32//! `.meta` file beside it, or of a folder's `_folder.meta`
33//! ([`DecodeCx::import_settings`]). The registry knows each section's type
34//! ([`Formats::register_import_settings`]) and checks every settings file
35//! of a bundle ([`Formats::check_import_settings`]); no format may claim
36//! `meta`.
37//!
38//! A front end may watch every decode the registry makes
39//! ([`watch_decodes`]): its kind, its file, its size and whether it read,
40//! told before and after each, on the thread that decodes. A debug build
41//! tags what each decode allocates with its kind this way, so the heap
42//! counter says what the game holds of each kind of asset.
43use crate::prelude::*;
44
45use alloc::collections::BTreeMap;
46use core::any::Any;
47use core::fmt;
48
49use once_cell::race::OnceBox;
50
51use crate::bundle::Bundle;
52use crate::error::{ContentError, codes};
53use crate::import_settings::{
54    self, CheckSection, ImportSettings, SETTINGS_EXTENSION, is_settings_file, settings_chain,
55};
56
57/// A kind of asset: the plain type a [`Format`] reads into. The engine's
58/// kinds are its own types (`ImageInfo`, `PaletteDef`, `DialogueFile`…);
59/// a game makes one of each of its data tables.
60pub trait AssetKind: Sized + 'static {
61    /// What messages call the kind, in lowercase words: `"image"`,
62    /// `"palette"`.
63    const NAME: &'static str;
64}
65
66/// The most bytes a format reads unless it says otherwise
67/// ([`Format::max_bytes`]): 16 MiB.
68pub const DEFAULT_MAX_BYTES: usize = 16 << 20;
69
70/// The most bytes the engine's text formats read: 4 MiB, forty times the
71/// biggest text file of the game the engine was carved from.
72pub const TEXT_MAX_BYTES: usize = 4 << 20;
73
74/// What a file asked for and missing is told.
75pub const NOT_FOUND: &str = "file not found in asset bundle";
76
77/// A file format: reads one [`AssetKind`] from a file's bytes.
78pub trait Format: Send + Sync + 'static {
79    /// The kind it reads.
80    type Asset: AssetKind;
81
82    /// Stable id, e.g. `"png"`, `"tiled-tmx"`, `"yarn"`: one per format
83    /// of a kind.
84    fn id(&self) -> &'static str;
85
86    /// File extensions this format claims, lowercase, without the dot. A
87    /// format that only stands in another's place (a one-bit picture at a
88    /// PNG's path) claims none and is found by [`sniff`](Self::sniff).
89    fn extensions(&self) -> &'static [&'static str];
90
91    /// True if these bytes look like this format (magic bytes); `false`
92    /// when the format has none.
93    fn sniff(&self, bytes: &[u8]) -> bool {
94        let _ = bytes;
95        false
96    }
97
98    /// The most bytes it reads: a bigger file is refused before
99    /// [`decode`](Self::decode) sees it. [`DEFAULT_MAX_BYTES`] unless the
100    /// format says.
101    fn max_bytes(&self) -> usize {
102        DEFAULT_MAX_BYTES
103    }
104
105    /// Every problem found, each with file and place; never a panic.
106    fn decode(
107        &self,
108        file: &AssetPath,
109        bytes: &[u8],
110        cx: &DecodeCx<'_>,
111    ) -> Result<Self::Asset, Vec<ContentError>>;
112}
113
114/// A format that can also write its kind: what tools and an optimized
115/// format derived from a source (ADR-0007 §4) use.
116pub trait Encode: Format {
117    /// `asset` as this format's bytes. Decoding them gives an asset equal
118    /// to `asset`.
119    fn encode(&self, asset: &Self::Asset) -> Vec<u8>;
120}
121
122/// Where a file is: its path in the bundle (relative to a layer's root,
123/// `/`-separated), and the name errors show it by (`base/images/a.png`).
124#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
125pub struct AssetPath {
126    path: String,
127    shown: String,
128}
129
130impl AssetPath {
131    /// The file at `path`, shown as `path`.
132    pub fn new(path: impl Into<String>) -> Self {
133        let path = path.into();
134        Self {
135            shown: path.clone(),
136            path,
137        }
138    }
139
140    /// The file at `path` in `bundle`, shown as the bundle shows it
141    /// ([`Bundle::display_path`]).
142    pub fn in_bundle(bundle: &Bundle, path: &str) -> Self {
143        Self {
144            path: path.to_owned(),
145            shown: bundle.display_path(path),
146        }
147    }
148
149    /// This path, shown as `shown`.
150    #[must_use]
151    pub fn shown_as(self, shown: impl Into<String>) -> Self {
152        Self {
153            shown: shown.into(),
154            ..self
155        }
156    }
157
158    /// The path in the bundle.
159    pub fn path(&self) -> &str {
160        &self.path
161    }
162
163    /// The name errors show it by.
164    pub fn shown(&self) -> &str {
165        &self.shown
166    }
167
168    /// The file's name: what follows the last `/`.
169    pub fn name(&self) -> &str {
170        self.path
171            .rsplit_once('/')
172            .map_or(&self.path, |(_, name)| name)
173    }
174
175    /// The directory it is in: what comes before the last `/`, or `""` at
176    /// the root.
177    pub fn dir(&self) -> &str {
178        self.path.rsplit_once('/').map_or("", |(dir, _)| dir)
179    }
180
181    /// Its extension as written (in any case), without the dot: what
182    /// follows the last `.` of its name, when something comes before it.
183    pub fn extension(&self) -> Option<&str> {
184        let (stem, extension) = self.name().rsplit_once('.')?;
185        (!stem.is_empty()).then_some(extension)
186    }
187
188    /// Its name without its extension.
189    pub fn stem(&self) -> &str {
190        let name = self.name();
191        match self.extension() {
192            Some(extension) => &name[..name.len() - extension.len() - 1],
193            None => name,
194        }
195    }
196
197    /// The path of the file `name` in this one's directory.
198    pub fn sibling(&self, name: &str) -> String {
199        join(self.dir(), name)
200    }
201
202    /// The path of the file beside this one with its stem and the
203    /// extension `extension` (`atlas.ron` → `atlas.png`).
204    pub fn with_extension(&self, extension: &str) -> String {
205        self.sibling(&format!("{}.{extension}", self.stem()))
206    }
207}
208
209/// `name` inside `dir` (`""` for the root).
210pub fn join(dir: &str, name: &str) -> String {
211    if dir.is_empty() {
212        name.to_owned()
213    } else {
214        format!("{dir}/{name}")
215    }
216}
217
218/// How much a decoding may read, beyond each format's own
219/// [`max_bytes`](Format::max_bytes).
220#[derive(Debug, Clone, Copy, PartialEq, Eq)]
221pub struct Limits {
222    /// The most bytes a file may have: lower it to refuse big files from
223    /// mods or downloads. [`usize::MAX`] (no limit but the formats' own)
224    /// by default.
225    pub max_bytes: usize,
226}
227
228impl Limits {
229    /// No limit but the formats' own.
230    pub const NONE: Self = Self {
231        max_bytes: usize::MAX,
232    };
233}
234
235impl Default for Limits {
236    fn default() -> Self {
237        Self::NONE
238    }
239}
240
241/// What a format has besides the file it decodes: the limits, and the
242/// bundle the file is in, for the files it names (a sidecar's picture, a
243/// manifest's sounds). Without a bundle, every file named is missing.
244#[derive(Debug, Clone, Copy, Default)]
245pub struct DecodeCx<'a> {
246    /// The limits of this decoding.
247    pub limits: Limits,
248    /// The bundle the file is in, if any.
249    pub bundle: Option<&'a Bundle>,
250}
251
252impl<'a> DecodeCx<'a> {
253    /// No bundle, no limits but the formats' own: for bytes decoded alone.
254    pub const fn new() -> Self {
255        Self {
256            limits: Limits::NONE,
257            bundle: None,
258        }
259    }
260
261    /// Decoding a file of `bundle`.
262    pub const fn in_bundle(bundle: &'a Bundle) -> Self {
263        Self {
264            limits: Limits::NONE,
265            bundle: Some(bundle),
266        }
267    }
268
269    /// This context, with `limits`.
270    #[must_use]
271    pub const fn with_limits(self, limits: Limits) -> Self {
272        Self { limits, ..self }
273    }
274
275    /// Whether the bundle has the file at `path` ([`Bundle::has`]).
276    pub fn has(&self, path: &str) -> bool {
277        self.bundle.is_some_and(|bundle| bundle.has(path))
278    }
279
280    /// The bytes of the file at `path` (relative to a layer's root), if the
281    /// bundle has it.
282    pub fn bytes(&self, path: &str) -> Option<&'static [u8]> {
283        self.bundle?.bytes(path)
284    }
285
286    /// The text of the file at `path`, if the bundle has it and it is valid
287    /// UTF-8.
288    pub fn text(&self, path: &str) -> Option<&'static str> {
289        self.bundle?.file(path)
290    }
291
292    /// The name errors show the file at `path` by.
293    pub fn shown(&self, path: &str) -> String {
294        self.bundle
295            .map_or_else(|| path.to_owned(), |bundle| bundle.display_path(path))
296    }
297
298    /// Whether the bundle leaves the file at `path` out on purpose
299    /// ([`Bundle::left_out`]).
300    pub fn left_out(&self, path: &str) -> bool {
301        self.bundle.is_some_and(|bundle| bundle.left_out(path))
302    }
303
304    /// The paths of the files inside directory `dir` or below it
305    /// ([`Bundle::files_under`]); none without a bundle.
306    pub fn files_under(&self, dir: &str) -> Vec<&'static str> {
307        self.bundle
308            .map(|bundle| bundle.files_under(dir))
309            .unwrap_or_default()
310    }
311
312    /// The import settings `S` of `file` (ADR-0057): its section in the
313    /// nearest settings file of [`settings_chain`] that has one (the file's
314    /// own `.meta`, then each folder's `_folder.meta` up to the root),
315    /// whole; `S::default()` without a bundle or when none has it. Each
316    /// file looked for is read with [`bytes`](Self::bytes), so the
317    /// [`watch`](crate::bundle::watch)er hears of it, found or not. A
318    /// settings file on the way that doesn't read is an error, even when a
319    /// farther one has the section.
320    pub fn import_settings<S: ImportSettings>(
321        &self,
322        file: &AssetPath,
323    ) -> Result<S, Vec<ContentError>> {
324        let max = TEXT_MAX_BYTES.min(self.limits.max_bytes);
325        for path in settings_chain(file.path()) {
326            let Some(bytes) = self.bytes(&path) else {
327                continue;
328            };
329            let shown = self.shown(&path);
330            let sections = import_settings::sections(&shown, bytes, max)?;
331            if let Some(section) = sections.iter().find(|s| s.name == S::SECTION) {
332                return import_settings::read_section(&shown, section).map_err(|e| vec![e]);
333            }
334        }
335        Ok(S::default())
336    }
337}
338
339/// The text of a text format's file `bytes`, or the error that it isn't
340/// valid UTF-8.
341pub fn text<'b>(file: &AssetPath, bytes: &'b [u8]) -> Result<&'b str, Vec<ContentError>> {
342    core::str::from_utf8(bytes).map_err(|_| {
343        vec![ContentError::new(file.shown(), "file is not valid UTF-8").with_code(codes::NOT_TEXT)]
344    })
345}
346
347/// `format`'s asset in `bytes` (the file `file`), unless the file is
348/// bigger than `format` or `cx` allow.
349pub fn decode_with<F: Format + ?Sized>(
350    format: &F,
351    file: &AssetPath,
352    bytes: &[u8],
353    cx: &DecodeCx<'_>,
354) -> Result<F::Asset, Vec<ContentError>> {
355    let max = format.max_bytes().min(cx.limits.max_bytes);
356    if bytes.len() > max {
357        let message = format!(
358            "the file is {} bytes; the \"{}\" {} format reads at most {max}",
359            bytes.len(),
360            format.id(),
361            <F::Asset as AssetKind>::NAME
362        );
363        return Err(vec![
364            ContentError::new(file.shown(), message).with_code(codes::TOO_BIG),
365        ]);
366    }
367    format.decode(file, bytes, cx)
368}
369
370/// One decode the registry made ([`Formats::decode`]), as a
371/// [`DecodeWatch`] is told it. Build it with [`Decoded::new`] (ADR-0039).
372#[derive(Debug, Clone, Copy, PartialEq, Eq)]
373#[non_exhaustive]
374pub struct Decoded<'a> {
375    /// The kind decoded: its [`AssetKind::NAME`], `"image"`, `"palette"`.
376    pub kind: &'static str,
377    /// The file's path in its bundle ([`AssetPath::path`]).
378    pub path: &'a str,
379    /// The file's size, in bytes.
380    pub bytes: usize,
381    /// Whether a format read it.
382    pub ok: bool,
383}
384
385impl<'a> Decoded<'a> {
386    /// A decode of `kind` from `path`, `bytes` long, that read (`ok`) or
387    /// didn't.
388    pub const fn new(kind: &'static str, path: &'a str, bytes: usize, ok: bool) -> Self {
389        Self {
390            kind,
391            path,
392            bytes,
393            ok,
394        }
395    }
396}
397
398/// What is told of every decode the registry makes, once set
399/// ([`watch_decodes`]): two plain functions, called on the thread that
400/// decodes. Build it with [`DecodeWatch::new`] (ADR-0039).
401#[derive(Debug, Clone, Copy)]
402#[non_exhaustive]
403pub struct DecodeWatch {
404    /// Told the kind as a decode starts; what it gives back, a token, goes
405    /// to `after`.
406    pub before: fn(kind: &'static str) -> u32,
407    /// Told the decode once it is decided, with `before`'s token.
408    pub after: fn(token: u32, decoded: &Decoded<'_>),
409}
410
411impl DecodeWatch {
412    /// A watch that calls `before` as each decode starts and `after` once
413    /// it is decided.
414    pub const fn new(
415        before: fn(kind: &'static str) -> u32,
416        after: fn(token: u32, decoded: &Decoded<'_>),
417    ) -> Self {
418        Self { before, after }
419    }
420}
421
422/// The watch of every decode, once one is set.
423static DECODE_WATCH: OnceBox<DecodeWatch> = OnceBox::new();
424
425/// Tells `watch` of every decode any registry makes from now on
426/// ([`Formats::decode`], so [`Formats::load`] and the rest): `before` as
427/// it starts, with its kind, and `after` once it is decided, with
428/// `before`'s token, the file and whether it read. For a front end that
429/// tags, logs or tallies what it loads. Set once: a second call gives its
430/// watch back and changes nothing.
431pub fn watch_decodes(watch: DecodeWatch) -> Result<(), DecodeWatch> {
432    DECODE_WATCH.set(Box::new(watch)).map_err(|watch| *watch)
433}
434
435/// Why a format, or a section of import settings, could not be
436/// registered. More reasons may come (ADR-0039).
437#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
438#[non_exhaustive]
439pub enum RegisterError {
440    /// It claims an extension an earlier format of its kind claims.
441    #[error(
442        "{kind} format \"{format}\" claims the extension \"{extension}\", which {kind} format \
443         \"{earlier}\" claims already; replace \"{earlier}\" to read .{extension} files with \
444         \"{format}\""
445    )]
446    Extension {
447        /// The kind.
448        kind: &'static str,
449        /// The format refused.
450        format: &'static str,
451        /// The format that claims the extension.
452        earlier: &'static str,
453        /// The extension.
454        extension: &'static str,
455    },
456    /// An earlier format of its kind has its id.
457    #[error("{kind} format \"{format}\" is registered already; replace it to use the new one")]
458    Id {
459        /// The kind.
460        kind: &'static str,
461        /// The id.
462        format: &'static str,
463    },
464    /// One of its extensions isn't lowercase, has a dot, or is empty.
465    #[error(
466        "{kind} format \"{format}\" claims the extension \"{extension}\"; an extension is \
467         lowercase, without the dot, and not empty"
468    )]
469    BadExtension {
470        /// The kind.
471        kind: &'static str,
472        /// The format refused.
473        format: &'static str,
474        /// The extension.
475        extension: &'static str,
476    },
477    /// It claims `meta`, the extension of import settings files.
478    #[error(
479        "{kind} format \"{format}\" claims the extension \"meta\", which is kept for import \
480         settings files (<file>.meta and _folder.meta)"
481    )]
482    Reserved {
483        /// The kind.
484        kind: &'static str,
485        /// The format refused.
486        format: &'static str,
487    },
488    /// A section of import settings of its name is registered already.
489    #[error("the import settings section \"{section}\" is registered already")]
490    SectionTwice {
491        /// The section's name.
492        section: &'static str,
493    },
494    /// The section's type can't read `()`: a field has no default.
495    #[error(
496        "the import settings section \"{section}\" can't read (): a field has no default; add \
497         #[serde(default)] to its struct"
498    )]
499    SectionWithoutDefault {
500        /// The section's name.
501        section: &'static str,
502    },
503    /// The section's type reads a field it doesn't have, so a typo would
504    /// go unseen.
505    #[error(
506        "the import settings section \"{section}\" reads fields it doesn't have, so a typo \
507         would go unseen; add #[serde(deny_unknown_fields)] to its struct"
508    )]
509    SectionAllowsUnknownFields {
510        /// The section's name.
511        section: &'static str,
512    },
513}
514
515/// A kind's formats, in the order they were registered.
516struct KindList<K> {
517    formats: Vec<Box<dyn Format<Asset = K>>>,
518}
519
520/// A [`KindList`] of any kind: what [`Formats`] keeps.
521trait Kind: Any + Send + Sync {
522    /// The kind's name.
523    fn name(&self) -> &'static str;
524
525    /// Its formats' ids, in order.
526    fn ids(&self) -> Vec<&'static str>;
527}
528
529impl<K: AssetKind> Kind for KindList<K> {
530    fn name(&self) -> &'static str {
531        K::NAME
532    }
533
534    fn ids(&self) -> Vec<&'static str> {
535        self.formats.iter().map(|format| format.id()).collect()
536    }
537}
538
539/// Whether `format` claims the extension `extension` (any case).
540fn claims<K: AssetKind>(format: &dyn Format<Asset = K>, extension: &str) -> bool {
541    format
542        .extensions()
543        .iter()
544        .any(|claimed| claimed.eq_ignore_ascii_case(extension))
545}
546
547/// The first of `earlier` that has `format`'s id or one of its extensions,
548/// as the error that refuses `format`.
549fn conflict<F: Format>(
550    earlier: &[Box<dyn Format<Asset = F::Asset>>],
551    format: &F,
552) -> Option<RegisterError> {
553    let kind = <F::Asset as AssetKind>::NAME;
554    earlier.iter().find_map(|old| {
555        if old.id() == format.id() {
556            return Some(RegisterError::Id {
557                kind,
558                format: format.id(),
559            });
560        }
561        let extension = format
562            .extensions()
563            .iter()
564            .find(|&&extension| claims(&**old, extension))?;
565        Some(RegisterError::Extension {
566            kind,
567            format: format.id(),
568            earlier: old.id(),
569            extension,
570        })
571    })
572}
573
574/// The error for the first of `format`'s extensions that isn't lowercase
575/// and without a dot, if one isn't.
576fn bad_extension<F: Format>(format: &F) -> Option<RegisterError> {
577    let extension = format.extensions().iter().find(|extension| {
578        extension.is_empty() || extension.contains('.') || extension.chars().any(char::is_uppercase)
579    })?;
580    Some(RegisterError::BadExtension {
581        kind: <F::Asset as AssetKind>::NAME,
582        format: format.id(),
583        extension,
584    })
585}
586
587/// The error refusing `format` if it claims [`SETTINGS_EXTENSION`].
588fn reserved<F: Format>(format: &F) -> Option<RegisterError> {
589    format
590        .extensions()
591        .iter()
592        .any(|extension| extension.eq_ignore_ascii_case(SETTINGS_EXTENSION))
593        .then(|| RegisterError::Reserved {
594            kind: <F::Asset as AssetKind>::NAME,
595            format: format.id(),
596        })
597}
598
599/// The formats of every kind ([module docs](self)).
600#[derive(Default)]
601pub struct Formats {
602    /// One [`KindList`] per kind that has a format.
603    kinds: Vec<Box<dyn Kind>>,
604    /// The sections of import settings, by name, each with how its text is
605    /// checked.
606    sections: BTreeMap<&'static str, CheckSection>,
607    /// The limits [`Formats::load`] and [`Formats::load_under`] decode with.
608    pub limits: Limits,
609}
610
611impl fmt::Debug for Formats {
612    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
613        let kinds: Vec<(&str, Vec<&str>)> = self
614            .kinds
615            .iter()
616            .map(|kind| (kind.name(), kind.ids()))
617            .collect();
618        f.debug_struct("Formats")
619            .field("kinds", &kinds)
620            .field("sections", &self.import_settings_sections())
621            .field("limits", &self.limits)
622            .finish()
623    }
624}
625
626impl Formats {
627    /// A registry with no format at all.
628    pub fn new() -> Self {
629        Self::default()
630    }
631
632    /// A registry with the engine's standard formats, those that need
633    /// nothing of the game ([`Formats::register_standard`]).
634    pub fn standard() -> Self {
635        let mut formats = Self::new();
636        // On an empty registry none of them is refused: they claim no id
637        // or extension twice in a kind, which a test checks.
638        let _ = formats.register_standard();
639        formats
640    }
641
642    /// Registers the engine's standard formats after those already here:
643    /// every format of the engine that needs nothing of the game. The
644    /// formats that read a game's own types (its map features, dialogue
645    /// directives, actions, shots) or follow its rules (the glyphs it
646    /// needs, its language rules) are the game's to register, with them.
647    pub fn register_standard(&mut self) -> Result<(), RegisterError> {
648        use crate::{
649            audio, bitmap, credits, dialogue, font, image, lighting, names, onebit, palette, target,
650        };
651        self.register(image::PngInfo)?;
652        self.register(bitmap::OneBitInfo)?;
653        self.register(bitmap::OneBitFile)?;
654        #[cfg(feature = "png")]
655        self.register(image::Png)?;
656        #[cfg(feature = "png")]
657        self.register(bitmap::dither::Dithered)?;
658        self.register(audio::Wav)?;
659        self.register(audio::OggVorbis)?;
660        self.register(audio::OggVorbisTrack)?;
661        self.register(audio::AudioManifestRon)?;
662        self.register(credits::CreditsRon::default())?;
663        self.register(font::FontAtlasRon::default())?;
664        self.register(palette::PaletteRon::default())?;
665        self.register(onebit::PatternMapRon)?;
666        self.register(lighting::LightingRon::default())?;
667        self.register(names::NamesRon)?;
668        self.register(dialogue::Dlg::<dialogue::NoDirective>::new())?;
669        self.register(target::TargetRon)?;
670        self.register_import_settings::<bitmap::dither::OneBitSettings>()?;
671        Ok(())
672    }
673
674    /// The list of kind `K`, if it has one.
675    fn list<K: AssetKind>(&self) -> &[Box<dyn Format<Asset = K>>] {
676        self.kinds
677            .iter()
678            .find_map(|kind| (&**kind as &dyn Any).downcast_ref::<KindList<K>>())
679            .map_or(&[], |list| &list.formats)
680    }
681
682    /// The list of kind `K`, if it has one, to change.
683    fn list_mut<K: AssetKind>(&mut self) -> Option<&mut KindList<K>> {
684        self.kinds
685            .iter_mut()
686            .find_map(|kind| (&mut **kind as &mut dyn Any).downcast_mut::<KindList<K>>())
687    }
688
689    /// Adds `format` after the formats of its kind, unless one of them has
690    /// its id or claims one of its extensions.
691    pub fn register<F: Format>(&mut self, format: F) -> Result<(), RegisterError> {
692        if let Some(error) = bad_extension(&format)
693            .or_else(|| reserved(&format))
694            .or_else(|| conflict(self.list(), &format))
695        {
696            return Err(error);
697        }
698        self.insert(None, Box::new(format));
699        Ok(())
700    }
701
702    /// Adds `format` in place of the formats of its kind that have its id or
703    /// claim one of its extensions (after them all if none does), and
704    /// returns their ids.
705    pub fn replace<F: Format>(&mut self, format: F) -> Result<Vec<&'static str>, RegisterError> {
706        if let Some(error) = bad_extension(&format).or_else(|| reserved(&format)) {
707            return Err(error);
708        }
709        let mut replaced = Vec::new();
710        let mut at = None;
711        if let Some(list) = self.list_mut::<F::Asset>() {
712            let clash = |old: &dyn Format<Asset = F::Asset>| {
713                old.id() == format.id()
714                    || format
715                        .extensions()
716                        .iter()
717                        .any(|&extension| claims(old, extension))
718            };
719            for old in core::mem::take(&mut list.formats) {
720                if clash(&*old) {
721                    // The place of the first replaced: after the formats
722                    // kept before it.
723                    at.get_or_insert(list.formats.len());
724                    replaced.push(old.id());
725                } else {
726                    list.formats.push(old);
727                }
728            }
729        }
730        self.insert(at, Box::new(format));
731        Ok(replaced)
732    }
733
734    /// Puts `format` at `at` among its kind's formats (last if `None`).
735    fn insert<K: AssetKind>(&mut self, at: Option<usize>, format: Box<dyn Format<Asset = K>>) {
736        match self.list_mut::<K>() {
737            Some(list) => list
738                .formats
739                .insert(at.unwrap_or(list.formats.len()), format),
740            None => self.kinds.push(Box::new(KindList {
741                formats: vec![format],
742            })),
743        }
744    }
745
746    /// Registers the section of import settings `S` (ADR-0057), so
747    /// [`check_import_settings`](Self::check_import_settings) knows it.
748    /// Refuses, in this order, a second section of its name, a type that
749    /// can't read `()` (a field with no default) and one that reads
750    /// `(shuyi_probe: ())` (it doesn't deny unknown fields).
751    pub fn register_import_settings<S: ImportSettings>(&mut self) -> Result<(), RegisterError> {
752        let section = S::SECTION;
753        if self.sections.contains_key(section) {
754            return Err(RegisterError::SectionTwice { section });
755        }
756        if ::ron::from_str::<S>("()").is_err() {
757            return Err(RegisterError::SectionWithoutDefault { section });
758        }
759        if ::ron::from_str::<S>("(shuyi_probe: ())").is_ok() {
760            return Err(RegisterError::SectionAllowsUnknownFields { section });
761        }
762        self.sections
763            .insert(section, import_settings::check_section::<S>);
764        Ok(())
765    }
766
767    /// The names of the sections of import settings registered, in order.
768    pub fn import_settings_sections(&self) -> Vec<&'static str> {
769        self.sections.keys().copied().collect()
770    }
771
772    /// Every problem of every settings file of `bundle` (ADR-0057): a file
773    /// that doesn't read, a section no one registered or that doesn't read
774    /// as its type, and a `<name>.meta` whose file the bundle lacks (and
775    /// doesn't leave out on purpose) or is itself a settings file.
776    pub fn check_import_settings(&self, bundle: &Bundle) -> Vec<ContentError> {
777        let max = TEXT_MAX_BYTES.min(self.limits.max_bytes);
778        let mut errors = Vec::new();
779        for path in bundle.files_under("") {
780            if !is_settings_file(path) {
781                continue;
782            }
783            let shown = bundle.display_path(path);
784            errors.extend(import_settings::orphan(bundle, path, &shown));
785            let bytes = bundle.bytes(path).unwrap_or_default();
786            match import_settings::sections(&shown, bytes, max) {
787                Ok(sections) => {
788                    for section in &sections {
789                        match self.sections.get(section.name.as_str()) {
790                            Some(check) => errors.extend(check(&shown, section)),
791                            None => errors.push(self.unknown_section(&shown, section)),
792                        }
793                    }
794                }
795                Err(e) => errors.extend(e),
796            }
797        }
798        errors
799    }
800
801    /// The error that `section`, of the settings file shown as `shown`, is
802    /// no registered section.
803    fn unknown_section(&self, shown: &str, section: &import_settings::Section<'_>) -> ContentError {
804        let known = if self.sections.is_empty() {
805            "no section is registered".to_owned()
806        } else {
807            format!(
808                "the sections registered are {}",
809                self.import_settings_sections().join(", ")
810            )
811        };
812        let message = format!("unknown section \"{}\"; {known}", section.name);
813        section.error(shown, message).with_code(codes::UNKNOWN_NAME)
814    }
815
816    /// The ids of kind `K`'s formats, in the order they are tried.
817    pub fn ids<K: AssetKind>(&self) -> Vec<&'static str> {
818        self.list::<K>().iter().map(|format| format.id()).collect()
819    }
820
821    /// The extensions kind `K`'s formats claim, in their order.
822    pub fn extensions<K: AssetKind>(&self) -> Vec<&'static str> {
823        self.list::<K>()
824            .iter()
825            .flat_map(|format| format.extensions().iter().copied())
826            .collect()
827    }
828
829    /// Whether a format of kind `K` claims the extension of the file at
830    /// `path`.
831    pub fn claims<K: AssetKind>(&self, path: &str) -> bool {
832        let file = AssetPath::new(path);
833        let Some(extension) = file.extension() else {
834            return false;
835        };
836        self.list::<K>()
837            .iter()
838            .any(|format| claims(&**format, extension))
839    }
840
841    /// The asset of kind `K` in `bytes`, the contents of `file`, by the
842    /// lookup rule: the formats that sniff `bytes` in order, then the one
843    /// that claims `file`'s extension. The first that decodes it gives the
844    /// asset; when none does, the errors are the first one's. A
845    /// [`watch_decodes`] watch is told before and after.
846    pub fn decode<K: AssetKind>(
847        &self,
848        file: &AssetPath,
849        bytes: &[u8],
850        cx: &DecodeCx<'_>,
851    ) -> Result<K, Vec<ContentError>> {
852        let Some(watch) = DECODE_WATCH.get() else {
853            return self.decode_unwatched(file, bytes, cx);
854        };
855        let token = (watch.before)(K::NAME);
856        let decoded = self.decode_unwatched(file, bytes, cx);
857        let told = Decoded::new(K::NAME, file.path(), bytes.len(), decoded.is_ok());
858        (watch.after)(token, &told);
859        decoded
860    }
861
862    /// [`decode`](Self::decode), with no watch told.
863    fn decode_unwatched<K: AssetKind>(
864        &self,
865        file: &AssetPath,
866        bytes: &[u8],
867        cx: &DecodeCx<'_>,
868    ) -> Result<K, Vec<ContentError>> {
869        let formats = self.list::<K>();
870        let sniffed: Vec<bool> = formats.iter().map(|format| format.sniff(bytes)).collect();
871        let extension = file.extension();
872        let by_sniff = formats.iter().zip(&sniffed).filter(|&(_, &yes)| yes);
873        let by_extension = formats.iter().zip(&sniffed).filter(|&(format, &yes)| {
874            !yes && extension.is_some_and(|extension| claims(&**format, extension))
875        });
876        let mut first = None;
877        for (format, _) in by_sniff.chain(by_extension) {
878            match decode_with(&**format, file, bytes, cx) {
879                Ok(asset) => return Ok(asset),
880                Err(errors) => {
881                    first.get_or_insert(errors);
882                }
883            }
884        }
885        Err(first.unwrap_or_else(|| vec![self.unread::<K>(file)]))
886    }
887
888    /// The error for `file`, which no format of kind `K` sniffs or claims.
889    fn unread<K: AssetKind>(&self, file: &AssetPath) -> ContentError {
890        let kind = K::NAME;
891        let why = match file.extension() {
892            Some(extension) => {
893                format!("no {kind} format knows its bytes or claims its extension \"{extension}\"")
894            }
895            None => format!("no {kind} format knows its bytes, and it has no extension"),
896        };
897        let ids = self.ids::<K>();
898        let known = if ids.is_empty() {
899            format!("no {kind} format is registered")
900        } else {
901            format!("the {kind} formats are {}", ids.join(", "))
902        };
903        ContentError::new(file.shown(), format!("{why}; {known}")).with_code(codes::NO_FORMAT)
904    }
905
906    /// The path of the asset of kind `K` with id `id`: the file at `id`
907    /// itself if `bundle` has one, else the one file at `id` and an
908    /// extension kind `K`'s formats claim (`images/card` is
909    /// `images/card.png`, or `images/card.txtimg` once a format claims
910    /// that).
911    pub fn find<K: AssetKind>(&self, bundle: &Bundle, id: &str) -> Result<String, ContentError> {
912        if bundle.has(id) {
913            return Ok(id.to_owned());
914        }
915        let tried: Vec<String> = self
916            .extensions::<K>()
917            .iter()
918            .map(|extension| format!("{id}.{extension}"))
919            .collect();
920        let found: Vec<&String> = tried.iter().filter(|path| bundle.has(path)).collect();
921        let (code, message) = match found.as_slice() {
922            [path] => return Ok((*path).clone()),
923            [] if tried.is_empty() => (codes::MISSING_FILE, NOT_FOUND.to_owned()),
924            [] => (
925                codes::MISSING_FILE,
926                format!("{NOT_FOUND}, nor {}", tried.join(" nor ")),
927            ),
928            many => {
929                let shown: Vec<String> =
930                    many.iter().map(|path| bundle.display_path(path)).collect();
931                let message = format!(
932                    "{} files could be this {}: {}; name the one meant with its extension",
933                    shown.len(),
934                    K::NAME,
935                    shown.join(", ")
936                );
937                (codes::AMBIGUOUS_FILE, message)
938            }
939        };
940        Err(ContentError::new(bundle.display_path(id), message).with_code(code))
941    }
942
943    /// The asset of kind `K` with id `id` in `bundle` ([`Formats::find`]),
944    /// decoded with the registry's [`limits`](Self::limits).
945    pub fn load<K: AssetKind>(&self, bundle: &Bundle, id: &str) -> Result<K, Vec<ContentError>> {
946        let path = self.find::<K>(bundle, id).map_err(|e| vec![e])?;
947        self.read(bundle, &path)
948    }
949
950    /// The file at `path` of `bundle`, decoded as kind `K`.
951    fn read<K: AssetKind>(&self, bundle: &Bundle, path: &str) -> Result<K, Vec<ContentError>> {
952        let bytes = bundle.bytes(path).unwrap_or_default();
953        let cx = DecodeCx::in_bundle(bundle).with_limits(self.limits);
954        self.decode(&AssetPath::in_bundle(bundle, path), bytes, &cx)
955    }
956
957    /// Every asset of kind `K` in `bundle` under directory `dir` (`""` for
958    /// the whole bundle): each file there whose extension a format of the
959    /// kind claims, by path. Reports every problem of every file.
960    ///
961    /// All or nothing over a tree: the assets come only when every file
962    /// reads, and folders below `dir` are read too. A loader that checks
963    /// each file after reading wants [`Formats::load_each`] instead.
964    pub fn load_under<K: AssetKind>(
965        &self,
966        bundle: &Bundle,
967        dir: &str,
968    ) -> Result<BTreeMap<&'static str, K>, Vec<ContentError>> {
969        let mut assets = BTreeMap::new();
970        let mut errors = Vec::new();
971        for path in bundle.files_under(dir) {
972            if !self.claims::<K>(path) {
973                continue;
974            }
975            match self.read(bundle, path) {
976                Ok(asset) => {
977                    assets.insert(path, asset);
978                }
979                Err(e) => errors.extend(e),
980            }
981        }
982        if errors.is_empty() {
983            Ok(assets)
984        } else {
985            Err(errors)
986        }
987    }
988
989    /// Each asset of kind `K` directly inside directory `dir` of `bundle`
990    /// (`""` for the root), by path: each file there whose extension a
991    /// format of the kind claims, with its asset or its errors.
992    ///
993    /// File by file in one folder: a broken file leaves the others read,
994    /// so checks that run after still see every good file, and folders
995    /// below `dir` (which may hold another kind with the same extension)
996    /// are not read. A missing folder gives an empty map.
997    pub fn load_each<K: AssetKind>(
998        &self,
999        bundle: &Bundle,
1000        dir: &str,
1001    ) -> BTreeMap<&'static str, Result<K, Vec<ContentError>>> {
1002        bundle
1003            .files_in(dir)
1004            .into_iter()
1005            .filter(|path| self.claims::<K>(path))
1006            .map(|path| (path, self.read(bundle, path)))
1007            .collect()
1008    }
1009}
1010
1011#[cfg(test)]
1012mod tests;
1013
1014#[cfg(test)]
1015pub(crate) mod builtin_tests;
1016
1017#[cfg(test)]
1018mod golden_tests;