mod effect_fn;
use effect_fn::EffectFn;
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
rc::Rc,
};
type ActiveEffectType = RefCell<Option<Rc<Effect>>>;
type BucketType = RefCell<HashMap<usize, HashMap<String, Rc<RefCell<HashSet<Rc<EffectFn>>>>>>>;
thread_local! {
pub static LOCK_SET_ACTIVE_EFFECT: RefCell<bool> = RefCell::new(false);
pub static ACTIVE_EFFECT: ActiveEffectType = RefCell::new(None);
static BUCKET: BucketType = RefCell::new(HashMap::default());
}
#[derive(Clone)]
pub struct Effect(Rc<EffectFn>);
impl core::hash::Hash for Effect {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.get_effect_fn_ptr_address().hash(state);
}
}
impl std::cmp::PartialEq for Effect {
fn eq(&self, other: &Self) -> bool {
self.get_effect_fn_ptr_address() == other.get_effect_fn_ptr_address()
}
}
impl std::cmp::Eq for Effect {}
impl Effect {
pub fn track<I: crate::rjson::json::Index + ToString + Clone>(target_ptr: usize, index: &I) {
ACTIVE_EFFECT.with_borrow(|active_effect| {
if active_effect.is_none() {
return;
}
let active_effect = active_effect.clone().unwrap();
BUCKET.with_borrow_mut(|bucket| {
let deps_map = bucket.entry(target_ptr).or_insert(HashMap::default());
let deps_set = deps_map.entry(index.to_string()).or_insert(Rc::new(RefCell::new(HashSet::default())));
let mut inner_deps_set = deps_set.borrow_mut();
if !inner_deps_set.contains(&active_effect.0) {
inner_deps_set.insert(active_effect.0.clone());
active_effect.0.deps_set.borrow_mut().push(deps_set.clone());
active_effect.0.on_track(target_ptr, index);
}
});
});
}
pub fn trigger<I: crate::rjson::json::Index + ToString + Clone>(target_ptr: usize, index: &I) {
BUCKET.with_borrow(|bucket| {
if let Some(deps_map) = bucket.get(&target_ptr) {
if let Some(deps_set) = deps_map.get(&index.to_string()) {
let inner_deps_set = deps_set.borrow();
for dep in inner_deps_set.iter() {
dep.on_trigger(target_ptr, index);
dep.run();
}
}
}
});
}
pub fn remove_target_ptr(target_ptr: usize) {
BUCKET.with_borrow_mut(|bucket| {
if bucket.contains_key(&target_ptr) {
bucket.remove(&target_ptr);
}
})
}
pub fn replace_target_ptr(target_ptr: usize, new_target_ptr: usize) {
BUCKET.with_borrow_mut(|bucket| {
if let Some(value) = bucket.remove(&target_ptr) {
bucket.insert(new_target_ptr, value);
}
});
}
}
impl Effect {
pub fn new<F>(closure: F) -> Rc<Effect>
where
F: Fn() -> () + 'static,
{
let effect = Rc::new(Effect(Rc::new(EffectFn::new(closure))));
LOCK_SET_ACTIVE_EFFECT.with_borrow_mut(|lock_set_active_effect| {
*lock_set_active_effect = true;
});
{
ACTIVE_EFFECT.with_borrow_mut(|active_effect| {
*active_effect = Some(effect.clone());
});
}
effect.0.run();
{
ACTIVE_EFFECT.with_borrow_mut(|active_effect| {
*active_effect = None;
});
}
LOCK_SET_ACTIVE_EFFECT.with_borrow_mut(|lock_set_active_effect| {
*lock_set_active_effect = true;
});
effect
}
pub fn get_effect_fn_ptr_address(&self) -> usize {
self.0.get_ptr_address()
}
fn cleanup(&mut self) {
let deps_effect_fn = self.0.deps_set.borrow();
for dep_effect_fn in deps_effect_fn.iter() {
let mut deps_effect_fn = dep_effect_fn.borrow_mut();
deps_effect_fn.remove(&self.0);
}
}
}
impl Drop for Effect {
fn drop(&mut self) {
log::trace!("drop effect: {:p}, inner effect_fn address {:?}", self, self.get_effect_fn_ptr_address());
self.cleanup();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let serde_json_value = serde_json::json!({
"name": "John Doe",
"age": 43,
"phones": [
"+44",
"+44 1234567",
"+44 2345678"
],
"address": {
"street": "123 Main St",
"city": "London",
"state": "UK",
"zip": 12345
}
});
{
BUCKET.with_borrow(|bucket| {
assert_eq!(bucket.keys().len(), 0);
});
}
base_test(serde_json_value.clone().into());
{
BUCKET.with_borrow(|bucket| {
assert_eq!(bucket.keys().len(), 0);
});
}
object_test(serde_json_value.clone().into());
{
BUCKET.with_borrow(|bucket| {
assert_eq!(bucket.keys().len(), 0);
});
}
array_test(serde_json_value.clone().into());
{
BUCKET.with_borrow(|bucket| {
assert_eq!(bucket.keys().len(), 0);
});
}
}
fn base_test(json: crate::RJson) {
{
let count = Rc::new(RefCell::new(0));
let eff_json1 = json.clone();
let eff_count = count.clone();
let _eff = crate::effect(move || {
*eff_count.borrow_mut() += 1;
eff_json1.get("name");
});
{
BUCKET.with_borrow(|bucket| {
assert_eq!(bucket.keys().len(), 1);
assert_eq!(bucket.get(&json.get_json_ptr_address()).unwrap().keys().len(), 1);
let deps = bucket.get(&json.get_json_ptr_address()).unwrap().get("name").unwrap();
assert_eq!(deps.borrow().len(), 1);
});
}
{
assert_eq!(*count.borrow(), 1);
}
json.set("age2", 0);
json.set("name", "name1");
{
assert_eq!(*count.borrow(), 2);
}
json.set("name", "name2");
json.set("age", 18);
{
assert_eq!(*count.borrow(), 3);
}
json.set("name", "name2");
{
assert_eq!(*count.borrow(), 4);
}
}
{
BUCKET.with_borrow(|bucket| {
let deps = bucket.get(&json.get_json_ptr_address()).unwrap().get("name").unwrap();
assert_eq!(deps.borrow().len(), 0);
});
}
}
fn object_test(json: crate::RJson) {
let count = Rc::new(RefCell::new(0));
let eff_json = json.clone();
let eff_count = count.clone();
let _eff = crate::effect(move || {
*eff_count.borrow_mut() += 1;
eff_json.get("name");
eff_json.get("address").get("street");
});
{
BUCKET.with_borrow(|bucket| {
assert_eq!(bucket.keys().len(), 2);
assert_eq!(bucket.get(&json.get_json_ptr_address()).unwrap().keys().len(), 2);
assert_eq!(bucket.get(&json.get("address").get_json_ptr_address()).unwrap().keys().len(), 1);
let deps = bucket.get(&json.get_json_ptr_address()).unwrap().get("name").unwrap();
assert_eq!(deps.borrow().len(), 1);
let deps = bucket.get(&json.get_json_ptr_address()).unwrap().get("address").unwrap();
assert_eq!(deps.borrow().len(), 1);
});
}
{
assert_eq!(*count.borrow(), 1);
}
json.set("age2", 0);
json.set("name", "name1");
json.set("age", "18");
{
assert_eq!(*count.borrow(), 2);
}
json.get("address").set("street", "street1");
{
assert_eq!(*count.borrow(), 3);
}
}
fn array_test(json: crate::RJson) {
let count = Rc::new(RefCell::new(0));
let eff_json1 = json.clone();
let eff_count = count.clone();
let _eff = crate::effect(move || {
*eff_count.borrow_mut() += 1;
eff_json1.get("name");
eff_json1.get("phones").get(0);
});
{
BUCKET.with_borrow(|bucket| {
assert_eq!(bucket.keys().len(), 2);
assert_eq!(bucket.get(&json.get_json_ptr_address()).unwrap().keys().len(), 2);
assert_eq!(bucket.get(&json.get("phones").get_json_ptr_address()).unwrap().keys().len(), 1);
let deps = bucket.get(&json.get_json_ptr_address()).unwrap().get("name").unwrap();
assert_eq!(deps.borrow().len(), 1);
let deps = bucket.get(&json.get_json_ptr_address()).unwrap().get("phones").unwrap();
assert_eq!(deps.borrow().len(), 1);
});
}
{
assert_eq!(*count.borrow(), 1);
}
json.set("age2", 0);
json.set("name", "name1");
json.set("age", "18");
{
assert_eq!(*count.borrow(), 2);
}
json.get("phones").set(0, "1234567");
{
assert_eq!(*count.borrow(), 3);
}
json.set("phones", serde_json::json!(["1"]));
{
assert_eq!(*count.borrow(), 4);
}
json.get("phones").set(0, "2");
{
assert_eq!(*count.borrow(), 5);
}
}
}