Skip to content

Commit

Permalink
wip: hydrator
Browse files Browse the repository at this point in the history
  • Loading branch information
mrchantey committed Jan 17, 2025
1 parent 0ef4b3b commit 5c4fb12
Show file tree
Hide file tree
Showing 23 changed files with 226 additions and 27 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/sweet_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ flume.workspace = true
strum.workspace = true
strum_macros.workspace = true


once_cell.workspace = true
bincode = { version = "1", optional = true }
quote = { workspace = true, optional = true }
proc-macro2 = { workspace = true, optional = true }
Expand Down
4 changes: 3 additions & 1 deletion crates/sweet_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@

pub mod error;
// nice and handy for commonly used tasks
pub mod signal;
pub mod tree;
pub mod utils;
pub use utils::log;
pub use utils::log::*;
pub use utils::sleep::*;
pub mod rsx;

#[rustfmt::skip]
pub mod prelude {
pub use crate::error::*;
pub use crate::signal::*;
pub use crate::tree::*;
pub use crate::utils::log::*;
pub use crate::utils::sleep::*;
Expand Down
2 changes: 0 additions & 2 deletions crates/sweet_core/src/rsx/rust_parts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ use strum_macros::AsRefStr;
/// The rusty parts of a rsx tree.
#[derive(AsRefStr)]
pub enum RustParts {
/// Any element containing rust needs a node id
DynNodeId,
/// **rust**
/// ie `<div>{value}</div>`
TextBlock(String),
Expand Down
84 changes: 84 additions & 0 deletions crates/sweet_core/src/signal/channel_signal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#![allow(unused)]
use super::SignalContext;
use crate::tree::TreePosition;
use std::any::Any;
use std::sync::Arc;
use std::sync::RwLock;

type Message = (usize, Box<dyn Any + Send>);

struct ChannelSignal {
send: flume::Sender<Message>,
receive: flume::Receiver<Message>,
effects: Vec<Box<dyn Fn() + Send + Sync>>,
}


static CTX: once_cell::sync::Lazy<Arc<RwLock<ChannelSignal>>> =
once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(ChannelSignal::new())));

impl ChannelSignal {
pub fn new() -> Self {
let (send, receive) = flume::unbounded();
Self {
send,
receive,
effects: Vec::new(),
}
}

pub fn set(self) {
let (send, receive) =
flume::unbounded::<(TreePosition, Box<dyn Any + Send>)>();

// *CTX.write().unwrap() = self;
SignalContext {
create_signal: Box::new(|_, _| {
panic!("signal context has not been set, please call SignalContext::set(my_cx)")
}),
create_effect: Box::new(|_, _| {
panic!("signal context has not been set, please call SignalContext::set(my_cx)")
}),
}.set();
}

pub fn signal<T: Clone>(initial: T) -> (impl Fn() -> T, impl Fn(T)) {
let val = Arc::new(RwLock::new(initial));
let val2 = val.clone();
let read = move || val.read().unwrap().clone();
let write = move |new: T| {
let mut val = val2.write().unwrap();
*val = new;
};
(read, write)
}
}

pub fn signal<T: Clone>(initial: T) -> (impl Fn() -> T, impl Fn(T)) {
let val = Arc::new(RwLock::new(initial));
let val2 = val.clone();
let read = move || val.read().unwrap().clone();
let write = move |new: T| {
let mut val = val2.write().unwrap();
*val = new;
};
(read, write)
}

pub fn effect<T: Clone>(func: impl Fn(T)) {}



#[cfg(test)]
mod test {
use crate::prelude::*;
use channel_signal::*;

#[test]
fn works() {
let (read, write) = signal(0);
expect(read()).to_be(0);
write(1);
expect(read()).to_be(1);
}
}
55 changes: 53 additions & 2 deletions crates/sweet_core/src/signal/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,60 @@
use crate::tree::TreePosition;
use std::any::Any;
use std::sync::Arc;
use std::sync::RwLock;

pub mod channel_signal;

/// Sweet does not have a reactive system,
/// and instead provides an interface for integrations with reactive libraries.
///
/// ## Reference
/// - [solid - building a reactive library from scratch](https://dev.to/ryansolid/building-a-reactive-library-from-scratch-1i0p)
/// - [leptos - reactive graph](https://book.leptos.dev/appendix_reactive_graph.html)
/// - [reactively - blog post](https://dev.to/milomg/super-charging-fine-grained-reactive-performance-47ph)
// pub trait SignalContext {
// fn register_setter(func: Box<dyn Fn(&mut Self)>);

pub trait Signal{
// fn register_getter(func: Box<dyn Fn(&mut Self)>);
// }

/// A function that registers a signal.
/// Input values are
/// - the position in the tree
/// - the initial value
type CreateSignal = Box<dyn Send + Sync + Fn(TreePosition, Box<dyn Any>)>;
type CreateEffect = Box<dyn Send + Sync + Fn(TreePosition, Box<dyn Fn()>)>;


}
pub type SignalGetter = Box<dyn Fn() -> Box<dyn Any> + Send + Sync>;
pub type SignalSetter = Box<dyn Fn(Box<dyn Any>) + Send + Sync>;

pub struct SignalContext {
pub create_signal: CreateSignal,
pub create_effect: CreateEffect,
}

impl Default for SignalContext {
fn default() -> Self {
SignalContext {
create_signal: Box::new(|_, _| {
panic!("signal context has not been set, please call SignalContext::set(my_cx)")
}),
create_effect: Box::new(|_, _| {
panic!("signal context has not been set, please call SignalContext::set(my_cx)")
}),
}
}
}

impl SignalContext {
/// store this context in the global context, replacing the previous one
pub fn set(self) { *CTX.write().unwrap() = self; }
}


static CTX: once_cell::sync::Lazy<Arc<RwLock<SignalContext>>> =
once_cell::sync::Lazy::new(|| {
Arc::new(RwLock::new(SignalContext::default()))
});
3 changes: 2 additions & 1 deletion crates/sweet_leptos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ keywords.workspace = true


[dependencies]
reactive_graph = { version = "0.1", features = ["effects"] }
reactive_graph = { version = "0.1", features = ["nightly", "effects"] }
thiserror.workspace = true
anyhow.workspace = true
futures.workspace = true

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio.workspace = true
Expand Down
51 changes: 30 additions & 21 deletions crates/sweet_leptos/examples/reactive_graph.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,45 @@
use reactive_graph::computed::ArcMemo;
use any_spawner::Executor;
use reactive_graph::effect::Effect;
use reactive_graph::owner::Owner;
use reactive_graph::prelude::Set;
use reactive_graph::prelude::*;
use reactive_graph::signal::ArcRwSignal;
use any_spawner::Executor;
use reactive_graph::signal::RwSignal;
use std::sync::Arc;
use std::sync::RwLock;
use tokio::task;


// execution is super flaky, not sure whats going on
// maybe different tokio versions?
#[tokio::main]
async fn main() {
let _ = Executor::init_tokio();
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
Executor::tick().await;

task::LocalSet::new()
.run_until(async {
let a = RwSignal::new(-1);

// simulate an arbitrary side effect
let b = Arc::new(RwLock::new(String::new()));

let count = ArcRwSignal::new(1);
let double_count = ArcMemo::new({
let count = count.clone();
move |_| *count.read() * 2
});
let double_count2 = double_count.clone();
Effect::new({
let b = b.clone();
move || {
let formatted = format!("Value is {}", a.get());
*b.write().unwrap() = formatted;
}
});

// the effect will run once initially
Effect::new(move |_| {
println!("double_count = {}", *double_count.read());
});
Executor::tick().await;
assert_eq!(b.read().unwrap().as_str(), "Value is -1");

// updating `count` will propagate changes to the dependencies,
// causing the effect to run again
println!("setting to 1");
a.set(1);

count.set(2);
assert_eq!(*double_count2.read(), 4);
// success but effect didnt run
Executor::tick().await;
assert_eq!(b.read().unwrap().as_str(), "Value is 1");
println!("ok");
})
.await
}
File renamed without changes.
18 changes: 18 additions & 0 deletions crates/sweet_site/src/content/docs/bevy/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: Bevy

---

Sweet uses terminology from the web but the structural concepts map directly to bevy.

| Web | Bevy |
| ------------------ | ------------ |
| Component | Scene |
| Parent/Child | Parent/Child |
| Element | Entity |
| Attribute | Component |
| Signal | Observer |
| Event | Trigger |
| Hydrate | Run App |
| Render to DOM | Spawn scene |
| Renderer to String | Serialize |
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,32 @@ title: How it works
---


```rust
trait RsxParser{
fn map_event(el: &RsxElement, key:&str){

}
}


Tree<RsxNode<BevyHydrate>>
Tree<SerdeRsxNode<BevyHydrate>>
```

```mermaid
graph TD
compiler -- Token Stream --- b[Rstml Parser]
b -- Rstml Tree --- c[BevyHydrate: RsxParser]
c -- Bevy Rsx Nodes --- d[Splitter]
d -- Bevy Serde Rsx Nodes --- e[File System]
e -- Bevy Serde Rsx Nodes --- f[Dom Renderer]
f -- DOM --- g[Differ & Hydrator]
```





```mermaid
graph TD
subgraph Sweet
Expand Down
File renamed without changes.
File renamed without changes.
6 changes: 6 additions & 0 deletions crates/sweet_site/src/content/docs/web/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Web

---


File renamed without changes.

0 comments on commit 5c4fb12

Please sign in to comment.