Skip to content

Commit 567f6a0

Browse files
committed
process render queue and setup data observers
This temporarily regresses data binding, since the downcasting doesn't quite work for downcasting into a trait object when we stored the actual object instead Will look into using MOPA (my own personal any)
1 parent 5b61d89 commit 567f6a0

File tree

4 files changed

+111
-28
lines changed

4 files changed

+111
-28
lines changed

README.md

+33-13
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,23 @@ Oh, and black hole's form from the collapse of a core of iron.. you know, the on
1414

1515
---
1616

17-
## How it works
17+
Everything is experimental, half-baked, full of caveats, and subject to change. But some basic principles are beginning to emerge. With Quasar...
18+
- **all your app and component state are statically typed in data structures of your choosing**
19+
- **updating state in your application automatically updates views** (unless you update your state via interior mutability)
1820

19-
Everything is experimental, half-baked, full of caveats, and subject to change. But currently:
21+
## How it works
2022

2123
- **Template engines** are swappable. There are [`examples`](https://github.com/anowell/quasar/tree/master/examples) using [mustache](https://crates.io/crates/mustache)(default) and [maud](https://crates.io/crates/maud). But replacing the template engine is just a matter of implementing the `Renderable` trait.
22-
- **Components** are the combination of data with a template or other rendering process - really anything that implements `Renderable`. Quasar takes ownership of your components when binding them to the DOM and makes the data available to your event handlers via `data()` and `data_mut()` methods. In general, methods that mutate the component will result in re-rendering it (TBD: at the end of the event handler or at next tick). Note, component data is local to the component and not shareable outside your component.
24+
- **Components** are the combination of data with a template or other rendering process - really anything that implements `Renderable`. Quasar takes ownership of your components when binding them to the DOM and makes the data available to your event handlers via `data()` and `data_mut()` methods. In general, methods that mutate the component will result in re-rendering it at the end of the event handler. Note, component data is local to the component and not shareable outside your component.
2325
- **Views** are the result of one-way binding of a component to the DOM. You can also attach event listeners to views. Note, that currently rendering a view uses the destructive `innerHtml = ...` approach, which kills DOM state like input focus, so eventually some sort of DOM diffing/patching or virtual DOM solution will become pretty important.
24-
- **State** or the shared app data is also available to event handlers. It is partitioned by a key (and by `TypeId`), and any attempt to read a shared data partition (calling `data(key)`) automatically registeres your view as an observer of that data partion (to-be implemented). Any attempt to write to an app data partition (calling `data_mut(key)`) will automatically add all observer views for that data partition to the re-render queue (TBD: processed at the end of the event handler or at the next tick).
25-
26-
A couple basic principles are beginning to emerge. With Quasar...
27-
- **it should be impossible in safe Rust to update state in your application without also updating views.**
28-
- **all your app and component state are statically typed in data structures of your choosing**
26+
- **App Data** is shared state that is also available to event handlers. It is partitioned by a key (and by `TypeId`), and any attempt to read a shared data partition (calling `data(key)`) automatically registers your view as an observer of that data partion. Any attempt to write to an app data partition (calling `data_mut(key)`) will automatically add all observer views for that data partition to the re-render queue process at the end of the event handler.
2927

3028
A basic example might include an HTML file like this:
3129

3230
```html
3331
<html>
3432
<body>
35-
<Reverser name="Malcom Reynolds"></Reverser>
36-
<Reverser name="Shepherd Book"></Reverser>
33+
<div id="counter"></div>
3734
</body>
3835
</html>
3936
```
@@ -66,13 +63,36 @@ fn main() {
6663

6764
See the [`examples`](https://github.com/anowell/quasar/tree/master/examples) directory to get a sense of how it works today.
6865

69-
## What's next?
66+
## Goals
7067

71-
I'm still working to better understand what works and what's missing in [webplatform](https://github.com/tcr/rust-webplatform).
72-
Here are some overarching questions that are guiding this experimentation right now:
68+
Quasar is still exploring some ideas and working to better understand what works and what's missing in [webplatform](https://github.com/tcr/rust-webplatform). Here are some overarching questions that are guiding this experimentation right now:
7369

7470
- Can Quasar achieve a level of abstractions that feel comparable to modern Javascript frameworks? (I believe some macros could allow it to rival the declarative syntax of some other frameworks.)
7571
- What might it look like to have "isomorphic" rust, where the same rendering code can run both client and server side?
7672
- How can I leverage the type system to achieve more flexible and/or more robust frontend development? (e.g. trait-based templating, leveraging immutable vs mutable access as a gate for identifying views that observer or mutate specific data.)
7773

7874
Admittedly Quasar is absent any perf goals at this time. Quasar also lacks a clear vision for why Quasar would be "better than X", so I'll probably ask myself "what problem is Quasar really solving?" multiple times throughout this experimentation.
75+
76+
## Building
77+
78+
You'll need emscripten setup and activated (see [brson's post](https://users.rust-lang.org/t/compiling-to-the-web-with-rust-and-emscripten/7627)), and then to add a couple compilation targets:
79+
80+
```bash
81+
rustup target add asmjs-unknown-emscripten
82+
rustup target add wasm32-unknown-emscripten
83+
```
84+
85+
Then `bin/build-asm` wraps cargo to simplify much of the build process
86+
87+
```bash
88+
bin/build-asm # build quasar
89+
bin/build-asm reverser # build quasar and the reverser example (output copied to `static/`)
90+
bin/build-asm app # build quasar and examples/app (output copied to `exaples/app/static/`)
91+
bin/build all # build everyting
92+
```
93+
94+
For any of the built examples, you can serve the result with any simple server, e.g. with the official nginx docker image:
95+
96+
```bash
97+
docker run --rm -it -v `pwd`/static:/usr/share/nginx/html -p 8081:80 nginx
98+
```

bin/build-asm

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ shift || true
1313

1414
echo "Building Quasar"
1515
cargo build --target=asmjs-unknown-emscripten $@
16+
test -n "$item" || exit 0
1617

1718
if [[ "$item" = "all" ]]; then
1819
echo "Building examples"

src/events.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
use ::{Element, View, QuasarApp, Renderable};
2-
use std::cell::RefCell;
3-
use std::rc::Rc;
1+
use ::{Element, View, AppContext};
42

53
#[derive(Clone)]
64
pub enum EventType {
@@ -38,8 +36,7 @@ impl EventType {
3836
}
3937

4038
pub struct Event<'a, 'b, 'c, R> {
41-
pub app: QuasarApp<'a>,
39+
pub app: AppContext<'a>,
4240
pub target: Element<'b>,
4341
pub view: View<'c, R>,
4442
}
45-

src/lib.rs

+75-10
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,44 @@ pub fn init<'a, 'doc: 'a>() -> QuasarApp<'a> {
3030
document: Rc::new(webplatform::init()),
3131
components: Rc::new(RefCell::new(HashMap::new())),
3232
state: Rc::new(RefCell::new(HashMap::new())),
33+
observers: Rc::new(RefCell::new(HashMap::new())),
3334
render_queue: Rc::new(RefCell::new(Vec::new())),
3435
}
3536
}
3637

38+
type DataStore = HashMap<TypedKey, Box<Any>>;
39+
type ObserverStore<'doc> = HashMap<TypedKey, Vec<Rc<HtmlNode<'doc>>>>;
40+
type RenderQueue<'doc> = Vec<(TypedKey, Rc<HtmlNode<'doc>>)>;
41+
3742
// TODO: revisit cloning of main app..
3843
// it feels very strange that QuasarApp is basically an `Rc` type
3944
// but it's non-trivial to pass around &QuasarApp since events need access
4045
// and almost certainly outlive the app instance if not for all the Rc members
4146
#[derive(Clone)]
4247
pub struct QuasarApp<'doc> {
4348
document: Rc<Document<'doc>>,
44-
components: Rc<RefCell<HashMap<TypedKey, Box<Any>>>>,
45-
state: Rc<RefCell<HashMap<TypedKey, Box<Any>>>>,
46-
render_queue: Rc<RefCell<Vec<(TypedKey, Rc<HtmlNode<'doc>>)>>>,
49+
components: Rc<RefCell<DataStore>>,
50+
state: Rc<RefCell<DataStore>>,
51+
observers: Rc<RefCell<ObserverStore<'doc>>>,
52+
render_queue: Rc<RefCell<RenderQueue<'doc>>>,
53+
}
54+
55+
pub struct AppContext<'doc> {
56+
app: QuasarApp<'doc>,
57+
view_id: TypedKey,
58+
node: Rc<HtmlNode<'doc>>,
59+
}
60+
61+
impl <'doc> AppContext<'doc> {
62+
pub fn data<T: 'static>(&self, key: &str) -> DataRef<T> {
63+
self.app.add_observer(self.view_id.clone(), self.node.clone());
64+
self.app.data(key)
65+
}
66+
67+
pub fn data_mut<T: 'static>(&mut self, key: &str) -> DataMutRef<T> {
68+
self.app.add_observer(self.view_id.clone(), self.node.clone());
69+
self.app.data_mut(key)
70+
}
4771
}
4872

4973
type DataRef<'a, T> = RefRef<'a, HashMap<TypedKey, Box<Any>>, T>;
@@ -82,7 +106,6 @@ impl <'doc> QuasarApp<'doc> {
82106
views.push(view);
83107
}
84108
Views {
85-
app: self.clone(),
86109
views: Rc::new(views),
87110
handlers: Rc::new(RefCell::new(Vec::new())),
88111
}
@@ -109,18 +132,30 @@ impl <'doc> QuasarApp<'doc> {
109132
pub fn data<T: 'static>(&self, key: &str) -> DataRef<T> {
110133
let data_id = TypedKey::new::<T>(key);
111134
RefRef::new(self.state.borrow()).map(|state| {
135+
println!("FOOFOO1");
112136
let entry = state.get(&data_id).unwrap();
113137
entry.downcast_ref().unwrap()
114138
})
115139
}
116140

117141
pub fn data_mut<T: 'static>(&self, key: &str) -> DataMutRef<T> {
118-
// TODO: look up observer views, and add enqueue for re-render
142+
// Look up observers, and enqueue them for re-render
119143
let data_id = TypedKey::new::<T>(key);
144+
{
145+
let observers = self.observers.borrow();
146+
if let Some(partition_observers) = observers.get(&data_id) {
147+
let mut queue = self.render_queue.borrow_mut();
148+
for observer in partition_observers {
149+
queue.push((data_id.clone(), observer.clone()));
150+
}
151+
}
152+
}
153+
120154
RefMutRef::new(self.state.borrow_mut()).map(|state| {
121155
// TODO: Look into getting an `OwnedMutRef` that supports `map_mut`
122156
let state = state as *const HashMap<_, _> as *mut HashMap<TypedKey, Box<Any>>;
123157
let mut state = unsafe { &mut *state };
158+
println!("FOOFOO2");
124159
let mut entry = state.get_mut(&data_id).unwrap();
125160
entry.downcast_mut().unwrap()
126161
})
@@ -133,22 +168,42 @@ impl <'doc> QuasarApp<'doc> {
133168
queue.push((view_id, view.node.clone()));
134169
}
135170

171+
fn add_observer(&self, view_id: TypedKey, node: Rc<HtmlNode<'doc>>) {
172+
let mut observers = self.observers.borrow_mut();
173+
let mut partition = observers.entry(view_id).or_insert_with(|| Vec::new());
174+
partition.push(node);
175+
}
176+
177+
fn process_render_queue(&self) {
178+
let mut queue = self.render_queue.borrow_mut();
179+
for item in queue.iter() {
180+
let (ref view_id, ref node) = *item;
181+
let components = self.components.borrow();
182+
let entry = components.get(&view_id).unwrap();
183+
println!("FIXME/REGRESSION: downcasting to Box<Renderable> doesn't work.");
184+
let component: &Rc<RefCell<Renderable>> = entry.downcast_ref().unwrap();
185+
let component = component.borrow();
186+
let props = lookup_props(&node, component.props());
187+
node.html_set(&component.render(props));
188+
}
189+
queue.clear();
190+
}
191+
136192
pub fn spin(self) {
137193
webplatform::spin();
138194
}
139195
}
140196

141197
/// A collection of `View`s returned from a query selector
142198
pub struct Views<'doc, R> {
143-
app: QuasarApp<'doc>,
144199
pub views: Rc<Vec<View<'doc, R>>>,
145200
// Views may have multiple handlers, hence Vec
146201
// We want interior mutability, hence RefCell
147202
// A handler may map to multiple views
148203
handlers: Rc<RefCell<Vec<Box<Fn(Event<R>) + 'doc>>>>,
149204
}
150205

151-
impl <'doc, R: Renderable + 'doc> Views<'doc, R> {
206+
impl <'doc, R: Renderable + 'static> Views<'doc, R> {
152207
pub fn on<F>(&self, event: EventType, f: F) where F: Fn(Event<R>) + 'doc {
153208
// Insert the handler into self and return it's index
154209
let offset = {
@@ -182,7 +237,11 @@ impl <'doc, R: Renderable + 'doc> Views<'doc, R> {
182237
println!("Event fired on {:?} for target {:?}", &view.node, evt.target);
183238
{
184239
let event = Event {
185-
app: app.clone(),
240+
app: AppContext {
241+
app: app.clone(),
242+
view_id: TypedKey::new::<R>(&el),
243+
node: node.clone(),
244+
},
186245
target: Element {
187246
node: evt.target.expect("Event did not have a target") ,
188247
},
@@ -200,12 +259,13 @@ impl <'doc, R: Renderable + 'doc> Views<'doc, R> {
200259
let node = v.node.clone();
201260
let component = v.component.clone();
202261
let rendered = {
203-
let mut component = component.borrow_mut();
262+
let component = component.borrow();
204263
let props = lookup_props(&node, component.props());
205264
component.render(props)
206265
};
207266
node.html_set(&rendered);
208267
}
268+
app.process_render_queue()
209269
});
210270
}
211271
println!("{} On handlers registered", self.views.len());
@@ -254,7 +314,11 @@ impl <'doc, R: 'static + Renderable> View<'doc, R> {
254314
{
255315
let target_node = evt.target.expect("Event did not have a target");
256316
let event = Event {
257-
app: app.clone(),
317+
app: AppContext {
318+
app: app.clone(),
319+
view_id: TypedKey::new::<R>(&el),
320+
node: node.clone(),
321+
},
258322
target: Element { node: target_node },
259323
view: view,
260324
};
@@ -265,6 +329,7 @@ impl <'doc, R: 'static + Renderable> View<'doc, R> {
265329
component.render(props)
266330
};
267331
node.html_set(&rendered);
332+
app.process_render_queue();
268333
});
269334
println!("On handler registered");
270335
}

0 commit comments

Comments
 (0)