mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'xvar/wgpu-egui' into 'master'
Added egui debug UI See merge request veloren/veloren!2252
This commit is contained in:
commit
fca8227db8
@ -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
101
Cargo.lock
generated
@ -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"
|
||||
|
@ -20,6 +20,9 @@ members = [
|
||||
"voxygen/anim",
|
||||
"voxygen/anim/dyn",
|
||||
"voxygen/i18n",
|
||||
"voxygen/dynlib",
|
||||
"voxygen/egui",
|
||||
"voxygen/egui/dyn",
|
||||
"world",
|
||||
"network",
|
||||
"network/protocol",
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
|
@ -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" }
|
||||
|
@ -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}
|
||||
|
@ -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::*;
|
||||
|
@ -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
11
voxygen/dynlib/Cargo.toml
Normal 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"
|
@ -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
20
voxygen/egui/Cargo.toml
Normal 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}
|
||||
|
14
voxygen/egui/dyn/Cargo.toml
Normal file
14
voxygen/egui/dyn/Cargo.toml
Normal 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 = "../" }
|
14
voxygen/egui/dyn/src/lib.rs
Normal file
14
voxygen/egui/dyn/src/lib.rs
Normal 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::*;
|
87
voxygen/egui/src/character_states.rs
Normal file
87
voxygen/egui/src/character_states.rs
Normal 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
697
voxygen/egui/src/lib.rs
Normal 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");
|
||||
},
|
||||
};
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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>(
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
|
68
voxygen/src/ui/egui/mod.rs
Normal file
68
voxygen/src/ui/egui/mod.rs
Normal 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);
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user