Served resources
Цей контент ще не доступний вашою мовою.
Items, recipes and workshops are built on one lower-level call,
Load::serve.
It serves any game resource, new or changed, as if it came from the game’s
archive. Reach for it when the high-level calls do not cover what you need.
Keys and bases
Section titled “Keys and bases”A resource is named by a
ResourceKey:
a GUID, a type hash and a part.
Guid::key(ty)
makes the key of part 0 with the hash the loader gives the type on the
running build, and
content::types
names the resource types mods commonly serve, such as ITEM_INFO and
ITEM_REGISTRY.
load.serve(key, base, builder) serves key, built by builder from the
bytes of the resource base:
- with
keyandbasedifferent, it is a new resource, such as a new item cloned from a vanilla one; - with them equal, it is a changed vanilla resource, such as the item registry with one more entry.
use enshwick::content::{Blob, Guid, Id, types};
const CHEST: Guid = Guid::parse("6943053a-32c1-4718-ae22-e61427ddf54d");const LOCKER_ID: Id = Id::new("my-mod:locker");
fn init(load: &mut enshwick::Load<'_>) -> Result<(), enshwick::Off> { const LOCKER: Guid = LOCKER_ID.guid(); load.declare(LOCKER_ID)?; load.serve(LOCKER.key(types::ITEM_INFO)?, CHEST.key(types::ITEM_INFO)?, |chest| { let mut item = Blob::new(chest); // Offsets for illustration: a real mod reads them from reflection. let written = item.write(0, &enshwick::ItemId::from_guid(LOCKER).get().to_le_bytes()) && item.write(4, &LOCKER.0); written.then(|| item.into_bytes()) })}Field offsets change between game builds; resolve a layout such as
ItemLayout::resolve
during init, which asks the loader by Enshwick’s ids, and move it into
the builder. For a field no layout names, load.field_offset(type, field)
and load.type_size(type) read the game’s reflection. See
Finding the game.
When the builder runs
Section titled “When the builder runs”serve only registers the builder; call it during init. The game builds
the resource the first time it reads it, on whatever thread it reads it, and
keeps the result for the life of the process. So the builder:
- is
Fn(&[u8]) -> Option<Vec<u8>> + Send + Sync + 'static: move what it needs into it; - runs once per resource, not per read, unless the mod rebuilds it;
- returns
Nonewhen the base does not read as expected. That turns the mod off. A panic in the builder counts the same.
When the game has no base in its archive, the served key stays missing and
the builder never runs.
Rebuilding
Section titled “Rebuilding”When values a builder reads change after the game built the resource, such
as a server’s join values on a joined client, the mod can have it built
again: load.allow_rebuild(key) during init, after serve, has the loader
keep a copy of the base, and content::rebuild(key) later, from any thread,
runs every mod’s patch on that copy and writes the bytes that differ over
the resource the game holds. Nothing happens while the game has not built
the resource yet. The game keeps reading the first build’s bytes, so a
rebuild must come out the same length: changed values qualify, added
entries do not, and rebuild returns Err(Off) for them. Each rebuild
keeps the new bytes for the process, so it is for rare changes.
Patches from several mods compose
Section titled “Patches from several mods compose”Several mods may serve one key. Each builder is a patch, and the loader runs the patches in load order, each on the result of the one before. This is how two mods both add an item to the item registry: each builder sees the registry with the earlier mods’ items already in it, and adds its own. Write builders to expect that: find entries by what they are, never by a fixed index or count.
The rules:
- every mod serving one key must name the same
base; a mod that names another base for a key already served is refused, andservereturnsErr(Off); - a patch that fails turns its mod off and is skipped; the next patch runs on the result so far;
- when every patch fails, the resource reads as missing;
- the patches of a mod already turned off are skipped.
At the debug level the loader’s log names each built resource and the mods
that built it:
served resource <type>:<guid> built by you.lockers, you.lanterns, <n> bytes.
The status summary counts what mods serve, what was built
and what failed.
Editing blobs
Section titled “Editing blobs”Game resources are blobs: plain data whose arrays and strings are offsets
relative to the field that holds them. Growing an array in place would break
every offset after it.
content::Blob
edits a blob safely, by moving a grown array to the end and correcting the
offsets that move with it.
| Method | What it does |
|---|---|
Blob::new(base) | A copy of base to edit. |
bytes(), into_bytes() | The bytes as edited, borrowed or owned. |
read_u32(at) | The u32 at at, None past the end. |
write(at, bytes) | Writes bytes at at; false when they do not fit. |
array(field) | The array whose header is at field: where its elements start and how many there are. |
set_array(field, start, count) | Points the header at field to count elements at start. |
append(data, align) | Appends data at the next multiple of align and returns where. |
grow_array(field, size, align, relative, extra) | Moves the array at field, of size-byte elements, to the end with extra elements after the old ones; returns the index of the first extra one. |
An array or string header is 8 bytes: a signed 32-bit offset from the header itself, then a 32-bit count.
grow_array takes two lists that keep offsets right:
relativelists the offsets, inside one element, of the element’s own array and string headers. As elements move, their headers are corrected so they still point at their data;- each extra element comes with
Some(offset)when its bytes were copied from that offset in the blob, so its headers are corrected the same way, orNonewhen its headers are empty or set later withset_array.
use enshwick::content::Blob;
// A root with one array header at 0: two u32 elements right after it.let mut base = vec![8, 0, 0, 0, 2, 0, 0, 0];base.extend_from_slice(&[1, 0, 0, 0, 2, 0, 0, 0]);let mut blob = Blob::new(&base);
let first = blob.grow_array(0, 4, 4, &[], &[(vec![3, 0, 0, 0], None)]);assert_eq!(first, Some(2));let (start, count) = blob.array(0).unwrap();assert_eq!(count, 3);assert_eq!(blob.read_u32(start + 8), Some(3));
// A string for a new element: append the text, then point a header at it.let text = blob.append(b"Locker\0", 1);let header = blob.append(&[0; 8], 4);assert!(blob.set_array(header, text, 4));assert_eq!(blob.array(header), Some((text, 4)));The API’s own
items.rs,
recipes.rs
and
workshops.rs
are worked examples: each builds its resources with Blob alone.
Reading vanilla resources
Section titled “Reading vanilla resources”A builder sees only its own base. To read another vanilla resource, such as
the template a station item places or an item’s name tag,
content::game_resource(key)
returns its bytes from the game’s archive files, as the game reads them
before it resolves their references, so Blob reads them the same way. It
works during init and later, on any thread and on both sides, and never
touches the game’s memory. Each call reads the archive, so keep what a mod
needs more than once.
use enshwick::content::{Blob, game_resource, types};use enshwick::content::workshops::stations;
let key = stations::WORKBENCH.item.key(types::ITEM_INFO);if let Some(item) = key.ok().and_then(game_resource) { let item = Blob::new(&item); let _id = item.read_u32(0);}One text for one tag
Section titled “One text for one tag”Load::add_text(tag, language, text)
is the low-level text call: the client shows text wherever the game asks
for the LocaTag tag, a declared content id’s
GUID, in language, a
Language
number. A served item whose name field holds tag shows that text as its
name.
use enshwick::content::Id;use enshwick::i18n::Language;
const LOCKER_NAME: Id = Id::new("my-mod:locker.name");
fn init(load: &mut enshwick::Load<'_>) -> Result<(), enshwick::Off> { let tag = load.declare(LOCKER_NAME)?; load.add_text(tag, Language::FALLBACK, "Locker")?; load.add_text(tag, Language::DE_DE, "Tresor")}Most mods use add_texts instead, which gives many tags their
texts in every language from the mod’s texts file.