1use crate::prelude::*;
8
9use alloc::collections::{BTreeMap, BTreeSet};
10use core::any::TypeId;
11use core::fmt;
12use core::marker::PhantomData;
13
14use serde::Serialize;
15use serde::de::DeserializeOwned;
16
17use super::{Content, PackInfo, Packed, write};
18use crate::bundle::Bundle;
19use crate::error::{ContentError, codes};
20use crate::format::{AssetKind, Encode, Formats, RegisterError};
21
22pub trait Bakeable: AssetKind + Serialize + DeserializeOwned + PartialEq + fmt::Debug {}
25
26impl<K: AssetKind + Serialize + DeserializeOwned + PartialEq + fmt::Debug> Bakeable for K {}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30enum Which {
31 Id(String),
33 Under(String),
36}
37
38trait BakeKind: Send + Sync {
40 fn name(&self) -> &'static str;
42
43 fn type_id(&self) -> TypeId;
45
46 fn paths(
48 &self,
49 formats: &Formats,
50 bundle: &Bundle,
51 which: &Which,
52 ) -> Result<Vec<String>, Vec<ContentError>>;
53
54 fn encode(
56 &self,
57 formats: &Formats,
58 bundle: &Bundle,
59 path: &str,
60 ) -> Result<Vec<u8>, Vec<ContentError>>;
61
62 fn same(
65 &self,
66 formats: &Formats,
67 source: &Bundle,
68 pack: &Bundle,
69 path: &str,
70 ) -> Result<(), Vec<ContentError>>;
71
72 fn register(&self, formats: &mut Formats) -> Result<(), RegisterError>;
74
75 fn load(&self, formats: &Formats, bundle: &Bundle, path: &str)
77 -> Result<(), Vec<ContentError>>;
78
79 fn own_format(&self) -> bool {
82 false
83 }
84}
85
86fn paths_of<K: AssetKind>(
88 formats: &Formats,
89 bundle: &Bundle,
90 which: &Which,
91) -> Result<Vec<String>, Vec<ContentError>> {
92 match which {
93 Which::Id(id) => formats
94 .find::<K>(bundle, id)
95 .map(|path| vec![path])
96 .map_err(|e| vec![e]),
97 Which::Under(dir) => Ok(bundle
98 .files_under(dir)
99 .into_iter()
100 .filter(|path| formats.claims::<K>(path))
101 .map(ToOwned::to_owned)
102 .collect()),
103 }
104}
105
106fn same_in<K: AssetKind + PartialEq>(
110 formats: &Formats,
111 (source, pack): (&Bundle, &Bundle),
112 path: &str,
113 id: &str,
114) -> Result<(), Vec<ContentError>> {
115 let shown = pack.display_path(path);
116 if !formats.ids::<K>().contains(&id) {
117 let message = format!(
118 "no format reads a baked {}: register the pack's formats (Bake::register)",
119 K::NAME
120 );
121 return Err(vec![ContentError::new(shown, message)]);
122 }
123 let from_source = formats.load::<K>(source, path)?;
124 let from_pack = formats.load::<K>(pack, path)?;
125 if from_source == from_pack {
126 return Ok(());
127 }
128 let message = format!(
129 "the {} read from the pack differs from the one read from its source",
130 K::NAME
131 );
132 Err(vec![
133 ContentError::new(shown, message).with_code(codes::PACK_DIFFERS),
134 ])
135}
136
137struct KindOf<K>(PhantomData<fn() -> K>);
139
140impl<K: Bakeable> BakeKind for KindOf<K> {
141 fn name(&self) -> &'static str {
142 K::NAME
143 }
144
145 fn type_id(&self) -> TypeId {
146 TypeId::of::<K>()
147 }
148
149 fn paths(
150 &self,
151 formats: &Formats,
152 bundle: &Bundle,
153 which: &Which,
154 ) -> Result<Vec<String>, Vec<ContentError>> {
155 paths_of::<K>(formats, bundle, which)
156 }
157
158 fn encode(
159 &self,
160 formats: &Formats,
161 bundle: &Bundle,
162 path: &str,
163 ) -> Result<Vec<u8>, Vec<ContentError>> {
164 let asset = formats.load::<K>(bundle, path)?;
165 postcard::to_allocvec(&asset).map_err(|e| {
166 let message = format!("its {} has no binary form: {e}", K::NAME);
167 vec![ContentError::new(bundle.display_path(path), message)]
168 })
169 }
170
171 fn same(
172 &self,
173 formats: &Formats,
174 source: &Bundle,
175 pack: &Bundle,
176 path: &str,
177 ) -> Result<(), Vec<ContentError>> {
178 same_in::<K>(formats, (source, pack), path, Packed::<K>::ID)
179 }
180
181 fn register(&self, formats: &mut Formats) -> Result<(), RegisterError> {
182 if formats.ids::<K>().contains(&Packed::<K>::ID) {
183 return Ok(());
184 }
185 formats.register(Packed::<K>::new())
186 }
187
188 fn load(
189 &self,
190 formats: &Formats,
191 bundle: &Bundle,
192 path: &str,
193 ) -> Result<(), Vec<ContentError>> {
194 formats.load::<K>(bundle, path).map(drop)
195 }
196}
197
198struct InFormat<F>(F);
201
202impl<F> BakeKind for InFormat<F>
203where
204 F: Encode + Clone,
205 F::Asset: PartialEq,
206{
207 fn name(&self) -> &'static str {
208 <F::Asset as AssetKind>::NAME
209 }
210
211 fn type_id(&self) -> TypeId {
212 TypeId::of::<F::Asset>()
213 }
214
215 fn paths(
216 &self,
217 formats: &Formats,
218 bundle: &Bundle,
219 which: &Which,
220 ) -> Result<Vec<String>, Vec<ContentError>> {
221 paths_of::<F::Asset>(formats, bundle, which)
222 }
223
224 fn encode(
225 &self,
226 formats: &Formats,
227 bundle: &Bundle,
228 path: &str,
229 ) -> Result<Vec<u8>, Vec<ContentError>> {
230 let asset = formats.load::<F::Asset>(bundle, path)?;
231 Ok(self.0.encode(&asset))
232 }
233
234 fn same(
235 &self,
236 formats: &Formats,
237 source: &Bundle,
238 pack: &Bundle,
239 path: &str,
240 ) -> Result<(), Vec<ContentError>> {
241 same_in::<F::Asset>(formats, (source, pack), path, self.0.id())
242 }
243
244 fn register(&self, formats: &mut Formats) -> Result<(), RegisterError> {
245 if formats.ids::<F::Asset>().contains(&self.0.id()) {
246 return Ok(());
247 }
248 formats.register(self.0.clone())
249 }
250
251 fn load(
252 &self,
253 formats: &Formats,
254 bundle: &Bundle,
255 path: &str,
256 ) -> Result<(), Vec<ContentError>> {
257 formats.load::<F::Asset>(bundle, path).map(drop)
258 }
259
260 fn own_format(&self) -> bool {
261 true
262 }
263}
264
265pub struct Bake {
277 stamp: String,
278 plan: Vec<(Box<dyn BakeKind>, Which)>,
279}
280
281impl fmt::Debug for Bake {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 let plan: Vec<(&str, &Which)> = self
284 .plan
285 .iter()
286 .map(|(kind, which)| (kind.name(), which))
287 .collect();
288 f.debug_struct("Bake")
289 .field("stamp", &self.stamp)
290 .field("plan", &plan)
291 .finish()
292 }
293}
294
295#[derive(Debug, Clone, PartialEq, Eq)]
297pub struct Baked {
298 pub bytes: Vec<u8>,
300 pub files: Vec<BakedFile>,
302}
303
304#[derive(Debug, Clone, PartialEq, Eq)]
306#[non_exhaustive]
307pub struct BakedFile {
308 pub path: String,
310 pub shown: String,
313 pub kinds: Vec<&'static str>,
315 pub source: usize,
317}
318
319impl Baked {
320 pub fn source_bytes(&self) -> usize {
322 self.files.iter().map(|file| file.source).sum()
323 }
324}
325
326impl Bake {
327 pub fn new(stamp: impl Into<String>) -> Self {
333 Self {
334 stamp: stamp.into(),
335 plan: Vec::new(),
336 }
337 }
338
339 pub fn stamp(&self) -> &str {
341 &self.stamp
342 }
343
344 #[must_use]
347 pub fn asset<K: Bakeable>(mut self, id: &str) -> Self {
348 self.plan
349 .push((Box::new(KindOf::<K>(PhantomData)), Which::Id(id.to_owned())));
350 self
351 }
352
353 #[must_use]
356 pub fn under<K: Bakeable>(mut self, dir: &str) -> Self {
357 self.plan.push((
358 Box::new(KindOf::<K>(PhantomData)),
359 Which::Under(dir.to_owned()),
360 ));
361 self
362 }
363
364 #[must_use]
386 pub fn under_as<F>(mut self, dir: &str, format: F) -> Self
387 where
388 F: Encode + Clone,
389 F::Asset: PartialEq,
390 {
391 self.plan
392 .push((Box::new(InFormat(format)), Which::Under(dir.to_owned())));
393 self
394 }
395
396 pub fn kinds(&self) -> Vec<&'static str> {
399 let mut kinds = Vec::new();
400 for (kind, _) in &self.plan {
401 if !kinds.contains(&kind.name()) {
402 kinds.push(kind.name());
403 }
404 }
405 kinds
406 }
407
408 pub fn register(&self, formats: &mut Formats) -> Result<(), RegisterError> {
412 self.plan
413 .iter()
414 .try_for_each(|(kind, _)| kind.register(formats))
415 }
416
417 fn planned(
420 &self,
421 formats: &Formats,
422 bundle: &Bundle,
423 ) -> (BTreeMap<String, Vec<&dyn BakeKind>>, Vec<ContentError>) {
424 let mut planned: BTreeMap<String, Vec<&dyn BakeKind>> = BTreeMap::new();
425 let mut errors = Vec::new();
426 let mut types: BTreeMap<&str, TypeId> = BTreeMap::new();
427 for (kind, which) in &self.plan {
428 let first = *types.entry(kind.name()).or_insert(kind.type_id());
429 if first != kind.type_id() {
430 let message = format!(
431 "the bake has two kinds called \"{}\": a pack tells kinds by name",
432 kind.name()
433 );
434 errors.push(ContentError::new("bake", message));
435 continue;
436 }
437 match kind.paths(formats, bundle, which) {
438 Ok(paths) => {
439 for path in paths {
440 let kinds = planned.entry(path).or_default();
441 if !kinds.iter().any(|k| k.type_id() == kind.type_id()) {
442 kinds.push(&**kind);
443 }
444 }
445 }
446 Err(e) => errors.extend(e),
447 }
448 }
449 for (path, kinds) in &planned {
450 if kinds.len() > 1 && kinds.iter().any(|kind| kind.own_format()) {
451 let names: Vec<&str> = kinds.iter().map(|kind| kind.name()).collect();
452 let message = format!(
453 "the bake keeps it in a format of its own and bakes it as {}; a file kept \
454 in its own format is baked as one kind",
455 names.join(" and ")
456 );
457 errors.push(ContentError::new(bundle.display_path(path), message));
458 }
459 }
460 (planned, errors)
461 }
462
463 pub fn bake(
471 &self,
472 formats: &Formats,
473 bundle: &Bundle,
474 target: Option<&str>,
475 ) -> Result<Baked, Vec<ContentError>> {
476 let (planned, mut errors) = self.planned(formats, bundle);
477 errors.extend(formats.check_import_settings(bundle));
478 let mut contents = BTreeMap::new();
479 let mut files = Vec::new();
480 for path in bundle.files_under("") {
481 let source = bundle.bytes(path).unwrap_or_default();
482 let kinds = planned.get(path).map(Vec::as_slice).unwrap_or_default();
483 let content = if kinds.is_empty() {
484 Content::Raw(source.to_vec())
485 } else if let [kind] = kinds
486 && kind.own_format()
487 {
488 let bytes = kind.encode(formats, bundle, path);
489 Content::Raw(bytes.map_err(|e| errors.extend(e)).unwrap_or_default())
490 } else {
491 let mut sections = BTreeMap::new();
492 for kind in kinds {
493 match kind.encode(formats, bundle, path) {
494 Ok(data) => {
495 sections.insert(kind.name(), data);
496 }
497 Err(e) => errors.extend(e),
498 }
499 }
500 Content::Baked(sections)
501 };
502 files.push(BakedFile {
503 path: path.to_owned(),
504 shown: bundle.display_path(path),
505 kinds: kinds.iter().map(|kind| kind.name()).collect(),
506 source: source.len(),
507 });
508 contents.insert(path.to_owned(), content);
509 }
510 if !errors.is_empty() {
511 return Err(errors);
512 }
513 let info = PackInfo {
514 stamp: self.stamp.clone(),
515 target: target.map(ToOwned::to_owned),
516 content: bundle.fingerprint(),
517 };
518 Ok(Baked {
519 bytes: write(&info, &contents),
520 files,
521 })
522 }
523
524 pub fn load(&self, formats: &Formats, bundle: &Bundle) -> Vec<ContentError> {
528 let (planned, mut errors) = self.planned(formats, bundle);
529 for (path, kinds) in &planned {
530 for kind in kinds {
531 if let Err(e) = kind.load(formats, bundle, path) {
532 errors.extend(e);
533 }
534 }
535 }
536 errors
537 }
538
539 pub fn check(&self, formats: &Formats, source: &Bundle, pack: &Bundle) -> Vec<ContentError> {
546 let (planned, mut errors) = self.planned(formats, source);
547 let sources = source.files_under("");
548 for path in &sources {
549 let shown = pack.display_path(path);
550 let Some(bytes) = pack.bytes(path) else {
551 errors.push(
552 ContentError::new(shown, "the pack lacks this file")
553 .with_code(codes::PACK_DIFFERS),
554 );
555 continue;
556 };
557 match planned.get(*path) {
558 Some(kinds) => {
559 for kind in kinds {
560 if let Err(e) = kind.same(formats, source, pack, path) {
561 errors.extend(e);
562 }
563 }
564 }
565 None if Some(bytes) != source.bytes(path) => {
566 errors.push(
567 ContentError::new(shown, "the pack's copy differs from the file")
568 .with_code(codes::PACK_DIFFERS),
569 );
570 }
571 None => {}
572 }
573 }
574 let sources: BTreeSet<&str> = sources.into_iter().collect();
575 for path in pack.files_under("") {
576 if !sources.contains(path) {
577 let message = "the pack has this file and the sources don't";
578 errors.push(
579 ContentError::new(pack.display_path(path), message)
580 .with_code(codes::PACK_DIFFERS),
581 );
582 }
583 }
584 errors
585 }
586}