mirror of
https://github.com/loro-dev/loro.git
synced 2025-01-22 21:07:43 +00:00
Perf cache WASM text toDelta result (#378)
* refactor: make cursor transformation in undo/redo better * chore: add release info * perf: cache to_delta value in wasm * chore: release msg
This commit is contained in:
parent
6d47015f6e
commit
dc55055b6f
7 changed files with 68 additions and 9 deletions
6
.changeset/strange-mirrors-visit.md
Normal file
6
.changeset/strange-mirrors-visit.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
"loro-wasm": patch
|
||||
"loro-crdt": patch
|
||||
---
|
||||
|
||||
Perf(wasm) cache text.toDelta
|
|
@ -4,7 +4,6 @@ use itertools::Itertools;
|
|||
use loro_delta::{array_vec::ArrayVec, delta_trait::DeltaAttr, DeltaItem, DeltaRope};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
use tracing::trace;
|
||||
|
||||
use crate::{
|
||||
container::richtext::richtext_state::RichtextStateChunk,
|
||||
|
@ -457,12 +456,11 @@ impl Diff {
|
|||
|
||||
/// Transform the cursor based on this diff
|
||||
pub(crate) fn transform_cursor(&self, pos: usize, left_prior: bool) -> usize {
|
||||
let ans = match self {
|
||||
match self {
|
||||
Diff::List(list) => list.transform_pos(pos, left_prior),
|
||||
Diff::Text(text) => text.transform_pos(pos, left_prior),
|
||||
_ => pos,
|
||||
};
|
||||
ans
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1186,6 +1186,20 @@ impl TextHandler {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the version id of the richtext
|
||||
///
|
||||
/// This can be used to detect whether the richtext is changed
|
||||
pub fn version_id(&self) -> usize {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(_) => {
|
||||
unimplemented!("Detached text container does not have version id")
|
||||
}
|
||||
MaybeDetached::Attached(a) => {
|
||||
a.with_state(|state| state.as_richtext_state_mut().unwrap().get_version_id())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_richtext_value(&self) -> LoroValue {
|
||||
match &self.inner {
|
||||
MaybeDetached::Detached(t) => {
|
||||
|
|
|
@ -37,7 +37,10 @@ use super::ContainerState;
|
|||
pub struct RichtextState {
|
||||
idx: ContainerIdx,
|
||||
config: Arc<RwLock<StyleConfigMap>>,
|
||||
pub(crate) state: LazyLoad<RichtextStateLoader, InnerState>,
|
||||
state: LazyLoad<RichtextStateLoader, InnerState>,
|
||||
/// This is used to indicate whether the richtext state is changed, so the downstream has an easy way to cache
|
||||
/// NOTE: We need to ensure the invariance that the version id is always increased when the richtext state is changed
|
||||
version_id: usize,
|
||||
}
|
||||
|
||||
struct Pos {
|
||||
|
@ -52,9 +55,23 @@ impl RichtextState {
|
|||
idx,
|
||||
config,
|
||||
state: LazyLoad::Src(Default::default()),
|
||||
version_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_version(&mut self) {
|
||||
self.version_id = self.version_id.wrapping_add(1);
|
||||
}
|
||||
|
||||
/// Get the version id of the richtext
|
||||
///
|
||||
/// This can be used to detect whether the richtext is changed
|
||||
#[inline]
|
||||
pub fn get_version_id(&self) -> usize {
|
||||
self.version_id
|
||||
}
|
||||
|
||||
/// Get the text content of the richtext
|
||||
///
|
||||
/// This uses `mut` because we may need to build the state from snapshot
|
||||
|
@ -84,6 +101,7 @@ impl RichtextState {
|
|||
style_starts: &mut FxHashMap<Arc<StyleOp>, Pos>,
|
||||
style: &Arc<StyleOp>,
|
||||
) -> Pos {
|
||||
self.update_version();
|
||||
match style_starts.remove(style) {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
|
@ -213,6 +231,7 @@ impl Clone for RichtextState {
|
|||
idx: self.idx,
|
||||
config: self.config.clone(),
|
||||
state: self.state.clone(),
|
||||
version_id: self.version_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -244,6 +263,7 @@ impl ContainerState for RichtextState {
|
|||
_txn: &Weak<Mutex<Option<Transaction>>>,
|
||||
_state: &Weak<Mutex<DocState>>,
|
||||
) -> Diff {
|
||||
self.update_version();
|
||||
let InternalDiff::RichtextRaw(richtext) = diff else {
|
||||
unreachable!()
|
||||
};
|
||||
|
@ -425,6 +445,7 @@ impl ContainerState for RichtextState {
|
|||
_txn: &Weak<Mutex<Option<Transaction>>>,
|
||||
_state: &Weak<Mutex<DocState>>,
|
||||
) {
|
||||
self.update_version();
|
||||
let InternalDiff::RichtextRaw(richtext) = diff else {
|
||||
unreachable!()
|
||||
};
|
||||
|
@ -511,6 +532,7 @@ impl ContainerState for RichtextState {
|
|||
}
|
||||
|
||||
fn apply_local_op(&mut self, r_op: &RawOp, op: &Op) -> LoroResult<()> {
|
||||
self.update_version();
|
||||
match &op.content {
|
||||
crate::op::InnerContent::List(l) => match l {
|
||||
list_op::InnerListOp::Insert { slice: _, pos: _ } => {
|
||||
|
@ -634,6 +656,7 @@ impl ContainerState for RichtextState {
|
|||
|
||||
#[doc = " Restore the state to the state represented by the ops that exported by `get_snapshot_ops`"]
|
||||
fn import_from_snapshot_ops(&mut self, ctx: StateSnapshotDecodeContext) {
|
||||
self.update_version();
|
||||
assert_eq!(ctx.mode, EncodeMode::Snapshot);
|
||||
let mut loader = RichtextStateLoader::default();
|
||||
let mut id_to_style = FxHashMap::default();
|
||||
|
|
|
@ -735,7 +735,7 @@ pub(crate) fn undo(
|
|||
let next = if i + 1 < spans.len() {
|
||||
spans[i + 1].0.id_last().into()
|
||||
} else {
|
||||
match last_frontiers_or_last_bi.clone() {
|
||||
match last_frontiers_or_last_bi {
|
||||
Either::Left(last_frontiers) => last_frontiers.clone(),
|
||||
Either::Right(right) => break 'block right,
|
||||
}
|
||||
|
|
|
@ -316,7 +316,12 @@ fn map_delta_to_js(value: &ResolvedMapDelta, doc: &Arc<LoroDoc>) -> JsValue {
|
|||
|
||||
pub(crate) fn handler_to_js_value(handler: Handler, doc: Option<Arc<LoroDoc>>) -> JsValue {
|
||||
match handler {
|
||||
Handler::Text(t) => LoroText { handler: t, doc }.into(),
|
||||
Handler::Text(t) => LoroText {
|
||||
handler: t,
|
||||
doc,
|
||||
delta_cache: None,
|
||||
}
|
||||
.into(),
|
||||
Handler::Map(m) => LoroMap { handler: m, doc }.into(),
|
||||
Handler::List(l) => LoroList { handler: l, doc }.into(),
|
||||
Handler::Tree(t) => LoroTree { handler: t, doc }.into(),
|
||||
|
|
|
@ -588,6 +588,7 @@ impl Loro {
|
|||
Ok(LoroText {
|
||||
handler: text,
|
||||
doc: Some(self.0.clone()),
|
||||
delta_cache: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -721,6 +722,7 @@ impl Loro {
|
|||
LoroText {
|
||||
handler: richtext,
|
||||
doc: Some(self.0.clone()),
|
||||
delta_cache: None,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
@ -1348,6 +1350,7 @@ fn convert_container_path_to_js_value(path: &[(ContainerID, Index)]) -> JsValue
|
|||
pub struct LoroText {
|
||||
handler: TextHandler,
|
||||
doc: Option<Arc<LoroDoc>>,
|
||||
delta_cache: Option<(usize, JsValue)>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
@ -1367,6 +1370,7 @@ impl LoroText {
|
|||
Self {
|
||||
handler: TextHandler::new_detached(),
|
||||
doc: None,
|
||||
delta_cache: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1480,10 +1484,19 @@ impl LoroText {
|
|||
/// console.log(text.toDelta()); // [ { insert: 'Hello', attributes: { bold: true } } ]
|
||||
/// ```
|
||||
#[wasm_bindgen(js_name = "toDelta")]
|
||||
pub fn to_delta(&self) -> JsStringDelta {
|
||||
pub fn to_delta(&mut self) -> JsStringDelta {
|
||||
let version = self.handler.version_id();
|
||||
if let Some((v, delta)) = self.delta_cache.as_ref() {
|
||||
if *v == version {
|
||||
return delta.clone().into();
|
||||
}
|
||||
}
|
||||
|
||||
let delta = self.handler.get_richtext_value();
|
||||
let value: JsValue = delta.into();
|
||||
value.into()
|
||||
let ans: JsStringDelta = value.clone().into();
|
||||
self.delta_cache = Some((version, value));
|
||||
ans
|
||||
}
|
||||
|
||||
/// Get the container id of the text.
|
||||
|
|
Loading…
Reference in a new issue