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
26 bundled_path: ConstStr,
28
29 options: AssetOptions,
31}
32
33impl PartialEq for BundledAsset {
34 fn eq(&self, other: &Self) -> bool {
35 self.absolute_source_path == other.absolute_source_path
36 && self.bundled_path == other.bundled_path
37 && self.options == other.options
38 }
39}
40
41impl PartialOrd for BundledAsset {
42 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
43 match self
44 .absolute_source_path
45 .partial_cmp(&other.absolute_source_path)
46 {
47 Some(core::cmp::Ordering::Equal) => {}
48 ord => return ord,
49 }
50 match self.bundled_path.partial_cmp(&other.bundled_path) {
51 Some(core::cmp::Ordering::Equal) => {}
52 ord => return ord,
53 }
54 self.options.partial_cmp(&other.options)
55 }
56}
57
58impl Hash for BundledAsset {
59 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
60 self.absolute_source_path.hash(state);
61 self.bundled_path.hash(state);
62 self.options.hash(state);
63 }
64}
65
66impl BundledAsset {
67 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.";
68
69 #[doc(hidden)]
70 pub const fn new(
73 absolute_source_path: &str,
74 bundled_path: &str,
75 options: AssetOptions,
76 ) -> Self {
77 Self {
78 absolute_source_path: ConstStr::new(absolute_source_path),
79 bundled_path: ConstStr::new(bundled_path),
80 options,
81 }
82 }
83
84 pub fn bundled_path(&self) -> &str {
86 self.bundled_path.as_str()
87 }
88
89 pub fn absolute_source_path(&self) -> &str {
91 self.absolute_source_path.as_str()
92 }
93
94 pub const fn options(&self) -> &AssetOptions {
96 &self.options
97 }
98}
99
100#[allow(unpredictable_function_pointer_comparisons)]
113#[derive(PartialEq, Clone, Copy)]
114pub struct Asset {
115 bundled: fn() -> &'static [u8],
124 legacy: fn() -> &'static [u8],
126}
127
128impl Debug for Asset {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 self.resolve().fmt(f)
131 }
132}
133
134unsafe impl Send for Asset {}
135unsafe impl Sync for Asset {}
136
137impl Asset {
138 #[doc(hidden)]
139 pub const fn new(
142 bundled: extern "Rust" fn() -> &'static [u8],
143 legacy: extern "Rust" fn() -> &'static [u8],
144 ) -> Self {
145 Self { bundled, legacy }
146 }
147
148 pub fn bundled(&self) -> BundledAsset {
150 fn read_slice_volatile(bundled: &'static [u8]) -> Vec<u8> {
153 let ptr = bundled as *const [u8] as *const u8;
154 let len = bundled.len();
155 if ptr.is_null() {
156 panic!("Tried to use an asset that was not bundled. Make sure you are compiling dx as the linker");
157 }
158 let mut bytes = Vec::with_capacity(len);
159 for byte in 0..len {
160 let byte = unsafe { std::ptr::read_volatile(ptr.add(byte)) };
163 bytes.push(byte);
164 }
165 bytes
166 }
167
168 let bundled = (self.bundled)();
169 let bytes = read_slice_volatile(bundled);
170 let read = bytes.as_slice();
171 let asset = deserialize_const!(BundledAsset, read).expect("Failed to deserialize asset. Make sure you built with the matching version of the Dioxus CLI").1;
172
173 if asset.bundled_path() == BundledAsset::PLACEHOLDER_HASH {
175 let bundled = (self.legacy)();
176 let bytes = read_slice_volatile(bundled);
177 let read = const_serialize_07::ConstReadBuffer::new(bytes.as_ref());
178 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;
179 asset
180 } else {
181 asset
182 }
183 }
184
185 pub fn resolve(&self) -> PathBuf {
190 #[cfg(feature = "dioxus")]
191 if !dioxus_core_types::is_bundled_app() {
193 return PathBuf::from(self.bundled().absolute_source_path.as_str());
194 }
195
196 #[cfg(feature = "dioxus")]
197 let bundle_root = {
198 let base_path = dioxus_cli_config::base_path();
199 let base_path = base_path
200 .as_deref()
201 .map(|base_path| {
202 let trimmed = base_path.trim_matches('/');
203 format!("/{trimmed}")
204 })
205 .unwrap_or_default();
206 PathBuf::from(format!("{base_path}/assets/"))
207 };
208 #[cfg(not(feature = "dioxus"))]
209 let bundle_root = PathBuf::from("/assets/");
210
211 bundle_root.join(PathBuf::from(
213 self.bundled().bundled_path.as_str().trim_start_matches('/'),
214 ))
215 }
216}
217
218impl From<Asset> for String {
219 fn from(value: Asset) -> Self {
220 value.to_string()
221 }
222}
223impl From<Asset> for Option<String> {
224 fn from(value: Asset) -> Self {
225 Some(value.to_string())
226 }
227}
228
229impl std::fmt::Display for Asset {
230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231 write!(f, "{}", self.resolve().display())
232 }
233}
234
235#[cfg(feature = "dioxus")]
236impl dioxus_core_types::DioxusFormattable for Asset {
237 fn format(&self) -> std::borrow::Cow<'static, str> {
238 std::borrow::Cow::Owned(self.to_string())
239 }
240}