2019-08-06 06:31:48 +00:00
|
|
|
//! Load assets (images or voxel data) from files
|
|
|
|
|
2019-04-26 03:30:46 +00:00
|
|
|
use dot_vox::DotVoxData;
|
|
|
|
use image::DynamicImage;
|
|
|
|
use lazy_static::lazy_static;
|
2020-12-13 01:09:57 +00:00
|
|
|
use std::{
|
|
|
|
borrow::Cow,
|
|
|
|
fs, io,
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
sync::Arc,
|
|
|
|
};
|
2019-04-26 03:30:46 +00:00
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
pub use assets_manager::{
|
2020-12-16 23:42:47 +00:00
|
|
|
asset::Ron,
|
2020-12-13 01:09:57 +00:00
|
|
|
loader::{
|
|
|
|
self, BincodeLoader, BytesLoader, JsonLoader, LoadFrom, Loader, RonLoader, StringLoader,
|
|
|
|
},
|
|
|
|
source, Asset, AssetCache, BoxedError, Compound, Error,
|
2020-12-12 22:14:24 +00:00
|
|
|
};
|
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-12-12 22:14:24 +00:00
|
|
|
static ref ASSETS: AssetCache = AssetCache::new(&*ASSETS_PATH).unwrap();
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
|
2020-12-13 01:09:57 +00:00
|
|
|
pub fn start_hot_reloading() { ASSETS.enhance_hot_reloading(); }
|
2019-05-24 12:25:31 +00:00
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
pub type AssetHandle<T> = assets_manager::Handle<'static, T>;
|
|
|
|
pub type AssetDir<T> = assets_manager::DirReader<'static, T, source::FileSystem>;
|
|
|
|
|
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.
|
2020-12-12 22:14:24 +00:00
|
|
|
pub trait AssetExt: Sized + Send + Sync + 'static {
|
2020-08-28 01:02:17 +00:00
|
|
|
/// Function used to load assets from the filesystem or the cache.
|
|
|
|
/// Example usage:
|
|
|
|
/// ```no_run
|
2020-12-12 22:14:24 +00:00
|
|
|
/// use veloren_common::assets::{self, AssetExt};
|
2020-08-28 01:02:17 +00:00
|
|
|
///
|
2020-12-12 22:14:24 +00:00
|
|
|
/// let my_image = assets::Image::load("core.ui.backgrounds.city").unwrap();
|
2020-08-28 01:02:17 +00:00
|
|
|
/// ```
|
2020-12-12 22:14:24 +00:00
|
|
|
fn load(specifier: &str) -> Result<AssetHandle<Self>, Error>;
|
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.
|
2020-12-12 22:14:24 +00:00
|
|
|
fn load_cloned(specifier: &str) -> Result<Self, Error>
|
2020-08-28 01:02:17 +00:00
|
|
|
where
|
2020-12-12 22:14:24 +00:00
|
|
|
Self: Clone,
|
2020-08-28 01:02:17 +00:00
|
|
|
{
|
2020-12-12 22:14:24 +00:00
|
|
|
Self::load(specifier).map(AssetHandle::cloned)
|
2020-08-28 01:02:17 +00:00
|
|
|
}
|
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
|
2020-12-12 22:14:24 +00:00
|
|
|
/// use veloren_common::assets::{self, AssetExt};
|
2020-08-28 01:02:17 +00:00
|
|
|
///
|
2020-12-12 22:14:24 +00:00
|
|
|
/// let my_image = assets::Image::load_expect("core.ui.backgrounds.city");
|
2020-08-28 01:02:17 +00:00
|
|
|
/// ```
|
2020-12-12 22:14:24 +00:00
|
|
|
#[track_caller]
|
|
|
|
fn load_expect(specifier: &str) -> AssetHandle<Self> {
|
2020-08-28 01:02:17 +00:00
|
|
|
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.
|
2020-12-12 22:14:24 +00:00
|
|
|
#[track_caller]
|
|
|
|
fn load_expect_cloned(specifier: &str) -> Self
|
2020-08-28 01:02:17 +00:00
|
|
|
where
|
2020-12-12 22:14:24 +00:00
|
|
|
Self: Clone,
|
2020-08-28 01:02:17 +00:00
|
|
|
{
|
2020-12-12 22:14:24 +00:00
|
|
|
Self::load_expect(specifier).cloned()
|
2019-08-03 05:42:33 +00:00
|
|
|
}
|
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
fn load_owned(specifier: &str) -> Result<Self, Error>;
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
pub fn load_dir<T: Asset>(specifier: &str) -> Result<AssetDir<T>, Error> {
|
|
|
|
Ok(ASSETS.load_dir(specifier)?)
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
impl<T: Compound> AssetExt for T {
|
2020-12-13 01:09:57 +00:00
|
|
|
fn load(specifier: &str) -> Result<AssetHandle<Self>, Error> { ASSETS.load(specifier) }
|
2020-02-01 20:39:39 +00:00
|
|
|
|
2020-12-13 01:09:57 +00:00
|
|
|
fn load_owned(specifier: &str) -> Result<Self, Error> { ASSETS.load_owned(specifier) }
|
2019-04-26 03:30:46 +00:00
|
|
|
}
|
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
pub struct Image(pub Arc<DynamicImage>);
|
2020-02-01 20:39:39 +00:00
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
impl Image {
|
2020-12-13 14:57:10 +00:00
|
|
|
pub fn to_image(&self) -> Arc<DynamicImage> { Arc::clone(&self.0) }
|
2019-06-03 22:11:27 +00:00
|
|
|
}
|
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
pub struct ImageLoader;
|
|
|
|
impl Loader<Image> for ImageLoader {
|
|
|
|
fn load(content: Cow<[u8]>, _: &str) -> Result<Image, BoxedError> {
|
|
|
|
let image = image::load_from_memory(&content)?;
|
|
|
|
Ok(Image(Arc::new(image)))
|
2020-08-28 01:02:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
impl Asset for Image {
|
|
|
|
type Loader = ImageLoader;
|
2020-12-13 01:09:57 +00:00
|
|
|
|
|
|
|
const EXTENSIONS: &'static [&'static str] = &["png", "jpg"];
|
2020-08-28 01:02:17 +00:00
|
|
|
}
|
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
pub struct DotVoxAsset(pub DotVoxData);
|
|
|
|
|
|
|
|
pub struct DotVoxLoader;
|
|
|
|
impl Loader<DotVoxAsset> for DotVoxLoader {
|
|
|
|
fn load(content: std::borrow::Cow<[u8]>, _: &str) -> Result<DotVoxAsset, BoxedError> {
|
|
|
|
let data = dot_vox::load_bytes(&content).map_err(|err| err.to_owned())?;
|
|
|
|
Ok(DotVoxAsset(data))
|
2020-08-28 01:02:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
impl Asset for DotVoxAsset {
|
|
|
|
type Loader = DotVoxLoader;
|
2020-12-13 01:09:57 +00:00
|
|
|
|
|
|
|
const EXTENSION: &'static str = "vox";
|
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
|
|
|
}
|
|
|
|
|
2021-01-03 16:48:23 +00:00
|
|
|
/// Returns the actual path of the specifier with the extension.
|
|
|
|
///
|
|
|
|
/// For directories, give `""` as extension.
|
|
|
|
pub fn path_of(specifier: &str, ext: &str) -> PathBuf { ASSETS.source().path_of(specifier, ext) }
|
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
fn get_dir_files(files: &mut Vec<String>, path: &Path, specifier: &str) -> io::Result<()> {
|
|
|
|
for entry in fs::read_dir(path)? {
|
|
|
|
if let Ok(entry) = entry {
|
|
|
|
let path = entry.path();
|
|
|
|
let maybe_stem = path.file_stem().and_then(|stem| stem.to_str());
|
2019-07-01 13:35:06 +00:00
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
if let Some(stem) = maybe_stem {
|
|
|
|
let specifier = format!("{}.{}", specifier, stem);
|
2019-07-01 13:35:06 +00:00
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
if path.is_dir() {
|
|
|
|
get_dir_files(files, &path, &specifier)?;
|
|
|
|
} else {
|
|
|
|
files.push(specifier);
|
|
|
|
}
|
|
|
|
}
|
2019-08-06 06:31:48 +00:00
|
|
|
}
|
2019-07-01 13:35:06 +00:00
|
|
|
}
|
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
Ok(())
|
2019-07-01 13:35:06 +00:00
|
|
|
}
|
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
pub struct Directory(Vec<String>);
|
2019-10-24 17:43:55 +00:00
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
impl Directory {
|
2020-12-13 01:09:57 +00:00
|
|
|
pub fn iter(&self) -> impl Iterator<Item = &String> { self.0.iter() }
|
2019-10-24 17:43:55 +00:00
|
|
|
}
|
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
impl Compound for Directory {
|
|
|
|
fn load<S: source::Source>(_: &AssetCache<S>, specifier: &str) -> Result<Self, Error> {
|
2020-12-18 17:11:48 +00:00
|
|
|
let specifier = specifier.strip_suffix(".*").unwrap_or(specifier);
|
2020-12-12 22:14:24 +00:00
|
|
|
let root = ASSETS.source().path_of(specifier, "");
|
|
|
|
let mut files = Vec::new();
|
2020-09-17 23:02:14 +00:00
|
|
|
|
2020-12-12 22:14:24 +00:00
|
|
|
get_dir_files(&mut files, &root, specifier)?;
|
|
|
|
|
|
|
|
Ok(Directory(files))
|
|
|
|
}
|
2020-12-13 01:09:57 +00:00
|
|
|
}
|
2021-03-03 21:45:36 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
#[test]
|
|
|
|
fn test_assets_items() {
|
|
|
|
// TODO: Figure out how to get file name in error so only a single glob is
|
|
|
|
// needed
|
|
|
|
|
|
|
|
// Separated out into subsections so that error more descriptive
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.armor.*")
|
|
|
|
.expect("Failed to iterate over armors.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.boss_drops.*")
|
|
|
|
.expect("Failed to iterate over boss drops.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.consumable.*")
|
|
|
|
.expect("Failed to iterate over consumables.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.crafting_ing.*")
|
|
|
|
.expect("Failed to iterate over crafting ingredients.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.crafting_tools.*")
|
|
|
|
.expect("Failed to iterate over crafting tools.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.debug.*")
|
|
|
|
.expect("Failed to iterate over debug items.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.flowers.*")
|
|
|
|
.expect("Failed to iterate over flower items.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.food.*")
|
|
|
|
.expect("Failed to iterate over food items.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.glider.*")
|
|
|
|
.expect("Failed to iterate over gliders.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.grasses.*")
|
|
|
|
.expect("Failed to iterate over grasses.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.lantern.*")
|
|
|
|
.expect("Failed to iterate over lanterns.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.npc_armor.*")
|
|
|
|
.expect("Failed to iterate over npc armors.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.npc_weapons.*")
|
|
|
|
.expect("Failed to iterate over npc weapons.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.ore.*")
|
|
|
|
.expect("Failed to iterate over ores.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.tag_examples.*")
|
|
|
|
.expect("Failed to iterate over tag examples.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.testing.*")
|
|
|
|
.expect("Failed to iterate over testing items.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.utility.*")
|
|
|
|
.expect("Failed to iterate over utility items.");
|
|
|
|
|
|
|
|
// Checks each weapon type to allow errors to be located more easily
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.weapons.axe.*")
|
|
|
|
.expect("Failed to iterate over axes.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.weapons.axe_1h.*")
|
|
|
|
.expect("Failed to iterate over 1h axes.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.weapons.bow.*")
|
|
|
|
.expect("Failed to iterate over bows.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.weapons.dagger.*")
|
|
|
|
.expect("Failed to iterate over daggers.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.weapons.empty.*")
|
|
|
|
.expect("Failed to iterate over empty.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.weapons.hammer.*")
|
|
|
|
.expect("Failed to iterate over hammers.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.weapons.hammer_1h.*")
|
|
|
|
.expect("Failed to iterate over 1h hammers.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.weapons.sceptre.*")
|
|
|
|
.expect("Failed to iterate over sceptres.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.weapons.shield.*")
|
|
|
|
.expect("Failed to iterate over shields.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.weapons.staff.*")
|
|
|
|
.expect("Failed to iterate over staffs.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.weapons.sword.*")
|
|
|
|
.expect("Failed to iterate over swords.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.weapons.sword_1h.*")
|
|
|
|
.expect("Failed to iterate over 1h swords.");
|
|
|
|
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.weapons.tool.*")
|
|
|
|
.expect("Failed to iterate over tools.");
|
|
|
|
|
|
|
|
// Checks all weapons should more weapons be added later
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.weapons.*")
|
|
|
|
.expect("Failed to iterate over weapons.");
|
|
|
|
|
|
|
|
// Final at the end to account for a new folder being added
|
|
|
|
crate::comp::item::Item::new_from_asset_glob("common.items.*")
|
|
|
|
.expect("Failed to iterate over item folders.");
|
|
|
|
}
|
|
|
|
}
|