Phase 3.1-3.3: ReactiveTracker with ElementId assignment during parsing, dependency scanning, variable change notification in app.rs
This commit is contained in:
19
src/app.rs
19
src/app.rs
@@ -19,23 +19,36 @@ impl GlintApp {
|
|||||||
pub fn update(&mut self, message: Message) -> iced::Task<Message> {
|
pub fn update(&mut self, message: Message) -> iced::Task<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::WindowScrolled(y) => {
|
Message::WindowScrolled(y) => {
|
||||||
self.doc.variables.insert("__scroll_y".to_string(), Value::Float(y as f64));
|
let var = "__scroll_y".to_string();
|
||||||
|
self.doc.variables.insert(var.clone(), Value::Float(y as f64));
|
||||||
|
self.doc.tracker.on_variable_changed(&var);
|
||||||
}
|
}
|
||||||
Message::EventTriggered(script) => {
|
Message::EventTriggered(script) => {
|
||||||
if !script.is_empty() {
|
if !script.is_empty() {
|
||||||
|
let old_vars: Vec<String> = self.doc.variables.keys().cloned().collect();
|
||||||
self.rhei.execute_action(&script, &mut self.doc.variables);
|
self.rhei.execute_action(&script, &mut self.doc.variables);
|
||||||
|
let new_vars: Vec<String> = self.doc.variables.keys().cloned().collect();
|
||||||
|
for v in &new_vars {
|
||||||
|
if !old_vars.contains(v) || self.doc.variables.get(v) != old_vars.iter().find_map(|k| if k == v { self.doc.variables.get(k) } else { None }) {
|
||||||
|
self.doc.tracker.on_variable_changed(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::InputChanged(Some(var), val) => {
|
Message::InputChanged(Some(var), val) => {
|
||||||
self.doc.variables.insert(var, Value::from(val));
|
self.doc.variables.insert(var.clone(), Value::from(val));
|
||||||
|
self.doc.tracker.on_variable_changed(&var);
|
||||||
}
|
}
|
||||||
Message::ToggleChanged(Some(var), val) => {
|
Message::ToggleChanged(Some(var), val) => {
|
||||||
self.doc.variables.insert(var, Value::Bool(val));
|
self.doc.variables.insert(var.clone(), Value::Bool(val));
|
||||||
|
self.doc.tracker.on_variable_changed(&var);
|
||||||
}
|
}
|
||||||
Message::SliderChanged(Some(var), val) => {
|
Message::SliderChanged(Some(var), val) => {
|
||||||
self.doc.variables.insert(var.clone(), Value::Float(val));
|
self.doc.variables.insert(var.clone(), Value::Float(val));
|
||||||
|
self.doc.tracker.on_variable_changed(&var);
|
||||||
if var == "volume_level" {
|
if var == "volume_level" {
|
||||||
self.doc.variables.insert("age".to_string(), Value::Float(val));
|
self.doc.variables.insert("age".to_string(), Value::Float(val));
|
||||||
|
self.doc.tracker.on_variable_changed("age");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
pub mod opcodes;
|
pub mod opcodes;
|
||||||
|
pub mod reactive;
|
||||||
pub mod reader;
|
pub mod reader;
|
||||||
pub mod rhei;
|
pub mod rhei;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
@@ -9,6 +10,7 @@ use std::borrow::Cow;
|
|||||||
pub use rhei::RheiContext;
|
pub use rhei::RheiContext;
|
||||||
use style::StyleSheet as SS;
|
use style::StyleSheet as SS;
|
||||||
pub use types::{ComponentDef, Document, Element, Interner, InterpError};
|
pub use types::{ComponentDef, Document, Element, Interner, InterpError};
|
||||||
|
pub use reactive::{ElementId, ReactiveTracker};
|
||||||
|
|
||||||
use compact_str::CompactString;
|
use compact_str::CompactString;
|
||||||
use opcodes::*;
|
use opcodes::*;
|
||||||
@@ -31,6 +33,7 @@ impl Interpreter {
|
|||||||
let mut components = HashMap::new();
|
let mut components = HashMap::new();
|
||||||
let mut rhei_scripts: Vec<String> = Vec::new();
|
let mut rhei_scripts: Vec<String> = Vec::new();
|
||||||
let mut stylesheet = SS::new();
|
let mut stylesheet = SS::new();
|
||||||
|
let mut tracker = ReactiveTracker::new();
|
||||||
|
|
||||||
let roots = Self::parse_block_elements(
|
let roots = Self::parse_block_elements(
|
||||||
&mut r,
|
&mut r,
|
||||||
@@ -38,13 +41,14 @@ impl Interpreter {
|
|||||||
&mut components,
|
&mut components,
|
||||||
&mut rhei_scripts,
|
&mut rhei_scripts,
|
||||||
&mut stylesheet,
|
&mut stylesheet,
|
||||||
|
&mut tracker,
|
||||||
true,
|
true,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
//let rhei_ctx = RheiContext::new(&rhei_scripts);
|
//let rhei_ctx = RheiContext::new(&rhei_scripts);
|
||||||
//rhei_ctx.initialize(&mut variables);
|
//rhei_ctx.initialize(&mut variables);
|
||||||
|
|
||||||
Ok(Document { roots, components, variables, rhei_scripts, stylesheet, interner: Interner::new() })
|
Ok(Document { roots, components, variables, rhei_scripts, stylesheet, interner: Interner::new(), tracker })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_block_elements<'a>(
|
fn parse_block_elements<'a>(
|
||||||
@@ -53,6 +57,7 @@ impl Interpreter {
|
|||||||
components: &mut HashMap<String, ComponentDef<'a>>,
|
components: &mut HashMap<String, ComponentDef<'a>>,
|
||||||
rhei_scripts: &mut Vec<String>,
|
rhei_scripts: &mut Vec<String>,
|
||||||
stylesheet: &mut SS,
|
stylesheet: &mut SS,
|
||||||
|
tracker: &mut ReactiveTracker,
|
||||||
is_root: bool,
|
is_root: bool,
|
||||||
) -> Result<Vec<Element<'a>>, InterpError> {
|
) -> Result<Vec<Element<'a>>, InterpError> {
|
||||||
let mut roots: Vec<Element> = Vec::new();
|
let mut roots: Vec<Element> = Vec::new();
|
||||||
@@ -63,8 +68,12 @@ impl Interpreter {
|
|||||||
if !is_root && op == OP_END_BLOCK { break; }
|
if !is_root && op == OP_END_BLOCK { break; }
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
OP_ELEM_PUSH => stack.push(Element::new(r.read_str_ref()?)),
|
OP_ELEM_PUSH => {
|
||||||
|
let elem_id = tracker.alloc_id();
|
||||||
|
let mut el = Element::new(r.read_str_ref()?);
|
||||||
|
el.element_id = elem_id;
|
||||||
|
stack.push(el);
|
||||||
|
}
|
||||||
OP_ELEM_POP => {
|
OP_ELEM_POP => {
|
||||||
let finished = stack.pop().ok_or(InterpError::UnexpectedPop)?;
|
let finished = stack.pop().ok_or(InterpError::UnexpectedPop)?;
|
||||||
Self::attach(&mut stack, &mut roots, finished);
|
Self::attach(&mut stack, &mut roots, finished);
|
||||||
@@ -96,10 +105,12 @@ impl Interpreter {
|
|||||||
val.push_str(RHEI_PREFIX);
|
val.push_str(RHEI_PREFIX);
|
||||||
val.push_str(expr_ref);
|
val.push_str(expr_ref);
|
||||||
if let Some(el) = stack.last_mut() {
|
if let Some(el) = stack.last_mut() {
|
||||||
|
tracker.scan_value(el.element_id, &val);
|
||||||
el.push_prop("text".to_string(), val);
|
el.push_prop("text".to_string(), val);
|
||||||
}
|
}
|
||||||
} else if let Some(value) = r.read_value_as_string(vop)? {
|
} else if let Some(value) = r.read_value_as_string(vop)? {
|
||||||
if let Some(el) = stack.last_mut() {
|
if let Some(el) = stack.last_mut() {
|
||||||
|
tracker.scan_value(el.element_id, &value);
|
||||||
el.push_prop("text".to_string(), value);
|
el.push_prop("text".to_string(), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +119,10 @@ impl Interpreter {
|
|||||||
OP_PROP_STR => {
|
OP_PROP_STR => {
|
||||||
let key = r.read_str_ref()?;
|
let key = r.read_str_ref()?;
|
||||||
let val = r.read_str_ref()?;
|
let val = r.read_str_ref()?;
|
||||||
if let Some(el) = stack.last_mut() { el.push_prop(key, val); }
|
if let Some(el) = stack.last_mut() {
|
||||||
|
tracker.scan_value(el.element_id, val);
|
||||||
|
el.push_prop(key, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
OP_PROP_VAR => {
|
OP_PROP_VAR => {
|
||||||
let key = r.read_str_ref()?;
|
let key = r.read_str_ref()?;
|
||||||
@@ -116,22 +130,31 @@ impl Interpreter {
|
|||||||
let mut val = String::with_capacity(val_ref.len() + 1);
|
let mut val = String::with_capacity(val_ref.len() + 1);
|
||||||
val.push('$');
|
val.push('$');
|
||||||
val.push_str(val_ref);
|
val.push_str(val_ref);
|
||||||
if let Some(el) = stack.last_mut() { el.push_prop(key, val); }
|
if let Some(el) = stack.last_mut() {
|
||||||
|
tracker.scan_value(el.element_id, &val);
|
||||||
|
el.push_prop(key, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
OP_PROP_INT => {
|
OP_PROP_INT => {
|
||||||
let key = r.read_str_ref()?;
|
let key = r.read_str_ref()?;
|
||||||
let val = r.read_i64()?.to_string();
|
let val = r.read_i64()?.to_string();
|
||||||
if let Some(el) = stack.last_mut() { el.push_prop(key, val); }
|
if let Some(el) = stack.last_mut() {
|
||||||
|
el.push_prop(key, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
OP_PROP_FLOAT => {
|
OP_PROP_FLOAT => {
|
||||||
let key = r.read_str_ref()?;
|
let key = r.read_str_ref()?;
|
||||||
let val = r.read_f64()?.to_string();
|
let val = r.read_f64()?.to_string();
|
||||||
if let Some(el) = stack.last_mut() { el.push_prop(key, val); }
|
if let Some(el) = stack.last_mut() {
|
||||||
|
el.push_prop(key, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
OP_PROP_BOOL => {
|
OP_PROP_BOOL => {
|
||||||
let key = r.read_str_ref()?;
|
let key = r.read_str_ref()?;
|
||||||
let val = (r.read_byte()? != 0).to_string();
|
let val = (r.read_byte()? != 0).to_string();
|
||||||
if let Some(el) = stack.last_mut() { el.push_prop(key, val); }
|
if let Some(el) = stack.last_mut() {
|
||||||
|
el.push_prop(key, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
OP_PROP_RHEI => {
|
OP_PROP_RHEI => {
|
||||||
let key = r.read_str_ref()?;
|
let key = r.read_str_ref()?;
|
||||||
@@ -140,13 +163,16 @@ impl Interpreter {
|
|||||||
val.push_str(RHEI_PREFIX);
|
val.push_str(RHEI_PREFIX);
|
||||||
val.push_str(expr_ref);
|
val.push_str(expr_ref);
|
||||||
if let Some(el) = stack.last_mut() {
|
if let Some(el) = stack.last_mut() {
|
||||||
|
tracker.scan_value(el.element_id, &val);
|
||||||
el.push_prop(key, val);
|
el.push_prop(key, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OP_PROP_UNIT | OP_PROP_CALL | OP_PROP_IDENT | OP_PROP_FSPATH | OP_PROP_COLOR => {
|
OP_PROP_UNIT | OP_PROP_CALL | OP_PROP_IDENT | OP_PROP_FSPATH | OP_PROP_COLOR => {
|
||||||
let key = r.read_str_ref()?;
|
let key = r.read_str_ref()?;
|
||||||
if let Some(val) = r.read_value_as_string(op)? {
|
if let Some(val) = r.read_value_as_string(op)? {
|
||||||
if let Some(el) = stack.last_mut() { el.push_prop(key, val); }
|
if let Some(el) = stack.last_mut() {
|
||||||
|
el.push_prop(key, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,10 +182,10 @@ impl Interpreter {
|
|||||||
rhei_scripts.push(script);
|
rhei_scripts.push(script);
|
||||||
} else {
|
} else {
|
||||||
let mut text_el = Element::new("#text");
|
let mut text_el = Element::new("#text");
|
||||||
text_el.push_prop(
|
text_el.element_id = tracker.alloc_id();
|
||||||
"text".to_string(),
|
let full_val = format!("{RHEI_PREFIX}{script}");
|
||||||
format!("{RHEI_PREFIX}{script}"),
|
tracker.scan_value(text_el.element_id, &full_val);
|
||||||
);
|
text_el.push_prop("text".to_string(), full_val);
|
||||||
Self::attach(&mut stack, &mut roots, text_el);
|
Self::attach(&mut stack, &mut roots, text_el);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,7 +197,7 @@ impl Interpreter {
|
|||||||
.map(|_| Ok((r.read_string()?, r.read_string()?)))
|
.map(|_| Ok((r.read_string()?, r.read_string()?)))
|
||||||
.collect::<Result<Vec<_>, InterpError>>()?;
|
.collect::<Result<Vec<_>, InterpError>>()?;
|
||||||
let children = Self::parse_block_elements(
|
let children = Self::parse_block_elements(
|
||||||
r, variables, components, rhei_scripts, stylesheet, false,
|
r, variables, components, rhei_scripts, stylesheet, tracker, false,
|
||||||
)?;
|
)?;
|
||||||
components.insert(name.clone(), ComponentDef { name, params, children });
|
components.insert(name.clone(), ComponentDef { name, params, children });
|
||||||
}
|
}
|
||||||
@@ -186,23 +212,26 @@ impl Interpreter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let true_children = Self::parse_block_elements(
|
let true_children = Self::parse_block_elements(
|
||||||
r, variables, components, rhei_scripts, stylesheet, false,
|
r, variables, components, rhei_scripts, stylesheet, tracker, false,
|
||||||
)?;
|
)?;
|
||||||
let has_else = r.read_byte()? == 1;
|
let has_else = r.read_byte()? == 1;
|
||||||
let false_children = if has_else {
|
let false_children = if has_else {
|
||||||
Self::parse_block_elements(
|
Self::parse_block_elements(
|
||||||
r, variables, components, rhei_scripts, stylesheet, false,
|
r, variables, components, rhei_scripts, stylesheet, tracker, false,
|
||||||
)?
|
)?
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut if_el = Element::new("@if");
|
let mut if_el = Element::new("@if");
|
||||||
|
if_el.element_id = tracker.alloc_id();
|
||||||
|
tracker.scan_value(if_el.element_id, &cond_val);
|
||||||
if_el.push_prop("condition", cond_val);
|
if_el.push_prop("condition", cond_val);
|
||||||
if_el.children = true_children;
|
if_el.children = true_children;
|
||||||
|
|
||||||
if !false_children.is_empty() {
|
if !false_children.is_empty() {
|
||||||
let mut else_el = Element::new("@else");
|
let mut else_el = Element::new("@else");
|
||||||
|
else_el.element_id = tracker.alloc_id();
|
||||||
else_el.children = false_children;
|
else_el.children = false_children;
|
||||||
if_el.children.push(else_el);
|
if_el.children.push(else_el);
|
||||||
}
|
}
|
||||||
@@ -221,10 +250,12 @@ impl Interpreter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let block_children = Self::parse_block_elements(
|
let block_children = Self::parse_block_elements(
|
||||||
r, variables, components, rhei_scripts, stylesheet, false,
|
r, variables, components, rhei_scripts, stylesheet, tracker, false,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut each_el = Element::new("@each");
|
let mut each_el = Element::new("@each");
|
||||||
|
each_el.element_id = tracker.alloc_id();
|
||||||
|
tracker.scan_value(each_el.element_id, &source_val);
|
||||||
each_el.push_prop("var_name".to_string(), var_name);
|
each_el.push_prop("var_name".to_string(), var_name);
|
||||||
each_el.push_prop("source".to_string(), source_val);
|
each_el.push_prop("source".to_string(), source_val);
|
||||||
each_el.children = block_children;
|
each_el.children = block_children;
|
||||||
|
|||||||
171
src/interpreter/reactive.rs
Normal file
171
src/interpreter/reactive.rs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
/// Unique identifier for an element in the VDOM tree.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ElementId(pub u32);
|
||||||
|
|
||||||
|
/// Tracks which variables affect which elements for incremental VDOM updates.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ReactiveTracker {
|
||||||
|
/// For each variable name, which elements depend on it
|
||||||
|
subscribers: HashMap<String, HashSet<ElementId>>,
|
||||||
|
/// For each element, which variables it depends on
|
||||||
|
dependencies: HashMap<ElementId, HashSet<String>>,
|
||||||
|
/// Elements that need to be re-evaluated
|
||||||
|
dirty_set: HashSet<ElementId>,
|
||||||
|
next_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReactiveTracker {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
subscribers: HashMap::new(),
|
||||||
|
dependencies: HashMap::new(),
|
||||||
|
dirty_set: HashSet::new(),
|
||||||
|
next_id: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alloc_id(&mut self) -> ElementId {
|
||||||
|
let id = ElementId(self.next_id);
|
||||||
|
self.next_id += 1;
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register that `element` depends on `var_name`.
|
||||||
|
/// Call this during template parsing for each `$var` or `!rhee:{expr}` reference.
|
||||||
|
pub fn add_dependency(&mut self, element: ElementId, var_name: &str) {
|
||||||
|
self.subscribers.entry(var_name.to_string())
|
||||||
|
.or_default()
|
||||||
|
.insert(element);
|
||||||
|
self.dependencies.entry(element)
|
||||||
|
.or_default()
|
||||||
|
.insert(var_name.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register multiple dependencies for an element from a property value.
|
||||||
|
pub fn scan_value(&mut self, element: ElementId, value: &str) {
|
||||||
|
// Scan for `$var` patterns
|
||||||
|
let mut chars = value.char_indices().peekable();
|
||||||
|
while let Some((_, c)) = chars.next() {
|
||||||
|
if c == '$' {
|
||||||
|
let mut var_name = String::new();
|
||||||
|
while let Some(&(_, next_c)) = chars.peek() {
|
||||||
|
if next_c.is_ascii_alphanumeric() || next_c == '_' {
|
||||||
|
var_name.push(chars.next().unwrap().1);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !var_name.is_empty() {
|
||||||
|
self.add_dependency(element, &var_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when a variable changes. Marks affected elements as dirty.
|
||||||
|
pub fn on_variable_changed(&mut self, name: &str) -> &HashSet<ElementId> {
|
||||||
|
if let Some(affected) = self.subscribers.get(name) {
|
||||||
|
self.dirty_set.extend(affected.iter());
|
||||||
|
}
|
||||||
|
&self.dirty_set
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current dirty set and clears it.
|
||||||
|
pub fn take_dirty_set(&mut self) -> HashSet<ElementId> {
|
||||||
|
std::mem::take(&mut self.dirty_set)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the element needs re-evaluation.
|
||||||
|
pub fn is_dirty(&self, id: ElementId) -> bool {
|
||||||
|
self.dirty_set.contains(&id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear all tracking data (e.g., on template reload).
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.subscribers.clear();
|
||||||
|
self.dependencies.clear();
|
||||||
|
self.dirty_set.clear();
|
||||||
|
self.next_id = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ReactiveTracker {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_dependency_tracking() {
|
||||||
|
let mut tracker = ReactiveTracker::new();
|
||||||
|
let e1 = tracker.alloc_id();
|
||||||
|
let e2 = tracker.alloc_id();
|
||||||
|
|
||||||
|
tracker.add_dependency(e1, "volume");
|
||||||
|
tracker.add_dependency(e2, "volume");
|
||||||
|
tracker.add_dependency(e2, "brightness");
|
||||||
|
|
||||||
|
let affected = tracker.on_variable_changed("volume");
|
||||||
|
assert!(affected.contains(&e1));
|
||||||
|
assert!(affected.contains(&e2));
|
||||||
|
|
||||||
|
let affected = tracker.on_variable_changed("brightness");
|
||||||
|
assert!(!affected.contains(&e1));
|
||||||
|
assert!(affected.contains(&e2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scan_value() {
|
||||||
|
let mut tracker = ReactiveTracker::new();
|
||||||
|
let e1 = tracker.alloc_id();
|
||||||
|
|
||||||
|
tracker.scan_value(e1, "Hello $name, you are $age years old");
|
||||||
|
|
||||||
|
let affected = tracker.on_variable_changed("name");
|
||||||
|
assert!(affected.contains(&e1));
|
||||||
|
|
||||||
|
let affected = tracker.on_variable_changed("age");
|
||||||
|
assert!(affected.contains(&e1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scan_no_vars() {
|
||||||
|
let mut tracker = ReactiveTracker::new();
|
||||||
|
let e1 = tracker.alloc_id();
|
||||||
|
|
||||||
|
tracker.scan_value(e1, "Hello world");
|
||||||
|
let affected = tracker.on_variable_changed("name");
|
||||||
|
assert!(!affected.contains(&e1));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_take_dirty_set() {
|
||||||
|
let mut tracker = ReactiveTracker::new();
|
||||||
|
let e1 = tracker.alloc_id();
|
||||||
|
|
||||||
|
tracker.add_dependency(e1, "x");
|
||||||
|
tracker.on_variable_changed("x");
|
||||||
|
|
||||||
|
let dirty = tracker.take_dirty_set();
|
||||||
|
assert_eq!(dirty.len(), 1);
|
||||||
|
assert!(tracker.dirty_set.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_reset() {
|
||||||
|
let mut tracker = ReactiveTracker::new();
|
||||||
|
let e1 = tracker.alloc_id();
|
||||||
|
tracker.add_dependency(e1, "x");
|
||||||
|
tracker.reset();
|
||||||
|
|
||||||
|
assert!(tracker.subscribers.is_empty());
|
||||||
|
assert!(tracker.dependencies.is_empty());
|
||||||
|
assert!(tracker.dirty_set.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ use std::collections::HashMap;
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
|
||||||
|
use super::reactive::{ElementId, ReactiveTracker};
|
||||||
use super::style::{ComputedStyle, StyleSheet};
|
use super::style::{ComputedStyle, StyleSheet};
|
||||||
use compact_str::CompactString;
|
use compact_str::CompactString;
|
||||||
|
|
||||||
@@ -164,6 +165,7 @@ pub struct Element<'a> {
|
|||||||
pub properties: Vec<(Cow<'a, str>, Cow<'a, str>)>,
|
pub properties: Vec<(Cow<'a, str>, Cow<'a, str>)>,
|
||||||
pub children: Vec<Element<'a>>,
|
pub children: Vec<Element<'a>>,
|
||||||
pub computed_style: ComputedStyle,
|
pub computed_style: ComputedStyle,
|
||||||
|
pub element_id: ElementId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Element<'a> {
|
impl<'a> Element<'a> {
|
||||||
@@ -179,6 +181,17 @@ impl<'a> Element<'a> {
|
|||||||
properties: Vec::with_capacity(8),
|
properties: Vec::with_capacity(8),
|
||||||
children: Vec::with_capacity(4),
|
children: Vec::with_capacity(4),
|
||||||
computed_style: ComputedStyle::default(),
|
computed_style: ComputedStyle::default(),
|
||||||
|
element_id: ElementId(u32::MAX),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_id(type_name: &'a str, id: ElementId) -> Self {
|
||||||
|
Self {
|
||||||
|
type_name,
|
||||||
|
properties: Vec::with_capacity(8),
|
||||||
|
children: Vec::with_capacity(4),
|
||||||
|
computed_style: ComputedStyle::default(),
|
||||||
|
element_id: id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -198,6 +211,7 @@ pub struct Document<'a> {
|
|||||||
pub rhei_scripts: Vec<String>,
|
pub rhei_scripts: Vec<String>,
|
||||||
pub stylesheet: StyleSheet,
|
pub stylesheet: StyleSheet,
|
||||||
pub interner: Interner,
|
pub interner: Interner,
|
||||||
|
pub tracker: ReactiveTracker,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|||||||
Reference in New Issue
Block a user