#![warn(missing_docs)]
pub mod error;
pub mod executor;
pub mod task;
mod hotreload;
use crate::resource::texture::{
CompressionOptions, TextureImportOptions, TextureMinificationFilter, TextureResource,
TextureResourceExtension,
};
use crate::scene::tilemap::{CustomTileCollider, TileMapData};
use crate::{
asset::{
event::ResourceEvent,
manager::{ResourceManager, ResourceWaitContext},
state::ResourceState,
untyped::{ResourceKind, UntypedResource},
Resource,
},
core::{
algebra::Vector2,
futures::{executor::block_on, future::join_all},
instant,
log::Log,
pool::Handle,
reflect::Reflect,
task::TaskPool,
variable::try_inherit_properties,
visitor::VisitError,
},
engine::{error::EngineError, task::TaskPoolHandler},
event::Event,
graph::{BaseSceneGraph, NodeMapping, SceneGraph},
gui::{
constructor::WidgetConstructorContainer,
font::{loader::FontLoader, Font, BUILT_IN_FONT},
loader::UserInterfaceLoader,
style::{self, resource::StyleLoader, Style},
UiContainer, UiUpdateSwitches, UserInterface,
},
material::{
self,
loader::MaterialLoader,
shader::{loader::ShaderLoader, Shader, ShaderResource, ShaderResourceExtension},
Material,
},
plugin::{
dylib::DyLibDynamicPlugin, DynamicPlugin, Plugin, PluginContainer, PluginContext,
PluginRegistrationContext,
},
renderer::{framework::error::FrameworkError, Renderer},
resource::{
curve::{loader::CurveLoader, CurveResourceState},
model::{loader::ModelLoader, Model, ModelResource},
texture::{self, loader::TextureLoader, Texture, TextureKind},
},
scene::{
base::NodeScriptMessage,
camera::SkyBoxKind,
graph::{GraphUpdateSwitches, NodePool},
mesh::surface::{self, SurfaceData, SurfaceDataLoader},
navmesh,
node::{
constructor::{new_node_constructor_container, NodeConstructorContainer},
Node,
},
sound::SoundEngine,
tilemap::{
brush::{TileMapBrush, TileMapBrushLoader},
tileset::{TileSet, TileSetLoader},
},
Scene, SceneContainer, SceneLoader,
},
script::{
constructor::ScriptConstructorContainer, PluginsRefMut, RoutingStrategy, Script,
ScriptContext, ScriptDeinitContext, ScriptMessage, ScriptMessageContext, ScriptMessageKind,
ScriptMessageSender, UniversalScriptContext,
},
window::{Window, WindowBuilder},
};
use fxhash::{FxHashMap, FxHashSet};
use fyrox_animation::AnimationTracksData;
use fyrox_graphics::gl::server::GlGraphicsServer;
use fyrox_graphics::server::SharedGraphicsServer;
use fyrox_sound::{
buffer::{loader::SoundBufferLoader, SoundBuffer},
renderer::hrtf::{HrirSphereLoader, HrirSphereResourceData},
};
use std::rc::Rc;
use std::{
any::TypeId,
collections::{HashSet, VecDeque},
fmt::{Display, Formatter},
io::Cursor,
ops::{Deref, DerefMut},
path::{Path, PathBuf},
sync::{
mpsc::{channel, Receiver, Sender},
Arc,
},
time::Duration,
};
use winit::window::Icon;
use winit::{
dpi::{Position, Size},
event_loop::EventLoopWindowTarget,
window::WindowAttributes,
};
pub struct SerializationContext {
pub node_constructors: NodeConstructorContainer,
pub script_constructors: ScriptConstructorContainer,
}
impl Default for SerializationContext {
fn default() -> Self {
Self::new()
}
}
impl SerializationContext {
pub fn new() -> Self {
Self {
node_constructors: new_node_constructor_container(),
script_constructors: ScriptConstructorContainer::new(),
}
}
}
#[derive(Debug, Default)]
pub struct PerformanceStatistics {
pub ui_time: Duration,
pub scripts_time: Duration,
pub plugins_time: Duration,
}
impl Display for PerformanceStatistics {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"Performance Statistics:\n\tUI: {:?}\n\tScripts: {:?}\n\tPlugins: {:?}",
self.ui_time, self.scripts_time, self.plugins_time
)
}
}
pub struct InitializedGraphicsContext {
pub window: Window,
pub renderer: Renderer,
params: GraphicsContextParams,
}
impl InitializedGraphicsContext {
pub fn set_window_icon_from_memory(&mut self, data: &[u8]) {
if let Ok(texture) = TextureResource::load_from_memory(
ResourceKind::Embedded,
data,
TextureImportOptions::default()
.with_compression(CompressionOptions::NoCompression)
.with_minification_filter(TextureMinificationFilter::Linear),
) {
self.set_window_icon_from_texture(&texture);
}
}
pub fn set_window_icon_from_texture(&mut self, texture: &TextureResource) {
let data = texture.data_ref();
if let TextureKind::Rectangle { width, height } = data.kind() {
if let Ok(img) = Icon::from_rgba(data.data().to_vec(), width, height) {
self.window.set_window_icon(Some(img));
}
}
}
}
#[allow(clippy::large_enum_variant)]
pub enum GraphicsContext {
Initialized(InitializedGraphicsContext),
Uninitialized(GraphicsContextParams),
}
impl GraphicsContext {
pub fn as_initialized_ref(&self) -> &InitializedGraphicsContext {
if let GraphicsContext::Initialized(ctx) = self {
ctx
} else {
panic!("Graphics context is uninitialized!")
}
}
pub fn as_initialized_mut(&mut self) -> &mut InitializedGraphicsContext {
if let GraphicsContext::Initialized(ctx) = self {
ctx
} else {
panic!("Graphics context is uninitialized!")
}
}
}
struct SceneLoadingOptions {
derived: bool,
}
pub struct AsyncSceneLoader {
resource_manager: ResourceManager,
serialization_context: Arc<SerializationContext>,
receiver: Receiver<SceneLoadingResult>,
sender: Sender<SceneLoadingResult>,
loading_scenes: FxHashMap<PathBuf, LoadingScene>,
}
struct LoadingScene {
reported: bool,
path: PathBuf,
options: SceneLoadingOptions,
}
struct SceneLoadingResult {
path: PathBuf,
result: Result<(Scene, Vec<u8>), VisitError>,
}
impl AsyncSceneLoader {
fn new(
resource_manager: ResourceManager,
serialization_context: Arc<SerializationContext>,
) -> Self {
let (sender, receiver) = channel();
Self {
resource_manager,
serialization_context,
receiver,
sender,
loading_scenes: Default::default(),
}
}
fn request_with_options<P: AsRef<Path>>(&mut self, path: P, opts: SceneLoadingOptions) {
let path = path.as_ref().to_path_buf();
if self.loading_scenes.contains_key(&path) {
Log::warn(format!("A scene {} is already loading!", path.display()))
} else {
self.loading_scenes.insert(
path.clone(),
LoadingScene {
reported: false,
path: path.clone(),
options: opts,
},
);
let sender = self.sender.clone();
let serialization_context = self.serialization_context.clone();
let resource_manager = self.resource_manager.clone();
let io = resource_manager.resource_io();
let future = async move {
match SceneLoader::from_file(
path.clone(),
io.as_ref(),
serialization_context,
resource_manager.clone(),
)
.await
{
Ok((loader, data)) => {
let scene = loader.finish().await;
Log::verify(sender.send(SceneLoadingResult {
path,
result: Ok((scene, data)),
}));
}
Err(e) => {
Log::verify(sender.send(SceneLoadingResult {
path,
result: Err(e),
}));
}
}
};
#[cfg(not(target_arch = "wasm32"))]
{
std::thread::spawn(move || block_on(future));
}
#[cfg(target_arch = "wasm32")]
{
crate::core::wasm_bindgen_futures::spawn_local(future);
}
}
}
pub fn request<P: AsRef<Path>>(&mut self, path: P) {
self.request_with_options(path, SceneLoadingOptions { derived: true });
}
pub fn request_raw<P: AsRef<Path>>(&mut self, path: P) {
self.request_with_options(path, SceneLoadingOptions { derived: false });
}
}
pub struct Engine {
pub graphics_context: GraphicsContext,
pub resource_manager: ResourceManager,
pub user_interfaces: UiContainer,
pub scenes: SceneContainer,
pub async_scene_loader: AsyncSceneLoader,
pub task_pool: TaskPoolHandler,
performance_statistics: PerformanceStatistics,
model_events_receiver: Receiver<ResourceEvent>,
#[allow(dead_code)] sound_engine: SoundEngine,
plugins: Vec<PluginContainer>,
plugins_enabled: bool,
elapsed_time: f32,
pub serialization_context: Arc<SerializationContext>,
pub widget_constructors: Arc<WidgetConstructorContainer>,
pub script_processor: ScriptProcessor,
}
pub struct ScriptMessageDispatcher {
type_groups: FxHashMap<TypeId, FxHashSet<Handle<Node>>>,
message_receiver: Receiver<ScriptMessage>,
}
impl ScriptMessageDispatcher {
fn new(message_receiver: Receiver<ScriptMessage>) -> Self {
Self {
type_groups: Default::default(),
message_receiver,
}
}
pub fn subscribe_to<T: 'static>(&mut self, receiver: Handle<Node>) {
self.type_groups
.entry(TypeId::of::<T>())
.and_modify(|v| {
v.insert(receiver);
})
.or_insert_with(|| FxHashSet::from_iter([receiver]));
}
pub fn unsubscribe_from<T: 'static>(&mut self, receiver: Handle<Node>) {
if let Some(group) = self.type_groups.get_mut(&TypeId::of::<T>()) {
group.remove(&receiver);
}
}
pub fn unsubscribe(&mut self, receiver: Handle<Node>) {
for group in self.type_groups.values_mut() {
group.remove(&receiver);
}
}
fn dispatch_messages(
&self,
scene: &mut Scene,
scene_handle: Handle<Scene>,
plugins: &mut [PluginContainer],
resource_manager: &ResourceManager,
dt: f32,
elapsed_time: f32,
message_sender: &ScriptMessageSender,
user_interfaces: &mut UiContainer,
graphics_context: &mut GraphicsContext,
task_pool: &mut TaskPoolHandler,
) {
while let Ok(message) = self.message_receiver.try_recv() {
let receivers = self.type_groups.get(&message.payload.deref().type_id());
if receivers.map_or(true, |r| r.is_empty()) {
Log::warn(format!(
"Script message {message:?} was sent, but there's no receivers. \
Did you forgot to subscribe your script to the message?"
));
}
if let Some(receivers) = receivers {
let mut payload = message.payload;
match message.kind {
ScriptMessageKind::Targeted(target) => {
if receivers.contains(&target) {
let mut context = ScriptMessageContext {
dt,
elapsed_time,
plugins: PluginsRefMut(plugins),
handle: target,
scene,
scene_handle,
resource_manager,
message_sender,
task_pool,
graphics_context,
user_interfaces,
script_index: 0,
};
process_node_scripts(&mut context, &mut |s, ctx| {
s.on_message(&mut *payload, ctx)
})
}
}
ScriptMessageKind::Hierarchical { root, routing } => match routing {
RoutingStrategy::Up => {
let mut node = root;
while let Some(node_ref) = scene.graph.try_get(node) {
let parent = node_ref.parent();
let mut context = ScriptMessageContext {
dt,
elapsed_time,
plugins: PluginsRefMut(plugins),
handle: node,
scene,
scene_handle,
resource_manager,
message_sender,
task_pool,
graphics_context,
user_interfaces,
script_index: 0,
};
if receivers.contains(&node) {
process_node_scripts(&mut context, &mut |s, ctx| {
s.on_message(&mut *payload, ctx)
});
}
node = parent;
}
}
RoutingStrategy::Down => {
for node in scene.graph.traverse_handle_iter(root).collect::<Vec<_>>() {
let mut context = ScriptMessageContext {
dt,
elapsed_time,
plugins: PluginsRefMut(plugins),
handle: node,
scene,
scene_handle,
resource_manager,
message_sender,
task_pool,
graphics_context,
user_interfaces,
script_index: 0,
};
if receivers.contains(&node) {
process_node_scripts(&mut context, &mut |s, ctx| {
s.on_message(&mut *payload, ctx)
});
}
}
}
},
ScriptMessageKind::Global => {
for &node in receivers {
let mut context = ScriptMessageContext {
dt,
elapsed_time,
plugins: PluginsRefMut(plugins),
handle: node,
scene,
scene_handle,
resource_manager,
message_sender,
task_pool,
graphics_context,
user_interfaces,
script_index: 0,
};
process_node_scripts(&mut context, &mut |s, ctx| {
s.on_message(&mut *payload, ctx)
});
}
}
}
}
}
}
}
pub struct ScriptedScene {
pub handle: Handle<Scene>,
pub message_sender: ScriptMessageSender,
message_dispatcher: ScriptMessageDispatcher,
}
#[derive(Default)]
pub struct ScriptProcessor {
wait_list: Vec<ResourceWaitContext>,
pub scripted_scenes: Vec<ScriptedScene>,
}
impl ScriptProcessor {
fn has_scripted_scene(&self, scene: Handle<Scene>) -> bool {
self.scripted_scenes.iter().any(|s| s.handle == scene)
}
fn register_scripted_scene(
&mut self,
scene: Handle<Scene>,
resource_manager: &ResourceManager,
) {
assert!(!self.has_scripted_scene(scene));
let (tx, rx) = channel();
self.scripted_scenes.push(ScriptedScene {
handle: scene,
message_sender: ScriptMessageSender { sender: tx },
message_dispatcher: ScriptMessageDispatcher::new(rx),
});
self.wait_list
.push(resource_manager.state().get_wait_context());
}
fn handle_scripts(
&mut self,
scenes: &mut SceneContainer,
plugins: &mut [PluginContainer],
resource_manager: &ResourceManager,
task_pool: &mut TaskPoolHandler,
graphics_context: &mut GraphicsContext,
user_interfaces: &mut UiContainer,
dt: f32,
elapsed_time: f32,
) {
self.wait_list
.retain_mut(|context| !context.is_all_loaded());
if !self.wait_list.is_empty() {
return;
}
self.scripted_scenes
.retain(|s| scenes.is_valid_handle(s.handle));
'scene_loop: for scripted_scene in self.scripted_scenes.iter_mut() {
let scene = &mut scenes[scripted_scene.handle];
if !*scene.enabled {
continue 'scene_loop;
}
let mut update_queue = VecDeque::new();
let mut start_queue = VecDeque::new();
let script_message_sender = scene.graph.script_message_sender.clone();
for (handle, node) in scene.graph.pair_iter_mut() {
node.scripts
.retain(|e| e.script.is_some() && !e.should_be_deleted);
if node.is_globally_enabled() {
for (i, entry) in node.scripts.iter().enumerate() {
if let Some(script) = entry.script.as_ref() {
if script.initialized {
if script.started {
update_queue.push_back((handle, i));
} else {
start_queue.push_back((handle, i));
}
} else {
script_message_sender
.send(NodeScriptMessage::InitializeScript {
handle,
script_index: i,
})
.unwrap();
}
}
}
}
}
let mut destruction_queue = VecDeque::new();
let max_iterations = 64;
'update_loop: for update_loop_iteration in 0..max_iterations {
let mut context = ScriptContext {
dt,
elapsed_time,
plugins: PluginsRefMut(plugins),
handle: Default::default(),
scene,
scene_handle: scripted_scene.handle,
resource_manager,
message_sender: &scripted_scene.message_sender,
message_dispatcher: &mut scripted_scene.message_dispatcher,
task_pool,
graphics_context,
user_interfaces,
script_index: 0,
};
'init_loop: for init_loop_iteration in 0..max_iterations {
while let Ok(event) = context.scene.graph.script_message_receiver.try_recv() {
match event {
NodeScriptMessage::InitializeScript {
handle,
script_index,
} => {
context.handle = handle;
context.script_index = script_index;
process_node_script(
script_index,
&mut context,
&mut |script, context| {
if !script.initialized {
script.on_init(context);
script.initialized = true;
}
start_queue.push_back((handle, script_index));
},
);
}
NodeScriptMessage::DestroyScript {
handle,
script,
script_index,
} => {
destruction_queue.push_back((handle, script, script_index));
}
}
}
if start_queue.is_empty() {
break 'init_loop;
} else {
while let Some((handle, script_index)) = start_queue.pop_front() {
context.handle = handle;
context.script_index = script_index;
process_node_script(
script_index,
&mut context,
&mut |script, context| {
if script.initialized && !script.started {
script.on_start(context);
script.started = true;
update_queue.push_back((handle, script_index));
}
},
);
}
}
if init_loop_iteration == max_iterations - 1 {
Log::warn(
"Infinite init loop detected! Most likely some of \
your scripts causing infinite prefab instantiation!",
)
}
}
if update_queue.is_empty() {
break 'update_loop;
} else {
while let Some((handle, script_index)) = update_queue.pop_front() {
context.handle = handle;
context.script_index = script_index;
process_node_script(script_index, &mut context, &mut |script, context| {
script.on_update(context);
});
}
}
if update_loop_iteration == max_iterations - 1 {
Log::warn(
"Infinite update loop detected! Most likely some of \
your scripts causing infinite prefab instantiation!",
)
}
}
scripted_scene.message_dispatcher.dispatch_messages(
scene,
scripted_scene.handle,
plugins,
resource_manager,
dt,
elapsed_time,
&scripted_scene.message_sender,
user_interfaces,
graphics_context,
task_pool,
);
let mut context = ScriptDeinitContext {
elapsed_time,
plugins: PluginsRefMut(plugins),
resource_manager,
scene,
scene_handle: scripted_scene.handle,
node_handle: Default::default(),
message_sender: &scripted_scene.message_sender,
user_interfaces,
graphics_context,
task_pool,
script_index: 0,
};
while let Some((handle, mut script, index)) = destruction_queue.pop_front() {
context.node_handle = handle;
context.script_index = index;
scripted_scene.message_dispatcher.unsubscribe(handle);
script.on_deinit(&mut context);
}
}
for (handle, mut detached_scene) in scenes.destruction_list.drain(..) {
if let Some(scripted_scene) = self.scripted_scenes.iter().find(|s| s.handle == handle) {
let mut context = ScriptDeinitContext {
elapsed_time,
plugins: PluginsRefMut(plugins),
resource_manager,
scene: &mut detached_scene,
scene_handle: scripted_scene.handle,
node_handle: Default::default(),
message_sender: &scripted_scene.message_sender,
task_pool,
graphics_context,
user_interfaces,
script_index: 0,
};
for node_index in 0..context.scene.graph.capacity() {
let handle_node = context.scene.graph.handle_from_index(node_index);
context.node_handle = handle_node;
process_node_scripts(&mut context, &mut |script, context| {
if script.initialized {
script.on_deinit(context)
}
});
}
}
}
}
}
struct ResourceGraphVertex {
resource: ModelResource,
children: Vec<ResourceGraphVertex>,
}
impl ResourceGraphVertex {
pub fn new(model: ModelResource, resource_manager: ResourceManager) -> Self {
let mut children = Vec::new();
let mut dependent_resources = HashSet::new();
for resource in resource_manager.state().iter() {
if let Some(other_model) = resource.try_cast::<Model>() {
let mut state = other_model.state();
if let Some(model_data) = state.data() {
if model_data
.get_scene()
.graph
.linear_iter()
.any(|n| n.resource.as_ref() == Some(&model))
{
dependent_resources.insert(other_model.clone());
}
}
}
}
children.extend(
dependent_resources
.into_iter()
.map(|r| ResourceGraphVertex::new(r, resource_manager.clone())),
);
Self {
resource: model,
children,
}
}
pub fn resolve(&self) {
Log::info(format!(
"Resolving {} resource from dependency graph...",
self.resource.kind()
));
if block_on(self.resource.clone()).is_ok() {
self.resource.data_ref().get_scene_mut();
for child in self.children.iter() {
child.resolve();
}
}
}
}
struct ResourceDependencyGraph {
root: ResourceGraphVertex,
}
impl ResourceDependencyGraph {
pub fn new(model: ModelResource, resource_manager: ResourceManager) -> Self {
Self {
root: ResourceGraphVertex::new(model, resource_manager),
}
}
pub fn resolve(&self) {
self.root.resolve()
}
}
pub type GraphicsServerConstructorResult = Result<(Window, SharedGraphicsServer), FrameworkError>;
pub type GraphicsServerConstructorCallback = dyn Fn(
&GraphicsContextParams,
&EventLoopWindowTarget<()>,
WindowBuilder,
) -> GraphicsServerConstructorResult;
#[derive(Clone)]
pub struct GraphicsServerConstructor(Rc<GraphicsServerConstructorCallback>);
impl Default for GraphicsServerConstructor {
fn default() -> Self {
Self(Rc::new(|params, window_target, window_builder| {
GlGraphicsServer::new(
params.vsync,
params.msaa_sample_count,
window_target,
window_builder,
)
}))
}
}
#[derive(Clone)]
pub struct GraphicsContextParams {
pub window_attributes: WindowAttributes,
pub vsync: bool,
pub msaa_sample_count: Option<u8>,
pub graphics_server_constructor: GraphicsServerConstructor,
}
impl Default for GraphicsContextParams {
fn default() -> Self {
Self {
window_attributes: Default::default(),
vsync: true,
msaa_sample_count: None,
graphics_server_constructor: Default::default(),
}
}
}
pub struct EngineInitParams {
pub graphics_context_params: GraphicsContextParams,
pub serialization_context: Arc<SerializationContext>,
pub widget_constructors: Arc<WidgetConstructorContainer>,
pub resource_manager: ResourceManager,
pub task_pool: Arc<TaskPool>,
}
fn process_node_script<T, C>(index: usize, context: &mut C, func: &mut T) -> bool
where
T: FnMut(&mut Script, &mut C),
C: UniversalScriptContext,
{
let Some(node) = context.node() else {
return false;
};
if !node.is_globally_enabled() {
return false;
}
let Some(entry) = node.scripts.get_mut(index) else {
return false;
};
let Some(mut script) = entry.take() else {
return false;
};
func(&mut script, context);
match context.node() {
Some(node) => {
let entry = node
.scripts
.get_mut(index)
.expect("Scripts array cannot be modified!");
if entry.should_be_deleted {
context.destroy_script_deferred(script, index);
} else {
entry.script = Some(script);
}
}
None => {
context.destroy_script_deferred(script, index);
}
}
true
}
fn process_node_scripts<T, C>(context: &mut C, func: &mut T)
where
T: FnMut(&mut Script, &mut C),
C: UniversalScriptContext,
{
let mut index = 0;
loop {
context.set_script_index(index);
if !process_node_script(index, context, func) {
return;
}
index += 1;
}
}
pub(crate) fn process_scripts<T>(
scene: &mut Scene,
scene_handle: Handle<Scene>,
plugins: &mut [PluginContainer],
resource_manager: &ResourceManager,
message_sender: &ScriptMessageSender,
message_dispatcher: &mut ScriptMessageDispatcher,
task_pool: &mut TaskPoolHandler,
graphics_context: &mut GraphicsContext,
user_interfaces: &mut UiContainer,
dt: f32,
elapsed_time: f32,
mut func: T,
) where
T: FnMut(&mut Script, &mut ScriptContext),
{
let mut context = ScriptContext {
dt,
elapsed_time,
plugins: PluginsRefMut(plugins),
handle: Default::default(),
scene,
scene_handle,
resource_manager,
message_sender,
message_dispatcher,
task_pool,
graphics_context,
user_interfaces,
script_index: 0,
};
for node_index in 0..context.scene.graph.capacity() {
context.handle = context.scene.graph.handle_from_index(node_index);
process_node_scripts(&mut context, &mut func);
}
}
pub(crate) fn initialize_resource_manager_loaders(
resource_manager: &ResourceManager,
serialization_context: Arc<SerializationContext>,
) {
let model_loader = ModelLoader {
resource_manager: resource_manager.clone(),
serialization_context,
default_import_options: Default::default(),
};
let mut state = resource_manager.state();
#[cfg(feature = "gltf")]
{
let gltf_loader = super::resource::gltf::GltfLoader {
resource_manager: resource_manager.clone(),
default_import_options: Default::default(),
};
state.loaders.set(gltf_loader);
}
for shader in ShaderResource::standard_shaders() {
state.built_in_resources.add((*shader).clone());
}
for texture in SkyBoxKind::built_in_skybox_textures() {
state.built_in_resources.add(texture.clone());
}
state.built_in_resources.add(BUILT_IN_FONT.clone());
state.built_in_resources.add(texture::PLACEHOLDER.clone());
state.built_in_resources.add(style::DEFAULT_STYLE.clone());
for material in [
&*material::STANDARD,
&*material::STANDARD_2D,
&*material::STANDARD_SPRITE,
&*material::STANDARD_TERRAIN,
&*material::STANDARD_TWOSIDES,
&*material::STANDARD_PARTICLE_SYSTEM,
] {
state.built_in_resources.add(material.clone());
}
for surface in [
&*surface::CUBE,
&*surface::QUAD,
&*surface::CYLINDER,
&*surface::SPHERE,
&*surface::CONE,
&*surface::TORUS,
] {
state.built_in_resources.add(surface.clone());
}
state.constructors_container.add::<Texture>();
state.constructors_container.add::<Shader>();
state.constructors_container.add::<Model>();
state.constructors_container.add::<CurveResourceState>();
state.constructors_container.add::<SoundBuffer>();
state.constructors_container.add::<HrirSphereResourceData>();
state.constructors_container.add::<Material>();
state.constructors_container.add::<Font>();
state.constructors_container.add::<UserInterface>();
state.constructors_container.add::<SurfaceData>();
state.constructors_container.add::<TileSet>();
state.constructors_container.add::<TileMapBrush>();
state.constructors_container.add::<TileMapData>();
state.constructors_container.add::<CustomTileCollider>();
state.constructors_container.add::<AnimationTracksData>();
state.constructors_container.add::<Style>();
let loaders = &mut state.loaders;
loaders.set(model_loader);
loaders.set(TextureLoader {
default_import_options: Default::default(),
});
loaders.set(SoundBufferLoader {
default_import_options: Default::default(),
});
loaders.set(ShaderLoader);
loaders.set(CurveLoader);
loaders.set(HrirSphereLoader);
loaders.set(MaterialLoader {
resource_manager: resource_manager.clone(),
});
loaders.set(FontLoader::default());
loaders.set(UserInterfaceLoader {
resource_manager: resource_manager.clone(),
});
loaders.set(SurfaceDataLoader {});
loaders.set(TileSetLoader {
resource_manager: resource_manager.clone(),
});
state.loaders.set(TileMapBrushLoader {
resource_manager: resource_manager.clone(),
});
state.loaders.set(StyleLoader {
resource_manager: resource_manager.clone(),
});
}
impl Engine {
#[inline]
#[allow(unused_variables)]
pub fn new(params: EngineInitParams) -> Result<Self, EngineError> {
let EngineInitParams {
graphics_context_params,
serialization_context,
widget_constructors,
resource_manager,
task_pool,
} = params;
initialize_resource_manager_loaders(&resource_manager, serialization_context.clone());
let (rx, tx) = channel();
resource_manager.state().event_broadcaster.add(rx);
let sound_engine = SoundEngine::without_device();
let user_interfaces =
UiContainer::new_with_ui(UserInterface::new(Vector2::new(100.0, 100.0)));
Ok(Self {
graphics_context: GraphicsContext::Uninitialized(graphics_context_params),
model_events_receiver: tx,
async_scene_loader: AsyncSceneLoader::new(
resource_manager.clone(),
serialization_context.clone(),
),
resource_manager,
scenes: SceneContainer::new(sound_engine.clone()),
sound_engine,
user_interfaces,
performance_statistics: Default::default(),
plugins: Default::default(),
serialization_context,
widget_constructors,
script_processor: Default::default(),
plugins_enabled: false,
elapsed_time: 0.0,
task_pool: TaskPoolHandler::new(task_pool),
})
}
pub fn initialize_graphics_context(
&mut self,
window_target: &EventLoopWindowTarget<()>,
) -> Result<(), EngineError> {
if let GraphicsContext::Uninitialized(params) = &self.graphics_context {
let mut window_builder = WindowBuilder::new();
if let Some(inner_size) = params.window_attributes.inner_size {
window_builder = window_builder.with_inner_size(inner_size);
}
if let Some(min_inner_size) = params.window_attributes.min_inner_size {
window_builder = window_builder.with_min_inner_size(min_inner_size);
}
if let Some(max_inner_size) = params.window_attributes.max_inner_size {
window_builder = window_builder.with_min_inner_size(max_inner_size);
}
if let Some(position) = params.window_attributes.position {
window_builder = window_builder.with_position(position);
}
if let Some(resize_increments) = params.window_attributes.resize_increments {
window_builder = window_builder.with_resize_increments(resize_increments);
}
unsafe {
window_builder = window_builder
.with_parent_window(params.window_attributes.parent_window().cloned());
}
window_builder = window_builder
.with_resizable(params.window_attributes.resizable)
.with_enabled_buttons(params.window_attributes.enabled_buttons)
.with_title(params.window_attributes.title.clone())
.with_fullscreen(params.window_attributes.fullscreen().cloned())
.with_maximized(params.window_attributes.maximized)
.with_visible(params.window_attributes.visible)
.with_transparent(params.window_attributes.transparent)
.with_decorations(params.window_attributes.decorations)
.with_window_icon(params.window_attributes.window_icon.clone())
.with_theme(params.window_attributes.preferred_theme)
.with_content_protected(params.window_attributes.content_protected)
.with_window_level(params.window_attributes.window_level)
.with_active(params.window_attributes.active);
let (window, server) =
params.graphics_server_constructor.0(params, window_target, window_builder)?;
let frame_size = (window.inner_size().width, window.inner_size().height);
let renderer = Renderer::new(server, frame_size, &self.resource_manager)?;
for ui in self.user_interfaces.iter_mut() {
ui.set_screen_size(Vector2::new(frame_size.0 as f32, frame_size.1 as f32));
}
self.graphics_context = GraphicsContext::Initialized(InitializedGraphicsContext {
renderer,
window,
params: params.clone(),
});
if let Err(err) = self.sound_engine.initialize_audio_output_device() {
Log::err(format!(
"Unable to initialize audio output device! Reason: {err:?}"
));
}
Ok(())
} else {
Err(EngineError::Custom(
"Graphics context is already initialized!".to_string(),
))
}
}
pub fn destroy_graphics_context(&mut self) -> Result<(), EngineError> {
if let GraphicsContext::Initialized(ref ctx) = self.graphics_context {
let params = &ctx.params;
let window = &ctx.window;
let mut window_attributes = WindowAttributes::default();
window_attributes.inner_size = Some(Size::Physical(window.inner_size()));
window_attributes.min_inner_size = params.window_attributes.min_inner_size;
window_attributes.max_inner_size = params.window_attributes.max_inner_size;
window_attributes.position = window.outer_position().ok().map(Position::Physical);
window_attributes.resizable = window.is_resizable();
window_attributes.enabled_buttons = window.enabled_buttons();
window_attributes.title = window.title();
window_attributes.maximized = window.is_maximized();
window_attributes.visible = window.is_visible().unwrap_or(true);
window_attributes.transparent = params.window_attributes.transparent;
window_attributes.decorations = window.is_decorated();
window_attributes.preferred_theme = params.window_attributes.preferred_theme;
window_attributes.resize_increments = window.resize_increments().map(Size::Physical);
window_attributes.content_protected = params.window_attributes.content_protected;
window_attributes.window_level = params.window_attributes.window_level;
window_attributes.active = params.window_attributes.active;
window_attributes
.window_icon
.clone_from(¶ms.window_attributes.window_icon);
self.graphics_context = GraphicsContext::Uninitialized(GraphicsContextParams {
window_attributes,
vsync: params.vsync,
msaa_sample_count: params.msaa_sample_count,
graphics_server_constructor: params.graphics_server_constructor.clone(),
});
self.sound_engine.destroy_audio_output_device();
Ok(())
} else {
Err(EngineError::Custom(
"Graphics context is already destroyed!".to_string(),
))
}
}
pub fn set_frame_size(&mut self, new_size: (u32, u32)) -> Result<(), FrameworkError> {
if let GraphicsContext::Initialized(ctx) = &mut self.graphics_context {
ctx.renderer.set_frame_size(new_size)?;
}
Ok(())
}
pub fn elapsed_time(&self) -> f32 {
self.elapsed_time
}
pub fn update(
&mut self,
dt: f32,
window_target: &EventLoopWindowTarget<()>,
lag: &mut f32,
switches: FxHashMap<Handle<Scene>, GraphUpdateSwitches>,
) {
self.handle_async_scene_loading(dt, lag, window_target);
self.pre_update(dt, window_target, lag, switches);
self.post_update(dt, &Default::default(), lag, window_target);
self.handle_plugins_hot_reloading(dt, window_target, lag, |_| {});
}
pub fn handle_plugins_hot_reloading<F>(
&mut self,
#[allow(unused_variables)] dt: f32,
#[allow(unused_variables)] window_target: &EventLoopWindowTarget<()>,
#[allow(unused_variables)] lag: &mut f32,
#[allow(unused_variables)] on_reloaded: F,
) where
F: FnMut(&dyn Plugin),
{
#[cfg(any(unix, windows))]
{
if let Err(message) = self.reload_dynamic_plugins(dt, window_target, lag, on_reloaded) {
Log::err(format!(
"Unable to reload dynamic plugins. Reason: {message}"
))
}
}
}
fn handle_async_scene_loading(
&mut self,
dt: f32,
lag: &mut f32,
window_target: &EventLoopWindowTarget<()>,
) {
let len = self.async_scene_loader.loading_scenes.len();
let mut n = 0;
while n < len {
if let Some(request) = self.async_scene_loader.loading_scenes.values_mut().nth(n) {
if !request.reported {
request.reported = true;
if self.plugins_enabled {
let path = request.path.clone();
let mut context = PluginContext {
scenes: &mut self.scenes,
resource_manager: &self.resource_manager,
graphics_context: &mut self.graphics_context,
dt,
lag,
user_interfaces: &mut self.user_interfaces,
serialization_context: &self.serialization_context,
widget_constructors: &self.widget_constructors,
performance_statistics: &self.performance_statistics,
elapsed_time: self.elapsed_time,
script_processor: &self.script_processor,
async_scene_loader: &mut self.async_scene_loader,
window_target: Some(window_target),
task_pool: &mut self.task_pool,
};
for plugin in self.plugins.iter_mut() {
plugin.on_scene_begin_loading(&path, &mut context);
}
}
}
}
n += 1;
}
while let Ok(loading_result) = self.async_scene_loader.receiver.try_recv() {
if let Some(request) = self
.async_scene_loader
.loading_scenes
.remove(&loading_result.path)
{
let mut context = PluginContext {
scenes: &mut self.scenes,
resource_manager: &self.resource_manager,
graphics_context: &mut self.graphics_context,
dt,
lag,
user_interfaces: &mut self.user_interfaces,
serialization_context: &self.serialization_context,
widget_constructors: &self.widget_constructors,
performance_statistics: &self.performance_statistics,
elapsed_time: self.elapsed_time,
script_processor: &self.script_processor,
async_scene_loader: &mut self.async_scene_loader,
window_target: Some(window_target),
task_pool: &mut self.task_pool,
};
match loading_result.result {
Ok((mut scene, data)) => {
if request.options.derived {
let model = Resource::new_ok(
ResourceKind::External(request.path.clone()),
Model {
mapping: NodeMapping::UseHandles,
scene: scene
.clone(
scene.graph.get_root(),
&mut |_, _| true,
&mut |_, _| {},
&mut |_, _, _| {},
)
.0,
},
);
Log::verify(self.resource_manager.register(
model.clone().into_untyped(),
request.path.clone(),
|_, _| true,
));
for (handle, node) in scene.graph.pair_iter_mut() {
node.set_inheritance_data(handle, model.clone());
}
(&mut scene as &mut dyn Reflect).apply_recursively_mut(
&mut |object| {
let type_id = (*object).type_id();
if type_id != TypeId::of::<NodePool>() {
object.as_inheritable_variable_mut(&mut |variable| {
if let Some(variable) = variable {
variable.reset_modified_flag();
}
});
}
},
&[
TypeId::of::<UntypedResource>(),
TypeId::of::<navmesh::Container>(),
],
)
} else {
if let Some(source_asset) =
scene.graph[scene.graph.get_root()].root_resource()
{
let source_asset_ref = source_asset.data_ref();
let source_scene_ref = &source_asset_ref.scene;
Log::verify(try_inherit_properties(
&mut scene,
source_scene_ref,
&[
TypeId::of::<NodePool>(),
TypeId::of::<UntypedResource>(),
TypeId::of::<navmesh::Container>(),
],
));
}
}
let scene_handle = context.scenes.add(scene);
if self.plugins_enabled {
for plugin in self.plugins.iter_mut() {
Log::info(format!(
"Scene {} was loaded successfully!",
loading_result.path.display()
));
plugin.on_scene_loaded(
&request.path,
scene_handle,
&data,
&mut context,
);
}
}
}
Err(error) => {
if self.plugins_enabled {
Log::err(format!(
"Unable to load scene {}. Reason: {:?}",
loading_result.path.display(),
error
));
for plugin in self.plugins.iter_mut() {
plugin.on_scene_loading_failed(&request.path, &error, &mut context);
}
}
}
}
}
}
}
pub fn pre_update(
&mut self,
dt: f32,
window_target: &EventLoopWindowTarget<()>,
lag: &mut f32,
switches: FxHashMap<Handle<Scene>, GraphUpdateSwitches>,
) {
self.resource_manager.state().update(dt);
self.handle_model_events();
let window_size = if let GraphicsContext::Initialized(ctx) = &mut self.graphics_context {
let inner_size = ctx.window.inner_size();
let window_size = Vector2::new(inner_size.width as f32, inner_size.height as f32);
ctx.renderer.update_caches(dt);
window_size
} else {
Vector2::new(1.0, 1.0)
};
for (handle, scene) in self.scenes.pair_iter_mut().filter(|(_, s)| *s.enabled) {
let frame_size =
scene
.rendering_options
.render_target
.as_ref()
.map_or(window_size, |rt| {
if let TextureKind::Rectangle { width, height } = rt.data_ref().kind() {
Vector2::new(width as f32, height as f32)
} else {
panic!("only rectangle textures can be used as render target!");
}
});
scene.update(
frame_size,
dt,
switches.get(&handle).cloned().unwrap_or_default(),
);
}
self.update_plugins(dt, window_target, lag);
self.handle_scripts(dt);
}
pub fn post_update(
&mut self,
dt: f32,
ui_update_switches: &UiUpdateSwitches,
lag: &mut f32,
window_target: &EventLoopWindowTarget<()>,
) {
if let GraphicsContext::Initialized(ref ctx) = self.graphics_context {
let inner_size = ctx.window.inner_size();
let window_size = Vector2::new(inner_size.width as f32, inner_size.height as f32);
let time = instant::Instant::now();
for ui in self.user_interfaces.iter_mut() {
ui.update(window_size, dt, ui_update_switches);
}
self.performance_statistics.ui_time = instant::Instant::now() - time;
self.elapsed_time += dt;
self.post_update_plugins(dt, window_target, lag);
}
}
pub fn has_scripted_scene(&self, scene: Handle<Scene>) -> bool {
self.script_processor.has_scripted_scene(scene)
}
pub fn register_scripted_scene(&mut self, scene: Handle<Scene>) {
self.script_processor
.register_scripted_scene(scene, &self.resource_manager)
}
fn handle_scripts(&mut self, dt: f32) {
let time = instant::Instant::now();
self.script_processor.handle_scripts(
&mut self.scenes,
&mut self.plugins,
&self.resource_manager,
&mut self.task_pool,
&mut self.graphics_context,
&mut self.user_interfaces,
dt,
self.elapsed_time,
);
self.performance_statistics.scripts_time = instant::Instant::now() - time;
}
fn handle_async_tasks(
&mut self,
dt: f32,
window_target: &EventLoopWindowTarget<()>,
lag: &mut f32,
) {
while let Some(result) = self.task_pool.inner().next_task_result() {
if let Some(plugin_task_handler) = self.task_pool.pop_plugin_task_handler(result.id) {
(plugin_task_handler)(
result.payload,
&mut self.plugins,
&mut PluginContext {
scenes: &mut self.scenes,
resource_manager: &self.resource_manager,
graphics_context: &mut self.graphics_context,
dt,
lag,
user_interfaces: &mut self.user_interfaces,
serialization_context: &self.serialization_context,
widget_constructors: &self.widget_constructors,
performance_statistics: &self.performance_statistics,
elapsed_time: self.elapsed_time,
script_processor: &self.script_processor,
async_scene_loader: &mut self.async_scene_loader,
window_target: Some(window_target),
task_pool: &mut self.task_pool,
},
)
} else if let Some(node_task_handler) = self.task_pool.pop_node_task_handler(result.id)
{
if let Some(scripted_scene) = self
.script_processor
.scripted_scenes
.iter_mut()
.find(|e| e.handle == node_task_handler.scene_handle)
{
let payload = result.payload;
if let Some(scene) = self.scenes.try_get_mut(node_task_handler.scene_handle) {
if let Some(node) = scene.graph.try_get_mut(node_task_handler.node_handle) {
if let Some(mut script) = node
.scripts
.get_mut(node_task_handler.script_index)
.and_then(|e| e.script.take())
{
(node_task_handler.closure)(
payload,
script.deref_mut(),
&mut ScriptContext {
dt,
elapsed_time: self.elapsed_time,
plugins: PluginsRefMut(&mut self.plugins),
handle: node_task_handler.node_handle,
scene,
scene_handle: scripted_scene.handle,
resource_manager: &self.resource_manager,
message_sender: &scripted_scene.message_sender,
message_dispatcher: &mut scripted_scene.message_dispatcher,
task_pool: &mut self.task_pool,
graphics_context: &mut self.graphics_context,
user_interfaces: &mut self.user_interfaces,
script_index: node_task_handler.script_index,
},
);
if let Some(node) =
scene.graph.try_get_mut(node_task_handler.node_handle)
{
if let Some(entry) =
node.scripts.get_mut(node_task_handler.script_index)
{
if entry.should_be_deleted {
Log::verify(scene.graph.script_message_sender.send(
NodeScriptMessage::DestroyScript {
script,
handle: node_task_handler.node_handle,
script_index: node_task_handler.script_index,
},
));
} else {
entry.script = Some(script);
}
}
}
}
}
}
}
}
}
}
fn update_plugins(
&mut self,
dt: f32,
window_target: &EventLoopWindowTarget<()>,
lag: &mut f32,
) {
let time = instant::Instant::now();
if self.plugins_enabled {
self.handle_async_tasks(dt, window_target, lag);
let mut context = PluginContext {
scenes: &mut self.scenes,
resource_manager: &self.resource_manager,
graphics_context: &mut self.graphics_context,
dt,
lag,
user_interfaces: &mut self.user_interfaces,
serialization_context: &self.serialization_context,
widget_constructors: &self.widget_constructors,
performance_statistics: &self.performance_statistics,
elapsed_time: self.elapsed_time,
script_processor: &self.script_processor,
async_scene_loader: &mut self.async_scene_loader,
window_target: Some(window_target),
task_pool: &mut self.task_pool,
};
for plugin in self.plugins.iter_mut() {
plugin.update(&mut context);
}
let mut uis = self
.user_interfaces
.pair_iter()
.map(|(h, _)| h)
.collect::<VecDeque<_>>();
while let Some(ui) = uis.pop_front() {
while let Some(message) = self
.user_interfaces
.try_get_mut(ui)
.and_then(|ui| ui.poll_message())
{
let mut context = PluginContext {
scenes: &mut self.scenes,
resource_manager: &self.resource_manager,
graphics_context: &mut self.graphics_context,
dt,
lag,
user_interfaces: &mut self.user_interfaces,
serialization_context: &self.serialization_context,
widget_constructors: &self.widget_constructors,
performance_statistics: &self.performance_statistics,
elapsed_time: self.elapsed_time,
script_processor: &self.script_processor,
async_scene_loader: &mut self.async_scene_loader,
window_target: Some(window_target),
task_pool: &mut self.task_pool,
};
for plugin in self.plugins.iter_mut() {
plugin.on_ui_message(&mut context, &message);
}
}
}
}
self.performance_statistics.plugins_time = instant::Instant::now() - time;
}
fn post_update_plugins(
&mut self,
dt: f32,
window_target: &EventLoopWindowTarget<()>,
lag: &mut f32,
) {
let time = instant::Instant::now();
if self.plugins_enabled {
let mut context = PluginContext {
scenes: &mut self.scenes,
resource_manager: &self.resource_manager,
graphics_context: &mut self.graphics_context,
dt,
lag,
user_interfaces: &mut self.user_interfaces,
serialization_context: &self.serialization_context,
widget_constructors: &self.widget_constructors,
performance_statistics: &self.performance_statistics,
elapsed_time: self.elapsed_time,
script_processor: &self.script_processor,
async_scene_loader: &mut self.async_scene_loader,
window_target: Some(window_target),
task_pool: &mut self.task_pool,
};
for plugin in self.plugins.iter_mut() {
plugin.post_update(&mut context);
}
}
self.performance_statistics.plugins_time += instant::Instant::now() - time;
}
pub(crate) fn handle_os_event_by_plugins(
&mut self,
event: &Event<()>,
dt: f32,
window_target: &EventLoopWindowTarget<()>,
lag: &mut f32,
) {
if self.plugins_enabled {
for plugin in self.plugins.iter_mut() {
plugin.on_os_event(
event,
PluginContext {
scenes: &mut self.scenes,
resource_manager: &self.resource_manager,
graphics_context: &mut self.graphics_context,
dt,
lag,
user_interfaces: &mut self.user_interfaces,
serialization_context: &self.serialization_context,
widget_constructors: &self.widget_constructors,
performance_statistics: &self.performance_statistics,
elapsed_time: self.elapsed_time,
script_processor: &self.script_processor,
async_scene_loader: &mut self.async_scene_loader,
window_target: Some(window_target),
task_pool: &mut self.task_pool,
},
);
}
}
}
pub(crate) fn handle_graphics_context_created_by_plugins(
&mut self,
dt: f32,
window_target: &EventLoopWindowTarget<()>,
lag: &mut f32,
) {
if self.plugins_enabled {
for plugin in self.plugins.iter_mut() {
plugin.on_graphics_context_initialized(PluginContext {
scenes: &mut self.scenes,
resource_manager: &self.resource_manager,
graphics_context: &mut self.graphics_context,
dt,
lag,
user_interfaces: &mut self.user_interfaces,
serialization_context: &self.serialization_context,
widget_constructors: &self.widget_constructors,
performance_statistics: &self.performance_statistics,
elapsed_time: self.elapsed_time,
script_processor: &self.script_processor,
async_scene_loader: &mut self.async_scene_loader,
window_target: Some(window_target),
task_pool: &mut self.task_pool,
});
}
}
}
pub(crate) fn handle_graphics_context_destroyed_by_plugins(
&mut self,
dt: f32,
window_target: &EventLoopWindowTarget<()>,
lag: &mut f32,
) {
if self.plugins_enabled {
for plugin in self.plugins.iter_mut() {
plugin.on_graphics_context_destroyed(PluginContext {
scenes: &mut self.scenes,
resource_manager: &self.resource_manager,
graphics_context: &mut self.graphics_context,
dt,
lag,
user_interfaces: &mut self.user_interfaces,
serialization_context: &self.serialization_context,
widget_constructors: &self.widget_constructors,
performance_statistics: &self.performance_statistics,
elapsed_time: self.elapsed_time,
script_processor: &self.script_processor,
async_scene_loader: &mut self.async_scene_loader,
window_target: Some(window_target),
task_pool: &mut self.task_pool,
});
}
}
}
pub(crate) fn handle_before_rendering_by_plugins(
&mut self,
dt: f32,
window_target: &EventLoopWindowTarget<()>,
lag: &mut f32,
) {
if self.plugins_enabled {
for plugin in self.plugins.iter_mut() {
plugin.before_rendering(PluginContext {
scenes: &mut self.scenes,
resource_manager: &self.resource_manager,
graphics_context: &mut self.graphics_context,
dt,
lag,
user_interfaces: &mut self.user_interfaces,
serialization_context: &self.serialization_context,
widget_constructors: &self.widget_constructors,
performance_statistics: &self.performance_statistics,
elapsed_time: self.elapsed_time,
script_processor: &self.script_processor,
async_scene_loader: &mut self.async_scene_loader,
window_target: Some(window_target),
task_pool: &mut self.task_pool,
});
}
}
}
pub(crate) fn handle_os_event_by_scripts(
&mut self,
event: &Event<()>,
scene_handle: Handle<Scene>,
dt: f32,
) {
if let Some(scripted_scene) = self
.script_processor
.scripted_scenes
.iter_mut()
.find(|s| s.handle == scene_handle)
{
let scene = &mut self.scenes[scene_handle];
if *scene.enabled {
process_scripts(
scene,
scene_handle,
&mut self.plugins,
&self.resource_manager,
&scripted_scene.message_sender,
&mut scripted_scene.message_dispatcher,
&mut self.task_pool,
&mut self.graphics_context,
&mut self.user_interfaces,
dt,
self.elapsed_time,
|script, context| {
if script.initialized && script.started {
script.on_os_event(event, context);
}
},
)
}
}
}
pub fn handle_model_events(&mut self) {
while let Ok(event) = self.model_events_receiver.try_recv() {
if let ResourceEvent::Reloaded(resource) = event {
if let Some(model) = resource.try_cast::<Model>() {
Log::info(format!(
"A model resource {} was reloaded, propagating changes...",
model.kind()
));
ResourceDependencyGraph::new(model, self.resource_manager.clone()).resolve();
Log::info("Propagating changes to active scenes...");
for scene in self.scenes.iter_mut() {
scene.resolve();
}
}
}
}
}
#[inline]
pub fn render(&mut self) -> Result<(), FrameworkError> {
for ui in self.user_interfaces.iter_mut() {
ui.set_time(self.elapsed_time);
ui.draw();
}
if let GraphicsContext::Initialized(ref mut ctx) = self.graphics_context {
ctx.renderer.render_and_swap_buffers(
&self.scenes,
self.elapsed_time,
self.user_interfaces
.iter()
.map(|ui| ui.get_drawing_context()),
&ctx.window,
)?;
}
Ok(())
}
pub(crate) fn enable_plugins(
&mut self,
scene_path: Option<&str>,
enabled: bool,
window_target: Option<&EventLoopWindowTarget<()>>,
) {
if self.plugins_enabled != enabled {
self.plugins_enabled = enabled;
if self.plugins_enabled {
for plugin in self.plugins.iter_mut() {
plugin.init(
scene_path,
PluginContext {
scenes: &mut self.scenes,
resource_manager: &self.resource_manager,
graphics_context: &mut self.graphics_context,
dt: 0.0,
lag: &mut 0.0,
user_interfaces: &mut self.user_interfaces,
serialization_context: &self.serialization_context,
widget_constructors: &self.widget_constructors,
performance_statistics: &self.performance_statistics,
elapsed_time: self.elapsed_time,
script_processor: &self.script_processor,
async_scene_loader: &mut self.async_scene_loader,
window_target,
task_pool: &mut self.task_pool,
},
);
}
} else {
self.handle_scripts(0.0);
for mut plugin in self.plugins.drain(..) {
plugin.on_deinit(PluginContext {
scenes: &mut self.scenes,
resource_manager: &self.resource_manager,
graphics_context: &mut self.graphics_context,
dt: 0.0,
lag: &mut 0.0,
user_interfaces: &mut self.user_interfaces,
serialization_context: &self.serialization_context,
widget_constructors: &self.widget_constructors,
performance_statistics: &self.performance_statistics,
elapsed_time: self.elapsed_time,
script_processor: &self.script_processor,
async_scene_loader: &mut self.async_scene_loader,
window_target,
task_pool: &mut self.task_pool,
});
}
}
}
}
fn register_plugin_internal(
serialization_context: &Arc<SerializationContext>,
widget_constructors: &Arc<WidgetConstructorContainer>,
resource_manager: &ResourceManager,
plugin: &dyn Plugin,
) {
plugin.register(PluginRegistrationContext {
serialization_context,
widget_constructors,
resource_manager,
});
}
fn register_plugin(&self, plugin: &dyn Plugin) {
Self::register_plugin_internal(
&self.serialization_context,
&self.widget_constructors,
&self.resource_manager,
plugin,
)
}
pub fn add_plugin<P>(&mut self, plugin: P)
where
P: Plugin + 'static,
{
self.register_plugin(&plugin);
self.plugins.push(PluginContainer::Static(Box::new(plugin)));
}
pub fn add_dynamic_plugin<P>(
&mut self,
path: P,
reload_when_changed: bool,
use_relative_paths: bool,
) -> Result<&dyn Plugin, String>
where
P: AsRef<Path> + 'static,
{
Ok(self.add_dynamic_plugin_custom(DyLibDynamicPlugin::new(
path,
reload_when_changed,
use_relative_paths,
)?))
}
pub fn add_dynamic_plugin_custom<P>(&mut self, plugin: P) -> &dyn Plugin
where
P: DynamicPlugin + 'static,
{
let display_name = plugin.display_name();
let plugin_container = PluginContainer::Dynamic(Box::new(plugin));
self.register_plugin(plugin_container.deref());
self.plugins.push(plugin_container);
Log::info(format!("Plugin {display_name:?} was loaded successfully"));
&**self.plugins.last().unwrap()
}
pub fn reload_plugin(
&mut self,
plugin_index: usize,
dt: f32,
window_target: &EventLoopWindowTarget<()>,
lag: &mut f32,
) -> Result<(), String> {
let plugin_container = &mut self.plugins[plugin_index];
let PluginContainer::Dynamic(plugin) = plugin_container else {
return Err(format!(
"Plugin {plugin_index} is static and cannot be reloaded!",
));
};
if !plugin.is_loaded() {
return Err(format!("Cannot reload unloaded plugin {plugin_index}!"));
}
plugin.prepare_to_reload();
let plugin_type_id = plugin.as_loaded_ref().type_id();
let plugin_assembly_name = plugin.as_loaded_ref().assembly_name();
let mut scenes_state = Vec::new();
for (scene_handle, scene) in self.scenes.pair_iter_mut() {
if let Some(data) = hotreload::SceneState::try_create_from_plugin(
scene_handle,
scene,
&self.serialization_context,
plugin.as_loaded_ref(),
)? {
scenes_state.push(data);
}
}
let mut prefab_scenes = Vec::new();
let rm_state = self.resource_manager.state();
for resource in rm_state.resources().iter() {
if let Some(model) = resource.try_cast::<Model>() {
let mut model_state = model.state();
if let Some(data) = model_state.data() {
if let Some(scene_state) = hotreload::SceneState::try_create_from_plugin(
Handle::NONE,
&mut data.scene,
&self.serialization_context,
plugin.as_loaded_ref(),
)? {
prefab_scenes.push((model.clone(), scene_state));
}
}
}
}
drop(rm_state);
let mut constructors = FxHashSet::default();
for (type_uuid, constructor) in self.serialization_context.script_constructors.map().iter()
{
if constructor.assembly_name == plugin_assembly_name {
constructors.insert(*type_uuid);
}
}
for type_uuid in constructors.iter() {
self.serialization_context
.script_constructors
.remove(*type_uuid);
}
let mut constructors = FxHashSet::default();
for (type_uuid, constructor) in self.serialization_context.node_constructors.map().iter() {
if constructor.assembly_name == plugin_assembly_name {
constructors.insert(*type_uuid);
}
}
for type_uuid in constructors.iter() {
self.serialization_context
.node_constructors
.remove(*type_uuid);
}
let mut constructors = FxHashSet::default();
for (type_uuid, constructor) in self.widget_constructors.map().iter() {
if constructor.assembly_name == plugin_assembly_name {
constructors.insert(*type_uuid);
}
}
for type_uuid in constructors.iter() {
self.widget_constructors.remove(*type_uuid);
}
{
let mut resources_to_reload = FxHashSet::default();
let mut state = self.resource_manager.state();
for resource in state.resources().iter() {
let data = resource.0.lock();
if let ResourceState::Ok(ref data) = data.state {
data.as_reflect(&mut |reflect| {
if reflect.assembly_name() == plugin_assembly_name {
resources_to_reload.insert(resource.clone());
}
})
}
}
for resource_to_reload in resources_to_reload.iter() {
Log::info(format!(
"Reloading {} resource, because it is used in plugin {plugin_assembly_name}",
resource_to_reload.kind()
));
state.reload_resource(resource_to_reload.clone());
}
drop(state);
block_on(join_all(resources_to_reload));
}
if let GraphicsContext::Initialized(ref mut graphics_context) = self.graphics_context {
let render_passes = graphics_context.renderer.render_passes().to_vec();
for render_pass in render_passes {
if render_pass.borrow().source_type_id() == plugin_type_id {
graphics_context.renderer.remove_render_pass(render_pass);
}
}
}
let mut visitor = hotreload::make_writing_visitor();
plugin
.as_loaded_mut()
.visit("Plugin", &mut visitor)
.map_err(|e| e.to_string())?;
let mut binary_blob = Cursor::new(Vec::<u8>::new());
visitor
.save_binary_to_memory(&mut binary_blob)
.map_err(|e| e.to_string())?;
Log::info(format!(
"Plugin {plugin_index} was serialized successfully!"
));
drop(visitor);
let binary_blob = binary_blob.into_inner();
plugin.reload(&mut |plugin| {
Self::register_plugin_internal(
&self.serialization_context,
&self.widget_constructors,
&self.resource_manager,
plugin,
);
let mut visitor = hotreload::make_reading_visitor(
&binary_blob,
&self.serialization_context,
&self.resource_manager,
&self.widget_constructors,
)
.map_err(|e| e.to_string())?;
plugin
.visit("Plugin", &mut visitor)
.map_err(|e| e.to_string())?;
Ok(())
})?;
for (model, scene_state) in prefab_scenes {
Log::info(format!("Deserializing {} prefab content...", model.kind()));
scene_state.deserialize_into_prefab_scene(
&model,
&self.serialization_context,
&self.resource_manager,
&self.widget_constructors,
)?;
}
for scene_state in scenes_state {
let scene = &mut self.scenes[scene_state.scene];
scene_state.deserialize_into_scene(
scene,
&self.serialization_context,
&self.resource_manager,
&self.widget_constructors,
)?;
}
plugin.as_loaded_mut().on_loaded(PluginContext {
scenes: &mut self.scenes,
resource_manager: &self.resource_manager,
user_interfaces: &mut self.user_interfaces,
graphics_context: &mut self.graphics_context,
dt,
lag,
serialization_context: &self.serialization_context,
widget_constructors: &self.widget_constructors,
performance_statistics: &Default::default(),
elapsed_time: self.elapsed_time,
script_processor: &self.script_processor,
async_scene_loader: &mut self.async_scene_loader,
window_target: Some(window_target),
task_pool: &mut self.task_pool,
});
Log::info(format!("Plugin {plugin_index} was successfully reloaded!"));
Ok(())
}
pub fn plugins(&self) -> &[PluginContainer] {
&self.plugins
}
pub fn plugins_mut(&mut self) -> &mut [PluginContainer] {
&mut self.plugins
}
pub fn reload_dynamic_plugins<F>(
&mut self,
dt: f32,
window_target: &EventLoopWindowTarget<()>,
lag: &mut f32,
mut on_reloaded: F,
) -> Result<(), String>
where
F: FnMut(&dyn Plugin),
{
for plugin_index in 0..self.plugins.len() {
if let PluginContainer::Dynamic(plugin) = &self.plugins[plugin_index] {
if plugin.is_reload_needed_now() {
self.reload_plugin(plugin_index, dt, window_target, lag)?;
on_reloaded(self.plugins[plugin_index].deref_mut());
}
}
}
Ok(())
}
}
impl Drop for Engine {
fn drop(&mut self) {
let scenes = self
.scenes
.pair_iter()
.map(|(h, _)| h)
.collect::<Vec<Handle<Scene>>>();
for handle in scenes {
self.scenes.remove(handle);
}
self.enable_plugins(None, false, None);
}
}
#[cfg(test)]
mod test {
use crate::{
asset::manager::ResourceManager,
core::{
pool::Handle, reflect::prelude::*, task::TaskPool, type_traits::prelude::*,
visitor::prelude::*,
},
engine::{task::TaskPoolHandler, GraphicsContext, ScriptProcessor},
graph::BaseSceneGraph,
scene::{base::BaseBuilder, node::Node, pivot::PivotBuilder, Scene, SceneContainer},
script::{
ScriptContext, ScriptDeinitContext, ScriptMessageContext, ScriptMessagePayload,
ScriptTrait,
},
};
use fyrox_ui::UiContainer;
use std::sync::{
mpsc::{self, Sender, TryRecvError},
Arc,
};
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
struct Source {
node_handle: Handle<Node>,
script_index: usize,
}
impl Source {
fn from_ctx(ctx: &ScriptContext) -> Self {
Self {
node_handle: ctx.handle,
script_index: ctx.script_index,
}
}
fn from_deinit_ctx(ctx: &ScriptDeinitContext) -> Self {
Self {
node_handle: ctx.node_handle,
script_index: ctx.script_index,
}
}
fn from_msg_ctx(ctx: &ScriptMessageContext) -> Self {
Self {
node_handle: ctx.handle,
script_index: ctx.script_index,
}
}
}
#[allow(clippy::enum_variant_names)]
#[derive(PartialEq, Eq, Clone, Debug)]
enum Event {
Initialized(Source),
Started(Source),
Updated(Source),
Destroyed(Source),
EventReceived(Source),
}
#[derive(Debug, Clone, Reflect, Visit, TypeUuidProvider, ComponentProvider)]
#[type_uuid(id = "2569de84-d4b2-427d-969b-d5c7b31a0ba6")]
struct MyScript {
#[reflect(hidden)]
#[visit(skip)]
sender: Sender<Event>,
spawned: bool,
}
impl ScriptTrait for MyScript {
fn on_init(&mut self, ctx: &mut ScriptContext) {
self.sender
.send(Event::Initialized(Source::from_ctx(ctx)))
.unwrap();
let handle = PivotBuilder::new(BaseBuilder::new().with_script(MySubScript {
sender: self.sender.clone(),
}))
.build(&mut ctx.scene.graph);
assert_eq!(handle, Handle::new(2, 1));
}
fn on_start(&mut self, ctx: &mut ScriptContext) {
self.sender
.send(Event::Started(Source::from_ctx(ctx)))
.unwrap();
let handle = PivotBuilder::new(BaseBuilder::new().with_script(MySubScript {
sender: self.sender.clone(),
}))
.build(&mut ctx.scene.graph);
assert_eq!(handle, Handle::new(3, 1));
}
fn on_deinit(&mut self, ctx: &mut ScriptDeinitContext) {
self.sender
.send(Event::Destroyed(Source::from_deinit_ctx(ctx)))
.unwrap();
}
fn on_update(&mut self, ctx: &mut ScriptContext) {
self.sender
.send(Event::Updated(Source::from_ctx(ctx)))
.unwrap();
if !self.spawned {
PivotBuilder::new(BaseBuilder::new().with_script(MySubScript {
sender: self.sender.clone(),
}))
.build(&mut ctx.scene.graph);
self.spawned = true;
}
}
}
#[derive(Debug, Clone, Reflect, Visit, TypeUuidProvider, ComponentProvider)]
#[type_uuid(id = "1cebacd9-b500-4753-93be-39db344add21")]
struct MySubScript {
#[reflect(hidden)]
#[visit(skip)]
sender: Sender<Event>,
}
impl ScriptTrait for MySubScript {
fn on_init(&mut self, ctx: &mut ScriptContext) {
self.sender
.send(Event::Initialized(Source::from_ctx(ctx)))
.unwrap();
}
fn on_start(&mut self, ctx: &mut ScriptContext) {
self.sender
.send(Event::Started(Source::from_ctx(ctx)))
.unwrap();
}
fn on_deinit(&mut self, ctx: &mut ScriptDeinitContext) {
self.sender
.send(Event::Destroyed(Source::from_deinit_ctx(ctx)))
.unwrap();
}
fn on_update(&mut self, ctx: &mut ScriptContext) {
self.sender
.send(Event::Updated(Source::from_ctx(ctx)))
.unwrap();
}
}
#[test]
fn test_order() {
let resource_manager = ResourceManager::new(Arc::new(Default::default()));
let mut scene = Scene::new();
let (tx, rx) = mpsc::channel();
let node_handle = PivotBuilder::new(
BaseBuilder::new()
.with_script(MyScript {
sender: tx.clone(),
spawned: false,
})
.with_script(MySubScript { sender: tx }),
)
.build(&mut scene.graph);
assert_eq!(node_handle, Handle::new(1, 1));
let node_handle_0 = Source {
node_handle,
script_index: 0,
};
let node_handle_1 = Source {
node_handle,
script_index: 1,
};
let mut scene_container = SceneContainer::new(Default::default());
let scene_handle = scene_container.add(scene);
let mut script_processor = ScriptProcessor::default();
script_processor.register_scripted_scene(scene_handle, &resource_manager);
let handle_on_init = Source {
node_handle: Handle::new(2, 1),
script_index: 0,
};
let handle_on_start = Source {
node_handle: Handle::new(3, 1),
script_index: 0,
};
let handle_on_update1 = Source {
node_handle: Handle::new(4, 1),
script_index: 0,
};
let mut task_pool = TaskPoolHandler::new(Arc::new(TaskPool::new()));
let mut gc = GraphicsContext::Uninitialized(Default::default());
let mut user_interfaces = UiContainer::default();
for iteration in 0..3 {
script_processor.handle_scripts(
&mut scene_container,
&mut Vec::new(),
&resource_manager,
&mut task_pool,
&mut gc,
&mut user_interfaces,
0.0,
0.0,
);
match iteration {
0 => {
assert_eq!(rx.try_recv(), Ok(Event::Initialized(node_handle_0)));
assert_eq!(rx.try_recv(), Ok(Event::Initialized(node_handle_1)));
assert_eq!(rx.try_recv(), Ok(Event::Initialized(handle_on_init)));
assert_eq!(rx.try_recv(), Ok(Event::Started(node_handle_0)));
assert_eq!(rx.try_recv(), Ok(Event::Started(node_handle_1)));
assert_eq!(rx.try_recv(), Ok(Event::Started(handle_on_init)));
assert_eq!(rx.try_recv(), Ok(Event::Initialized(handle_on_start)));
assert_eq!(rx.try_recv(), Ok(Event::Started(handle_on_start)));
assert_eq!(rx.try_recv(), Ok(Event::Updated(node_handle_0)));
assert_eq!(rx.try_recv(), Ok(Event::Updated(node_handle_1)));
assert_eq!(rx.try_recv(), Ok(Event::Updated(handle_on_init)));
assert_eq!(rx.try_recv(), Ok(Event::Updated(handle_on_start)));
assert_eq!(rx.try_recv(), Ok(Event::Initialized(handle_on_update1)));
assert_eq!(rx.try_recv(), Ok(Event::Started(handle_on_update1)));
assert_eq!(rx.try_recv(), Ok(Event::Updated(handle_on_update1)));
}
1 => {
assert_eq!(rx.try_recv(), Ok(Event::Updated(node_handle_0)));
assert_eq!(rx.try_recv(), Ok(Event::Updated(node_handle_1)));
assert_eq!(rx.try_recv(), Ok(Event::Updated(handle_on_init)));
assert_eq!(rx.try_recv(), Ok(Event::Updated(handle_on_start)));
assert_eq!(rx.try_recv(), Ok(Event::Updated(handle_on_update1)));
assert_eq!(rx.try_recv(), Err(TryRecvError::Empty));
let graph = &mut scene_container[scene_handle].graph;
graph.remove_node(node_handle);
graph.remove_node(handle_on_init.node_handle);
graph.remove_node(handle_on_start.node_handle);
graph.remove_node(handle_on_update1.node_handle);
}
2 => {
assert_eq!(rx.try_recv(), Ok(Event::Destroyed(node_handle_0)));
assert_eq!(rx.try_recv(), Ok(Event::Destroyed(node_handle_1)));
assert_eq!(rx.try_recv(), Ok(Event::Destroyed(handle_on_init)));
assert_eq!(rx.try_recv(), Ok(Event::Destroyed(handle_on_start)));
assert_eq!(rx.try_recv(), Ok(Event::Destroyed(handle_on_update1)));
assert_eq!(rx.try_recv(), Err(TryRecvError::Disconnected));
}
_ => (),
}
}
}
#[derive(Debug)]
enum MyMessage {
Foo(usize),
Bar(String),
}
#[derive(Debug, Clone, Reflect, Visit, TypeUuidProvider, ComponentProvider)]
#[type_uuid(id = "bf2976ad-f41d-4de6-9a32-b1a293956058")]
struct ScriptListeningToMessages {
index: u32,
#[reflect(hidden)]
#[visit(skip)]
sender: Sender<Event>,
}
impl ScriptTrait for ScriptListeningToMessages {
fn on_start(&mut self, ctx: &mut ScriptContext) {
ctx.message_dispatcher.subscribe_to::<MyMessage>(ctx.handle);
}
fn on_message(
&mut self,
message: &mut dyn ScriptMessagePayload,
ctx: &mut ScriptMessageContext,
) {
let typed_message = message.downcast_ref::<MyMessage>().unwrap();
match self.index {
0 => {
if let MyMessage::Foo(num) = typed_message {
assert_eq!(*num, 123);
self.sender
.send(Event::EventReceived(Source::from_msg_ctx(ctx)))
.unwrap();
} else {
unreachable!()
}
}
1 => {
if let MyMessage::Bar(string) = typed_message {
assert_eq!(string, "Foobar");
self.sender
.send(Event::EventReceived(Source::from_msg_ctx(ctx)))
.unwrap();
} else {
unreachable!()
}
}
_ => (),
}
self.index += 1;
}
}
#[derive(Debug, Clone, Reflect, Visit, TypeUuidProvider, ComponentProvider)]
#[type_uuid(id = "6bcbf9b4-9546-42d3-965a-de055ab85475")]
struct ScriptSendingMessages {
index: u32,
}
impl ScriptTrait for ScriptSendingMessages {
fn on_update(&mut self, ctx: &mut ScriptContext) {
match self.index {
0 => ctx.message_sender.send_global(MyMessage::Foo(123)),
1 => ctx
.message_sender
.send_global(MyMessage::Bar("Foobar".to_string())),
_ => (),
}
self.index += 1;
}
}
#[test]
fn test_messages() {
let resource_manager = ResourceManager::new(Arc::new(Default::default()));
let mut scene = Scene::new();
let (tx, rx) = mpsc::channel();
PivotBuilder::new(BaseBuilder::new().with_script(ScriptSendingMessages { index: 0 }))
.build(&mut scene.graph);
let receiver_messages =
PivotBuilder::new(BaseBuilder::new().with_script(ScriptListeningToMessages {
sender: tx,
index: 0,
}))
.build(&mut scene.graph);
let receiver_messages_source = Source {
node_handle: receiver_messages,
script_index: 0,
};
let mut scene_container = SceneContainer::new(Default::default());
let scene_handle = scene_container.add(scene);
let mut script_processor = ScriptProcessor::default();
let mut task_pool = TaskPoolHandler::new(Arc::new(TaskPool::new()));
let mut gc = GraphicsContext::Uninitialized(Default::default());
let mut user_interfaces = UiContainer::default();
script_processor.register_scripted_scene(scene_handle, &resource_manager);
for iteration in 0..2 {
script_processor.handle_scripts(
&mut scene_container,
&mut Vec::new(),
&resource_manager,
&mut task_pool,
&mut gc,
&mut user_interfaces,
0.0,
0.0,
);
match iteration {
0 => {
assert_eq!(
rx.try_recv(),
Ok(Event::EventReceived(receiver_messages_source))
);
assert_eq!(rx.try_recv(), Err(TryRecvError::Empty));
}
1 => {
assert_eq!(
rx.try_recv(),
Ok(Event::EventReceived(receiver_messages_source))
);
assert_eq!(rx.try_recv(), Err(TryRecvError::Empty));
}
_ => (),
}
}
}
#[derive(Clone, Debug, PartialEq, Reflect, Visit, TypeUuidProvider, ComponentProvider)]
#[type_uuid(id = "7bcbf9b4-9546-42d3-965a-de055ab85475")]
pub struct ScriptSpawningAsyncTasks {
num: Option<u32>,
}
impl ScriptTrait for ScriptSpawningAsyncTasks {
fn on_start(&mut self, ctx: &mut ScriptContext) {
ctx.task_pool.spawn_script_task(
ctx.scene_handle,
ctx.handle,
ctx.script_index,
async move { 123u32 },
|result, script: &mut ScriptSpawningAsyncTasks, _ctx| {
assert_eq!(result, 123u32);
script.num = Some(result);
},
)
}
}
#[derive(Clone, Debug, PartialEq, Reflect, Visit, TypeUuidProvider, ComponentProvider)]
#[type_uuid(id = "8bcbf9b4-9546-42d3-965a-de055ab85475")]
pub struct ScriptWithoutAsyncTasks {}
impl ScriptTrait for ScriptWithoutAsyncTasks {}
#[test]
#[cfg(not(target_os = "macos"))] fn test_async_script_tasks() {
use crate::engine::{Engine, EngineInitParams};
use std::mem::{ManuallyDrop, MaybeUninit};
use winit::event_loop::EventLoop;
#[allow(invalid_value)]
#[allow(clippy::uninit_assumed_init)]
let event_loop =
unsafe { ManuallyDrop::new(MaybeUninit::<EventLoop<()>>::uninit().assume_init()) };
let task_pool = Arc::new(TaskPool::default());
let mut engine = Engine::new(EngineInitParams {
graphics_context_params: Default::default(),
serialization_context: Arc::new(Default::default()),
widget_constructors: Arc::new(Default::default()),
resource_manager: ResourceManager::new(task_pool.clone()),
task_pool,
})
.unwrap();
engine.enable_plugins(None, true, None);
let mut scene = Scene::new();
let handle = PivotBuilder::new(
BaseBuilder::new()
.with_script(ScriptSpawningAsyncTasks { num: None })
.with_script(ScriptWithoutAsyncTasks {}),
)
.build(&mut scene.graph);
let scene_handle = engine.scenes.add(scene);
engine.register_scripted_scene(scene_handle);
let mut time = 0.0;
let dt = 1.0 / 60.0;
let mut lag = 0.0;
while time <= 10.0 {
engine.update(dt, &event_loop, &mut lag, Default::default());
time += dt;
}
let mut scripts = engine.scenes[scene_handle].graph[handle].scripts();
assert_eq!(
scripts
.next()
.and_then(|s| s.cast::<ScriptSpawningAsyncTasks>()),
Some(&ScriptSpawningAsyncTasks { num: Some(123) })
);
assert_eq!(
scripts
.next()
.and_then(|s| s.cast::<ScriptWithoutAsyncTasks>()),
Some(&ScriptWithoutAsyncTasks {})
);
}
#[derive(Clone, Debug, Reflect, Visit, TypeUuidProvider, ComponentProvider)]
#[type_uuid(id = "9bcbf9b4-9546-42d3-965a-de055ab85475")]
pub struct ScriptThatDeletesItself {
#[reflect(hidden)]
#[visit(skip)]
sender: Sender<Event>,
}
impl ScriptTrait for ScriptThatDeletesItself {
fn on_init(&mut self, ctx: &mut ScriptContext) {
self.sender
.send(Event::Initialized(Source::from_ctx(ctx)))
.unwrap();
}
fn on_start(&mut self, ctx: &mut ScriptContext) {
self.sender
.send(Event::Started(Source::from_ctx(ctx)))
.unwrap();
}
fn on_deinit(&mut self, ctx: &mut ScriptDeinitContext) {
self.sender
.send(Event::Destroyed(Source::from_deinit_ctx(ctx)))
.unwrap();
}
fn on_update(&mut self, ctx: &mut ScriptContext) {
self.sender
.send(Event::Updated(Source::from_ctx(ctx)))
.unwrap();
let node = &mut ctx.scene.graph[ctx.handle];
node.remove_script(ctx.script_index);
}
}
#[derive(Clone, Debug, Reflect, Visit, TypeUuidProvider, ComponentProvider)]
#[type_uuid(id = "9bcbf9b4-9546-42d3-965a-de055ab85475")]
pub struct ScriptThatAddsScripts {
num: usize,
#[reflect(hidden)]
#[visit(skip)]
sender: Sender<Event>,
}
impl ScriptTrait for ScriptThatAddsScripts {
fn on_init(&mut self, ctx: &mut ScriptContext) {
self.sender
.send(Event::Initialized(Source::from_ctx(ctx)))
.unwrap();
}
fn on_start(&mut self, ctx: &mut ScriptContext) {
self.sender
.send(Event::Started(Source::from_ctx(ctx)))
.unwrap();
for i in 0..self.num {
ctx.scene.graph[ctx.handle].add_script(SimpleScript {
stuff: i,
sender: self.sender.clone(),
});
}
}
fn on_deinit(&mut self, ctx: &mut ScriptDeinitContext) {
self.sender
.send(Event::Destroyed(Source::from_deinit_ctx(ctx)))
.unwrap();
}
fn on_update(&mut self, ctx: &mut ScriptContext) {
self.sender
.send(Event::Updated(Source::from_ctx(ctx)))
.unwrap();
}
}
#[derive(Clone, Debug, Reflect, Visit, TypeUuidProvider, ComponentProvider)]
#[type_uuid(id = "9bcbf9b4-9546-42d3-965a-de055ab85475")]
pub struct SimpleScript {
stuff: usize,
#[reflect(hidden)]
#[visit(skip)]
sender: Sender<Event>,
}
impl ScriptTrait for SimpleScript {
fn on_init(&mut self, ctx: &mut ScriptContext) {
self.sender
.send(Event::Initialized(Source::from_ctx(ctx)))
.unwrap();
}
fn on_start(&mut self, ctx: &mut ScriptContext) {
self.sender
.send(Event::Started(Source::from_ctx(ctx)))
.unwrap();
}
fn on_deinit(&mut self, ctx: &mut ScriptDeinitContext) {
self.sender
.send(Event::Destroyed(Source::from_deinit_ctx(ctx)))
.unwrap();
}
fn on_update(&mut self, ctx: &mut ScriptContext) {
self.sender
.send(Event::Updated(Source::from_ctx(ctx)))
.unwrap();
}
}
#[test]
fn test_script_adding_removing() {
let resource_manager = ResourceManager::new(Arc::new(Default::default()));
let mut scene = Scene::new();
let (tx, rx) = mpsc::channel();
let node_handle = PivotBuilder::new(
BaseBuilder::new()
.with_script(ScriptThatDeletesItself { sender: tx.clone() })
.with_script(ScriptThatAddsScripts { num: 2, sender: tx }),
)
.build(&mut scene.graph);
assert_eq!(node_handle, Handle::new(1, 1));
let mut scene_container = SceneContainer::new(Default::default());
let scene_handle = scene_container.add(scene);
let mut script_processor = ScriptProcessor::default();
script_processor.register_scripted_scene(scene_handle, &resource_manager);
let mut task_pool = TaskPoolHandler::new(Arc::new(TaskPool::new()));
let mut gc = GraphicsContext::Uninitialized(Default::default());
let mut user_interfaces = UiContainer::default();
for iteration in 0..2 {
script_processor.handle_scripts(
&mut scene_container,
&mut Vec::new(),
&resource_manager,
&mut task_pool,
&mut gc,
&mut user_interfaces,
0.0,
0.0,
);
match iteration {
0 => {
for i in 0..2 {
assert_eq!(
rx.try_recv(),
Ok(Event::Initialized(Source {
node_handle,
script_index: i,
}))
);
}
for i in 0..2 {
assert_eq!(
rx.try_recv(),
Ok(Event::Started(Source {
node_handle,
script_index: i,
}))
);
}
for i in 2..4 {
assert_eq!(
rx.try_recv(),
Ok(Event::Initialized(Source {
node_handle,
script_index: i,
}))
);
}
for i in 2..4 {
assert_eq!(
rx.try_recv(),
Ok(Event::Started(Source {
node_handle,
script_index: i,
}))
);
}
for i in 0..4 {
assert_eq!(
rx.try_recv(),
Ok(Event::Updated(Source {
node_handle,
script_index: i,
}))
);
}
assert_eq!(
rx.try_recv(),
Ok(Event::Destroyed(Source {
node_handle,
script_index: 0,
}))
);
assert_eq!(rx.try_recv(), Err(TryRecvError::Empty));
}
1 => {
for i in 0..3 {
assert_eq!(
rx.try_recv(),
Ok(Event::Updated(Source {
node_handle,
script_index: i,
}))
);
}
assert_eq!(rx.try_recv(), Err(TryRecvError::Empty));
}
_ => (),
}
}
}
}