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
|
|
|
|
2020-08-28 01:02:17 +00:00
|
|
|
use core::{any::Any, fmt, marker::PhantomData};
|
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;
|
2020-08-28 01:02:17 +00:00
|
|
|
use serde::Deserialize;
|
2019-06-03 22:11:27 +00:00
|
|
|
use serde_json::Value;
|
2019-04-26 03:30:46 +00:00
|
|
|
use std::{
|
2019-09-04 23:03:49 +00:00
|
|
|
fs::{self, File, ReadDir},
|
2019-06-23 20:42:17 +00:00
|
|
|
io::{BufReader, Read},
|
2020-05-11 22:02:21 +00:00
|
|
|
path::PathBuf,
|
2019-04-28 02:12:30 +00:00
|
|
|
sync::{Arc, RwLock},
|
2019-04-26 03:30:46 +00:00
|
|
|
};
|
2020-06-22 13:34:35 +00:00
|
|
|
use tracing::{error, trace};
|
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 {
|
2020-03-29 01:09:19 +00:00
|
|
|
/// Parsing error occurred.
|
|
|
|
ParseError(Arc<dyn std::fmt::Debug>),
|
2020-02-01 20:39:39 +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
|
|
|
}
|
|
|
|
|
2020-03-29 01:09:19 +00:00
|
|
|
impl Error {
|
|
|
|
pub fn parse_error<E: std::fmt::Debug + 'static>(err: E) -> Self {
|
|
|
|
Self::ParseError(Arc::new(err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-29 03:38:45 +00:00
|
|
|
impl fmt::Display for Error {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
2020-03-29 01:09:19 +00:00
|
|
|
Error::ParseError(err) => write!(f, "{:?}", err),
|
2020-01-29 03:38:45 +00:00
|
|
|
Error::InvalidType => write!(
|
|
|
|
f,
|
|
|
|
"an asset of a different type has already been loaded with this specifier."
|
|
|
|
),
|
|
|
|
Error::NotFound(s) => write!(f, "{}", s),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-26 03:30:46 +00:00
|
|
|
impl From<Arc<dyn Any + 'static + Sync + Send>> for Error {
|
2020-02-01 20:39:39 +00:00
|
|
|
fn from(_: Arc<dyn Any + 'static + Sync + Send>) -> Self { Error::InvalidType }
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<std::io::Error> for Error {
|
2020-02-01 20:39:39 +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.
|
2020-05-11 22:02:21 +00:00
|
|
|
static ref ASSETS: RwLock<HashMap<String, Arc<dyn Any + 'static + Sync + Send>>> =
|
2019-04-26 03:30:46 +00:00
|
|
|
RwLock::new(HashMap::new());
|
|
|
|
}
|
|
|
|
|
2020-08-28 01:02:17 +00:00
|
|
|
fn reload<A: Asset>(specifier: &str) -> Result<(), Error>
|
|
|
|
where
|
|
|
|
A::Output: Send + Sync + 'static,
|
|
|
|
{
|
2020-09-17 23:02:14 +00:00
|
|
|
let asset = Arc::new(A::parse(load_file(specifier, A::ENDINGS)?, specifier)?);
|
2020-08-28 01:02:17 +00:00
|
|
|
let mut assets_write = ASSETS.write().unwrap();
|
|
|
|
match assets_write.get_mut(specifier) {
|
|
|
|
Some(a) => *a = asset,
|
2019-07-01 20:42:43 +00:00
|
|
|
None => {
|
2020-08-28 01:02:17 +00:00
|
|
|
assets_write.insert(specifier.to_owned(), asset);
|
2020-02-01 20:39:39 +00:00
|
|
|
},
|
2019-07-01 20:42:43 +00:00
|
|
|
}
|
2020-08-28 01:02:17 +00:00
|
|
|
|
|
|
|
Ok(())
|
2019-05-24 12:25:31 +00:00
|
|
|
}
|
|
|
|
|
2020-08-28 01:02:17 +00:00
|
|
|
/// The Asset trait, which is implemented by all structures that have their data
|
|
|
|
/// stored in the filesystem.
|
|
|
|
pub trait Asset: Sized {
|
|
|
|
type Output = Self;
|
|
|
|
|
|
|
|
const ENDINGS: &'static [&'static str];
|
|
|
|
/// Parse the input file and return the correct Asset.
|
2020-09-17 23:02:14 +00:00
|
|
|
fn parse(buf_reader: BufReader<File>, specifier: &str) -> Result<Self::Output, Error>;
|
2020-08-28 01:02:17 +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. Example usage:
|
|
|
|
/// ```no_run
|
|
|
|
/// use vek::*;
|
|
|
|
/// use veloren_common::{assets::Asset, terrain::Structure};
|
|
|
|
///
|
|
|
|
/// let my_tree_structure = Structure::load_map("world.tree.oak_green.1", |s: Structure| {
|
|
|
|
/// s.with_center(Vec3::new(15, 18, 14))
|
|
|
|
/// })
|
|
|
|
/// .unwrap();
|
|
|
|
/// ```
|
|
|
|
fn load_map<F: FnOnce(Self::Output) -> Self::Output>(
|
|
|
|
specifier: &str,
|
|
|
|
f: F,
|
|
|
|
) -> Result<Arc<Self::Output>, Error>
|
|
|
|
where
|
|
|
|
Self::Output: Send + Sync + 'static,
|
|
|
|
{
|
2020-09-17 23:02:14 +00:00
|
|
|
let assets_read = ASSETS.read().unwrap();
|
|
|
|
match assets_read.get(specifier) {
|
2020-08-28 01:02:17 +00:00
|
|
|
Some(asset) => Ok(Arc::clone(asset).downcast()?),
|
|
|
|
None => {
|
2020-09-17 23:02:14 +00:00
|
|
|
drop(assets_read); // Drop the asset hashmap to permit recursive loading
|
|
|
|
let asset = Arc::new(f(Self::parse(
|
|
|
|
load_file(specifier, Self::ENDINGS)?,
|
|
|
|
specifier,
|
|
|
|
)?));
|
2020-08-28 01:02:17 +00:00
|
|
|
let clone = Arc::clone(&asset);
|
|
|
|
ASSETS.write().unwrap().insert(specifier.to_owned(), clone);
|
|
|
|
Ok(asset)
|
|
|
|
},
|
|
|
|
}
|
2019-10-24 17:43:55 +00:00
|
|
|
}
|
|
|
|
|
2020-08-28 01:02:17 +00:00
|
|
|
fn load_glob(specifier: &str) -> Result<Arc<Vec<Arc<Self::Output>>>, Error>
|
|
|
|
where
|
|
|
|
Self::Output: Send + Sync + 'static,
|
|
|
|
{
|
|
|
|
if let Some(assets) = ASSETS.read().unwrap().get(specifier) {
|
|
|
|
return Ok(Arc::clone(assets).downcast()?);
|
|
|
|
}
|
|
|
|
|
2020-09-17 23:02:14 +00:00
|
|
|
match get_glob_matches(specifier) {
|
2020-08-28 01:02:17 +00:00
|
|
|
Ok(glob_matches) => {
|
|
|
|
let assets = Arc::new(
|
|
|
|
glob_matches
|
|
|
|
.into_iter()
|
|
|
|
.filter_map(|name| {
|
2020-09-17 23:02:14 +00:00
|
|
|
Self::load(&name)
|
2020-08-28 01:02:17 +00:00
|
|
|
.map_err(|e| {
|
|
|
|
error!(
|
|
|
|
?e,
|
|
|
|
"Failed to load \"{}\" as part of glob \"{}\"",
|
|
|
|
name,
|
|
|
|
specifier
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.ok()
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
);
|
|
|
|
let clone = Arc::clone(&assets);
|
|
|
|
|
|
|
|
let mut assets_write = ASSETS.write().unwrap();
|
|
|
|
assets_write.insert(specifier.to_owned(), clone);
|
|
|
|
Ok(assets)
|
|
|
|
},
|
|
|
|
Err(error) => Err(error),
|
|
|
|
}
|
2019-10-24 17:43:55 +00:00
|
|
|
}
|
2019-04-26 03:30:46 +00:00
|
|
|
|
2020-09-17 23:02:14 +00:00
|
|
|
fn load_glob_cloned(specifier: &str) -> Result<Vec<(Self::Output, String)>, Error>
|
|
|
|
where
|
|
|
|
Self::Output: Clone + Send + Sync + 'static,
|
|
|
|
{
|
|
|
|
match get_glob_matches(specifier) {
|
|
|
|
Ok(glob_matches) => Ok(glob_matches
|
|
|
|
.into_iter()
|
|
|
|
.map(|name| {
|
|
|
|
let full_specifier = &specifier.replace("*", &name);
|
|
|
|
(
|
|
|
|
Self::load_expect_cloned(full_specifier),
|
|
|
|
full_specifier.to_string(),
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>()),
|
|
|
|
Err(error) => Err(error),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-28 01:02:17 +00:00
|
|
|
/// Function used to load assets from the filesystem or the cache.
|
|
|
|
/// Example usage:
|
|
|
|
/// ```no_run
|
|
|
|
/// use image::DynamicImage;
|
|
|
|
/// use veloren_common::assets::Asset;
|
|
|
|
///
|
|
|
|
/// let my_image = DynamicImage::load("core.ui.backgrounds.city").unwrap();
|
|
|
|
/// ```
|
|
|
|
fn load(specifier: &str) -> Result<Arc<Self::Output>, Error>
|
|
|
|
where
|
|
|
|
Self::Output: Send + Sync + 'static,
|
|
|
|
{
|
|
|
|
Self::load_map(specifier, |x| x)
|
|
|
|
}
|
2019-10-22 18:18:40 +00:00
|
|
|
|
2020-08-28 01:02:17 +00:00
|
|
|
/// Function used to load assets from the filesystem or the cache and return
|
|
|
|
/// a clone.
|
|
|
|
fn load_cloned(specifier: &str) -> Result<Self::Output, Error>
|
|
|
|
where
|
|
|
|
Self::Output: Clone + Send + Sync + 'static,
|
|
|
|
{
|
|
|
|
Self::load(specifier).map(|asset| (*asset).clone())
|
|
|
|
}
|
2019-04-29 00:23:14 +00:00
|
|
|
|
2020-08-28 01:02:17 +00:00
|
|
|
/// Function used to load essential assets from the filesystem or the cache.
|
|
|
|
/// It will panic if the asset is not found. Example usage:
|
|
|
|
/// ```no_run
|
|
|
|
/// use image::DynamicImage;
|
|
|
|
/// use veloren_common::assets::Asset;
|
|
|
|
///
|
|
|
|
/// let my_image = DynamicImage::load_expect("core.ui.backgrounds.city");
|
|
|
|
/// ```
|
|
|
|
fn load_expect(specifier: &str) -> Arc<Self::Output>
|
|
|
|
where
|
|
|
|
Self::Output: Send + Sync + 'static,
|
|
|
|
{
|
|
|
|
Self::load(specifier).unwrap_or_else(|err| {
|
|
|
|
panic!(
|
|
|
|
"Failed loading essential asset: {} (error={:?})",
|
|
|
|
specifier, err
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
2019-10-22 18:18:40 +00:00
|
|
|
|
2020-08-28 01:02:17 +00:00
|
|
|
/// Function used to load essential assets from the filesystem or the cache
|
|
|
|
/// and return a clone. It will panic if the asset is not found.
|
|
|
|
fn load_expect_cloned(specifier: &str) -> Self::Output
|
|
|
|
where
|
|
|
|
Self::Output: Clone + Send + Sync + 'static,
|
|
|
|
{
|
|
|
|
Self::load_expect(specifier).as_ref().clone()
|
2019-08-03 05:42:33 +00:00
|
|
|
}
|
|
|
|
|
2020-08-28 01:02:17 +00:00
|
|
|
/// Load an asset while registering it to be watched and reloaded when it
|
|
|
|
/// changes
|
|
|
|
fn load_watched(
|
|
|
|
specifier: &str,
|
|
|
|
indicator: &mut watch::ReloadIndicator,
|
|
|
|
) -> Result<Arc<Self::Output>, Error>
|
|
|
|
where
|
|
|
|
Self::Output: Send + Sync + 'static,
|
|
|
|
{
|
|
|
|
let asset = Self::load(specifier)?;
|
|
|
|
|
|
|
|
// Determine path to watch
|
|
|
|
let path = unpack_specifier(specifier);
|
|
|
|
let mut path_with_extension = None;
|
|
|
|
for ending in Self::ENDINGS {
|
|
|
|
let mut path = path.clone();
|
|
|
|
path.set_extension(ending);
|
|
|
|
|
|
|
|
if path.exists() {
|
|
|
|
path_with_extension = Some(path);
|
|
|
|
break;
|
2019-08-12 01:53:48 +00:00
|
|
|
}
|
2020-08-28 01:02:17 +00:00
|
|
|
}
|
2019-08-03 05:42:33 +00:00
|
|
|
|
2020-08-28 01:02:17 +00:00
|
|
|
let owned_specifier = specifier.to_string();
|
|
|
|
indicator.add(
|
|
|
|
path_with_extension
|
|
|
|
.ok_or_else(|| Error::NotFound(path.to_string_lossy().into_owned()))?,
|
|
|
|
move || {
|
|
|
|
if let Err(e) = reload::<Self>(&owned_specifier) {
|
|
|
|
error!(?e, ?owned_specifier, "Error reloading owned_specifier");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(asset)
|
2019-08-03 05:42:33 +00:00
|
|
|
}
|
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"];
|
2020-02-01 20:39:39 +00:00
|
|
|
|
2020-09-17 23:02:14 +00:00
|
|
|
fn parse(mut buf_reader: BufReader<File>, _specifier: &str) -> 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)?;
|
2020-03-29 01:09:19 +00:00
|
|
|
image::load_from_memory(&buf).map_err(Error::parse_error)
|
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"];
|
2020-02-01 20:39:39 +00:00
|
|
|
|
2020-09-17 23:02:14 +00:00
|
|
|
fn parse(mut buf_reader: BufReader<File>, _specifier: &str) -> 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)?;
|
2020-03-29 01:09:19 +00:00
|
|
|
dot_vox::load_bytes(&buf).map_err(Error::parse_error)
|
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"];
|
2020-02-01 20:39:39 +00:00
|
|
|
|
2020-09-17 23:02:14 +00:00
|
|
|
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, Error> {
|
2020-03-29 01:09:19 +00:00
|
|
|
serde_json::from_reader(buf_reader).map_err(Error::parse_error)
|
2019-06-03 22:11:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-17 23:02:14 +00:00
|
|
|
/// Load from an arbitrary RON file.
|
2020-08-28 01:02:17 +00:00
|
|
|
pub struct Ron<T>(pub PhantomData<T>);
|
2020-02-01 20:39:39 +00:00
|
|
|
|
2020-08-28 01:02:17 +00:00
|
|
|
impl<T: Send + Sync + for<'de> Deserialize<'de>> Asset for Ron<T> {
|
|
|
|
type Output = T;
|
|
|
|
|
|
|
|
const ENDINGS: &'static [&'static str] = &["ron"];
|
|
|
|
|
2020-09-17 23:02:14 +00:00
|
|
|
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<T, Error> {
|
2020-08-28 01:02:17 +00:00
|
|
|
ron::de::from_reader(buf_reader).map_err(Error::parse_error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Load from a specific asset path.
|
|
|
|
pub struct AssetWith<T: Asset, const ASSET_PATH: &'static str> {
|
|
|
|
pub asset: Arc<T::Output>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: Asset, const ASSET_PATH: &'static str> Clone for AssetWith<T, ASSET_PATH> {
|
|
|
|
fn clone(&self) -> Self {
|
|
|
|
Self {
|
|
|
|
asset: Arc::clone(&self.asset),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: Asset, const ASSET_PATH: &'static str> AssetWith<T, ASSET_PATH>
|
|
|
|
where
|
|
|
|
T::Output: Send + Sync + 'static,
|
|
|
|
{
|
|
|
|
#[inline]
|
|
|
|
pub fn load_watched(indicator: &mut watch::ReloadIndicator) -> Result<Self, Error> {
|
|
|
|
T::load_watched(ASSET_PATH, indicator).map(|asset| Self { asset })
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn reload(&mut self) -> Result<(), Error> {
|
|
|
|
self.asset = T::load(ASSET_PATH)?;
|
|
|
|
Ok(())
|
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.
|
2020-08-31 08:24:13 +00:00
|
|
|
/// Cases we need to account for:
|
|
|
|
/// 1. Running through airshipper (`assets` next to binary)
|
|
|
|
/// 2. Install with package manager and run (assets probably in `/usr/share/veloren/assets` while binary in `/usr/bin/`)
|
|
|
|
/// 3. Download & hopefully extract zip (`assets` next to binary)
|
|
|
|
/// 4. Running through cargo (`assets` in workspace root but not always in cwd incase you `cd voxygen && cargo r`)
|
|
|
|
/// 5. Running executable in the target dir (`assets` in workspace)
|
2020-05-11 22:02:21 +00:00
|
|
|
pub static ref ASSETS_PATH: PathBuf = {
|
2019-08-03 05:42:33 +00:00
|
|
|
let mut paths = Vec::new();
|
2019-07-02 12:42:39 +00:00
|
|
|
|
2020-08-19 06:52:14 +00:00
|
|
|
// Note: Ordering matters here!
|
|
|
|
|
|
|
|
// 1. VELOREN_ASSETS environment variable
|
2019-08-03 05:42:33 +00:00
|
|
|
if let Ok(var) = std::env::var("VELOREN_ASSETS") {
|
2020-06-08 18:37:41 +00:00
|
|
|
paths.push(var.into());
|
2019-08-03 05:42:33 +00:00
|
|
|
}
|
2019-07-02 12:42:39 +00:00
|
|
|
|
2020-08-31 08:24:13 +00:00
|
|
|
// 2. Executable path
|
|
|
|
if let Ok(mut path) = std::env::current_exe() {
|
|
|
|
path.pop();
|
|
|
|
paths.push(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3. Working path
|
|
|
|
if let Ok(path) = std::env::current_dir() {
|
|
|
|
paths.push(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 4. Cargo Workspace (e.g. local development)
|
|
|
|
// https://github.com/rust-lang/cargo/issues/3946#issuecomment-359619839
|
|
|
|
if let Ok(Ok(path)) = std::env::var("CARGO_MANIFEST_DIR").map(|s| s.parse::<PathBuf>()) {
|
|
|
|
paths.push(path.parent().unwrap().to_path_buf());
|
|
|
|
paths.push(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 5. System paths
|
2020-05-18 15:53:50 +00:00
|
|
|
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
|
2020-04-12 16:44:21 +00:00
|
|
|
{
|
|
|
|
if let Ok(result) = std::env::var("XDG_DATA_HOME") {
|
2020-08-31 08:24:13 +00:00
|
|
|
paths.push(format!("{}/veloren/", result).into());
|
2020-04-12 16:44:21 +00:00
|
|
|
} else if let Ok(result) = std::env::var("HOME") {
|
2020-08-31 08:24:13 +00:00
|
|
|
paths.push(format!("{}/.local/share/veloren/", result).into());
|
2020-04-12 16:44:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if let Ok(result) = std::env::var("XDG_DATA_DIRS") {
|
2020-08-31 08:24:13 +00:00
|
|
|
result.split(':').for_each(|x| paths.push(format!("{}/veloren/", x).into()));
|
2020-04-12 16:44:21 +00:00
|
|
|
} else {
|
|
|
|
// Fallback
|
|
|
|
let fallback_paths = vec!["/usr/local/share", "/usr/share"];
|
|
|
|
for fallback_path in fallback_paths {
|
2020-08-31 08:24:13 +00:00
|
|
|
paths.push(format!("{}/veloren/", fallback_path).into());
|
2020-04-12 16:44:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-08-03 05:42:33 +00:00
|
|
|
|
2020-08-18 10:54:19 +00:00
|
|
|
tracing::trace!("Possible asset locations paths={:?}", paths);
|
|
|
|
|
2020-08-31 14:43:16 +00:00
|
|
|
for mut path in paths.clone() {
|
|
|
|
if !path.ends_with("assets") {
|
|
|
|
path = path.join("assets");
|
|
|
|
}
|
|
|
|
|
|
|
|
if path.is_dir() {
|
|
|
|
tracing::info!("Assets found path={}", path.display());
|
|
|
|
return path;
|
2019-08-03 05:42:33 +00:00
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
2020-02-01 20:39:39 +00:00
|
|
|
/// Converts a specifier like "core.backgrounds.city" to
|
|
|
|
/// ".../veloren/assets/core/backgrounds/city".
|
2019-08-06 06:31:48 +00:00
|
|
|
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> {
|
2019-09-04 23:03:49 +00:00
|
|
|
let path = unpack_specifier(specifier);
|
2019-08-06 06:31:48 +00:00
|
|
|
for ending in endings {
|
|
|
|
let mut path = path.clone();
|
|
|
|
path.set_extension(ending);
|
2019-07-01 13:35:06 +00:00
|
|
|
|
2020-06-21 21:47:49 +00:00
|
|
|
trace!(?path, "Trying to access");
|
2019-08-06 06:31:48 +00:00
|
|
|
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-10-24 17:43:55 +00:00
|
|
|
/// Loads a file based on the specifier and possible extensions
|
|
|
|
pub fn load_file_glob(specifier: &str, endings: &[&str]) -> Result<BufReader<File>, Error> {
|
|
|
|
let path = unpack_specifier(specifier);
|
|
|
|
for ending in endings {
|
|
|
|
let mut path = path.clone();
|
|
|
|
path.set_extension(ending);
|
|
|
|
|
2020-06-21 21:47:49 +00:00
|
|
|
trace!(?path, "Trying to access");
|
2019-10-24 17:43:55 +00:00
|
|
|
if let Ok(file) = File::open(path) {
|
|
|
|
return Ok(BufReader::new(file));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(Error::NotFound(path.to_string_lossy().into_owned()))
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
2020-09-17 23:02:14 +00:00
|
|
|
|
|
|
|
// Finds all files matching the provided glob specifier - includes files from
|
|
|
|
// subdirectories
|
|
|
|
fn get_glob_matches(specifier: &str) -> Result<Vec<String>, Error> {
|
|
|
|
let specifier = specifier.trim_end_matches(".*");
|
|
|
|
read_dir(specifier).map(|dir| {
|
|
|
|
dir.filter_map(|direntry| {
|
|
|
|
direntry.ok().and_then(|dir_entry| {
|
|
|
|
if dir_entry.path().is_dir() {
|
|
|
|
let sub_dir_glob = format!(
|
|
|
|
"{}.{}.*",
|
|
|
|
specifier.to_string(),
|
|
|
|
dir_entry.file_name().to_string_lossy()
|
|
|
|
);
|
|
|
|
Some(get_glob_matches(&sub_dir_glob).ok()?)
|
|
|
|
} else {
|
|
|
|
Some(vec![format!(
|
|
|
|
"{}.{}",
|
|
|
|
specifier,
|
|
|
|
dir_entry
|
|
|
|
.file_name()
|
|
|
|
.to_string_lossy()
|
|
|
|
.rsplitn(2, '.')
|
|
|
|
.last()
|
|
|
|
.map(|s| s.to_owned())
|
|
|
|
.unwrap()
|
|
|
|
)])
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.flat_map(|x| x)
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
})
|
|
|
|
}
|