query_flow/
asset.rs

1//! Asset types for external resources.
2//!
3//! Assets are external inputs (files, network resources, etc.) that:
4//! - Are always leaves in the dependency graph (no dependencies)
5//! - May need IO to load
6//! - Loading differs by platform (filesystem locally, network/memory in playground)
7//! - Can be depended upon by queries with proper dependency tracking
8
9use std::any::{Any, TypeId};
10use std::fmt::Debug;
11use std::sync::Arc;
12
13use crate::key::Key;
14
15/// Durability levels for dependency tracking optimization.
16///
17/// Higher values indicate the data changes less frequently.
18/// Durability is specified when resolving assets, not on the type itself.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
20#[repr(u8)]
21pub enum DurabilityLevel {
22    /// Changes frequently (user input, live feeds).
23    #[default]
24    Volatile = 0,
25    /// Changes occasionally (configuration, session data).
26    Transient = 1,
27    /// Changes rarely (external dependencies).
28    Stable = 2,
29    /// Fixed for this session (bundled assets, constants).
30    Static = 3,
31}
32
33impl DurabilityLevel {
34    /// Convert to u8 for whale integration.
35    pub fn as_u8(self) -> u8 {
36        self as u8
37    }
38}
39
40/// Trait for asset keys that map to loadable assets.
41///
42/// Asset keys identify external resources (files, URLs, etc.) and define
43/// the type of asset they load. Assets are leaf nodes in the dependency
44/// graph - they have no dependencies but can be depended upon by queries.
45///
46/// Durability is specified when calling `resolve_asset()`, not on the key type.
47///
48/// # Example
49///
50/// ```ignore
51/// use query_flow::{asset_key, AssetKey};
52///
53/// #[asset_key(asset = String)]
54/// pub struct ConfigFile(pub PathBuf);
55///
56/// // Or manually:
57/// pub struct TextureId(pub u32);
58///
59/// impl AssetKey for TextureId {
60///     type Asset = ImageData;
61///
62///     fn asset_eq(old: &Self::Asset, new: &Self::Asset) -> bool {
63///         old.bytes == new.bytes
64///     }
65/// }
66/// ```
67pub trait AssetKey: Key + 'static {
68    /// The asset type this key loads.
69    type Asset: Send + Sync + 'static;
70
71    /// Compare two asset values for equality (for early cutoff).
72    ///
73    /// When an asset is re-resolved with the same value, dependent queries
74    /// can skip recomputation (early cutoff).
75    fn asset_eq(old: &Self::Asset, new: &Self::Asset) -> bool;
76}
77
78/// Result of locating an asset.
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub enum LocateResult<A> {
81    /// Asset is immediately available (e.g., from memory cache).
82    Ready {
83        /// The asset value.
84        value: A,
85        /// The durability level of this asset.
86        durability: DurabilityLevel,
87    },
88    /// Asset needs to be loaded asynchronously.
89    /// The runtime will track this as a pending request.
90    Pending,
91    /// Asset does not exist or cannot be located.
92    NotFound,
93}
94
95/// Trait for locating and loading assets.
96///
97/// Implement this trait to define how assets are found for a given key type.
98/// Different locators can be registered for different platforms:
99/// - Filesystem locator for desktop
100/// - Network locator for web/playground
101/// - Memory locator for testing
102///
103/// # Example
104///
105/// ```ignore
106/// struct FileSystemLocator {
107///     base_path: PathBuf,
108/// }
109///
110/// impl AssetLocator<FilePath> for FileSystemLocator {
111///     fn locate(&self, key: &FilePath) -> LocateResult<String> {
112///         // For sync IO, could read directly:
113///         // let path = self.base_path.join(&key.0);
114///         // match std::fs::read_to_string(&path) {
115///         //     Ok(content) => LocateResult::Ready {
116///         //         value: content,
117///         //         durability: DurabilityLevel::Stable,
118///         //     },
119///         //     Err(_) => LocateResult::NotFound,
120///         // }
121///
122///         // For async IO, return Pending:
123///         LocateResult::Pending
124///     }
125/// }
126/// ```
127pub trait AssetLocator<K: AssetKey>: Send + Sync + 'static {
128    /// Attempt to locate an asset for the given key.
129    ///
130    /// This method should be fast and non-blocking:
131    /// - Return `Ready { value, durability }` if the asset is immediately available
132    /// - Return `Pending` if the asset needs async loading
133    /// - Return `NotFound` if the asset cannot be found
134    ///
135    /// For assets requiring IO, typically return `Pending` and let the user
136    /// fetch the asset externally, then call `runtime.resolve_asset()`.
137    fn locate(&self, key: &K) -> LocateResult<K::Asset>;
138}
139
140/// A pending asset request that needs to be resolved.
141#[derive(Clone)]
142pub struct PendingAsset {
143    /// Type-erased key for the asset (stored as Arc for efficient cloning)
144    key: Arc<dyn Any + Send + Sync>,
145    /// Type ID of the AssetKey type
146    key_type: TypeId,
147    /// Debug representation
148    debug_repr: String,
149}
150
151impl PendingAsset {
152    /// Create a new pending asset.
153    pub fn new<K: AssetKey>(key: K) -> Self {
154        Self {
155            debug_repr: format!("{:?}", key),
156            key_type: TypeId::of::<K>(),
157            key: Arc::new(key),
158        }
159    }
160
161    /// Create from pre-computed parts (used by PendingStorage).
162    pub(crate) fn new_from_parts(
163        key_type: TypeId,
164        debug_repr: &str,
165        key: Arc<dyn Any + Send + Sync>,
166    ) -> Self {
167        Self {
168            key_type,
169            debug_repr: debug_repr.to_string(),
170            key,
171        }
172    }
173
174    /// Downcast the key to its concrete type.
175    pub fn key<K: AssetKey>(&self) -> Option<&K> {
176        if self.key_type == TypeId::of::<K>() {
177            self.key.downcast_ref()
178        } else {
179            None
180        }
181    }
182
183    /// Check if this pending asset is for the given key type.
184    pub fn is<K: AssetKey>(&self) -> bool {
185        self.key_type == TypeId::of::<K>()
186    }
187
188    /// Get the TypeId of the key type.
189    pub fn key_type(&self) -> TypeId {
190        self.key_type
191    }
192
193    /// Get debug representation.
194    pub fn debug_repr(&self) -> &str {
195        &self.debug_repr
196    }
197}
198
199impl Debug for PendingAsset {
200    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201        write!(f, "PendingAsset({})", self.debug_repr)
202    }
203}
204
205/// Full cache key for assets (includes AssetKey type information).
206///
207/// This is similar to `FullCacheKey` for queries but marks the entry
208/// as an asset in the dependency graph.
209#[derive(Clone)]
210pub(crate) struct FullAssetKey {
211    /// Type ID of the AssetKey type
212    key_type: TypeId,
213    /// Hash of the key value
214    key_hash: u64,
215    /// Debug representation
216    debug_repr: Arc<str>,
217}
218
219impl FullAssetKey {
220    /// Create a new full asset key.
221    pub fn new<K: AssetKey>(key: &K) -> Self {
222        use std::hash::Hasher;
223        let mut hasher = ahash::AHasher::default();
224        key.hash(&mut hasher);
225        let key_hash = hasher.finish();
226
227        Self {
228            key_type: TypeId::of::<K>(),
229            key_hash,
230            debug_repr: Arc::from(format!("Asset:{}({:?})", std::any::type_name::<K>(), key)),
231        }
232    }
233
234    /// Get debug representation for error messages.
235    pub fn debug_repr(&self) -> &str {
236        &self.debug_repr
237    }
238
239    /// Get the key type.
240    pub fn key_type(&self) -> TypeId {
241        self.key_type
242    }
243
244    /// Get the key hash.
245    pub fn key_hash(&self) -> u64 {
246        self.key_hash
247    }
248}
249
250impl Debug for FullAssetKey {
251    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252        write!(f, "{}", self.debug_repr)
253    }
254}
255
256impl std::hash::Hash for FullAssetKey {
257    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
258        self.key_type.hash(state);
259        self.key_hash.hash(state);
260    }
261}
262
263impl PartialEq for FullAssetKey {
264    fn eq(&self, other: &Self) -> bool {
265        self.key_type == other.key_type && self.key_hash == other.key_hash
266    }
267}
268
269impl Eq for FullAssetKey {}