konnektoren_bevy/assets/
challenge_asset.rs

1use bevy::{
2    asset::{io::Reader, AssetLoader, LoadContext},
3    prelude::*,
4    reflect::TypePath,
5};
6
7#[cfg(feature = "assets")]
8use konnektoren_core::challenges::challenge_type::ChallengeType;
9#[cfg(feature = "assets")]
10use serde_yaml;
11#[cfg(feature = "assets")]
12use thiserror::Error;
13
14/// Asset representation of a challenge
15#[derive(Asset, TypePath, Debug, Clone)]
16pub struct ChallengeAsset {
17    #[cfg(feature = "assets")]
18    pub challenge_type: ChallengeType,
19    #[cfg(not(feature = "assets"))]
20    pub challenge_type: String, // Fallback for when assets feature is disabled
21    pub file_path: String,
22}
23
24#[cfg(feature = "assets")]
25impl ChallengeAsset {
26    /// Get the challenge ID
27    pub fn id(&self) -> &str {
28        self.challenge_type.id()
29    }
30
31    /// Get the challenge name
32    pub fn name(&self) -> &str {
33        self.challenge_type.name()
34    }
35}
36
37#[cfg(not(feature = "assets"))]
38impl ChallengeAsset {
39    /// Get the challenge ID (fallback)
40    pub fn id(&self) -> &str {
41        &self.challenge_type
42    }
43
44    /// Get the challenge name (fallback)
45    pub fn name(&self) -> &str {
46        &self.challenge_type
47    }
48}
49
50/// Loader for challenge files in YAML format
51#[derive(Default)]
52pub struct ChallengeAssetLoader;
53
54/// Possible errors that can be produced by ChallengeAssetLoader
55#[non_exhaustive]
56#[derive(Debug, Error)]
57#[cfg(feature = "assets")]
58pub enum ChallengeAssetLoaderError {
59    /// An IO Error
60    #[error("Could not load challenge asset: {0}")]
61    Io(#[from] std::io::Error),
62
63    /// A YAML parsing error
64    #[error("Could not parse YAML challenge: {0}")]
65    YamlError(#[from] serde_yaml::Error),
66}
67
68#[cfg(not(feature = "assets"))]
69#[derive(Debug)]
70pub enum ChallengeAssetLoaderError {
71    Io(std::io::Error),
72    YamlError(String),
73}
74
75#[cfg(not(feature = "assets"))]
76impl std::fmt::Display for ChallengeAssetLoaderError {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        match self {
79            ChallengeAssetLoaderError::Io(e) => write!(f, "IO Error: {}", e),
80            ChallengeAssetLoaderError::YamlError(e) => write!(f, "YAML Error: {}", e),
81        }
82    }
83}
84
85#[cfg(not(feature = "assets"))]
86impl std::error::Error for ChallengeAssetLoaderError {}
87
88#[cfg(feature = "assets")]
89impl AssetLoader for ChallengeAssetLoader {
90    type Asset = ChallengeAsset;
91    type Settings = ();
92    type Error = ChallengeAssetLoaderError;
93
94    async fn load(
95        &self,
96        reader: &mut dyn Reader,
97        _settings: &(),
98        load_context: &mut LoadContext<'_>,
99    ) -> Result<Self::Asset, Self::Error> {
100        let mut bytes = Vec::new();
101        reader.read_to_end(&mut bytes).await?;
102
103        let challenge_type = serde_yaml::from_slice::<ChallengeType>(&bytes)?;
104        let file_path = load_context.path().to_string_lossy().to_string();
105
106        info!(
107            "Loaded challenge '{}' ({}) from {}",
108            challenge_type.name(),
109            challenge_type.id(),
110            file_path
111        );
112
113        Ok(ChallengeAsset {
114            challenge_type,
115            file_path,
116        })
117    }
118
119    fn extensions(&self) -> &[&str] {
120        &["yml", "yaml"]
121    }
122}
123
124#[cfg(not(feature = "assets"))]
125impl AssetLoader for ChallengeAssetLoader {
126    type Asset = ChallengeAsset;
127    type Settings = ();
128    type Error = ChallengeAssetLoaderError;
129
130    async fn load(
131        &self,
132        reader: &mut dyn Reader,
133        _settings: &(),
134        load_context: &mut LoadContext<'_>,
135    ) -> Result<Self::Asset, Self::Error> {
136        let mut bytes = Vec::new();
137        reader
138            .read_to_end(&mut bytes)
139            .await
140            .map_err(ChallengeAssetLoaderError::Io)?;
141
142        let file_path = load_context.path().to_string_lossy().to_string();
143        let challenge_type = "unknown".to_string(); // Fallback
144
145        Ok(ChallengeAsset {
146            challenge_type,
147            file_path,
148        })
149    }
150
151    fn extensions(&self) -> &[&str] {
152        &["yml", "yaml"]
153    }
154}