Navigation Rewrite
This commit is contained in:
parent
4fc097045b
commit
99c398cc12
288
src/commands.rs
288
src/commands.rs
@ -1,282 +1,28 @@
|
||||
use std::{
|
||||
fs, iter,
|
||||
path::{Path, PathBuf},
|
||||
fs, path::PathBuf,
|
||||
};
|
||||
|
||||
use bevy::{
|
||||
ecs::{
|
||||
system::SystemState,
|
||||
world::{Command, CommandQueue},
|
||||
},
|
||||
ecs::
|
||||
world::{Command, CommandQueue}
|
||||
,
|
||||
prelude::*,
|
||||
tasks::AsyncComputeTaskPool,
|
||||
};
|
||||
use crypto::{
|
||||
aes::KeySize,
|
||||
blockmodes::{EcbEncryptor, PkcsPadding},
|
||||
blockmodes::PkcsPadding,
|
||||
buffer::{BufferResult, ReadBuffer, RefReadBuffer, RefWriteBuffer, WriteBuffer},
|
||||
};
|
||||
use occule::Error;
|
||||
use xz2::read::{XzDecoder, XzEncoder};
|
||||
|
||||
use crate::{
|
||||
components::DirworldEntity,
|
||||
events::{DirworldNavigationEvent, DirworldSpawn},
|
||||
payload::{DirworldComponent, DirworldComponentDiscriminants, DirworldEntityPayload},
|
||||
resources::{
|
||||
DirworldCodecs, DirworldCurrentDir, DirworldObservers, DirworldRootDir, DirworldTasks,
|
||||
EntryType,
|
||||
},
|
||||
Extensions,
|
||||
payload::{DirworldComponent, DirworldComponentDiscriminants, DirworldEntityPayload}, resources::{
|
||||
DirworldCodecs, DirworldObservers, DirworldTasks,
|
||||
}, utils::extract_entity_payload, Extensions
|
||||
};
|
||||
|
||||
struct DirworldNavigateCommand {
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl Command for DirworldNavigateCommand {
|
||||
fn apply(self, world: &mut World) {
|
||||
let root_dir = world.remove_resource::<DirworldRootDir>().unwrap();
|
||||
let mut current_dir = world.remove_resource::<DirworldCurrentDir>().unwrap();
|
||||
|
||||
let current_path;
|
||||
let old_dir;
|
||||
if let Some(old_path) = ¤t_dir.0 {
|
||||
world.send_event(DirworldNavigationEvent::LeftRoom {
|
||||
path: old_path.clone(),
|
||||
});
|
||||
|
||||
current_path = old_path.join(self.path);
|
||||
old_dir = Some(old_path.clone());
|
||||
} else {
|
||||
current_path = self.path;
|
||||
old_dir = None;
|
||||
}
|
||||
current_dir.0 = Some(current_path.clone());
|
||||
|
||||
let mut system_state: SystemState<(
|
||||
Commands,
|
||||
Query<(Entity, &DirworldEntity)>,
|
||||
Res<DirworldObservers>,
|
||||
Res<DirworldCodecs>,
|
||||
)> = SystemState::new(world);
|
||||
let (mut commands, dirworld_entities, observers, codecs) = system_state.get_mut(world);
|
||||
update_entries(
|
||||
&mut commands,
|
||||
&dirworld_entities,
|
||||
old_dir,
|
||||
¤t_path,
|
||||
&root_dir.0.clone().unwrap(),
|
||||
&observers,
|
||||
&codecs,
|
||||
);
|
||||
system_state.apply(world);
|
||||
|
||||
world.send_event(DirworldNavigationEvent::EnteredRoom { path: current_path });
|
||||
world.insert_resource(current_dir);
|
||||
world.insert_resource(root_dir);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_entries(
|
||||
commands: &mut Commands,
|
||||
dirworld_entities: &Query<(Entity, &DirworldEntity)>,
|
||||
old_dir: Option<PathBuf>,
|
||||
current_dir: &PathBuf,
|
||||
project_dir: &PathBuf,
|
||||
observers: &DirworldObservers,
|
||||
codecs: &DirworldCodecs,
|
||||
) {
|
||||
let directory = current_dir.read_dir().unwrap();
|
||||
|
||||
if let Some(old_dir) = old_dir {
|
||||
let mut entities_to_despawn = vec![];
|
||||
for (entity, dirworld_entity) in dirworld_entities.iter() {
|
||||
if dirworld_entity.path.parent().unwrap() == old_dir {
|
||||
entities_to_despawn.push(entity);
|
||||
}
|
||||
}
|
||||
for entity in entities_to_despawn {
|
||||
commands.entity(entity).despawn_recursive();
|
||||
}
|
||||
}
|
||||
|
||||
let mut entry_paths: Vec<PathBuf> = directory
|
||||
.flatten()
|
||||
.map(|entry| entry.path().canonicalize().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
entry_paths.retain(|entry| {
|
||||
!entry
|
||||
.file_name()
|
||||
.is_some_and(|entry| entry.to_string_lossy().starts_with("."))
|
||||
});
|
||||
if current_dir != project_dir {
|
||||
entry_paths = iter::once(current_dir.join(".."))
|
||||
.chain(entry_paths)
|
||||
.collect();
|
||||
}
|
||||
|
||||
for entry_path in entry_paths {
|
||||
process_entry(commands, &entry_path, &observers, &codecs);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn process_entry(
|
||||
commands: &mut Commands,
|
||||
entry_path: &PathBuf,
|
||||
observers: &DirworldObservers,
|
||||
codecs: &DirworldCodecs,
|
||||
) {
|
||||
let (payload, data) = extract_payload(entry_path, codecs);
|
||||
let transform = if let Some(component) = payload
|
||||
.as_ref()
|
||||
.and_then(|payload| payload.component("Transform"))
|
||||
{
|
||||
if let DirworldComponent::Transform(component) = component {
|
||||
component.clone()
|
||||
} else {
|
||||
panic!("Failed to decompose component")
|
||||
}
|
||||
} else {
|
||||
Transform::default()
|
||||
};
|
||||
|
||||
let entity = commands.spawn((
|
||||
SpatialBundle {
|
||||
transform,
|
||||
..Default::default()
|
||||
},
|
||||
DirworldEntity {
|
||||
path: entry_path.clone(),
|
||||
payload: payload.clone(),
|
||||
},
|
||||
));
|
||||
|
||||
let entity = entity.id();
|
||||
let entry_type = if entry_path.is_dir() {
|
||||
EntryType::Folder
|
||||
} else {
|
||||
let extensions = entry_path.extensions();
|
||||
EntryType::File(extensions)
|
||||
};
|
||||
if let Some(observer) = observers.get(&entry_type) {
|
||||
commands.trigger_targets(DirworldSpawn { entity, data }, observer.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_payload(
|
||||
entry_path: &PathBuf,
|
||||
codecs: &DirworldCodecs,
|
||||
) -> (Option<DirworldEntityPayload>, Option<Vec<u8>>) {
|
||||
let entry_type = if entry_path.is_dir() {
|
||||
EntryType::Folder
|
||||
} else {
|
||||
let extensions = entry_path.extensions();
|
||||
EntryType::File(extensions)
|
||||
};
|
||||
|
||||
let mut data: Option<Vec<u8>> = None;
|
||||
let mut payload: Option<DirworldEntityPayload> = None;
|
||||
match &entry_type {
|
||||
EntryType::File(Some(extension)) => {
|
||||
if let Ok(file_data) = fs::read(entry_path.clone()) {
|
||||
match codecs.get(extension) {
|
||||
Some(codec) => match codec.decode(&file_data.as_slice()) {
|
||||
Ok((carrier, extracted_payload)) => {
|
||||
match rmp_serde::from_slice::<DirworldEntityPayload>(
|
||||
extracted_payload.as_slice(),
|
||||
) {
|
||||
Ok(deserialized_payload) => {
|
||||
data = Some(carrier);
|
||||
payload = Some(deserialized_payload);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("{:?}", e);
|
||||
data = Some(file_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => match e {
|
||||
Error::DataNotEncoded => {
|
||||
data = Some(file_data);
|
||||
}
|
||||
_ => error!("{:?}", e),
|
||||
},
|
||||
},
|
||||
None => {
|
||||
data = Some(file_data);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("Failed to read data from {entry_path:?}");
|
||||
}
|
||||
}
|
||||
EntryType::Folder => {
|
||||
let door_path = entry_path.join(".door");
|
||||
if door_path.exists() {
|
||||
let door_file_data = fs::read(door_path).unwrap();
|
||||
match rmp_serde::from_slice::<DirworldEntityPayload>(&door_file_data.as_slice()) {
|
||||
Ok(deserialized_payload) => {
|
||||
payload = Some(deserialized_payload);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
(payload, data)
|
||||
}
|
||||
|
||||
struct DirworldChangeRootCommand {
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl Command for DirworldChangeRootCommand {
|
||||
fn apply(self, world: &mut World) {
|
||||
let mut root_dir = world.remove_resource::<DirworldRootDir>().unwrap();
|
||||
let mut current_dir = world.remove_resource::<DirworldCurrentDir>().unwrap();
|
||||
|
||||
let old_root;
|
||||
if let DirworldRootDir(Some(old_dir)) = root_dir {
|
||||
world.send_event(DirworldNavigationEvent::LeftRoom {
|
||||
path: self.path.clone(),
|
||||
});
|
||||
old_root = Some(old_dir);
|
||||
} else {
|
||||
old_root = None;
|
||||
}
|
||||
|
||||
root_dir.0 = Some(self.path.canonicalize().unwrap());
|
||||
current_dir.0 = Some(self.path.canonicalize().unwrap());
|
||||
|
||||
let mut system_state: SystemState<(
|
||||
Commands,
|
||||
Query<(Entity, &DirworldEntity)>,
|
||||
Res<DirworldObservers>,
|
||||
Res<DirworldCodecs>,
|
||||
)> = SystemState::new(world);
|
||||
let (mut commands, dirworld_entities, observers, codecs) = system_state.get_mut(world);
|
||||
update_entries(
|
||||
&mut commands,
|
||||
&dirworld_entities,
|
||||
old_root,
|
||||
¤t_dir.0.clone().unwrap(),
|
||||
&root_dir.0.clone().unwrap(),
|
||||
&observers,
|
||||
&codecs,
|
||||
);
|
||||
system_state.apply(world);
|
||||
|
||||
world.send_event(DirworldNavigationEvent::EnteredRoom { path: self.path });
|
||||
|
||||
world.insert_resource(root_dir);
|
||||
world.insert_resource(current_dir);
|
||||
}
|
||||
}
|
||||
|
||||
struct DirworldLockDoorCommand {
|
||||
path: PathBuf,
|
||||
key: Vec<u8>,
|
||||
@ -287,7 +33,7 @@ impl Command for DirworldLockDoorCommand {
|
||||
let path = self.path.clone();
|
||||
// Get existing payload
|
||||
let codecs = world.remove_resource::<DirworldCodecs>().unwrap();
|
||||
let (payload, _) = extract_payload(&path, &codecs);
|
||||
let (payload, _) = extract_entity_payload(&path, &codecs);
|
||||
world.insert_resource(codecs);
|
||||
let task = AsyncComputeTaskPool::get().spawn(async move {
|
||||
// Tar directory
|
||||
@ -357,7 +103,7 @@ impl Command for DirworldUnlockDoorCommand {
|
||||
let path = self.path.clone();
|
||||
// Get existing payload
|
||||
let codecs = world.remove_resource::<DirworldCodecs>().unwrap();
|
||||
let (payload, carrier) = extract_payload(&path, &codecs);
|
||||
let (payload, carrier) = extract_entity_payload(&path, &codecs);
|
||||
world.insert_resource(codecs);
|
||||
let task = AsyncComputeTaskPool::get().spawn(async move {
|
||||
// Decrypt archive
|
||||
@ -509,12 +255,6 @@ impl Command for DirworldSaveEntityCommand {
|
||||
|
||||
/// Commands for dirworld navigation
|
||||
pub trait DirworldCommands {
|
||||
/// Change the root of the world. This will also set the current directory. This is not really meant to be used in-game but is useful for editor applications.
|
||||
fn dirworld_change_root(&mut self, path: PathBuf);
|
||||
|
||||
/// Move to given directory
|
||||
fn dirworld_navigate(&mut self, path: PathBuf);
|
||||
|
||||
/// Lock Door
|
||||
fn dirworld_lock_door(&mut self, path: PathBuf, key: Vec<u8>);
|
||||
|
||||
@ -525,14 +265,6 @@ pub trait DirworldCommands {
|
||||
}
|
||||
|
||||
impl<'w, 's> DirworldCommands for Commands<'w, 's> {
|
||||
fn dirworld_change_root(&mut self, path: PathBuf) {
|
||||
self.add(DirworldChangeRootCommand { path });
|
||||
}
|
||||
|
||||
fn dirworld_navigate(&mut self, path: PathBuf) {
|
||||
self.add(DirworldNavigateCommand { path });
|
||||
}
|
||||
|
||||
fn dirworld_lock_door(&mut self, path: PathBuf, key: Vec<u8>) {
|
||||
self.add(DirworldLockDoorCommand { key, path });
|
||||
}
|
||||
|
@ -14,3 +14,6 @@ pub struct DirworldEntity {
|
||||
pub path: PathBuf,
|
||||
pub payload: Option<DirworldEntityPayload>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Component)]
|
||||
pub struct Persist;
|
||||
|
@ -17,8 +17,17 @@ pub enum DirworldNavigationEvent {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Event, Deref, DerefMut)]
|
||||
pub struct DirworldLeaveRoom(pub PathBuf);
|
||||
|
||||
#[derive(Debug, Event, Deref, DerefMut)]
|
||||
pub struct DirworldEnterRoom(pub PathBuf);
|
||||
|
||||
#[derive(Debug, Event, Deref, DerefMut)]
|
||||
pub struct DirworldChangeRoot(pub PathBuf);
|
||||
|
||||
#[derive(Event)]
|
||||
pub struct DirworldSpawn {
|
||||
pub entity: Entity,
|
||||
pub data: Option<Vec<u8>>,
|
||||
}
|
||||
}
|
||||
|
24
src/lib.rs
24
src/lib.rs
@ -6,8 +6,9 @@ use std::{ffi::OsStr, path::PathBuf};
|
||||
|
||||
use bevy::{ecs::system::IntoObserverSystem, prelude::*};
|
||||
use bevy_scriptum::{runtimes::lua::LuaRuntime, BuildScriptingRuntime, ScriptingRuntimeBuilder};
|
||||
use events::{DirworldNavigationEvent, DirworldSpawn};
|
||||
use events::{DirworldChangeRoot, DirworldEnterRoom, DirworldLeaveRoom, DirworldNavigationEvent, DirworldSpawn};
|
||||
use occule::Codec;
|
||||
use resources::DirworldCache;
|
||||
use resources::{
|
||||
DirworldCodecs, DirworldCurrentDir, DirworldObservers, DirworldRootDir, DirworldTasks,
|
||||
EntryType,
|
||||
@ -31,6 +32,10 @@ pub mod commands;
|
||||
|
||||
mod systems;
|
||||
|
||||
mod observers;
|
||||
|
||||
mod utils;
|
||||
|
||||
/// Payload for dirworld entities
|
||||
pub mod payload;
|
||||
|
||||
@ -54,24 +59,27 @@ impl Plugin for DirworldPlugin {
|
||||
Update,
|
||||
(systems::remove_completed_tasks, lua_api::trigger_update),
|
||||
)
|
||||
.add_systems(PostUpdate, watcher::update)
|
||||
.add_systems(
|
||||
PreUpdate,
|
||||
watcher::handle_changes,
|
||||
)
|
||||
.add_systems(PostUpdate, (watcher::update, systems::sync_entity_transforms))
|
||||
.add_scripting::<LuaRuntime>(|runtime| {
|
||||
let runtime = lua_api::register(runtime);
|
||||
if let Some(register_custom) = &self.register_custom_lua_api {
|
||||
(register_custom)(runtime);
|
||||
}
|
||||
})
|
||||
.add_event::<DirworldNavigationEvent>()
|
||||
.init_resource::<DirworldCache>()
|
||||
.init_resource::<DirworldRootDir>()
|
||||
.init_resource::<DirworldCurrentDir>()
|
||||
.init_resource::<DirworldTasks>()
|
||||
.init_resource::<DirworldObservers>()
|
||||
.init_resource::<DirworldCodecs>()
|
||||
.add_event::<DirworldWatcherEvent>();
|
||||
.add_event::<DirworldEnterRoom>()
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
|
142
src/observers.rs
Normal file
142
src/observers.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use notify::{
|
||||
event::{MetadataKind, ModifyKind, RenameMode},
|
||||
EventKind,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
components::{DirworldEntity, Persist},
|
||||
events::{DirworldChangeRoot, DirworldEnterRoom, DirworldLeaveRoom},
|
||||
resources::{
|
||||
DirworldCache, DirworldCodecs, DirworldCurrentDir, DirworldObservers, DirworldRootDir,
|
||||
},
|
||||
utils::{despawn_entity_by_path, spawn_entity},
|
||||
DirworldWatcherEvent,
|
||||
};
|
||||
|
||||
/// On navigation from a room, insert modified payloads into the cache
|
||||
pub fn navigate_from_room(
|
||||
_: Trigger<DirworldLeaveRoom>,
|
||||
entities: Query<(Entity, Ref<DirworldEntity>), Without<Persist>>,
|
||||
mut cache: ResMut<DirworldCache>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
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());
|
||||
}
|
||||
commands.entity(entity).despawn_recursive();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn navigate_to_room(
|
||||
trigger: Trigger<DirworldEnterRoom>,
|
||||
root_dir: Res<DirworldRootDir>,
|
||||
mut cache: ResMut<DirworldCache>,
|
||||
observers: Res<DirworldObservers>,
|
||||
codecs: Res<DirworldCodecs>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
let path = &trigger.event().0;
|
||||
|
||||
let entries = match path.read_dir() {
|
||||
Ok(entries) => entries
|
||||
.flatten()
|
||||
.map(|entry| entry.path().canonicalize())
|
||||
.flatten()
|
||||
.filter(|entry| {
|
||||
!entry
|
||||
.file_name()
|
||||
.is_some_and(|file_name| file_name.to_string_lossy().starts_with("."))
|
||||
})
|
||||
.chain(
|
||||
root_dir
|
||||
.clone()
|
||||
.map(|root_dir| {
|
||||
if root_dir == *path {
|
||||
None
|
||||
} else {
|
||||
Some(path.join(".."))
|
||||
}
|
||||
})
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
),
|
||||
Err(e) => {
|
||||
error!("Failed to read directory \"{}\", ({:?})", path.display(), e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for entry in entries {
|
||||
spawn_entity(&entry, &mut cache, &codecs, &observers, &mut commands);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_changes(
|
||||
trigger: Trigger<DirworldWatcherEvent>,
|
||||
mut commands: Commands,
|
||||
dirworld_entities: Query<(Entity, &DirworldEntity)>,
|
||||
observers: Res<DirworldObservers>,
|
||||
codecs: Res<DirworldCodecs>,
|
||||
mut cache: ResMut<DirworldCache>,
|
||||
) {
|
||||
let event = &trigger.event().0;
|
||||
info!("Watcher Event: {event:?}");
|
||||
match event.kind {
|
||||
EventKind::Remove(_) | EventKind::Modify(ModifyKind::Name(RenameMode::From)) => {
|
||||
for path in &event.paths {
|
||||
despawn_entity_by_path(&mut commands, &dirworld_entities, path);
|
||||
}
|
||||
}
|
||||
EventKind::Create(_) | EventKind::Modify(ModifyKind::Name(RenameMode::To)) => {
|
||||
for path in &event.paths {
|
||||
spawn_entity(path, &mut cache, &codecs, &observers, &mut commands);
|
||||
}
|
||||
}
|
||||
EventKind::Modify(ModifyKind::Name(RenameMode::Both)) => {
|
||||
despawn_entity_by_path(&mut commands, &dirworld_entities, &event.paths[0]);
|
||||
spawn_entity(
|
||||
&event.paths[1],
|
||||
&mut cache,
|
||||
&codecs,
|
||||
&observers,
|
||||
&mut commands,
|
||||
);
|
||||
}
|
||||
EventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)) => {
|
||||
despawn_entity_by_path(&mut commands, &dirworld_entities, &event.paths[1]);
|
||||
spawn_entity(
|
||||
&event.paths[0],
|
||||
&mut cache,
|
||||
&codecs,
|
||||
&observers,
|
||||
&mut commands,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
// warn!("Not Processed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_root(
|
||||
trigger: Trigger<DirworldChangeRoot>,
|
||||
mut root_dir: ResMut<DirworldRootDir>,
|
||||
mut current_dir: ResMut<DirworldCurrentDir>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let DirworldRootDir(Some(old_dir)) = root_dir.deref() {
|
||||
commands.trigger(DirworldLeaveRoom(old_dir.to_path_buf()));
|
||||
};
|
||||
|
||||
let new_root = &trigger.event().0;
|
||||
info!("Changing Root to {}", new_root.display());
|
||||
**root_dir = Some(new_root.to_path_buf());
|
||||
**current_dir = Some(new_root.to_path_buf());
|
||||
|
||||
commands.trigger(DirworldEnterRoom(new_root.to_path_buf()));
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
use std::{collections::BTreeMap, path::PathBuf};
|
||||
use std::{collections::{BTreeMap, HashMap}, path::PathBuf};
|
||||
|
||||
use bevy::{ecs::world::CommandQueue, prelude::*, tasks::Task};
|
||||
use multi_key_map::MultiKeyMap;
|
||||
use occule::Codec;
|
||||
|
||||
use crate::payload::DirworldEntityPayload;
|
||||
|
||||
/// Root directory of the world
|
||||
#[derive(Resource, Deref, DerefMut, Default)]
|
||||
pub struct DirworldRootDir(pub Option<PathBuf>);
|
||||
@ -27,3 +29,7 @@ pub enum EntryType {
|
||||
File(Option<String>),
|
||||
Folder,
|
||||
}
|
||||
|
||||
/// Structure containing payload data for cached (non-current) rooms
|
||||
#[derive(Resource, Default, Debug, Deref, DerefMut)]
|
||||
pub struct DirworldCache(pub HashMap<PathBuf, DirworldEntityPayload>);
|
||||
|
@ -1,6 +1,9 @@
|
||||
use bevy::{prelude::{Commands, ResMut}, tasks::{block_on, futures_lite::future}};
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
tasks::{block_on, futures_lite::future},
|
||||
};
|
||||
|
||||
use crate::resources::DirworldTasks;
|
||||
use crate::{components::DirworldEntity, payload::DirworldComponent, resources::DirworldTasks};
|
||||
|
||||
pub fn remove_completed_tasks(mut commands: Commands, mut tasks: ResMut<DirworldTasks>) {
|
||||
tasks.retain(|_, task| {
|
||||
@ -12,3 +15,20 @@ pub fn remove_completed_tasks(mut commands: Commands, mut tasks: ResMut<Dirworld
|
||||
!task.is_finished()
|
||||
});
|
||||
}
|
||||
|
||||
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 {
|
||||
if let Some(DirworldComponent::Transform(payload_transform)) =
|
||||
payload.component_mut("Transform")
|
||||
{
|
||||
let transform = global_transform.compute_transform();
|
||||
*payload_transform = transform;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
124
src/utils.rs
Normal file
124
src/utils.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::{
|
||||
components::DirworldEntity, events::DirworldSpawn, payload::{DirworldComponent, DirworldEntityPayload}, resources::{DirworldCache, DirworldCodecs, DirworldObservers, EntryType}, Extensions
|
||||
};
|
||||
|
||||
pub fn extract_entity_payload(
|
||||
path: &PathBuf,
|
||||
codecs: &DirworldCodecs,
|
||||
) -> (Option<DirworldEntityPayload>, Option<Vec<u8>>) {
|
||||
let mut data = None;
|
||||
let mut payload = None;
|
||||
|
||||
if path.is_dir() {
|
||||
let payload_file_path = path.join(".door");
|
||||
if payload_file_path.exists() {
|
||||
if let Ok(payload_file_data) = fs::read(&payload_file_path) {
|
||||
match rmp_serde::from_slice::<DirworldEntityPayload>(&payload_file_data) {
|
||||
Ok(deserialized_payload) => {
|
||||
payload = Some(deserialized_payload);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Could not deserialize extracted payload: {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let Some(extensions) = path.extensions() {
|
||||
if let Ok(file_data) = fs::read(&path) {
|
||||
if let Some(codec) = codecs.get(&extensions) {
|
||||
match codec.decode(&file_data) {
|
||||
Ok((carrier, extracted_payload)) => {
|
||||
match rmp_serde::from_slice::<DirworldEntityPayload>(&extracted_payload)
|
||||
{
|
||||
Ok(deserialized_payload) => {
|
||||
data = Some(carrier);
|
||||
payload = Some(deserialized_payload);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Could not deserialize extracted payload: {e:?}");
|
||||
data = Some(file_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => match e {
|
||||
occule::Error::DataNotEncoded => {
|
||||
data = Some(file_data);
|
||||
}
|
||||
_ => error!("Could not decode payload: {e:?}"),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
data = Some(file_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(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 = if let Some(component) = payload
|
||||
.as_ref()
|
||||
.and_then(|payload| payload.component("Transform"))
|
||||
{
|
||||
if let DirworldComponent::Transform(transform) = component {
|
||||
transform.clone()
|
||||
} else {
|
||||
panic!("BAD DECOMPOSE: TRANSFORM ({component:?})");
|
||||
}
|
||||
} else {
|
||||
Transform::default()
|
||||
};
|
||||
let entry_type = if entry.is_dir() {
|
||||
EntryType::Folder
|
||||
} else {
|
||||
EntryType::File(entry.extensions())
|
||||
};
|
||||
let entity = commands
|
||||
.spawn((
|
||||
SpatialBundle {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn despawn_entity_by_path(
|
||||
commands: &mut Commands,
|
||||
dirworld_entities: &Query<(Entity, &DirworldEntity)>,
|
||||
path: &PathBuf,
|
||||
) {
|
||||
if let Some((entity, _)) = dirworld_entities
|
||||
.iter()
|
||||
.find(|(_, dirworld_entity)| dirworld_entity.path == *path)
|
||||
{
|
||||
commands.entity(entity).despawn_recursive();
|
||||
} else {
|
||||
warn!("Failed to find entity corresponding to path for despawning: {path:?}");
|
||||
}
|
||||
}
|
@ -1,4 +1,7 @@
|
||||
use std::{path::{Path, PathBuf}, time::Duration};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use async_channel::{Receiver, Sender};
|
||||
use bevy::{prelude::*, tasks::IoTaskPool};
|
||||
@ -9,16 +12,15 @@ use notify::{
|
||||
use notify_debouncer_full::{new_debouncer, DebounceEventResult};
|
||||
|
||||
use crate::{
|
||||
commands::process_entry,
|
||||
components::DirworldEntity,
|
||||
resources::{DirworldCodecs, DirworldObservers, DirworldRootDir},
|
||||
resources::{DirworldCache, DirworldCodecs, DirworldObservers, DirworldRootDir},
|
||||
};
|
||||
|
||||
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct DirworldWatcherSet;
|
||||
|
||||
/// Event fired when a file watcher event is caught.
|
||||
#[derive(Event)]
|
||||
#[derive(Event, Debug)]
|
||||
pub struct DirworldWatcherEvent(pub notify::Event);
|
||||
|
||||
#[derive(Resource)]
|
||||
@ -42,23 +44,33 @@ pub fn setup(mut commands: Commands) {
|
||||
|
||||
async fn file_watcher(rx: Receiver<PathBuf>, tx: Sender<notify::Event>) {
|
||||
let (watcher_tx, watcher_rx) = std::sync::mpsc::channel();
|
||||
let mut debouncer = new_debouncer(Duration::from_millis(500), None, move |result: DebounceEventResult| {
|
||||
match result {
|
||||
Ok(events) => for event in events.iter() {
|
||||
watcher_tx.send(event.clone()).unwrap();
|
||||
let mut debouncer = new_debouncer(
|
||||
Duration::from_millis(500),
|
||||
None,
|
||||
move |result: DebounceEventResult| match result {
|
||||
Ok(events) => {
|
||||
for event in events.iter() {
|
||||
watcher_tx.send(event.clone()).unwrap();
|
||||
}
|
||||
}
|
||||
Err(errors) => for error in errors.iter() {
|
||||
error!("{error:?}");
|
||||
Err(errors) => {
|
||||
for error in errors.iter() {
|
||||
error!("{error:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}).unwrap();
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let mut old_path: Option<PathBuf> = None;
|
||||
loop {
|
||||
while let Ok(message) = rx.try_recv() {
|
||||
if let Some(old_path) = &old_path {
|
||||
debouncer.watcher().unwatch(old_path).unwrap();
|
||||
}
|
||||
debouncer.watcher().watch(&message, RecursiveMode::NonRecursive).unwrap();
|
||||
debouncer
|
||||
.watcher()
|
||||
.watch(&message, RecursiveMode::NonRecursive)
|
||||
.unwrap();
|
||||
old_path = Some(message);
|
||||
}
|
||||
|
||||
@ -70,8 +82,8 @@ async fn file_watcher(rx: Receiver<PathBuf>, tx: Sender<notify::Event>) {
|
||||
|
||||
pub fn update(
|
||||
watcher_channels: Res<WatcherChannels>,
|
||||
mut event_writer: EventWriter<DirworldWatcherEvent>,
|
||||
root_dir: Res<DirworldRootDir>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if root_dir.is_changed() {
|
||||
if let Some(project_dir) = &root_dir.0 {
|
||||
@ -79,61 +91,8 @@ pub fn update(
|
||||
}
|
||||
} else {
|
||||
while let Ok(event) = watcher_channels.rx_changes.try_recv() {
|
||||
event_writer.send(DirworldWatcherEvent(event));
|
||||
commands.trigger(DirworldWatcherEvent(event));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_changes(
|
||||
mut event_reader: EventReader<DirworldWatcherEvent>,
|
||||
mut commands: Commands,
|
||||
dirworld_entities: Query<(Entity, &DirworldEntity)>,
|
||||
observers: Res<DirworldObservers>,
|
||||
codecs: Res<DirworldCodecs>,
|
||||
) {
|
||||
if !event_reader.is_empty() {
|
||||
for DirworldWatcherEvent(event) in event_reader.read() {
|
||||
info!("Watcher Event: {event:?}");
|
||||
match event.kind {
|
||||
EventKind::Remove(_) | EventKind::Modify(ModifyKind::Name(RenameMode::From)) => {
|
||||
for path in &event.paths {
|
||||
remove_entity(&mut commands, &dirworld_entities, path);
|
||||
}
|
||||
}
|
||||
EventKind::Create(_) | EventKind::Modify(ModifyKind::Name(RenameMode::To)) => {
|
||||
for path in &event.paths {
|
||||
process_entry(&mut commands, path, &observers, &codecs);
|
||||
}
|
||||
}
|
||||
EventKind::Modify(ModifyKind::Name(RenameMode::Both))
|
||||
=> {
|
||||
remove_entity(&mut commands, &dirworld_entities, &event.paths[0]);
|
||||
process_entry(&mut commands, &event.paths[1], &observers, &codecs);
|
||||
}
|
||||
// EventKind::Modify(ModifyKind::Data(DataChange::Content))
|
||||
EventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)) => {
|
||||
remove_entity(&mut commands, &dirworld_entities, &event.paths[0]);
|
||||
process_entry(&mut commands, &event.paths[0], &observers, &codecs);
|
||||
}
|
||||
_ => {
|
||||
// warn!("Not Processed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_entity(
|
||||
commands: &mut Commands,
|
||||
dirworld_entities: &Query<(Entity, &DirworldEntity)>,
|
||||
path: &Path,
|
||||
) {
|
||||
if let Some((entity, _)) = dirworld_entities
|
||||
.iter()
|
||||
.find(|(_, dirworld_entity)| dirworld_entity.path == *path)
|
||||
{
|
||||
commands.entity(entity).despawn_recursive();
|
||||
} else {
|
||||
warn!("Failed to find entity corresponding to path for despawning: {path:?}");
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user