Merge branch 'a1phyr/use_assets_manager' into 'master'

Use `assets_manager` to load assets

See merge request veloren/veloren!1624
This commit is contained in:
Marcel 2020-12-17 12:25:29 +00:00
commit d2da8d671f
56 changed files with 1456 additions and 1754 deletions

View File

@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Initial support for game plugins, both server-side and client-side
- Reflective LoD water
- Map indicators for group members
- Hot-reloading for i18n, sounds, loot lotteries, and more
### Changed

107
Cargo.lock generated
View File

@ -52,6 +52,17 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
[[package]]
name = "ahash"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75b7e6a93ecd6dbd2c225154d0fa7f86205574ecaa6c87429fb5f66ee677c44"
dependencies = [
"getrandom 0.2.0",
"lazy_static",
"version_check 0.9.2",
]
[[package]]
name = "aho-corasick"
version = "0.7.13"
@ -230,6 +241,23 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109"
[[package]]
name = "assets_manager"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3593b8ddb9708251c7a57b07c917c77ecc685e804b697366d26fb4af64c9b960"
dependencies = [
"ahash 0.6.2",
"bincode",
"crossbeam-channel 0.5.0",
"log",
"notify 4.0.15",
"parking_lot 0.11.0",
"ron",
"serde",
"serde_json",
]
[[package]]
name = "async-std"
version = "1.5.0"
@ -791,7 +819,7 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25e4c606eb459dd29f7c57b2e0879f2b6f14ee130918c2b78ccb58a9624e6c7a"
dependencies = [
"getrandom",
"getrandom 0.1.15",
"proc-macro-hack",
]
@ -1745,6 +1773,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "fsevent"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6"
dependencies = [
"bitflags",
"fsevent-sys 2.0.1",
]
[[package]]
name = "fsevent"
version = "2.0.2"
@ -1752,7 +1790,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97f347202c95c98805c216f9e1df210e8ebaec9fdb2365700a43c10797a35e63"
dependencies = [
"bitflags",
"fsevent-sys",
"fsevent-sys 3.0.2",
]
[[package]]
name = "fsevent-sys"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0"
dependencies = [
"libc",
]
[[package]]
@ -1972,6 +2019,17 @@ dependencies = [
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4"
dependencies = [
"cfg-if 0.1.10",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "gfx"
version = "0.18.2"
@ -2561,6 +2619,17 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "inotify"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f"
dependencies = [
"bitflags",
"inotify-sys",
"libc",
]
[[package]]
name = "inotify"
version = "0.8.3"
@ -3249,6 +3318,24 @@ dependencies = [
"version_check 0.9.2",
]
[[package]]
name = "notify"
version = "4.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd"
dependencies = [
"bitflags",
"filetime",
"fsevent 0.4.0",
"fsevent-sys 2.0.1",
"inotify 0.7.1",
"libc",
"mio 0.6.22",
"mio-extras",
"walkdir 2.3.1",
"winapi 0.3.9",
]
[[package]]
name = "notify"
version = "5.0.0-pre.3"
@ -3259,9 +3346,9 @@ dependencies = [
"bitflags",
"crossbeam-channel 0.4.4",
"filetime",
"fsevent",
"fsevent-sys",
"inotify",
"fsevent 2.0.2",
"fsevent-sys 3.0.2",
"inotify 0.8.3",
"libc",
"mio 0.6.22",
"mio-extras",
@ -4033,7 +4120,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom",
"getrandom 0.1.15",
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
@ -4082,7 +4169,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom",
"getrandom 0.1.15",
]
[[package]]
@ -4231,7 +4318,7 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
dependencies = [
"getrandom",
"getrandom 0.1.15",
"redox_syscall",
"rust-argon2",
]
@ -5699,6 +5786,7 @@ name = "veloren-common"
version = "0.8.0"
dependencies = [
"arraygen",
"assets_manager",
"criterion",
"crossbeam-channel 0.5.0",
"crossbeam-utils 0.8.1",
@ -5710,7 +5798,6 @@ dependencies = [
"image",
"indexmap",
"lazy_static",
"notify",
"num-derive",
"num-traits 0.2.14",
"ordered-float 2.0.1",
@ -5933,7 +6020,7 @@ dependencies = [
"inline_tweak",
"lazy_static",
"libloading 0.6.3",
"notify",
"notify 5.0.0-pre.3",
"tracing",
"vek 0.12.0",
"veloren-common",

View File

@ -30,10 +30,10 @@ vek = { version = "0.12.0", features = ["serde"] }
uuid = { version = "0.8.1", default-features = false, features = ["serde", "v4"] }
# Assets
assets_manager = {version = "0.4.2", features = ["bincode", "ron", "json", "hot-reloading"]}
directories-next = "2.0"
dot_vox = "4.0"
image = { version = "0.23.12", default-features = false, features = ["png"] }
notify = "5.0.0-pre.3"
# Data structures
hashbrown = { version = "0.9", features = ["rayon", "serde", "nightly"] }

244
common/src/assets.rs Normal file
View File

@ -0,0 +1,244 @@
//! Load assets (images or voxel data) from files
use dot_vox::DotVoxData;
use image::DynamicImage;
use lazy_static::lazy_static;
use std::{
borrow::Cow,
fs, io,
path::{Path, PathBuf},
sync::Arc,
};
pub use assets_manager::{
asset::Ron,
loader::{
self, BincodeLoader, BytesLoader, JsonLoader, LoadFrom, Loader, RonLoader, StringLoader,
},
source, Asset, AssetCache, BoxedError, Compound, Error,
};
lazy_static! {
/// The HashMap where all loaded assets are stored in.
static ref ASSETS: AssetCache = AssetCache::new(&*ASSETS_PATH).unwrap();
}
pub fn start_hot_reloading() { ASSETS.enhance_hot_reloading(); }
pub type AssetHandle<T> = assets_manager::Handle<'static, T>;
pub type AssetDir<T> = assets_manager::DirReader<'static, T, source::FileSystem>;
/// The Asset trait, which is implemented by all structures that have their data
/// stored in the filesystem.
pub trait AssetExt: Sized + Send + Sync + 'static {
/// Function used to load assets from the filesystem or the cache.
/// Example usage:
/// ```no_run
/// use veloren_common::assets::{self, AssetExt};
///
/// let my_image = assets::Image::load("core.ui.backgrounds.city").unwrap();
/// ```
fn load(specifier: &str) -> Result<AssetHandle<Self>, Error>;
/// Function used to load assets from the filesystem or the cache and return
/// a clone.
fn load_cloned(specifier: &str) -> Result<Self, Error>
where
Self: Clone,
{
Self::load(specifier).map(AssetHandle::cloned)
}
/// 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 veloren_common::assets::{self, AssetExt};
///
/// let my_image = assets::Image::load_expect("core.ui.backgrounds.city");
/// ```
#[track_caller]
fn load_expect(specifier: &str) -> AssetHandle<Self> {
Self::load(specifier).unwrap_or_else(|err| {
panic!(
"Failed loading essential asset: {} (error={:?})",
specifier, err
)
})
}
/// 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.
#[track_caller]
fn load_expect_cloned(specifier: &str) -> Self
where
Self: Clone,
{
Self::load_expect(specifier).cloned()
}
fn load_owned(specifier: &str) -> Result<Self, Error>;
}
pub fn load_dir<T: Asset>(specifier: &str) -> Result<AssetDir<T>, Error> {
Ok(ASSETS.load_dir(specifier)?)
}
impl<T: Compound> AssetExt for T {
fn load(specifier: &str) -> Result<AssetHandle<Self>, Error> { ASSETS.load(specifier) }
fn load_owned(specifier: &str) -> Result<Self, Error> { ASSETS.load_owned(specifier) }
}
pub struct Image(pub Arc<DynamicImage>);
impl Image {
pub fn to_image(&self) -> Arc<DynamicImage> { Arc::clone(&self.0) }
}
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)))
}
}
impl Asset for Image {
type Loader = ImageLoader;
const EXTENSIONS: &'static [&'static str] = &["png", "jpg"];
}
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))
}
}
impl Asset for DotVoxAsset {
type Loader = DotVoxLoader;
const EXTENSION: &'static str = "vox";
}
lazy_static! {
/// Lazy static to find and cache where the asset directory is.
/// 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)
pub static ref ASSETS_PATH: PathBuf = {
let mut paths = Vec::new();
// Note: Ordering matters here!
// 1. VELOREN_ASSETS environment variable
if let Ok(var) = std::env::var("VELOREN_ASSETS") {
paths.push(var.into());
}
// 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
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
{
if let Ok(result) = std::env::var("XDG_DATA_HOME") {
paths.push(format!("{}/veloren/", result).into());
} else if let Ok(result) = std::env::var("HOME") {
paths.push(format!("{}/.local/share/veloren/", result).into());
}
if let Ok(result) = std::env::var("XDG_DATA_DIRS") {
result.split(':').for_each(|x| paths.push(format!("{}/veloren/", x).into()));
} else {
// Fallback
let fallback_paths = vec!["/usr/local/share", "/usr/share"];
for fallback_path in fallback_paths {
paths.push(format!("{}/veloren/", fallback_path).into());
}
}
}
tracing::trace!("Possible asset locations paths={:?}", paths);
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;
}
}
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
}),
);
};
}
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());
if let Some(stem) = maybe_stem {
let specifier = format!("{}.{}", specifier, stem);
if path.is_dir() {
get_dir_files(files, &path, &specifier)?;
} else {
files.push(specifier);
}
}
}
}
Ok(())
}
pub struct Directory(Vec<String>);
impl Directory {
pub fn iter(&self) -> impl Iterator<Item = &String> { self.0.iter() }
}
impl Compound for Directory {
fn load<S: source::Source>(_: &AssetCache<S>, specifier: &str) -> Result<Self, Error> {
let root = ASSETS.source().path_of(specifier, "");
let mut files = Vec::new();
get_dir_files(&mut files, &root, specifier)?;
Ok(Directory(files))
}
}

View File

@ -1,508 +0,0 @@
//! Load assets (images or voxel data) from files
pub mod watch;
use core::{any::Any, fmt, marker::PhantomData};
use dot_vox::DotVoxData;
use hashbrown::HashMap;
use image::DynamicImage;
use lazy_static::lazy_static;
use serde::Deserialize;
use serde_json::Value;
use std::{
fs::{self, File, ReadDir},
io::{BufReader, Read},
path::PathBuf,
sync::{Arc, RwLock},
};
use tracing::{error, trace};
/// The error returned by asset loading functions
#[derive(Debug, Clone)]
pub enum Error {
/// Parsing error occurred.
ParseError(Arc<dyn std::fmt::Debug>),
/// An asset of a different type has already been loaded with this
/// specifier.
InvalidType,
/// Asset does not exist.
NotFound(String),
}
impl Error {
pub fn parse_error<E: std::fmt::Debug + 'static>(err: E) -> Self {
Self::ParseError(Arc::new(err))
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::ParseError(err) => write!(f, "{:?}", err),
Error::InvalidType => write!(
f,
"an asset of a different type has already been loaded with this specifier."
),
Error::NotFound(s) => write!(f, "{}", s),
}
}
}
impl From<Arc<dyn Any + 'static + Sync + Send>> for Error {
fn from(_: Arc<dyn Any + 'static + Sync + Send>) -> Self { Error::InvalidType }
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self { Error::NotFound(format!("{}", err)) }
}
lazy_static! {
/// The HashMap where all loaded assets are stored in.
static ref ASSETS: RwLock<HashMap<String, Arc<dyn Any + 'static + Sync + Send>>> =
RwLock::new(HashMap::new());
}
fn reload<A: Asset>(specifier: &str) -> Result<(), Error>
where
A::Output: Send + Sync + 'static,
{
let asset = Arc::new(A::parse(load_file(specifier, A::ENDINGS)?, specifier)?);
let mut assets_write = ASSETS.write().unwrap();
match assets_write.get_mut(specifier) {
Some(a) => *a = asset,
None => {
assets_write.insert(specifier.to_owned(), asset);
},
}
Ok(())
}
/// 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.
fn parse(buf_reader: BufReader<File>, specifier: &str) -> Result<Self::Output, Error>;
// 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,
{
let assets_read = ASSETS.read().unwrap();
match assets_read.get(specifier) {
Some(asset) => Ok(Arc::clone(asset).downcast()?),
None => {
drop(assets_read); // Drop the asset hashmap to permit recursive loading
let asset = Arc::new(f(Self::parse(
load_file(specifier, Self::ENDINGS)?,
specifier,
)?));
let clone = Arc::clone(&asset);
ASSETS.write().unwrap().insert(specifier.to_owned(), clone);
Ok(asset)
},
}
}
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()?);
}
match get_glob_matches(specifier) {
Ok(glob_matches) => {
let assets = Arc::new(
glob_matches
.into_iter()
.filter_map(|name| {
Self::load(&name)
.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),
}
}
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),
}
}
/// 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)
}
/// 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())
}
/// 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
)
})
}
/// 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()
}
/// 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;
}
}
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)
}
}
impl Asset for DynamicImage {
const ENDINGS: &'static [&'static str] = &["png", "jpg"];
fn parse(mut buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, Error> {
let mut buf = Vec::new();
buf_reader.read_to_end(&mut buf)?;
image::load_from_memory(&buf).map_err(Error::parse_error)
}
}
impl Asset for DotVoxData {
const ENDINGS: &'static [&'static str] = &["vox"];
fn parse(mut buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, Error> {
let mut buf = Vec::new();
buf_reader.read_to_end(&mut buf)?;
dot_vox::load_bytes(&buf).map_err(Error::parse_error)
}
}
// Read a JSON file
impl Asset for Value {
const ENDINGS: &'static [&'static str] = &["json"];
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, Error> {
serde_json::from_reader(buf_reader).map_err(Error::parse_error)
}
}
/// Load from an arbitrary RON file.
pub struct Ron<T>(pub PhantomData<T>);
impl<T: Send + Sync + for<'de> Deserialize<'de>> Asset for Ron<T> {
type Output = T;
const ENDINGS: &'static [&'static str] = &["ron"];
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<T, Error> {
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(())
}
}
lazy_static! {
/// Lazy static to find and cache where the asset directory is.
/// 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)
pub static ref ASSETS_PATH: PathBuf = {
let mut paths = Vec::new();
// Note: Ordering matters here!
// 1. VELOREN_ASSETS environment variable
if let Ok(var) = std::env::var("VELOREN_ASSETS") {
paths.push(var.into());
}
// 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
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
{
if let Ok(result) = std::env::var("XDG_DATA_HOME") {
paths.push(format!("{}/veloren/", result).into());
} else if let Ok(result) = std::env::var("HOME") {
paths.push(format!("{}/.local/share/veloren/", result).into());
}
if let Ok(result) = std::env::var("XDG_DATA_DIRS") {
result.split(':').for_each(|x| paths.push(format!("{}/veloren/", x).into()));
} else {
// Fallback
let fallback_paths = vec!["/usr/local/share", "/usr/share"];
for fallback_path in fallback_paths {
paths.push(format!("{}/veloren/", fallback_path).into());
}
}
}
tracing::trace!("Possible asset locations paths={:?}", paths);
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;
}
}
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
}),
);
};
}
/// Converts a specifier like "core.backgrounds.city" to
/// ".../veloren/assets/core/backgrounds/city".
fn unpack_specifier(specifier: &str) -> PathBuf {
let mut path = ASSETS_PATH.clone();
path.push(specifier.replace(".", "/"));
path
}
/// Loads a file based on the specifier and possible extensions
pub fn load_file(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);
trace!(?path, "Trying to access");
if let Ok(file) = File::open(path) {
return Ok(BufReader::new(file));
}
}
Err(Error::NotFound(path.to_string_lossy().into_owned()))
}
/// 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);
trace!(?path, "Trying to access");
if let Ok(file) = File::open(path) {
return Ok(BufReader::new(file));
}
}
Err(Error::NotFound(path.to_string_lossy().into_owned()))
}
/// 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()))
}
}
// 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<_>>()
})
}

View File

@ -1,182 +0,0 @@
use crossbeam_channel::{select, unbounded, Receiver, Sender};
use lazy_static::lazy_static;
use notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _};
use std::{
collections::HashMap,
path::PathBuf,
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex, Weak,
},
thread,
};
use tracing::{error, warn};
type Handler = Box<dyn Fn() + Send>;
lazy_static! {
static ref WATCHER_TX: Mutex<Sender<(PathBuf, Handler, Weak<AtomicBool>)>> =
Mutex::new(Watcher::new().run());
}
// This will need to be adjusted when specifier mapping to asset location
// becomes more dynamic
struct Watcher {
watching: HashMap<PathBuf, (Handler, Vec<Weak<AtomicBool>>)>,
watcher: RecommendedWatcher,
event_rx: Receiver<Result<Event, notify::Error>>,
}
impl Watcher {
fn new() -> Self {
let (event_tx, event_rx) = unbounded();
Watcher {
watching: HashMap::new(),
watcher: notify::Watcher::new_immediate(move |event| {
let _ = event_tx.send(event);
})
.expect("Failed to create notify::Watcher"),
event_rx,
}
}
fn watch(&mut self, path: PathBuf, handler: Handler, signal: Weak<AtomicBool>) {
match self.watching.get_mut(&path) {
Some((_, ref mut v)) => {
if !v.iter().any(|s| match (s.upgrade(), signal.upgrade()) {
(Some(arc1), Some(arc2)) => Arc::ptr_eq(&arc1, &arc2),
_ => false,
}) {
v.push(signal);
}
},
None => {
if let Err(e) = self.watcher.watch(path.clone(), RecursiveMode::Recursive) {
warn!(?e, ?path, "Could not start watching file");
return;
}
self.watching.insert(path, (handler, vec![signal]));
},
}
}
fn handle_event(&mut self, event: Event) {
if let Event {
kind: EventKind::Modify(_),
paths,
..
} = event
{
for path in paths {
match self.watching.get_mut(&path) {
Some((reloader, ref mut signals)) => {
if !signals.is_empty() {
// Reload this file
reloader();
signals.retain(|signal| match signal.upgrade() {
Some(signal) => {
signal.store(true, Ordering::Release);
true
},
None => false,
});
}
// If there is no one to signal stop watching this path
if signals.is_empty() {
if let Err(err) = self.watcher.unwatch(&path) {
warn!("Error unwatching: {}", err);
}
self.watching.remove(&path);
}
},
None => {
warn!(
"Watching {:#?} but there are no signals for this path. The path will \
be unwatched.",
path
);
if let Err(err) = self.watcher.unwatch(&path) {
warn!("Error unwatching: {}", err);
}
},
}
}
}
}
#[allow(clippy::drop_copy)] // TODO: Pending review in #587
#[allow(clippy::single_match)] // TODO: Pending review in #587
#[allow(clippy::zero_ptr)] // TODO: Pending review in #587
fn run(mut self) -> Sender<(PathBuf, Handler, Weak<AtomicBool>)> {
let (watch_tx, watch_rx) = unbounded();
thread::spawn(move || {
loop {
select! {
recv(watch_rx) -> res => match res {
Ok((path, handler, signal)) => self.watch(path, handler, signal),
// Disconnected
Err(_) => (),
},
recv(self.event_rx) -> res => match res {
Ok(Ok(event)) => self.handle_event(event),
// Notify Error
Ok(Err(e)) => error!(?e, "Notify error"),
// Disconnected
Err(_) => (),
},
}
}
});
watch_tx
}
}
pub struct ReloadIndicator {
reloaded: Arc<AtomicBool>,
// Paths that have already been added
paths: Vec<PathBuf>,
}
impl ReloadIndicator {
#[allow(clippy::new_without_default)] // TODO: Pending review in #587
pub fn new() -> Self {
Self {
reloaded: Arc::new(AtomicBool::new(false)),
paths: Vec::new(),
}
}
pub fn add<F>(&mut self, path: PathBuf, reloader: F)
where
F: 'static + Fn() + Send,
{
// Check to see if this was already added
if self.paths.iter().any(|p| *p == path) {
// Nothing else needs to be done
return;
} else {
self.paths.push(path.clone());
};
if WATCHER_TX
.lock()
.unwrap()
.send((path, Box::new(reloader), Arc::downgrade(&self.reloaded)))
.is_err()
{
error!("Could not add. Asset watcher channel disconnected.");
}
}
// Returns true if the watched file was changed
pub fn reloaded(&self) -> bool {
// Optimize for the common case by performing an initial relaxed read, avoiding
// the atomic write.
if self.reloaded.load(Ordering::Relaxed) {
self.reloaded.swap(false, Ordering::Acquire)
} else {
false
}
}
}

View File

@ -151,8 +151,9 @@ lazy_static! {
.iter()
.map(|s| s.to_string())
.collect();
/// TODO: Make this use hot-reloading
static ref ENTITIES: Vec<String> = {
let npc_names = &*npc::NPC_NAMES;
let npc_names = &*npc::NPC_NAMES.read();
npc::ALL_NPCS
.iter()
.map(|&npc| npc_names[npc].keyword.clone())

View File

@ -16,7 +16,7 @@ use arraygen::Arraygen;
use serde::{Deserialize, Serialize};
use specs::{Component, FlaggedStorage};
use specs_idvs::IdvStorage;
use std::{fs::File, io::BufReader, time::Duration};
use std::time::Duration;
use vek::Vec3;
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
@ -238,11 +238,9 @@ impl Default for CharacterAbility {
}
impl Asset for CharacterAbility {
const ENDINGS: &'static [&'static str] = &["ron"];
type Loader = assets::RonLoader;
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, assets::Error> {
ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error)
}
const EXTENSION: &'static str = "ron";
}
impl CharacterAbility {

View File

@ -20,7 +20,6 @@ use crate::{
use serde::{Deserialize, Serialize};
use specs::{Component, FlaggedStorage};
use specs_idvs::IdvStorage;
use std::{fs::File, io::BufReader};
use vek::*;
make_case_elim!(
@ -123,15 +122,13 @@ impl<'a, BodyMeta, SpeciesMeta> core::ops::Index<&'a Body> for AllBodies<BodyMet
}
impl<
BodyMeta: Send + Sync + for<'de> serde::Deserialize<'de>,
SpeciesMeta: Send + Sync + for<'de> serde::Deserialize<'de>,
BodyMeta: Send + Sync + for<'de> serde::Deserialize<'de> + 'static,
SpeciesMeta: Send + Sync + for<'de> serde::Deserialize<'de> + 'static,
> Asset for AllBodies<BodyMeta, SpeciesMeta>
{
const ENDINGS: &'static [&'static str] = &["json"];
type Loader = assets::JsonLoader;
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, assets::Error> {
serde_json::de::from_reader(buf_reader).map_err(assets::Error::parse_error)
}
const EXTENSION: &'static str = "json";
}
impl Body {

View File

@ -5,7 +5,7 @@ pub mod tool;
pub use tool::{AbilitySet, Hands, Tool, ToolKind, UniqueKind};
use crate::{
assets::{self, Asset, Error},
assets::{self, AssetExt, Error},
effect::Effect,
lottery::Lottery,
terrain::{Block, SpriteKind},
@ -16,8 +16,6 @@ use serde::{Deserialize, Serialize};
use specs::{Component, FlaggedStorage};
use specs_idvs::IdvStorage;
use std::{
fs::File,
io::BufReader,
num::{NonZeroU32, NonZeroU64},
sync::Arc,
};
@ -160,28 +158,58 @@ impl PartialEq for Item {
}
}
impl Asset for ItemDef {
const ENDINGS: &'static [&'static str] = &["ron"];
impl assets::Compound for ItemDef {
fn load<S: assets_manager::source::Source>(
cache: &assets_manager::AssetCache<S>,
specifier: &str,
) -> Result<Self, Error> {
let raw = cache.load_owned::<RawItemDef>(specifier)?;
fn parse(buf_reader: BufReader<File>, specifier: &str) -> Result<Self, assets::Error> {
let item: Result<Self, Error> =
ron::de::from_reader(buf_reader).map_err(Error::parse_error);
let RawItemDef {
name,
description,
kind,
quality,
} = raw;
// Some commands like /give_item provide the asset specifier separated with \
// instead of .
let specifier = specifier.replace('\\', ".");
//
// TODO: This probably does not belong here
let item_definition_id = specifier.replace('\\', ".");
item.map(|item| ItemDef {
item_definition_id: specifier,
..item
Ok(ItemDef {
item_definition_id,
name,
description,
kind,
quality,
})
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename = "ItemDef")]
struct RawItemDef {
name: String,
description: String,
kind: ItemKind,
quality: Quality,
}
impl assets::Asset for RawItemDef {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
#[derive(Debug)]
pub struct OperationFailure;
impl Item {
// TODO: consider alternatives such as default abilities that can be added to a
// loadout when no weapon is present
pub fn empty() -> Self { Item::new(ItemDef::load_expect("common.items.weapons.empty.empty")) }
pub fn empty() -> Self { Item::new_from_asset_expect("common.items.weapons.empty.empty") }
pub fn new(inner_item: Arc<ItemDef>) -> Self {
Item {
@ -194,27 +222,28 @@ impl Item {
/// Creates a new instance of an `Item` from the provided asset identifier
/// Panics if the asset does not exist.
pub fn new_from_asset_expect(asset_specifier: &str) -> Self {
let inner_item = ItemDef::load_expect(asset_specifier);
let inner_item = Arc::<ItemDef>::load_expect_cloned(asset_specifier);
Item::new(inner_item)
}
/// Creates a Vec containing one of each item that matches the provided
/// asset glob pattern
pub fn new_from_asset_glob(asset_glob: &str) -> Result<Vec<Self>, Error> {
let items = ItemDef::load_glob(asset_glob)?;
//let items = ItemDef::load_glob(asset_glob)?;
let result = items
let specifiers = assets::Directory::load(asset_glob)?;
specifiers
.read()
.iter()
.map(|item_def| Item::new(Arc::clone(item_def)))
.collect::<Vec<_>>();
Ok(result)
.map(|spec| Self::new_from_asset(&spec))
.collect()
}
/// Creates a new instance of an `Item from the provided asset identifier if
/// it exists
pub fn new_from_asset(asset: &str) -> Result<Self, Error> {
let inner_item = ItemDef::load(asset)?;
let inner_item = Arc::<ItemDef>::load_cloned(asset)?;
Ok(Item::new(inner_item))
}
@ -254,30 +283,30 @@ impl Item {
/// up by another player.
pub fn put_in_world(&mut self) { self.reset_item_id() }
pub fn increase_amount(&mut self, increase_by: u32) -> Result<(), assets::Error> {
pub fn increase_amount(&mut self, increase_by: u32) -> Result<(), OperationFailure> {
let amount = u32::from(self.amount);
self.amount = amount
.checked_add(increase_by)
.and_then(NonZeroU32::new)
.ok_or(assets::Error::InvalidType)?;
.ok_or(OperationFailure)?;
Ok(())
}
pub fn decrease_amount(&mut self, decrease_by: u32) -> Result<(), assets::Error> {
pub fn decrease_amount(&mut self, decrease_by: u32) -> Result<(), OperationFailure> {
let amount = u32::from(self.amount);
self.amount = amount
.checked_sub(decrease_by)
.and_then(NonZeroU32::new)
.ok_or(assets::Error::InvalidType)?;
.ok_or(OperationFailure)?;
Ok(())
}
pub fn set_amount(&mut self, give_amount: u32) -> Result<(), assets::Error> {
pub fn set_amount(&mut self, give_amount: u32) -> Result<(), OperationFailure> {
if give_amount == 1 || self.item_def.is_stackable() {
self.amount = NonZeroU32::new(give_amount).ok_or(assets::Error::InvalidType)?;
self.amount = NonZeroU32::new(give_amount).ok_or(OperationFailure)?;
Ok(())
} else {
Err(assets::Error::InvalidType)
Err(OperationFailure)
}
}
@ -327,7 +356,8 @@ impl Item {
3 => "common.loot_tables.loot_table_armor_cloth",
4 => "common.loot_tables.loot_table_armor_heavy",
_ => "common.loot_tables.loot_table_armor_misc",
});
})
.read();
chosen.choose()
},
SpriteKind::ChestBurried => {
@ -336,7 +366,8 @@ impl Item {
2 => "common.loot_tables.loot_table_armor_light",
3 => "common.loot_tables.loot_table_armor_cloth",
_ => "common.loot_tables.loot_table_armor_misc",
});
})
.read();
chosen.choose()
},
SpriteKind::Mud => {
@ -345,14 +376,16 @@ impl Item {
1 => "common.loot_tables.loot_table_weapon_common",
2 => "common.loot_tables.loot_table_armor_misc",
_ => "common.loot_tables.loot_table_rocks",
});
})
.read();
chosen.choose()
},
SpriteKind::Crate => {
chosen = Lottery::<String>::load_expect(match rng.gen_range(0, 4) {
0 => "common.loot_tables.loot_table_crafting",
_ => "common.loot_tables.loot_table_food",
});
})
.read();
chosen.choose()
},
SpriteKind::Beehive => "common.items.crafting_ing.honey",

View File

@ -7,7 +7,7 @@ use crate::{
};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use std::{fs::File, io::BufReader, time::Duration};
use std::time::Duration;
use tracing::error;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
@ -107,7 +107,7 @@ pub struct AbilitySet<T> {
}
impl AbilitySet<CharacterAbility> {
pub fn modified_by_tool(self, tool: &Tool) -> Self {
fn modified_by_tool(self, tool: &Tool) -> Self {
self.map(|a| a.adjusted_by_stats(tool.base_power(), tool.base_speed()))
}
}
@ -120,6 +120,14 @@ impl<T> AbilitySet<T> {
skills: self.skills.into_iter().map(|x| f(x)).collect(),
}
}
pub fn map_ref<U, F: FnMut(&T) -> U>(&self, mut f: F) -> AbilitySet<U> {
AbilitySet {
primary: f(&self.primary),
secondary: f(&self.secondary),
skills: self.skills.iter().map(|x| f(x)).collect(),
}
}
}
impl Default for AbilitySet<CharacterAbility> {
@ -135,37 +143,33 @@ impl Default for AbilitySet<CharacterAbility> {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AbilityMap<T = CharacterAbility>(HashMap<ToolKind, AbilitySet<T>>);
impl Asset for AbilityMap {
const ENDINGS: &'static [&'static str] = &["ron"];
impl Asset for AbilityMap<String> {
type Loader = assets::RonLoader;
fn parse(buf_reader: BufReader<File>, specifier: &str) -> Result<Self, assets::Error> {
ron::de::from_reader::<BufReader<File>, AbilityMap<String>>(buf_reader)
.map(|map| {
AbilityMap(
map.0
.into_iter()
.map(|(kind, set)| {
(
kind,
set.map(|s| match CharacterAbility::load(&s) {
Ok(ability) => ability.as_ref().clone(),
Err(err) => {
error!(
?err,
"Error loading CharacterAbility: {} for the ability \
map: {} replacing with default",
s,
specifier
);
CharacterAbility::default()
},
}),
)
})
.collect(),
)
})
.map_err(assets::Error::parse_error)
const EXTENSION: &'static str = "ron";
}
impl assets::Compound for AbilityMap {
fn load<S: assets_manager::source::Source>(
cache: &assets_manager::AssetCache<S>,
specifier: &str,
) -> Result<Self, assets::Error> {
let manifest = cache.load::<AbilityMap<String>>(specifier)?.read();
Ok(AbilityMap(
manifest
.0
.iter()
.map(|(kind, set)| {
(
*kind,
// expect cannot fail because CharacterAbility always
// provides a default value in case of failure
set.map_ref(|s| cache.load_expect(&s).cloned()),
)
})
.collect(),
))
}
}

View File

@ -139,7 +139,7 @@ fn loadout_insert(
///
/// ```
/// use veloren_common::{
/// assets::Asset,
/// assets::AssetExt,
/// comp::{
/// item::tool::AbilityMap,
/// slot::{loadout_remove, EquipSlot},
@ -263,7 +263,7 @@ pub fn swap(
///
/// ```
/// use veloren_common::{
/// assets::Asset,
/// assets::AssetExt,
/// comp::{
/// item::tool::AbilityMap,
/// slot::{equip, EquipSlot},
@ -324,7 +324,7 @@ pub fn equip(slot: usize, inventory: &mut Inventory, loadout: &mut Loadout, map:
///
/// ```
/// use veloren_common::{
/// assets::Asset,
/// assets::AssetExt,
/// comp::{
/// item::tool::AbilityMap,
/// slot::{unequip, EquipSlot},
@ -365,7 +365,7 @@ pub fn unequip(
#[cfg(test)]
mod tests {
use super::*;
use crate::{comp::Item, LoadoutBuilder};
use crate::{assets::AssetExt, comp::Item, LoadoutBuilder};
#[test]
fn test_unequip_items_both_hands() {
@ -374,7 +374,6 @@ mod tests {
amount: 0,
};
use crate::assets::Asset;
let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
let sword = LoadoutBuilder::default_item_config_from_str(
@ -419,7 +418,6 @@ mod tests {
let mut loadout = LoadoutBuilder::new().defaults().build();
use crate::assets::Asset;
let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
// We should start with the starting sandles
@ -444,7 +442,6 @@ mod tests {
"common.items.armor.starter.sandals_0",
));
use crate::assets::Asset;
let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
let mut loadout = LoadoutBuilder::new().defaults().build();
@ -469,7 +466,6 @@ mod tests {
#[test]
fn test_loadout_remove() {
use crate::assets::Asset;
let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
let sword = LoadoutBuilder::default_item_config_from_str(

View File

@ -115,22 +115,23 @@ impl EntityInfo {
}
pub fn with_automatic_name(mut self) -> Self {
let npc_names = NPC_NAMES.read();
self.name = match &self.body {
Body::Humanoid(body) => Some(get_npc_name(&NPC_NAMES.humanoid, body.species)),
Body::Humanoid(body) => Some(get_npc_name(&npc_names.humanoid, body.species)),
Body::QuadrupedMedium(body) => {
Some(get_npc_name(&NPC_NAMES.quadruped_medium, body.species))
Some(get_npc_name(&npc_names.quadruped_medium, body.species))
},
Body::BirdMedium(body) => Some(get_npc_name(&NPC_NAMES.bird_medium, body.species)),
Body::FishSmall(body) => Some(get_npc_name(&NPC_NAMES.fish_small, body.species)),
Body::FishMedium(body) => Some(get_npc_name(&NPC_NAMES.fish_medium, body.species)),
Body::Theropod(body) => Some(get_npc_name(&NPC_NAMES.theropod, body.species)),
Body::BirdMedium(body) => Some(get_npc_name(&npc_names.bird_medium, body.species)),
Body::FishSmall(body) => Some(get_npc_name(&npc_names.fish_small, body.species)),
Body::FishMedium(body) => Some(get_npc_name(&npc_names.fish_medium, body.species)),
Body::Theropod(body) => Some(get_npc_name(&npc_names.theropod, body.species)),
Body::QuadrupedSmall(body) => {
Some(get_npc_name(&NPC_NAMES.quadruped_small, body.species))
Some(get_npc_name(&npc_names.quadruped_small, body.species))
},
Body::Dragon(body) => Some(get_npc_name(&NPC_NAMES.dragon, body.species)),
Body::QuadrupedLow(body) => Some(get_npc_name(&NPC_NAMES.quadruped_low, body.species)),
Body::Golem(body) => Some(get_npc_name(&NPC_NAMES.golem, body.species)),
Body::BipedLarge(body) => Some(get_npc_name(&NPC_NAMES.biped_large, body.species)),
Body::Dragon(body) => Some(get_npc_name(&npc_names.dragon, body.species)),
Body::QuadrupedLow(body) => Some(get_npc_name(&npc_names.quadruped_low, body.species)),
Body::Golem(body) => Some(get_npc_name(&npc_names.golem, body.species)),
Body::BipedLarge(body) => Some(get_npc_name(&npc_names.biped_large, body.species)),
_ => None,
}
.map(|s| {

View File

@ -11,7 +11,7 @@ use rand::Rng;
///
/// ```
/// use veloren_common::{
/// assets::Asset,
/// assets::AssetExt,
/// comp::item::tool::AbilityMap,
/// LoadoutBuilder,
/// };

View File

@ -1,7 +1,6 @@
use crate::assets::{self, Asset};
use crate::assets;
use rand::prelude::*;
use serde::{de::DeserializeOwned, Deserialize};
use std::{fs::File, io::BufReader};
#[derive(Clone, Debug, PartialEq, Deserialize)]
pub struct Lottery<T> {
@ -9,28 +8,26 @@ pub struct Lottery<T> {
total: f32,
}
impl<T: DeserializeOwned + Send + Sync> Asset for Lottery<T> {
const ENDINGS: &'static [&'static str] = &["ron"];
impl<T: DeserializeOwned + Send + Sync + 'static> assets::Asset for Lottery<T> {
type Loader = assets::LoadFrom<Vec<(f32, T)>, assets::RonLoader>;
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, assets::Error> {
ron::de::from_reader::<BufReader<File>, Vec<(f32, T)>>(buf_reader)
.map(|items| Lottery::from_rates(items.into_iter()))
.map_err(assets::Error::parse_error)
const EXTENSION: &'static str = "ron";
}
impl<T> From<Vec<(f32, T)>> for Lottery<T> {
fn from(mut items: Vec<(f32, T)>) -> Lottery<T> {
let mut total = 0.0;
for (rate, _) in &mut items {
total += *rate;
*rate = total - *rate;
}
Self { items, total }
}
}
impl<T> Lottery<T> {
pub fn from_rates(items: impl Iterator<Item = (f32, T)>) -> Self {
let mut total = 0.0;
let items = items
.map(|(rate, item)| {
total += rate;
(total - rate, item)
})
.collect();
Self { items, total }
}
pub fn choose_seeded(&self, seed: u32) -> &T {
let x = ((seed % 65536) as f32 / 65536.0) * self.total;
&self.items[self
@ -48,13 +45,13 @@ impl<T> Lottery<T> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{assets::Asset, comp::Item};
use crate::{assets::AssetExt, comp::Item};
#[test]
fn test_loot_table() {
let test = Lottery::<String>::load_expect("common.loot_tables.loot_table");
for (_, item_asset_specifier) in test.iter() {
for (_, item_asset_specifier) in test.read().iter() {
assert!(
Item::new_from_asset(item_asset_specifier).is_ok(),
"Invalid loot table item '{}'",

View File

@ -1,11 +1,11 @@
use crate::{
assets::Asset,
assets::{AssetExt, AssetHandle},
comp::{self, AllBodies, Body},
};
use lazy_static::lazy_static;
use rand::seq::SliceRandom;
use serde::Deserialize;
use std::{str::FromStr, sync::Arc};
use std::str::FromStr;
#[derive(Clone, Copy, PartialEq)]
pub enum NpcKind {
@ -67,14 +67,14 @@ pub struct SpeciesNames {
pub type NpcNames = AllBodies<BodyNames, SpeciesNames>;
lazy_static! {
pub static ref NPC_NAMES: Arc<NpcNames> = NpcNames::load_expect("common.npc_names");
pub static ref NPC_NAMES: AssetHandle<NpcNames> = NpcNames::load_expect("common.npc_names");
}
impl FromStr for NpcKind {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let npc_names = &*NPC_NAMES;
fn from_str(s: &str) -> Result<Self, ()> {
let npc_names = &*NPC_NAMES.read();
ALL_NPCS
.iter()
.copied()
@ -83,11 +83,15 @@ impl FromStr for NpcKind {
}
}
pub fn get_npc_name(npc_type: NpcKind) -> &'static str {
let BodyNames { keyword, names } = &NPC_NAMES[npc_type];
pub fn get_npc_name(npc_type: NpcKind) -> String {
let npc_names = NPC_NAMES.read();
let BodyNames { keyword, names } = &npc_names[npc_type];
// If no pretty name is found, fall back to the keyword.
names.choose(&mut rand::thread_rng()).unwrap_or(keyword)
names
.choose(&mut rand::thread_rng())
.unwrap_or(keyword)
.clone()
}
/// Randomly generates a body associated with this NPC kind.
@ -164,7 +168,7 @@ impl NpcBody {
)
})
}
let npc_names = &NPC_NAMES;
let npc_names = &*NPC_NAMES.read();
// First, parse npc kind names.
NpcKind::from_str(s)
.map(|kind| NpcBody(kind, Box::new(move || kind_to_body(kind))))

View File

@ -1,10 +1,10 @@
use crate::{
assets::{self, Asset},
assets::{self, AssetExt, AssetHandle},
comp::{item::ItemDef, Inventory, Item},
};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use std::{fs::File, io::BufReader, sync::Arc};
use std::sync::Arc;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Recipe {
@ -65,36 +65,44 @@ impl RecipeBook {
}
}
impl Asset for RecipeBook {
const ENDINGS: &'static [&'static str] = &["ron"];
#[derive(Deserialize)]
#[serde(transparent)]
#[allow(clippy::type_complexity)]
struct RawRecipeBook(HashMap<String, ((String, u32), Vec<(String, u32)>)>);
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, assets::Error> {
ron::de::from_reader::<
BufReader<File>,
HashMap<String, ((String, u32), Vec<(String, u32)>)>,
>(buf_reader)
.map_err(assets::Error::parse_error)
.and_then(|recipes| {
Ok(RecipeBook {
recipes: recipes
.into_iter()
.map::<Result<(String, Recipe), assets::Error>, _>(
|(name, ((output, amount), inputs))| {
Ok((name, Recipe {
output: (ItemDef::load(&output)?, amount),
inputs: inputs
.into_iter()
.map::<Result<(Arc<ItemDef>, u32), assets::Error>, _>(
|(name, amount)| Ok((ItemDef::load(&name)?, amount)),
)
.collect::<Result<_, _>>()?,
}))
},
)
.collect::<Result<_, _>>()?,
impl assets::Asset for RawRecipeBook {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
impl assets::Compound for RecipeBook {
fn load<S: assets_manager::source::Source>(
cache: &assets_manager::AssetCache<S>,
specifier: &str,
) -> Result<Self, assets_manager::Error> {
#[inline]
fn load_item_def(spec: &(String, u32)) -> Result<(Arc<ItemDef>, u32), assets::Error> {
let def = Arc::<ItemDef>::load_cloned(&spec.0)?;
Ok((def, spec.1))
}
let raw = cache.load::<RawRecipeBook>(specifier)?.read();
let recipes = raw
.0
.iter()
.map(|(name, (output, inputs))| {
let inputs = inputs.iter().map(load_item_def).collect::<Result<_, _>>()?;
let output = load_item_def(output)?;
Ok((name.clone(), Recipe { inputs, output }))
})
})
.collect::<Result<_, assets::Error>>()?;
Ok(RecipeBook { recipes })
}
}
pub fn default_recipe_book() -> Arc<RecipeBook> { RecipeBook::load_expect("common.recipe_book") }
pub fn default_recipe_book() -> AssetHandle<RecipeBook> {
RecipeBook::load_expect("common.recipe_book")
}

View File

@ -13,7 +13,7 @@ pub use self::{
map::MapSizeLg,
site::SitesKind,
sprite::SpriteKind,
structure::Structure,
structure::{Structure, StructuresGroup},
};
use roots::find_roots_cubic;
use serde::{Deserialize, Serialize};

View File

@ -1,13 +1,12 @@
use super::BlockKind;
use crate::{
assets::{self, Asset, Ron},
assets::{self, AssetExt, AssetHandle, DotVoxAsset, Error},
make_case_elim,
vol::{BaseVol, ReadVol, SizedVol, WriteVol},
volumes::dyna::{Dyna, DynaError},
};
use dot_vox::DotVoxData;
use serde::Deserialize;
use std::{fs::File, io::BufReader, sync::Arc};
use std::sync::Arc;
use vek::*;
make_case_elim!(
@ -40,20 +39,49 @@ pub enum StructureError {}
#[derive(Clone)]
pub struct Structure {
center: Vec3<i32>,
base: Arc<BaseStructure>,
}
struct BaseStructure {
vol: Dyna<StructureBlock, ()>,
empty: StructureBlock,
default_kind: BlockKind,
}
pub struct StructuresGroup(Vec<Structure>);
impl std::ops::Deref for StructuresGroup {
type Target = [Structure];
fn deref(&self) -> &[Structure] { &self.0 }
}
impl assets::Compound for StructuresGroup {
fn load<S: assets_manager::source::Source>(
cache: &assets_manager::AssetCache<S>,
specifier: &str,
) -> Result<Self, Error> {
let specs = cache.load::<StructuresGroupSpec>(specifier)?.read();
Ok(StructuresGroup(
specs
.0
.iter()
.map(|sp| {
let base = cache.load::<Arc<BaseStructure>>(&sp.specifier)?.cloned();
Ok(Structure {
center: Vec3::from(sp.center),
base,
})
})
.collect::<Result<_, Error>>()?,
))
}
}
impl Structure {
pub fn load_group(specifier: &str) -> Vec<Arc<Structure>> {
let spec = StructuresSpec::load_expect(&["world.manifests.", specifier].concat());
spec.iter()
.map(|sp| {
Structure::load_map(&sp.specifier[..], |s| s.with_center(Vec3::from(sp.center)))
.unwrap()
})
.collect()
pub fn load_group(specifier: &str) -> AssetHandle<StructuresGroup> {
StructuresGroup::load_expect(&["world.manifests.", specifier].concat())
}
pub fn with_center(mut self, center: Vec3<i32>) -> Self {
@ -61,19 +89,14 @@ impl Structure {
self
}
pub fn with_default_kind(mut self, kind: BlockKind) -> Self {
self.default_kind = kind;
self
}
pub fn get_bounds(&self) -> Aabb<i32> {
Aabb {
min: -self.center,
max: self.vol.size().map(|e| e as i32) - self.center,
max: self.base.vol.size().map(|e| e as i32) - self.center,
}
}
pub fn default_kind(&self) -> BlockKind { self.default_kind }
pub fn default_kind(&self) -> BlockKind { self.base.default_kind }
}
impl BaseVol for Structure {
@ -84,18 +107,20 @@ impl BaseVol for Structure {
impl ReadVol for Structure {
#[inline(always)]
fn get(&self, pos: Vec3<i32>) -> Result<&Self::Vox, StructureError> {
match self.vol.get(pos + self.center) {
match self.base.vol.get(pos + self.center) {
Ok(block) => Ok(block),
Err(DynaError::OutOfBounds) => Ok(&self.empty),
Err(DynaError::OutOfBounds) => Ok(&self.base.empty),
}
}
}
impl Asset for Structure {
const ENDINGS: &'static [&'static str] = &["vox"];
fn parse(buf_reader: BufReader<File>, specifier: &str) -> Result<Self, assets::Error> {
let dot_vox_data = DotVoxData::parse(buf_reader, specifier)?;
impl assets::Compound for BaseStructure {
fn load<S: assets_manager::source::Source>(
cache: &assets_manager::AssetCache<S>,
specifier: &str,
) -> Result<Self, Error> {
let dot_vox_data = cache.load::<DotVoxAsset>(specifier)?.read();
let dot_vox_data = &dot_vox_data.0;
if let Some(model) = dot_vox_data.models.get(0) {
let palette = dot_vox_data
@ -138,15 +163,13 @@ impl Asset for Structure {
let _ = vol.set(Vec3::new(voxel.x, voxel.y, voxel.z).map(i32::from), block);
}
Ok(Structure {
center: Vec3::zero(),
Ok(BaseStructure {
vol,
empty: StructureBlock::None,
default_kind: BlockKind::Misc,
})
} else {
Ok(Self {
center: Vec3::zero(),
Ok(BaseStructure {
vol: Dyna::filled(Vec3::zero(), StructureBlock::None, ()),
empty: StructureBlock::None,
default_kind: BlockKind::Misc,
@ -161,4 +184,11 @@ struct StructureSpec {
center: [i32; 3],
}
type StructuresSpec = Ron<Vec<StructureSpec>>;
#[derive(Deserialize)]
struct StructuresGroupSpec(Vec<StructureSpec>);
impl assets::Asset for StructuresGroupSpec {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}

View File

@ -777,7 +777,7 @@ fn handle_spawn(
.state
.create_npc(
pos,
comp::Stats::new(get_npc_name(id).into(), body),
comp::Stats::new(get_npc_name(id), body),
comp::Health::new(body, 1),
loadout,
body,

View File

@ -5,7 +5,7 @@ use crate::{
Server, SpawnPoint, StateExt,
};
use common::{
assets::Asset,
assets::AssetExt,
comp::{
self, aura, buff,
chat::{KillSource, KillType},
@ -354,7 +354,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
let item = {
let mut item_drops = state.ecs().write_storage::<comp::ItemDrop>();
item_drops.remove(entity).map_or_else(
|| Item::new_from_asset_expect(lottery().choose()),
|| Item::new_from_asset_expect(lottery().read().choose()),
|item_drop| item_drop.0,
)
};

View File

@ -446,7 +446,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
.write_storage::<comp::Inventory>()
.get_mut(entity)
{
let recipe_book = default_recipe_book();
let recipe_book = default_recipe_book().read();
let craft_result = recipe_book.get(&recipe).and_then(|r| r.perform(inv).ok());
// FIXME: We should really require the drop and write to be atomic!

View File

@ -47,7 +47,7 @@ use crate::{
sys::sentinel::{DeletedEntities, TrackedComps},
};
use common::{
assets::Asset,
assets::AssetExt,
cmd::ChatCommand,
comp,
event::{EventBus, ServerEvent},
@ -939,7 +939,7 @@ impl Server {
max_group_size: self.settings().max_player_group_size,
client_timeout: self.settings().client_timeout,
world_map: self.map.clone(),
recipe_book: (&*default_recipe_book()).clone(),
recipe_book: default_recipe_book().cloned(),
ability_map: (&*self
.state
.ecs()

View File

@ -145,11 +145,13 @@ impl<'a> System<'a> for Sys {
if entity.is_giant {
if rand::random::<f32>() < 0.65 && entity.alignment != Alignment::Enemy {
let body_new = comp::humanoid::Body::random();
let npc_names = NPC_NAMES.read();
body = comp::Body::Humanoid(body_new);
stats = comp::Stats::new(
format!(
"Gentle Giant {}",
get_npc_name(&NPC_NAMES.humanoid, body_new.species)
get_npc_name(&npc_names.humanoid, body_new.species)
),
body,
);

View File

@ -4,7 +4,10 @@ use crate::{
scene::Camera,
};
use client::Client;
use common::{assets, vol::ReadVol};
use common::{
assets::{self, AssetExt, AssetHandle},
vol::ReadVol,
};
use common_sys::state::State;
use serde::Deserialize;
use std::time::Instant;
@ -27,7 +30,7 @@ pub struct AmbientItem {
}
pub struct AmbientMgr {
soundtrack: AmbientCollection,
soundtrack: AssetHandle<AmbientCollection>,
began_playing: Instant,
next_track_change: f32,
volume: f32,
@ -56,7 +59,7 @@ impl AmbientMgr {
client: &Client,
camera: &Camera,
) {
if audio.sfx_enabled() && !self.soundtrack.tracks.is_empty() {
if audio.sfx_enabled() && !self.soundtrack.read().tracks.is_empty() {
let focus_off = camera.get_focus_pos().map(f32::trunc);
let cam_pos = camera.dependents().cam_pos + focus_off;
@ -107,8 +110,8 @@ impl AmbientMgr {
// Right now there is only wind non-positional sfx so it is always
// selected. Modify this variable assignment when adding other non-
// positional sfx
let track = &self
.soundtrack
let soundtrack = self.soundtrack.read();
let track = &soundtrack
.tracks
.iter()
.find(|track| track.tag == AmbientChannelTag::Wind);
@ -123,27 +126,23 @@ impl AmbientMgr {
}
}
fn load_soundtrack_items() -> AmbientCollection {
match assets::load_file("voxygen.audio.ambient", &["ron"]) {
Ok(file) => match ron::de::from_reader(file) {
Ok(config) => config,
Err(error) => {
warn!(
"Error parsing music config file, music will not be available: {}",
format!("{:#?}", error)
);
AmbientCollection::default()
},
},
Err(error) => {
warn!(
"Error reading music config file, music will not be available: {}",
format!("{:#?}", error)
);
AmbientCollection::default()
},
}
fn load_soundtrack_items() -> AssetHandle<AmbientCollection> {
// Cannot fail: A default value is always provided
AmbientCollection::load_expect("voxygen.audio.ambient")
}
}
impl assets::Asset for AmbientCollection {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
fn default_value(_: &str, error: assets::Error) -> Result<Self, assets::Error> {
warn!(
"Error reading music config file, music will not be available: {:#?}",
error
);
Ok(AmbientCollection::default())
}
}

View File

@ -10,12 +10,12 @@ pub mod soundcache;
use channel::{AmbientChannel, AmbientChannelTag, MusicChannel, MusicChannelTag, SfxChannel};
use fader::Fader;
use sfx::{SfxEvent, SfxTriggerItem};
use soundcache::SoundCache;
use soundcache::{OggSound, WavSound};
use std::time::Duration;
use tracing::debug;
use common::assets;
use rodio::{source::Source, Decoder, OutputStream, OutputStreamHandle, StreamError};
use common::assets::AssetExt;
use rodio::{source::Source, OutputStream, OutputStreamHandle, StreamError};
use vek::*;
#[derive(Default, Clone)]
@ -38,7 +38,6 @@ pub struct AudioFrontend {
//pub audio_device: Option<Device>,
pub stream: Option<rodio::OutputStream>,
audio_stream: Option<rodio::OutputStreamHandle>,
sound_cache: SoundCache,
music_channels: Vec<MusicChannel>,
ambient_channels: Vec<AmbientChannel>,
@ -76,7 +75,6 @@ impl AudioFrontend {
//audio_device,
stream,
audio_stream,
sound_cache: SoundCache::default(),
music_channels: Vec::new(),
sfx_channels,
ambient_channels: Vec::new(),
@ -95,7 +93,6 @@ impl AudioFrontend {
//audio_device: None,
stream: None,
audio_stream: None,
sound_cache: SoundCache::default(),
music_channels: Vec::new(),
sfx_channels: Vec::new(),
ambient_channels: Vec::new(),
@ -231,9 +228,9 @@ impl AudioFrontend {
/// Play (once) an sfx file by file path at the give position and volume
pub fn play_sfx(&mut self, sound: &str, pos: Vec3<f32>, vol: Option<f32>) {
if self.audio_stream.is_some() {
let sound = self
.sound_cache
.load_sound(sound)
let sound = WavSound::load_expect(sound)
.cloned()
.decoder()
.amplify(vol.unwrap_or(1.0));
let listener = self.listener.clone();
@ -250,9 +247,9 @@ impl AudioFrontend {
/// being underwater
pub fn play_underwater_sfx(&mut self, sound: &str, pos: Vec3<f32>, vol: Option<f32>) {
if self.audio_stream.is_some() {
let sound = self
.sound_cache
.load_sound(sound)
let sound = WavSound::load_expect(sound)
.cloned()
.decoder()
.amplify(vol.unwrap_or(1.0));
let listener = self.listener.clone();
@ -272,9 +269,7 @@ impl AudioFrontend {
) {
if self.audio_stream.is_some() {
if let Some(channel) = self.get_ambient_channel(channel_tag, volume_multiplier) {
let file = assets::load_file(&sound, &["ogg"]).expect("Failed to load sound");
let sound = Decoder::new(file).expect("Failed to decode sound");
let sound = OggSound::load_expect(sound).cloned().decoder();
channel.play(sound);
}
}
@ -326,9 +321,7 @@ impl AudioFrontend {
fn play_music(&mut self, sound: &str, channel_tag: MusicChannelTag) {
if self.music_enabled() {
if let Some(channel) = self.get_music_channel(channel_tag) {
let file = assets::load_file(&sound, &["ogg"]).expect("Failed to load sound");
let sound = Decoder::new(file).expect("Failed to decode sound");
let sound = OggSound::load_expect(sound).cloned().decoder();
channel.play(sound, channel_tag);
}
}

View File

@ -45,7 +45,7 @@
use crate::audio::{AudioFrontend, MusicChannelTag};
use client::Client;
use common::{
assets,
assets::{self, AssetExt, AssetHandle},
terrain::{BiomeKind, SitesKind},
};
use common_sys::state::State;
@ -103,7 +103,7 @@ enum PlayState {
/// Provides methods to control music playback
pub struct MusicMgr {
/// Collection of all the tracks
soundtrack: SoundtrackCollection,
soundtrack: AssetHandle<SoundtrackCollection>,
/// Instant at which the current track began playing
began_playing: Instant,
/// Time until the next track should be played
@ -139,7 +139,7 @@ impl MusicMgr {
//}
if audio.music_enabled()
&& !self.soundtrack.tracks.is_empty()
&& !self.soundtrack.read().tracks.is_empty()
&& self.began_playing.elapsed().as_secs_f32() > self.next_track_change
{
self.play_random_track(audio, state, client);
@ -158,8 +158,8 @@ impl MusicMgr {
let current_site = client.current_site();
// Filters out tracks not matching the timing, site, and biome
let maybe_tracks = self
.soundtrack
let soundtrack = self.soundtrack.read();
let maybe_tracks = soundtrack
.tracks
.iter()
.filter(|track| {
@ -225,27 +225,23 @@ impl MusicMgr {
}
}
fn load_soundtrack_items() -> SoundtrackCollection {
match assets::load_file("voxygen.audio.soundtrack", &["ron"]) {
Ok(file) => match ron::de::from_reader(file) {
Ok(config) => config,
Err(error) => {
warn!(
"Error parsing music config file, music will not be available: {}",
format!("{:#?}", error)
);
SoundtrackCollection::default()
},
},
Err(error) => {
warn!(
"Error reading music config file, music will not be available: {}",
format!("{:#?}", error)
);
SoundtrackCollection::default()
},
}
fn load_soundtrack_items() -> AssetHandle<SoundtrackCollection> {
// Cannot fail: A default value is always provided
SoundtrackCollection::load_expect("voxygen.audio.soundtrack")
}
}
impl assets::Asset for SoundtrackCollection {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
fn default_value(_: &str, error: assets::Error) -> Result<Self, assets::Error> {
warn!(
"Error reading music config file, music will not be available: {:#?}",
error
);
Ok(SoundtrackCollection::default())
}
}

View File

@ -89,7 +89,7 @@ use crate::{
use client::Client;
use common::{
assets,
assets::{self, AssetExt, AssetHandle},
comp::{
item::{ItemKind, ToolKind},
object, Body, CharacterAbilityType, InventoryUpdateEvent,
@ -237,7 +237,9 @@ impl SfxTriggers {
}
pub struct SfxMgr {
pub triggers: SfxTriggers,
/// This is an `AssetHandle` so it is reloaded automatically
/// when the manifest is edited.
pub triggers: AssetHandle<SfxTriggers>,
event_mapper: SfxEventMapper,
}
@ -273,12 +275,14 @@ impl SfxMgr {
// same direction as the camera
audio.set_listener_pos(cam_pos, camera.dependents().cam_dir);
let triggers = self.triggers.read();
self.event_mapper.maintain(
audio,
state,
player_entity,
camera,
&self.triggers,
&triggers,
terrain,
client,
);
@ -357,27 +361,23 @@ impl SfxMgr {
}
}
fn load_sfx_items() -> SfxTriggers {
match assets::load_file("voxygen.audio.sfx", &["ron"]) {
Ok(file) => match ron::de::from_reader(file) {
Ok(config) => config,
Err(error) => {
warn!(
"Error parsing sfx config file, sfx will not be available: {}",
format!("{:#?}", error)
);
SfxTriggers::default()
},
},
Err(error) => {
warn!(
"Error reading sfx config file, sfx will not be available: {}",
format!("{:#?}", error)
);
SfxTriggers::default()
},
}
fn load_sfx_items() -> AssetHandle<SfxTriggers> {
// Cannot fail: A default value is always provided
SfxTriggers::load_expect("voxygen.audio.sfx")
}
}
impl assets::Asset for SfxTriggers {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
fn default_value(_: &str, error: assets::Error) -> Result<Self, assets::Error> {
warn!(
"Error reading sfx config file, sfx will not be available: {:#?}",
error
);
Ok(SfxTriggers::default())
}
}

View File

@ -1,63 +1,84 @@
//! Handles caching and retrieval of decoded `.wav` sfx sound data, eliminating
//! the need to decode files on each playback
use common::assets;
use hashbrown::HashMap;
use std::{convert::AsRef, io, io::Read, sync::Arc};
use std::{borrow::Cow, io, sync::Arc};
use tracing::warn;
// Implementation of sound taken from this github issue:
// https://github.com/RustAudio/rodio/issues/141
pub struct Sound(Arc<Vec<u8>>);
#[derive(Clone)]
pub struct WavSound(Arc<Vec<u8>>);
impl AsRef<[u8]> for Sound {
impl AsRef<[u8]> for WavSound {
fn as_ref(&self) -> &[u8] { &self.0 }
}
pub struct SoundLoader;
impl assets::Loader<WavSound> for SoundLoader {
fn load(content: Cow<[u8]>, _: &str) -> Result<WavSound, assets::BoxedError> {
let arc = Arc::new(content.into_owned());
Ok(WavSound(arc))
}
}
impl assets::Asset for WavSound {
type Loader = SoundLoader;
const EXTENSION: &'static str = "wav";
fn default_value(specifier: &str, error: assets::Error) -> Result<Self, assets::Error> {
warn!(?specifier, ?error, "Failed to load sound");
Ok(WavSound::empty())
}
}
/// Wrapper for decoded audio data
impl Sound {
pub fn load(filename: &str) -> Result<Sound, assets::Error> {
let mut file = assets::load_file(filename, &["wav"])?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Ok(Sound(Arc::new(buf)))
impl WavSound {
pub fn decoder(self) -> rodio::Decoder<io::Cursor<WavSound>> {
let cursor = io::Cursor::new(self);
rodio::Decoder::new(cursor).unwrap()
}
pub fn cursor(&self) -> io::Cursor<Sound> { io::Cursor::new(Sound(Arc::clone(&self.0))) }
pub fn decoder(&self) -> rodio::Decoder<io::Cursor<Sound>> {
rodio::Decoder::new(self.cursor()).unwrap()
}
/// Returns a `Sound` containing empty .wav data. This intentionally doesn't
/// load from the filesystem so we have a reliable fallback when there
/// is a failure to read a file.
/// Returns a `WavSound` containing empty .wav data. This intentionally
/// doesn't load from the filesystem so we have a reliable fallback when
/// there is a failure to read a file.
///
/// The data below is the result of passing a very short, silent .wav file
/// to `Sound::load()`.
pub fn empty() -> Sound {
Sound(Arc::new(vec![
pub fn empty() -> WavSound {
WavSound(Arc::new(vec![
82, 73, 70, 70, 40, 0, 0, 0, 87, 65, 86, 69, 102, 109, 116, 32, 16, 0, 0, 0, 1, 0, 1,
0, 68, 172, 0, 0, 136, 88, 1, 0, 2, 0, 16, 0, 100, 97, 116, 97, 4, 0, 0, 0, 0, 0, 0, 0,
]))
}
}
#[derive(Default)]
pub struct SoundCache {
sounds: HashMap<String, Sound>,
#[derive(Clone)]
pub struct OggSound(Arc<Vec<u8>>);
impl AsRef<[u8]> for OggSound {
fn as_ref(&self) -> &[u8] { &self.0 }
}
impl SoundCache {
pub fn load_sound(&mut self, name: &str) -> rodio::Decoder<io::Cursor<Sound>> {
self.sounds
.entry(name.to_string())
.or_insert_with(|| {
Sound::load(name).unwrap_or_else(|_| {
warn!(?name, "SoundCache: Failed to load sound");
Sound::empty()
})
})
.decoder()
impl assets::Loader<OggSound> for SoundLoader {
fn load(content: Cow<[u8]>, _: &str) -> Result<OggSound, assets::BoxedError> {
let arc = Arc::new(content.into_owned());
Ok(OggSound(arc))
}
}
impl assets::Asset for OggSound {
type Loader = SoundLoader;
const EXTENSION: &'static str = "ogg";
}
/// Wrapper for decoded audio data
impl OggSound {
pub fn decoder(self) -> rodio::Decoder<io::Cursor<OggSound>> {
let cursor = io::Cursor::new(self);
rodio::Decoder::new(cursor).unwrap()
}
}

View File

@ -1,6 +1,6 @@
use crate::ui::{Graphic, SampleStrat, Transform, Ui};
use common::{
assets::{self, watch::ReloadIndicator, Asset},
assets::{self, AssetExt, AssetHandle, DotVoxAsset},
comp::item::{
armor::{Armor, ArmorKind},
Glider, ItemDesc, ItemKind, Lantern, Throwable, Utility,
@ -8,11 +8,10 @@ use common::{
figure::Segment,
};
use conrod_core::image::Id;
use dot_vox::DotVoxData;
use hashbrown::HashMap;
use image::DynamicImage;
use serde::{Deserialize, Serialize};
use std::{fs::File, io::BufReader, sync::Arc};
use std::sync::Arc;
use tracing::{error, warn};
use vek::*;
@ -84,37 +83,35 @@ impl ImageSpec {
}
#[derive(Serialize, Deserialize)]
struct ItemImagesSpec(HashMap<ItemKey, ImageSpec>);
impl Asset for ItemImagesSpec {
const ENDINGS: &'static [&'static str] = &["ron"];
impl assets::Asset for ItemImagesSpec {
type Loader = assets::RonLoader;
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, assets::Error> {
ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error)
}
const EXTENSION: &'static str = "ron";
}
// TODO: when there are more images don't load them all into memory
pub struct ItemImgs {
map: HashMap<ItemKey, Id>,
indicator: ReloadIndicator,
manifest: AssetHandle<ItemImagesSpec>,
not_found: Id,
}
impl ItemImgs {
pub fn new(ui: &mut Ui, not_found: Id) -> Self {
let mut indicator = ReloadIndicator::new();
Self {
map: ItemImagesSpec::load_watched(
"voxygen.item_image_manifest",
&mut indicator,
)
.expect("Unable to load item image manifest")
let manifest = ItemImagesSpec::load_expect("voxygen.item_image_manifest");
let map = manifest
.read()
.0
.iter()
// TODO: what if multiple kinds map to the same image, it would be nice to use the same
// image id for both, although this does interfere with the current hot-reloading
// strategy
.map(|(kind, spec)| (kind.clone(), ui.add_graphic(spec.create_graphic())))
.collect(),
indicator,
.collect();
Self {
map,
manifest,
not_found,
}
}
@ -122,12 +119,8 @@ impl ItemImgs {
/// Checks if the manifest has been changed and reloads the images if so
/// Reuses img ids
pub fn reload_if_changed(&mut self, ui: &mut Ui) {
if self.indicator.reloaded() {
for (kind, spec) in ItemImagesSpec::load("voxygen.item_image_manifest")
.expect("Unable to load item image manifest")
.0
.iter()
{
if self.manifest.reloaded() {
for (kind, spec) in self.manifest.read().0.iter() {
// Load new graphic
let graphic = spec.create_graphic();
// See if we already have an id we can use
@ -163,30 +156,31 @@ impl ItemImgs {
// Copied from figure/load.rs
// TODO: remove code dup?
fn graceful_load_vox(specifier: &str) -> Arc<DotVoxData> {
fn graceful_load_vox(specifier: &str) -> AssetHandle<DotVoxAsset> {
let full_specifier: String = ["voxygen.", specifier].concat();
match DotVoxData::load(full_specifier.as_str()) {
match DotVoxAsset::load(full_specifier.as_str()) {
Ok(dot_vox) => dot_vox,
Err(_) => {
error!(?full_specifier, "Could not load vox file for item images",);
DotVoxData::load_expect("voxygen.voxel.not_found")
DotVoxAsset::load_expect("voxygen.voxel.not_found")
},
}
}
fn graceful_load_img(specifier: &str) -> Arc<DynamicImage> {
let full_specifier: String = ["voxygen.", specifier].concat();
match DynamicImage::load(full_specifier.as_str()) {
let handle = match assets::Image::load(&full_specifier) {
Ok(img) => img,
Err(_) => {
error!(?full_specifier, "Could not load image file for item images");
DynamicImage::load_expect("voxygen.element.not_found")
assets::Image::load_expect("voxygen.element.not_found")
},
}
};
handle.read().to_image()
}
fn graceful_load_segment_no_skin(specifier: &str) -> Arc<Segment> {
use common::figure::{mat_cell::MatCell, MatSegment};
let mat_seg = MatSegment::from(&*graceful_load_vox(specifier));
let mat_seg = MatSegment::from(&graceful_load_vox(specifier).read().0);
let seg = mat_seg
.map(|mat_cell| match mat_cell {
MatCell::None => None,

View File

@ -46,7 +46,7 @@ use spell::Spell;
use crate::{
ecs::{comp as vcomp, comp::HpFloaterList},
hud::img_ids::ImgsRot,
i18n::{i18n_asset_key, LanguageMetadata, Localization},
i18n::{LanguageMetadata, Localization},
render::{Consts, Globals, RenderMode, Renderer},
scene::camera::{self, Camera},
ui::{fonts::Fonts, img_ids::Rotations, slot, Graphic, Ingameable, ScaleMode, Ui},
@ -55,7 +55,6 @@ use crate::{
};
use client::Client;
use common::{
assets::Asset,
comp,
comp::{
item::{ItemDesc, Quality},
@ -627,7 +626,6 @@ pub struct Hud {
tab_complete: Option<String>,
pulse: f32,
velocity: f32,
i18n: std::sync::Arc<Localization>,
slot_manager: slots::SlotManager,
hotbar: hotbar::State,
events: Vec<Event>,
@ -660,12 +658,9 @@ impl Hud {
let rot_imgs = ImgsRot::load(&mut ui).expect("Failed to load rot images!");
// Load item images.
let item_imgs = ItemImgs::new(&mut ui, imgs.not_found);
// Load language.
let i18n = Localization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
// Load fonts.
let fonts = Fonts::load(&i18n.fonts, &mut ui).expect("Impossible to load fonts!");
let fonts = Fonts::load(&global_state.i18n.read().fonts, &mut ui)
.expect("Impossible to load fonts!");
// Get the server name.
let server = &client.server_info().name;
// Get the id, unwrap is safe because this CANNOT be None at this
@ -726,7 +721,6 @@ impl Hud {
tab_complete: None,
pulse: 0.0,
velocity: 0.0,
i18n,
slot_manager,
hotbar: hotbar_state,
events: Vec::new(),
@ -734,10 +728,8 @@ impl Hud {
}
}
pub fn update_language(&mut self, i18n: std::sync::Arc<Localization>) {
self.i18n = i18n;
self.fonts =
Fonts::load(&self.i18n.fonts, &mut self.ui).expect("Impossible to load fonts!");
pub fn update_fonts(&mut self, i18n: &Localization) {
self.fonts = Fonts::load(&i18n.fonts, &mut self.ui).expect("Impossible to load fonts!");
}
#[allow(clippy::assign_op_pattern)] // TODO: Pending review in #587
@ -759,6 +751,7 @@ impl Hud {
// FPS
let fps = global_state.clock.stats().average_tps;
let version = common::util::DISPLAY_VERSION_LONG.clone();
let i18n = &*global_state.i18n.read();
if self.show.ingame {
let ecs = client.state().ecs();
@ -1267,7 +1260,7 @@ impl Hud {
in_group,
&global_state.settings.gameplay,
self.pulse,
&self.i18n,
i18n,
&self.imgs,
&self.fonts,
)
@ -1470,8 +1463,8 @@ impl Hud {
Intro::Show => {
if self.pulse > 20.0 {
self.show.want_grab = false;
let quest_headline = &self.i18n.get("hud.temp_quest_headline");
let quest_text = &self.i18n.get("hud.temp_quest_text");
let quest_headline = &i18n.get("hud.temp_quest_headline");
let quest_text = &i18n.get("hud.temp_quest_text");
Image::new(self.imgs.quest_bg)
.w_h(404.0, 858.0)
.middle_of(ui_widgets.window)
@ -1508,7 +1501,7 @@ impl Hud {
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.mid_bottom_with_margin_on(self.ids.q_text_bg, -120.0)
.label(&self.i18n.get("common.accept"))
.label(&i18n.get("common.accept"))
.label_font_id(self.fonts.cyri.conrod_id)
.label_font_size(self.fonts.cyri.scale(22))
.label_color(TEXT_COLOR)
@ -1685,8 +1678,7 @@ impl Hud {
// Help Window
if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) {
Text::new(
&self
.i18n
&i18n
.get("hud.press_key_to_toggle_keybindings_fmt")
.replace("{key}", help_key.to_string().as_str()),
)
@ -1703,8 +1695,7 @@ impl Hud {
.get_binding(GameInput::ToggleDebug)
{
Text::new(
&self
.i18n
&i18n
.get("hud.press_key_to_toggle_debug_info_fmt")
.replace("{key}", toggle_debug_key.to_string().as_str()),
)
@ -1718,8 +1709,7 @@ impl Hud {
// Help Window
if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) {
Text::new(
&self
.i18n
&i18n
.get("hud.press_key_to_show_keybindings_fmt")
.replace("{key}", help_key.to_string().as_str()),
)
@ -1736,8 +1726,7 @@ impl Hud {
.get_binding(GameInput::ToggleDebug)
{
Text::new(
&self
.i18n
&i18n
.get("hud.press_key_to_show_debug_info_fmt")
.replace("{key}", toggle_debug_key.to_string().as_str()),
)
@ -1754,8 +1743,7 @@ impl Hud {
.get_binding(GameInput::ToggleLantern)
{
Text::new(
&self
.i18n
&i18n
.get("hud.press_key_to_toggle_lantern_fmt")
.replace("{key}", toggle_lantern_key.to_string().as_str()),
)
@ -1800,7 +1788,7 @@ impl Hud {
global_state,
&self.rot_imgs,
tooltip_manager,
&self.i18n,
i18n,
&player_stats,
)
.set(self.ids.buttons, ui_widgets)
@ -1822,7 +1810,7 @@ impl Hud {
&self.fonts,
&self.rot_imgs,
tooltip_manager,
&self.i18n,
i18n,
&player_buffs,
self.pulse,
&global_state,
@ -1842,7 +1830,7 @@ impl Hud {
&self.imgs,
&self.rot_imgs,
&self.fonts,
&self.i18n,
i18n,
self.pulse,
&global_state,
tooltip_manager,
@ -1859,7 +1847,7 @@ impl Hud {
}
// Popup (waypoint saved and similar notifications)
Popup::new(
&self.i18n,
i18n,
client,
&self.new_notifications,
&self.fonts,
@ -1895,7 +1883,7 @@ impl Hud {
tooltip_manager,
&mut self.slot_manager,
self.pulse,
&self.i18n,
i18n,
&player_stats,
&self.show,
)
@ -1963,7 +1951,7 @@ impl Hud {
&self.hotbar,
tooltip_manager,
&mut self.slot_manager,
&self.i18n,
i18n,
&self.show,
&ability_map,
)
@ -1978,7 +1966,7 @@ impl Hud {
client,
&self.imgs,
&self.fonts,
&self.i18n,
i18n,
&self.rot_imgs,
tooltip_manager,
&self.item_imgs,
@ -2017,7 +2005,7 @@ impl Hud {
global_state,
&self.imgs,
&self.fonts,
&self.i18n,
i18n,
)
.and_then(self.force_chat_input.take(), |c, input| c.input(input))
.and_then(self.tab_complete.take(), |c, input| {
@ -2054,7 +2042,7 @@ impl Hud {
&self.show,
&self.imgs,
&self.fonts,
&self.i18n,
i18n,
fps as f32,
)
.set(self.ids.settings_window, ui_widgets)
@ -2210,7 +2198,7 @@ impl Hud {
client,
&self.imgs,
&self.fonts,
&self.i18n,
i18n,
info.selected_entity,
&self.rot_imgs,
tooltip_manager,
@ -2238,7 +2226,7 @@ impl Hud {
// Spellbook
if self.show.spell {
match Spell::new(&self.show, client, &self.imgs, &self.fonts, &self.i18n)
match Spell::new(&self.show, client, &self.imgs, &self.fonts, i18n)
.set(self.ids.spell, ui_widgets)
{
Some(spell::Event::Close) => {
@ -2258,7 +2246,7 @@ impl Hud {
&self.world_map,
&self.fonts,
self.pulse,
&self.i18n,
i18n,
&global_state,
tooltip_manager,
)
@ -2302,9 +2290,7 @@ impl Hud {
}
if self.show.esc_menu {
match EscMenu::new(&self.imgs, &self.fonts, &self.i18n)
.set(self.ids.esc_menu, ui_widgets)
{
match EscMenu::new(&self.imgs, &self.fonts, i18n).set(self.ids.esc_menu, ui_widgets) {
Some(esc_menu::Event::OpenSettings(tab)) => {
self.show.open_setting_tab(tab);
},
@ -2344,8 +2330,7 @@ impl Hud {
{
if self.show.free_look {
Text::new(
&self
.i18n
&i18n
.get("hud.free_look_indicator")
.replace("{key}", freelook_key.to_string().as_str()),
)
@ -2355,8 +2340,7 @@ impl Hud {
.font_size(self.fonts.cyri.scale(20))
.set(self.ids.free_look_bg, ui_widgets);
Text::new(
&self
.i18n
&i18n
.get("hud.free_look_indicator")
.replace("{key}", freelook_key.to_string().as_str()),
)
@ -2370,13 +2354,13 @@ impl Hud {
// Auto walk indicator
if self.show.auto_walk {
Text::new(&self.i18n.get("hud.auto_walk_indicator"))
Text::new(i18n.get("hud.auto_walk_indicator"))
.color(TEXT_BG)
.mid_top_with_margin_on(ui_widgets.window, 70.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(20))
.set(self.ids.auto_walk_bg, ui_widgets);
Text::new(&self.i18n.get("hud.auto_walk_indicator"))
Text::new(i18n.get("hud.auto_walk_indicator"))
.color(KILL_COLOR)
.top_left_with_margins_on(self.ids.auto_walk_bg, -1.0, -1.0)
.font_id(self.fonts.cyri.conrod_id)

View File

@ -1,9 +1,8 @@
use common::assets::{self, Asset};
use common::assets::{self, AssetExt};
use deunicode::deunicode;
use hashbrown::{HashMap, HashSet};
use ron::de::from_reader;
use serde::{Deserialize, Serialize};
use std::{fs::File, io::BufReader};
use std::borrow::Cow;
use tracing::warn;
/// The reference language, aka the more up-to-date localization data.
@ -44,7 +43,7 @@ impl Font {
pub type Fonts = HashMap<String, Font>;
/// Store internationalization data
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Localization {
/// A map storing the localized texts
///
@ -92,9 +91,9 @@ impl Localization {
}
/// Return the missing keys compared to the reference language
pub fn list_missing_entries(&self) -> (HashSet<String>, HashSet<String>) {
fn list_missing_entries(&self) -> (HashSet<String>, HashSet<String>) {
let reference_localization =
Localization::load_expect(i18n_asset_key(REFERENCE_LANG).as_ref());
Localization::load_expect(&i18n_asset_key(REFERENCE_LANG)).read();
let reference_string_keys: HashSet<_> =
reference_localization.string_map.keys().cloned().collect();
@ -133,15 +132,16 @@ impl Localization {
}
}
impl Asset for Localization {
const ENDINGS: &'static [&'static str] = &["ron"];
impl assets::Asset for Localization {
type Loader = LocalizationLoader;
/// Load the translations located in the input buffer and convert them
/// into a `Localization` object.
#[allow(clippy::into_iter_on_ref)] // TODO: Pending review in #587
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, assets::Error> {
let mut asked_localization: Localization =
from_reader(buf_reader).map_err(assets::Error::parse_error)?;
const EXTENSION: &'static str = "ron";
}
pub struct LocalizationLoader;
impl assets::Loader<Localization> for LocalizationLoader {
fn load(content: Cow<[u8]>, ext: &str) -> Result<Localization, assets::BoxedError> {
let mut asked_localization: Localization = assets::RonLoader::load(content, ext)?;
// Update the text if UTF-8 to ASCII conversion is enabled
if asked_localization.convert_utf8_to_ascii {
@ -150,7 +150,7 @@ impl Asset for Localization {
}
for value in asked_localization.vector_map.values_mut() {
*value = value.into_iter().map(|s| deunicode(s)).collect();
*value = value.iter().map(|s| deunicode(s)).collect();
}
}
asked_localization.metadata.language_name =
@ -162,9 +162,11 @@ impl Asset for Localization {
/// Load all the available languages located in the voxygen asset directory
pub fn list_localizations() -> Vec<LanguageMetadata> {
let voxygen_locales_assets = "voxygen.i18n.*";
let lang_list = Localization::load_glob(voxygen_locales_assets).unwrap();
lang_list.iter().map(|e| (*e).metadata.clone()).collect()
assets::load_dir::<Localization>("voxygen.i18n")
.unwrap()
.iter_all()
.filter_map(|(_, lang)| lang.ok().map(|e| e.read().metadata.clone()))
.collect()
}
/// Return the asset associated with the language_id

View File

@ -34,12 +34,13 @@ pub use crate::error::Error;
use crate::singleplayer::Singleplayer;
use crate::{
audio::AudioFrontend,
i18n::Localization,
profile::Profile,
render::Renderer,
settings::Settings,
window::{Event, Window},
};
use common::{assets::watch, clock::Clock, span};
use common::{assets::AssetHandle, clock::Clock, span};
/// A type used to store state that is shared between all play states.
pub struct GlobalState {
@ -52,7 +53,7 @@ pub struct GlobalState {
#[cfg(feature = "singleplayer")]
pub singleplayer: Option<Singleplayer>,
// TODO: redo this so that the watcher doesn't have to exist for reloading to occur
pub localization_watcher: watch::ReloadIndicator,
pub i18n: AssetHandle<Localization>,
pub clipboard: Option<iced_winit::Clipboard>,
}

View File

@ -15,7 +15,7 @@ use veloren_voxygen::{
};
use common::{
assets::{watch, Asset},
assets::{self, AssetExt},
clock::Clock,
};
use std::panic;
@ -138,6 +138,8 @@ fn main() {
default_hook(panic_info);
}));
assets::start_hot_reloading();
// Initialise watcher for animation hotreloading
#[cfg(feature = "hot-anim")]
anim::init();
@ -155,26 +157,18 @@ fn main() {
// Load the profile.
let profile = Profile::load();
let mut localization_watcher = watch::ReloadIndicator::new();
let localized_strings = Localization::load_watched(
&i18n_asset_key(&settings.language.selected_language),
&mut localization_watcher,
)
.unwrap_or_else(|error| {
let selected_language = &settings.language.selected_language;
warn!(
?error,
?selected_language,
"Impossible to load language: change to the default language (English) instead.",
);
settings.language.selected_language = i18n::REFERENCE_LANG.to_owned();
Localization::load_watched(
&i18n_asset_key(&settings.language.selected_language),
&mut localization_watcher,
)
.unwrap()
});
localized_strings.log_missing_entries();
let i18n = Localization::load(&i18n_asset_key(&settings.language.selected_language))
.unwrap_or_else(|error| {
let selected_language = &settings.language.selected_language;
warn!(
?error,
?selected_language,
"Impossible to load language: change to the default language (English) instead.",
);
settings.language.selected_language = i18n::REFERENCE_LANG.to_owned();
Localization::load_expect(&i18n_asset_key(&settings.language.selected_language))
});
i18n.read().log_missing_entries();
// Create window
let (window, event_loop) = Window::new(&settings).expect("Failed to create window!");
@ -192,7 +186,7 @@ fn main() {
info_message: None,
#[cfg(feature = "singleplayer")]
singleplayer: None,
localization_watcher,
i18n,
clipboard,
};

View File

@ -1,7 +1,6 @@
mod ui;
use crate::{
i18n::{i18n_asset_key, Localization},
render::Renderer,
scene::simple::{self as scene, Scene},
session::SessionState,
@ -10,7 +9,7 @@ use crate::{
Direction, GlobalState, PlayState, PlayStateResult,
};
use client::{self, Client};
use common::{assets::Asset, comp, resources::DeltaTime, span};
use common::{comp, resources::DeltaTime, span};
use specs::WorldExt;
use std::{cell::RefCell, rc::Rc};
use tracing::error;
@ -64,11 +63,7 @@ impl PlayState for CharSelectionState {
self.client.borrow_mut().load_character_list();
// Updated localization in case the selected language was changed
let localized_strings = crate::i18n::Localization::load_expect(
&crate::i18n::i18n_asset_key(&global_state.settings.language.selected_language),
);
self.char_selection_ui
.update_language(std::sync::Arc::clone(&localized_strings));
self.char_selection_ui.update_language(global_state.i18n);
// Set scale mode in case it was change
self.char_selection_ui
.set_scale_mode(global_state.settings.gameplay.ui_scale);
@ -167,9 +162,7 @@ impl PlayState for CharSelectionState {
}
// Tick the client (currently only to keep the connection alive).
let localized_strings = Localization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
let localized_strings = &*global_state.i18n.read();
match self.client.borrow_mut().tick(
comp::ControllerInputs::default(),

View File

@ -1,5 +1,5 @@
use crate::{
i18n::{i18n_asset_key, Localization},
i18n::Localization,
render::Renderer,
ui::{
self,
@ -22,7 +22,7 @@ use crate::{
};
use client::Client;
use common::{
assets::Asset,
assets::AssetHandle,
character::{CharacterId, CharacterItem, MAX_CHARACTERS_PER_PLAYER},
comp::{self, humanoid, item::tool::AbilityMap},
LoadoutBuilder,
@ -220,7 +220,6 @@ enum InfoContent {
struct Controls {
fonts: Fonts,
imgs: Imgs,
i18n: std::sync::Arc<Localization>,
// Voxygen version
version: String,
// Alpha disclaimer
@ -264,19 +263,13 @@ enum Message {
}
impl Controls {
fn new(
fonts: Fonts,
imgs: Imgs,
i18n: std::sync::Arc<Localization>,
selected: Option<CharacterId>,
) -> Self {
fn new(fonts: Fonts, imgs: Imgs, selected: Option<CharacterId>) -> Self {
let version = common::util::DISPLAY_VERSION_LONG.clone();
let alpha = format!("Veloren {}", common::util::DISPLAY_VERSION.as_str());
Self {
fonts,
imgs,
i18n,
version,
alpha,
@ -287,12 +280,13 @@ impl Controls {
}
}
fn view(
&mut self,
fn view<'a>(
&'a mut self,
_settings: &Settings,
client: &Client,
error: &Option<String>,
) -> Element<Message> {
i18n: &'a Localization,
) -> Element<'a, Message> {
// TODO: use font scale thing for text size (use on button size for buttons with
// text)
@ -301,7 +295,6 @@ impl Controls {
let imgs = &self.imgs;
let fonts = &self.fonts;
let i18n = &self.i18n;
let tooltip_manager = &self.tooltip_manager;
let button_style = style::button::Style::new(imgs.button)
@ -1298,7 +1291,7 @@ impl Controls {
body.skin = rng.gen_range(0, species.num_skin_colors());
body.eye_color = rng.gen_range(0, species.num_eye_colors());
body.eyes = rng.gen_range(0, species.num_eyes(body_type));
*name = npc::get_npc_name(npc::NpcKind::Humanoid).to_string();
*name = npc::get_npc_name(npc::NpcKind::Humanoid);
}
},
Message::ConfirmDeletion => {
@ -1402,20 +1395,10 @@ impl CharSelectionUi {
let selected_character = global_state.profile.get_selected_character(server_name);
// Load language
let i18n = Localization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
let i18n = global_state.i18n.read();
// TODO: don't add default font twice
let font = {
use std::io::Read;
let mut buf = Vec::new();
common::assets::load_file(&i18n.fonts.get("cyri").unwrap().asset_key, &["ttf"])
.unwrap()
.read_to_end(&mut buf)
.unwrap();
ui::ice::Font::try_from_vec(buf).unwrap()
};
let font = ui::ice::load_font(&i18n.fonts.get("cyri").unwrap().asset_key);
let mut ui = Ui::new(
&mut global_state.window,
@ -1429,7 +1412,6 @@ impl CharSelectionUi {
let controls = Controls::new(
fonts,
Imgs::load(&mut ui).expect("Failed to load images"),
i18n,
selected_character,
);
@ -1476,20 +1458,13 @@ impl CharSelectionUi {
}
}
pub fn update_language(&mut self, i18n: std::sync::Arc<Localization>) {
let font = {
use std::io::Read;
let mut buf = Vec::new();
common::assets::load_file(&i18n.fonts.get("cyri").unwrap().asset_key, &["ttf"])
.unwrap()
.read_to_end(&mut buf)
.unwrap();
ui::ice::Font::try_from_vec(buf).unwrap()
};
self.controls.i18n = i18n;
pub fn update_language(&mut self, i18n: AssetHandle<Localization>) {
let i18n = i18n.read();
let font = ui::ice::load_font(&i18n.fonts.get("cyri").unwrap().asset_key);
self.ui.clear_fonts(font);
self.controls.fonts = Fonts::load(&self.controls.i18n.fonts, &mut self.ui)
.expect("Impossible to load fonts!");
self.controls.fonts =
Fonts::load(&i18n.fonts, &mut self.ui).expect("Impossible to load fonts!");
}
pub fn set_scale_mode(&mut self, scale_mode: ui::ScaleMode) {
@ -1503,10 +1478,11 @@ impl CharSelectionUi {
// TODO: do we need whole client here or just character list?
pub fn maintain(&mut self, global_state: &mut GlobalState, client: &Client) -> Vec<Event> {
let mut events = Vec::new();
let i18n = global_state.i18n.read();
let (mut messages, _) = self.ui.maintain(
self.controls
.view(&global_state.settings, &client, &self.error),
.view(&global_state.settings, &client, &self.error, &i18n),
global_state.window.renderer_mut(),
global_state.clipboard.as_ref(),
);

View File

@ -12,7 +12,7 @@ use crate::{
Direction, GlobalState, PlayState, PlayStateResult,
};
use client_init::{ClientInit, Error as InitError, Msg as InitMsg};
use common::{assets::Asset, comp, span};
use common::{assets::AssetExt, comp, span};
use tracing::error;
use ui::{Event as MainMenuEvent, MainMenuUi};
@ -48,13 +48,8 @@ impl PlayState for MainMenuState {
}
// Updated localization in case the selected language was changed
let localized_strings = crate::i18n::Localization::load_expect(
&crate::i18n::i18n_asset_key(&global_state.settings.language.selected_language),
);
self.main_menu_ui.update_language(
std::sync::Arc::clone(&localized_strings),
&global_state.settings,
);
self.main_menu_ui
.update_language(global_state.i18n, &global_state.settings);
// Set scale mode in case it was change
self.main_menu_ui
.set_scale_mode(global_state.settings.gameplay.ui_scale);
@ -63,9 +58,6 @@ impl PlayState for MainMenuState {
#[allow(clippy::single_match)] // TODO: remove when event match has multiple arms
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
span!(_guard, "tick", "<MainMenuState as PlayState>::tick");
let mut localized_strings = crate::i18n::Localization::load_expect(
&crate::i18n::i18n_asset_key(&global_state.settings.language.selected_language),
);
// Poll server creation
#[cfg(feature = "singleplayer")]
@ -121,6 +113,7 @@ impl PlayState for MainMenuState {
)));
},
Some(InitMsg::Done(Err(err))) => {
let localized_strings = global_state.i18n.read();
self.client_init = None;
global_state.info_message = Some({
let err = match err {
@ -268,14 +261,12 @@ impl PlayState for MainMenuState {
MainMenuEvent::ChangeLanguage(new_language) => {
global_state.settings.language.selected_language =
new_language.language_identifier;
localized_strings = Localization::load_expect(&i18n_asset_key(
global_state.i18n = Localization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
localized_strings.log_missing_entries();
self.main_menu_ui.update_language(
std::sync::Arc::clone(&localized_strings),
&global_state.settings,
);
global_state.i18n.read().log_missing_entries();
self.main_menu_ui
.update_language(global_state.i18n, &global_state.settings);
},
#[cfg(feature = "singleplayer")]
MainMenuEvent::StartSingleplayer => {

View File

@ -5,12 +5,12 @@ mod login;
mod servers;
use crate::{
i18n::{i18n_asset_key, LanguageMetadata, Localization},
i18n::{LanguageMetadata, Localization},
render::Renderer,
ui::{
self,
fonts::IcedFonts as Fonts,
ice::{style, widget, Element, Font, IcedUi as Ui},
ice::{load_font, style, widget, Element, IcedUi as Ui},
img_ids::{ImageGraphic, VoxelGraphic},
Graphic,
},
@ -19,8 +19,7 @@ use crate::{
use iced::{text_input, Column, Container, HorizontalAlignment, Length, Row, Space};
//ImageFrame, Tooltip,
use crate::settings::Settings;
use common::assets::Asset;
use image::DynamicImage;
use common::assets::{self, AssetExt, AssetHandle};
use rand::{seq::SliceRandom, thread_rng};
use std::time::Duration;
@ -132,7 +131,7 @@ struct Controls {
fonts: Fonts,
imgs: Imgs,
bg_img: widget::image::Handle,
i18n: std::sync::Arc<Localization>,
i18n: AssetHandle<Localization>,
// Voxygen version
version: String,
// Alpha disclaimer
@ -177,7 +176,7 @@ impl Controls {
fonts: Fonts,
imgs: Imgs,
bg_img: widget::image::Handle,
i18n: std::sync::Arc<Localization>,
i18n: AssetHandle<Localization>,
settings: &Settings,
) -> Self {
let version = common::util::DISPLAY_VERSION_LONG.clone();
@ -281,7 +280,7 @@ impl Controls {
&self.imgs,
&self.login_info,
error.as_deref(),
&self.i18n,
&self.i18n.read(),
self.is_selecting_language,
self.selected_language_index,
&language_metadatas,
@ -293,7 +292,7 @@ impl Controls {
&self.imgs,
&settings.networking.servers,
self.selected_server_index,
&self.i18n,
&self.i18n.read(),
button_style,
),
Screen::Connecting {
@ -304,7 +303,7 @@ impl Controls {
&self.imgs,
&connection_state,
self.time,
&self.i18n,
&self.i18n.read(),
button_style,
settings.gameplay.loading_tips,
),
@ -482,20 +481,9 @@ pub struct MainMenuUi {
impl<'a> MainMenuUi {
pub fn new(global_state: &mut GlobalState) -> Self {
// Load language
let i18n = Localization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
let i18n = &*global_state.i18n.read();
// TODO: don't add default font twice
let font = {
use std::io::Read;
let mut buf = Vec::new();
common::assets::load_file(&i18n.fonts.get("cyri").unwrap().asset_key, &["ttf"])
.unwrap()
.read_to_end(&mut buf)
.unwrap();
Font::try_from_vec(buf).unwrap()
};
let font = load_font(&i18n.fonts.get("cyri").unwrap().asset_key);
let mut ui = Ui::new(
&mut global_state.window,
@ -508,31 +496,25 @@ impl<'a> MainMenuUi {
let bg_img_spec = BG_IMGS.choose(&mut thread_rng()).unwrap();
let bg_img = assets::Image::load_expect(bg_img_spec).read().to_image();
let controls = Controls::new(
fonts,
Imgs::load(&mut ui).expect("Failed to load images"),
ui.add_graphic(Graphic::Image(DynamicImage::load_expect(bg_img_spec), None)),
i18n,
ui.add_graphic(Graphic::Image(bg_img, None)),
global_state.i18n,
&global_state.settings,
);
Self { ui, controls }
}
pub fn update_language(&mut self, i18n: std::sync::Arc<Localization>, settings: &Settings) {
let font = {
use std::io::Read;
let mut buf = Vec::new();
common::assets::load_file(&i18n.fonts.get("cyri").unwrap().asset_key, &["ttf"])
.unwrap()
.read_to_end(&mut buf)
.unwrap();
Font::try_from_vec(buf).unwrap()
};
pub fn update_language(&mut self, i18n: AssetHandle<Localization>, settings: &Settings) {
self.controls.i18n = i18n;
let i18n = &*i18n.read();
let font = load_font(&i18n.fonts.get("cyri").unwrap().asset_key);
self.ui.clear_fonts(font);
self.controls.fonts = Fonts::load(&self.controls.i18n.fonts, &mut self.ui)
.expect("Impossible to load fonts!");
self.controls.fonts =
Fonts::load(&i18n.fonts, &mut self.ui).expect("Impossible to load fonts!");
let language_metadatas = crate::i18n::list_localizations();
self.controls.selected_language_index = language_metadatas
.iter()

View File

@ -13,7 +13,7 @@ use super::{
ShadowMapMode, ShadowMode, WrapMode,
};
use common::{
assets::{self, watch::ReloadIndicator, Asset},
assets::{self, AssetExt, AssetHandle},
span,
};
use core::convert::TryFrom;
@ -24,11 +24,6 @@ use gfx::{
traits::{Device, Factory, FactoryExt},
};
use glsl_include::Context as IncludeContext;
use image::DynamicImage;
use std::{
fs::File,
io::{BufReader, Read},
};
use tracing::{error, warn};
use vek::*;
@ -104,17 +99,126 @@ pub type ColLightInfo = (
);
/// Load from a GLSL file.
pub struct Glsl;
pub struct Glsl(String);
impl Asset for Glsl {
type Output = String;
impl From<String> for Glsl {
fn from(s: String) -> Glsl { Glsl(s) }
}
const ENDINGS: &'static [&'static str] = &["glsl"];
impl assets::Asset for Glsl {
type Loader = assets::LoadFrom<String, assets::StringLoader>;
fn parse(mut buf_reader: BufReader<File>, _specifier: &str) -> Result<String, assets::Error> {
let mut string = String::new();
buf_reader.read_to_string(&mut string)?;
Ok(string)
const EXTENSION: &'static str = "glsl";
}
struct Shaders {
constants: AssetHandle<Glsl>,
globals: AssetHandle<Glsl>,
sky: AssetHandle<Glsl>,
light: AssetHandle<Glsl>,
srgb: AssetHandle<Glsl>,
random: AssetHandle<Glsl>,
lod: AssetHandle<Glsl>,
shadows: AssetHandle<Glsl>,
anti_alias_none: AssetHandle<Glsl>,
anti_alias_fxaa: AssetHandle<Glsl>,
anti_alias_msaa_x4: AssetHandle<Glsl>,
anti_alias_msaa_x8: AssetHandle<Glsl>,
anti_alias_msaa_x16: AssetHandle<Glsl>,
cloud_none: AssetHandle<Glsl>,
cloud_regular: AssetHandle<Glsl>,
figure_vert: AssetHandle<Glsl>,
terrain_point_shadow_vert: AssetHandle<Glsl>,
terrain_directed_shadow_vert: AssetHandle<Glsl>,
figure_directed_shadow_vert: AssetHandle<Glsl>,
directed_shadow_frag: AssetHandle<Glsl>,
skybox_vert: AssetHandle<Glsl>,
skybox_frag: AssetHandle<Glsl>,
figure_frag: AssetHandle<Glsl>,
terrain_vert: AssetHandle<Glsl>,
terrain_frag: AssetHandle<Glsl>,
fluid_vert: AssetHandle<Glsl>,
fluid_frag_cheap: AssetHandle<Glsl>,
fluid_frag_shiny: AssetHandle<Glsl>,
sprite_vert: AssetHandle<Glsl>,
sprite_frag: AssetHandle<Glsl>,
particle_vert: AssetHandle<Glsl>,
particle_frag: AssetHandle<Glsl>,
ui_vert: AssetHandle<Glsl>,
ui_frag: AssetHandle<Glsl>,
lod_terrain_vert: AssetHandle<Glsl>,
lod_terrain_frag: AssetHandle<Glsl>,
clouds_vert: AssetHandle<Glsl>,
clouds_frag: AssetHandle<Glsl>,
postprocess_vert: AssetHandle<Glsl>,
postprocess_frag: AssetHandle<Glsl>,
player_shadow_frag: AssetHandle<Glsl>,
light_shadows_geom: AssetHandle<Glsl>,
light_shadows_frag: AssetHandle<Glsl>,
}
impl assets::Compound for Shaders {
// TODO: Taking the specifier argument as a base for shaders specifiers
// would allow to use several shaders groups easily
fn load<S: assets::source::Source>(
_: &assets::AssetCache<S>,
_: &str,
) -> Result<Shaders, assets::Error> {
Ok(Shaders {
constants: AssetExt::load("voxygen.shaders.include.constants")?,
globals: AssetExt::load("voxygen.shaders.include.globals")?,
sky: AssetExt::load("voxygen.shaders.include.sky")?,
light: AssetExt::load("voxygen.shaders.include.light")?,
srgb: AssetExt::load("voxygen.shaders.include.srgb")?,
random: AssetExt::load("voxygen.shaders.include.random")?,
lod: AssetExt::load("voxygen.shaders.include.lod")?,
shadows: AssetExt::load("voxygen.shaders.include.shadows")?,
anti_alias_none: AssetExt::load("voxygen.shaders.antialias.none")?,
anti_alias_fxaa: AssetExt::load("voxygen.shaders.antialias.fxaa")?,
anti_alias_msaa_x4: AssetExt::load("voxygen.shaders.antialias.msaa-x4")?,
anti_alias_msaa_x8: AssetExt::load("voxygen.shaders.antialias.msaa-x8")?,
anti_alias_msaa_x16: AssetExt::load("voxygen.shaders.antialias.msaa-x16")?,
cloud_none: AssetExt::load("voxygen.shaders.include.cloud.none")?,
cloud_regular: AssetExt::load("voxygen.shaders.include.cloud.regular")?,
figure_vert: AssetExt::load("voxygen.shaders.figure-vert")?,
terrain_point_shadow_vert: AssetExt::load("voxygen.shaders.light-shadows-vert")?,
terrain_directed_shadow_vert: AssetExt::load(
"voxygen.shaders.light-shadows-directed-vert",
)?,
figure_directed_shadow_vert: AssetExt::load(
"voxygen.shaders.light-shadows-figure-vert",
)?,
directed_shadow_frag: AssetExt::load("voxygen.shaders.light-shadows-directed-frag")?,
skybox_vert: AssetExt::load("voxygen.shaders.skybox-vert")?,
skybox_frag: AssetExt::load("voxygen.shaders.skybox-frag")?,
figure_frag: AssetExt::load("voxygen.shaders.figure-frag")?,
terrain_vert: AssetExt::load("voxygen.shaders.terrain-vert")?,
terrain_frag: AssetExt::load("voxygen.shaders.terrain-frag")?,
fluid_vert: AssetExt::load("voxygen.shaders.fluid-vert")?,
fluid_frag_cheap: AssetExt::load("voxygen.shaders.fluid-frag.cheap")?,
fluid_frag_shiny: AssetExt::load("voxygen.shaders.fluid-frag.shiny")?,
sprite_vert: AssetExt::load("voxygen.shaders.sprite-vert")?,
sprite_frag: AssetExt::load("voxygen.shaders.sprite-frag")?,
particle_vert: AssetExt::load("voxygen.shaders.particle-vert")?,
particle_frag: AssetExt::load("voxygen.shaders.particle-frag")?,
ui_vert: AssetExt::load("voxygen.shaders.ui-vert")?,
ui_frag: AssetExt::load("voxygen.shaders.ui-frag")?,
lod_terrain_vert: AssetExt::load("voxygen.shaders.lod-terrain-vert")?,
lod_terrain_frag: AssetExt::load("voxygen.shaders.lod-terrain-frag")?,
clouds_vert: AssetExt::load("voxygen.shaders.clouds-vert")?,
clouds_frag: AssetExt::load("voxygen.shaders.clouds-frag")?,
postprocess_vert: AssetExt::load("voxygen.shaders.postprocess-vert")?,
postprocess_frag: AssetExt::load("voxygen.shaders.postprocess-frag")?,
player_shadow_frag: AssetExt::load("voxygen.shaders.player-shadow-frag")?,
light_shadows_geom: AssetExt::load("voxygen.shaders.light-shadows-geom")?,
light_shadows_frag: AssetExt::load("voxygen.shaders.light-shadows-frag")?,
})
}
}
@ -172,7 +276,7 @@ pub struct Renderer {
postprocess_pipeline: GfxPipeline<postprocess::pipe::Init<'static>>,
player_shadow_pipeline: GfxPipeline<figure::pipe::Init<'static>>,
shader_reload_indicator: ReloadIndicator,
shaders: AssetHandle<Shaders>,
noise_tex: Texture<(gfx::format::R8, gfx::format::Unorm)>,
@ -198,7 +302,6 @@ impl Renderer {
let dims = win_color_view.get_dimensions();
let mut shader_reload_indicator = ReloadIndicator::new();
let shadow_views = Self::create_shadow_views(
&mut factory,
(dims.0, dims.1),
@ -209,6 +312,8 @@ impl Renderer {
})
.ok();
let shaders = Shaders::load_expect("");
let (
skybox_pipeline,
figure_pipeline,
@ -224,12 +329,7 @@ impl Renderer {
point_shadow_pipeline,
terrain_directed_shadow_pipeline,
figure_directed_shadow_pipeline,
) = create_pipelines(
&mut factory,
&mode,
shadow_views.is_some(),
&mut shader_reload_indicator,
)?;
) = create_pipelines(&mut factory, &shaders.read(), &mode, shadow_views.is_some())?;
let (
tgt_color_view,
@ -285,7 +385,7 @@ impl Renderer {
let noise_tex = Texture::new(
&mut factory,
&DynamicImage::load_expect("voxygen.texture.noise"),
&assets::Image::load_expect("voxygen.texture.noise").read().0,
Some(gfx::texture::FilterMethod::Trilinear),
Some(gfx::texture::WrapMode::Tile),
None,
@ -323,7 +423,7 @@ impl Renderer {
postprocess_pipeline,
player_shadow_pipeline,
shader_reload_indicator,
shaders,
noise_tex,
@ -789,7 +889,7 @@ impl Renderer {
self.device.cleanup();
// If the shaders files were changed attempt to recreate the shaders
if self.shader_reload_indicator.reloaded() {
if self.shaders.reloaded() {
self.recreate_pipelines();
}
}
@ -798,9 +898,9 @@ impl Renderer {
fn recreate_pipelines(&mut self) {
match create_pipelines(
&mut self.factory,
&self.shaders.read(),
&self.mode,
self.shadow_map.is_some(),
&mut self.shader_reload_indicator,
) {
Ok((
skybox_pipeline,
@ -1771,9 +1871,9 @@ struct GfxPipeline<P: gfx::pso::PipelineInit> {
#[allow(clippy::type_complexity)] // TODO: Pending review in #587
fn create_pipelines(
factory: &mut gfx_backend::Factory,
shaders: &Shaders,
mode: &RenderMode,
has_shadow_views: bool,
shader_reload_indicator: &mut ReloadIndicator,
) -> Result<
(
GfxPipeline<skybox::pipe::Init<'static>>,
@ -1793,20 +1893,6 @@ fn create_pipelines(
),
RenderError,
> {
let constants =
Glsl::load_watched("voxygen.shaders.include.constants", shader_reload_indicator).unwrap();
let globals =
Glsl::load_watched("voxygen.shaders.include.globals", shader_reload_indicator).unwrap();
let sky = Glsl::load_watched("voxygen.shaders.include.sky", shader_reload_indicator).unwrap();
let light =
Glsl::load_watched("voxygen.shaders.include.light", shader_reload_indicator).unwrap();
let srgb = Glsl::load_watched("voxygen.shaders.include.srgb", shader_reload_indicator).unwrap();
let random =
Glsl::load_watched("voxygen.shaders.include.random", shader_reload_indicator).unwrap();
let lod = Glsl::load_watched("voxygen.shaders.include.lod", shader_reload_indicator).unwrap();
let shadows =
Glsl::load_watched("voxygen.shaders.include.shadows", shader_reload_indicator).unwrap();
// We dynamically add extra configuration settings to the constants file.
let constants = format!(
r#"
@ -1819,7 +1905,7 @@ fn create_pipelines(
#define SHADOW_MODE {}
"#,
constants,
shaders.constants.read().0,
// TODO: Configurable vertex/fragment shader preference.
"VOXYGEN_COMPUTATION_PREFERENCE_FRAGMENT",
match mode.fluid {
@ -1846,74 +1932,37 @@ fn create_pipelines(
},
);
let anti_alias = Glsl::load_watched(
&["voxygen.shaders.antialias.", match mode.aa {
AaMode::None => "none",
AaMode::Fxaa => "fxaa",
AaMode::MsaaX4 => "msaa-x4",
AaMode::MsaaX8 => "msaa-x8",
AaMode::MsaaX16 => "msaa-x16",
}]
.concat(),
shader_reload_indicator,
)
.unwrap();
let anti_alias = &match mode.aa {
AaMode::None => shaders.anti_alias_none,
AaMode::Fxaa => shaders.anti_alias_fxaa,
AaMode::MsaaX4 => shaders.anti_alias_msaa_x4,
AaMode::MsaaX8 => shaders.anti_alias_msaa_x8,
AaMode::MsaaX16 => shaders.anti_alias_msaa_x16,
};
let cloud = Glsl::load_watched(
&["voxygen.shaders.include.cloud.", match mode.cloud {
CloudMode::None => "none",
_ => "regular",
}]
.concat(),
shader_reload_indicator,
)
.unwrap();
let cloud = &match mode.cloud {
CloudMode::None => shaders.cloud_none,
_ => shaders.cloud_regular,
};
let mut include_ctx = IncludeContext::new();
include_ctx.include("constants.glsl", &constants);
include_ctx.include("globals.glsl", &globals);
include_ctx.include("shadows.glsl", &shadows);
include_ctx.include("sky.glsl", &sky);
include_ctx.include("light.glsl", &light);
include_ctx.include("srgb.glsl", &srgb);
include_ctx.include("random.glsl", &random);
include_ctx.include("lod.glsl", &lod);
include_ctx.include("anti-aliasing.glsl", &anti_alias);
include_ctx.include("cloud.glsl", &cloud);
let figure_vert =
Glsl::load_watched("voxygen.shaders.figure-vert", shader_reload_indicator).unwrap();
let terrain_point_shadow_vert = Glsl::load_watched(
"voxygen.shaders.light-shadows-vert",
shader_reload_indicator,
)
.unwrap();
let terrain_directed_shadow_vert = Glsl::load_watched(
"voxygen.shaders.light-shadows-directed-vert",
shader_reload_indicator,
)
.unwrap();
let figure_directed_shadow_vert = Glsl::load_watched(
"voxygen.shaders.light-shadows-figure-vert",
shader_reload_indicator,
)
.unwrap();
let directed_shadow_frag = Glsl::load_watched(
"voxygen.shaders.light-shadows-directed-frag",
shader_reload_indicator,
)
.unwrap();
include_ctx.include("globals.glsl", &shaders.globals.read().0);
include_ctx.include("shadows.glsl", &shaders.shadows.read().0);
include_ctx.include("sky.glsl", &shaders.sky.read().0);
include_ctx.include("light.glsl", &shaders.light.read().0);
include_ctx.include("srgb.glsl", &shaders.srgb.read().0);
include_ctx.include("random.glsl", &shaders.random.read().0);
include_ctx.include("lod.glsl", &shaders.lod.read().0);
include_ctx.include("anti-aliasing.glsl", &anti_alias.read().0);
include_ctx.include("cloud.glsl", &cloud.read().0);
// Construct a pipeline for rendering skyboxes
let skybox_pipeline = create_pipeline(
factory,
skybox::pipe::new(),
&Glsl::load_watched("voxygen.shaders.skybox-vert", shader_reload_indicator).unwrap(),
&Glsl::load_watched("voxygen.shaders.skybox-frag", shader_reload_indicator).unwrap(),
&shaders.skybox_vert.read().0,
&shaders.skybox_frag.read().0,
&include_ctx,
gfx::state::CullFace::Back,
)?;
@ -1922,8 +1971,8 @@ fn create_pipelines(
let figure_pipeline = create_pipeline(
factory,
figure::pipe::new(),
&figure_vert,
&Glsl::load_watched("voxygen.shaders.figure-frag", shader_reload_indicator).unwrap(),
&shaders.figure_vert.read().0,
&shaders.figure_frag.read().0,
&include_ctx,
gfx::state::CullFace::Back,
)?;
@ -1932,8 +1981,8 @@ fn create_pipelines(
let terrain_pipeline = create_pipeline(
factory,
terrain::pipe::new(),
&Glsl::load_watched("voxygen.shaders.terrain-vert", shader_reload_indicator).unwrap(),
&Glsl::load_watched("voxygen.shaders.terrain-frag", shader_reload_indicator).unwrap(),
&shaders.terrain_vert.read().0,
&shaders.terrain_frag.read().0,
&include_ctx,
gfx::state::CullFace::Back,
)?;
@ -1942,16 +1991,13 @@ fn create_pipelines(
let fluid_pipeline = create_pipeline(
factory,
fluid::pipe::new(),
&Glsl::load_watched("voxygen.shaders.fluid-vert", shader_reload_indicator).unwrap(),
&Glsl::load_watched(
&["voxygen.shaders.fluid-frag.", match mode.fluid {
FluidMode::Cheap => "cheap",
FluidMode::Shiny => "shiny",
}]
.concat(),
shader_reload_indicator,
)
.unwrap(),
&shaders.fluid_vert.read().0,
&match mode.fluid {
FluidMode::Cheap => shaders.fluid_frag_cheap,
FluidMode::Shiny => shaders.fluid_frag_shiny,
}
.read()
.0,
&include_ctx,
gfx::state::CullFace::Nothing,
)?;
@ -1960,8 +2006,8 @@ fn create_pipelines(
let sprite_pipeline = create_pipeline(
factory,
sprite::pipe::new(),
&Glsl::load_watched("voxygen.shaders.sprite-vert", shader_reload_indicator).unwrap(),
&Glsl::load_watched("voxygen.shaders.sprite-frag", shader_reload_indicator).unwrap(),
&shaders.sprite_vert.read().0,
&shaders.sprite_frag.read().0,
&include_ctx,
gfx::state::CullFace::Back,
)?;
@ -1970,8 +2016,8 @@ fn create_pipelines(
let particle_pipeline = create_pipeline(
factory,
particle::pipe::new(),
&Glsl::load_watched("voxygen.shaders.particle-vert", shader_reload_indicator).unwrap(),
&Glsl::load_watched("voxygen.shaders.particle-frag", shader_reload_indicator).unwrap(),
&shaders.particle_vert.read().0,
&shaders.particle_frag.read().0,
&include_ctx,
gfx::state::CullFace::Back,
)?;
@ -1980,8 +2026,8 @@ fn create_pipelines(
let ui_pipeline = create_pipeline(
factory,
ui::pipe::new(),
&Glsl::load_watched("voxygen.shaders.ui-vert", shader_reload_indicator).unwrap(),
&Glsl::load_watched("voxygen.shaders.ui-frag", shader_reload_indicator).unwrap(),
&shaders.ui_vert.read().0,
&shaders.ui_frag.read().0,
&include_ctx,
gfx::state::CullFace::Back,
)?;
@ -1990,8 +2036,8 @@ fn create_pipelines(
let lod_terrain_pipeline = create_pipeline(
factory,
lod_terrain::pipe::new(),
&Glsl::load_watched("voxygen.shaders.lod-terrain-vert", shader_reload_indicator).unwrap(),
&Glsl::load_watched("voxygen.shaders.lod-terrain-frag", shader_reload_indicator).unwrap(),
&shaders.lod_terrain_vert.read().0,
&shaders.lod_terrain_frag.read().0,
&include_ctx,
gfx::state::CullFace::Back,
)?;
@ -2000,8 +2046,8 @@ fn create_pipelines(
let clouds_pipeline = create_pipeline(
factory,
clouds::pipe::new(),
&Glsl::load_watched("voxygen.shaders.clouds-vert", shader_reload_indicator).unwrap(),
&Glsl::load_watched("voxygen.shaders.clouds-frag", shader_reload_indicator).unwrap(),
&shaders.clouds_vert.read().0,
&shaders.clouds_frag.read().0,
&include_ctx,
gfx::state::CullFace::Back,
)?;
@ -2010,8 +2056,8 @@ fn create_pipelines(
let postprocess_pipeline = create_pipeline(
factory,
postprocess::pipe::new(),
&Glsl::load_watched("voxygen.shaders.postprocess-vert", shader_reload_indicator).unwrap(),
&Glsl::load_watched("voxygen.shaders.postprocess-frag", shader_reload_indicator).unwrap(),
&shaders.postprocess_vert.read().0,
&shaders.postprocess_frag.read().0,
&include_ctx,
gfx::state::CullFace::Back,
)?;
@ -2028,12 +2074,8 @@ fn create_pipelines(
),*/),
..figure::pipe::new()
},
&figure_vert,
&Glsl::load_watched(
"voxygen.shaders.player-shadow-frag",
shader_reload_indicator,
)
.unwrap(),
&shaders.figure_vert.read().0,
&shaders.player_shadow_frag.read().0,
&include_ctx,
gfx::state::CullFace::Back,
)?;
@ -2042,19 +2084,9 @@ fn create_pipelines(
let point_shadow_pipeline = match create_shadow_pipeline(
factory,
shadow::pipe::new(),
&terrain_point_shadow_vert,
Some(
&Glsl::load_watched(
"voxygen.shaders.light-shadows-geom",
shader_reload_indicator,
)
.unwrap(),
),
&Glsl::load_watched(
"voxygen.shaders.light-shadows-frag",
shader_reload_indicator,
)
.unwrap(),
&shaders.terrain_point_shadow_vert.read().0,
Some(&shaders.light_shadows_geom.read().0),
&shaders.light_shadows_frag.read().0,
&include_ctx,
gfx::state::CullFace::Back,
None, // Some(gfx::state::Offset(2, 0))
@ -2070,9 +2102,9 @@ fn create_pipelines(
let terrain_directed_shadow_pipeline = match create_shadow_pipeline(
factory,
shadow::pipe::new(),
&terrain_directed_shadow_vert,
&shaders.terrain_directed_shadow_vert.read().0,
None,
&directed_shadow_frag,
&shaders.directed_shadow_frag.read().0,
&include_ctx,
gfx::state::CullFace::Back,
None, // Some(gfx::state::Offset(2, 1))
@ -2091,9 +2123,9 @@ fn create_pipelines(
let figure_directed_shadow_pipeline = match create_shadow_pipeline(
factory,
shadow::figure_pipe::new(),
&figure_directed_shadow_vert,
&shaders.figure_directed_shadow_vert.read().0,
None,
&directed_shadow_frag,
&shaders.directed_shadow_frag.read().0,
&include_ctx,
gfx::state::CullFace::Back,
None, // Some(gfx::state::Offset(2, 1))

View File

@ -8,7 +8,7 @@ use crate::{
};
use anim::Skeleton;
use common::{
assets::watch::ReloadIndicator,
assets::AssetHandle,
comp::{
item::{
armor::{Armor, ArmorKind},
@ -234,8 +234,7 @@ where
Skel::Body: BodySpec,
{
models: HashMap<FigureKey<Skel::Body>, ((FigureModelEntryFuture<LOD_COUNT>, Skel::Attr), u64)>,
manifests: Arc<<Skel::Body as BodySpec>::Spec>,
manifest_indicator: ReloadIndicator,
manifests: AssetHandle<<Skel::Body as BodySpec>::Spec>,
}
impl<Skel: Skeleton> FigureModelCache<Skel>
@ -244,15 +243,10 @@ where
{
#[allow(clippy::new_without_default)] // TODO: Pending review in #587
pub fn new() -> Self {
let mut manifest_indicator = ReloadIndicator::new();
Self {
models: HashMap::new(),
// NOTE: It might be better to bubble this error up rather than panicking.
manifests: Arc::new(
<Skel::Body as BodySpec>::load_watched(&mut manifest_indicator)
.expect("Could not load manifests for body type"),
),
manifest_indicator,
manifests: <Skel::Body as BodySpec>::load_spec().unwrap(),
}
}
@ -364,13 +358,13 @@ where
Entry::Vacant(v) => {
let key = v.key().clone();
let slot = Arc::new(atomic::AtomicCell::new(None));
let manifests = Arc::clone(&self.manifests);
let manifests = self.manifests;
let slot_ = Arc::clone(&slot);
thread_pool.execute(move || {
// First, load all the base vertex data.
let manifests = &*manifests;
let meshes = <Skel::Body as BodySpec>::bone_meshes(&key, &*manifests);
let manifests = &*manifests.read();
let meshes = <Skel::Body as BodySpec>::bone_meshes(&key, manifests);
// Then, set up meshing context.
let mut greedy = FigureModel::make_greedy();
@ -522,12 +516,9 @@ where
{
// Check for reloaded manifests
// TODO: maybe do this in a different function, maintain?
if self.manifest_indicator.reloaded() {
if self.manifests.reloaded() {
col_lights.atlas.clear();
self.models.clear();
if let Err(err) = <Skel::Body as BodySpec>::reload(Arc::make_mut(&mut self.manifests)) {
tracing::warn!(?err, "Hot reload failed.");
}
}
// TODO: Don't hard-code this.
if tick % 60 == 0 {

View File

@ -1,6 +1,6 @@
use super::cache::FigureKey;
use common::{
assets::{self, watch::ReloadIndicator, Asset, AssetWith, Ron},
assets::{self, AssetExt, AssetHandle, DotVoxAsset, Ron},
comp::{
biped_large::{self, BodyType as BLBodyType, Species as BLSpecies},
bird_medium::{self, BodyType as BMBodyType, Species as BMSpecies},
@ -18,10 +18,8 @@ use common::{
},
figure::{DynaUnionizer, MatSegment, Material, Segment},
};
use dot_vox::DotVoxData;
use hashbrown::HashMap;
use serde::Deserialize;
use std::sync::Arc;
use tracing::{error, warn};
use vek::*;
@ -29,29 +27,29 @@ pub type BoneMeshes = (Segment, Vec3<f32>);
fn load_segment(mesh_name: &str) -> Segment {
let full_specifier: String = ["voxygen.voxel.", mesh_name].concat();
Segment::from(DotVoxData::load_expect(full_specifier.as_str()).as_ref())
Segment::from(&DotVoxAsset::load_expect(&full_specifier).read().0)
}
fn graceful_load_vox(mesh_name: &str) -> Arc<DotVoxData> {
fn graceful_load_vox(mesh_name: &str) -> AssetHandle<DotVoxAsset> {
let full_specifier: String = ["voxygen.voxel.", mesh_name].concat();
match DotVoxData::load(full_specifier.as_str()) {
match DotVoxAsset::load(&full_specifier) {
Ok(dot_vox) => dot_vox,
Err(_) => {
error!(?full_specifier, "Could not load vox file for figure");
DotVoxData::load_expect("voxygen.voxel.not_found")
DotVoxAsset::load_expect("voxygen.voxel.not_found")
},
}
}
fn graceful_load_segment(mesh_name: &str) -> Segment {
Segment::from(graceful_load_vox(mesh_name).as_ref())
Segment::from(&graceful_load_vox(mesh_name).read().0)
}
fn graceful_load_segment_flipped(mesh_name: &str) -> Segment {
Segment::from_vox(graceful_load_vox(mesh_name).as_ref(), true)
Segment::from_vox(&graceful_load_vox(mesh_name).read().0, true)
}
fn graceful_load_mat_segment(mesh_name: &str) -> MatSegment {
MatSegment::from(graceful_load_vox(mesh_name).as_ref())
MatSegment::from(&graceful_load_vox(mesh_name).read().0)
}
fn graceful_load_mat_segment_flipped(mesh_name: &str) -> MatSegment {
MatSegment::from_vox(graceful_load_vox(mesh_name).as_ref(), true)
MatSegment::from_vox(&graceful_load_vox(mesh_name).read().0, true)
}
pub fn load_mesh(mesh_name: &str, position: Vec3<f32>) -> BoneMeshes {
@ -76,12 +74,8 @@ fn recolor_grey(rgb: Rgb<u8>, color: Rgb<u8>) -> Rgb<u8> {
pub trait BodySpec: Sized {
type Spec;
/// Initialize all the specifications for this Body and watch for changes.
fn load_watched(indicator: &mut ReloadIndicator) -> Result<Self::Spec, assets::Error>;
/// Reload all specifications for this Body (to be called if the reload
/// indicator is set).
fn reload(spec: &mut Self::Spec) -> Result<(), assets::Error>;
/// Initialize all the specifications for this Body.
fn load_spec() -> Result<AssetHandle<Self::Spec>, assets::Error>;
/// Mesh bones using the given spec, character state, and mesh generation
/// function.
@ -104,23 +98,23 @@ macro_rules! make_vox_spec {
) => {
#[derive(Clone)]
pub struct $Spec {
$( $field: AssetWith<Ron<$ty>, $asset_path>, )*
$( $field: AssetHandle<Ron<$ty>>, )*
}
impl assets::Compound for $Spec {
fn load<S: assets::source::Source>(_: &assets::AssetCache<S>, _: &str) -> Result<Self, assets::Error> {
Ok($Spec {
$( $field: AssetExt::load($asset_path)?, )*
})
}
}
impl BodySpec for $body {
type Spec = $Spec;
#[allow(unused_variables)]
fn load_watched(indicator: &mut ReloadIndicator) -> Result<Self::Spec, assets::Error> {
Ok(Self::Spec {
$( $field: AssetWith::load_watched(indicator)?, )*
})
}
#[allow(unused_variables)]
fn reload(spec: &mut Self::Spec) -> Result<(), assets::Error> {
$( spec.$field.reload()?; )*
Ok(())
fn load_spec() -> Result<AssetHandle<Self::Spec>, assets::Error> {
Self::Spec::load("")
}
fn bone_meshes(
@ -375,95 +369,97 @@ make_vox_spec!(
let hand = loadout.hand.as_deref();
let foot = loadout.foot.as_deref();
let color = &spec.color.read().0;
[
third_person.map(|_| {
spec.head.asset.mesh_head(
spec.head.read().0.mesh_head(
body,
&spec.color.asset,
color,
)
}),
third_person.map(|loadout| {
spec.armor_chest.asset.mesh_chest(
spec.armor_chest.read().0.mesh_chest(
body,
&spec.color.asset,
color,
loadout.chest.as_deref(),
)
}),
third_person.map(|loadout| {
spec.armor_belt.asset.mesh_belt(
spec.armor_belt.read().0.mesh_belt(
body,
&spec.color.asset,
color,
loadout.belt.as_deref(),
)
}),
third_person.map(|loadout| {
spec.armor_back.asset.mesh_back(
spec.armor_back.read().0.mesh_back(
body,
&spec.color.asset,
color,
loadout.back.as_deref(),
)
}),
third_person.map(|loadout| {
spec.armor_pants.asset.mesh_pants(
spec.armor_pants.read().0.mesh_pants(
body,
&spec.color.asset,
color,
loadout.pants.as_deref(),
)
}),
Some(spec.armor_hand.asset.mesh_left_hand(
Some(spec.armor_hand.read().0.mesh_left_hand(
body,
&spec.color.asset,
color,
hand,
)),
Some(spec.armor_hand.asset.mesh_right_hand(
Some(spec.armor_hand.read().0.mesh_right_hand(
body,
&spec.color.asset,
color,
hand,
)),
Some(spec.armor_foot.asset.mesh_left_foot(
Some(spec.armor_foot.read().0.mesh_left_foot(
body,
&spec.color.asset,
color,
foot,
)),
Some(spec.armor_foot.asset.mesh_right_foot(
Some(spec.armor_foot.read().0.mesh_right_foot(
body,
&spec.color.asset,
color,
foot,
)),
third_person.map(|loadout| {
spec.armor_shoulder.asset.mesh_left_shoulder(
spec.armor_shoulder.read().0.mesh_left_shoulder(
body,
&spec.color.asset,
color,
loadout.shoulder.as_deref(),
)
}),
third_person.map(|loadout| {
spec.armor_shoulder.asset.mesh_right_shoulder(
spec.armor_shoulder.read().0.mesh_right_shoulder(
body,
&spec.color.asset,
color,
loadout.shoulder.as_deref(),
)
}),
Some(spec.armor_glider.asset.mesh_glider(
Some(spec.armor_glider.read().0.mesh_glider(
body,
&spec.color.asset,
color,
glider,
)),
tool.and_then(|tool| tool.active.as_ref()).map(|tool| {
spec.main_weapon.asset.mesh_main_weapon(
spec.main_weapon.read().0.mesh_main_weapon(
tool,
false,
)
}),
tool.and_then(|tool| tool.second.as_ref()).map(|tool| {
spec.main_weapon.asset.mesh_main_weapon(
spec.main_weapon.read().0.mesh_main_weapon(
tool,
true,
)
}),
Some(spec.armor_lantern.asset.mesh_lantern(
Some(spec.armor_lantern.read().0.mesh_lantern(
body,
&spec.color.asset,
color,
lantern,
)),
Some(mesh_hold()),
@ -1065,31 +1061,31 @@ make_vox_spec!(
},
|FigureKey { body, .. }, spec| {
[
Some(spec.central.asset.mesh_head(
Some(spec.central.read().0.mesh_head(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_chest(
Some(spec.central.read().0.mesh_chest(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_fl(
Some(spec.lateral.read().0.mesh_foot_fl(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_fr(
Some(spec.lateral.read().0.mesh_foot_fr(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_bl(
Some(spec.lateral.read().0.mesh_foot_bl(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_br(
Some(spec.lateral.read().0.mesh_foot_br(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_tail(
Some(spec.central.read().0.mesh_tail(
body.species,
body.body_type,
)),
@ -1269,63 +1265,63 @@ make_vox_spec!(
},
|FigureKey { body, .. }, spec| {
[
Some(spec.central.asset.mesh_head(
Some(spec.central.read().0.mesh_head(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_neck(
Some(spec.central.read().0.mesh_neck(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_jaw(
Some(spec.central.read().0.mesh_jaw(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_tail(
Some(spec.central.read().0.mesh_tail(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_torso_front(
Some(spec.central.read().0.mesh_torso_front(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_torso_back(
Some(spec.central.read().0.mesh_torso_back(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_ears(
Some(spec.central.read().0.mesh_ears(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_leg_fl(
Some(spec.lateral.read().0.mesh_leg_fl(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_leg_fr(
Some(spec.lateral.read().0.mesh_leg_fr(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_leg_bl(
Some(spec.lateral.read().0.mesh_leg_bl(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_leg_br(
Some(spec.lateral.read().0.mesh_leg_br(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_fl(
Some(spec.lateral.read().0.mesh_foot_fl(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_fr(
Some(spec.lateral.read().0.mesh_foot_fr(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_bl(
Some(spec.lateral.read().0.mesh_foot_bl(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_br(
Some(spec.lateral.read().0.mesh_foot_br(
body.species,
body.body_type,
)),
@ -1618,31 +1614,31 @@ make_vox_spec!(
},
|FigureKey { body, .. }, spec| {
[
Some(spec.central.asset.mesh_head(
Some(spec.central.read().0.mesh_head(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_torso(
Some(spec.central.read().0.mesh_torso(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_tail(
Some(spec.central.read().0.mesh_tail(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_wing_l(
Some(spec.lateral.read().0.mesh_wing_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_wing_r(
Some(spec.lateral.read().0.mesh_wing_r(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_l(
Some(spec.lateral.read().0.mesh_foot_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_r(
Some(spec.lateral.read().0.mesh_foot_r(
body.species,
body.body_type,
)),
@ -1817,55 +1813,55 @@ make_vox_spec!(
},
|FigureKey { body, .. }, spec| {
[
Some(spec.central.asset.mesh_head(
Some(spec.central.read().0.mesh_head(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_jaw(
Some(spec.central.read().0.mesh_jaw(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_neck(
Some(spec.central.read().0.mesh_neck(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_chest_front(
Some(spec.central.read().0.mesh_chest_front(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_chest_back(
Some(spec.central.read().0.mesh_chest_back(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_tail_front(
Some(spec.central.read().0.mesh_tail_front(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_tail_back(
Some(spec.central.read().0.mesh_tail_back(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_hand_l(
Some(spec.lateral.read().0.mesh_hand_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_hand_r(
Some(spec.lateral.read().0.mesh_hand_r(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_leg_l(
Some(spec.lateral.read().0.mesh_leg_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_leg_r(
Some(spec.lateral.read().0.mesh_leg_r(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_l(
Some(spec.lateral.read().0.mesh_foot_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_r(
Some(spec.lateral.read().0.mesh_foot_r(
body.species,
body.body_type,
)),
@ -2124,31 +2120,31 @@ make_vox_spec!(
},
|FigureKey { body, .. }, spec| {
[
Some(spec.central.asset.mesh_head(
Some(spec.central.read().0.mesh_head(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_jaw(
Some(spec.central.read().0.mesh_jaw(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_chest_front(
Some(spec.central.read().0.mesh_chest_front(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_chest_back(
Some(spec.central.read().0.mesh_chest_back(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_tail(
Some(spec.central.read().0.mesh_tail(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_fin_l(
Some(spec.lateral.read().0.mesh_fin_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_fin_r(
Some(spec.lateral.read().0.mesh_fin_r(
body.species,
body.body_type,
)),
@ -2316,19 +2312,19 @@ make_vox_spec!(
},
|FigureKey { body, .. }, spec| {
[
Some(spec.central.asset.mesh_chest(
Some(spec.central.read().0.mesh_chest(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_tail(
Some(spec.central.read().0.mesh_tail(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_fin_l(
Some(spec.lateral.read().0.mesh_fin_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_fin_r(
Some(spec.lateral.read().0.mesh_fin_r(
body.species,
body.body_type,
)),
@ -2465,63 +2461,63 @@ make_vox_spec!(
},
|FigureKey { body, .. }, spec| {
[
Some(spec.central.asset.mesh_head_upper(
Some(spec.central.read().0.mesh_head_upper(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_head_lower(
Some(spec.central.read().0.mesh_head_lower(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_jaw(
Some(spec.central.read().0.mesh_jaw(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_chest_front(
Some(spec.central.read().0.mesh_chest_front(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_chest_rear(
Some(spec.central.read().0.mesh_chest_rear(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_tail_front(
Some(spec.central.read().0.mesh_tail_front(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_tail_rear(
Some(spec.central.read().0.mesh_tail_rear(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_wing_in_l(
Some(spec.lateral.read().0.mesh_wing_in_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_wing_in_r(
Some(spec.lateral.read().0.mesh_wing_in_r(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_wing_out_l(
Some(spec.lateral.read().0.mesh_wing_out_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_wing_out_r(
Some(spec.lateral.read().0.mesh_wing_out_r(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_fl(
Some(spec.lateral.read().0.mesh_foot_fl(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_fr(
Some(spec.lateral.read().0.mesh_foot_fr(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_bl(
Some(spec.lateral.read().0.mesh_foot_bl(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_br(
Some(spec.lateral.read().0.mesh_foot_br(
body.species,
body.body_type,
)),
@ -2883,63 +2879,63 @@ make_vox_spec!(
},
|FigureKey { body, .. }, spec| {
[
Some(spec.central.asset.mesh_head(
Some(spec.central.read().0.mesh_head(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_jaw(
Some(spec.central.read().0.mesh_jaw(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_torso_upper(
Some(spec.central.read().0.mesh_torso_upper(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_torso_lower(
Some(spec.central.read().0.mesh_torso_lower(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_tail(
Some(spec.central.read().0.mesh_tail(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_main(
Some(spec.central.read().0.mesh_main(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_second(
Some(spec.central.read().0.mesh_second(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_shoulder_l(
Some(spec.lateral.read().0.mesh_shoulder_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_shoulder_r(
Some(spec.lateral.read().0.mesh_shoulder_r(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_hand_l(
Some(spec.lateral.read().0.mesh_hand_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_hand_r(
Some(spec.lateral.read().0.mesh_hand_r(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_leg_l(
Some(spec.lateral.read().0.mesh_leg_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_leg_r(
Some(spec.lateral.read().0.mesh_leg_r(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_l(
Some(spec.lateral.read().0.mesh_foot_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_r(
Some(spec.lateral.read().0.mesh_foot_r(
body.species,
body.body_type,
)),
@ -3235,51 +3231,51 @@ make_vox_spec!(
},
|FigureKey { body, .. }, spec| {
[
Some(spec.central.asset.mesh_head(
Some(spec.central.read().0.mesh_head(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_jaw(
Some(spec.central.read().0.mesh_jaw(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_torso_upper(
Some(spec.central.read().0.mesh_torso_upper(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_torso_lower(
Some(spec.central.read().0.mesh_torso_lower(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_shoulder_l(
Some(spec.lateral.read().0.mesh_shoulder_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_shoulder_r(
Some(spec.lateral.read().0.mesh_shoulder_r(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_hand_l(
Some(spec.lateral.read().0.mesh_hand_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_hand_r(
Some(spec.lateral.read().0.mesh_hand_r(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_leg_l(
Some(spec.lateral.read().0.mesh_leg_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_leg_r(
Some(spec.lateral.read().0.mesh_leg_r(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_l(
Some(spec.lateral.read().0.mesh_foot_l(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_r(
Some(spec.lateral.read().0.mesh_foot_r(
body.species,
body.body_type,
)),
@ -3529,43 +3525,43 @@ make_vox_spec!(
},
|FigureKey { body, .. }, spec| {
[
Some(spec.central.asset.mesh_head_upper(
Some(spec.central.read().0.mesh_head_upper(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_head_lower(
Some(spec.central.read().0.mesh_head_lower(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_jaw(
Some(spec.central.read().0.mesh_jaw(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_chest(
Some(spec.central.read().0.mesh_chest(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_tail_front(
Some(spec.central.read().0.mesh_tail_front(
body.species,
body.body_type,
)),
Some(spec.central.asset.mesh_tail_rear(
Some(spec.central.read().0.mesh_tail_rear(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_fl(
Some(spec.lateral.read().0.mesh_foot_fl(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_fr(
Some(spec.lateral.read().0.mesh_foot_fr(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_bl(
Some(spec.lateral.read().0.mesh_foot_bl(
body.species,
body.body_type,
)),
Some(spec.lateral.asset.mesh_foot_br(
Some(spec.lateral.read().0.mesh_foot_br(
body.species,
body.body_type,
)),
@ -3765,7 +3761,7 @@ make_vox_spec!(
},
|FigureKey { body, .. }, spec| {
[
Some(spec.central.asset.mesh_bone0(
Some(spec.central.read().0.mesh_bone0(
body,
)),
None,

View File

@ -7,7 +7,7 @@ use crate::{
},
};
use common::{
assets::Asset,
assets::{AssetExt, DotVoxAsset},
comp::{item::Reagent, object, Body, CharacterState, Ori, Pos, Shockwave},
figure::Segment,
outcome::Outcome,
@ -18,7 +18,6 @@ use common::{
terrain::TerrainChunk,
vol::{RectRasterableVol, SizedVol},
};
use dot_vox::DotVoxData;
use hashbrown::HashMap;
use rand::prelude::*;
use specs::{Join, WorldExt};
@ -686,7 +685,7 @@ fn default_cache(renderer: &mut Renderer) -> HashMap<&'static str, Model<Particl
let mut model_cache = HashMap::new();
model_cache.entry(DEFAULT_MODEL_KEY).or_insert_with(|| {
let vox = DotVoxData::load_expect(DEFAULT_MODEL_KEY);
let vox = DotVoxAsset::load_expect(DEFAULT_MODEL_KEY);
// NOTE: If we add texturing we may eventually try to share it among all
// particles in a single atlas.
@ -695,7 +694,7 @@ fn default_cache(renderer: &mut Renderer) -> HashMap<&'static str, Model<Particl
guillotiere::Size::new(i32::from(max_texture_size), i32::from(max_texture_size));
let mut greedy = GreedyMesh::new(max_size);
let segment = Segment::from(vox.as_ref());
let segment = Segment::from(&vox.read().0);
let segment_size = segment.size();
let mut mesh =
Meshable::<ParticlePipeline, &mut GreedyMesh>::generate_mesh(segment, &mut greedy).0;

View File

@ -13,7 +13,7 @@ use crate::{
use super::{math, LodData, SceneData};
use common::{
assets::{Asset, Ron},
assets::{self, AssetExt, DotVoxAsset},
figure::Segment,
span,
spiral::Spiral2d,
@ -23,11 +23,9 @@ use common::{
};
use core::{f32, fmt::Debug, i32, marker::PhantomData, time::Duration};
use crossbeam::channel;
use dot_vox::DotVoxData;
use enum_iterator::IntoEnumIterator;
use guillotiere::AtlasAllocator;
use hashbrown::HashMap;
use image::DynamicImage;
use serde::Deserialize;
use std::sync::Arc;
use tracing::warn;
@ -125,7 +123,15 @@ struct SpriteConfig<Model> {
/// Configuration data for all sprite models.
///
/// NOTE: Model is an asset path to the appropriate sprite .vox model.
type SpriteSpec = sprite::sprite_kind::PureCases<Option<SpriteConfig<String>>>;
#[derive(Deserialize)]
#[serde(transparent)]
struct SpriteSpec(sprite::sprite_kind::PureCases<Option<SpriteConfig<String>>>);
impl assets::Asset for SpriteSpec {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
/// Function executed by worker threads dedicated to chunk meshing.
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
@ -177,7 +183,7 @@ fn mesh_worker<V: BaseVol<Vox = Block> + RectRasterableVol + ReadVol + Debug + '
continue;
};
if let Some(cfg) = sprite.elim_case_pure(&sprite_config) {
if let Some(cfg) = sprite.elim_case_pure(&sprite_config.0) {
let seed = wpos.x as u64 * 3
+ wpos.y as u64 * 7
+ wpos.x as u64 * wpos.y as u64; // Awful PRNG
@ -227,6 +233,9 @@ struct SpriteData {
pub struct Terrain<V: RectRasterableVol = TerrainChunk> {
atlas: AtlasAllocator,
/// FIXME: This could possibly become an `AssetHandle<SpriteSpec>`, to get
/// hot-reloading for free, but I am not sure if sudden changes of this
/// value would break something
sprite_config: Arc<SpriteSpec>,
chunks: HashMap<Vec2<i32>, TerrainChunkData>,
/// Temporary storage for dead chunks that might still be shadowing chunks
@ -268,8 +277,8 @@ impl<V: RectRasterableVol> Terrain<V> {
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
pub fn new(renderer: &mut Renderer) -> Self {
// Load all the sprite config data.
let sprite_config = Ron::<SpriteSpec>::load("voxygen.voxel.sprite_manifest")
.expect("Failed to find sprite model data!");
let sprite_config =
Arc::<SpriteSpec>::load_expect("voxygen.voxel.sprite_manifest").cloned();
// Create a new mpsc (Multiple Produced, Single Consumer) pair for communicating
// with worker threads that are meshing chunks.
@ -287,7 +296,7 @@ impl<V: RectRasterableVol> Terrain<V> {
// NOTE: Tracks the start vertex of the next model to be meshed.
let sprite_data: HashMap<(SpriteKind, usize), _> = SpriteKind::into_enum_iter()
.filter_map(|kind| Some((kind, kind.elim_case_pure(&sprite_config_).as_ref()?)))
.filter_map(|kind| Some((kind, kind.elim_case_pure(&sprite_config_.0).as_ref()?)))
.flat_map(|(kind, sprite_config)| {
let wind_sway = sprite_config.wind_sway;
sprite_config.variations.iter().enumerate().map(
@ -302,9 +311,11 @@ impl<V: RectRasterableVol> Terrain<V> {
let scaled = [1.0, 0.8, 0.6, 0.4, 0.2];
let offset = Vec3::from(*offset);
let lod_axes = Vec3::from(*lod_axes);
let model = DotVoxData::load_expect(model);
let model = DotVoxAsset::load_expect(model);
let zero = Vec3::zero();
let model_size = model
.read()
.0
.models
.first()
.map(
@ -344,7 +355,7 @@ impl<V: RectRasterableVol> Terrain<V> {
// interesting return value, but updates the mesh.
let mut opaque_mesh = Mesh::new();
Meshable::<SpritePipeline, &mut GreedyMesh>::generate_mesh(
Segment::from(model.as_ref()).scaled_by(lod_scale),
Segment::from(&model.read().0).scaled_by(lod_scale),
(
greedy,
&mut opaque_mesh,
@ -403,7 +414,7 @@ impl<V: RectRasterableVol> Terrain<V> {
sprite_col_lights,
waves: renderer
.create_texture(
&DynamicImage::load_expect("voxygen.texture.waves"),
&assets::Image::load_expect("voxygen.texture.waves").read().0,
Some(gfx::texture::FilterMethod::Trilinear),
Some(gfx::texture::WrapMode::Tile),
None,
@ -1154,7 +1165,7 @@ impl<V: RectRasterableVol> Terrain<V> {
for (kind, instances) in (&chunk.sprite_instances).into_iter() {
let SpriteData { model, locals, .. } = if kind
.0
.elim_case_pure(&self.sprite_config)
.elim_case_pure(&self.sprite_config.0)
.as_ref()
.map(|config| config.wind_sway >= 0.4)
.unwrap_or(false)

View File

@ -13,7 +13,7 @@ use crate::{
};
use client::{self, Client};
use common::{
assets::Asset,
assets::AssetExt,
comp,
comp::{ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel},
consts::{MAX_MOUNT_RANGE, MAX_PICKUP_RANGE},
@ -29,7 +29,7 @@ use common::{
use common_net::msg::PresenceKind;
use ordered_float::OrderedFloat;
use specs::{Join, WorldExt};
use std::{cell::RefCell, rc::Rc, sync::Arc, time::Duration};
use std::{cell::RefCell, rc::Rc, time::Duration};
use tracing::{error, info};
use vek::*;
@ -48,7 +48,6 @@ pub struct SessionState {
key_state: KeyState,
inputs: comp::ControllerInputs,
selected_block: Block,
i18n: std::sync::Arc<Localization>,
walk_forward_dir: Vec2<f32>,
walk_right_dir: Vec2<f32>,
freefly_vel: Vec3<f32>,
@ -75,10 +74,6 @@ impl SessionState {
.camera_mut()
.set_fov_deg(global_state.settings.graphics.fov);
let hud = Hud::new(global_state, &client.borrow());
let i18n = Localization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
let walk_forward_dir = scene.camera().forward_xy();
let walk_right_dir = scene.camera().right_xy();
@ -89,7 +84,6 @@ impl SessionState {
inputs: comp::ControllerInputs::default(),
hud,
selected_block: Block::new(BlockKind::Misc, Rgb::broadcast(255)),
i18n,
walk_forward_dir,
walk_right_dir,
freefly_vel: Vec3::zero(),
@ -125,24 +119,23 @@ impl SessionState {
self.hud.new_message(m);
},
client::Event::InventoryUpdated(inv_event) => {
let sfx_trigger_item = self
.scene
.sfx_mgr
.triggers
.get_key_value(&SfxEvent::from(&inv_event));
let sfx_triggers = self.scene.sfx_mgr.triggers.read();
let sfx_trigger_item = sfx_triggers.get_key_value(&SfxEvent::from(&inv_event));
global_state.audio.emit_sfx_item(sfx_trigger_item);
let i18n = global_state.i18n.read();
match inv_event {
InventoryUpdateEvent::CollectFailed => {
self.hud.new_message(ChatMsg {
message: self.i18n.get("hud.chat.loot_fail").to_string(),
message: i18n.get("hud.chat.loot_fail").to_string(),
chat_type: ChatType::CommandError,
});
},
InventoryUpdateEvent::Collected(item) => {
self.hud.new_message(ChatMsg {
message: self
.i18n
message: i18n
.get("hud.chat.loot_msg")
.replace("{item}", item.name()),
chat_type: ChatType::Loot,
@ -153,10 +146,11 @@ impl SessionState {
},
client::Event::Disconnect => return Ok(TickAction::Disconnect),
client::Event::DisconnectionNotification(time) => {
let i18n = global_state.i18n.read();
let message = match time {
0 => String::from(self.i18n.get("hud.chat.goodbye")),
_ => self
.i18n
0 => String::from(i18n.get("hud.chat.goodbye")),
_ => i18n
.get("hud.chat.connection_lost")
.replace("{time}", time.to_string().as_str()),
};
@ -169,7 +163,11 @@ impl SessionState {
client::Event::Kicked(reason) => {
global_state.info_message = Some(format!(
"{}: {}",
self.i18n.get("main.login.kicked").to_string(),
global_state
.i18n
.read()
.get("main.login.kicked")
.to_string(),
reason
));
return Ok(TickAction::Disconnect);
@ -212,10 +210,6 @@ impl PlayState for SessionState {
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
span!(_guard, "tick", "<Session as PlayState>::tick");
// TODO: let mut client = self.client.borrow_mut();
// NOTE: Not strictly necessary, but useful for hotloading translation changes.
self.i18n = Localization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
// TODO: can this be a method on the session or are there borrowcheck issues?
let (client_presence, client_registered) = {
@ -680,8 +674,13 @@ impl PlayState for SessionState {
Ok(TickAction::Continue) => {}, // Do nothing
Ok(TickAction::Disconnect) => return PlayStateResult::Pop, // Go to main menu
Err(err) => {
global_state.info_message =
Some(self.i18n.get("common.connection_lost").to_owned());
global_state.info_message = Some(
global_state
.i18n
.read()
.get("common.connection_lost")
.to_owned(),
);
error!("[session] Failed to tick the scene: {:?}", err);
return PlayStateResult::Pop;
@ -758,9 +757,9 @@ impl PlayState for SessionState {
);
// Look for changes in the localization files
if global_state.localization_watcher.reloaded() {
if global_state.i18n.reloaded() {
hud_events.push(HudEvent::ChangeLanguage(Box::new(
self.i18n.metadata.clone(),
global_state.i18n.read().metadata.clone(),
)));
}
@ -1016,13 +1015,11 @@ impl PlayState for SessionState {
HudEvent::ChangeLanguage(new_language) => {
global_state.settings.language.selected_language =
new_language.language_identifier;
self.i18n = Localization::load_watched(
&i18n_asset_key(&global_state.settings.language.selected_language),
&mut global_state.localization_watcher,
)
.unwrap();
self.i18n.log_missing_entries();
self.hud.update_language(Arc::clone(&self.i18n));
global_state.i18n = Localization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language,
));
global_state.i18n.read().log_missing_entries();
self.hud.update_fonts(&global_state.i18n.read());
},
HudEvent::ChangeFullscreenMode(new_fullscreen_settings) => {
global_state

View File

@ -1,5 +1,5 @@
use crate::i18n;
use common::assets::Asset;
use crate::{i18n, ui::ice::RawFont};
use common::assets::{self, AssetExt};
pub struct Font {
metadata: i18n::Font,
@ -8,11 +8,13 @@ pub struct Font {
impl Font {
#[allow(clippy::needless_return)] // TODO: Pending review in #587
pub fn new(font: &i18n::Font, ui: &mut crate::ui::Ui) -> Self {
Self {
fn new(font: &i18n::Font, ui: &mut crate::ui::Ui) -> Result<Self, assets::Error> {
let raw_font = RawFont::load(&font.asset_key)?.cloned();
Ok(Self {
metadata: font.clone(),
conrod_id: ui.new_font(crate::ui::ice::RawFont::load_expect(&font.asset_key)),
}
conrod_id: ui.new_font(raw_font),
})
}
/// Scale input size to final UI size
@ -27,9 +29,9 @@ macro_rules! conrod_fonts {
}
impl Fonts {
pub fn load(fonts: &i18n::Fonts, ui: &mut crate::ui::Ui) -> Result<Self, common::assets::Error> {
pub fn load(fonts: &i18n::Fonts, ui: &mut crate::ui::Ui) -> Result<Self, assets::Error> {
Ok(Self {
$( $name: Font::new(fonts.get(stringify!($name)).unwrap(), ui),)*
$( $name: Font::new(fonts.get(stringify!($name)).unwrap(), ui)?, )*
})
}
}
@ -47,11 +49,13 @@ pub struct IcedFont {
}
impl IcedFont {
pub fn new(font: &i18n::Font, ui: &mut crate::ui::ice::IcedUi) -> Self {
Self {
fn new(font: &i18n::Font, ui: &mut crate::ui::ice::IcedUi) -> Result<Self, assets::Error> {
let raw_font = RawFont::load(&font.asset_key)?.cloned();
Ok(Self {
metadata: font.clone(),
id: ui.add_font((*crate::ui::ice::RawFont::load_expect(&font.asset_key)).clone()),
}
id: ui.add_font(raw_font),
})
}
/// Scale input size to final UI size
@ -67,9 +71,9 @@ macro_rules! iced_fonts {
}
impl IcedFonts {
pub fn load(fonts: &i18n::Fonts, ui: &mut crate::ui::ice::IcedUi) -> Result<Self, common::assets::Error> {
pub fn load(fonts: &i18n::Fonts, ui: &mut crate::ui::ice::IcedUi) -> Result<Self, assets::Error> {
Ok(Self {
$( $name: IcedFont::new(fonts.get(stringify!($name)).unwrap(), ui),)*
$( $name: IcedFont::new(fonts.get(stringify!($name)).unwrap(), ui)?, )*
})
}
}

View File

@ -3,8 +3,12 @@ use crate::{
render::{Renderer, Texture},
Error,
};
use common::assets::{self, AssetExt};
use glyph_brush::GlyphBrushBuilder;
use std::cell::{RefCell, RefMut};
use std::{
borrow::Cow,
cell::{RefCell, RefMut},
};
use vek::*;
// Multiplied by current window size
@ -20,6 +24,23 @@ type GlyphBrush = glyph_brush::GlyphBrush<(Aabr<f32>, Aabr<f32>), ()>;
// TODO: might not need pub
pub type Font = glyph_brush::ab_glyph::FontArc;
struct FontAsset(Font);
struct FontLoader;
impl assets::Loader<FontAsset> for FontLoader {
fn load(data: Cow<[u8]>, _: &str) -> Result<FontAsset, assets::BoxedError> {
let font = Font::try_from_vec(data.into_owned())?;
Ok(FontAsset(font))
}
}
impl assets::Asset for FontAsset {
type Loader = FontLoader;
const EXTENSION: &'static str = "ttf";
}
pub fn load_font(specifier: &str) -> Font { FontAsset::load_expect(specifier).read().0.clone() }
#[derive(Clone, Copy, Default)]
pub struct FontId(pub(super) glyph_brush::FontId);
@ -121,16 +142,13 @@ impl Cache {
// TODO: use font type instead of raw vec once we convert to full iced
#[derive(Clone)]
pub struct RawFont(pub Vec<u8>);
impl common::assets::Asset for RawFont {
const ENDINGS: &'static [&'static str] = &["ttf"];
fn parse(
mut buf_reader: std::io::BufReader<std::fs::File>,
_specifier: &str,
) -> Result<Self, common::assets::Error> {
use std::io::Read;
let mut buf = Vec::new();
buf_reader.read_to_end(&mut buf)?;
Ok(Self(buf))
}
impl From<Vec<u8>> for RawFont {
fn from(raw: Vec<u8>) -> RawFont { RawFont(raw) }
}
impl assets::Asset for RawFont {
type Loader = assets::LoadFrom<Vec<u8>, assets::BytesLoader>;
const EXTENSION: &'static str = "ttf";
}

View File

@ -4,7 +4,7 @@ pub mod component;
mod renderer;
pub mod widget;
pub use cache::{Font, FontId, RawFont};
pub use cache::{load_font, Font, FontId, RawFont};
pub use graphic::{Id, Rotation};
pub use iced::Event;
pub use iced_winit::conversion::window_event;

View File

@ -1,10 +1,8 @@
use super::{Graphic, SampleStrat, Transform};
use common::{
assets::{Asset, Error},
assets::{self, AssetExt, DotVoxAsset, Error},
figure::Segment,
};
use dot_vox::DotVoxData;
use image::DynamicImage;
use std::sync::Arc;
use vek::*;
@ -24,7 +22,8 @@ impl<'a> GraphicCreator<'a> for ImageGraphic {
type Specifier = &'a str;
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
Ok(Graphic::Image(DynamicImage::load(specifier)?, None))
let image = assets::Image::load(specifier)?.read().to_image();
Ok(Graphic::Image(image, None))
}
}
@ -37,8 +36,8 @@ pub enum VoxelSs9Graphic {}
pub enum VoxelPixArtGraphic {}
fn load_segment(specifier: &str) -> Result<Arc<Segment>, Error> {
let dot_vox = DotVoxData::load(specifier)?;
let seg = dot_vox.as_ref().into();
let dot_vox = DotVoxAsset::load(specifier)?;
let seg = Segment::from(&dot_vox.read().0);
Ok(Arc::new(seg))
}

View File

@ -47,7 +47,7 @@ use conrod_core::{
use core::{convert::TryInto, f32, f64, ops::Range};
use graphic::TexId;
use hashbrown::hash_map::Entry;
use std::{sync::Arc, time::Duration};
use std::time::Duration;
use tracing::{error, warn};
use vek::*;
@ -215,8 +215,8 @@ impl Ui {
self.image_map.replace(id, (graphic_id, Rotation::None));
}
pub fn new_font(&mut self, font: Arc<crate::ui::ice::RawFont>) -> font::Id {
let font = text::Font::from_bytes(font.0.clone()).unwrap();
pub fn new_font(&mut self, font: crate::ui::ice::RawFont) -> font::Id {
let font = text::Font::from_bytes(font.0).unwrap();
self.ui.fonts.insert(font)
}

View File

@ -8,7 +8,7 @@ const H: usize = 480;
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
fn main() {
let seed = 1337;
let (ref index, ref colors) = Index::new(seed);
let index = &Index::new(seed);
let mut win =
minifb::Window::new("Settlement Viewer", W, H, minifb::WindowOptions::default()).unwrap();
@ -17,6 +17,7 @@ fn main() {
let mut focus = Vec2::<f32>::zero();
let mut zoom = 1.0;
let colors = &**index.colors().read();
let index = IndexRef { colors, index };
while win.is_open() {

View File

@ -1,6 +1,6 @@
use crate::{site::Site, Colors};
use common::{
assets::{watch::ReloadIndicator, Asset, Ron},
assets::{AssetExt, AssetHandle},
store::Store,
};
use core::ops::Deref;
@ -14,7 +14,7 @@ pub struct Index {
pub time: f32,
pub noise: Noise,
pub sites: Store<Site>,
indicator: ReloadIndicator,
colors: AssetHandle<Arc<Colors>>,
}
/// An owned reference to indexed data.
@ -51,26 +51,25 @@ impl<'a> Deref for IndexRef<'a> {
impl Index {
/// NOTE: Panics if the color manifest cannot be loaded.
pub fn new(seed: u32) -> (Self, Arc<Colors>) {
let mut indicator = ReloadIndicator::new();
let colors = Ron::<Colors>::load_watched(WORLD_COLORS_MANIFEST, &mut indicator)
.expect("Could not load world colors!");
pub fn new(seed: u32) -> Self {
let colors = Arc::<Colors>::load_expect(WORLD_COLORS_MANIFEST);
(
Self {
seed,
time: 0.0,
noise: Noise::new(seed),
sites: Store::default(),
indicator,
},
Self {
seed,
time: 0.0,
noise: Noise::new(seed),
sites: Store::default(),
colors,
)
}
}
pub fn colors(&self) -> AssetHandle<Arc<Colors>> { self.colors }
}
impl IndexOwned {
pub fn new(index: Index, colors: Arc<Colors>) -> Self {
pub fn new(index: Index) -> Self {
let colors = index.colors.cloned();
Self {
index: Arc::new(index),
colors,
@ -88,9 +87,9 @@ impl IndexOwned {
&mut self,
reload: impl FnOnce(&mut Self) -> R,
) -> Option<R> {
self.indicator.reloaded().then(move || {
// We know the asset was loaded before, so load_expect should be fine.
self.colors = Ron::<Colors>::load_expect(WORLD_COLORS_MANIFEST);
self.index.colors.reloaded_global().then(move || {
// Reload the color from the asset handle, which is updated automatically
self.colors = self.index.colors.cloned();
reload(self)
})
}

View File

@ -10,7 +10,7 @@ use crate::{
Canvas, IndexRef,
};
use common::{
assets::Asset,
assets::AssetExt,
comp,
generation::{ChunkSupplement, EntityInfo},
lottery::Lottery,
@ -182,7 +182,8 @@ pub fn apply_caves_to(canvas: &mut Canvas) {
.chance(wpos2d.into(), 0.001 * difficulty.powf(1.5))
&& cave_base < surface_z as i32 - 25
{
let kind = *Lottery::<SpriteKind>::load_expect("common.cave_scatter")
let lottery = Lottery::<SpriteKind>::load_expect("common.cave_scatter").read();
let kind = *lottery
.choose_seeded(RandomField::new(info.index().seed + 1).get(wpos2d.into()));
canvas.map(Vec3::new(wpos2d.x, wpos2d.y, cave_base), |block| {
block.with_sprite(kind)

View File

@ -6,27 +6,29 @@ use crate::{
Canvas, CONFIG,
};
use common::{
terrain::{structure::Structure, Block, BlockKind},
assets::AssetHandle,
terrain::{Block, BlockKind, Structure, StructuresGroup},
vol::ReadVol,
};
use hashbrown::HashMap;
use lazy_static::lazy_static;
use std::{f32, sync::Arc};
use std::f32;
use vek::*;
lazy_static! {
pub static ref OAKS: Vec<Arc<Structure>> = Structure::load_group("oaks");
pub static ref OAK_STUMPS: Vec<Arc<Structure>> = Structure::load_group("oak_stumps");
pub static ref PINES: Vec<Arc<Structure>> = Structure::load_group("pines");
pub static ref PALMS: Vec<Arc<Structure>> = Structure::load_group("palms");
pub static ref ACACIAS: Vec<Arc<Structure>> = Structure::load_group("acacias");
pub static ref BAOBABS: Vec<Arc<Structure>> = Structure::load_group("baobabs");
pub static ref FRUIT_TREES: Vec<Arc<Structure>> = Structure::load_group("fruit_trees");
pub static ref BIRCHES: Vec<Arc<Structure>> = Structure::load_group("birch");
pub static ref MANGROVE_TREES: Vec<Arc<Structure>> = Structure::load_group("mangrove_trees");
pub static ref QUIRKY: Vec<Arc<Structure>> = Structure::load_group("quirky");
pub static ref QUIRKY_DRY: Vec<Arc<Structure>> = Structure::load_group("quirky_dry");
pub static ref SWAMP_TREES: Vec<Arc<Structure>> = Structure::load_group("swamp_trees");
static ref OAKS: AssetHandle<StructuresGroup> = Structure::load_group("oaks");
static ref OAK_STUMPS: AssetHandle<StructuresGroup> = Structure::load_group("oak_stumps");
static ref PINES: AssetHandle<StructuresGroup> = Structure::load_group("pines");
static ref PALMS: AssetHandle<StructuresGroup> = Structure::load_group("palms");
static ref ACACIAS: AssetHandle<StructuresGroup> = Structure::load_group("acacias");
static ref BAOBABS: AssetHandle<StructuresGroup> = Structure::load_group("baobabs");
static ref FRUIT_TREES: AssetHandle<StructuresGroup> = Structure::load_group("fruit_trees");
static ref BIRCHES: AssetHandle<StructuresGroup> = Structure::load_group("birch");
static ref MANGROVE_TREES: AssetHandle<StructuresGroup> =
Structure::load_group("mangrove_trees");
static ref QUIRKY: AssetHandle<StructuresGroup> = Structure::load_group("quirky");
static ref QUIRKY_DRY: AssetHandle<StructuresGroup> = Structure::load_group("quirky_dry");
static ref SWAMP_TREES: AssetHandle<StructuresGroup> = Structure::load_group("swamp_trees");
}
static MODEL_RAND: RandomPerm = RandomPerm::new(0xDB21C052);
@ -37,7 +39,7 @@ static QUIRKY_RAND: RandomPerm = RandomPerm::new(0xA634460F);
pub fn apply_trees_to(canvas: &mut Canvas) {
struct Tree {
pos: Vec3<i32>,
model: Arc<Structure>,
model: Structure,
seed: u32,
units: (Vec2<i32>, Vec2<i32>),
}
@ -72,34 +74,34 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
Some(Tree {
pos: Vec3::new(tree_wpos.x, tree_wpos.y, col.alt as i32),
model: {
let models: &'static [_] = if is_quirky {
let models: AssetHandle<_> = if is_quirky {
if col.temp > CONFIG.desert_temp {
&QUIRKY_DRY
*QUIRKY_DRY
} else {
&QUIRKY
*QUIRKY
}
} else {
match col.forest_kind {
ForestKind::Oak if QUIRKY_RAND.chance(seed + 1, 1.0 / 16.0) => {
&OAK_STUMPS
*OAK_STUMPS
},
ForestKind::Oak if QUIRKY_RAND.chance(seed + 2, 1.0 / 20.0) => {
&FRUIT_TREES
*FRUIT_TREES
},
ForestKind::Palm => &PALMS,
ForestKind::Acacia => &ACACIAS,
ForestKind::Baobab => &BAOBABS,
ForestKind::Oak => &OAKS,
ForestKind::Pine => &PINES,
ForestKind::Birch => &BIRCHES,
ForestKind::Mangrove => &MANGROVE_TREES,
ForestKind::Swamp => &SWAMP_TREES,
ForestKind::Palm => *PALMS,
ForestKind::Acacia => *ACACIAS,
ForestKind::Baobab => *BAOBABS,
ForestKind::Oak => *OAKS,
ForestKind::Pine => *PINES,
ForestKind::Birch => *BIRCHES,
ForestKind::Mangrove => *MANGROVE_TREES,
ForestKind::Swamp => *SWAMP_TREES,
}
};
Arc::clone(
&models[(MODEL_RAND.get(seed.wrapping_mul(17)) / 13) as usize
% models.len()],
)
let models = models.read();
models[(MODEL_RAND.get(seed.wrapping_mul(17)) / 13) as usize % models.len()]
.clone()
},
seed,
units: UNIT_CHOOSER.get(seed),

View File

@ -41,6 +41,7 @@ use crate::{
util::{Grid, Sampler},
};
use common::{
assets,
generation::{ChunkSupplement, EntityInfo},
terrain::{Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
vol::{ReadVol, RectVolSize, WriteVol},
@ -70,17 +71,23 @@ pub struct Colors {
pub site: site::Colors,
}
impl assets::Asset for Colors {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
impl World {
pub fn generate(seed: u32, opts: sim::WorldOpts) -> (Self, IndexOwned) {
// NOTE: Generating index first in order to quickly fail if the color manifest
// is broken.
let (mut index, colors) = Index::new(seed);
let mut index = Index::new(seed);
let mut sim = sim::WorldSim::generate(seed, opts);
let civs = civ::Civs::generate(seed, &mut sim, &mut index);
sim2::simulate(&mut index, &mut sim);
(Self { sim, civs }, IndexOwned::new(index, colors))
(Self { sim, civs }, IndexOwned::new(index))
}
pub fn sim(&self) -> &sim::WorldSim { &self.sim }

View File

@ -35,7 +35,7 @@ use crate::{
IndexRef, CONFIG,
};
use common::{
assets,
assets::{self, AssetExt},
grid::Grid,
store::Id,
terrain::{
@ -220,7 +220,7 @@ pub enum WorldFileError {
/// invalidation or make sure that the map is synchronized with updates to
/// noise-rs, changes to other parameters, etc.
///
/// The map is verisoned to enable format detection between versions of Veloren,
/// The map is versioned to enable format detection between versions of Veloren,
/// so that when we update the map format we don't break existing maps (or at
/// least, we will try hard not to break maps between versions; if we can't
/// avoid it, we can at least give a reasonable error message).
@ -245,6 +245,12 @@ pub enum WorldFile {
Veloren0_7_0(WorldMap_0_7_0) = 1,
}
impl assets::Asset for WorldFile {
type Loader = assets::BincodeLoader;
const EXTENSION: &'static str = "bin";
}
/// Data for the most recent map type. Update this when you add a new map
/// version.
pub type ModernMap = WorldMap_0_7_0;
@ -408,27 +414,24 @@ impl WorldSim {
map.into_modern()
},
FileOpts::LoadAsset(ref specifier) => {
let reader = match assets::load_file(specifier, &["bin"]) {
Ok(reader) => reader,
Err(e) => {
warn!(?e, ?specifier, "Couldn't read asset specifier for maps",);
return None;
},
};
let map: WorldFile = match bincode::deserialize_from(reader) {
Ok(map) => map,
Err(e) => {
warn!(
?e,
"Couldn't parse modern map. Maybe you meant to try a legacy load?"
);
return None;
},
};
map.into_modern()
FileOpts::LoadAsset(ref specifier) => match WorldFile::load_owned(&specifier) {
Ok(map) => map.into_modern(),
Err(err) => {
match err {
assets::Error::Io(e) => {
warn!(?e, ?specifier, "Couldn't read asset specifier for maps");
},
assets::Error::Conversion(e) => {
warn!(
?e,
"Couldn't parse modern map. Maybe you meant to try a legacy \
load?"
);
},
assets::Error::NoDefaultValue => unreachable!(),
}
return None;
},
},
FileOpts::Generate | FileOpts::Save => return None,
};

View File

@ -8,13 +8,13 @@ use crate::{
IndexRef,
};
use common::{
assets::Asset,
assets::{AssetExt, AssetHandle},
astar::Astar,
comp::{self},
generation::{ChunkSupplement, EntityInfo},
lottery::Lottery,
store::{Id, Store},
terrain::{Block, BlockKind, SpriteKind, Structure, TerrainChunkSize},
terrain::{Block, BlockKind, SpriteKind, Structure, StructuresGroup, TerrainChunkSize},
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, WriteVol},
};
use core::{f32, hash::BuildHasherDefault};
@ -22,7 +22,6 @@ use fxhash::FxHasher64;
use lazy_static::lazy_static;
use rand::prelude::*;
use serde::Deserialize;
use std::sync::Arc;
use vek::*;
pub struct Dungeon {
@ -111,11 +110,12 @@ impl Dungeon {
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
) {
lazy_static! {
pub static ref ENTRANCES: Vec<Arc<Structure>> =
pub static ref ENTRANCES: AssetHandle<StructuresGroup> =
Structure::load_group("dungeon_entrances");
}
let entrance = &ENTRANCES[self.seed as usize % ENTRANCES.len()];
let entrances = ENTRANCES.read();
let entrance = &entrances[self.seed as usize % entrances.len()];
for y in 0..vol.size_xy().y as i32 {
for x in 0..vol.size_xy().x as i32 {
@ -581,6 +581,7 @@ impl Floor {
"common.loot_tables.loot_table_armor_misc",
),
};
let chosen = chosen.read();
let chosen = chosen.choose();
//let is_giant =
// RandomField::new(room.seed.wrapping_add(1)).chance(Vec3::from(tile_pos),
@ -741,6 +742,7 @@ impl Floor {
"common.loot_tables.loot_table_armor_misc",
),
};
let chosen = chosen.read();
let chosen = chosen.choose();
let entity = match room.difficulty {
0 => vec![
@ -923,6 +925,7 @@ impl Floor {
"common.loot_tables.loot_table_armor_misc",
),
};
let chosen = chosen.read();
let chosen = chosen.choose();
let entity = match room.difficulty {
0 => vec![