micro_games_kit/assets/
spine.rs

1use super::texture::TextureAsset;
2use anput::world::World;
3use keket::{
4    database::{
5        handle::{AssetDependency, AssetHandle},
6        path::AssetPathStatic,
7    },
8    protocol::AssetProtocol,
9};
10use rusty_spine::{Atlas, SkeletonData, SkeletonJson};
11use std::{
12    collections::HashMap,
13    error::Error,
14    io::{Cursor, Read},
15    sync::Arc,
16};
17use zip::ZipArchive;
18
19#[derive(Debug)]
20pub struct SpineAsset {
21    pub atlas: Arc<Atlas>,
22    pub skeleton_data: Arc<SkeletonData>,
23    pub textures: HashMap<String, AssetPathStatic>,
24}
25
26pub struct SpineAssetProtocol;
27
28impl AssetProtocol for SpineAssetProtocol {
29    fn name(&self) -> &str {
30        "spine"
31    }
32
33    fn process_bytes(
34        &mut self,
35        handle: AssetHandle,
36        storage: &mut World,
37        bytes: Vec<u8>,
38    ) -> Result<(), Box<dyn Error>> {
39        let mut archive = ZipArchive::new(Cursor::new(bytes))?;
40        let mut atlas = None;
41        let mut skeleton_data = None;
42        let mut atlas_page_names = Vec::new();
43        for file_name in archive.file_names() {
44            if file_name.ends_with(".atlas") {
45                atlas = Some(file_name.to_string());
46            } else if file_name.ends_with(".json") {
47                skeleton_data = Some(file_name.to_string());
48            } else if file_name.ends_with(".png") {
49                atlas_page_names.push(file_name.to_string());
50            }
51        }
52        let Some(atlas_name) = atlas else {
53            return Err("No atlas file found in Spine package".into());
54        };
55        let Some(skeleton_data_name) = skeleton_data else {
56            return Err("No skeleton data file found in Spine package".into());
57        };
58        let path_part = storage
59            .component::<true, AssetPathStatic>(handle.entity())?
60            .path()
61            .to_owned();
62
63        let mut bytes = vec![];
64        archive.by_name(&atlas_name)?.read_to_end(&mut bytes)?;
65        let atlas = Arc::new(Atlas::new(&bytes, "")?);
66
67        bytes.clear();
68        archive
69            .by_name(&skeleton_data_name)?
70            .read_to_end(&mut bytes)?;
71        let skeleton_data = Arc::new(SkeletonJson::new(atlas.clone()).read_skeleton_data(&bytes)?);
72
73        bytes.clear();
74        let mut textures = HashMap::new();
75        for atlas_page_name in atlas_page_names {
76            let mut bytes = vec![];
77            archive.by_name(&atlas_page_name)?.read_to_end(&mut bytes)?;
78            let image = image::load_from_memory(&bytes)?.into_rgba8();
79            let path = AssetPathStatic::new(format!("texture://{path_part}/{atlas_page_name}"));
80            let asset = TextureAsset {
81                image,
82                cols: 1,
83                rows: 1,
84            };
85            let entity = storage.spawn((path.clone(), asset))?;
86            textures.insert(atlas_page_name, path);
87            storage.relate::<true, _>(AssetDependency, handle.entity(), entity)?;
88        }
89
90        storage.insert(
91            handle.entity(),
92            (SpineAsset {
93                atlas,
94                skeleton_data,
95                textures,
96            },),
97        )?;
98
99        Ok(())
100    }
101}