Skip to main content

mecha10_cli_core/
simulation_assets.rs

1//! Layout convention for where a scaffolded project's bundled demo simulation images live under
2//! `<project>/assets/images/` (asset layout consolidation follow-up to the "unsupported content
3//! tyoe" MuJoCo crash fix).
4//!
5//! Two independent places need to agree on this exact convention so a scaffolded project ends
6//! up with exactly one copy of each bundled demo image, never a silently duplicated one under
7//! `simulation/environments/<env>/assets/...`:
8//! - `scripts/release/build/package-templates.sh`, which stages the downloaded-templates tarball
9//!   (statically, from `packages/mecha10-templates/templates/assets/`) - the single
10//!   template-resolution path `mecha10 init` copies from (LAB-2034).
11//! - `mecha10_cli::handlers::init::rewrite_bundled_image_source_paths`, which fixes up a copied
12//!   `environment.json`'s `type: "image"` `source` fields to point at wherever the tarball above
13//!   actually placed the file.
14//!
15//! # The rule
16//!
17//! - An image referenced by exactly one bundled environment's `type: "image"` objects is
18//!   *environment-specific*: it lands at `assets/images/<env_name>/<basename>`.
19//! - An image referenced by more than one bundled environment is *shared*: it lands at
20//!   `assets/images/<basename>` directly - written/copied once, not once per environment.
21//!
22//! As of this writing only `basic_arena` is bundled, so every one of its demo images
23//! (`aiko.jpg`/`phoebe.jpg`) lands in the env-specific tier at
24//! `assets/images/basic_arena/`. This module's logic re-derives the tier from whatever set of
25//! environments is actually present, so a second bundled environment sharing one of these files
26//! would automatically start landing it in the shared tier.
27//!
28//! A user's own hand-authored image (e.g. dropped at `assets/images/my_photo.jpg` and referenced
29//! from their own edited `environment.json`) is untouched by any of this - it was never part of
30//! the bundled catalog, so it never appears in the `references` this module reasons about.
31
32use std::collections::{HashMap, HashSet};
33
34/// Count how many *distinct* environments reference each image basename, given every
35/// `(environment_name, image_basename)` pair found across a set of bundled environments'
36/// `type: "image"` objects.
37pub fn count_referencing_environments<'a>(
38    references: impl IntoIterator<Item = (&'a str, &'a str)>,
39) -> HashMap<String, HashSet<String>> {
40    let mut usage: HashMap<String, HashSet<String>> = HashMap::new();
41    for (env_name, basename) in references {
42        usage
43            .entry(basename.to_string())
44            .or_default()
45            .insert(env_name.to_string());
46    }
47    usage
48}
49
50/// Where a bundled demo image with the given `basename`, declared by `env_name`, belongs under a
51/// project's canonical `assets/images/` tree - see the module docs for the shared-vs-env-specific
52/// rule. `referencing_env_count` is the number of distinct environments that reference this
53/// basename (from [`count_referencing_environments`]).
54pub fn bundled_image_project_path(env_name: &str, basename: &str, referencing_env_count: usize) -> String {
55    if referencing_env_count > 1 {
56        format!("assets/images/{basename}")
57    } else {
58        format!("assets/images/{env_name}/{basename}")
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn env_specific_image_used_by_only_one_environment() {
68        let usage = count_referencing_environments([("basic_arena", "aiko.jpg"), ("basic_arena", "phoebe.jpg")]);
69
70        assert_eq!(usage["aiko.jpg"].len(), 1);
71        assert_eq!(
72            bundled_image_project_path("basic_arena", "aiko.jpg", usage["aiko.jpg"].len()),
73            "assets/images/basic_arena/aiko.jpg"
74        );
75    }
76
77    #[test]
78    fn shared_image_used_by_more_than_one_environment() {
79        let usage = count_referencing_environments([("basic_arena", "logo.jpg"), ("obstacle_course", "logo.jpg")]);
80
81        assert_eq!(usage["logo.jpg"].len(), 2);
82        assert_eq!(
83            bundled_image_project_path("basic_arena", "logo.jpg", usage["logo.jpg"].len()),
84            "assets/images/logo.jpg"
85        );
86        assert_eq!(
87            bundled_image_project_path("obstacle_course", "logo.jpg", usage["logo.jpg"].len()),
88            "assets/images/logo.jpg"
89        );
90    }
91
92    #[test]
93    fn duplicate_references_from_the_same_environment_count_once() {
94        // A single environment referencing the same basename twice (two billboards of the same
95        // image) must not be mistaken for two distinct *environments* referencing it.
96        let usage = count_referencing_environments([("basic_arena", "aiko.jpg"), ("basic_arena", "aiko.jpg")]);
97
98        assert_eq!(usage["aiko.jpg"].len(), 1);
99    }
100}