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