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:
Zixuan Chen 2024-06-06 16:54:52 +08:00 committed by GitHub
parent 6d47015f6e
commit dc55055b6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 68 additions and 9 deletions

View file

@ -0,0 +1,6 @@
---
"loro-wasm": patch
"loro-crdt": patch
---
Perf(wasm) cache text.toDelta

View file

@ -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
}
}
}

View file

@ -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) => {

View file

@ -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();

View file

@ -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,
}

View file

@ -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(),

View file

@ -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.