Merge branch 'xvar/wgpu-egui' into 'master'

Added egui debug UI

See merge request veloren/veloren!2252
This commit is contained in:
Ben Wallis 2021-07-04 09:47:19 +00:00
commit fca8227db8
31 changed files with 1298 additions and 145 deletions

View File

@ -9,8 +9,8 @@ csv-import = "run --manifest-path common/Cargo.toml --features=bin_csv --bin csv
test-server = "run --bin veloren-server-cli --no-default-features -- -b"
tracy-server = "-Zunstable-options run --bin veloren-server-cli --no-default-features --features tracy,simd --profile no_overflow"
tracy-world-server = "-Zunstable-options run --bin veloren-server-cli --features tracy,simd --profile no_overflow -- -b"
test-voxygen = "run --bin veloren-voxygen --no-default-features --features simd"
tracy-voxygen = "-Zunstable-options run --bin veloren-voxygen --no-default-features --features tracy,simd --profile no_overflow"
test-voxygen = "run --bin veloren-voxygen --no-default-features --features simd,egui-ui"
tracy-voxygen = "-Zunstable-options run --bin veloren-voxygen --no-default-features --features tracy,simd,egui-ui --profile no_overflow"
server = "run --bin veloren-server-cli"
dbg-voxygen = "run --bin veloren-voxygen -Zunstable-options --profile debuginfo"

101
Cargo.lock generated
View File

@ -257,6 +257,12 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9ff149ed9780025acfdb36862d35b28856bb693ceb451259a7164442f22fdc3"
[[package]]
name = "atomic_refcell"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "681b971236e0f76b20fcafca0236b8718c9186ee778d67cd78bd5f28fd85427f"
[[package]]
name = "atomicwrites"
version = "0.3.0"
@ -1437,12 +1443,47 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "egui"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "788148861d80b87d28d64440a3d31cae190e50ccc3ea585597466d38428365d7"
dependencies = [
"epaint",
]
[[package]]
name = "egui_wgpu_backend"
version = "0.8.0"
source = "git+https://github.com/hasenbanck/egui_wgpu_backend.git?rev=63a002c6a9b6c016e45806dd065864431caab621#63a002c6a9b6c016e45806dd065864431caab621"
dependencies = [
"bytemuck",
"epi",
"wgpu",
]
[[package]]
name = "egui_winit_platform"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd4cf17c0cd4dbcf2f8fef55a3592b9b7cfd970576c7302d8ba5c521b8560371"
dependencies = [
"egui",
"winit",
]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "emath"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e73d6c8c70eadb71756fbbc3c303ab25e163b46b656886dd250de5636efea12"
[[package]]
name = "encoding_rs"
version = "0.8.28"
@ -1499,6 +1540,28 @@ dependencies = [
"syn 1.0.72",
]
[[package]]
name = "epaint"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80e2db640801230bdda80629bc3a063927a462f5eaf38a98da676954e78ccb99"
dependencies = [
"ahash 0.7.4",
"atomic_refcell",
"emath",
"ordered-float 2.5.1",
"rusttype 0.9.2",
]
[[package]]
name = "epi"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59c4f6cbede1fc8f836384f85295a59199a4825940abcc3a8a29cfe2e3c37583"
dependencies = [
"egui",
]
[[package]]
name = "error-code"
version = "2.3.0"
@ -6095,6 +6158,9 @@ dependencies = [
"directories-next",
"dispatch 0.1.4",
"dot_vox",
"egui",
"egui_wgpu_backend",
"egui_winit_platform",
"enum-iterator",
"euc",
"futures-executor",
@ -6138,6 +6204,7 @@ dependencies = [
"veloren-i18n",
"veloren-server",
"veloren-voxygen-anim",
"veloren-voxygen-egui",
"veloren-world",
"wgpu",
"wgpu-profiler",
@ -6151,13 +6218,10 @@ name = "veloren-voxygen-anim"
version = "0.10.0"
dependencies = [
"bytemuck",
"find_folder",
"lazy_static",
"libloading 0.7.0",
"notify 5.0.0-pre.9",
"tracing",
"vek",
"veloren-common",
"veloren-voxygen-dynlib",
]
[[package]]
@ -6167,6 +6231,35 @@ dependencies = [
"veloren-voxygen-anim",
]
[[package]]
name = "veloren-voxygen-dynlib"
version = "0.1.0"
dependencies = [
"find_folder",
"libloading 0.7.0",
"notify 5.0.0-pre.9",
"tracing",
]
[[package]]
name = "veloren-voxygen-egui"
version = "0.9.0"
dependencies = [
"egui",
"egui_winit_platform",
"lazy_static",
"veloren-client",
"veloren-common",
"veloren-voxygen-dynlib",
]
[[package]]
name = "veloren-voxygen-egui-dyn"
version = "0.9.0"
dependencies = [
"veloren-voxygen-egui",
]
[[package]]
name = "veloren-world"
version = "0.10.0"

View File

@ -20,6 +20,9 @@ members = [
"voxygen/anim",
"voxygen/anim/dyn",
"voxygen/i18n",
"voxygen/dynlib",
"voxygen/egui",
"voxygen/egui/dyn",
"world",
"network",
"network/protocol",

View File

@ -13,7 +13,7 @@ pub use common_net::msg::ServerInfo;
pub use specs::{
join::Join,
saveload::{Marker, MarkerAllocator},
Builder, DispatcherBuilder, Entity as EcsEntity, ReadStorage, WorldExt,
Builder, DispatcherBuilder, Entity as EcsEntity, ReadStorage, World, WorldExt,
};
use crate::addr::ConnectionArgs;

View File

@ -61,6 +61,7 @@ where
.add_directive("gfx_backend_vulkan=info".parse().unwrap())
.add_directive("wgpu_core=info".parse().unwrap())
.add_directive("wgpu_core::device=warn".parse().unwrap())
.add_directive("wgpu_core::swap_chain=info".parse().unwrap())
.add_directive("veloren_network_protocol=info".parse().unwrap())
.add_directive("quinn_proto::connection=info".parse().unwrap())
.add_directive(

View File

@ -85,7 +85,7 @@ impl Clock {
.map_or(self.last_dt.as_secs_f32(), |t| t.into_inner()),
);
if self.last_dts.len() >= NUMBER_OF_DELTAS_COMPARED && self.last_dt > 2 * stable_dt {
tracing::debug!(?self.last_dt, ?self.total_tick_time, "lag spike detected, unusually slow tick");
tracing::trace!(?self.last_dt, ?self.total_tick_time, "lag spike detected, unusually slow tick");
stable_dt
} else {
self.last_dt

View File

@ -23,13 +23,14 @@ use crate::{
use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage};
use specs_idvs::IdvStorage;
use strum_macros::Display;
use vek::*;
use super::{BuffKind, Density, Mass};
make_case_elim!(
body,
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, Display, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u32)]
pub enum Body {
Humanoid(body: humanoid::Body) = 0,

View File

@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage, VecStorage};
use specs_idvs::IdvStorage;
use std::collections::{BTreeMap, VecDeque};
use strum_macros::Display;
use vek::*;
/// Data returned from character behavior fn's to Character Behavior System.
@ -44,7 +45,7 @@ impl From<&JoinData<'_>> for StateUpdate {
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Debug, Display, PartialEq, Serialize, Deserialize)]
pub enum CharacterState {
Idle,
Climb(climb::Data),

View File

@ -19,6 +19,7 @@ use std::{
ops::{Add, Div},
time::Duration,
};
use strum_macros::Display;
use vek::*;
pub const MOVEMENT_THRESHOLD_VEL: f32 = 3.0;
@ -869,7 +870,7 @@ pub fn tick_attack_or_default(
/// Determines what portion a state is in. Used in all attacks (eventually). Is
/// used to control aspects of animation code, as well as logic within the
/// character states.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Display, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum StageSection {
Buildup,
Recover,

View File

@ -23,17 +23,16 @@ buildInputs = ["xorg.libxcb"]
[features]
hot-anim = ["anim/use-dyn-lib"]
hot-egui = ["voxygen-egui/use-dyn-lib", "egui"]
singleplayer = ["server"]
simd = ["vek/platform_intrinsics"]
tracy = ["profiling", "profiling/profile-with-tracy", "common-frontend/tracy", "client/tracy"]
plugins = ["client/plugins"]
egui-ui = ["voxygen-egui", "egui", "egui_wgpu_backend", "egui_winit_platform"]
# We don't ship egui with published release builds so a separate feature is required that excludes it.
# This feature has been added ahead of the egui merge to allow time for Flatpak, AUR and Nix to be updated
# to ensure there isn't any period of time where we're shipping the egui-ui feature enabled by default.
# This comment will be updated after MR 2253 merges.
default-publish = ["singleplayer", "native-dialog", "plugins", "simd"]
default = ["default-publish"]
default = ["default-publish", "egui-ui"]
[dependencies]
client = {package = "veloren-client", path = "../client"}
@ -47,6 +46,7 @@ common-state = {package = "veloren-common-state", path = "../common/state"}
anim = {package = "veloren-voxygen-anim", path = "anim"}
i18n = {package = "veloren-i18n", path = "i18n"}
voxygen-egui = {package = "veloren-voxygen-egui", path = "egui", optional = true }
# Graphics
winit = {version = "0.25.0", features = ["serde"]}
@ -65,6 +65,11 @@ window_clipboard = "0.2"
glyph_brush = "0.7.0"
keyboard-keynames = { git = "https://gitlab.com/Frinksy/keyboard-keynames.git", rev = "9ae8f89014d0b0c5b61d0e821c5aeb6140c5c0dc" }
# EGUI
egui = {version = "0.12", optional = true }
egui_wgpu_backend = {git = "https://github.com/hasenbanck/egui_wgpu_backend.git", rev = "63a002c6a9b6c016e45806dd065864431caab621", optional = true }
egui_winit_platform = {version = "0.8", optional = true }
# ECS
specs = {git = "https://github.com/amethyst/specs.git", rev = "f985bec5d456f7b0dd8aae99848f9473c2cd9d46"}
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", rev = "8be2abcddf8f524cb5876e8dd20a7e47cfaf7573" }

View File

@ -5,7 +5,7 @@ name = "veloren-voxygen-anim"
version = "0.10.0"
[features]
use-dyn-lib = ["libloading", "notify", "lazy_static", "tracing", "find_folder"]
use-dyn-lib = ["lazy_static", "voxygen-dynlib"]
be-dyn-lib = []
simd = ["vek/platform_intrinsics"]
@ -13,11 +13,10 @@ default = ["simd"]
[dependencies]
common = {package = "veloren-common", path = "../../common"}
find_folder = {version = "0.3.0", optional = true}
# inline_tweak = "1.0.2"
lazy_static = {version = "1.4.0", optional = true}
libloading = {version = "0.7", optional = true}
notify = {version = "5.0.0-pre.2", optional = true}
tracing = {version = "0.1", optional = true}
bytemuck = { version = "1.4", features=["derive"] }
vek = {version = "=0.14.1", features = ["serde"]}
bytemuck = { version="1.4", features=["derive"] }
voxygen-dynlib = {package = "veloren-voxygen-dynlib", path = "../dynlib", optional = true}
# Hot Reloading
lazy_static = {version = "1.4.0", optional = true}

View File

@ -8,7 +8,7 @@
//! start earlier since a cdylib doesn't pipeline with it's dependencies.
//!
//! NOTE: the `be-dyn-lib` feature must be used for this crate to be useful, it
//! is not on by default becaue this causes cargo to switch the feature on in
//! is not on by default because this causes cargo to switch the feature on in
//! the anim crate when compiling the static lib into voxygen.
#[cfg(feature = "be-dyn-lib")]
pub use veloren_voxygen_anim::*;

View File

@ -54,7 +54,6 @@ pub mod bird_large;
pub mod bird_medium;
pub mod character;
pub mod dragon;
#[cfg(feature = "use-dyn-lib")] pub mod dyn_lib;
pub mod fish_medium;
pub mod fish_small;
pub mod fixture;
@ -67,14 +66,13 @@ pub mod ship;
pub mod theropod;
pub mod vek;
#[cfg(feature = "use-dyn-lib")]
pub use dyn_lib::init;
#[cfg(feature = "use-dyn-lib")]
use std::ffi::CStr;
use self::vek::*;
use bytemuck::{Pod, Zeroable};
#[cfg(feature = "use-dyn-lib")]
use {
lazy_static::lazy_static, std::ffi::CStr, std::sync::Arc, std::sync::Mutex,
voxygen_dynlib::LoadedLib,
};
type MatRaw = [[f32; 4]; 4];
@ -91,6 +89,15 @@ fn make_bone(mat: Mat4<f32>) -> FigureBoneData {
pub type Bone = Transform<f32, f32, f32>;
#[cfg(feature = "use-dyn-lib")]
lazy_static! {
static ref LIB: Arc<Mutex<Option<LoadedLib>>> =
voxygen_dynlib::init("veloren-voxygen-anim", "veloren-voxygen-anim-dyn", "anim");
}
#[cfg(feature = "use-dyn-lib")]
pub fn init() { lazy_static::initialize(&LIB); }
pub trait Skeleton: Send + Sync + 'static {
type Attr;
type Body;
@ -118,10 +125,11 @@ pub fn compute_matrices<S: Skeleton>(
}
#[cfg(feature = "use-dyn-lib")]
{
let lock = dyn_lib::LIB.lock().unwrap();
let lock = LIB.lock().unwrap();
let lib = &lock.as_ref().unwrap().lib;
let compute_fn: libloading::Symbol<
#[allow(clippy::type_complexity)]
let compute_fn: voxygen_dynlib::Symbol<
fn(&S, Mat4<f32>, &mut [FigureBoneData; MAX_BONE_COUNT]) -> Vec3<f32>,
> = unsafe { lib.get(S::COMPUTE_FN) }.unwrap_or_else(|e| {
panic!(
@ -169,10 +177,11 @@ pub trait Animation {
}
#[cfg(feature = "use-dyn-lib")]
{
let lock = dyn_lib::LIB.lock().unwrap();
let lock = LIB.lock().unwrap();
let lib = &lock.as_ref().unwrap().lib;
let update_fn: libloading::Symbol<
#[allow(clippy::type_complexity)]
let update_fn: voxygen_dynlib::Symbol<
fn(
&Self::Skeleton,
Self::Dependency<'a>,
@ -183,9 +192,8 @@ pub trait Animation {
> = unsafe {
//let start = std::time::Instant::now();
// Overhead of 0.5-5 us (could use hashmap to mitigate if this is an issue)
let f = lib.get(Self::UPDATE_FN);
lib.get(Self::UPDATE_FN)
//println!("{}", start.elapsed().as_nanos());
f
}
.unwrap_or_else(|e| {
panic!(

11
voxygen/dynlib/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "veloren-voxygen-dynlib"
version = "0.1.0"
authors = ["Ben Wallis <atomyc@gmail.com>"]
edition = "2018"
[dependencies]
find_folder = {version = "0.3.0"}
libloading = {version = "0.7"}
notify = {version = "5.0.0-pre.2"}
tracing = "0.1"

View File

@ -1,4 +1,3 @@
use lazy_static::lazy_static;
use libloading::Library;
use notify::{immediate_watcher, EventKind, RecursiveMode, Watcher};
use std::{
@ -8,23 +7,16 @@ use std::{
};
use find_folder::Search;
use std::{env, path::PathBuf};
use std::{
env,
env::consts::{DLL_PREFIX, DLL_SUFFIX},
path::{Path, PathBuf},
sync::Arc,
};
use tracing::{debug, error, info};
#[cfg(target_os = "windows")]
const COMPILED_FILE: &str = "veloren_voxygen_anim_dyn.dll";
#[cfg(target_os = "windows")]
const ACTIVE_FILE: &str = "veloren_voxygen_anim_dyn_active.dll";
#[cfg(not(target_os = "windows"))]
const COMPILED_FILE: &str = "libveloren_voxygen_anim_dyn.so";
#[cfg(not(target_os = "windows"))]
const ACTIVE_FILE: &str = "libveloren_voxygen_anim_dyn_active.so";
// This option is required as `hotreload()` moves the `LoadedLib`.
lazy_static! {
pub static ref LIB: Mutex<Option<LoadedLib>> = Mutex::new(Some(LoadedLib::compile_load()));
}
// Re-exports
pub use libloading::Symbol;
/// LoadedLib holds a loaded dynamic library and the location of library file
/// with the appropriate OS specific name and extension i.e.
@ -45,20 +37,20 @@ impl LoadedLib {
///
/// This is necessary because the very first time you use hot reloading you
/// wont have the library, so you can't load it until you have compiled it!
fn compile_load() -> Self {
fn compile_load(dyn_package: &str) -> Self {
#[cfg(target_os = "macos")]
error!("The hot reloading feature does not work on macos.");
// Compile
if !compile() {
panic!("Animation compile failed.");
if !compile(dyn_package) {
panic!("{} compile failed.", dyn_package);
} else {
info!("Animation compile succeeded.");
info!("{} compile succeeded.", dyn_package);
}
copy(&LoadedLib::determine_path());
copy(&LoadedLib::determine_path(dyn_package), dyn_package);
Self::load()
Self::load(dyn_package)
}
/// Load a library from disk.
@ -66,8 +58,8 @@ impl LoadedLib {
/// Currently this is pretty fragile, it gets the path of where it thinks
/// the dynamic library should be and tries to load it. It will panic if it
/// is missing.
fn load() -> Self {
let lib_path = LoadedLib::determine_path();
fn load(dyn_package: &str) -> Self {
let lib_path = LoadedLib::determine_path(dyn_package);
// Try to load the library.
let lib = match unsafe { Library::new(lib_path.clone()) } {
@ -84,7 +76,7 @@ impl LoadedLib {
/// Determine the path to the dynamic library based on the path of the
/// current executable.
fn determine_path() -> PathBuf {
fn determine_path(dyn_package: &str) -> PathBuf {
let current_exe = env::current_exe();
// If we got the current_exe, we need to go up a level and then down
@ -109,7 +101,7 @@ impl LoadedLib {
Err(e) => {
panic!(
"Could not determine the path of the current executable, this is needed to \
hotreload the dynamic library. {:?}",
hot-reload the dynamic library. {:?}",
e
);
},
@ -117,7 +109,7 @@ impl LoadedLib {
// Determine the platform specific path and push it onto our already
// established target/debug dir.
lib_path.push(ACTIVE_FILE);
lib_path.push(active_file(dyn_package));
lib_path
}
@ -125,13 +117,14 @@ impl LoadedLib {
/// Initialise a watcher.
///
/// The assumption is that this is run from the voxygen crate's root directory
/// as it will watch the relative path `anim` for any changes to `.rs`
/// files. Upon noticing changes it will wait a moment and then recompile.
pub fn init() {
// Make sure first compile is done by accessing the lazy_static and then
// immediately dropping (because we don't actually need it).
drop(LIB.lock());
/// This will search for the directory named `package_source_dir` and watch the
/// files within it for any changes.
pub fn init(
package: &'static str,
dyn_package: &'static str,
package_source_dir: &'static str,
) -> Arc<Mutex<Option<LoadedLib>>> {
let lib_storage = Arc::new(Mutex::new(Some(LoadedLib::compile_load(dyn_package))));
// TODO: use crossbeam
let (reload_send, reload_recv) = mpsc::channel();
@ -139,18 +132,24 @@ pub fn init() {
// Start watcher
let mut watcher = immediate_watcher(move |res| event_fn(res, &reload_send)).unwrap();
// Search for the anim directory.
let anim_dir = Search::Kids(1)
.for_folder("anim")
.expect("Could not find the anim crate directory relative to the current directory");
// Search for the source directory of the package being hot-reloaded.
let watch_dir = Search::Kids(1)
.for_folder(package_source_dir)
.unwrap_or_else(|_| {
panic!(
"Could not find the {} crate directory relative to the current directory",
package_source_dir
)
});
watcher.watch(anim_dir, RecursiveMode::Recursive).unwrap();
watcher.watch(watch_dir, RecursiveMode::Recursive).unwrap();
// Start reloader that watcher signals
// "Debounces" events since I can't find the option to do this in the latest
// `notify`
let lib_storage_clone = Arc::clone(&lib_storage);
std::thread::Builder::new()
.name("voxygen_anim_watcher".into())
.name(format!("{}_hotreload_watcher", package))
.spawn(move || {
let mut modified_paths = std::collections::HashSet::new();
while let Ok(path) = reload_recv.recv() {
@ -162,16 +161,32 @@ pub fn init() {
info!(
?modified_paths,
"Hot reloading animations because files in `anim` modified."
"Hot reloading {} because files in `{}` modified.", package, package_source_dir
);
hotreload();
hotreload(dyn_package, &lib_storage_clone);
}
})
.unwrap();
// Let the watcher live forever
std::mem::forget(watcher);
lib_storage
}
fn compiled_file(dyn_package: &str) -> String { dyn_lib_file(dyn_package, false) }
fn active_file(dyn_package: &str) -> String { dyn_lib_file(dyn_package, true) }
fn dyn_lib_file(dyn_package: &str, active: bool) -> String {
format!(
"{}{}{}{}",
DLL_PREFIX,
dyn_package.replace("-", "_"),
if active { "_active" } else { "" },
DLL_SUFFIX
)
}
/// Event function to hotreload the dynamic library
@ -180,8 +195,8 @@ pub fn init() {
/// before sending them back.
fn event_fn(res: notify::Result<notify::Event>, sender: &mpsc::Sender<String>) {
match res {
Ok(event) => match event.kind {
EventKind::Modify(_) => {
Ok(event) => {
if let EventKind::Modify(_) = event.kind {
event
.paths
.iter()
@ -189,10 +204,9 @@ fn event_fn(res: notify::Result<notify::Event>, sender: &mpsc::Sender<String>) {
.map(|p| p.to_string_lossy().into_owned())
// Signal reloader
.for_each(|p| { let _ = sender.send(p); });
},
_ => {},
}
},
Err(e) => error!(?e, "Animation hotreload watcher error."),
Err(e) => error!(?e, "hotreload watcher error."),
}
}
@ -200,35 +214,35 @@ fn event_fn(res: notify::Result<notify::Event>, sender: &mpsc::Sender<String>) {
///
/// This will reload the dynamic library by first internally calling compile
/// and then reloading the library.
fn hotreload() {
fn hotreload(dyn_package: &str, loaded_lib: &Mutex<Option<LoadedLib>>) {
// Do nothing if recompile failed.
if compile() {
let mut lock = LIB.lock().unwrap();
if compile(dyn_package) {
let mut lock = loaded_lib.lock().unwrap();
// Close lib.
let loaded_lib = lock.take().unwrap();
loaded_lib.lib.close().unwrap();
copy(&loaded_lib.lib_path);
copy(&loaded_lib.lib_path, dyn_package);
// Open new lib.
*lock = Some(LoadedLib::load());
*lock = Some(LoadedLib::load(dyn_package));
info!("Updated animations.");
info!("Updated {}.", dyn_package);
}
}
/// Recompile the anim package
/// Recompile the dyn package
///
/// Returns `false` if the compile failed.
fn compile() -> bool {
fn compile(dyn_package: &str) -> bool {
let output = Command::new("cargo")
.stderr(Stdio::inherit())
.stdout(Stdio::inherit())
.arg("build")
.arg("--package")
.arg("veloren-voxygen-anim-dyn")
.arg(dyn_package)
.arg("--features")
.arg("veloren-voxygen-anim-dyn/be-dyn-lib")
.arg(format!("{}/be-dyn-lib", dyn_package))
.output()
.unwrap();
@ -239,10 +253,10 @@ fn compile() -> bool {
///
/// We do this for all OS's although it is only strictly necessary for windows.
/// The reason we do this is to make the code easier to understand and debug.
fn copy(lib_path: &PathBuf) {
fn copy(lib_path: &Path, dyn_package: &str) {
// Use the platform specific names.
let lib_compiled_path = lib_path.with_file_name(COMPILED_FILE);
let lib_output_path = lib_path.with_file_name(ACTIVE_FILE);
let lib_compiled_path = lib_path.with_file_name(compiled_file(dyn_package));
let lib_output_path = lib_path.with_file_name(active_file(dyn_package));
// Get the path to where the lib was compiled to.
debug!(?lib_compiled_path, ?lib_output_path, "Moving.");

20
voxygen/egui/Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
authors = ["Ben Wallis <atomyc@gmail.com>"]
name = "veloren-voxygen-egui"
edition = "2018"
version = "0.9.0"
[features]
use-dyn-lib = ["lazy_static", "voxygen-dynlib"]
be-dyn-lib = []
[dependencies]
client = {package = "veloren-client", path = "../../client"}
common = {package = "veloren-common", path = "../../common"}
egui = "0.12"
egui_winit_platform = "0.8"
voxygen-dynlib = {package = "veloren-voxygen-dynlib", path = "../dynlib", optional = true}
# Hot Reloading
lazy_static = {version = "1.4.0", optional = true}

View File

@ -0,0 +1,14 @@
[package]
authors = ["Imbris <imbrisf@gmail.com>"]
edition = "2018"
name = "veloren-voxygen-egui-dyn"
version = "0.9.0"
[lib]
crate-type = ["dylib"]
[features]
be-dyn-lib = ["veloren-voxygen-egui/be-dyn-lib"]
[dependencies]
veloren-voxygen-egui = { path = "../" }

View File

@ -0,0 +1,14 @@
//! This crate hacks around the inability to dynamically specify the
//! `crate-type` for cargo to build.
//!
//! For more details on the issue this is a decent starting point: https://github.com/rust-lang/cargo/pull/8789
//!
//! This crate avoids use building the dynamic lib when it isn't needed and the
//! same with the non dynamic build. Additionally, this allows compilation to
//! start earlier since a cdylib doesn't pipeline with it's dependencies.
//!
//! NOTE: the `be-dyn-lib` feature must be used for this crate to be useful, it
//! is not on by default because this causes cargo to switch the feature on in
//! the anim crate when compiling the static lib into voxygen.
#[cfg(feature = "be-dyn-lib")]
pub use veloren_voxygen_egui::*;

View File

@ -0,0 +1,87 @@
use crate::{two_col_row, SelectedEntityInfo};
use common::{
comp::CharacterState,
states::{charged_melee, combo_melee, dash_melee, leap_melee},
};
use egui::{Grid, Ui};
pub fn draw_char_state_group(
ui: &mut Ui,
_selected_entity_info: &SelectedEntityInfo,
character_state: &CharacterState,
) {
ui.horizontal(|ui| {
ui.label("Current State: ");
ui.label(character_state.to_string());
});
match character_state {
CharacterState::ComboMelee(data) => {
combo_melee_grid(ui, data);
},
CharacterState::DashMelee(data) => dash_melee_grid(ui, data),
CharacterState::ChargedMelee(data) => charged_melee_grid(ui, data),
// Character states with no associated data to display
CharacterState::Dance
| CharacterState::Idle
| CharacterState::Sit
| CharacterState::GlideWield
| CharacterState::Sneak
| CharacterState::Talk
| CharacterState::Wielding => {},
CharacterState::LeapMelee(data) => leap_melee_grid(ui, data),
_ => {
ui.label("<Rendering not yet implemented for this state>");
},
};
}
fn charged_melee_grid(ui: &mut Ui, data: &charged_melee::Data) {
Grid::new("selected_entity_charged_melee_grid")
.spacing([40.0, 4.0])
.max_col_width(100.0)
.striped(true)
.show(ui, |ui| #[rustfmt::skip] {
two_col_row(ui, "Stage Section", data.stage_section.to_string());
two_col_row(ui, "Timer", format!("{}ms", data.timer.as_millis()));
two_col_row(ui, "Charge Amount", format!("{:.1}", data.charge_amount));
two_col_row(ui, "Exhausted", if data.exhausted { "True" } else { "False" });
});
}
fn combo_melee_grid(ui: &mut Ui, data: &combo_melee::Data) {
Grid::new("selected_entity_combo_melee_grid")
.spacing([40.0, 4.0])
.max_col_width(100.0)
.striped(true)
.show(ui, |ui| #[rustfmt::skip] {
two_col_row(ui, "Stage", data.stage.to_string());
two_col_row(ui, "Timer", format!("{}ms", data.timer.as_millis()));
two_col_row(ui, "num_stages", data.static_data.num_stages.to_string());
});
}
fn dash_melee_grid(ui: &mut Ui, data: &dash_melee::Data) {
Grid::new("selected_entity_dash_melee_grid")
.spacing([40.0, 4.0])
.max_col_width(100.0)
.striped(true)
.show(ui, |ui| #[rustfmt::skip] {
two_col_row(ui, "Auto Charge", if data.auto_charge { "True" } else { "False " });
two_col_row(ui, "Timer", format!("{}ms", data.timer.as_millis()));
two_col_row(ui, "Stage Section", data.stage_section.to_string());
two_col_row(ui, "Exhausted", if data.exhausted { "True" } else { "False " });
two_col_row(ui, "Charge End Timer", format!("{}ms", data.charge_end_timer.as_millis()));
});
}
fn leap_melee_grid(ui: &mut Ui, data: &leap_melee::Data) {
Grid::new("selected_entity_leap_melee_grid")
.spacing([40.0, 4.0])
.max_col_width(100.0)
.striped(true)
.show(ui, |ui| #[rustfmt::skip] {
two_col_row(ui, "Stage Section", data.stage_section.to_string());
two_col_row(ui, "Timer", format!("{}ms", data.timer.as_millis()));
two_col_row(ui, "Exhausted", if data.exhausted { "True" } else { "False " });
});
}

697
voxygen/egui/src/lib.rs Normal file
View File

@ -0,0 +1,697 @@
#![feature(stmt_expr_attributes)]
#[cfg(all(feature = "be-dyn-lib", feature = "use-dyn-lib"))]
compile_error!("Can't use both \"be-dyn-lib\" and \"use-dyn-lib\" features at once");
mod character_states;
use client::{Client, Join, World, WorldExt};
use common::{
comp,
comp::{Poise, PoiseState},
};
use core::mem;
use egui::{
plot::{Plot, Value},
widgets::plot::Curve,
CollapsingHeader, Color32, Grid, Label, ScrollArea, Slider, Ui, Window,
};
fn two_col_row(ui: &mut Ui, label: impl Into<Label>, content: impl Into<Label>) {
ui.label(label);
ui.label(content);
ui.end_row();
}
use crate::character_states::draw_char_state_group;
use common::comp::{aura::AuraKind::Buff, Body, Fluid};
use egui_winit_platform::Platform;
use std::time::Duration;
#[cfg(feature = "use-dyn-lib")]
use {
lazy_static::lazy_static, std::ffi::CStr, std::sync::Arc, std::sync::Mutex,
voxygen_dynlib::LoadedLib,
};
#[cfg(feature = "use-dyn-lib")]
lazy_static! {
static ref LIB: Arc<Mutex<Option<LoadedLib>>> =
voxygen_dynlib::init("veloren-voxygen-egui", "veloren-voxygen-egui-dyn", "egui");
}
#[cfg(feature = "use-dyn-lib")]
const MAINTAIN_EGUI_FN: &[u8] = b"maintain_egui_inner\0";
pub struct SelectedEntityInfo {
entity_id: u32,
debug_shape_id: Option<u64>,
character_state_history: Vec<String>,
}
impl SelectedEntityInfo {
fn new(entity_id: u32) -> Self {
Self {
entity_id,
debug_shape_id: None,
character_state_history: Vec::new(),
}
}
}
pub struct EguiDebugInfo {
pub frame_time: Duration,
pub ping_ms: f64,
}
pub struct EguiInnerState {
selected_entity_info: Option<SelectedEntityInfo>,
max_entity_distance: f32,
selected_entity_cylinder_height: f32,
frame_times: Vec<f32>,
}
#[derive(Clone, Default)]
pub struct EguiWindows {
egui_inspection: bool,
egui_settings: bool,
egui_memory: bool,
frame_time: bool,
ecs_entities: bool,
}
impl Default for EguiInnerState {
fn default() -> Self {
Self {
selected_entity_info: None,
max_entity_distance: 100000.0,
selected_entity_cylinder_height: 10.0,
frame_times: Vec::new(),
}
}
}
pub enum DebugShapeAction {
AddCylinder {
radius: f32,
height: f32,
},
RemoveShape(u64),
SetPosAndColor {
id: u64,
pos: [f32; 4],
color: [f32; 4],
},
}
#[derive(Default)]
pub struct EguiActions {
pub actions: Vec<DebugShapeAction>,
}
#[cfg(feature = "use-dyn-lib")]
pub fn init() { lazy_static::initialize(&LIB); }
pub fn maintain(
platform: &mut Platform,
egui_state: &mut EguiInnerState,
egui_windows: &mut EguiWindows,
client: &Client,
debug_info: Option<EguiDebugInfo>,
added_cylinder_shape_id: Option<u64>,
) -> EguiActions {
#[cfg(not(feature = "use-dyn-lib"))]
{
maintain_egui_inner(
platform,
egui_state,
egui_windows,
client,
debug_info,
added_cylinder_shape_id,
)
}
#[cfg(feature = "use-dyn-lib")]
{
let lock = LIB.lock().unwrap();
let lib = &lock.as_ref().unwrap().lib;
#[allow(clippy::type_complexity)]
let maintain_fn: voxygen_dynlib::Symbol<
fn(
&mut Platform,
&mut EguiInnerState,
&mut EguiWindows,
&Client,
Option<EguiDebugInfo>,
Option<u64>,
) -> EguiActions,
> = unsafe { lib.get(MAINTAIN_EGUI_FN) }.unwrap_or_else(|e| {
panic!(
"Trying to use: {} but had error: {:?}",
CStr::from_bytes_with_nul(MAINTAIN_EGUI_FN)
.map(CStr::to_str)
.unwrap()
.unwrap(),
e
)
});
maintain_fn(
platform,
egui_state,
egui_windows,
client,
debug_info,
added_cylinder_shape_id,
)
}
}
#[cfg_attr(feature = "be-dyn-lib", export_name = "maintain_egui_inner")]
pub fn maintain_egui_inner(
platform: &mut Platform,
egui_state: &mut EguiInnerState,
egui_windows: &mut EguiWindows,
client: &Client,
debug_info: Option<EguiDebugInfo>,
added_cylinder_shape_id: Option<u64>,
) -> EguiActions {
platform.begin_frame();
let ctx = &platform.context();
let mut egui_actions = EguiActions::default();
let mut previous_selected_entity: Option<SelectedEntityInfo> = None;
let mut max_entity_distance = egui_state.max_entity_distance;
let mut selected_entity_cylinder_height = egui_state.selected_entity_cylinder_height;
// If a debug cylinder was added in the last frame, store it against the
// selected entity
if let Some(shape_id) = added_cylinder_shape_id {
if let Some(selected_entity) = &mut egui_state.selected_entity_info {
selected_entity.debug_shape_id = Some(shape_id);
}
}
if let Some(debug_info) = debug_info.as_ref() {
egui_state
.frame_times
.push(debug_info.frame_time.as_nanos() as f32);
if egui_state.frame_times.len() > 250 {
egui_state.frame_times.remove(0);
}
};
egui::Window::new("Debug Control")
.default_width(200.0)
.default_height(200.0)
.show(&platform.context(), |ui| {
ui.horizontal(|ui| {
ui.label(format!(
"Ping: {:.1}ms",
debug_info.as_ref().map_or(0.0, |x| x.ping_ms)
));
});
ui.group(|ui| {
ui.vertical(|ui| {
ui.checkbox(&mut egui_windows.ecs_entities, "ECS Entities");
ui.checkbox(&mut egui_windows.frame_time, "Frame Time");
});
});
ui.group(|ui| {
ui.vertical(|ui| {
ui.label("Show EGUI Windows");
ui.horizontal(|ui| {
ui.checkbox(&mut egui_windows.egui_inspection, "🔍 Inspection");
ui.checkbox(&mut egui_windows.egui_settings, "🔧 Settings");
ui.checkbox(&mut egui_windows.egui_memory, "📝 Memory");
})
})
});
});
Window::new("🔧 Settings")
.open(&mut egui_windows.egui_settings)
.scroll(true)
.show(ctx, |ui| {
ctx.settings_ui(ui);
});
Window::new("🔍 Inspection")
.open(&mut egui_windows.egui_inspection)
.scroll(true)
.show(ctx, |ui| {
ctx.inspection_ui(ui);
});
Window::new("📝 Memory")
.open(&mut egui_windows.egui_memory)
.resizable(false)
.show(ctx, |ui| {
ctx.memory_ui(ui);
});
Window::new("Frame Time")
.open(&mut egui_windows.frame_time)
.default_width(200.0)
.default_height(200.0)
.show(ctx, |ui| {
let plot = Plot::new("Frame Time").curve(Curve::from_values_iter(
egui_state
.frame_times
.iter()
.enumerate()
.map(|(i, x)| Value::new(i as f64, *x)),
));
ui.add(plot);
});
if egui_windows.ecs_entities {
let ecs = client.state().ecs();
let positions = client.state().ecs().read_storage::<comp::Pos>();
let client_pos = positions.get(client.entity());
egui::Window::new("ECS Entities")
.open(&mut egui_windows.ecs_entities)
.default_width(500.0)
.default_height(500.0)
.show(ctx, |ui| {
ui.label(format!("Entity count: {}", &ecs.entities().join().count()));
ui.add(
Slider::new(&mut max_entity_distance, 1.0..=100000.0)
.logarithmic(true)
.clamp_to_range(true)
.text("Max entity distance"),
);
ui.add(
Slider::new(&mut selected_entity_cylinder_height, 0.1..=100.0)
.logarithmic(true)
.clamp_to_range(true)
.text("Cylinder height"),
);
let scroll_area = ScrollArea::from_max_height(800.0);
let (_current_scroll, _max_scroll) = scroll_area.show(ui, |ui| {
Grid::new("entities_grid")
.spacing([40.0, 4.0])
.max_col_width(300.0)
.striped(true)
.show(ui, |ui| {
ui.label("-");
ui.label("ID");
ui.label("Pos");
ui.label("Vel");
ui.label("Name");
ui.label("Body");
ui.label("Poise");
ui.label("Character State");
ui.end_row();
for (entity, body, stats, pos, _ori, vel, poise, character_state) in (
&ecs.entities(),
ecs.read_storage::<comp::Body>().maybe(),
ecs.read_storage::<comp::Stats>().maybe(),
ecs.read_storage::<comp::Pos>().maybe(),
ecs.read_storage::<comp::Ori>().maybe(),
ecs.read_storage::<comp::Vel>().maybe(),
ecs.read_storage::<comp::Poise>().maybe(),
ecs.read_storage::<comp::CharacterState>().maybe(),
)
.join()
.filter(|(_, _, _, pos, _, _, _, _)| {
client_pos.map_or(true, |client_pos| {
pos.map_or(0.0, |pos| pos.0.distance_squared(client_pos.0))
< max_entity_distance
})
})
{
if ui.button("View").clicked() {
previous_selected_entity =
mem::take(&mut egui_state.selected_entity_info);
if pos.is_some() {
egui_actions.actions.push(DebugShapeAction::AddCylinder {
radius: 1.0,
height: egui_state.selected_entity_cylinder_height,
});
}
egui_state.selected_entity_info =
Some(SelectedEntityInfo::new(entity.id()));
}
ui.label(format!("{}", entity.id()));
if let Some(pos) = pos {
ui.label(format!(
"{:.0},{:.0},{:.0}",
pos.0.x, pos.0.y, pos.0.z
));
} else {
ui.label("-");
}
if let Some(vel) = vel {
ui.label(format!("{:.1}u/s", vel.0.magnitude()));
} else {
ui.label("-");
}
if let Some(stats) = stats {
ui.label(&stats.name);
} else {
ui.label("-");
}
if let Some(body) = body {
ui.label(body_species(&body));
} else {
ui.label("-");
}
if let Some(poise) = poise {
poise_state_label(ui, poise);
} else {
ui.label("-");
}
if let Some(character_state) = character_state {
ui.label(character_state.to_string());
} else {
ui.label("-");
}
ui.end_row();
}
});
let margin = ui.visuals().clip_rect_margin;
let current_scroll = ui.clip_rect().top() - ui.min_rect().top() + margin;
let max_scroll =
ui.min_rect().height() - ui.clip_rect().height() + 2.0 * margin;
(current_scroll, max_scroll)
});
});
if let Some(selected_entity_info) = &mut egui_state.selected_entity_info {
let selected_entity = ecs.entities().entity(selected_entity_info.entity_id);
if !selected_entity.gen().is_alive() {
previous_selected_entity = mem::take(&mut egui_state.selected_entity_info);
} else {
selected_entity_window(platform, ecs, selected_entity_info, &mut egui_actions);
}
}
}
if let Some(previous) = previous_selected_entity {
if let Some(debug_shape_id) = previous.debug_shape_id {
egui_actions
.actions
.push(DebugShapeAction::RemoveShape(debug_shape_id));
}
};
if let Some(selected_entity) = &egui_state.selected_entity_info {
if let Some(debug_shape_id) = selected_entity.debug_shape_id {
if (egui_state.selected_entity_cylinder_height - selected_entity_cylinder_height).abs()
> f32::EPSILON
{
egui_actions
.actions
.push(DebugShapeAction::RemoveShape(debug_shape_id));
egui_actions.actions.push(DebugShapeAction::AddCylinder {
radius: 1.0,
height: selected_entity_cylinder_height,
});
}
}
};
egui_state.max_entity_distance = max_entity_distance;
egui_state.selected_entity_cylinder_height = selected_entity_cylinder_height;
egui_actions
}
fn selected_entity_window(
platform: &mut Platform,
ecs: &World,
selected_entity_info: &mut SelectedEntityInfo,
egui_actions: &mut EguiActions,
) {
let entity_id = selected_entity_info.entity_id;
for (
_entity,
body,
stats,
pos,
_ori,
vel,
poise,
buffs,
auras,
character_state,
physics_state,
alignment,
scale,
mass,
(density, health, energy),
) in (
&ecs.entities(),
ecs.read_storage::<comp::Body>().maybe(),
ecs.read_storage::<comp::Stats>().maybe(),
ecs.read_storage::<comp::Pos>().maybe(),
ecs.read_storage::<comp::Ori>().maybe(),
ecs.read_storage::<comp::Vel>().maybe(),
ecs.read_storage::<comp::Poise>().maybe(),
ecs.read_storage::<comp::Buffs>().maybe(),
ecs.read_storage::<comp::Auras>().maybe(),
ecs.read_storage::<comp::CharacterState>().maybe(),
ecs.read_storage::<comp::PhysicsState>().maybe(),
ecs.read_storage::<comp::Alignment>().maybe(),
ecs.read_storage::<comp::Scale>().maybe(),
ecs.read_storage::<comp::Mass>().maybe(),
(
ecs.read_storage::<comp::Density>().maybe(),
ecs.read_storage::<comp::Health>().maybe(),
ecs.read_storage::<comp::Energy>().maybe(),
),
)
.join()
.filter(|(e, _, _, _, _, _, _, _, _, _, _, _, _, _, (_, _, _))| e.id() == entity_id)
{
if let Some(pos) = pos {
if let Some(shape_id) = selected_entity_info.debug_shape_id {
egui_actions.actions.push(DebugShapeAction::SetPosAndColor {
id: shape_id,
color: [1.0, 1.0, 0.0, 0.5],
pos: [pos.0.x, pos.0.y, pos.0.z + 2.0, 0.0],
});
}
};
egui::Window::new("Selected Entity")
.default_width(300.0)
.default_height(200.0)
.show(&platform.context(), |ui| {
ui.vertical(|ui| {
CollapsingHeader::new("General").default_open(true).show(ui, |ui| {
Grid::new("selected_entity_general_grid")
.spacing([40.0, 4.0])
.striped(true)
.show(ui, |ui| {
two_col_row(ui, "Health", health.map_or("-".to_owned(), |x| format!("{:.1}/{:.1}", x.current(), x.maximum())));
two_col_row(ui, "Energy", energy.map_or("-".to_owned(), |x| format!("{:.1}/{:.1}", x.current(), x.maximum())));
two_col_row(ui, "Position", pos.map_or("-".to_owned(), |x| format!("({:.1},{:.1},{:.1})", x.0.x, x.0.y, x.0.z)));
two_col_row(ui, "Velocity", vel.map_or("-".to_owned(), |x| format!("({:.1},{:.1},{:.1}) ({:.1} u/s)", x.0.x, x.0.y, x.0.z, x.0.magnitude())));
two_col_row(ui, "Alignment", alignment.map_or("-".to_owned(), |x| format!("{:?}", x)));
two_col_row(ui, "Scale", scale.map_or("-".to_owned(), |x| format!("{:?}", x)));
two_col_row(ui, "Mass", mass.map_or("-".to_owned(), |x| format!("{:.1}", x.0)));
two_col_row(ui, "Density", density.map_or("-".to_owned(), |x| format!("{:.1}", x.0)));
});
});
if let Some(stats) = stats {
CollapsingHeader::new("Stats").default_open(true).show(ui, |ui| {
Grid::new("selected_entity_stats_grid")
.spacing([40.0, 4.0])
.striped(true)
.show(ui, |ui| {
two_col_row(ui, "Name", stats.name.to_string());
two_col_row(ui, "Damage Reduction", format!("{:.1}", stats.damage_reduction));
two_col_row(ui, "Max Health Modifier", format!("{:.1}", stats.max_health_modifier));
two_col_row(ui, "Move Speed Modifier", format!("{:.1}", stats.move_speed_modifier));
});
});
}
if let Some(body) = body {
CollapsingHeader::new("Body").default_open(false).show(ui, |ui| {
Grid::new("selected_entity_body_grid")
.spacing([40.0, 4.0])
.striped(true)
.show(ui, |ui| {
two_col_row(ui, "Type", body.to_string());
two_col_row(ui, "Species", body_species(body));
});
});
}
if let Some(pos) = pos {
CollapsingHeader::new("Pos").default_open(false).show(ui, |ui| {
Grid::new("selected_entity_pos_grid")
.spacing([40.0, 4.0])
.max_col_width(100.0)
.striped(true)
.show(ui, |ui| {
two_col_row(ui, "x", format!("{:.1}", pos.0.x));
two_col_row(ui, "y", format!("{:.1}", pos.0.y));
two_col_row(ui, "z", format!("{:.1}", pos.0.z));
});
});
}
if let Some(poise) = poise {
CollapsingHeader::new("Poise").default_open(false).show(ui, |ui| {
Grid::new("selected_entity_poise_grid")
.spacing([40.0, 4.0])
.max_col_width(100.0)
.striped(true)
.show(ui, |ui| #[rustfmt::skip] {
ui.label("State");
poise_state_label(ui, poise);
ui.end_row();
two_col_row(ui, "Current", format!("{}/{}", poise.current(), poise.maximum()));
two_col_row(ui, "Base Max", poise.base_max().to_string());
});
});
}
if let Some(buffs) = buffs {
CollapsingHeader::new("Buffs").default_open(false).show(ui, |ui| {
Grid::new("selected_entity_buffs_grid")
.spacing([40.0, 4.0])
.max_col_width(100.0)
.striped(true)
.show(ui, |ui| {
ui.label("Kind");
ui.label("Time");
ui.label("Source");
ui.end_row();
buffs.buffs.iter().for_each(|(_, v)| {
ui.label(format!("{:?}", v.kind));
ui.label(
v.time.map_or("-".to_string(), |time| {
format!("{:?}", time)
}),
);
ui.label(format!("{:?}", v.source));
ui.end_row();
});
});
});
}
if let Some(auras) = auras {
CollapsingHeader::new("Auras").default_open(false).show(ui, |ui| {
Grid::new("selected_entity_auras_grid")
.spacing([40.0, 4.0])
.striped(true)
.show(ui, |ui| {
ui.label("Kind");
ui.label("Radius");
ui.label("Duration");
ui.label("Target");
ui.end_row();
auras.auras.iter().for_each(|(_, v)| {
ui.label(match v.aura_kind {
Buff { kind, .. } => format!("Buff - {:?}", kind)
});
ui.label(format!("{:1}", v.radius));
ui.label(v.duration.map_or("-".to_owned(), |x| format!("{:1}s", x.as_secs())));
ui.label(format!("{:?}", v.target));
ui.end_row();
});
});
});
}
if let Some(character_state) = character_state {
if selected_entity_info
.character_state_history
.first()
.unwrap_or(&"-".to_owned())
!= &character_state.to_string()
{
selected_entity_info
.character_state_history
.insert(0, character_state.to_string());
if selected_entity_info.character_state_history.len() > 50 {
selected_entity_info.character_state_history.pop();
}
}
CollapsingHeader::new("Character State").default_open(false).show(ui, |ui| {
draw_char_state_group(ui, selected_entity_info, character_state);
});
}
if let Some(physics_state) = physics_state {
CollapsingHeader::new("Physics State").default_open(false).show(ui, |ui| {
Grid::new("selected_entity_physics_state_grid")
.spacing([40.0, 4.0])
.striped(true)
.show(ui, |ui| {
two_col_row(ui, "On Ground", physics_state.on_ground.map_or("None".to_owned(), |x| format!("{:?}", x)));
two_col_row(ui, "On Ceiling", (if physics_state.on_ceiling { "True" } else { "False " }).to_string());
two_col_row(ui, "On Wall", physics_state.on_wall.map_or("-".to_owned(), |x| format!("{:.1},{:.1},{:.1}", x.x, x.y, x.z )));
two_col_row(ui, "Touching Entities", physics_state.touch_entities.len().to_string());
two_col_row(ui, "In Fluid", match physics_state.in_fluid {
Some(Fluid::Air { elevation, .. }) => format!("Air (Elevation: {:.1})", elevation),
Some(Fluid::Liquid { depth, kind, .. }) => format!("{:?} (Depth: {:.1})", kind, depth),
_ => "None".to_owned() });
});
});
}
});
});
}
}
fn body_species(body: &Body) -> String {
match body {
Body::Humanoid(body) => format!("{:?}", body.species),
Body::QuadrupedSmall(body) => format!("{:?}", body.species),
Body::QuadrupedMedium(body) => format!("{:?}", body.species),
Body::BirdMedium(body) => format!("{:?}", body.species),
Body::FishMedium(body) => format!("{:?}", body.species),
Body::Dragon(body) => format!("{:?}", body.species),
Body::BirdLarge(body) => format!("{:?}", body.species),
Body::FishSmall(body) => format!("{:?}", body.species),
Body::BipedLarge(body) => format!("{:?}", body.species),
Body::BipedSmall(body) => format!("{:?}", body.species),
Body::Object(body) => format!("{:?}", body),
Body::Golem(body) => format!("{:?}", body.species),
Body::Theropod(body) => format!("{:?}", body.species),
Body::QuadrupedLow(body) => format!("{:?}", body.species),
Body::Ship(body) => format!("{:?}", body),
}
}
fn poise_state_label(ui: &mut Ui, poise: &Poise) {
match poise.poise_state() {
PoiseState::Normal => {
ui.label("Normal");
},
PoiseState::Interrupted => {
ui.colored_label(Color32::YELLOW, "Interrupted");
},
PoiseState::Stunned => {
ui.colored_label(Color32::RED, "Stunned");
},
PoiseState::Dazed => {
ui.colored_label(Color32::RED, "Dazed");
},
PoiseState::KnockedDown => {
ui.colored_label(Color32::BLUE, "Knocked Down");
},
};
}

View File

@ -38,10 +38,12 @@ pub use i18n;
#[cfg(feature = "singleplayer")]
use crate::singleplayer::Singleplayer;
#[cfg(feature = "egui-ui")]
use crate::ui::egui::EguiState;
use crate::{
audio::AudioFrontend,
profile::Profile,
render::Renderer,
render::{Drawer, GlobalsBindGroup},
settings::Settings,
window::{Event, Window},
};
@ -54,6 +56,8 @@ pub struct GlobalState {
pub settings: Settings,
pub profile: Profile,
pub window: Window,
#[cfg(feature = "egui-ui")]
pub egui_state: EguiState,
pub lazy_init: scene::terrain::SpriteRenderContextLazy,
pub audio: AudioFrontend,
pub info_message: Option<String>,
@ -67,6 +71,8 @@ pub struct GlobalState {
// enter the game before confirmation of successful character load
/// An error returned by Client that needs to be displayed by the UI
pub client_error: Option<String>,
// Used to clear the shadow textures when entering a PlayState that doesn't utilise shadows
pub clear_shadows_next_frame: bool,
}
impl GlobalState {
@ -136,6 +142,11 @@ pub trait PlayState {
/// Determines whether the play state should have an enforced FPS cap
fn capped_fps(&self) -> bool;
fn globals_bind_group(&self) -> &GlobalsBindGroup;
/// Draw the play state.
fn render(&mut self, renderer: &mut Renderer, settings: &Settings);
fn render<'a>(&'a self, drawer: &mut Drawer<'a>, settings: &Settings);
/// Determines whether egui will be rendered for this play state
fn egui_enabled(&self) -> bool;
}

View File

@ -20,6 +20,8 @@ use common::{
};
use std::panic;
use tracing::{error, info, warn};
#[cfg(feature = "egui-ui")]
use veloren_voxygen::ui::egui::EguiState;
#[allow(clippy::manual_unwrap_or)]
fn main() {
@ -145,9 +147,17 @@ fn main() {
assets::start_hot_reloading();
// Initialise watcher for animation hotreloading
// Initialise watcher for animation hot-reloading
#[cfg(feature = "hot-anim")]
anim::init();
{
anim::init();
}
// Initialise watcher for egui hot-reloading
#[cfg(feature = "hot-egui")]
{
voxygen_egui::init();
}
// Setup audio
let mut audio = match settings.audio.output {
@ -184,10 +194,15 @@ fn main() {
let lazy_init = SpriteRenderContext::new(window.renderer_mut());
#[cfg(feature = "egui-ui")]
let egui_state = EguiState::new(&window);
let global_state = GlobalState {
audio,
profile,
window,
#[cfg(feature = "egui-ui")]
egui_state,
lazy_init,
clock: Clock::new(std::time::Duration::from_secs_f64(
1.0 / get_fps(settings.graphics.max_fps) as f64,
@ -199,6 +214,7 @@ fn main() {
i18n,
clipboard,
client_error: None,
clear_shadows_next_frame: false,
};
run::run(global_state, event_loop);

View File

@ -1,7 +1,7 @@
mod ui;
use crate::{
render::Renderer,
render::{Drawer, GlobalsBindGroup},
scene::simple::{self as scene, Scene},
session::SessionState,
settings::Settings,
@ -20,7 +20,6 @@ pub struct CharSelectionState {
char_selection_ui: CharSelectionUi,
client: Rc<RefCell<Client>>,
scene: Scene,
need_shadow_clear: bool,
}
impl CharSelectionState {
@ -37,7 +36,6 @@ impl CharSelectionState {
char_selection_ui,
client,
scene,
need_shadow_clear: false,
}
}
@ -75,7 +73,7 @@ impl PlayState for CharSelectionState {
.set_scale_mode(global_state.settings.interface.ui_scale);
// Clear shadow textures since we don't render to them here
self.need_shadow_clear = true;
global_state.clear_shadows_next_frame = true;
}
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<WinEvent>) -> PlayStateResult {
@ -234,21 +232,9 @@ impl PlayState for CharSelectionState {
fn capped_fps(&self) -> bool { true }
fn render(&mut self, renderer: &mut Renderer, _: &Settings) {
let mut drawer = match renderer
.start_recording_frame(self.scene.global_bind_group())
.expect("Unrecoverable render error when starting a new frame!")
{
Some(d) => d,
// Couldn't get swap chain texture this fime
None => return,
};
if self.need_shadow_clear {
drawer.clear_shadows();
self.need_shadow_clear = false;
}
fn globals_bind_group(&self) -> &GlobalsBindGroup { self.scene.global_bind_group() }
fn render<'a>(&'a self, drawer: &mut Drawer<'a>, _: &Settings) {
let client = self.client.borrow();
let (humanoid_body, loadout) =
Self::get_humanoid_body_inventory(&self.char_selection_ui, &client);
@ -270,4 +256,6 @@ impl PlayState for CharSelectionState {
self.char_selection_ui.render(&mut ui_drawer);
};
}
fn egui_enabled(&self) -> bool { false }
}

View File

@ -6,8 +6,11 @@ use super::char_selection::CharSelectionState;
#[cfg(feature = "singleplayer")]
use crate::singleplayer::Singleplayer;
use crate::{
i18n::LocalizationHandle, render::Renderer, settings::Settings, window::Event, Direction,
GlobalState, PlayState, PlayStateResult,
i18n::LocalizationHandle,
render::{Drawer, GlobalsBindGroup},
settings::Settings,
window::Event,
Direction, GlobalState, PlayState, PlayStateResult,
};
use client::{
addr::ConnectionArgs,
@ -319,21 +322,17 @@ impl PlayState for MainMenuState {
fn capped_fps(&self) -> bool { true }
fn render(&mut self, renderer: &mut Renderer, _: &Settings) {
let mut drawer = match renderer
.start_recording_frame(self.scene.global_bind_group())
.expect("Unrecoverable render error when starting a new frame!")
{
Some(d) => d,
// Couldn't get swap chain texture this frame
None => return,
};
fn globals_bind_group(&self) -> &GlobalsBindGroup { self.scene.global_bind_group() }
fn render<'a>(&'a self, drawer: &mut Drawer<'a>, _: &Settings) {
// Draw the UI to the screen.
if let Some(mut ui_drawer) = drawer.third_pass().draw_ui() {
let mut third_pass = drawer.third_pass();
if let Some(mut ui_drawer) = third_pass.draw_ui() {
self.main_menu_ui.render(&mut ui_drawer);
};
}
fn egui_enabled(&self) -> bool { false }
}
fn get_client_msg_error(

View File

@ -30,6 +30,8 @@ use super::{
use common::assets::{self, AssetExt, AssetHandle};
use common_base::span;
use core::convert::TryFrom;
#[cfg(feature = "egui-ui")]
use egui_wgpu_backend::wgpu::TextureFormat;
use std::sync::Arc;
use tracing::{error, info, warn};
use vek::*;
@ -136,6 +138,9 @@ pub struct Renderer {
profile_times: Vec<wgpu_profiler::GpuTimerScopeResult>,
profiler_features_enabled: bool,
#[cfg(feature = "egui-ui")]
egui_renderpass: egui_wgpu_backend::RenderPass,
// This checks is added because windows resizes the window to 0,0 when
// minimizing and this causes a bunch of validation errors
is_minimized: bool,
@ -398,6 +403,10 @@ impl Renderer {
profiler.enable_timer = mode.profiler_enabled;
profiler.enable_debug_marker = mode.profiler_enabled;
#[cfg(feature = "egui-ui")]
let egui_renderpass =
egui_wgpu_backend::RenderPass::new(&*device, TextureFormat::Bgra8UnormSrgb, 1);
Ok(Self {
device,
queue,
@ -430,6 +439,9 @@ impl Renderer {
profile_times: Vec::new(),
profiler_features_enabled,
#[cfg(feature = "egui-ui")]
egui_renderpass,
is_minimized: false,
graphics_backend,

View File

@ -14,6 +14,8 @@ use core::{num::NonZeroU32, ops::Range};
use std::sync::Arc;
use vek::Aabr;
use wgpu_profiler::scope::{ManualOwningScope, OwningScope, Scope};
#[cfg(feature = "egui-ui")]
use {common_base::span, egui_wgpu_backend::ScreenDescriptor, egui_winit_platform::Platform};
// Currently available pipelines
enum Pipelines<'frame> {
@ -53,6 +55,8 @@ impl<'frame> Pipelines<'frame> {
struct RendererBorrow<'frame> {
queue: &'frame wgpu::Queue,
device: &'frame wgpu::Device,
#[cfg(feature = "egui-ui")]
sc_desc: &'frame wgpu::SwapChainDescriptor,
shadow: Option<&'frame super::Shadow>,
pipelines: Pipelines<'frame>,
locals: &'frame super::locals::Locals,
@ -60,6 +64,8 @@ struct RendererBorrow<'frame> {
mode: &'frame super::super::RenderMode,
quad_index_buffer_u16: &'frame Buffer<u16>,
quad_index_buffer_u32: &'frame Buffer<u32>,
#[cfg(feature = "egui-ui")]
egui_render_pass: &'frame mut egui_wgpu_backend::RenderPass,
}
pub struct Drawer<'frame> {
@ -100,6 +106,8 @@ impl<'frame> Drawer<'frame> {
let borrow = RendererBorrow {
queue: &renderer.queue,
device: &renderer.device,
#[cfg(feature = "egui-ui")]
sc_desc: &renderer.sc_desc,
shadow,
pipelines,
locals: &renderer.locals,
@ -107,6 +115,8 @@ impl<'frame> Drawer<'frame> {
mode: &renderer.mode,
quad_index_buffer_u16: &renderer.quad_index_buffer_u16,
quad_index_buffer_u32: &renderer.quad_index_buffer_u32,
#[cfg(feature = "egui-ui")]
egui_render_pass: &mut renderer.egui_renderpass,
};
let encoder =
@ -261,6 +271,48 @@ impl<'frame> Drawer<'frame> {
}
}
#[cfg(feature = "egui-ui")]
pub fn draw_egui(&mut self, platform: &mut Platform, scale_factor: f32) {
span!(guard, "Draw egui");
let (_output, paint_commands) = platform.end_frame();
let paint_jobs = platform.context().tessellate(paint_commands);
let screen_descriptor = ScreenDescriptor {
physical_width: self.borrow.sc_desc.width,
physical_height: self.borrow.sc_desc.height,
scale_factor: scale_factor as f32,
};
self.borrow.egui_render_pass.update_texture(
self.borrow.device,
self.borrow.queue,
&platform.context().texture(),
);
self.borrow
.egui_render_pass
.update_user_textures(self.borrow.device, self.borrow.queue);
self.borrow.egui_render_pass.update_buffers(
self.borrow.device,
self.borrow.queue,
&paint_jobs,
&screen_descriptor,
);
self.borrow.egui_render_pass.execute(
&mut self.encoder.as_mut().unwrap(),
self.taking_screenshot
.as_ref()
.map_or(&self.swap_tex.view, |s| s.texture_view()),
&paint_jobs,
&screen_descriptor,
None,
);
drop(guard);
}
/// Does nothing if the shadow pipelines are not available or shadow map
/// rendering is disabled
pub fn draw_point_shadows<'data: 'frame>(

View File

@ -31,6 +31,8 @@ pub fn run(mut global_state: GlobalState, event_loop: EventLoop) {
// Continuously run loop since we handle sleeping
*control_flow = winit::event_loop::ControlFlow::Poll;
#[cfg(feature = "egui-ui")]
global_state.egui_state.platform.handle_event(&event);
// Get events for the ui.
if let Some(event) = ui::Event::try_from(&event, global_state.window.window()) {
global_state.window.send_event(Event::Ui(event));
@ -167,13 +169,37 @@ fn handle_main_events_cleared(
let mut capped_fps = false;
drop(guard);
#[cfg(feature = "egui-ui")]
let scale_factor = global_state.window.window().scale_factor() as f32;
if let Some(last) = states.last_mut() {
span!(guard, "Render");
let renderer = global_state.window.renderer_mut();
// Render the screen using the global renderer
last.render(renderer, &global_state.settings);
capped_fps = last.capped_fps();
span!(guard, "Render");
// Render the screen using the global renderer
if let Some(mut drawer) = global_state
.window
.renderer_mut()
.start_recording_frame(last.globals_bind_group())
.expect("Unrecoverable render error when starting a new frame!")
{
if global_state.clear_shadows_next_frame {
drawer.clear_shadows();
}
last.render(&mut drawer, &global_state.settings);
#[cfg(feature = "egui-ui")]
if last.egui_enabled() && global_state.settings.interface.toggle_debug {
drawer.draw_egui(&mut global_state.egui_state.platform, scale_factor);
}
};
if global_state.clear_shadows_next_frame {
global_state.clear_shadows_next_frame = false;
}
drop(guard);
}

View File

@ -50,7 +50,7 @@ impl DebugShape {
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct DebugShapeId(u64);
pub struct DebugShapeId(pub u64);
pub struct Debug {
next_shape_id: DebugShapeId,

View File

@ -38,7 +38,7 @@ use crate::{
hud::{DebugInfo, Event as HudEvent, Hud, HudInfo, LootMessage, PromptDialogSettings},
key_state::KeyState,
menu::char_selection::CharSelectionState,
render::Renderer,
render::{Drawer, GlobalsBindGroup},
scene::{camera, terrain::Interaction, CameraMode, DebugShapeId, Scene, SceneData},
settings::Settings,
window::{AnalogGameInput, Event, GameInput},
@ -46,6 +46,8 @@ use crate::{
};
use hashbrown::HashMap;
use settings_change::Language::ChangeLanguage;
#[cfg(feature = "egui-ui")]
use voxygen_egui::EguiDebugInfo;
/// The action to perform after a tick
enum TickAction {
@ -1022,6 +1024,19 @@ impl PlayState for SessionState {
self.interactable,
);
// Maintain egui (debug interface)
#[cfg(feature = "egui-ui")]
if global_state.settings.interface.toggle_debug {
global_state.egui_state.maintain(
&self.client.borrow(),
&mut self.scene,
debug_info.map(|debug_info| EguiDebugInfo {
frame_time: debug_info.frame_time,
ping_ms: debug_info.ping_ms,
}),
);
}
// Look for changes in the localization files
if global_state.i18n.reloaded() {
hud_events.push(HudEvent::SettingsChange(
@ -1401,19 +1416,13 @@ impl PlayState for SessionState {
fn capped_fps(&self) -> bool { false }
fn globals_bind_group(&self) -> &GlobalsBindGroup { self.scene.global_bind_group() }
/// Render the session to the screen.
///
/// This method should be called once per frame.
fn render(&mut self, renderer: &mut Renderer, settings: &Settings) {
fn render<'a>(&'a self, mut drawer: &mut Drawer<'a>, settings: &Settings) {
span!(_guard, "render", "<Session as PlayState>::render");
let mut drawer = match renderer
.start_recording_frame(self.scene.global_bind_group())
.expect("Unrecoverable render error when starting a new frame!")
{
Some(d) => d,
// Couldn't get swap chain texture this frame
None => return,
};
// Render world
{
@ -1465,6 +1474,8 @@ impl PlayState for SessionState {
}; // Note: this semicolon is needed for the third_pass borrow to be dropped before it's lifetime ends
}
}
fn egui_enabled(&self) -> bool { true }
}
/// Max distance an entity can be "targeted"

View File

@ -0,0 +1,68 @@
use crate::{
scene::{DebugShape, DebugShapeId, Scene},
window::Window,
};
use client::Client;
use egui::FontDefinitions;
use egui_winit_platform::{Platform, PlatformDescriptor};
use voxygen_egui::{DebugShapeAction, EguiDebugInfo, EguiInnerState, EguiWindows};
pub struct EguiState {
pub platform: Platform,
egui_inner_state: EguiInnerState,
egui_windows: EguiWindows,
new_debug_shape_id: Option<u64>,
}
impl EguiState {
pub fn new(window: &Window) -> Self {
let platform = Platform::new(PlatformDescriptor {
physical_width: window.window().inner_size().width as u32,
physical_height: window.window().inner_size().height as u32,
scale_factor: window.scale_factor(),
font_definitions: FontDefinitions::default(),
style: Default::default(),
});
Self {
platform,
egui_inner_state: EguiInnerState::default(),
egui_windows: EguiWindows::default(),
new_debug_shape_id: None,
}
}
pub fn maintain(
&mut self,
client: &Client,
scene: &mut Scene,
debug_info: Option<EguiDebugInfo>,
) {
let egui_actions = voxygen_egui::maintain(
&mut self.platform,
&mut self.egui_inner_state,
&mut self.egui_windows,
client,
debug_info,
self.new_debug_shape_id.take(),
);
egui_actions.actions.iter().for_each(|action| match action {
DebugShapeAction::AddCylinder { height, radius } => {
let shape_id = scene.debug.add_shape(DebugShape::Cylinder {
height: *height,
radius: *radius,
});
self.new_debug_shape_id = Some(shape_id.0);
},
DebugShapeAction::RemoveShape(debug_shape_id) => {
scene.debug.remove_shape(DebugShapeId(*debug_shape_id));
},
DebugShapeAction::SetPosAndColor { id, pos, color } => {
scene
.debug
.set_pos_and_color(DebugShapeId(*id), *pos, *color);
},
})
}
}

View File

@ -7,6 +7,7 @@ mod widgets;
pub mod img_ids;
#[macro_use]
pub mod fonts;
#[cfg(feature = "egui-ui")] pub mod egui;
pub mod ice;
pub mod keyed_jobs;