aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml25
-rw-r--r--src/actor/mod.rs3
-rw-r--r--src/cache/mod.rs3
-rw-r--r--src/cache/resources.rs22
-rw-r--r--src/commands.rs7
-rw-r--r--src/components.rs3
-rw-r--r--src/conditionals.rs22
-rw-r--r--src/events.rs11
-rw-r--r--src/lib.rs109
-rw-r--r--src/lua_api.rs184
-rw-r--r--src/observers.rs47
-rw-r--r--src/payload/components/mod.rs20
-rw-r--r--src/payload/mod.rs13
-rw-r--r--src/preload/events.rs12
-rw-r--r--src/preload/mod.rs82
-rw-r--r--src/preload/resources.rs7
-rw-r--r--src/preload/systems.rs41
-rw-r--r--src/resources.rs13
-rw-r--r--src/room_generation/mod.rs0
-rw-r--r--src/systems.rs26
-rw-r--r--src/utils.rs39
-rw-r--r--src/watcher.rs14
22 files changed, 475 insertions, 228 deletions
diff --git a/Cargo.toml b/Cargo.toml
index cad6065..b3460b0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,10 +1,9 @@
[package]
name = "bevy_dirworld"
-version = "0.2.1"
+version = "0.4.0"
edition = "2021"
[dependencies]
-anyhow = "1.0"
async-channel = "2.3"
notify = "7.0"
tar = "0.4"
@@ -13,10 +12,8 @@ rust-crypto = "0.2"
multi_key_map = "0.3"
serde = "1.0"
rmp-serde = "1.3"
-anymap = "0.12"
notify-debouncer-full = "0.4"
md5 = "0.7"
-bevy-async-ecs = "0.6"
aes = "0.8"
hex = "0.4"
hex-literal = "0.4"
@@ -24,30 +21,30 @@ uuid = "1.11"
lazy_static = "1.5"
[dependencies.bevy]
-version = "0.14"
+version = "0.15"
default-features = false
-features = ["serialize", "multi_threaded"]
+features = ["serialize", "multi_threaded", "bevy_state"]
[dependencies.avian3d]
-version = "0.1"
+version = "0.2"
features = ["serialize"]
[dependencies.occule]
-git = "http://github.com/exvacuum/occule"
-branch = "wip"
+git = "https://git.exvacuum.dev/occule"
+tag = "v0.3.1"
[dependencies.yarnspinner]
git = "https://github.com/YarnSpinnerTool/YarnSpinner-Rust"
optional = true
features = ["serde"]
-[dependencies.bevy_scriptum]
-version = "0.6"
-features = ["lua"]
+[dependencies.bevy_mod_scripting]
+version = "0.8"
+features = ["lua54", "lua_script_api"]
[dependencies.bevy_basic_interaction]
-git = "https://github.com/exvacuum/bevy_basic_interaction.git"
-branch = "wip"
+git = "https://git.exvacuum.dev/bevy_basic_interaction"
+tag = "v0.2.0"
[dependencies.strum]
version = "0.26"
diff --git a/src/actor/mod.rs b/src/actor/mod.rs
index 9d0af43..eabf0fe 100644
--- a/src/actor/mod.rs
+++ b/src/actor/mod.rs
@@ -6,7 +6,7 @@ use std::sync::{Arc, Mutex};
use bevy::{prelude::*, utils::HashMap};
use lazy_static::lazy_static;
use resources::FunctionLibrary;
-use yarnspinner::{core::{LineId, YarnValue}, runtime::Dialogue};
+use yarnspinner::core::YarnValue;
pub mod components;
pub mod events;
@@ -23,6 +23,7 @@ lazy_static! {
/// Plugin which controls the behavior of actors
pub struct ActorPlugin {
+ /// Callback for registering custom yarnspinner functions
pub custom_function_registration: Option<fn(&mut FunctionLibrary)>,
}
diff --git a/src/cache/mod.rs b/src/cache/mod.rs
new file mode 100644
index 0000000..19c3a6f
--- /dev/null
+++ b/src/cache/mod.rs
@@ -0,0 +1,3 @@
+mod resources;
+pub use resources::DirworldCache;
+
diff --git a/src/cache/resources.rs b/src/cache/resources.rs
new file mode 100644
index 0000000..6aba61a
--- /dev/null
+++ b/src/cache/resources.rs
@@ -0,0 +1,22 @@
+use std::{collections::HashMap, path::PathBuf};
+
+use bevy::prelude::*;
+
+use crate::{components::DirworldEntity, payload::DirworldEntityPayload};
+
+/// Structure containing payload data for cached (non-current) rooms
+#[derive(Resource, Default, Debug, Deref, DerefMut)]
+pub struct DirworldCache(pub HashMap<PathBuf, DirworldEntityPayload>);
+
+impl DirworldCache {
+ /// Stores an entity's payload in the cache, if it exists
+ pub fn cache_entity(&mut self, dirworld_entity: &DirworldEntity) {
+ if let Some(payload) = &dirworld_entity.payload {
+ self.insert(dirworld_entity.path.clone(), payload.clone());
+ }
+ }
+
+ pub fn get_entity_cache(&mut self, path: impl Into<PathBuf>) -> Option<DirworldEntityPayload> {
+ self.remove(&path.into())
+ }
+}
diff --git a/src/commands.rs b/src/commands.rs
index 60da108..616f326 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -261,19 +261,20 @@ pub trait DirworldCommands {
/// Unlock Door
fn dirworld_unlock_door(&mut self, path: PathBuf, key: Vec<u8>);
+ /// Save entity
fn dirworld_save_entity(&mut self, path: PathBuf, payload: DirworldEntityPayload);
}
impl<'w, 's> DirworldCommands for Commands<'w, 's> {
fn dirworld_lock_door(&mut self, path: PathBuf, key: Vec<u8>) {
- self.add(DirworldLockDoorCommand { key, path });
+ self.queue(DirworldLockDoorCommand { key, path });
}
fn dirworld_unlock_door(&mut self, path: PathBuf, key: Vec<u8>) {
- self.add(DirworldUnlockDoorCommand { key, path });
+ self.queue(DirworldUnlockDoorCommand { key, path });
}
fn dirworld_save_entity(&mut self, path: PathBuf, payload: DirworldEntityPayload) {
- self.add(DirworldSaveEntityCommand { path, payload });
+ self.queue(DirworldSaveEntityCommand { path, payload });
}
}
diff --git a/src/components.rs b/src/components.rs
index 8bb2bff..0a52560 100644
--- a/src/components.rs
+++ b/src/components.rs
@@ -11,9 +11,12 @@ pub struct Tooltip(pub String);
/// A marker component for entities spawned by dirworld handlers, i.e. they should be removed when the room changes.
#[derive(Component, Clone, Debug)]
pub struct DirworldEntity {
+ /// Path on filesystem corresponding to this entity
pub path: PathBuf,
+ /// Extracted payload if present
pub payload: Option<DirworldEntityPayload>,
}
+/// Marker component that prevents an entity from despawning on room change
#[derive(Debug, Component)]
pub struct Persist;
diff --git a/src/conditionals.rs b/src/conditionals.rs
index 266379a..5362066 100644
--- a/src/conditionals.rs
+++ b/src/conditionals.rs
@@ -3,44 +3,60 @@ use bevy::{
prelude::{AncestorIter, Entity, Parent, Query, World},
};
use serde::{Deserialize, Serialize};
-use strum::{AsRefStr, EnumString};
+use strum::AsRefStr;
use uuid::Uuid;
use crate::{components::DirworldEntity, resources::DirworldCurrentDir};
-// I Store Conditions as Enum Data
+/// Conditions which can be checked in lua and yarnspinner scripts
#[derive(Serialize, Deserialize, AsRefStr, Debug, Default, Clone, PartialEq, Eq)]
pub enum Condition {
+ /// Always true
#[default]
#[strum(serialize = "Always True")]
True,
+ /// True if `child` is a child of `parent`
#[strum(serialize = "Child Of")]
ChildOf {
+ /// Entity that must be child
child: Uuid,
+ /// Entity that must be parent
parent: Uuid,
},
+ /// True if `parent` is the parent of `child`
#[strum(serialize = "Parent Of")]
ParentOf {
+ /// Entity that must be parent
parent: Uuid,
+ /// Entity that must be child
child: Uuid,
},
+ /// True if `descendant` is a descendant of `ancestor`
#[strum(serialize = "Descendant Of")]
DescendantOf {
+ /// Entity that must be descendant
descendant: Uuid,
+ /// Entity that must be ancestor
ancestor: Uuid,
},
+ /// True if `ancestor` is an ancestor of `descendant`
#[strum(serialize = "Ancestor Of")]
AncestorOf {
+ /// Entity that must be ancestor
ancestor: Uuid,
+ /// Entity that must be descendant
descendant: Uuid,
},
+ /// True if current room matches provided id
#[strum(serialize = "In Room")]
InRoom(Uuid),
+ /// True if an object with the provided id is in the current room
#[strum(serialize = "Object In Room")]
ObjectInRoom(Uuid),
}
impl Condition {
+ /// Evaluate the condition and return the result
pub fn evaluate(&self, world: &mut World) -> bool {
match self {
Condition::True => true,
@@ -59,6 +75,7 @@ impl Condition {
}
}
+ /// Get the name of the condition's corresponding function for lua/yarnspinner APIs
pub fn get_api_function_name(&self) -> &'static str {
match self {
Condition::True => "conditional_true",
@@ -71,6 +88,7 @@ impl Condition {
}
}
+ /// Parses function name and argument strings into the corresponding condition representation
pub fn from_api_function_name_and_args(name: &str, args: &[&str]) -> Option<Self> {
match name {
"conditional_true" => Some(Condition::True),
diff --git a/src/events.rs b/src/events.rs
index 2fa7f81..7932a67 100644
--- a/src/events.rs
+++ b/src/events.rs
@@ -17,17 +17,18 @@ pub enum DirworldNavigationEvent {
},
}
+/// Event called when leaving a room
#[derive(Debug, Event, Deref, DerefMut, Clone)]
pub struct DirworldLeaveRoom(pub PathBuf);
+/// Event called when entering a room
#[derive(Debug, Event, Deref, DerefMut, Clone)]
pub struct DirworldEnterRoom(pub PathBuf);
+/// Event called when changing the world root
#[derive(Debug, Event, Deref, DerefMut, Clone)]
pub struct DirworldChangeRoot(pub PathBuf);
-#[derive(Event)]
-pub struct DirworldSpawn {
- pub entity: Entity,
- pub data: Option<Vec<u8>>,
-}
+/// Event called to spawn a dirworld entities
+#[derive(Event, Debug, Deref, DerefMut, Clone, Copy)]
+pub struct DirworldSpawn(pub Entity);
diff --git a/src/lib.rs b/src/lib.rs
index e6a5f25..7d0749a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,26 +1,23 @@
-// #![warn(missing_docs)]
+#![warn(missing_docs)]
//! Plugin for bevy engine enabling interaction with and representation of the file system in the world.
use std::{ffi::OsStr, path::PathBuf};
use actor::ActorPlugin;
-use bevy::render::mesh::ExtrusionBuilder;
use bevy::{ecs::system::IntoObserverSystem, prelude::*};
-use bevy_scriptum::{runtimes::lua::LuaRuntime, BuildScriptingRuntime, ScriptingRuntimeBuilder};
-use events::{
- DirworldChangeRoot, DirworldEnterRoom, DirworldLeaveRoom, DirworldNavigationEvent,
- DirworldSpawn,
-};
+use bevy_mod_scripting::core::{AddScriptApiProvider, AddScriptHost, AddScriptHostHandler, ScriptingPlugin};
+use bevy_mod_scripting::lua::LuaScriptHost;
+use cache::DirworldCache;
+use events::{DirworldChangeRoot, DirworldEnterRoom, DirworldLeaveRoom, DirworldSpawn};
use occule::Codec;
-use resources::DirworldCache;
+use preload::{DirworldPreload, DirworldPreloadPlugin};
+use resources::EntryType;
use resources::{
DirworldCodecs, DirworldCurrentDir, DirworldObservers, DirworldRootDir, DirworldTasks,
- EntryType,
};
pub use watcher::DirworldWatcherEvent;
pub use watcher::DirworldWatcherSet;
-use yarnspinner::core::Library;
/// Components used by this plugin
pub mod components;
@@ -31,15 +28,10 @@ pub mod events;
/// Resources used by this plugin
pub mod resources;
-mod watcher;
-
/// Commands for this plugin
pub mod commands;
-mod systems;
-
-mod observers;
-
+/// Utility functions
pub mod utils;
/// Payload for dirworld entities
@@ -48,27 +40,37 @@ pub mod payload;
/// Actor component
pub mod actor;
+/// System for dirworld-related condition checking
+pub mod conditionals;
+
+/// Room/asset preloading
+pub mod preload;
+
+mod cache;
+
+mod yarnspinner_api;
+
mod lua_api;
-pub mod conditionals;
+mod systems;
-pub mod yarnspinner_api;
+mod observers;
-pub mod room_generation;
+mod watcher;
/// Plugin which enables high-level interaction
#[derive(Default)]
-pub struct DirworldPlugin {
- pub register_custom_lua_api:
- Option<Box<dyn Fn(ScriptingRuntimeBuilder<LuaRuntime>) + Send + Sync>>,
-}
+pub struct DirworldPlugin;
impl Plugin for DirworldPlugin {
fn build(&self, app: &mut App) {
- info!("building");
- app.add_plugins(ActorPlugin {
- custom_function_registration: Some(yarnspinner_api::setup_yarnspinner_functions),
- })
+ app.add_plugins((
+ ActorPlugin {
+ custom_function_registration: Some(yarnspinner_api::setup_yarnspinner_functions),
+ },
+ DirworldPreloadPlugin,
+ ScriptingPlugin,
+ ))
.add_systems(Startup, watcher::setup)
.add_systems(
Update,
@@ -78,18 +80,12 @@ impl Plugin for DirworldPlugin {
yarnspinner_api::process_commands,
),
)
- .add_systems(
- PostUpdate,
- watcher::update,
- )
- .add_scripting::<LuaRuntime>(|runtime| {
- let runtime = lua_api::register(runtime);
- if let Some(register_custom) = &self.register_custom_lua_api {
- (register_custom)(runtime);
- }
- })
- .init_resource::<DirworldCache>()
+ .add_script_host::<LuaScriptHost<()>>(PostUpdate)
+ .add_script_handler::<LuaScriptHost<()>, 0, 0>(PostUpdate)
+ .add_api_provider::<LuaScriptHost<()>>(Box::new(lua_api::ConditionalAPI))
+ .add_systems(PostUpdate, watcher::update)
.init_resource::<DirworldRootDir>()
+ .init_resource::<DirworldCache>()
.init_resource::<DirworldCurrentDir>()
.init_resource::<DirworldTasks>()
.init_resource::<DirworldObservers>()
@@ -98,18 +94,22 @@ impl Plugin for DirworldPlugin {
.add_event::<DirworldLeaveRoom>()
.add_event::<DirworldChangeRoot>()
.add_event::<DirworldWatcherEvent>()
- .observe(observers::navigate_to_room)
- .observe(observers::handle_changes)
- .observe(observers::change_root)
- .observe(observers::navigate_from_room);
+ .add_observer(observers::navigate_to_room)
+ .add_observer(observers::handle_changes)
+ .add_observer(observers::change_root)
+ .add_observer(observers::navigate_from_room);
}
}
+/// Extension trait for working with multiple file extensions on paths
pub trait Extensions {
+ /// Get all the extensions on this path if applicable
fn extensions(&self) -> Option<String>;
+ /// Gets the file stem (without any extensions) of this path if applicable
fn file_stem_no_extensions(&self) -> Option<String>;
+ /// Gets the path with any extensions removed
fn no_extensions(&self) -> PathBuf;
}
@@ -148,13 +148,20 @@ impl Extensions for PathBuf {
}
}
+/// Extension trait providing functions for registering callbacks and codecs for filesystem entries
pub trait DirworldApp {
- fn register_dirworld_entry_callback<B: Bundle, M>(
+ /// Register callbacks to be executed when a file with given [`EntryType`]s is loaded. The
+ /// `preload_callback` parameter controls loading assets and is called before spawning any
+ /// entities in the room, and the `spawn_callback` handles initializing the spawned entities.
+ fn register_dirworld_entry_callbacks<B: Bundle, M, PB: Bundle, PM>(
&mut self,
extensions: Vec<EntryType>,
- observer: impl IntoObserverSystem<DirworldSpawn, B, M>,
+ preload_callback: Option<impl IntoObserverSystem<DirworldPreload, PB, PM>>,
+ spawn_callback: impl IntoObserverSystem<DirworldSpawn, B, M>,
) -> &mut Self;
+ /// Register a [`Codec`] to be used to extract [`crate::payload::DirworldEntityPayload`]s from
+ /// files with matching extensions.
fn register_dirworld_entry_codec<C: Codec + Send + Sync + 'static>(
&mut self,
extensions: Vec<String>,
@@ -163,10 +170,11 @@ pub trait DirworldApp {
}
impl DirworldApp for App {
- fn register_dirworld_entry_callback<B: Bundle, M>(
+ fn register_dirworld_entry_callbacks<B: Bundle, M, PB: Bundle, PM>(
&mut self,
extensions: Vec<EntryType>,
- observer: impl IntoObserverSystem<DirworldSpawn, B, M>,
+ preload_callback: Option<impl IntoObserverSystem<DirworldPreload, PB, PM>>,
+ spawn_observer: impl IntoObserverSystem<DirworldSpawn, B, M>,
) -> &mut Self {
let world = self.world_mut();
let observer_entity_id;
@@ -174,7 +182,14 @@ impl DirworldApp for App {
{
let mut observer_entity = world.spawn_empty();
observer_entity_id = observer_entity.id();
- observer_entity.insert(Observer::new(observer).with_entity(observer_entity_id));
+ if let Some(preload_callback) = preload_callback {
+ observer_entity.with_children(|parent| {
+ parent.spawn(Observer::new(preload_callback).with_entity(observer_entity_id));
+ });
+ }
+ observer_entity.with_children(|parent| {
+ parent.spawn(Observer::new(spawn_observer).with_entity(observer_entity_id));
+ });
}
world.flush();
diff --git a/src/lua_api.rs b/src/lua_api.rs
index f9a34d4..53252a5 100644
--- a/src/lua_api.rs
+++ b/src/lua_api.rs
@@ -1,25 +1,21 @@
-use std::str::FromStr;
+use std::{str::FromStr, sync::Mutex};
use bevy::prelude::*;
-use bevy_scriptum::{
- runtimes::lua::{BevyEntity, BevyVec3, LuaRuntime, LuaScriptData},
- Runtime, ScriptingRuntimeBuilder,
-};
+use bevy_mod_scripting::api::providers::bevy_reflect::LuaVec3;
+use bevy_mod_scripting::{api::providers::bevy_ecs::LuaEntity, lua::tealr::mlu::mlua::Error as LuaError};
+use bevy_mod_scripting::lua::LuaEvent;
+use bevy_mod_scripting::prelude::*;
use uuid::Uuid;
use crate::{components::DirworldEntity, conditionals::Condition};
-pub fn trigger_update(
- mut scripted_entities: Query<(Entity, &mut LuaScriptData)>,
- scripting_runtime: Res<LuaRuntime>,
- time: Res<Time>,
-) {
- let delta = time.delta_seconds();
- for (entity, mut script_data) in scripted_entities.iter_mut() {
- if let Err(e) = scripting_runtime.call_fn("on_update", &mut script_data, entity, (delta,)) {
- error!("Encountered lua scripting error: {:?}", e);
- }
- }
+pub fn trigger_update(mut w: PriorityEventWriter<LuaEvent<()>>) {
+ let event = LuaEvent::<()> {
+ args: (),
+ hook_name: "on_update".into(),
+ recipients: Recipients::All,
+ };
+ w.send(event, 0);
}
// ACTUAL API STUFF BELOW THIS POINT {{{
@@ -27,16 +23,15 @@ pub fn trigger_update(
macro_rules! register_fns {
($runtime:expr, $($function:expr),+) => {
{
- $runtime$(.add_function(stringify!($function).to_string(), $function))+
+ let ctx = $runtime.get_mut().unwrap();
+ $(ctx.globals().set(stringify!($function).to_string(), ctx.create_function($function).unwrap()).unwrap();)+
}
};
}
-pub fn register(
- runtime: ScriptingRuntimeBuilder<LuaRuntime>,
-) -> ScriptingRuntimeBuilder<LuaRuntime> {
+pub fn register(api: &mut Mutex<Lua>) {
register_fns!(
- runtime,
+ api,
translate,
rotate,
get_dirworld_id,
@@ -50,113 +45,148 @@ pub fn register(
)
}
-fn translate(
- In((BevyEntity(entity), BevyVec3(translation))): In<(BevyEntity, BevyVec3)>,
- mut transform_query: Query<&mut Transform>,
-) {
- if let Ok(mut transform) = transform_query.get_mut(entity) {
- transform.translation += translation;
+fn translate(ctx: &Lua, (entity, translation): (LuaEntity, LuaVec3)) -> Result<(), LuaError> {
+ let world = ctx.get_world()?;
+ let mut world = world.write();
+ if let Some(mut transform) = world.entity_mut(entity.inner().unwrap()).get_mut::<Transform>() {
+ transform.translation += translation.inner().unwrap();
}
+ Ok(())
}
-fn rotate(
- In((BevyEntity(entity), BevyVec3(axis), angle)): In<(BevyEntity, BevyVec3, f32)>,
- mut transform_query: Query<&mut Transform>,
-) {
- if let Ok(mut transform) = transform_query.get_mut(entity) {
- if let Ok(direction) = Dir3::new(axis) {
- transform.rotate_axis(direction, angle);
- } else {
- warn!("Provided axis was not a valid direction!");
- }
+fn rotate(ctx: &Lua, (entity, axis, angle): (LuaEntity, LuaVec3, f32)) -> Result<(), LuaError> {
+ let world = ctx.get_world()?;
+ let mut world = world.write();
+ if let Some(mut transform) = world.entity_mut(entity.inner().unwrap()).get_mut::<Transform>() {
+ transform.rotation *= Quat::from_axis_angle(axis.inner().unwrap(), angle);
}
+ Ok(())
}
-fn get_dirworld_id(In((BevyEntity(entity),)): In<(BevyEntity,)>, dirworld_entity_query: Query<&DirworldEntity>) -> Option<String> {
- dirworld_entity_query.get(entity).ok().and_then(|entity| entity.payload.as_ref().map(|payload| payload.id.to_string()))
+fn get_dirworld_id(ctx: &Lua, (entity,): (LuaEntity,)) -> Result<String, LuaError> {
+ let world = ctx.get_world()?;
+ let world = world.read();
+ if let Some(dirworld_entity) = world.entity(entity.inner().unwrap()).get::<DirworldEntity>() {
+ dirworld_entity.payload.as_ref().map(|p| p.id.to_string()).ok_or(LuaError::runtime("Failed to get entity id from payload"))
+ } else {
+ Err(LuaError::runtime("Entity missing DirworldEntity component"))
+ }
}
// Conditionals
-fn condition_true(world: &mut World) -> bool {
- Condition::True.evaluate(world)
+
+pub struct ConditionalAPI;
+
+impl APIProvider for ConditionalAPI {
+ type APITarget = Mutex<Lua>;
+
+ type ScriptContext = Mutex<Lua>;
+
+ type DocTarget = LuaDocFragment;
+
+ fn attach_api(
+ &mut self,
+ api: &mut Self::APITarget,
+ ) -> Result<(), bevy_mod_scripting::prelude::ScriptError> {
+ register(api);
+ Ok(())
+ }
}
-fn condition_ancestor_of(
- In((ancestor, descendant)): In<(String, String)>,
- world: &mut World,
-) -> bool {
+
+
+fn condition_true(ctx: &Lua, _: ()) -> Result<bool, LuaError> {
+ let world = ctx.get_world()?;
+ let mut world = world.write();
+ Ok(Condition::True.evaluate(&mut world))
+}
+
+fn condition_ancestor_of(ctx: &Lua, (ancestor, descendant): (String, String)) -> Result<bool, LuaError> {
+ let world = ctx.get_world()?;
+ let mut world = world.write();
let Ok(ancestor) = Uuid::from_str(&ancestor) else {
warn!("Provided ancestor is not a valid UUID");
- return false;
+ return Ok(false);
};
let Ok(descendant) = Uuid::from_str(&descendant) else {
warn!("Provided descendant is not a valid UUID");
- return false;
+ return Ok(false);
};
- Condition::AncestorOf {
+ Ok(Condition::AncestorOf {
ancestor,
descendant,
- }
- .evaluate(world)
+ }.evaluate(&mut world))
}
-fn condition_descendant_of(
- In((descendant, ancestor)): In<(String, String)>,
- world: &mut World,
-) -> bool {
+fn condition_descendant_of(ctx: &Lua, (descendant, ancestor): (String, String)) -> Result<bool, LuaError> {
+ let world = ctx.get_world()?;
+ let mut world = world.write();
let Ok(ancestor) = Uuid::from_str(&ancestor) else {
warn!("Provided ancestor is not a valid UUID");
- return false;
+ return Ok(false);
};
let Ok(descendant) = Uuid::from_str(&descendant) else {
warn!("Provided descendant is not a valid UUID");
- return false;
+ return Ok(false);
};
- Condition::DescendantOf {
+ Ok(Condition::DescendantOf {
ancestor,
descendant,
- }
- .evaluate(world)
+ }.evaluate(&mut world))
}
-fn condition_parent_of(In((parent, child)): In<(String, String)>, world: &mut World) -> bool {
+fn condition_parent_of(ctx: &Lua, (parent, child): (String, String)) -> Result<bool, LuaError> {
+ let world = ctx.get_world()?;
+ let mut world = world.write();
let Ok(parent) = Uuid::from_str(&parent) else {
- warn!("Provided parent is not a valid UUID");
- return false;
+ warn!("Provided ancestor is not a valid UUID");
+ return Ok(false);
};
let Ok(child) = Uuid::from_str(&child) else {
- warn!("Provided child is not a valid UUID");
- return false;
+ warn!("Provided descendant is not a valid UUID");
+ return Ok(false);
};
- Condition::ParentOf { parent, child }.evaluate(world)
+ Ok(Condition::ParentOf {
+ parent,
+ child,
+ }.evaluate(&mut world))
}
-fn condition_child_of(In((child, parent)): In<(String, String)>, world: &mut World) -> bool {
+fn condition_child_of(ctx: &Lua, (child, parent): (String, String)) -> Result<bool, LuaError> {
+ let world = ctx.get_world()?;
+ let mut world = world.write();
let Ok(parent) = Uuid::from_str(&parent) else {
- warn!("Provided parent is not a valid UUID");
- return false;
+ warn!("Provided ancestor is not a valid UUID");
+ return Ok(false);
};
let Ok(child) = Uuid::from_str(&child) else {
- warn!("Provided child is not a valid UUID");
- return false;
+ warn!("Provided descendant is not a valid UUID");
+ return Ok(false);
};
- Condition::ChildOf { parent, child }.evaluate(world)
+ Ok(Condition::ChildOf {
+ parent,
+ child,
+ }.evaluate(&mut world))
}
-fn condition_in_room(In((room,)): In<(String,)>, world: &mut World) -> bool {
+fn condition_in_room(ctx: &Lua, (room,): (String,)) -> Result<bool, LuaError> {
+ let world = ctx.get_world()?;
+ let mut world = world.write();
let Ok(room) = Uuid::from_str(&room) else {
warn!("Provided room is not a valid UUID");
- return false;
+ return Ok(false);
};
- Condition::InRoom(room).evaluate(world)
+ Ok(Condition::InRoom(room).evaluate(&mut world))
}
-fn condition_object_in_room(In((object,)): In<(String,)>, world: &mut World) -> bool {
+fn condition_object_in_room(ctx: &Lua, (object,): (String,)) -> Result<bool, LuaError> {
+ let world = ctx.get_world()?;
+ let mut world = world.write();
let Ok(object) = Uuid::from_str(&object) else {
warn!("Provided object is not a valid UUID");
- return false;
+ return Ok(false);
};
- Condition::ObjectInRoom(object).evaluate(world)
+ Ok(Condition::ObjectInRoom(object).evaluate(&mut world))
}
// }}}
diff --git a/src/observers.rs b/src/observers.rs
index 44edb50..bd1d1a6 100644
--- a/src/observers.rs
+++ b/src/observers.rs
@@ -7,13 +7,9 @@ use notify::{
};
use crate::{
- components::{DirworldEntity, Persist},
- events::{DirworldChangeRoot, DirworldEnterRoom, DirworldLeaveRoom},
- resources::{
- DirworldCache, DirworldCodecs, DirworldCurrentDir, DirworldObservers, DirworldRootDir,
- },
- utils::{despawn_entity_by_path, extract_entity_payload, spawn_entity},
- DirworldWatcherEvent,
+ cache::DirworldCache, components::{DirworldEntity, Persist}, events::{DirworldChangeRoot, DirworldEnterRoom, DirworldLeaveRoom}, preload::{load_entity, PreloadState, RoomAssets}, resources::{
+ DirworldCodecs, DirworldCurrentDir, DirworldObservers, DirworldRootDir,
+ }, utils::{despawn_entity_by_path, extract_entity_payload}, DirworldWatcherEvent
};
/// On navigation from a room, insert modified payloads into the cache
@@ -25,10 +21,7 @@ pub fn navigate_from_room(
mut event_writer: EventWriter<DirworldLeaveRoom>,
) {
for (entity, dirworld_entity) in entities.iter() {
- if let Some(payload) = &dirworld_entity.payload {
- info!("Caching {entity:?}");
- cache.insert(dirworld_entity.path.clone(), payload.clone());
- }
+ cache.cache_entity(&dirworld_entity);
commands.entity(entity).despawn_recursive();
}
event_writer.send(trigger.event().clone());
@@ -43,6 +36,8 @@ pub fn navigate_to_room(
mut commands: Commands,
mut event_writer: EventWriter<DirworldEnterRoom>,
mut current_dir: ResMut<DirworldCurrentDir>,
+ mut next_preload_state: ResMut<NextState<PreloadState>>,
+ mut room_assets: ResMut<RoomAssets>,
) {
let path = &trigger.event().0;
@@ -81,7 +76,15 @@ pub fn navigate_to_room(
};
for entry in entries {
- spawn_entity(&entry, &mut cache, &codecs, &observers, &mut commands);
+ load_entity(
+ &entry,
+ &mut cache,
+ &codecs,
+ &observers,
+ &mut commands,
+ &mut next_preload_state,
+ &mut room_assets,
+ );
}
event_writer.send(trigger.event().clone());
}
@@ -94,6 +97,8 @@ pub fn handle_changes(
codecs: Res<DirworldCodecs>,
mut cache: ResMut<DirworldCache>,
mut event_writer: EventWriter<DirworldWatcherEvent>,
+ mut next_preload_state: ResMut<NextState<PreloadState>>,
+ mut room_assets: ResMut<RoomAssets>,
) {
let event = &trigger.event().0;
info!("Watcher Event: {event:?}");
@@ -105,27 +110,39 @@ pub fn handle_changes(
}
EventKind::Create(_) | EventKind::Modify(ModifyKind::Name(RenameMode::To)) => {
for path in &event.paths {
- spawn_entity(path, &mut cache, &codecs, &observers, &mut commands);
+ load_entity(
+ &path,
+ &mut cache,
+ &codecs,
+ &observers,
+ &mut commands,
+ &mut next_preload_state,
+ &mut room_assets,
+ );
}
}
EventKind::Modify(ModifyKind::Name(RenameMode::Both)) => {
despawn_entity_by_path(&mut commands, &dirworld_entities, &event.paths[0]);
- spawn_entity(
+ load_entity(
&event.paths[1],
&mut cache,
&codecs,
&observers,
&mut commands,
+ &mut next_preload_state,
+ &mut room_assets,
);
}
EventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)) => {
despawn_entity_by_path(&mut commands, &dirworld_entities, &event.paths[1]);
- spawn_entity(
+ load_entity(
&event.paths[0],
&mut cache,
&codecs,
&observers,
&mut commands,
+ &mut next_preload_state,
+ &mut room_assets,
);
}
_ => {
diff --git a/src/payload/components/mod.rs b/src/payload/components/mod.rs
index 2a83713..fc24ac1 100644
--- a/src/payload/components/mod.rs
+++ b/src/payload/components/mod.rs
@@ -5,24 +5,36 @@ use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use yarnspinner::core::YarnValue;
+/// Payload component that corresponds to [`bevy::prelude::Transform`]
#[derive(Serialize, Deserialize, Clone, Default, Deref, DerefMut, Debug)]
pub struct Transform(pub bevy::prelude::Transform);
+/// Payload component that represent's an entity's name
#[derive(Serialize, Deserialize, Clone, Default, Deref, DerefMut, Debug)]
pub struct Name(pub String);
+/// Payload component that represents a yarnspinner actor
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
pub struct Actor {
+ /// Actor-local variables
pub local_variables: HashMap<String, YarnValue>,
+ /// Source for the yarnspinner dialog
pub yarn_source: Vec<u8>,
}
+/// Payload component that represents a character's voice. Uses rustysynth to generate random MIDI
+/// tones based on given parameters.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Voice {
+ /// Base MIDI pitch of voice. Defaults to 60
pub pitch: i32,
+ /// MIDI preset to use for voice. Defaults to 0
pub preset: i32,
+ /// MIDI bank to use. Defaults to 0
pub bank: i32,
+ /// Variance in pitch of voice. Defaults to 3
pub variance: u32,
+ /// Speed of voice. Defaults to 1.0
pub speed: f32,
}
@@ -38,22 +50,30 @@ impl Default for Voice {
}
}
+/// Payload component that wraps a [`avian3d::prelude::RigidBody`]
#[derive(Serialize, Deserialize, Clone, Default, Deref, DerefMut, Debug)]
pub struct Rigidbody(pub RigidBody);
+/// Payload component that represents mesh colliders that will be generated for this entity
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
pub struct MeshCollider {
+ /// Whether the generated colliders should be convex hulls
pub convex: bool,
+ /// Whether the generated colliders should be triggers
pub sensor: bool,
}
+/// Payload component representing a lua script that will be attached to an entity
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
pub struct Script {
+ /// Lua script source
pub lua_source: Vec<u8>,
}
+/// Payload component for an arbitrary relationship map, can store 128-bit identifiers indexed by names
#[derive(Serialize, Deserialize, Clone, Default, Deref, DerefMut, Debug)]
pub struct Relationships(pub HashMap<String, [u8; 16]>);
+/// Payload component that indicates that this entity should be able to be picked up
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
pub struct Pickup;
diff --git a/src/payload/mod.rs b/src/payload/mod.rs
index dd064f2..68e23a0 100644
--- a/src/payload/mod.rs
+++ b/src/payload/mod.rs
@@ -1,23 +1,36 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
+/// Payload components
pub mod components;
+/// Payload steganographically embedded into asset files
#[derive(Serialize, Deserialize, Default, Clone, Debug)]
pub struct DirworldEntityPayload {
+ /// Unique identifier for this entity, used by conditional system
pub id: Uuid,
+ /// Transform of this entity
pub transform: components::Transform,
+ /// Name for this entity
pub name: Option<components::Name>,
+ /// Actor information for this entity
pub actor: Option<components::Actor>,
+ /// Voice information for this entity
pub voice: Option<components::Voice>,
+ /// Rigidbody for this entity
pub rigidbody: Option<components::Rigidbody>,
+ /// Mesh collider information for this entity
pub mesh_collider: Option<components::MeshCollider>,
+ /// Lua scripts for this entity
pub scripts: Option<Vec<components::Script>>,
+ /// Relationships for this entity
pub relationships: Option<components::Relationships>,
+ /// Pickup information for this entity
pub pickup: Option<components::Pickup>,
}
impl DirworldEntityPayload {
+ /// Create a new default payload with a randomized UUID
pub fn new() -> Self {
Self {
id: Uuid::new_v4(),
diff --git a/src/preload/events.rs b/src/preload/events.rs
new file mode 100644
index 0000000..7167d73
--- /dev/null
+++ b/src/preload/events.rs
@@ -0,0 +1,12 @@
+use bevy::prelude::*;
+
+/// Event used to trigger preload callbacks after the asset file has been pre-processed to extract
+/// the payload
+#[derive(Debug, Event, Clone)]
+pub struct DirworldPreload {
+ /// Entity with the `[DirworldEntity]` component corresponding to the entity being preloaded
+ pub entity: Entity,
+ /// The data portion of the file after being pre-processed
+ pub data: Option<Vec<u8>>,
+}
+
diff --git a/src/preload/mod.rs b/src/preload/mod.rs
new file mode 100644
index 0000000..b90db38
--- /dev/null
+++ b/src/preload/mod.rs
@@ -0,0 +1,82 @@
+use crate::cache::DirworldCache;
+use crate::{
+ components::DirworldEntity,
+ resources::{DirworldCodecs, DirworldObservers, EntryType},
+ utils::extract_entity_payload,
+ Extensions,
+};
+use bevy::prelude::*;
+use std::{collections::HashMap, path::PathBuf};
+
+mod systems;
+
+mod resources;
+pub use resources::*;
+
+mod events;
+pub use events::DirworldPreload;
+
+pub(crate) struct DirworldPreloadPlugin;
+
+impl Plugin for DirworldPreloadPlugin {
+ fn build(&self, app: &mut App) {
+ app.add_systems(
+ PostUpdate,
+ systems::handle_preload.run_if(in_state(PreloadState::Loading)),
+ )
+ .add_systems(OnEnter(PreloadState::Done), systems::handle_spawn)
+ .init_resource::<RoomAssets>()
+ .init_state::<PreloadState>();
+ }
+}
+
+/// State of asset preloading
+#[derive(States, Debug, Clone, PartialEq, Eq, Hash, Default)]
+pub enum PreloadState {
+ /// Indicates assets are in the process of loading
+ #[default]
+ Loading,
+ /// Indicates all room assets are finished loading, i.e. all assets are loaded with
+ /// dependencies
+ Done,
+}
+
+/// Initiates loading of an asset
+// TODO: Make into a command extension
+pub fn load_entity(
+ entry: &PathBuf,
+ cache: &mut DirworldCache,
+ codecs: &DirworldCodecs,
+ observers: &DirworldObservers,
+ commands: &mut Commands,
+ preload_state: &mut NextState<PreloadState>,
+ room_assets: &mut RoomAssets,
+) {
+ let (mut payload, data) = extract_entity_payload(&entry, &codecs);
+ payload = payload.map(|p| cache.get_entity_cache(&entry).unwrap_or(p));
+ let entry_type = if entry.is_dir() {
+ EntryType::Folder
+ } else {
+ EntryType::File(entry.extensions())
+ };
+ let transform = payload
+ .as_ref()
+ .map(|payload| payload.transform.clone())
+ .unwrap_or_default();
+ let entity = commands
+ .spawn((
+ *transform,
+ Visibility::Inherited,
+ DirworldEntity {
+ path: entry.clone(),
+ payload,
+ },
+ ))
+ .id();
+ if let Some(observer) = observers.get(&entry_type) {
+ preload_state.set(PreloadState::Loading);
+ room_assets.insert(entry.clone(), HashMap::default());
+ commands.trigger_targets(DirworldPreload { entity, data }, observer.clone());
+ info!("Triggered preload for {entry:?}");
+ }
+}
diff --git a/src/preload/resources.rs b/src/preload/resources.rs
new file mode 100644
index 0000000..4060c10
--- /dev/null
+++ b/src/preload/resources.rs
@@ -0,0 +1,7 @@
+use std::{collections::HashMap, path::PathBuf};
+
+use bevy::prelude::*;
+
+/// A map of asset handles required by each entry in a room, indexed by their paths
+#[derive(Resource, Default, Debug, Deref, DerefMut)]
+pub struct RoomAssets(pub HashMap<PathBuf, HashMap<String, UntypedHandle>>);
diff --git a/src/preload/systems.rs b/src/preload/systems.rs
new file mode 100644
index 0000000..ec867ae
--- /dev/null
+++ b/src/preload/systems.rs
@@ -0,0 +1,41 @@
+use bevy::prelude::*;
+
+use crate::{components::DirworldEntity, events::DirworldSpawn, resources::{DirworldObservers, EntryType}, Extensions};
+
+use super::{PreloadState, RoomAssets};
+
+pub fn handle_preload(
+ asset_server: Res<AssetServer>,
+ room_assets: Res<RoomAssets>,
+ mut next_state: ResMut<NextState<PreloadState>>,
+) {
+ if room_assets.is_empty()
+ || room_assets
+ .values()
+ .flat_map(|v| v.values())
+ .all(|a| asset_server.is_loaded_with_dependencies(a))
+ {
+ info!("Preload Done.");
+ next_state.set(PreloadState::Done);
+ }
+}
+
+pub fn handle_spawn(
+ dirworld_entity_query: Query<(Entity, &DirworldEntity)>,
+ mut commands: Commands,
+ observers: Res<DirworldObservers>,
+) {
+ info!("Spawning");
+ for (entity, DirworldEntity { path, .. }) in dirworld_entity_query.iter() {
+ let entry_type = if path.is_dir() {
+ EntryType::Folder
+ } else {
+ EntryType::File(path.extensions())
+ };
+ if let Some(observer) = observers.get(&entry_type) {
+ info!("Found observer {observer:?} for {entry_type:?}");
+ commands.trigger_targets(DirworldSpawn(entity), observer.clone());
+ }
+ }
+}
+
diff --git a/src/resources.rs b/src/resources.rs
index cbf570d..bf0c072 100644
--- a/src/resources.rs
+++ b/src/resources.rs
@@ -1,9 +1,8 @@
-use std::{collections::{BTreeMap, HashMap}, path::PathBuf};
+use std::{collections::BTreeMap, path::PathBuf};
use bevy::{ecs::world::CommandQueue, prelude::*, tasks::Task};
use multi_key_map::MultiKeyMap;
use occule::Codec;
-use uuid::Uuid;
use crate::payload::DirworldEntityPayload;
@@ -14,7 +13,9 @@ pub struct DirworldRootDir(pub Option<PathBuf>);
/// Current directory of the world
#[derive(Resource, Default)]
pub struct DirworldCurrentDir{
+ /// Path of current directory
pub path: PathBuf,
+ /// Payload (contents of .door file) in current directory, if present
pub payload: Option<DirworldEntityPayload>,
}
@@ -22,18 +23,20 @@ pub struct DirworldCurrentDir{
#[derive(Default, Resource, Deref, DerefMut)]
pub struct DirworldTasks(pub BTreeMap<String, Task<Option<CommandQueue>>>);
+/// A map between file types and their corresponding preload/spawn callback observers
#[derive(Debug, Default, Resource, Deref, DerefMut)]
pub struct DirworldObservers(pub MultiKeyMap<EntryType, Entity>);
+/// A map between file extensions and their corresponding [`Codec`]s
#[derive(Default, Resource, Deref, DerefMut)]
pub struct DirworldCodecs(pub MultiKeyMap<String, Box<dyn Codec + Send + Sync>>);
+/// Type of a filesystem entry
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum EntryType {
+ /// A file with an optional extension
File(Option<String>),
+ /// A folder
Folder,
}
-/// Structure containing payload data for cached (non-current) rooms
-#[derive(Resource, Default, Debug, Deref, DerefMut)]
-pub struct DirworldCache(pub HashMap<PathBuf, DirworldEntityPayload>);
diff --git a/src/room_generation/mod.rs b/src/room_generation/mod.rs
deleted file mode 100644
index e69de29..0000000
--- a/src/room_generation/mod.rs
+++ /dev/null
diff --git a/src/systems.rs b/src/systems.rs
index 3f894ec..d6840ee 100644
--- a/src/systems.rs
+++ b/src/systems.rs
@@ -3,7 +3,7 @@ use bevy::{
tasks::{block_on, futures_lite::future},
};
-use crate::{components::DirworldEntity, resources::DirworldTasks};
+use crate::resources::DirworldTasks;
pub fn remove_completed_tasks(mut commands: Commands, mut tasks: ResMut<DirworldTasks>) {
tasks.retain(|_, task| {
@@ -16,15 +16,15 @@ pub fn remove_completed_tasks(mut commands: Commands, mut tasks: ResMut<Dirworld
});
}
-pub fn sync_entity_transforms(
- mut dirworld_entity_query: Query<(&mut DirworldEntity, Ref<Transform>, &GlobalTransform)>,
-) {
- for (mut dirworld_entity, transform, global_transform) in dirworld_entity_query.iter_mut() {
- if transform.is_changed() && !transform.is_added() {
- if let Some(payload) = &mut dirworld_entity.payload {
- let transform = global_transform.compute_transform();
- *payload.transform = transform;
- }
- }
- }
-}
+// pub fn sync_entity_transforms(
+// mut dirworld_entity_query: Query<(&mut DirworldEntity, Ref<Transform>, &GlobalTransform)>,
+// ) {
+// for (mut dirworld_entity, transform, global_transform) in dirworld_entity_query.iter_mut() {
+// if transform.is_changed() && !transform.is_added() {
+// if let Some(payload) = &mut dirworld_entity.payload {
+// let transform = global_transform.compute_transform();
+// *payload.transform = transform;
+// }
+// }
+// }
+// }
diff --git a/src/utils.rs b/src/utils.rs
index e4692bb..74451ae 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -3,9 +3,10 @@ use std::{fs, path::PathBuf};
use bevy::prelude::*;
use crate::{
- components::DirworldEntity, events::DirworldSpawn, payload::DirworldEntityPayload, resources::{DirworldCache, DirworldCodecs, DirworldObservers, EntryType}, Extensions
+ components::DirworldEntity, payload::DirworldEntityPayload, resources::DirworldCodecs, Extensions
};
+/// Extracts the binary payload from a file
pub fn extract_entity_payload(
path: &PathBuf,
codecs: &DirworldCodecs,
@@ -62,41 +63,7 @@ pub fn extract_entity_payload(
(payload, data)
}
-pub fn spawn_entity(
- entry: &PathBuf,
- cache: &mut DirworldCache,
- codecs: &DirworldCodecs,
- observers: &DirworldObservers,
- commands: &mut Commands,
-) {
- let (mut payload, data) = extract_entity_payload(&entry, &codecs);
- if let Some(cached_payload) = cache.remove(entry) {
- payload = Some(cached_payload);
- }
-
- let transform = payload.as_ref().map(|payload| payload.transform.clone()).unwrap_or_default();
- let entry_type = if entry.is_dir() {
- EntryType::Folder
- } else {
- EntryType::File(entry.extensions())
- };
- let entity = commands
- .spawn((
- SpatialBundle {
- transform: *transform,
- ..Default::default()
- },
- DirworldEntity {
- path: entry.clone(),
- payload,
- },
- ))
- .id();
- if let Some(observer) = observers.get(&entry_type) {
- commands.trigger_targets(DirworldSpawn { entity, data }, observer.clone());
- }
-}
-
+/// Despawns an entity corresponding to a path on the filesystem
pub fn despawn_entity_by_path(
commands: &mut Commands,
dirworld_entities: &Query<(Entity, &DirworldEntity)>,
diff --git a/src/watcher.rs b/src/watcher.rs
index 8918dda..0e23c55 100644
--- a/src/watcher.rs
+++ b/src/watcher.rs
@@ -1,21 +1,16 @@
use std::{
- path::{Path, PathBuf},
+ path::PathBuf,
time::Duration,
};
use async_channel::{Receiver, Sender};
use bevy::{prelude::*, tasks::IoTaskPool};
-use notify::{
- event::{AccessKind, AccessMode, DataChange, MetadataKind, ModifyKind, RenameMode},
- EventKind, RecursiveMode, Watcher,
-};
+use notify::RecursiveMode;
use notify_debouncer_full::{new_debouncer, DebounceEventResult};
-use crate::{
- components::DirworldEntity,
- resources::{DirworldCache, DirworldCodecs, DirworldObservers, DirworldRootDir},
-};
+use crate::resources::DirworldRootDir;
+/// SystemSet for dirworld watcher systems
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct DirworldWatcherSet;
@@ -94,4 +89,3 @@ pub fn update(
}
}
}
-