2019-04-26 03:30:46 +00:00
|
|
|
use dot_vox::DotVoxData;
|
|
|
|
use image::DynamicImage;
|
|
|
|
use lazy_static::lazy_static;
|
2019-06-03 22:11:27 +00:00
|
|
|
use serde_json::Value;
|
2019-04-26 03:30:46 +00:00
|
|
|
use std::{
|
|
|
|
any::Any,
|
|
|
|
collections::HashMap,
|
2019-07-01 13:35:06 +00:00
|
|
|
env,
|
|
|
|
fs::{read_dir, read_link, File, ReadDir},
|
2019-05-13 23:28:17 +00:00
|
|
|
io::BufReader,
|
2019-04-26 03:30:46 +00:00
|
|
|
io::Read,
|
2019-07-01 13:35:06 +00:00
|
|
|
path::{Path, PathBuf},
|
2019-04-28 02:12:30 +00:00
|
|
|
sync::{Arc, RwLock},
|
2019-04-26 03:30:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum Error {
|
2019-05-17 09:22:32 +00:00
|
|
|
/// An asset of a different type has already been loaded with this specifier.
|
2019-04-26 03:30:46 +00:00
|
|
|
InvalidType,
|
2019-05-17 09:22:32 +00:00
|
|
|
/// Asset does not exist.
|
2019-04-29 00:23:14 +00:00
|
|
|
NotFound(String),
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Arc<dyn Any + 'static + Sync + Send>> for Error {
|
2019-04-29 00:23:14 +00:00
|
|
|
fn from(_: Arc<dyn Any + 'static + Sync + Send>) -> Self {
|
2019-04-26 03:30:46 +00:00
|
|
|
Error::InvalidType
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<std::io::Error> for Error {
|
2019-04-29 00:23:14 +00:00
|
|
|
fn from(err: std::io::Error) -> Self {
|
|
|
|
Error::NotFound(format!("{:?}", err))
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
static ref ASSETS: RwLock<HashMap<String, Arc<dyn Any + 'static + Sync + Send>>> =
|
|
|
|
RwLock::new(HashMap::new());
|
|
|
|
}
|
|
|
|
|
2019-05-25 08:38:30 +00:00
|
|
|
/// Function used to load assets. Permits manipulating the loaded asset with a mapping function.
|
|
|
|
/// Loaded assets are cached in a global singleton hashmap.
|
|
|
|
/// Example usage:
|
|
|
|
/// ```no_run
|
|
|
|
/// use image::DynamicImage;
|
|
|
|
/// use veloren_common::assets;
|
|
|
|
///
|
|
|
|
/// let my_image = assets::load::<DynamicImage>("core.ui.backgrounds.city").unwrap();
|
|
|
|
/// ```
|
2019-05-25 05:54:47 +00:00
|
|
|
pub fn load_map<A: Asset + 'static, F: FnOnce(A) -> A>(
|
|
|
|
specifier: &str,
|
|
|
|
f: F,
|
|
|
|
) -> Result<Arc<A>, Error> {
|
2019-05-24 12:25:31 +00:00
|
|
|
Ok(ASSETS
|
|
|
|
.write()
|
|
|
|
.unwrap()
|
|
|
|
.entry(specifier.to_string())
|
|
|
|
.or_insert(Arc::new(f(A::load(specifier)?)))
|
|
|
|
.clone()
|
|
|
|
.downcast()?)
|
|
|
|
}
|
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
/// Function used to load assets.
|
|
|
|
/// Loaded assets are cached in a global singleton hashmap.
|
2019-04-26 03:30:46 +00:00
|
|
|
/// Example usage:
|
2019-05-07 18:36:07 +00:00
|
|
|
/// ```no_run
|
2019-04-26 03:30:46 +00:00
|
|
|
/// use image::DynamicImage;
|
2019-05-07 18:36:07 +00:00
|
|
|
/// use veloren_common::assets;
|
2019-05-07 05:40:03 +00:00
|
|
|
///
|
2019-05-07 18:36:07 +00:00
|
|
|
/// let my_image = assets::load::<DynamicImage>("core.ui.backgrounds.city").unwrap();
|
2019-04-26 03:30:46 +00:00
|
|
|
/// ```
|
|
|
|
pub fn load<A: Asset + 'static>(specifier: &str) -> Result<Arc<A>, Error> {
|
2019-05-24 12:25:31 +00:00
|
|
|
load_map(specifier, |x| x)
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
/// Function used to load assets that will panic if the asset is not found.
|
|
|
|
/// Use this to load essential assets.
|
|
|
|
/// Loaded assets are cached in a global singleton hashmap.
|
2019-04-29 00:23:14 +00:00
|
|
|
/// Example usage:
|
2019-05-07 18:36:07 +00:00
|
|
|
/// ```no_run
|
2019-04-29 00:23:14 +00:00
|
|
|
/// use image::DynamicImage;
|
2019-05-07 18:36:07 +00:00
|
|
|
/// use veloren_common::assets;
|
2019-05-07 05:40:03 +00:00
|
|
|
///
|
2019-05-07 18:36:07 +00:00
|
|
|
/// let my_image = assets::load_expect::<DynamicImage>("core.ui.backgrounds.city");
|
2019-04-29 00:23:14 +00:00
|
|
|
/// ```
|
|
|
|
pub fn load_expect<A: Asset + 'static>(specifier: &str) -> Arc<A> {
|
2019-05-07 05:40:03 +00:00
|
|
|
load(specifier).expect(&format!("Failed loading essential asset: {}", specifier))
|
2019-04-29 00:23:14 +00:00
|
|
|
}
|
|
|
|
|
2019-04-26 03:30:46 +00:00
|
|
|
/// Asset Trait
|
|
|
|
pub trait Asset: Send + Sync + Sized {
|
|
|
|
fn load(specifier: &str) -> Result<Self, Error>;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Asset for DynamicImage {
|
|
|
|
fn load(specifier: &str) -> Result<Self, Error> {
|
2019-05-13 23:28:17 +00:00
|
|
|
let mut buf = Vec::new();
|
|
|
|
load_from_path(specifier)?.read_to_end(&mut buf)?;
|
|
|
|
Ok(image::load_from_memory(&buf).unwrap())
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Asset for DotVoxData {
|
|
|
|
fn load(specifier: &str) -> Result<Self, Error> {
|
2019-05-13 23:28:17 +00:00
|
|
|
let mut buf = Vec::new();
|
|
|
|
load_from_path(specifier)?.read_to_end(&mut buf)?;
|
|
|
|
Ok(dot_vox::load_bytes(&buf).unwrap())
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-03 22:11:27 +00:00
|
|
|
impl Asset for Value {
|
|
|
|
fn load(specifier: &str) -> Result<Self, Error> {
|
2019-06-04 12:39:46 +00:00
|
|
|
Ok(serde_json::from_reader(load_from_path(specifier)?).unwrap())
|
2019-06-03 22:11:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
// TODO: System to load file from specifiers (e.g.: "core.ui.backgrounds.city").
|
2019-07-01 13:35:06 +00:00
|
|
|
fn assets_folder() -> PathBuf {
|
2019-07-02 12:42:39 +00:00
|
|
|
let mut paths = Vec::new();
|
|
|
|
|
|
|
|
// VELOREN_ASSETS environment variable
|
|
|
|
if let Ok(var) = std::env::var("VELOREN_ASSETS") {
|
|
|
|
paths.push(var.to_string().into());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Executable path
|
|
|
|
if let Ok(mut path) = std::env::current_exe() {
|
|
|
|
path.pop();
|
|
|
|
paths.push(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Working path
|
|
|
|
if let Ok(mut path) = std::env::current_dir() {
|
|
|
|
path.pop();
|
|
|
|
paths.push(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
// System paths
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
paths.push("/usr/share/veloren/assets".into());
|
|
|
|
|
|
|
|
for path in paths.clone() {
|
|
|
|
match find_folder::Search::ParentsThenKids(3, 1)
|
|
|
|
.of(path)
|
|
|
|
.for_folder("assets")
|
|
|
|
{
|
|
|
|
Ok(assets_path) => return assets_path,
|
|
|
|
Err(_) => continue,
|
2019-07-01 13:35:06 +00:00
|
|
|
}
|
2019-06-28 17:38:36 +00:00
|
|
|
}
|
2019-07-02 12:42:39 +00:00
|
|
|
|
|
|
|
panic!(
|
|
|
|
"Could not find assets folder (tried to search... {})",
|
|
|
|
paths.iter().fold(String::new(), |mut a, path| {
|
|
|
|
a += path.to_str().unwrap_or("<invalid>");
|
|
|
|
a += "\n";
|
|
|
|
a
|
|
|
|
}),
|
|
|
|
);
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
|
2019-07-01 13:35:06 +00:00
|
|
|
// TODO: System to load file from specifiers (e.g.: "core.ui.backgrounds.city").
|
2019-05-13 23:28:17 +00:00
|
|
|
pub fn load_from_path(name: &str) -> Result<BufReader<File>, Error> {
|
2019-07-01 13:35:06 +00:00
|
|
|
debug!("Trying to access \"{}\"", name);
|
|
|
|
|
|
|
|
let mut path = assets_folder();
|
|
|
|
path.push(name);
|
|
|
|
|
|
|
|
match File::open(path) {
|
|
|
|
Ok(file) => Ok(BufReader::new(file)),
|
|
|
|
Err(_) => Err(Error::NotFound(name.to_owned())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Read directory from `veloren/assets/*`
|
|
|
|
pub fn read_from_assets(dir_name: &str) -> Result<ReadDir, Error> {
|
|
|
|
let mut entry = assets_folder();
|
|
|
|
entry.push("../assets/");
|
|
|
|
entry.push(dir_name);
|
|
|
|
match Path::new(&entry).exists() {
|
|
|
|
true => Ok(read_dir(entry).expect("`read_dir` failed.")),
|
|
|
|
false => Err(Error::NotFound(entry.to_str().unwrap().to_owned())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the cargo manifest directory when running the executable with cargo
|
|
|
|
/// or the directory in which the executable resides otherwise,
|
|
|
|
/// traversing symlinks if necessary.
|
|
|
|
pub fn application_root_dir() -> String {
|
|
|
|
match env::var("PROFILE") {
|
|
|
|
Ok(_) => String::from(env!("CARGO_MANIFEST_DIR")),
|
|
|
|
Err(_) => {
|
|
|
|
let mut path = env::current_exe().expect("Failed to find executable path.");
|
|
|
|
while let Ok(target) = read_link(path.clone()) {
|
|
|
|
path = target;
|
|
|
|
}
|
|
|
|
String::from(
|
|
|
|
path.parent()
|
|
|
|
.expect("Failed to get parent directory of the executable.")
|
|
|
|
.to_str()
|
|
|
|
.unwrap(),
|
|
|
|
)
|
|
|
|
}
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
}
|