2019-08-06 06:31:48 +00:00
|
|
|
//! Load assets (images or voxel data) from files
|
2019-08-03 05:42:33 +00:00
|
|
|
pub mod watch;
|
2019-08-06 06:31:48 +00:00
|
|
|
|
2019-04-26 03:30:46 +00:00
|
|
|
use dot_vox::DotVoxData;
|
2019-08-11 20:38:28 +00:00
|
|
|
use hashbrown::HashMap;
|
2019-04-26 03:30:46 +00:00
|
|
|
use image::DynamicImage;
|
|
|
|
use lazy_static::lazy_static;
|
2019-08-03 22:33:59 +00:00
|
|
|
use log::error;
|
2019-06-03 22:11:27 +00:00
|
|
|
use serde_json::Value;
|
2019-04-26 03:30:46 +00:00
|
|
|
use std::{
|
|
|
|
any::Any,
|
2019-07-01 13:35:06 +00:00
|
|
|
env,
|
2019-08-06 06:31:48 +00:00
|
|
|
fs::{self, read_link, File, ReadDir},
|
2019-06-23 20:42:17 +00:00
|
|
|
io::{BufReader, 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
|
|
|
};
|
|
|
|
|
2019-08-06 06:31:48 +00:00
|
|
|
/// The error returned by asset loading functions
|
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! {
|
2019-08-06 06:31:48 +00:00
|
|
|
/// The HashMap where all loaded assets are stored in.
|
2019-04-26 03:30:46 +00:00
|
|
|
static ref ASSETS: RwLock<HashMap<String, Arc<dyn Any + 'static + Sync + Send>>> =
|
|
|
|
RwLock::new(HashMap::new());
|
|
|
|
}
|
|
|
|
|
2019-08-06 06:31:48 +00:00
|
|
|
// TODO: Remove this function. It's only used in world/ in a really ugly way.To do this properly
|
|
|
|
// assets should have all their necessary data in one file. A ron file could be used to combine
|
|
|
|
// voxel data with positioning data for example.
|
|
|
|
/// Function used to load assets from the filesystem or the cache. Permits manipulating the loaded asset with a mapping function.
|
2019-05-25 08:38:30 +00:00
|
|
|
/// Example usage:
|
2019-08-06 06:31:48 +00:00
|
|
|
/// ```no_run
|
2019-06-23 20:42:17 +00:00
|
|
|
/// use veloren_common::{assets, terrain::Structure};
|
2019-07-03 09:17:46 +00:00
|
|
|
/// use vek::*;
|
2019-05-25 08:38:30 +00:00
|
|
|
///
|
2019-06-23 20:42:17 +00:00
|
|
|
/// let my_tree_structure = assets::load_map(
|
2019-08-06 06:31:48 +00:00
|
|
|
/// "world.tree.oak_green.1",
|
2019-06-23 20:42:17 +00:00
|
|
|
/// |s: Structure| s.with_center(Vec3::new(15, 18, 14)),
|
|
|
|
/// ).unwrap();
|
2019-05-25 08:38:30 +00:00
|
|
|
/// ```
|
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-07-01 20:42:43 +00:00
|
|
|
let mut assets_write = ASSETS.write().unwrap();
|
2019-08-03 05:42:33 +00:00
|
|
|
match assets_write.get(specifier) {
|
2019-07-01 20:42:43 +00:00
|
|
|
Some(asset) => Ok(Arc::clone(asset).downcast()?),
|
|
|
|
None => {
|
2019-08-06 06:31:48 +00:00
|
|
|
let asset = Arc::new(f(A::parse(load_file(specifier, A::ENDINGS)?)?));
|
2019-07-01 20:42:43 +00:00
|
|
|
let clone = Arc::clone(&asset);
|
|
|
|
assets_write.insert(specifier.to_owned(), clone);
|
|
|
|
Ok(asset)
|
|
|
|
}
|
|
|
|
}
|
2019-05-24 12:25:31 +00:00
|
|
|
}
|
|
|
|
|
2019-08-06 06:31:48 +00:00
|
|
|
/// Function used to load assets from the filesystem or the cache.
|
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-08-06 06:31:48 +00:00
|
|
|
/// Function used to load essential assets from the filesystem or the cache. It will panic if the asset is not found.
|
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-07-01 20:42:43 +00:00
|
|
|
load(specifier).unwrap_or_else(|_| panic!("Failed loading essential asset: {}", specifier))
|
2019-04-29 00:23:14 +00:00
|
|
|
}
|
|
|
|
|
2019-08-03 05:42:33 +00:00
|
|
|
/// Load an asset while registering it to be watched and reloaded when it changes
|
|
|
|
pub fn load_watched<A: Asset + 'static>(
|
|
|
|
specifier: &str,
|
|
|
|
indicator: &mut watch::ReloadIndicator,
|
|
|
|
) -> Result<Arc<A>, Error> {
|
2019-08-03 22:33:59 +00:00
|
|
|
let asset = load(specifier)?;
|
|
|
|
|
2019-08-03 05:42:33 +00:00
|
|
|
// Determine path to watch
|
2019-08-12 01:53:48 +00:00
|
|
|
let path = unpack_specifier(specifier);
|
|
|
|
let mut path_with_extension = None;
|
2019-08-03 05:42:33 +00:00
|
|
|
for ending in A::ENDINGS {
|
|
|
|
let mut path = path.clone();
|
|
|
|
path.set_extension(ending);
|
|
|
|
|
|
|
|
if path.exists() {
|
2019-08-12 01:53:48 +00:00
|
|
|
path_with_extension = Some(path);
|
2019-08-03 05:42:33 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let owned_specifier = specifier.to_string();
|
2019-08-12 01:53:48 +00:00
|
|
|
indicator.add(
|
|
|
|
path_with_extension.ok_or_else(|| Error::NotFound(path.to_string_lossy().into_owned()))?,
|
|
|
|
move || {
|
|
|
|
if let Err(err) = reload::<A>(&owned_specifier) {
|
|
|
|
error!("Error reloading {}: {:#?}", &owned_specifier, err);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
2019-08-03 22:33:59 +00:00
|
|
|
|
|
|
|
Ok(asset)
|
2019-08-03 05:42:33 +00:00
|
|
|
}
|
|
|
|
|
2019-08-06 06:31:48 +00:00
|
|
|
/// The Asset trait, which is implemented by all structures that have their data stored in the
|
|
|
|
/// filesystem.
|
2019-08-03 05:42:33 +00:00
|
|
|
fn reload<A: Asset + 'static>(specifier: &str) -> Result<(), Error> {
|
|
|
|
let asset = Arc::new(A::parse(load_file(specifier, A::ENDINGS)?)?);
|
|
|
|
let clone = Arc::clone(&asset);
|
|
|
|
let mut assets_write = ASSETS.write().unwrap();
|
|
|
|
match assets_write.get_mut(specifier) {
|
|
|
|
Some(a) => *a = clone,
|
|
|
|
None => {
|
|
|
|
assets_write.insert(specifier.to_owned(), clone);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Asset Trait
|
2019-04-26 03:30:46 +00:00
|
|
|
pub trait Asset: Send + Sync + Sized {
|
2019-08-06 06:31:48 +00:00
|
|
|
const ENDINGS: &'static [&'static str];
|
|
|
|
/// Parse the input file and return the correct Asset.
|
|
|
|
fn parse(buf_reader: BufReader<File>) -> Result<Self, Error>;
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Asset for DynamicImage {
|
2019-08-06 06:31:48 +00:00
|
|
|
const ENDINGS: &'static [&'static str] = &["png", "jpg"];
|
|
|
|
fn parse(mut buf_reader: BufReader<File>) -> Result<Self, Error> {
|
2019-05-13 23:28:17 +00:00
|
|
|
let mut buf = Vec::new();
|
2019-06-23 20:42:17 +00:00
|
|
|
buf_reader.read_to_end(&mut buf)?;
|
2019-05-13 23:28:17 +00:00
|
|
|
Ok(image::load_from_memory(&buf).unwrap())
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Asset for DotVoxData {
|
2019-08-06 06:31:48 +00:00
|
|
|
const ENDINGS: &'static [&'static str] = &["vox"];
|
|
|
|
fn parse(mut buf_reader: BufReader<File>) -> Result<Self, Error> {
|
2019-05-13 23:28:17 +00:00
|
|
|
let mut buf = Vec::new();
|
2019-06-23 20:42:17 +00:00
|
|
|
buf_reader.read_to_end(&mut buf)?;
|
2019-05-13 23:28:17 +00:00
|
|
|
Ok(dot_vox::load_bytes(&buf).unwrap())
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-06 06:31:48 +00:00
|
|
|
// Read a JSON file
|
2019-06-03 22:11:27 +00:00
|
|
|
impl Asset for Value {
|
2019-08-06 06:31:48 +00:00
|
|
|
const ENDINGS: &'static [&'static str] = &["json"];
|
|
|
|
fn parse(buf_reader: BufReader<File>) -> Result<Self, Error> {
|
2019-06-23 20:42:17 +00:00
|
|
|
Ok(serde_json::from_reader(buf_reader).unwrap())
|
2019-06-03 22:11:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-03 05:42:33 +00:00
|
|
|
impl Asset for String {
|
|
|
|
const ENDINGS: &'static [&'static str] = &["glsl"];
|
|
|
|
fn parse(mut buf_reader: BufReader<File>) -> Result<Self, Error> {
|
|
|
|
let mut string = String::new();
|
|
|
|
buf_reader.read_to_string(&mut string)?;
|
|
|
|
Ok(string)
|
2019-07-02 12:42:39 +00:00
|
|
|
}
|
2019-08-03 05:42:33 +00:00
|
|
|
}
|
2019-07-02 12:42:39 +00:00
|
|
|
|
2019-08-03 05:42:33 +00:00
|
|
|
lazy_static! {
|
2019-08-23 10:11:37 +00:00
|
|
|
/// Lazy static to find and cache where the asset directory is.
|
2019-08-03 05:42:33 +00:00
|
|
|
static ref ASSETS_PATH: PathBuf = {
|
|
|
|
let mut paths = Vec::new();
|
2019-07-02 12:42:39 +00:00
|
|
|
|
2019-08-03 05:42:33 +00:00
|
|
|
// VELOREN_ASSETS environment variable
|
|
|
|
if let Ok(var) = std::env::var("VELOREN_ASSETS") {
|
|
|
|
paths.push(var.to_owned().into());
|
|
|
|
}
|
2019-07-02 12:42:39 +00:00
|
|
|
|
2019-08-03 05:42:33 +00:00
|
|
|
// Executable path
|
|
|
|
if let Ok(mut path) = std::env::current_exe() {
|
|
|
|
path.pop();
|
|
|
|
paths.push(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Working path
|
|
|
|
if let Ok(path) = std::env::current_dir() {
|
|
|
|
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-07-02 12:42:39 +00:00
|
|
|
|
2019-08-03 05:42:33 +00:00
|
|
|
panic!(
|
|
|
|
"Asset directory not found. In attempting to find it, we searched:\n{})",
|
|
|
|
paths.iter().fold(String::new(), |mut a, path| {
|
|
|
|
a += &path.to_string_lossy();
|
|
|
|
a += "\n";
|
|
|
|
a
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
};
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
|
2019-08-06 06:31:48 +00:00
|
|
|
/// Converts a specifier like "core.backgrounds.city" to ".../veloren/assets/core/backgrounds/city".
|
|
|
|
fn unpack_specifier(specifier: &str) -> PathBuf {
|
2019-08-03 05:42:33 +00:00
|
|
|
let mut path = ASSETS_PATH.clone();
|
2019-08-06 06:31:48 +00:00
|
|
|
path.push(specifier.replace(".", "/"));
|
|
|
|
path
|
|
|
|
}
|
2019-07-01 13:35:06 +00:00
|
|
|
|
2019-08-06 06:31:48 +00:00
|
|
|
/// Loads a file based on the specifier and possible extensions
|
|
|
|
pub fn load_file(specifier: &str, endings: &[&str]) -> Result<BufReader<File>, Error> {
|
|
|
|
let mut path = unpack_specifier(specifier);
|
|
|
|
for ending in endings {
|
|
|
|
let mut path = path.clone();
|
|
|
|
path.set_extension(ending);
|
2019-07-01 13:35:06 +00:00
|
|
|
|
2019-08-06 06:31:48 +00:00
|
|
|
debug!("Trying to access \"{:?}\"", path);
|
|
|
|
if let Ok(file) = File::open(path) {
|
|
|
|
return Ok(BufReader::new(file));
|
|
|
|
}
|
2019-07-01 13:35:06 +00:00
|
|
|
}
|
|
|
|
|
2019-08-06 06:31:48 +00:00
|
|
|
Err(Error::NotFound(path.to_string_lossy().into_owned()))
|
2019-07-01 13:35:06 +00:00
|
|
|
}
|
|
|
|
|
2019-08-06 06:31:48 +00:00
|
|
|
/// Read directory from `veloren/assets/*`
|
|
|
|
pub fn read_dir(specifier: &str) -> Result<ReadDir, Error> {
|
|
|
|
let dir_name = unpack_specifier(specifier);
|
|
|
|
if dir_name.exists() {
|
|
|
|
Ok(fs::read_dir(dir_name).expect("`read_dir` failed."))
|
|
|
|
} else {
|
|
|
|
Err(Error::NotFound(dir_name.to_string_lossy().into_owned()))
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
}
|