diff --git a/crates/loro-core/src/container/registry.rs b/crates/loro-core/src/container/registry.rs index 9894d9c2..9d8efc82 100644 --- a/crates/loro-core/src/container/registry.rs +++ b/crates/loro-core/src/container/registry.rs @@ -435,7 +435,8 @@ pub trait ContainerWrapper { let ans = match event { Some(event) => { debug_log::debug_log!("get event"); - hierarchy.try_lock().unwrap().notify(event); + // let mut hierarchy = hierarchy.try_lock().unwrap(); + Hierarchy::notify_without_lock(hierarchy, event); Ok(ans) } None => Ok(ans), diff --git a/crates/loro-core/src/hierarchy.rs b/crates/loro-core/src/hierarchy.rs index f752690d..abdd4c5a 100644 --- a/crates/loro-core/src/hierarchy.rs +++ b/crates/loro-core/src/hierarchy.rs @@ -1,4 +1,7 @@ -use std::{fmt::Debug, sync::RwLockWriteGuard}; +use std::{ + fmt::Debug, + sync::{Arc, Mutex, RwLockWriteGuard}, +}; use fxhash::{FxHashMap, FxHashSet}; @@ -224,21 +227,91 @@ impl Hierarchy { false } - pub(crate) fn notify_without_path<'a, 'b>( - &'a mut self, - mut raw_event: RawEvent, - store: RwLockWriteGuard<'b, LogStore>, - ) { - let reg = &store.reg; + // pub(crate) fn notify_without_path<'a, 'b>( + // &'a mut self, + // mut raw_event: RawEvent, + // store: RwLockWriteGuard<'b, LogStore>, + // ) { + // let reg = &store.reg; - if raw_event.abs_path.is_empty() { - let Some(absolute_path) = self.get_path(reg, &raw_event.container_id, None) else { - return ; + // if raw_event.abs_path.is_empty() { + // let Some(absolute_path) = self.get_path(reg, &raw_event.container_id, None) else { + // return ; + // }; + // raw_event.abs_path = absolute_path; + // } + // drop(store); + // self.notify(raw_event); + // } + + pub(crate) fn notify_without_lock(hierarchy: Arc>, raw_event: RawEvent) { + let (mut nodes, mut root_observers) = { + let mut hierarchy = hierarchy.try_lock().unwrap(); + ( + std::mem::take(&mut hierarchy.nodes), + std::mem::take(&mut hierarchy.root_observers), + ) }; - raw_event.abs_path = absolute_path; + + // mut nodes: FxHashMap, + // mut root_observers: FxHashMap, + + let target_id = raw_event.container_id; + let mut event = Event { + absolute_path: raw_event.abs_path, + relative_path: Default::default(), + old_version: raw_event.old_version, + new_version: raw_event.new_version, + current_target: Some(target_id.clone()), + target: target_id.clone(), + diff: raw_event.diff, + local: raw_event.local, + }; + let mut current_target_id = Some(target_id.clone()); + let mut count = 0; + let mut path_to_root = event.absolute_path.clone(); + path_to_root.reverse(); + let node = nodes.entry(target_id).or_default(); + if !node.observers.is_empty() { + for (_, observer) in node.observers.iter_mut() { + observer(&event); + } + } + while let Some(id) = current_target_id { + let Some(node) = nodes.get_mut(&id) else { + break; + }; + if !node.deep_observers.is_empty() { + let mut relative_path = path_to_root[..count].to_vec(); + relative_path.reverse(); + event.relative_path = relative_path; + event.current_target = Some(id.clone()); + for (_, observer) in node.deep_observers.iter_mut() { + observer(&event); + } + } + + count += 1; + if node.parent.is_none() { + debug_assert!(id.is_root()); + } + + current_target_id = node.parent.as_ref().cloned(); + } + + if !root_observers.is_empty() { + debug_log::debug_log!("notify root"); + event.relative_path = event.absolute_path.clone(); + event.current_target = None; + for (_, observer) in root_observers.iter_mut() { + observer(&event); + } + } + { + let mut hierarchy = hierarchy.try_lock().unwrap(); + hierarchy.nodes = nodes; + hierarchy.root_observers = root_observers; } - drop(store); - self.notify(raw_event); } pub fn notify(&mut self, raw_event: RawEvent) { @@ -348,4 +421,13 @@ impl Hierarchy { self.notify(event); } } + + pub fn send_notifications_without_lock( + hierarchy: Arc>, + events: Vec, + ) { + for event in events { + Hierarchy::notify_without_lock(hierarchy.clone(), event); + } + } } diff --git a/crates/loro-core/src/loro.rs b/crates/loro-core/src/loro.rs index 09253d84..4e91190f 100644 --- a/crates/loro-core/src/loro.rs +++ b/crates/loro-core/src/loro.rs @@ -49,7 +49,7 @@ impl LoroCore { #[inline(always)] pub fn get_list>(&mut self, id: I) -> List { let id: ContainerIdRaw = id.into(); - let mut store = self.log_store.write().unwrap(); + let mut store = self.log_store.try_write().unwrap(); let instance = store.get_or_create_container(&id.with_type(ContainerType::List)); let cid = store.this_client_id(); List::from_instance(instance, cid) @@ -58,7 +58,7 @@ impl LoroCore { #[inline(always)] pub fn get_map>(&mut self, id: I) -> Map { let id: ContainerIdRaw = id.into(); - let mut store = self.log_store.write().unwrap(); + let mut store = self.log_store.try_write().unwrap(); let instance = store.get_or_create_container(&id.with_type(ContainerType::Map)); let cid = store.this_client_id(); Map::from_instance(instance, cid) @@ -67,7 +67,7 @@ impl LoroCore { #[inline(always)] pub fn get_text>(&mut self, id: I) -> Text { let id: ContainerIdRaw = id.into(); - let mut store = self.log_store.write().unwrap(); + let mut store = self.log_store.try_write().unwrap(); let instance = store.get_or_create_container(&id.with_type(ContainerType::Text)); let cid = store.this_client_id(); Text::from_instance(instance, cid) @@ -126,4 +126,9 @@ impl LoroCore { let mut h = self.hierarchy.try_lock().unwrap(); h.send_notifications(events); } + + #[instrument(skip_all)] + pub fn notify_without_lock(&self, events: Vec) { + Hierarchy::send_notifications_without_lock(self.hierarchy.clone(), events) + } } diff --git a/crates/loro-wasm/deno_test/test.ts b/crates/loro-wasm/deno_test/test.ts index da6a68e5..7355f847 100644 --- a/crates/loro-wasm/deno_test/test.ts +++ b/crates/loro-wasm/deno_test/test.ts @@ -169,3 +169,26 @@ Deno.test({ name: "test prelim" }, async (t) => { }]]); }); }); + +Deno.test("subscribe_lock", () => { + const loro = new Loro(); + const text = loro.getText("text"); + const list = loro.getList("list"); + let count = 0; + let i = 3; + const sub = loro.subscribe(() => { + if (i >0){ + list.insert(loro, 0, i); + i--; + } + + count += 1; + }); + text.insert(loro, 0, "hello world"); + assertEquals(count, 1); + text.insert(loro, 0, "hello world"); + assertEquals(count, 2); + loro.unsubscribe(sub); + text.insert(loro, 0, "hello world"); + assertEquals(count, 2); +}); \ No newline at end of file