diff options
author | 2024-11-21 12:17:44 -0500 | |
---|---|---|
committer | 2024-11-21 12:17:44 -0500 | |
commit | 26e2eddd1efeb0a5fff8ffabebefdae48c4a65dc (patch) | |
tree | 7f0903a6c90fb4e09d2894f55cffd0acbcb51350 | |
parent | 99c398cc127dbc83480f98fea8c76f7c19d4dce8 (diff) |
Thu Nov 21 12:17:44 PM EST 2024
-rw-r--r-- | Cargo.toml | 27 | ||||
-rw-r--r-- | src/actor.rs | 8 | ||||
-rw-r--r-- | src/actor/components.rs | 52 | ||||
-rw-r--r-- | src/actor/events.rs | 72 | ||||
-rw-r--r-- | src/actor/mod.rs | 99 | ||||
-rw-r--r-- | src/actor/resources.rs | 8 | ||||
-rw-r--r-- | src/actor/systems.rs | 121 | ||||
-rw-r--r-- | src/commands.rs | 52 | ||||
-rw-r--r-- | src/conditionals.rs | 202 | ||||
-rw-r--r-- | src/events.rs | 6 | ||||
-rw-r--r-- | src/lib.rs | 78 | ||||
-rw-r--r-- | src/lua_api.rs | 121 | ||||
-rw-r--r-- | src/observers.rs | 18 | ||||
-rw-r--r-- | src/payload.rs | 84 | ||||
-rw-r--r-- | src/payload/components/mod.rs | 56 | ||||
-rw-r--r-- | src/payload/mod.rs | 27 | ||||
-rw-r--r-- | src/resources.rs | 12 | ||||
-rw-r--r-- | src/room_generation/mod.rs | 0 | ||||
-rw-r--r-- | src/systems.rs | 10 | ||||
-rw-r--r-- | src/utils.rs | 17 | ||||
-rw-r--r-- | src/watcher.rs | 5 | ||||
-rw-r--r-- | src/yarnspinner_api.rs | 79 |
22 files changed, 955 insertions, 199 deletions
@@ -4,22 +4,25 @@ version = "0.2.1" edition = "2021" [dependencies] -anyhow = "1.0.83" -async-channel = "2.3.1" -notify = "6.1.1" -tar = "0.4.41" -xz2 = "0.1.7" -rust-crypto = "0.2.36" -multi_key_map = "0.3.0" +anyhow = "1.0" +async-channel = "2.3" +notify = "7.0" +tar = "0.4" +xz2 = "0.1" +rust-crypto = "0.2" +multi_key_map = "0.3" serde = "1.0" -rmp-serde = "1.3.0" +rmp-serde = "1.3" anymap = "0.12" -notify-debouncer-full = "0.3" +notify-debouncer-full = "0.4" md5 = "0.7" bevy-async-ecs = "0.6" aes = "0.8" hex = "0.4" hex-literal = "0.4" +uuid = "1.11" +strum = "0.26" +lazy_static = "1.5" [dependencies.bevy] version = "0.14" @@ -42,9 +45,9 @@ features = ["serde"] version = "0.6" features = ["lua"] -[dependencies.strum] -version = "0.26" -features = ["derive"] +[dependencies.bevy_basic_interaction] +git = "https://github.com/exvacuum/bevy_basic_interaction.git" +tag = "v0.1.0" [features] default = ["yarnspinner"] diff --git a/src/actor.rs b/src/actor.rs deleted file mode 100644 index 3198f32..0000000 --- a/src/actor.rs +++ /dev/null @@ -1,8 +0,0 @@ -use bevy::{prelude::*, utils::HashMap}; -use yarnspinner::{core::LineId, runtime::Dialogue}; - -#[derive(Component)] -pub struct Actor { - pub dialogue: Dialogue, - pub metadata: HashMap<LineId, Vec<String>>, -} diff --git a/src/actor/components.rs b/src/actor/components.rs new file mode 100644 index 0000000..43987c0 --- /dev/null +++ b/src/actor/components.rs @@ -0,0 +1,52 @@ +//! Components related to actors + +use bevy::{prelude::*, utils::HashMap}; +use yarnspinner::{compiler::{Compiler, File}, core::{Library, LineId}, runtime::{Dialogue, MemoryVariableStorage, StringTableTextProvider}}; + +/// Main actor component, holds state about dialogue along with the dialogue runner itself +#[derive(Component)] +pub struct Actor { + /// Whether this actor is currently conversing + pub active: bool, + /// Yarnspinner dialogue runner + pub dialogue: Dialogue, + /// Yarnspinner dialogue metadata + pub metadata: HashMap<LineId, Vec<String>>, +} + +impl Actor { + /// Create a new actor from the given source code, starting on the given start node, and with + /// the given function library + pub fn new(file_name: &str, source: &[u8], start_node: &str, function_library: &Library) -> Self { + let compilation = Compiler::new() + .add_file(File { + source: String::from_utf8_lossy(source).into(), + file_name: file_name.into(), + }) + .compile() + .unwrap(); + + let mut base_language_string_table = std::collections::HashMap::new(); + let mut metadata = HashMap::new(); + + for (k, v) in compilation.string_table { + base_language_string_table.insert(k.clone(), v.text); + metadata.insert(k, v.metadata); + } + + let mut text_provider = StringTableTextProvider::new(); + text_provider.extend_base_language(base_language_string_table); + + let mut dialogue = Dialogue::new(Box::new(MemoryVariableStorage::new()), Box::new(text_provider)); + dialogue.library_mut().extend(function_library.clone()); + dialogue.add_program(compilation.program.unwrap()); + dialogue.set_node(start_node).unwrap(); + + Self { + active: false, + dialogue, + metadata, + } + } +} + diff --git a/src/actor/events.rs b/src/actor/events.rs new file mode 100644 index 0000000..e24e7f3 --- /dev/null +++ b/src/actor/events.rs @@ -0,0 +1,72 @@ +//! Actor-related events + +use bevy::prelude::*; +use yarnspinner::{core::LineId, runtime::{Command, DialogueOption, Line, OptionId}}; + +/// Event called by user to progress dialogue +#[derive(Debug, Event)] +pub enum ContinueDialogueEvent { + /// Continue to next line of dialogue for given actor entity + Continue(Entity), + /// Submit option selection to given actor entity + SelectedOption { + /// Target actor entity + actor: Entity, + /// Selected option ID + option: OptionId + }, +} + +/// Event called by plugin in response to a corresponding yarnspinner dialogue events +/// +/// The user should catch these events to update UI, and never call it directly. +#[derive(Event)] +pub enum DialogueEvent { + /// Recieved new line of dialogue + Line { + /// Actor entity + actor: Entity, + /// Line of dialogue received + line: Line, + }, + /// Dialogue complete + DialogueComplete { + /// Actor entity + actor: Entity, + }, + /// Encountered an option selection + Options { + /// Actor entity + actor: Entity, + /// Options to select from + options: Vec<DialogueOption>, + }, + /// Triggered a yarnspinner command + Command { + /// Actor entity + actor: Entity, + /// Triggered command + command: Command, + }, + /// Node started + NodeStart { + /// Actor entity + actor: Entity, + /// Name of started node + name: String, + }, + /// Node complete + NodeComplete { + /// Actor entity + actor: Entity, + /// Name of completed node + name: String, + }, + /// Received line hints + LineHints { + /// Actor entity + actor: Entity, + /// Lines affected + lines: Vec<LineId>, + }, +} diff --git a/src/actor/mod.rs b/src/actor/mod.rs new file mode 100644 index 0000000..9d0af43 --- /dev/null +++ b/src/actor/mod.rs @@ -0,0 +1,99 @@ +//! NPCs containing their own individual yarnspinner contexts +// TODO: Split off into own crate? + +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}; + +pub mod components; +pub mod events; +pub mod resources; +mod systems; + +lazy_static! { + /// Custom yarnspinner variable storage + /// Stores variables as <instance>.<varname> + /// Global variables are stored in the "global" instance + pub static ref DIRWORLD_VARIABLE_STORAGE: Arc<Mutex<DirworldVariableStorage>> = + Arc::new(Mutex::new(DirworldVariableStorage::default())); +} + +/// Plugin which controls the behavior of actors +pub struct ActorPlugin { + pub custom_function_registration: Option<fn(&mut FunctionLibrary)>, +} + +impl Plugin for ActorPlugin { + fn build(&self, app: &mut App) { + let mut function_library = FunctionLibrary::default(); + function_library.add_function("get_string", get_string); + function_library.add_function("get_number", get_number); + function_library.add_function("get_bool", get_bool); + if let Some(custom_function_registration) = &self.custom_function_registration { + (custom_function_registration)(&mut function_library) + } + + app.add_systems( + Update, + (systems::handle_dialog_initiation, systems::progress_dialog, systems::handle_variable_set_commands), + ) + .insert_resource(function_library) + .add_event::<events::ContinueDialogueEvent>() + .add_event::<events::DialogueEvent>(); + } +} + +fn get_string(instance_name: &str, var_name: &str) -> String { + if let Some(YarnValue::String(value)) = DIRWORLD_VARIABLE_STORAGE + .lock() + .unwrap() + .get(instance_name, var_name) + { + value + } else { + "".into() + } +} + +fn get_number(instance_name: &str, var_name: &str) -> f32 { + if let Some(YarnValue::Number(value)) = DIRWORLD_VARIABLE_STORAGE + .lock() + .unwrap() + .get(instance_name, var_name) + { + value + } else { + 0.0 + } +} + +fn get_bool(instance_name: &str, var_name: &str) -> bool { + if let Some(YarnValue::Boolean(value)) = DIRWORLD_VARIABLE_STORAGE + .lock() + .unwrap() + .get(instance_name, var_name) + { + value + } else { + false + } +} + +/// Variable Storage +#[derive(Default, Debug)] +pub struct DirworldVariableStorage(pub HashMap<String, YarnValue>); + +impl DirworldVariableStorage { + /// Set value of instance variable (use "global" for global) + pub fn set(&mut self, instance_name: &str, var_name: &str, value: YarnValue) { + self.0.insert(format!("{instance_name}.{var_name}"), value); + } + + /// Get value of instance variable (use "global" for global) + pub fn get(&self, instance_name: &str, var_name: &str) -> Option<YarnValue> { + self.0.get(&format!("{instance_name}.{var_name}")).cloned() + } +} diff --git a/src/actor/resources.rs b/src/actor/resources.rs new file mode 100644 index 0000000..76ead59 --- /dev/null +++ b/src/actor/resources.rs @@ -0,0 +1,8 @@ +//! Actor-related resources + +use bevy::prelude::*; +use yarnspinner::core::Library; + +/// Library of yarnspinner function callbacks +#[derive(Resource, Deref, DerefMut, Default, Debug)] +pub struct FunctionLibrary(pub Library); diff --git a/src/actor/systems.rs b/src/actor/systems.rs new file mode 100644 index 0000000..a719858 --- /dev/null +++ b/src/actor/systems.rs @@ -0,0 +1,121 @@ +use bevy::prelude::*; +use bevy_basic_interaction::events::InteractionEvent; +use yarnspinner::core::YarnValue; + +use super::{ + components::Actor, + events::{ContinueDialogueEvent, DialogueEvent}, DIRWORLD_VARIABLE_STORAGE, +}; + +pub fn handle_dialog_initiation( + mut event_reader: EventReader<InteractionEvent>, + mut actor_query: Query<(Entity, &mut Actor)>, + mut event_writer: EventWriter<ContinueDialogueEvent>, +) { + for InteractionEvent { interactable, .. } in event_reader.read() { + if let Ok((actor_entity, mut actor)) = actor_query.get_mut(*interactable) { + actor.active = true; + event_writer.send(ContinueDialogueEvent::Continue(actor_entity)); + } + } +} + +pub fn progress_dialog( + mut event_reader: EventReader<ContinueDialogueEvent>, + mut actor_query: Query<&mut Actor>, + mut event_writer: EventWriter<DialogueEvent>, +) { + for event in event_reader.read() { + let actor_entity = match event { + ContinueDialogueEvent::Continue(actor) => actor, + ContinueDialogueEvent::SelectedOption { actor, .. } => actor, + }; + + if let Ok(mut actor) = actor_query.get_mut(*actor_entity) { + if let ContinueDialogueEvent::SelectedOption { option, .. } = event { + actor.dialogue.set_selected_option(*option).unwrap(); + } + if actor.dialogue.current_node().is_none() { + actor.dialogue.set_node("Start").unwrap(); + } + match actor.dialogue.continue_() { + Ok(events) => { + info!("BATCH"); + for event in events { + info!("Event: {:?}", event); + match event { + yarnspinner::prelude::DialogueEvent::Line(line) => { + event_writer.send(DialogueEvent::Line { + actor: *actor_entity, + line, + }); + } + yarnspinner::prelude::DialogueEvent::DialogueComplete => { + event_writer.send(DialogueEvent::DialogueComplete { + actor: *actor_entity, + }); + } + yarnspinner::prelude::DialogueEvent::Options(options) => { + event_writer.send(DialogueEvent::Options { + actor: *actor_entity, + options, + }); + } + yarnspinner::runtime::DialogueEvent::Command(command) => { + event_writer.send(DialogueEvent::Command { + actor: *actor_entity, + command, + }); + } + yarnspinner::runtime::DialogueEvent::NodeStart(name) => { + event_writer.send(DialogueEvent::NodeStart { + actor: *actor_entity, + name, + }); + } + yarnspinner::runtime::DialogueEvent::NodeComplete(name) => { + event_writer.send(DialogueEvent::NodeComplete { + actor: *actor_entity, + name, + }); + } + yarnspinner::runtime::DialogueEvent::LineHints(lines) => { + event_writer.send(DialogueEvent::LineHints { + actor: *actor_entity, + lines, + }); + } + } + } + } + Err(err) => error!("{:?}", err), + } + } + } +} + +pub fn handle_variable_set_commands( + mut event_reader: EventReader<DialogueEvent>, + mut event_writer: EventWriter<ContinueDialogueEvent>, +) { + for event in event_reader.read() { + if let DialogueEvent::Command { command, actor } = event { + if command.name != "set_var" { + continue; + } + + event_writer.send(ContinueDialogueEvent::Continue(*actor)); + + if command.parameters.len() != 3 { + warn!("Incorrect number of parameters passed to set command: {}", command.parameters.len()); + continue; + } + + if let YarnValue::String(instance_name) = &command.parameters[0] { + if let YarnValue::String(var_name) = &command.parameters[1] { + DIRWORLD_VARIABLE_STORAGE.lock().unwrap().set(instance_name, var_name, command.parameters[2].clone()); + } + } + } + } +} diff --git a/src/commands.rs b/src/commands.rs index 6066ab7..60da108 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,11 +1,7 @@ -use std::{ - fs, path::PathBuf, -}; +use std::{fs, path::PathBuf}; use bevy::{ - ecs:: - world::{Command, CommandQueue} - , + ecs::world::{Command, CommandQueue}, prelude::*, tasks::AsyncComputeTaskPool, }; @@ -18,9 +14,10 @@ use occule::Error; use xz2::read::{XzDecoder, XzEncoder}; use crate::{ - payload::{DirworldComponent, DirworldComponentDiscriminants, DirworldEntityPayload}, resources::{ - DirworldCodecs, DirworldObservers, DirworldTasks, - }, utils::extract_entity_payload, Extensions + payload::DirworldEntityPayload, + resources::{DirworldCodecs, DirworldObservers, DirworldTasks}, + utils::extract_entity_payload, + Extensions, }; struct DirworldLockDoorCommand { @@ -57,7 +54,13 @@ impl Command for DirworldLockDoorCommand { let result = crypter .encrypt(&mut read_buffer, &mut write_buffer, true) .expect("Failed to encrypt data!"); - encrypted.extend(write_buffer.take_read_buffer().take_remaining().iter().map(|&i|i)); + encrypted.extend( + write_buffer + .take_read_buffer() + .take_remaining() + .iter() + .map(|&i| i), + ); match result { BufferResult::BufferUnderflow => break, BufferResult::BufferOverflow => {} @@ -72,11 +75,9 @@ impl Command for DirworldLockDoorCommand { // Insert key hash as payload relationship let key_digest = md5::compute(&self.key[..16]); - let mut payload = payload.unwrap_or_default(); - payload.push(DirworldComponent::Relationship { - label: "key".into(), - hash: key_digest.0, - }); + let mut payload = payload.unwrap_or_else(|| DirworldEntityPayload::new()); + let relationships = payload.relationships.get_or_insert_default(); + relationships.insert("key".into(), key_digest.0); // Write payload let mut command_queue = CommandQueue::default(); @@ -119,7 +120,13 @@ impl Command for DirworldUnlockDoorCommand { let result = decrypter .decrypt(&mut read_buffer, &mut write_buffer, true) .expect("Failed to encrypt data!"); - decrypted.extend(write_buffer.take_read_buffer().take_remaining().iter().map(|&i|i)); + decrypted.extend( + write_buffer + .take_read_buffer() + .take_remaining() + .iter() + .map(|&i| i), + ); match result { BufferResult::BufferUnderflow => break, BufferResult::BufferOverflow => {} @@ -137,16 +144,9 @@ impl Command for DirworldUnlockDoorCommand { fs::remove_file(path.clone()).unwrap(); if let Some(mut payload) = payload { - for (index, relationship) in payload.iter().enumerate().filter(|(_, x)| { - DirworldComponentDiscriminants::from(*x) - == DirworldComponentDiscriminants::Relationship - }) { - if let DirworldComponent::Relationship { label, .. } = relationship { - if label == "key" { - payload.remove(index); - break; - } - } + // Remove key relationship + if let Some(ref mut relationships) = payload.relationships { + relationships.remove("key"); } // Write payload diff --git a/src/conditionals.rs b/src/conditionals.rs new file mode 100644 index 0000000..266379a --- /dev/null +++ b/src/conditionals.rs @@ -0,0 +1,202 @@ +use bevy::{ + ecs::system::SystemState, + prelude::{AncestorIter, Entity, Parent, Query, World}, +}; +use serde::{Deserialize, Serialize}; +use strum::{AsRefStr, EnumString}; +use uuid::Uuid; + +use crate::{components::DirworldEntity, resources::DirworldCurrentDir}; + +// I Store Conditions as Enum Data +#[derive(Serialize, Deserialize, AsRefStr, Debug, Default, Clone, PartialEq, Eq)] +pub enum Condition { + #[default] + #[strum(serialize = "Always True")] + True, + #[strum(serialize = "Child Of")] + ChildOf { + child: Uuid, + parent: Uuid, + }, + #[strum(serialize = "Parent Of")] + ParentOf { + parent: Uuid, + child: Uuid, + }, + #[strum(serialize = "Descendant Of")] + DescendantOf { + descendant: Uuid, + ancestor: Uuid, + }, + #[strum(serialize = "Ancestor Of")] + AncestorOf { + ancestor: Uuid, + descendant: Uuid, + }, + #[strum(serialize = "In Room")] + InRoom(Uuid), + #[strum(serialize = "Object In Room")] + ObjectInRoom(Uuid), +} + +impl Condition { + pub fn evaluate(&self, world: &mut World) -> bool { + match self { + Condition::True => true, + Condition::ChildOf { child, parent } => parent_of(world, *parent, *child), + Condition::ParentOf { parent, child } => parent_of(world, *parent, *child), + Condition::DescendantOf { + descendant, + ancestor, + } => ancestor_of(world, *ancestor, *descendant), + Condition::AncestorOf { + ancestor, + descendant, + } => ancestor_of(world, *ancestor, *descendant), + Condition::InRoom(uuid) => in_room(world, *uuid), + Condition::ObjectInRoom(uuid) => object_in_room(world, *uuid), + } + } + + pub fn get_api_function_name(&self) -> &'static str { + match self { + Condition::True => "conditional_true", + Condition::ChildOf { .. } => "conditional_child_of", + Condition::ParentOf { .. } => "conditional_parent_of", + Condition::DescendantOf { .. } => "conditional_descendant_of", + Condition::AncestorOf { .. } =>"conditional_ancestor_of", + Condition::InRoom(_) => "conditional_in_room", + Condition::ObjectInRoom(_) => "conditional_object_in_room", + } + } + + pub fn from_api_function_name_and_args(name: &str, args: &[&str]) -> Option<Self> { + match name { + "conditional_true" => Some(Condition::True), + "conditional_child_of" => { + let Some(child) = args.get(0).and_then(|arg| Uuid::parse_str(arg).ok()) else { + return None; + }; + let Some(parent) = args.get(1).and_then(|arg| Uuid::parse_str(arg).ok()) else { + return None; + }; + Some(Condition::ChildOf { child, parent }) + } + "conditional_parent_of" => { + let Some(child) = args.get(1).and_then(|arg| Uuid::parse_str(arg).ok()) else { + return None; + }; + let Some(parent) = args.get(0).and_then(|arg| Uuid::parse_str(arg).ok()) else { + return None; + }; + Some(Condition::ParentOf { child, parent }) + } + "conditional_descendant_of" => { + let Some(descendant) = args.get(0).and_then(|arg| Uuid::parse_str(arg).ok()) else { + return None; + }; + let Some(ancestor) = args.get(1).and_then(|arg| Uuid::parse_str(arg).ok()) else { + return None; + }; + Some(Condition::DescendantOf { descendant, ancestor }) + } + "conditional_ancestor_of" => { + let Some(descendant) = args.get(1).and_then(|arg| Uuid::parse_str(arg).ok()) else { + return None; + }; + let Some(ancestor) = args.get(0).and_then(|arg| Uuid::parse_str(arg).ok()) else { + return None; + }; + Some(Condition::AncestorOf { descendant, ancestor }) + } + "condtitional_in_room" => { + let Some(room_id) = args.get(0).and_then(|arg| Uuid::parse_str(arg).ok()) else { + return None; + }; + Some(Condition::InRoom(room_id)) + } + "condtitional_object_in_room" => { + let Some(object_id) = args.get(0).and_then(|arg| Uuid::parse_str(arg).ok()) else { + return None; + }; + Some(Condition::ObjectInRoom(object_id)) + } + _ => None, + } + } +} + +// Condition Checkers beyond this point + +fn ancestor_of(world: &mut World, ancestor: Uuid, descendant: Uuid) -> bool { + let mut system_state = + SystemState::<(Query<(Entity, &DirworldEntity)>, Query<&Parent>)>::new(world); + let (dirworld_entities, parents) = system_state.get(world); + let Some((ancestor_entity, _)) = dirworld_entities.iter().find(|(_, entity)| { + entity + .payload + .as_ref() + .is_some_and(|payload| payload.id == ancestor) + }) else { + return false; + }; + + let Some((descendant_entity, _)) = dirworld_entities.iter().find(|(_, entity)| { + entity + .payload + .as_ref() + .is_some_and(|payload| payload.id == descendant) + }) else { + return false; + }; + + AncestorIter::new(&parents, descendant_entity) + .find(|descendant| *descendant == ancestor_entity) + .is_some() +} + +fn parent_of(world: &mut World, parent: Uuid, child: Uuid) -> bool { + let mut system_state = + SystemState::<(Query<(Entity, &DirworldEntity)>, Query<&Parent>)>::new(world); + let (dirworld_entities, parents) = system_state.get(world); + let Some((parent_entity, _)) = dirworld_entities.iter().find(|(_, entity)| { + entity + .payload + .as_ref() + .is_some_and(|payload| payload.id == parent) + }) else { + return false; + }; + + let Some((child_entity, _)) = dirworld_entities.iter().find(|(_, entity)| { + entity + .payload + .as_ref() + .is_some_and(|payload| payload.id == child) + }) else { + return false; + }; + + parents + .get(child_entity) + .is_ok_and(|parent| parent.get() == parent_entity) +} + +fn in_room(world: &mut World, room: Uuid) -> bool { + let current_dir = world.resource::<DirworldCurrentDir>(); + current_dir.payload.as_ref().is_some_and(|payload| payload.id == room) +} + +fn object_in_room(world: &mut World, object: Uuid) -> bool { + let mut dirworld_entities = world.query::<&DirworldEntity>(); + dirworld_entities + .iter(world) + .find(|entity| { + entity + .payload + .as_ref() + .is_some_and(|payload| payload.id == object) + }) + .is_some() +} diff --git a/src/events.rs b/src/events.rs index 4d12f5b..2fa7f81 100644 --- a/src/events.rs +++ b/src/events.rs @@ -17,13 +17,13 @@ pub enum DirworldNavigationEvent { }, } -#[derive(Debug, Event, Deref, DerefMut)] +#[derive(Debug, Event, Deref, DerefMut, Clone)] pub struct DirworldLeaveRoom(pub PathBuf); -#[derive(Debug, Event, Deref, DerefMut)] +#[derive(Debug, Event, Deref, DerefMut, Clone)] pub struct DirworldEnterRoom(pub PathBuf); -#[derive(Debug, Event, Deref, DerefMut)] +#[derive(Debug, Event, Deref, DerefMut, Clone)] pub struct DirworldChangeRoot(pub PathBuf); #[derive(Event)] @@ -4,9 +4,14 @@ 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 events::{ + DirworldChangeRoot, DirworldEnterRoom, DirworldLeaveRoom, DirworldNavigationEvent, + DirworldSpawn, +}; use occule::Codec; use resources::DirworldCache; use resources::{ @@ -15,6 +20,7 @@ use resources::{ }; pub use watcher::DirworldWatcherEvent; pub use watcher::DirworldWatcherSet; +use yarnspinner::core::Library; /// Components used by this plugin pub mod components; @@ -34,7 +40,7 @@ mod systems; mod observers; -mod utils; +pub mod utils; /// Payload for dirworld entities pub mod payload; @@ -44,6 +50,12 @@ pub mod actor; mod lua_api; +pub mod conditionals; + +pub mod yarnspinner_api; + +pub mod room_generation; + /// Plugin which enables high-level interaction #[derive(Default)] pub struct DirworldPlugin { @@ -54,32 +66,42 @@ pub struct DirworldPlugin { impl Plugin for DirworldPlugin { fn build(&self, app: &mut App) { info!("building"); - app.add_systems(Startup, watcher::setup) - .add_systems( - Update, - (systems::remove_completed_tasks, lua_api::trigger_update), - ) - .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); - } - }) - .init_resource::<DirworldCache>() - .init_resource::<DirworldRootDir>() - .init_resource::<DirworldCurrentDir>() - .init_resource::<DirworldTasks>() - .init_resource::<DirworldObservers>() - .init_resource::<DirworldCodecs>() - .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); + app.add_plugins(ActorPlugin { + custom_function_registration: Some(yarnspinner_api::setup_yarnspinner_functions), + }) + .add_systems(Startup, watcher::setup) + .add_systems( + Update, + ( + systems::remove_completed_tasks, + lua_api::trigger_update, + 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>() + .init_resource::<DirworldRootDir>() + .init_resource::<DirworldCurrentDir>() + .init_resource::<DirworldTasks>() + .init_resource::<DirworldObservers>() + .init_resource::<DirworldCodecs>() + .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); } } diff --git a/src/lua_api.rs b/src/lua_api.rs index cda2486..f9a34d4 100644 --- a/src/lua_api.rs +++ b/src/lua_api.rs @@ -1,5 +1,13 @@ +use std::str::FromStr; + use bevy::prelude::*; -use bevy_scriptum::{runtimes::lua::{BevyEntity, BevyVec3, LuaRuntime, LuaScriptData}, ScriptingRuntimeBuilder, Runtime}; +use bevy_scriptum::{ + runtimes::lua::{BevyEntity, BevyVec3, LuaRuntime, LuaScriptData}, + Runtime, ScriptingRuntimeBuilder, +}; +use uuid::Uuid; + +use crate::{components::DirworldEntity, conditionals::Condition}; pub fn trigger_update( mut scripted_entities: Query<(Entity, &mut LuaScriptData)>, @@ -8,7 +16,7 @@ pub fn trigger_update( ) { 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, )) { + if let Err(e) = scripting_runtime.call_fn("on_update", &mut script_data, entity, (delta,)) { error!("Encountered lua scripting error: {:?}", e); } } @@ -18,14 +26,28 @@ pub fn trigger_update( macro_rules! register_fns { ($runtime:expr, $($function:expr),+) => { - { + { $runtime$(.add_function(stringify!($function).to_string(), $function))+ } }; } -pub fn register(runtime: ScriptingRuntimeBuilder<LuaRuntime>) -> ScriptingRuntimeBuilder<LuaRuntime> { - register_fns!(runtime, translate, rotate) +pub fn register( + runtime: ScriptingRuntimeBuilder<LuaRuntime>, +) -> ScriptingRuntimeBuilder<LuaRuntime> { + register_fns!( + runtime, + translate, + rotate, + get_dirworld_id, + condition_true, + condition_ancestor_of, + condition_descendant_of, + condition_parent_of, + condition_child_of, + condition_in_room, + condition_object_in_room + ) } fn translate( @@ -50,4 +72,91 @@ fn rotate( } } -// }}} +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())) +} + +// Conditionals +fn condition_true(world: &mut World) -> bool { + Condition::True.evaluate(world) +} + +fn condition_ancestor_of( + In((ancestor, descendant)): In<(String, String)>, + world: &mut World, +) -> bool { + let Ok(ancestor) = Uuid::from_str(&ancestor) else { + warn!("Provided ancestor is not a valid UUID"); + return false; + }; + let Ok(descendant) = Uuid::from_str(&descendant) else { + warn!("Provided descendant is not a valid UUID"); + return false; + }; + Condition::AncestorOf { + ancestor, + descendant, + } + .evaluate(world) +} + +fn condition_descendant_of( + In((descendant, ancestor)): In<(String, String)>, + world: &mut World, +) -> bool { + let Ok(ancestor) = Uuid::from_str(&ancestor) else { + warn!("Provided ancestor is not a valid UUID"); + return false; + }; + let Ok(descendant) = Uuid::from_str(&descendant) else { + warn!("Provided descendant is not a valid UUID"); + return false; + }; + Condition::DescendantOf { + ancestor, + descendant, + } + .evaluate(world) +} + +fn condition_parent_of(In((parent, child)): In<(String, String)>, world: &mut World) -> bool { + let Ok(parent) = Uuid::from_str(&parent) else { + warn!("Provided parent is not a valid UUID"); + return false; + }; + let Ok(child) = Uuid::from_str(&child) else { + warn!("Provided child is not a valid UUID"); + return false; + }; + Condition::ParentOf { parent, child }.evaluate(world) +} + +fn condition_child_of(In((child, parent)): In<(String, String)>, world: &mut World) -> bool { + let Ok(parent) = Uuid::from_str(&parent) else { + warn!("Provided parent is not a valid UUID"); + return false; + }; + let Ok(child) = Uuid::from_str(&child) else { + warn!("Provided child is not a valid UUID"); + return false; + }; + Condition::ChildOf { parent, child }.evaluate(world) +} + +fn condition_in_room(In((room,)): In<(String,)>, world: &mut World) -> bool { + let Ok(room) = Uuid::from_str(&room) else { + warn!("Provided room is not a valid UUID"); + return false; + }; + Condition::InRoom(room).evaluate(world) +} + +fn condition_object_in_room(In((object,)): In<(String,)>, world: &mut World) -> bool { + let Ok(object) = Uuid::from_str(&object) else { + warn!("Provided object is not a valid UUID"); + return false; + }; + Condition::ObjectInRoom(object).evaluate(world) +} + +// }}} diff --git a/src/observers.rs b/src/observers.rs index b321c53..44edb50 100644 --- a/src/observers.rs +++ b/src/observers.rs @@ -12,16 +12,17 @@ use crate::{ resources::{ DirworldCache, DirworldCodecs, DirworldCurrentDir, DirworldObservers, DirworldRootDir, }, - utils::{despawn_entity_by_path, spawn_entity}, + utils::{despawn_entity_by_path, extract_entity_payload, spawn_entity}, DirworldWatcherEvent, }; /// On navigation from a room, insert modified payloads into the cache pub fn navigate_from_room( - _: Trigger<DirworldLeaveRoom>, + trigger: Trigger<DirworldLeaveRoom>, entities: Query<(Entity, Ref<DirworldEntity>), Without<Persist>>, mut cache: ResMut<DirworldCache>, mut commands: Commands, + mut event_writer: EventWriter<DirworldLeaveRoom>, ) { for (entity, dirworld_entity) in entities.iter() { if let Some(payload) = &dirworld_entity.payload { @@ -30,6 +31,7 @@ pub fn navigate_from_room( } commands.entity(entity).despawn_recursive(); } + event_writer.send(trigger.event().clone()); } pub fn navigate_to_room( @@ -39,9 +41,16 @@ pub fn navigate_to_room( observers: Res<DirworldObservers>, codecs: Res<DirworldCodecs>, mut commands: Commands, + mut event_writer: EventWriter<DirworldEnterRoom>, + mut current_dir: ResMut<DirworldCurrentDir>, ) { let path = &trigger.event().0; + let room_payload = extract_entity_payload(&path.join(".door"), &codecs).0; + *current_dir = DirworldCurrentDir { + path: path.to_path_buf(), + payload: room_payload, + }; let entries = match path.read_dir() { Ok(entries) => entries .flatten() @@ -74,6 +83,7 @@ pub fn navigate_to_room( for entry in entries { spawn_entity(&entry, &mut cache, &codecs, &observers, &mut commands); } + event_writer.send(trigger.event().clone()); } pub fn handle_changes( @@ -83,6 +93,7 @@ pub fn handle_changes( observers: Res<DirworldObservers>, codecs: Res<DirworldCodecs>, mut cache: ResMut<DirworldCache>, + mut event_writer: EventWriter<DirworldWatcherEvent>, ) { let event = &trigger.event().0; info!("Watcher Event: {event:?}"); @@ -121,12 +132,12 @@ pub fn handle_changes( // warn!("Not Processed.") } } + event_writer.send(trigger.event().clone()); } 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() { @@ -136,7 +147,6 @@ pub fn change_root( 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())); } diff --git a/src/payload.rs b/src/payload.rs deleted file mode 100644 index 2aa3f23..0000000 --- a/src/payload.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::{collections::HashMap, str::FromStr}; - -use avian3d::prelude::RigidBody; -use bevy::prelude::*; -use serde::{Deserialize, Serialize}; -use strum::{EnumDiscriminants, EnumString}; -use yarnspinner::core::YarnValue; - -#[derive(Serialize, Deserialize, Default, Clone, Deref, DerefMut, Debug)] -pub struct DirworldEntityPayload(Vec<DirworldComponent>); - -impl DirworldEntityPayload { - pub fn component(&self, name: &str) -> Option<&DirworldComponent> { - if let Ok(discriminant) = DirworldComponentDiscriminants::from_str(name) { - self.iter() - .find(|component| discriminant == DirworldComponentDiscriminants::from(*component)) - } else { - None - } - } - - pub fn component_mut(&mut self, name: &str) -> Option<&mut DirworldComponent> { - if let Ok(discriminant) = DirworldComponentDiscriminants::from_str(name) { - self.iter_mut() - .find(|component| discriminant == DirworldComponentDiscriminants::from(&**component)) - } else { - None - } - } - - pub fn components(&self, name: &str) -> Vec<&DirworldComponent> { - if let Ok(discriminant) = DirworldComponentDiscriminants::from_str(name) { - self.iter() - .filter(|component| { - discriminant == DirworldComponentDiscriminants::from(*component) - }) - .collect() - } else { - vec![] - } - } - - pub fn components_mut(&mut self, name: &str) -> Vec<&mut DirworldComponent> { - if let Ok(discriminant) = DirworldComponentDiscriminants::from_str(name) { - self.iter_mut() - .filter(|component| { - discriminant == DirworldComponentDiscriminants::from(&**component) - }) - .collect() - } else { - vec![] - } - } -} - -#[derive(Serialize, Deserialize, Clone, EnumDiscriminants, Debug)] -#[strum_discriminants(derive(EnumString))] -pub enum DirworldComponent { - Transform(Transform), - Name(String), - Actor { - local_variables: HashMap<String, YarnValue>, - yarn_source: Vec<u8>, - }, - Voice { - pitch: i32, - preset: i32, - bank: i32, - variance: u32, - speed: f32, - }, - Rigidbody(RigidBody), - MeshCollider { - convex: bool, - sensor: bool, - }, - Script { - lua_source: Vec<u8>, - }, - Relationship { - label: String, - hash: [u8; 16], - }, -} diff --git a/src/payload/components/mod.rs b/src/payload/components/mod.rs new file mode 100644 index 0000000..e8919fd --- /dev/null +++ b/src/payload/components/mod.rs @@ -0,0 +1,56 @@ +use std::collections::HashMap; + +use avian3d::prelude::RigidBody; +use bevy::prelude::*; +use serde::{Deserialize, Serialize}; +use yarnspinner::core::YarnValue; + +#[derive(Serialize, Deserialize, Clone, Default, Deref, DerefMut, Debug)] +pub struct Transform(pub bevy::prelude::Transform); + +#[derive(Serialize, Deserialize, Clone, Default, Deref, DerefMut, Debug)] +pub struct Name(pub String); + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +pub struct Actor { + pub local_variables: HashMap<String, YarnValue>, + pub yarn_source: Vec<u8>, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Voice { + pub pitch: i32, + pub preset: i32, + pub bank: i32, + pub variance: u32, + pub speed: f32, +} + +impl Default for Voice { + fn default() -> Self { + Self { + pitch: 60, + preset: 0, + bank: 0, + variance: 3, + speed: 1.0, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Default, Deref, DerefMut, Debug)] +pub struct Rigidbody(pub RigidBody); + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +pub struct MeshCollider { + pub convex: bool, + pub sensor: bool, +} + +#[derive(Serialize, Deserialize, Clone, Default, Debug)] +pub struct Script { + pub lua_source: Vec<u8>, +} + +#[derive(Serialize, Deserialize, Clone, Default, Deref, DerefMut, Debug)] +pub struct Relationships(pub HashMap<String, [u8; 16]>); diff --git a/src/payload/mod.rs b/src/payload/mod.rs new file mode 100644 index 0000000..483fee5 --- /dev/null +++ b/src/payload/mod.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +pub mod components; + +#[derive(Serialize, Deserialize, Default, Clone, Debug)] +pub struct DirworldEntityPayload { + pub id: Uuid, + pub transform: components::Transform, + pub name: Option<components::Name>, + pub actor: Option<components::Actor>, + pub voice: Option<components::Voice>, + pub rigidbody: Option<components::Rigidbody>, + pub mesh_collider: Option<components::MeshCollider>, + pub scripts: Option<Vec<components::Script>>, + pub relationships: Option<components::Relationships>, +} + +impl DirworldEntityPayload { + pub fn new() -> Self { + Self { + id: Uuid::new_v4(), + ..Default::default() + } + } +} + diff --git a/src/resources.rs b/src/resources.rs index 152a149..cbf570d 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -3,6 +3,7 @@ use std::{collections::{BTreeMap, HashMap}, 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; @@ -11,18 +12,21 @@ use crate::payload::DirworldEntityPayload; pub struct DirworldRootDir(pub Option<PathBuf>); /// Current directory of the world -#[derive(Resource, Deref, DerefMut, Default)] -pub struct DirworldCurrentDir(pub Option<PathBuf>); +#[derive(Resource, Default)] +pub struct DirworldCurrentDir{ + pub path: PathBuf, + pub payload: Option<DirworldEntityPayload>, +} /// Running background tasks #[derive(Default, Resource, Deref, DerefMut)] pub struct DirworldTasks(pub BTreeMap<String, Task<Option<CommandQueue>>>); #[derive(Debug, Default, Resource, Deref, DerefMut)] -pub(crate) struct DirworldObservers(pub MultiKeyMap<EntryType, Entity>); +pub struct DirworldObservers(pub MultiKeyMap<EntryType, Entity>); #[derive(Default, Resource, Deref, DerefMut)] -pub(crate) struct DirworldCodecs(pub MultiKeyMap<String, Box<dyn Codec + Send + Sync>>); +pub struct DirworldCodecs(pub MultiKeyMap<String, Box<dyn Codec + Send + Sync>>); #[derive(Debug, PartialEq, Eq, Hash)] pub enum EntryType { diff --git a/src/room_generation/mod.rs b/src/room_generation/mod.rs new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/room_generation/mod.rs diff --git a/src/systems.rs b/src/systems.rs index 6c3bc71..3f894ec 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, payload::DirworldComponent, resources::DirworldTasks}; +use crate::{components::DirworldEntity, resources::DirworldTasks}; pub fn remove_completed_tasks(mut commands: Commands, mut tasks: ResMut<DirworldTasks>) { tasks.retain(|_, task| { @@ -22,12 +22,8 @@ pub fn sync_entity_transforms( 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; - } + let transform = global_transform.compute_transform(); + *payload.transform = transform; } } } diff --git a/src/utils.rs b/src/utils.rs index df0a8b6..e4692bb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,7 +3,7 @@ use std::{fs, path::PathBuf}; use bevy::prelude::*; use crate::{ - components::DirworldEntity, events::DirworldSpawn, payload::{DirworldComponent, DirworldEntityPayload}, resources::{DirworldCache, DirworldCodecs, DirworldObservers, EntryType}, Extensions + components::DirworldEntity, events::DirworldSpawn, payload::DirworldEntityPayload, resources::{DirworldCache, DirworldCodecs, DirworldObservers, EntryType}, Extensions }; pub fn extract_entity_payload( @@ -74,18 +74,7 @@ pub fn spawn_entity( 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 transform = payload.as_ref().map(|payload| payload.transform.clone()).unwrap_or_default(); let entry_type = if entry.is_dir() { EntryType::Folder } else { @@ -94,7 +83,7 @@ pub fn spawn_entity( let entity = commands .spawn(( SpatialBundle { - transform, + transform: *transform, ..Default::default() }, DirworldEntity { diff --git a/src/watcher.rs b/src/watcher.rs index 78d74f2..8918dda 100644 --- a/src/watcher.rs +++ b/src/watcher.rs @@ -20,7 +20,7 @@ use crate::{ pub struct DirworldWatcherSet; /// Event fired when a file watcher event is caught. -#[derive(Event, Debug)] +#[derive(Event, Clone, Debug)] pub struct DirworldWatcherEvent(pub notify::Event); #[derive(Resource)] @@ -65,10 +65,9 @@ async fn file_watcher(rx: Receiver<PathBuf>, tx: Sender<notify::Event>) { loop { while let Ok(message) = rx.try_recv() { if let Some(old_path) = &old_path { - debouncer.watcher().unwatch(old_path).unwrap(); + debouncer.unwatch(old_path).unwrap(); } debouncer - .watcher() .watch(&message, RecursiveMode::NonRecursive) .unwrap(); old_path = Some(message); diff --git a/src/yarnspinner_api.rs b/src/yarnspinner_api.rs new file mode 100644 index 0000000..e7f8781 --- /dev/null +++ b/src/yarnspinner_api.rs @@ -0,0 +1,79 @@ +use bevy::{log::info, prelude::World}; +use lazy_static::lazy_static; +use std::sync::{ + Arc, Condvar, Mutex, +}; + +use crate::{actor::resources::FunctionLibrary, conditionals::Condition}; + +lazy_static! { + static ref YARNSPINNER_WORLD: Arc<Mutex<World>> = Arc::new(Mutex::new(World::new())); + static ref YARNSPINNER_CVAR: Arc<(Condvar, Mutex<bool>)> = + Arc::new((Condvar::new(), Mutex::new(false))); + static ref YARNSPINNER_COMMAND_COUNT: Arc<(Condvar, Mutex<usize>)> = + Arc::new((Condvar::new(), Mutex::new(0))); +} + +macro_rules! register_fns { + ($runtime:expr, $($function:expr),+) => { + { + $runtime$(.add_function(stringify!($function).to_string(), $function))+ + } + }; +} + +pub fn setup_yarnspinner_functions(library: &mut FunctionLibrary) { + register_fns!(library, conditional_true); +} + +pub fn process_commands(world: &mut World) { + let command_count = YARNSPINNER_COMMAND_COUNT.1.lock().unwrap(); + if *command_count <= 0 { + return; + } + + info!("Swapping World to Yarnspinner"); + let mut temp_world = World::new(); + std::mem::swap(&mut temp_world, world); + { + let mut world_swapped = YARNSPINNER_CVAR.1.lock().unwrap(); + *world_swapped = true; + } + + let mut command_count = YARNSPINNER_COMMAND_COUNT.1.lock().unwrap(); + while !*command_count <= 0 { + info!("Command Count: {}", *command_count); + command_count = YARNSPINNER_COMMAND_COUNT.0.wait(command_count).unwrap(); + } + + info!("Swapping World from Yarnspinner"); + std::mem::swap(&mut temp_world, world); + { + let mut world_swapped = YARNSPINNER_CVAR.1.lock().unwrap(); + *world_swapped = false; + } +} + +fn conditional(condition: Condition) -> bool { + { + let mut command_count = YARNSPINNER_COMMAND_COUNT.1.lock().unwrap(); + *command_count += 1; + } + + let mut world_swapped = YARNSPINNER_CVAR.1.lock().unwrap(); + while !*world_swapped { + world_swapped = YARNSPINNER_CVAR.0.wait(world_swapped).unwrap(); + } + + let result = condition.evaluate(&mut YARNSPINNER_WORLD.lock().unwrap()); + + { + let mut command_count = YARNSPINNER_COMMAND_COUNT.1.lock().unwrap(); + *command_count -= 1; + } + result +} + +fn conditional_true() -> bool { + conditional(Condition::True) +} |