#![warn(missing_docs)]
pub mod accel;
pub mod animation;
pub mod base;
pub mod camera;
pub mod collider;
pub mod debug;
pub mod decal;
pub mod dim2;
pub mod graph;
pub mod joint;
pub mod light;
pub mod mesh;
pub mod navmesh;
pub mod node;
pub mod particle_system;
pub mod pivot;
pub mod probe;
pub mod ragdoll;
pub mod rigidbody;
pub mod skybox;
pub mod sound;
pub mod sprite;
pub mod terrain;
pub mod tilemap;
pub mod transform;
use crate::{
asset::{self, io::ResourceIo, manager::ResourceManager, untyped::UntypedResource},
core::{
algebra::Vector2,
color::Color,
futures::future::join_all,
log::{Log, MessageKind},
pool::{Handle, Pool, Ticket},
reflect::prelude::*,
type_traits::prelude::*,
variable::InheritableVariable,
visitor::{error::VisitError, Visit, VisitResult, Visitor},
SafeLock,
},
engine::SerializationContext,
graph::NodeHandleMap,
graphics::PolygonFillMode,
resource::texture::TextureResource,
scene::{
debug::SceneDrawingContext,
graph::{Graph, GraphPerformanceStatistics, GraphUpdateSwitches},
node::Node,
skybox::{SkyBox, SkyBoxKind},
sound::SoundEngine,
},
utils::navmesh::Navmesh,
};
use fxhash::FxHashSet;
use fyrox_core::dyntype::DynTypeConstructorContainer;
use fyrox_core::pool::PoolError;
use std::{
fmt::{Display, Formatter},
ops::{Index, IndexMut},
path::Path,
path::PathBuf,
sync::Arc,
};
use strum_macros::{AsRefStr, EnumString, VariantNames};
#[derive(Default, Clone, Debug, Visit)]
pub struct NavMeshContainer {
pool: Pool<Navmesh>,
}
impl NavMeshContainer {
pub fn add(&mut self, navmesh: Navmesh) -> Handle<Navmesh> {
self.pool.spawn(navmesh)
}
pub fn remove(&mut self, handle: Handle<Navmesh>) -> Navmesh {
self.pool.free(handle)
}
pub fn iter(&self) -> impl Iterator<Item = &Navmesh> {
self.pool.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Navmesh> {
self.pool.iter_mut()
}
pub fn handle_from_index(&self, i: u32) -> Handle<Navmesh> {
self.pool.handle_from_index(i)
}
pub fn clear(&mut self) {
self.pool.clear()
}
pub fn is_valid_handle(&self, handle: Handle<Navmesh>) -> bool {
self.pool.is_valid_handle(handle)
}
pub fn at(&self, i: u32) -> Result<&Navmesh, PoolError> {
self.pool.at(i)
}
pub fn try_get(&self, handle: Handle<Navmesh>) -> Result<&Navmesh, PoolError> {
self.pool.try_borrow(handle)
}
pub fn at_mut(&mut self, i: u32) -> Result<&mut Navmesh, PoolError> {
self.pool.at_mut(i)
}
pub fn try_get_mut(&mut self, handle: Handle<Navmesh>) -> Result<&mut Navmesh, PoolError> {
self.pool.try_borrow_mut(handle)
}
}
impl Index<Handle<Navmesh>> for NavMeshContainer {
type Output = Navmesh;
fn index(&self, index: Handle<Navmesh>) -> &Self::Output {
&self.pool[index]
}
}
impl IndexMut<Handle<Navmesh>> for NavMeshContainer {
fn index_mut(&mut self, index: Handle<Navmesh>) -> &mut Self::Output {
&mut self.pool[index]
}
}
#[derive(
Reflect,
Visit,
Debug,
Default,
Clone,
Copy,
PartialEq,
AsRefStr,
EnumString,
VariantNames,
TypeUuidProvider,
)]
#[type_uuid(id = "28f22fe7-22ed-47e1-ae43-779866a46cdf")]
pub enum EnvironmentLightingSource {
#[default]
SkyBox,
AmbientColor,
}
#[derive(Debug, Visit, Reflect, PartialEq)]
pub struct SceneRenderingOptions {
pub render_target: Option<TextureResource>,
pub clear_color: Option<Color>,
pub polygon_rasterization_mode: PolygonFillMode,
pub ambient_lighting_color: Color,
pub environment_lighting_source: EnvironmentLightingSource,
#[visit(optional)]
pub environment_lighting_brightness: f32,
}
impl Default for SceneRenderingOptions {
fn default() -> Self {
Self {
render_target: None,
clear_color: None,
polygon_rasterization_mode: Default::default(),
ambient_lighting_color: Color::opaque(100, 100, 100),
environment_lighting_source: Default::default(),
environment_lighting_brightness: 1.0,
}
}
}
impl Clone for SceneRenderingOptions {
fn clone(&self) -> Self {
Self {
render_target: None, clear_color: self.clear_color,
polygon_rasterization_mode: self.polygon_rasterization_mode,
ambient_lighting_color: self.ambient_lighting_color,
environment_lighting_source: self.environment_lighting_source,
environment_lighting_brightness: self.environment_lighting_brightness,
}
}
}
#[derive(Debug, Reflect)]
pub struct Scene {
pub graph: Graph,
pub rendering_options: InheritableVariable<SceneRenderingOptions>,
#[reflect(hidden)]
pub drawing_context: SceneDrawingContext,
#[reflect(hidden)]
pub performance_statistics: PerformanceStatistics,
#[reflect(setter = "set_skybox")]
sky_box: InheritableVariable<Option<SkyBox>>,
pub enabled: InheritableVariable<bool>,
}
impl Clone for Scene {
fn clone(&self) -> Self {
self.clone_one_to_one().0
}
}
impl Default for Scene {
fn default() -> Self {
Self {
graph: Default::default(),
rendering_options: Default::default(),
drawing_context: Default::default(),
performance_statistics: Default::default(),
enabled: true.into(),
sky_box: Some(SkyBoxKind::built_in_skybox().clone()).into(),
}
}
}
#[derive(Clone, Default, Debug)]
pub struct PerformanceStatistics {
pub graph: GraphPerformanceStatistics,
}
impl Display for PerformanceStatistics {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Graph: {:?}\n\
\tSync Time: {:?}\n\
\tSound: {:?}\n\
\tPhysics: {:?}\n\
\t\tSimulation: {:?}\n\
\t\tRay cast: {:?}\n\
\tPhysics 2D: {:?}\n\
\t\tSimulation: {:?}\n\
\t\tRay cast: {:?}\n\
\tHierarchy: {:?}",
self.graph.total(),
self.graph.sync_time,
self.graph.sound_update_time,
self.graph.physics.total(),
self.graph.physics.step_time,
self.graph.physics.total_ray_cast_time.get(),
self.graph.physics2d.total(),
self.graph.physics2d.step_time,
self.graph.physics2d.total_ray_cast_time.get(),
self.graph.hierarchical_properties_time,
)
}
}
pub struct SceneLoader {
scene: Scene,
path: Option<PathBuf>,
resource_manager: ResourceManager,
}
impl SceneLoader {
pub async fn from_file<P: AsRef<Path>>(
path: P,
io: &dyn ResourceIo,
serialization_context: Arc<SerializationContext>,
dyn_type_constructors: Arc<DynTypeConstructorContainer>,
resource_manager: ResourceManager,
) -> Result<(Self, Vec<u8>), VisitError> {
if !resource_manager.registry_is_loaded() {
return Err(VisitError::User(format!(
"Unable to load a scene from {} path, because the \
resource registry isn't loaded!",
path.as_ref().display()
)));
}
let data = io.load_file(path.as_ref()).await?;
let mut visitor = Visitor::load_from_memory(&data)?;
let loader = Self::load(
"Scene",
serialization_context,
dyn_type_constructors,
resource_manager,
&mut visitor,
Some(path.as_ref().to_path_buf()),
)?;
Ok((loader, data))
}
pub fn load(
region_name: &str,
serialization_context: Arc<SerializationContext>,
dyn_type_constructors: Arc<DynTypeConstructorContainer>,
resource_manager: ResourceManager,
visitor: &mut Visitor,
path: Option<PathBuf>,
) -> Result<Self, VisitError> {
if !visitor.is_reading() {
return Err(VisitError::User(
"Visitor must be in read mode!".to_string(),
));
}
visitor.blackboard.register(serialization_context);
visitor.blackboard.register(dyn_type_constructors);
visitor
.blackboard
.register(Arc::new(resource_manager.clone()));
let mut scene = Scene::default();
scene.visit(region_name, visitor)?;
Ok(Self {
scene,
path,
resource_manager,
})
}
pub async fn finish(self) -> Scene {
let mut scene = self.scene;
Log::info("SceneLoader::finish() - Collecting resources used by the scene...");
let mut used_resources = scene.collect_used_resources();
if let Some(path) = self.path {
let exclusion_list = used_resources
.iter()
.filter(|res| {
let uuid = res.resource_uuid();
let state = self.resource_manager.state();
let registry = state.resource_registry.safe_lock();
registry.uuid_to_path(uuid) == Some(&path)
})
.cloned()
.collect::<Vec<_>>();
for excluded_resource in exclusion_list {
assert!(used_resources.remove(&excluded_resource));
}
}
let used_resources_count = used_resources.len();
Log::info(format!(
"SceneLoader::finish() - {used_resources_count} resources collected. Waiting them to load..."
));
let results = join_all(used_resources.into_iter()).await;
for result in results {
if let Err(err) = result {
Log::err(format!("Scene resource loading error: {:?}", err));
}
}
Log::info(format!(
"SceneLoader::finish() - All {used_resources_count} resources have finished loading."
));
let mut skybox_textures = Vec::new();
if let Some(skybox) = scene.skybox_ref() {
skybox_textures.extend(skybox.textures().iter().filter_map(|t| t.clone()));
}
join_all(skybox_textures).await;
scene.resolve();
scene
}
}
impl Scene {
#[inline]
pub fn new() -> Self {
Self {
graph: Graph::new(),
rendering_options: Default::default(),
drawing_context: Default::default(),
performance_statistics: Default::default(),
enabled: true.into(),
sky_box: Some(SkyBoxKind::built_in_skybox().clone()).into(),
}
}
pub fn set_skybox(&mut self, skybox: Option<SkyBox>) -> Option<SkyBox> {
self.sky_box.set_value_and_mark_modified(skybox)
}
pub fn skybox_mut(&mut self) -> Option<&mut SkyBox> {
self.sky_box.get_value_mut_and_mark_modified().as_mut()
}
pub fn skybox_ref(&self) -> Option<&SkyBox> {
self.sky_box.as_ref()
}
pub fn replace_skybox(&mut self, new: Option<SkyBox>) -> Option<SkyBox> {
std::mem::replace(self.sky_box.get_value_mut_and_mark_modified(), new)
}
pub fn resolve(&mut self) {
Log::writeln(MessageKind::Information, "Starting resolve...");
if let Some(skybox) = self.skybox_mut() {
Log::verify(skybox.create_cubemap());
}
self.graph.resolve();
Log::writeln(MessageKind::Information, "Resolve succeeded!");
}
pub fn collect_used_resources(&self) -> FxHashSet<UntypedResource> {
let mut collection = FxHashSet::default();
asset::collect_used_resources(self, &mut collection);
collection
}
pub fn update(&mut self, frame_size: Vector2<f32>, dt: f32, switches: GraphUpdateSwitches) {
self.graph.update(frame_size, dt, switches);
self.performance_statistics.graph = self.graph.performance_statistics.clone();
}
pub fn clone_ex<F, Pre, Post>(
&self,
root: Handle<Node>,
preserve_handles: bool,
filter: &mut F,
pre_process_callback: &mut Pre,
post_process_callback: &mut Post,
) -> (Self, NodeHandleMap<Node>)
where
F: FnMut(Handle<Node>, &Node) -> bool,
Pre: FnMut(Handle<Node>, &mut Node),
Post: FnMut(Handle<Node>, Handle<Node>, &mut Node),
{
let (graph, old_new_map) = self.graph.clone_ex(
root,
preserve_handles,
filter,
pre_process_callback,
post_process_callback,
);
(
Self {
graph,
rendering_options: self.rendering_options.clone(),
drawing_context: self.drawing_context.clone(),
performance_statistics: Default::default(),
enabled: self.enabled.clone(),
sky_box: self.sky_box.clone(),
},
old_new_map,
)
}
pub fn clone_one_to_one(&self) -> (Self, NodeHandleMap<Node>) {
self.clone_ex(
self.graph.get_root(),
true,
&mut |_, _| true,
&mut |_, _| {},
&mut |_, _, _| {},
)
}
fn visit(&mut self, region_name: &str, visitor: &mut Visitor) -> VisitResult {
let mut region = visitor.enter_region(region_name)?;
self.graph.visit("Graph", &mut region)?;
self.enabled.visit("Enabled", &mut region)?;
self.rendering_options
.visit("RenderingOptions", &mut region)?;
self.sky_box.visit("SkyBox", &mut region)?;
Ok(())
}
pub fn save(&mut self, region_name: &str, visitor: &mut Visitor) -> VisitResult {
if visitor.is_reading() {
return Err(VisitError::User(
"Visitor must be in write mode!".to_string(),
));
}
self.visit(region_name, visitor)
}
}
pub struct SceneContainer {
pool: Pool<Scene>,
sound_engine: SoundEngine,
pub(crate) destruction_list: Vec<(Handle<Scene>, Scene)>,
}
impl SceneContainer {
pub(crate) fn new(sound_engine: SoundEngine) -> Self {
Self {
pool: Pool::new(),
sound_engine,
destruction_list: Default::default(),
}
}
pub fn is_valid_handle(&self, handle: Handle<Scene>) -> bool {
self.pool.is_valid_handle(handle)
}
pub fn pair_iter(&self) -> impl Iterator<Item = (Handle<Scene>, &Scene)> {
self.pool.pair_iter()
}
pub fn pair_iter_mut(&mut self) -> impl Iterator<Item = (Handle<Scene>, &mut Scene)> {
self.pool.pair_iter_mut()
}
pub fn try_get(&self, handle: Handle<Scene>) -> Result<&Scene, PoolError> {
self.pool.try_borrow(handle)
}
pub fn try_get_mut(&mut self, handle: Handle<Scene>) -> Result<&mut Scene, PoolError> {
self.pool.try_borrow_mut(handle)
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &Scene> {
self.pool.iter()
}
#[inline]
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Scene> {
self.pool.iter_mut()
}
#[inline]
pub fn add(&mut self, scene: Scene) -> Handle<Scene> {
self.sound_engine
.state()
.add_context(scene.graph.sound_context.native.clone());
self.pool.spawn(scene)
}
#[inline]
pub fn clear(&mut self) {
self.pool.clear()
}
#[inline]
pub fn remove(&mut self, handle: Handle<Scene>) {
self.sound_engine
.state()
.remove_context(self.pool[handle].graph.sound_context.native.clone());
self.destruction_list.push((handle, self.pool.free(handle)));
}
pub fn take_reserve(&mut self, handle: Handle<Scene>) -> (Ticket<Scene>, Scene) {
self.pool.take_reserve(handle)
}
pub fn put_back(&mut self, ticket: Ticket<Scene>, scene: Scene) -> Handle<Scene> {
self.pool.put_back(ticket, scene)
}
pub fn forget_ticket(&mut self, ticket: Ticket<Scene>) {
self.pool.forget_ticket(ticket)
}
}
impl Index<Handle<Scene>> for SceneContainer {
type Output = Scene;
#[inline]
fn index(&self, index: Handle<Scene>) -> &Self::Output {
&self.pool[index]
}
}
impl IndexMut<Handle<Scene>> for SceneContainer {
#[inline]
fn index_mut(&mut self, index: Handle<Scene>) -> &mut Self::Output {
&mut self.pool[index]
}
}