1use crate::AssetOptions;
2use const_serialize_07 as const_serialize;
3use const_serialize_08::{deserialize_const, ConstStr, SerializeConst};
4use std::{fmt::Debug, hash::Hash, path::PathBuf};
5
6#[derive(
12 Debug,
13 Eq,
14 Clone,
15 Copy,
16 SerializeConst,
17 const_serialize::SerializeConst,
18 serde::Serialize,
19 serde::Deserialize,
20)]
21#[const_serialize(crate = const_serialize_08)]
22pub struct BundledAsset {
23 absolute_source_path: ConstStr,
25 bundled_path: ConstStr,
27 options: AssetOptions,
29}
30
31impl PartialEq for BundledAsset {
32 fn eq(&self, other: &Self) -> bool {
33 self.absolute_source_path == other.absolute_source_path
34 && self.bundled_path == other.bundled_path
35 && self.options == other.options
36 }
37}
38
39impl PartialOrd for BundledAsset {
40 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
41 match self
42 .absolute_source_path
43 .partial_cmp(&other.absolute_source_path)
44 {
45 Some(core::cmp::Ordering::Equal) => {}
46 ord => return ord,
47 }
48 match self.bundled_path.partial_cmp(&other.bundled_path) {
49 Some(core::cmp::Ordering::Equal) => {}
50 ord => return ord,
51 }
52 self.options.partial_cmp(&other.options)
53 }
54}
55
56impl Hash for BundledAsset {
57 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
58 self.absolute_source_path.hash(state);
59 self.bundled_path.hash(state);
60 self.options.hash(state);
61 }
62}
63
64impl BundledAsset {
65 pub const PLACEHOLDER_HASH: &str = "This should be replaced by dx as part of the build process. If you see this error, make sure you are using a matching version of dx and dioxus and you are not stripping symbols from your binary.";
66
67 #[doc(hidden)]
68 pub const fn new(
71 absolute_source_path: &str,
72 bundled_path: &str,
73 options: AssetOptions,
74 ) -> Self {
75 Self {
76 absolute_source_path: ConstStr::new(absolute_source_path),
77 bundled_path: ConstStr::new(bundled_path),
78 options,
79 }
80 }
81
82 pub fn bundled_path(&self) -> &str {
84 self.bundled_path.as_str()
85 }
86
87 pub fn absolute_source_path(&self) -> &str {
89 self.absolute_source_path.as_str()
90 }
91
92 pub const fn options(&self) -> &AssetOptions {
94 &self.options
95 }
96}
97
98#[allow(unpredictable_function_pointer_comparisons)]
111#[derive(PartialEq, Clone, Copy)]
112pub struct Asset {
113 bundled: fn() -> &'static [u8],
122 legacy: fn() -> &'static [u8],
124}
125
126impl Debug for Asset {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 self.resolve().fmt(f)
129 }
130}
131
132unsafe impl Send for Asset {}
133unsafe impl Sync for Asset {}
134
135impl Asset {
136 #[doc(hidden)]
137 pub const fn new(
140 bundled: extern "Rust" fn() -> &'static [u8],
141 legacy: extern "Rust" fn() -> &'static [u8],
142 ) -> Self {
143 Self { bundled, legacy }
144 }
145
146 pub fn bundled(&self) -> BundledAsset {
148 fn read_slice_volatile(bundled: &'static [u8]) -> const_serialize_07::ConstVec<u8> {
151 let ptr = bundled as *const [u8] as *const u8;
152 let len = bundled.len();
153 if ptr.is_null() {
154 panic!("Tried to use an asset that was not bundled. Make sure you are compiling dx as the linker");
155 }
156 let mut bytes = const_serialize_07::ConstVec::new();
157 for byte in 0..len {
158 let byte = unsafe { std::ptr::read_volatile(ptr.add(byte)) };
161 bytes = bytes.push(byte);
162 }
163 bytes
164 }
165
166 let bundled = (self.bundled)();
167 let bytes = read_slice_volatile(bundled);
168 let read = bytes.as_ref();
169 let asset = deserialize_const!(BundledAsset, read).expect("Failed to deserialize asset. Make sure you built with the matching version of the Dioxus CLI").1;
170
171 if asset.bundled_path() == BundledAsset::PLACEHOLDER_HASH {
173 let bundled = (self.legacy)();
174 let bytes = read_slice_volatile(bundled);
175 let read = bytes.read();
176 let asset = const_serialize_07::deserialize_const!(BundledAsset, read).expect("Failed to deserialize asset. Make sure you built with the matching version of the Dioxus CLI").1;
177 asset
178 } else {
179 asset
180 }
181 }
182
183 pub fn resolve(&self) -> PathBuf {
188 #[cfg(feature = "dioxus")]
189 if !dioxus_core_types::is_bundled_app() {
191 return PathBuf::from(self.bundled().absolute_source_path.as_str());
192 }
193
194 #[cfg(feature = "dioxus")]
195 let bundle_root = {
196 let base_path = dioxus_cli_config::base_path();
197 let base_path = base_path
198 .as_deref()
199 .map(|base_path| {
200 let trimmed = base_path.trim_matches('/');
201 format!("/{trimmed}")
202 })
203 .unwrap_or_default();
204 PathBuf::from(format!("{base_path}/assets/"))
205 };
206 #[cfg(not(feature = "dioxus"))]
207 let bundle_root = PathBuf::from("/assets/");
208
209 bundle_root.join(PathBuf::from(
211 self.bundled().bundled_path.as_str().trim_start_matches('/'),
212 ))
213 }
214}
215
216impl From<Asset> for String {
217 fn from(value: Asset) -> Self {
218 value.to_string()
219 }
220}
221impl From<Asset> for Option<String> {
222 fn from(value: Asset) -> Self {
223 Some(value.to_string())
224 }
225}
226
227impl std::fmt::Display for Asset {
228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229 write!(f, "{}", self.resolve().display())
230 }
231}
232
233#[cfg(feature = "dioxus")]
234impl dioxus_core_types::DioxusFormattable for Asset {
235 fn format(&self) -> std::borrow::Cow<'static, str> {
236 std::borrow::Cow::Owned(self.to_string())
237 }
238}