diff --git a/Cargo.lock b/Cargo.lock index 13791313ef63285cc513593ab15d7aa884fba41d..e220fcdc378c5e2d3a31210ad5357cc8d3852f65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1332,6 +1332,16 @@ dependencies = [ "syn", ] +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +dependencies = [ + "cfg-if 1.0.0", + "num_cpus", +] + [[package]] name = "deflate" version = "0.8.6" @@ -1353,6 +1363,17 @@ dependencies = [ "syn", ] +[[package]] +name = "desync" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f27dcf5aa323a4d6fe62caa089e59f280445a5fa64734ffdbe4cdccf2d927a" +dependencies = [ + "futures", + "lazy_static", + "num_cpus", +] + [[package]] name = "diff-struct" version = "0.3.1" @@ -1459,6 +1480,23 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" +[[package]] +name = "flo_binding" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711a65cd60b91114a1cecbf6a9d533c3fde6e38532506cde25bc5f7bc87d2fa0" +dependencies = [ + "desync", + "flo_rope", + "futures", +] + +[[package]] +name = "flo_rope" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934e2de58fcae0ada41e86f12f49f169d2a0349ebd27fc4cfb3d3ba34db827be" + [[package]] name = "fnv" version = "1.0.7" @@ -1499,17 +1537,53 @@ dependencies = [ "libc", ] +[[package]] +name = "futures" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd0210d8c325c245ff06fd95a3b13689a1a276ac8cfa8e8720cb840bfb84b9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" +checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445" + +[[package]] +name = "futures-executor" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b808bf53348a36cab739d7e04755909b9fcaaa69b7d7e588b37b6ec62704c97" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] [[package]] name = "futures-io" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" +checksum = "e481354db6b5c353246ccf6a728b0c5511d752c08da7260546fc0933869daa11" [[package]] name = "futures-lite" @@ -1526,6 +1600,47 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "futures-macro" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89f17b21645bc4ed773c69af9c9a0effd4a3f1a3876eadd453469f8854e7fdd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "996c6442437b62d21a32cd9906f9c41e7dc1e19a9579843fad948696769305af" + +[[package]] +name = "futures-task" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12" + +[[package]] +name = "futures-util" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "fxhash" version = "0.2.1" @@ -1886,8 +2001,10 @@ name = "kayak_core" version = "0.1.0" dependencies = [ "as-any", + "dashmap", "derivative", "diff-struct", + "flo_binding", "fontdue", "kayak_render_macros", "morphorm", @@ -2506,6 +2623,12 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.22" diff --git a/examples/bevy.rs b/examples/bevy.rs index dc9c042deae3ec846a5b6af15b4de9f4bcc31f72..eade83d0f7e9484a8953b84578d665cfc306b05e 100644 --- a/examples/bevy.rs +++ b/examples/bevy.rs @@ -12,10 +12,6 @@ use kayak_ui::core::{rsx, widget}; #[widget] fn TestState() { - let _new_x = { - let x = context.create_state(0.0f32).unwrap(); - *x + 0.1 - }; rsx! { <> <Window position={(50.0, 50.0)} size={(300.0, 300.0)} title={"Window 1".to_string()}> diff --git a/examples/counter.rs b/examples/counter.rs index 4e5c163b199b5711d8466a687c966ed5d7e908af..3e555e729e2f53665343840bad6b60b8983c5578 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -8,17 +8,13 @@ use bevy_kayak_ui::{BevyContext, BevyKayakUIPlugin, FontMapping, UICameraBundle} use kayak_components::{Button, Text, Window}; use kayak_core::{ styles::{Style, StyleProp, Units}, - EventType, Index, OnEvent, + Bound, EventType, Index, MutableBound, OnEvent, }; use kayak_ui::components::App; use kayak_ui::core::{rsx, widget}; #[widget] fn Counter(context: &mut KayakContext) { - let count = { - let x = context.create_state(0i32).unwrap(); - *x - }; let text_styles = Style { bottom: StyleProp::Value(Units::Stretch(1.0)), left: StyleProp::Value(Units::Stretch(1.0)), @@ -28,19 +24,20 @@ fn Counter(context: &mut KayakContext) { ..Default::default() }; - let id = self.get_id(); - let on_event = OnEvent::new(move |context, event| match event.event_type { + let count = context.create_state(0i32).unwrap(); + let cloned_count = count.clone(); + let on_event = OnEvent::new(move |_, event| match event.event_type { EventType::Click => { - context.set_current_id(id); - context.set_state(count + 1); + cloned_count.set(cloned_count.get() + 1); } _ => {} }); + let count_value = count.get(); rsx! { <> <Window position={(50.0, 50.0)} size={(300.0, 300.0)} title={"Counter Example".to_string()}> - <Text size={32.0} content={format!("Current Count: {}", count).to_string()}>{}</Text> + <Text size={32.0} content={format!("Current Count: {}", count_value).to_string()}>{}</Text> <Button on_event={Some(on_event)}> <Text styles={Some(text_styles)} size={24.0} content={"Count!".to_string()}>{}</Text> </Button> diff --git a/examples/full_ui.rs b/examples/full_ui.rs index 9eed459c4cd41a86d84a949fc6cc0acd2a0576ca..295d11bd0a8de0ffebc3b86606a5e04f4c1fec3d 100644 --- a/examples/full_ui.rs +++ b/examples/full_ui.rs @@ -9,7 +9,7 @@ use kayak_components::{NinePatch, Text}; use kayak_core::{ layout_cache::Space, styles::{LayoutType, Style, StyleProp, Units}, - widget, Children, EventType, Index, OnEvent, + widget, Bound, Children, EventType, Index, MutableBound, OnEvent, }; use kayak_ui::components::App; use kayak_ui::core::rsx; @@ -41,7 +41,7 @@ fn BlueButton(context: KayakContext, children: Children, styles: Option<Style>) (blue_button_handle, blue_button_hover_handle) }; - let current_button_handle = *context.create_state::<u16>(blue_button_handle).unwrap(); + let current_button_handle = context.create_state::<u16>(blue_button_handle).unwrap(); let button_styles = Style { width: StyleProp::Value(Units::Pixels(200.0)), @@ -53,15 +53,13 @@ fn BlueButton(context: KayakContext, children: Children, styles: Option<Style>) ..styles.clone().unwrap_or_default() }; - let button_id = self.get_id(); - let on_event = OnEvent::new(move |context, event| match event.event_type { + let cloned_current_button_handle = current_button_handle.clone(); + let on_event = OnEvent::new(move |_context, event| match event.event_type { EventType::MouseIn => { - context.set_current_id(button_id); - context.set_state::<u16>(blue_button_hover_handle); + cloned_current_button_handle.set(blue_button_hover_handle); } EventType::MouseOut => { - context.set_current_id(button_id); - context.set_state::<u16>(blue_button_handle); + cloned_current_button_handle.set(blue_button_handle); } _ => (), }); @@ -74,7 +72,7 @@ fn BlueButton(context: KayakContext, children: Children, styles: Option<Style>) top: 10.0, bottom: 10.0, }} - handle={current_button_handle} + handle={current_button_handle.get()} styles={Some(button_styles)} on_event={Some(on_event)} > diff --git a/kayak_core/Cargo.toml b/kayak_core/Cargo.toml index 1f1eb406add78c7fc40e27691abdd060d8716e45..10e71dafb74a2aea95e8a99e319082dd9625bddb 100644 --- a/kayak_core/Cargo.toml +++ b/kayak_core/Cargo.toml @@ -7,11 +7,11 @@ edition = "2021" [dependencies] as-any = "0.2" +dashmap = "4.0" diff-struct = "0.3" derivative = "2.2" +flo_binding = "2.0.1" fontdue = "0.6" - kayak_render_macros = { path = "../kayak_render_macros" } morphorm = { git = "https://github.com/geom3trik/morphorm" } -# dyn_partial_eq = "0.1" resources = "1.1" diff --git a/kayak_core/examples/test3.rs b/kayak_core/examples/test3.rs index 5907f5b84014c2d2c7b2f00e91a9837e9da10239..e88e0ef3f8ec06ac3cdeed3f4dfc1e7d681a5ae8 100644 --- a/kayak_core/examples/test3.rs +++ b/kayak_core/examples/test3.rs @@ -2,12 +2,12 @@ use kayak_core::color::Color; use kayak_core::context::KayakContext; use kayak_core::render_command::RenderCommand; use kayak_core::styles::{Style, StyleProp}; -use kayak_core::{rsx, widget, Children, Index}; +use kayak_core::{rsx, widget, Bound, Children, Index, MutableBound}; use morphorm::{PositionType, Units}; #[widget] fn MyWidget(context: &mut KayakContext, children: Children) { - let number = *context.create_state::<u32>(0).unwrap(); + let number = context.create_state::<u32>(0).unwrap(); let my_styles = Style { render_command: StyleProp::Value(RenderCommand::Quad), width: StyleProp::Value(Units::Pixels(300.0)), @@ -16,7 +16,7 @@ fn MyWidget(context: &mut KayakContext, children: Children) { ..Style::default() }; rsx! { - <MyWidget2 styles={Some(my_styles)} test={number}> + <MyWidget2 styles={Some(my_styles)} test={number.get()}> {children} </MyWidget2> } @@ -24,7 +24,7 @@ fn MyWidget(context: &mut KayakContext, children: Children) { #[widget] fn MyWidget2(test: u32, children: Children) { - let _test = test; + dbg!(test); rsx! { <> {children} @@ -52,7 +52,8 @@ fn main() { let mut my_widget = context.widget_manager.take(widget_id); my_widget.render(&mut context); context.set_current_id(widget_id); - context.set_state::<u32>(1); + let number = context.create_state::<u32>(0).unwrap(); + number.set(1); my_widget.render(&mut context); context.widget_manager.repossess(my_widget); diff --git a/kayak_core/src/binding.rs b/kayak_core/src/binding.rs new file mode 100644 index 0000000000000000000000000000000000000000..f1221a4063ad8cd6b9dbb60da25e1f54d1dba377 --- /dev/null +++ b/kayak_core/src/binding.rs @@ -0,0 +1 @@ +pub use flo_binding::{bind, notify, Binding, Bound, Changeable, MutableBound, Releasable}; diff --git a/kayak_core/src/context.rs b/kayak_core/src/context.rs index 895ea2aaea2b604e1a1830a1a3a8cb4e3604f55c..582f36514a408b6948cb191aee4ae87730ceb0a0 100644 --- a/kayak_core/src/context.rs +++ b/kayak_core/src/context.rs @@ -1,10 +1,11 @@ -use resources::Ref; +use flo_binding::Changeable; use std::collections::HashMap; use crate::{node::NodeIndex, widget_manager::WidgetManager, Event, EventType, Index, InputEvent}; pub struct KayakContext { component_states: HashMap<crate::Index, resources::Resources>, + // component_state_lifetimes: DashMap<crate::Index, Vec<Box<dyn crate::Releasable>>>, current_id: Index, pub widget_manager: WidgetManager, last_mouse_position: (f32, f32), @@ -24,6 +25,7 @@ impl KayakContext { pub fn new() -> Self { Self { component_states: HashMap::new(), + // component_state_lifetimes: DashMap::new(), current_id: crate::Index::default(), widget_manager: WidgetManager::new(), last_mouse_position: (0.0, 0.0), @@ -36,50 +38,94 @@ impl KayakContext { self.current_id = id; } - pub fn create_state<T: resources::Resource + Clone>( + pub fn create_state<T: resources::Resource + Clone + PartialEq>( &mut self, initial_state: T, - ) -> Option<Ref<T>> { + ) -> Option<crate::Binding<T>> { if self.component_states.contains_key(&self.current_id) { let states = self.component_states.get_mut(&self.current_id).unwrap(); - if !states.contains::<T>() { - states.insert(initial_state); + if !states.contains::<crate::Binding<T>>() { + let state = crate::bind(initial_state); + let dirty_nodes = self.widget_manager.dirty_nodes.clone(); + let cloned_id = self.current_id; + let mut lifetime = state.when_changed(crate::notify(move || { + if let Ok(mut dirty_nodes) = dirty_nodes.lock() { + dirty_nodes.insert(cloned_id); + } + })); + lifetime.keep_alive(); + // Self::insert_state_lifetime( + // &mut self.component_state_lifetimes, + // self.current_id, + // lifetime, + // ); + states.insert(state); } } else { let mut states = resources::Resources::default(); - states.insert(initial_state); + let state = crate::bind(initial_state); + let dirty_nodes = self.widget_manager.dirty_nodes.clone(); + let cloned_id = self.current_id; + let mut lifetime = state.when_changed(crate::notify(move || { + if let Ok(mut dirty_nodes) = dirty_nodes.lock() { + dirty_nodes.insert(cloned_id); + } + })); + lifetime.keep_alive(); + // Self::insert_state_lifetime( + // &mut self.component_state_lifetimes, + // self.current_id, + // lifetime, + // ); + states.insert(state); self.component_states.insert(self.current_id, states); } return self.get_state(); } - fn get_state<T: resources::Resource + Clone>(&self) -> Option<Ref<T>> { + // fn insert_state_lifetime( + // lifetimes: &mut DashMap<crate::Index, Vec<Box<dyn crate::Releasable>>>, + // id: Index, + // lifetime: Box<dyn crate::Releasable>, + // ) { + // if lifetimes.contains_key(&id) { + // if let Some(mut lifetimes) = lifetimes.get_mut(&id) { + // lifetimes.push(lifetime); + // } + // } else { + // lifetimes.insert(id, vec![lifetime]); + // } + // } + + fn get_state<T: resources::Resource + Clone + PartialEq>(&self) -> Option<T> { if self.component_states.contains_key(&self.current_id) { let states = self.component_states.get(&self.current_id).unwrap(); if let Ok(state) = states.get::<T>() { - return Some(state); + return Some(state.clone()); } } return None; } - pub fn set_state<T: resources::Resource + Clone>(&mut self, state: T) { - if self.component_states.contains_key(&self.current_id) { - let states = self.component_states.get(&self.current_id).unwrap(); - if states.contains::<T>() { - let mut mutate_t = states.get_mut::<T>().unwrap(); - self.widget_manager.dirty_nodes.insert(self.current_id); - *mutate_t = state; - } else { - panic!( - "No specific state created for component with id: {:?}!", - self.current_id - ); - } - } else { - // Do nothing.. - } - } + // pub fn set_state<T: resources::Resource + Clone>(&mut self, state: T) { + // if self.component_states.contains_key(&self.current_id) { + // let states = self.component_states.get(&self.current_id).unwrap(); + // if states.contains::<T>() { + // let mut mutate_t = states.get_mut::<T>().unwrap(); + // if let Ok(mut dirty_nodes) = self.widget_manager.dirty_nodes.lock() { + // dirty_nodes.insert(self.current_id); + // } + // *mutate_t = state; + // } else { + // panic!( + // "No specific state created for component with id: {:?}!", + // self.current_id + // ); + // } + // } else { + // // Do nothing.. + // } + // } pub fn set_global_state<T: resources::Resource>(&mut self, state: T) { self.global_state.insert(state); @@ -96,7 +142,12 @@ impl KayakContext { } pub fn render(&mut self) { - let dirty_nodes: Vec<_> = self.widget_manager.dirty_nodes.drain().collect(); + let dirty_nodes: Vec<_> = + if let Ok(mut dirty_nodes) = self.widget_manager.dirty_nodes.lock() { + dirty_nodes.drain().collect() + } else { + panic!("Couldn't get lock on dirty nodes!") + }; for node_index in dirty_nodes { // if self // .widget_manager @@ -104,9 +155,9 @@ impl KayakContext { // .iter() // .any(|dirty_index| node_index == *dirty_index) // { - let mut widget = self.widget_manager.take(node_index); - widget.render(self); - self.widget_manager.repossess(widget); + let mut widget = self.widget_manager.take(node_index); + widget.render(self); + self.widget_manager.repossess(widget); // } } diff --git a/kayak_core/src/lib.rs b/kayak_core/src/lib.rs index 43a1b59fab9e97c0e3a11bdef34d3f84f71e57d7..d5735a447e872319ed76ebca06517e6dd0b51391 100644 --- a/kayak_core/src/lib.rs +++ b/kayak_core/src/lib.rs @@ -1,3 +1,4 @@ +mod binding; pub mod color; pub mod context; pub mod event; @@ -15,6 +16,7 @@ pub mod widget_manager; use std::sync::{Arc, RwLock}; +pub use binding::*; pub use context::*; pub use event::*; pub use fragment::Fragment; diff --git a/kayak_core/src/widget_manager.rs b/kayak_core/src/widget_manager.rs index cee0ceafc7582ec01acad79e677c17563c93498b..02e73407d432688d159ec11a283e16d060b58bbc 100644 --- a/kayak_core/src/widget_manager.rs +++ b/kayak_core/src/widget_manager.rs @@ -1,4 +1,7 @@ -use std::collections::HashSet; +use std::{ + collections::HashSet, + sync::{Arc, Mutex}, +}; use crate::{ layout_cache::LayoutCache, @@ -15,7 +18,7 @@ use crate::{ pub struct WidgetManager { pub(crate) current_widgets: Arena<Option<Box<dyn Widget>>>, pub(crate) dirty_render_nodes: Vec<Index>, - pub(crate) dirty_nodes: HashSet<Index>, + pub(crate) dirty_nodes: Arc<Mutex<HashSet<Index>>>, pub(crate) nodes: Arena<Option<Node>>, pub tree: Tree, pub node_tree: Tree, @@ -28,7 +31,7 @@ impl WidgetManager { Self { current_widgets: Arena::new(), dirty_render_nodes: Vec::new(), - dirty_nodes: HashSet::new(), + dirty_nodes: Arc::new(Mutex::new(HashSet::new())), nodes: Arena::new(), tree: Tree::default(), node_tree: Tree::default(), @@ -42,12 +45,14 @@ impl WidgetManager { /// Can be slow. pub fn dirty(&mut self, force: bool) { // Force tree to re-render from root. - self.dirty_nodes.insert(self.tree.root_node); + if let Ok(mut dirty_nodes) = self.dirty_nodes.lock() { + dirty_nodes.insert(self.tree.root_node); - if force { - for (node_index, _) in self.current_widgets.iter() { - self.dirty_nodes.insert(node_index); - self.dirty_render_nodes.push(node_index); + if force { + for (node_index, _) in self.current_widgets.iter() { + dirty_nodes.insert(node_index); + self.dirty_render_nodes.push(node_index); + } } } } diff --git a/kayak_core/tests/mod.rs b/kayak_core/tests/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..6b9848f243c01a755677df80fc6465dda4f3d9ca --- /dev/null +++ b/kayak_core/tests/mod.rs @@ -0,0 +1,31 @@ +#[test] +fn flo_binding_test() { + use flo_binding::{Binding, Bound, Changeable, MutableBound}; + + #[derive(Clone, PartialEq)] + struct TestState { + pub value: u32, + } + + let mut some_state = resources::Resources::default(); + + let test_state = flo_binding::bind(TestState { value: 0 }); + + let mut lifetime = test_state.when_changed(flo_binding::notify(|| { + dbg!("Changed!"); + })); + + some_state.insert(test_state); + + if let Ok(test_state) = some_state.get::<Binding<TestState>>() { + assert!(test_state.get().value == 0); + + test_state.set(TestState { value: 2 }); + + assert!(test_state.get().value == 2); + + lifetime.done(); + }; + + panic!(); +}