pub struct Bundle { /* private fields */ }Expand description
The layers a game reads its files from.
Implementations§
Source§impl Bundle
impl Bundle
Sourcepub fn fingerprint(&self) -> ContentHash
pub fn fingerprint(&self) -> ContentHash
A fingerprint of the content that ships, the same on every
platform: what a record or a save is stamped with
(shuyi_core::Made). Any byte, name or file changed in a layer
that ships changes it; the test layers never count, whatever the
thread (story_only changes nothing).
The layers that ship are taken from the last read to the first (the base layer first), each file of a layer in path order, as its path’s length and bytes then its contents’ length and bytes, into the word-wise FNV-1a hash of the pack’s checksum. A layer that stands for a bundle (a pack’s) replaces what was taken so far with that bundle’s fingerprint. So a build that ships one pack has the fingerprint of the files it was baked from, and a layer laid over it counts as it would over them.
It reads every byte that ships, without telling the
watcher: compute it once and keep it.
Source§impl Bundle
impl Bundle
Sourcepub fn new(layers: Vec<Layer>) -> Bundle
pub fn new(layers: Vec<Layer>) -> Bundle
The bundle of layers, read in the order given, except that the
test layers are read after all the others.
Sourcepub fn for_target(
layers: Vec<Layer>,
per_target: &[Layer],
target: Option<&str>,
) -> Bundle
pub fn for_target( layers: Vec<Layer>, per_target: &[Layer], target: Option<&str>, ) -> Bundle
The bundle of layers for target (ADR-0008 §4): each of
layers with its layer for target from per_target laid right
above it, the one named <name>@<target>
(target_layer_name), so a file there replaces the one at the
same path. Every other layer of per_target (another target’s, or
one over no layer of layers) is left out, and with no target all
of them are: the bundle of layers alone (Bundle::new).
A game embeds its target layers beside its files, and picks the
bundle by the target it bakes for (GameTools::bake) or runs on
(PlatformFacts::target):
use shuyi_assets::bundle::{Bundle, Layer};
static ASSETS: &[(&str, &[u8])] = &[("hero.png", b"colour"), ("map.ron", b"map")];
static PLAYDATE: &[(&str, &[u8])] = &[("hero.png", b"drawn in black and white")];
let layers = || vec![Layer::table("assets", ASSETS)];
let targets = [Layer::table("assets@playdate", PLAYDATE)];
let bundle = Bundle::for_target(layers(), &targets, Some("playdate"));
assert_eq!(bundle.file("hero.png"), Some("drawn in black and white"));
assert_eq!(bundle.display_path("hero.png"), "assets@playdate/hero.png");
assert_eq!(bundle.file("map.ron"), Some("map"));
let web = Bundle::for_target(layers(), &targets, Some("web"));
assert_eq!(web.file("hero.png"), Some("colour"));Sourcepub fn layer_of(&self, path: &str) -> Option<&Layer>
pub fn layer_of(&self, path: &str) -> Option<&Layer>
The layer this thread reads the file at path from, if a layer has
it: whether it is the game’s own, a mod’s, or a target’s
(Layer::target).
Sourcepub fn file(&self, path: &str) -> Option<&'static str>
pub fn file(&self, path: &str) -> Option<&'static str>
Returns the UTF-8 contents of the file at path (relative to a
layer’s root, /-separated), or None if it is missing or not
valid UTF-8.
Sourcepub fn bytes(&self, path: &str) -> Option<&'static [u8]>
pub fn bytes(&self, path: &str) -> Option<&'static [u8]>
Returns the raw bytes of the file at path (relative to a layer’s
root, /-separated), e.g. an image, or None if it is missing.
Sourcepub fn has(&self, path: &str) -> bool
pub fn has(&self, path: &str) -> bool
Whether a layer this thread reads has the file at path. Unlike
Bundle::bytes, this reads nothing: the watcher isn’t told,
and recording doesn’t hear of it.
Sourcepub fn replace(&self, path: &str, bytes: Vec<u8>) -> bool
pub fn replace(&self, path: &str, bytes: Vec<u8>) -> bool
Puts bytes at path in the first layer handed in at run time that
has the file, or the first of them if none has it, and says whether
one took it (Layer::replace). A file of an embedded layer stays
as it is, so a layer above one still wins over it.
Debug builds only, for a hot reload: the old bytes are leaked, so
every &'static read of them stays valid.
use std::collections::BTreeMap;
use shuyi_assets::bundle::{Bundle, Layer};
let files = BTreeMap::from([("a.txt".to_owned(), b"old".to_vec())]);
let layer = Layer::owned("disk".to_owned(), files).unwrap();
let bundle = Bundle::new(vec![layer]);
let old = bundle.file("a.txt");
assert!(bundle.replace("a.txt", b"new".to_vec()));
assert_eq!(bundle.file("a.txt"), Some("new"));
assert_eq!(old, Some("old"));Sourcepub fn left_out(&self, path: &str) -> bool
pub fn left_out(&self, path: &str) -> bool
Whether a layer this thread reads leaves the file at path out on
purpose (Layer::leaving_out): a loader then does without it
instead of reporting it missing.
Sourcepub fn test_part(&self, path: &str) -> Option<(String, &'static str)>
pub fn test_part(&self, path: &str) -> Option<(String, &'static str)>
The test layers’ own file at path, with its human-facing name,
when a layer that ships has a file there too: the entries a data
table gains in a build with the test files. Bundle::file reads
the shipped one. None without test layers, inside story_only,
and for a path only one of the two has.
Sourcepub fn files_in(&self, dir: &str) -> Vec<&'static str>
pub fn files_in(&self, dir: &str) -> Vec<&'static str>
Lists the paths (relative to a layer’s root, /-separated, sorted,
each once) of every file directly inside directory dir, in any
layer. A missing directory yields an empty list. Use "" for the
root.
Sourcepub fn files_under(&self, dir: &str) -> Vec<&'static str>
pub fn files_under(&self, dir: &str) -> Vec<&'static str>
Lists the paths (relative to a layer’s root, /-separated, sorted,
each once) of every file inside directory dir or any directory
below it, in any layer. A missing directory yields an empty list.
Use "" for the whole bundle.
Sourcepub fn load_each<T, E>(
&self,
dir: &str,
ext: &str,
parse: impl FnMut(&LoadFile<'_>) -> Result<T, Vec<E>>,
) -> Result<BTreeMap<String, T>, Vec<E>>
pub fn load_each<T, E>( &self, dir: &str, ext: &str, parse: impl FnMut(&LoadFile<'_>) -> Result<T, Vec<E>>, ) -> Result<BTreeMap<String, T>, Vec<E>>
Parses every file of extension ext (without the dot) directly inside
dir with parse, into a map by file name without the extension.
A file that is not valid UTF-8 is an error of E (NotText); every
broken file is reported, not only the first: the result is either the
whole map or all the errors, in file order.
use shuyi_assets::bundle::{Bundle, Layer};
use shuyi_assets::ContentError;
static FILES: &[(&str, &[u8])] = &[("crops/a.txt", b"1"), ("crops/b.txt", b"2")];
let bundle = Bundle::new(vec![Layer::table("base", FILES)]);
let crops = bundle.load_each("crops", "txt", |file| {
file.text
.parse::<u32>()
.map_err(|e| vec![ContentError::new(file.path, e.to_string())])
});
assert_eq!(crops.unwrap().get("b"), Some(&2));§Errors
Every error parse returned, and a NotText for each file that
is not UTF-8.
Sourcepub fn display_path(&self, path: &str) -> String
pub fn display_path(&self, path: &str) -> String
The human-facing name of path: under the name of the layer it is
read from (base/data/x.ron), or of the first layer that is neither
private nor test files when no layer has it.
Sourcepub fn is_private(&self, shown: &str) -> bool
pub fn is_private(&self, shown: &str) -> bool
Whether shown (a human-facing name, from Bundle::display_path)
names a file of a private layer.