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 loro_delta::{array_vec::ArrayVec, delta_trait::DeltaAttr, DeltaItem, DeltaRope};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use tracing::trace;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
container::richtext::richtext_state::RichtextStateChunk,
|
container::richtext::richtext_state::RichtextStateChunk,
|
||||||
|
@ -457,12 +456,11 @@ impl Diff {
|
||||||
|
|
||||||
/// Transform the cursor based on this diff
|
/// Transform the cursor based on this diff
|
||||||
pub(crate) fn transform_cursor(&self, pos: usize, left_prior: bool) -> usize {
|
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::List(list) => list.transform_pos(pos, left_prior),
|
||||||
Diff::Text(text) => text.transform_pos(pos, left_prior),
|
Diff::Text(text) => text.transform_pos(pos, left_prior),
|
||||||
_ => pos,
|
_ => 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 {
|
pub fn get_richtext_value(&self) -> LoroValue {
|
||||||
match &self.inner {
|
match &self.inner {
|
||||||
MaybeDetached::Detached(t) => {
|
MaybeDetached::Detached(t) => {
|
||||||
|
|
|
@ -37,7 +37,10 @@ use super::ContainerState;
|
||||||
pub struct RichtextState {
|
pub struct RichtextState {
|
||||||
idx: ContainerIdx,
|
idx: ContainerIdx,
|
||||||
config: Arc<RwLock<StyleConfigMap>>,
|
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 {
|
struct Pos {
|
||||||
|
@ -52,9 +55,23 @@ impl RichtextState {
|
||||||
idx,
|
idx,
|
||||||
config,
|
config,
|
||||||
state: LazyLoad::Src(Default::default()),
|
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
|
/// Get the text content of the richtext
|
||||||
///
|
///
|
||||||
/// This uses `mut` because we may need to build the state from snapshot
|
/// 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_starts: &mut FxHashMap<Arc<StyleOp>, Pos>,
|
||||||
style: &Arc<StyleOp>,
|
style: &Arc<StyleOp>,
|
||||||
) -> Pos {
|
) -> Pos {
|
||||||
|
self.update_version();
|
||||||
match style_starts.remove(style) {
|
match style_starts.remove(style) {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => {
|
None => {
|
||||||
|
@ -213,6 +231,7 @@ impl Clone for RichtextState {
|
||||||
idx: self.idx,
|
idx: self.idx,
|
||||||
config: self.config.clone(),
|
config: self.config.clone(),
|
||||||
state: self.state.clone(),
|
state: self.state.clone(),
|
||||||
|
version_id: self.version_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -244,6 +263,7 @@ impl ContainerState for RichtextState {
|
||||||
_txn: &Weak<Mutex<Option<Transaction>>>,
|
_txn: &Weak<Mutex<Option<Transaction>>>,
|
||||||
_state: &Weak<Mutex<DocState>>,
|
_state: &Weak<Mutex<DocState>>,
|
||||||
) -> Diff {
|
) -> Diff {
|
||||||
|
self.update_version();
|
||||||
let InternalDiff::RichtextRaw(richtext) = diff else {
|
let InternalDiff::RichtextRaw(richtext) = diff else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
@ -425,6 +445,7 @@ impl ContainerState for RichtextState {
|
||||||
_txn: &Weak<Mutex<Option<Transaction>>>,
|
_txn: &Weak<Mutex<Option<Transaction>>>,
|
||||||
_state: &Weak<Mutex<DocState>>,
|
_state: &Weak<Mutex<DocState>>,
|
||||||
) {
|
) {
|
||||||
|
self.update_version();
|
||||||
let InternalDiff::RichtextRaw(richtext) = diff else {
|
let InternalDiff::RichtextRaw(richtext) = diff else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
@ -511,6 +532,7 @@ impl ContainerState for RichtextState {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_local_op(&mut self, r_op: &RawOp, op: &Op) -> LoroResult<()> {
|
fn apply_local_op(&mut self, r_op: &RawOp, op: &Op) -> LoroResult<()> {
|
||||||
|
self.update_version();
|
||||||
match &op.content {
|
match &op.content {
|
||||||
crate::op::InnerContent::List(l) => match l {
|
crate::op::InnerContent::List(l) => match l {
|
||||||
list_op::InnerListOp::Insert { slice: _, pos: _ } => {
|
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`"]
|
#[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) {
|
fn import_from_snapshot_ops(&mut self, ctx: StateSnapshotDecodeContext) {
|
||||||
|
self.update_version();
|
||||||
assert_eq!(ctx.mode, EncodeMode::Snapshot);
|
assert_eq!(ctx.mode, EncodeMode::Snapshot);
|
||||||
let mut loader = RichtextStateLoader::default();
|
let mut loader = RichtextStateLoader::default();
|
||||||
let mut id_to_style = FxHashMap::default();
|
let mut id_to_style = FxHashMap::default();
|
||||||
|
|
|
@ -735,7 +735,7 @@ pub(crate) fn undo(
|
||||||
let next = if i + 1 < spans.len() {
|
let next = if i + 1 < spans.len() {
|
||||||
spans[i + 1].0.id_last().into()
|
spans[i + 1].0.id_last().into()
|
||||||
} else {
|
} else {
|
||||||
match last_frontiers_or_last_bi.clone() {
|
match last_frontiers_or_last_bi {
|
||||||
Either::Left(last_frontiers) => last_frontiers.clone(),
|
Either::Left(last_frontiers) => last_frontiers.clone(),
|
||||||
Either::Right(right) => break 'block right,
|
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 {
|
pub(crate) fn handler_to_js_value(handler: Handler, doc: Option<Arc<LoroDoc>>) -> JsValue {
|
||||||
match handler {
|
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::Map(m) => LoroMap { handler: m, doc }.into(),
|
||||||
Handler::List(l) => LoroList { handler: l, doc }.into(),
|
Handler::List(l) => LoroList { handler: l, doc }.into(),
|
||||||
Handler::Tree(t) => LoroTree { handler: t, doc }.into(),
|
Handler::Tree(t) => LoroTree { handler: t, doc }.into(),
|
||||||
|
|
|
@ -588,6 +588,7 @@ impl Loro {
|
||||||
Ok(LoroText {
|
Ok(LoroText {
|
||||||
handler: text,
|
handler: text,
|
||||||
doc: Some(self.0.clone()),
|
doc: Some(self.0.clone()),
|
||||||
|
delta_cache: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -721,6 +722,7 @@ impl Loro {
|
||||||
LoroText {
|
LoroText {
|
||||||
handler: richtext,
|
handler: richtext,
|
||||||
doc: Some(self.0.clone()),
|
doc: Some(self.0.clone()),
|
||||||
|
delta_cache: None,
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
@ -1348,6 +1350,7 @@ fn convert_container_path_to_js_value(path: &[(ContainerID, Index)]) -> JsValue
|
||||||
pub struct LoroText {
|
pub struct LoroText {
|
||||||
handler: TextHandler,
|
handler: TextHandler,
|
||||||
doc: Option<Arc<LoroDoc>>,
|
doc: Option<Arc<LoroDoc>>,
|
||||||
|
delta_cache: Option<(usize, JsValue)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -1367,6 +1370,7 @@ impl LoroText {
|
||||||
Self {
|
Self {
|
||||||
handler: TextHandler::new_detached(),
|
handler: TextHandler::new_detached(),
|
||||||
doc: None,
|
doc: None,
|
||||||
|
delta_cache: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1480,10 +1484,19 @@ impl LoroText {
|
||||||
/// console.log(text.toDelta()); // [ { insert: 'Hello', attributes: { bold: true } } ]
|
/// console.log(text.toDelta()); // [ { insert: 'Hello', attributes: { bold: true } } ]
|
||||||
/// ```
|
/// ```
|
||||||
#[wasm_bindgen(js_name = "toDelta")]
|
#[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 delta = self.handler.get_richtext_value();
|
||||||
let value: JsValue = delta.into();
|
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.
|
/// Get the container id of the text.
|
||||||
|
|
Loading…
Reference in a new issue