1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
use crate::engine::d2::{asset::AssetEntry, util::StringExtensions, System};
use super::AssetFormat;
/// An asset manifest contains all the information needed to load an asset pack. A manifest is
/// usually created with `Manifest.fromAssets("directory")`, but manifests can also be assembled
/// programmatically.
#[derive(Default, Debug)]
pub struct Manifest {
pub entries: Vec<AssetEntry>,
/// A base path on the current domain to load this manifest's assets from, or None.
pub local_base: Option<String>,
/// A base URL on another domain to load this manifest's assets from, or None. May be used to
/// load assets from a CDN, in browsers that support cross-domain requests. If not supported or
/// unset, will fallback to using localBase.
pub remote_base: Option<String>,
}
impl Manifest {
pub fn new() -> Self {
Self {
entries: Vec::new(),
remote_base: None,
local_base: None,
}
}
/// Gets the manifest of a pack in the asset directory, that was processed at build-time.
/// @param packName The folder name in your assets/ directory.
/// @param required When true and this pack was not found, throw an error. Otherwise None is
/// returned.
// static
// required :bool = true
pub fn from_assets(pack_name: String, required: bool) -> Option<Manifest> {
// let packData: Vec<Vec<String>> = Reflect::field(Meta::getType(Manifest).assets[0], packName);
// if packData.is_none() {
// if required {
// panic!("Missing asset pack ["name", {}]", packName);
// }
// return None;
// }
// let manifest = Manifest::new();
// manifest.localBase = "assets";
// for asset in packData {
// let name = asset.name;
// let path = packName + "/" + name + "?v=" + asset.md5;
// let format = Self::inferFormat(name);
// if format != AssetFormat::BLOB {
// // If this an asset that not all platforms may support, trim the extension from
// // the name. We'll only load one of the assets if this creates a name collision.
// name = name.remove_file_extension();
// }
// manifest.add(name, path, asset.bytes, format);
// }
// return manifest;
unimplemented!()
}
/// Tries to find a pack suffixed with the closest available variant of the locale. For example,
/// fromAssetsLocalized("foo", "pt-BR") will first try to load foo_pt-BR, then foo_pt, then just
/// foo.
/// @param packName The folder name in your assets/ directory.
/// @param locale An RFC 4646 language tag, or None to use the system language.
/// @param required When true and this pack was not found, throw an error. Otherwise None is
/// returned.
// static
// locale :String = None, required :bool = true
pub fn from_assets_localized(
pack_name: String,
locale: Option<String>,
required: bool,
) -> Option<Manifest> {
let locale = if locale.is_none() {
System::locale()
} else {
locale
};
if let Some(locale) = locale {
let mut parts: Vec<String> = locale.split("-").map(String::from).collect();
while parts.len() > 0 {
let path = format!("{}_{}", pack_name, parts.join("-"));
let manifest = Self::from_assets(path, false);
if manifest.is_some() {
return manifest;
}
parts.pop();
}
}
Self::from_assets(pack_name, required)
}
/// Returns true if the given named pack was included in the asset directory at build-time.
// static
pub fn exists(pack_name: String) -> bool {
// return Reflect::hasField(Meta::getType(Manifest).assets[0], packName);
unimplemented!()
}
/// Adds an asset entry to this manifest.
/// @param name The name of the asset.
/// @param url The URL this asset will be downloaded from.
/// @param bytes The size in bytes.
/// @param format Optionally specified content format, by default infer it from the URL.
// bytes: i32 = 0, ?format :AssetFormat
pub fn add(
&mut self,
name: String,
url: String,
bytes: usize,
format: Option<AssetFormat>,
) -> AssetEntry {
let format = format.unwrap_or(Self::infer_format(url.as_str()));
let entry = AssetEntry::new(name, url, format, bytes);
self.entries.push(entry.clone());
entry
}
/// Iterates over all the assets defined in this manifest.
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &AssetEntry> + '_ {
self.entries.iter()
}
pub fn len(&self) -> usize {
self.entries.len()
}
/// Get the full URL to load an asset from. Will prepend localBase or remoteBase depending on
/// cross-domain support.
pub fn full_url(&self, entry: AssetEntry) -> String {
const SUPPORT_CROSS_ORIGIN: bool = true;
let base_path = if self.remote_base.is_some() && SUPPORT_CROSS_ORIGIN {
&self.remote_base
} else {
&self.local_base
};
if let Some(path) = base_path {
path.join_path(entry.url)
} else {
entry.url
}
}
fn local_base(&self) -> Option<String> {
self.local_base.clone()
}
fn set_local_base(&mut self, local_base: Option<String>) {
if let Some(ref local_base) = local_base {
assert!(
!local_base.starts_with("http://") && !local_base.starts_with("https://"),
"localBase must be a path on the same domain, NOT starting with http(s)://",
);
}
self.local_base = local_base;
}
fn remote_base(&self) -> Option<String> {
self.remote_base.clone()
}
fn set_remote_base(&mut self, remote_base: Option<String>) {
if let Some(ref remote_base) = remote_base {
assert!(
remote_base.starts_with("http://") || remote_base.starts_with("https://"),
"remoteBase must be on a remote domain, starting with http(s)://",
);
}
self.remote_base = remote_base;
}
// static
fn infer_format(url: &str) -> AssetFormat {
if let Some(extension) = url.url_extension() {
return match extension.to_lowercase().as_str() {
"gif" => AssetFormat::GIF,
"jpg" | "jpeg" => AssetFormat::JPG,
"jxr" | "wdp" => AssetFormat::JXR,
"png" => AssetFormat::PNG,
"webp" => AssetFormat::WEBP,
"dds" => AssetFormat::DDS,
"pvr" => AssetFormat::PVR,
"pkm" => AssetFormat::PKM,
"m4a" => AssetFormat::M4A,
"mp3" => AssetFormat::MP3,
"ogg" => AssetFormat::OGG,
"opus" => AssetFormat::OPUS,
"wav" => AssetFormat::WAV,
_ => AssetFormat::BLOB,
};
} else {
// log::warn!("No file extension for asset, it will be loaded as data [url, {}]", url);
}
return AssetFormat::BLOB;
}
// // Whether the environment fully supports loading assets from another domain
// // static
// let _supportsCrossOrigin = (|| -> bool {
// let detected =
// #[cfg(feature = "html")]
// (fn () {
// // CORS in the stock Android browser is buggy. If your game is contained in an iframe, XHR
// // will work the first time. If the response had an Expires header, on subsequent page loads
// // instead of retrieving it from the cache, it will fail with error code 0.
// // http://stackoverflow.com/questions/6090816/android-cors-requests-work-only-once
// if Browser::navigator.userAgent.iter().position(|x| x == "Linux; U; Android").is_some() {
// return false;
// }
// let xhr :Dynamic = __new__("XMLHttpRequest");
// return (xhr.withCredentials.is_some());
// })();
// #else
// true; // Assumes you have a valid crossdomain.xml
// #end
// if (!detected) {
// log::warn!("This browser does not support cross-domain asset loading, any Manifest.remoteBase setting will be ignored.");
// }
// return detected;
// })();
}
impl PartialEq for Manifest {
fn eq(&self, other: &Self) -> bool {
self.local_base == other.local_base && self.remote_base == other.remote_base
}
}