mirror of
https://github.com/loro-dev/loro.git
synced 2025-01-22 12:57:20 +00:00
feat: event & wasm
This commit is contained in:
parent
f63c346e5c
commit
15be521777
34 changed files with 1812 additions and 640 deletions
24
Cargo.lock
generated
24
Cargo.lock
generated
|
@ -564,6 +564,16 @@ version = "0.1.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "364ff57c5031fee39b026dcdfdc9c7dc1d1d79451bfdacba90f040524b766254"
|
||||
|
||||
[[package]]
|
||||
name = "debug-log"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d13e7dd03f70e6b332a2b42a9cdfa5b04f1015098c9656e77995c09772c947"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.3.1"
|
||||
|
@ -721,13 +731,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -999,13 +1011,14 @@ dependencies = [
|
|||
"crdt-list",
|
||||
"criterion 0.4.0",
|
||||
"ctor",
|
||||
"debug-log",
|
||||
"debug-log 0.1.4",
|
||||
"dhat",
|
||||
"enum-as-inner 0.5.1",
|
||||
"enum_dispatch",
|
||||
"flate2",
|
||||
"fxhash",
|
||||
"generic-btree",
|
||||
"getrandom",
|
||||
"im",
|
||||
"itertools",
|
||||
"js-sys",
|
||||
|
@ -1019,7 +1032,6 @@ dependencies = [
|
|||
"proptest",
|
||||
"proptest-derive",
|
||||
"rand",
|
||||
"ring",
|
||||
"rle",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
|
@ -1060,6 +1072,8 @@ name = "loro-wasm"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"debug-log 0.2.1",
|
||||
"getrandom",
|
||||
"js-sys",
|
||||
"loro-internal",
|
||||
"serde-wasm-bindgen",
|
||||
|
@ -1725,7 +1739,7 @@ dependencies = [
|
|||
"color-backtrace",
|
||||
"crdt-list",
|
||||
"ctor",
|
||||
"debug-log",
|
||||
"debug-log 0.1.4",
|
||||
"enum-as-inner 0.5.1",
|
||||
"fxhash",
|
||||
"heapless",
|
||||
|
|
|
@ -13,7 +13,6 @@ loro-common = { path = "../loro-common" }
|
|||
smallvec = { version = "1.8.0", features = ["serde"] }
|
||||
smartstring = { version = "1.0.1" }
|
||||
fxhash = "0.2.1"
|
||||
ring = "0.16.20"
|
||||
serde = { version = "1.0.140", features = ["derive"] }
|
||||
thiserror = "1.0.31"
|
||||
enum-as-inner = "0.5.1"
|
||||
|
@ -40,6 +39,7 @@ generic-btree = "0.4.0"
|
|||
compact-bytes = { path = "../compact-bytes" }
|
||||
lz4_flex = "0.11.1"
|
||||
miniz_oxide = "0.7.1"
|
||||
getrandom = "0.2.10"
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0.87"
|
||||
|
|
|
@ -61,8 +61,8 @@ mod run {
|
|||
let text = txn.get_text("text");
|
||||
|
||||
for TextAction { pos, ins, del } in actions.iter() {
|
||||
text.delete(&mut txn, *pos, *del);
|
||||
text.insert(&mut txn, *pos, ins);
|
||||
text.delete(&mut txn, *pos, *del).unwrap();
|
||||
text.insert(&mut txn, *pos, ins).unwrap();
|
||||
}
|
||||
|
||||
txn.commit().unwrap();
|
||||
|
|
|
@ -20,8 +20,8 @@ mod run {
|
|||
let mut txn = loro.txn().unwrap();
|
||||
|
||||
for TextAction { pos, ins, del } in actions.iter() {
|
||||
text.delete(&mut txn, *pos, *del);
|
||||
text.insert(&mut txn, *pos, ins);
|
||||
text.delete(&mut txn, *pos, *del).unwrap();
|
||||
text.insert(&mut txn, *pos, ins).unwrap();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
@ -35,8 +35,8 @@ mod run {
|
|||
}));
|
||||
let mut txn = loro.txn().unwrap();
|
||||
for TextAction { pos, ins, del } in actions.iter() {
|
||||
text.delete(&mut txn, *pos, *del);
|
||||
text.insert(&mut txn, *pos, ins);
|
||||
text.delete(&mut txn, *pos, *del).unwrap();
|
||||
text.insert(&mut txn, *pos, ins).unwrap();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
@ -54,8 +54,8 @@ mod run {
|
|||
txn = loro.txn().unwrap();
|
||||
}
|
||||
n += 1;
|
||||
text.delete(&mut txn, *pos, *del);
|
||||
text.insert(&mut txn, *pos, ins);
|
||||
text.delete(&mut txn, *pos, *del).unwrap();
|
||||
text.insert(&mut txn, *pos, ins).unwrap();
|
||||
}
|
||||
txn.commit().unwrap();
|
||||
|
||||
|
@ -77,8 +77,8 @@ mod run {
|
|||
txn = loro.txn().unwrap();
|
||||
}
|
||||
n += 1;
|
||||
text.delete(&mut txn, *pos, *del);
|
||||
text.insert(&mut txn, *pos, ins);
|
||||
text.delete(&mut txn, *pos, *del).unwrap();
|
||||
text.insert(&mut txn, *pos, ins).unwrap();
|
||||
}
|
||||
txn.commit().unwrap();
|
||||
|
||||
|
@ -99,8 +99,8 @@ mod run {
|
|||
txn = loro.txn().unwrap();
|
||||
}
|
||||
n += 1;
|
||||
text.delete(&mut txn, *pos, *del);
|
||||
text.insert(&mut txn, *pos, ins);
|
||||
text.delete(&mut txn, *pos, *del).unwrap();
|
||||
text.insert(&mut txn, *pos, ins).unwrap();
|
||||
}
|
||||
txn.commit().unwrap();
|
||||
|
||||
|
@ -124,8 +124,8 @@ mod run {
|
|||
txn = loro.txn().unwrap();
|
||||
}
|
||||
n += 1;
|
||||
text.delete(&mut txn, *pos, *del);
|
||||
text.insert(&mut txn, *pos, ins);
|
||||
text.delete(&mut txn, *pos, *del).unwrap();
|
||||
text.insert(&mut txn, *pos, ins).unwrap();
|
||||
}
|
||||
txn.commit().unwrap();
|
||||
|
||||
|
@ -143,8 +143,8 @@ mod run {
|
|||
let mut txn = loro.txn().unwrap();
|
||||
|
||||
for TextAction { pos, ins, del } in actions.iter() {
|
||||
text.delete_utf16(&mut txn, *pos, *del);
|
||||
text.insert_utf16(&mut txn, *pos, ins);
|
||||
text.delete_utf16(&mut txn, *pos, *del).unwrap();
|
||||
text.insert_utf16(&mut txn, *pos, ins).unwrap();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
@ -162,8 +162,8 @@ mod run {
|
|||
txn = loro.txn().unwrap();
|
||||
}
|
||||
n += 1;
|
||||
text.delete(&mut txn, *pos, *del);
|
||||
text.insert(&mut txn, *pos, ins);
|
||||
text.delete(&mut txn, *pos, *del).unwrap();
|
||||
text.insert(&mut txn, *pos, ins).unwrap();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
@ -175,8 +175,8 @@ mod run {
|
|||
{
|
||||
for TextAction { pos, ins, del } in actions.iter() {
|
||||
let mut txn = loro.txn().unwrap();
|
||||
text.delete(&mut txn, *pos, *del);
|
||||
text.insert(&mut txn, *pos, ins);
|
||||
text.delete(&mut txn, *pos, *del).unwrap();
|
||||
text.insert(&mut txn, *pos, ins).unwrap();
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -193,8 +193,8 @@ mod run {
|
|||
{
|
||||
for TextAction { pos, ins, del } in actions.iter() {
|
||||
let mut txn = loro.txn().unwrap();
|
||||
text.delete(&mut txn, *pos, *del);
|
||||
text.insert(&mut txn, *pos, ins);
|
||||
text.delete(&mut txn, *pos, *del).unwrap();
|
||||
text.insert(&mut txn, *pos, ins).unwrap();
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -209,8 +209,8 @@ mod run {
|
|||
for TextAction { pos, ins, del } in actions.iter() {
|
||||
{
|
||||
let mut txn = loro.txn().unwrap();
|
||||
text.delete(&mut txn, *pos, *del);
|
||||
text.insert(&mut txn, *pos, ins);
|
||||
text.delete(&mut txn, *pos, *del).unwrap();
|
||||
text.insert(&mut txn, *pos, ins).unwrap();
|
||||
}
|
||||
|
||||
loro_b
|
||||
|
@ -239,14 +239,14 @@ mod run {
|
|||
|
||||
{
|
||||
let mut txn = loro.txn().unwrap();
|
||||
text.delete(&mut txn, pos, del);
|
||||
text.insert(&mut txn, pos, ins);
|
||||
text.delete(&mut txn, pos, del).unwrap();
|
||||
text.insert(&mut txn, pos, ins).unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
let mut txn = loro_b.txn().unwrap();
|
||||
text2.delete(&mut txn, pos, del);
|
||||
text2.insert(&mut txn, pos, ins);
|
||||
text2.delete(&mut txn, pos, del).unwrap();
|
||||
text2.insert(&mut txn, pos, ins).unwrap();
|
||||
}
|
||||
loro_b
|
||||
.import(&loro.export_from(&loro_b.vv_cloned()))
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
use crate::{log_store::GcConfig, Timestamp};
|
||||
use ring::rand::{SecureRandom, SystemRandom};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Configure {
|
||||
|
@ -19,6 +18,14 @@ impl Debug for Configure {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct DefaultRandom;
|
||||
|
||||
impl SecureRandomGenerator for DefaultRandom {
|
||||
fn fill_byte(&self, dest: &mut [u8]) {
|
||||
getrandom::getrandom(dest).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SecureRandomGenerator: Send + Sync {
|
||||
fn fill_byte(&self, dest: &mut [u8]);
|
||||
fn next_u64(&self) -> u64 {
|
||||
|
@ -46,18 +53,12 @@ pub trait SecureRandomGenerator: Send + Sync {
|
|||
}
|
||||
}
|
||||
|
||||
impl SecureRandomGenerator for SystemRandom {
|
||||
fn fill_byte(&self, dest: &mut [u8]) {
|
||||
self.fill(dest).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Configure {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
gc: GcConfig::default(),
|
||||
get_time: || 0,
|
||||
rand: Arc::new(SystemRandom::new()),
|
||||
rand: Arc::new(DefaultRandom),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
//! Every [Container] can take a [Snapshot], which contains [crate::LoroValue] that describes the state.
|
||||
//!
|
||||
use crate::{
|
||||
arena::SharedArena,
|
||||
event::{Observer, ObserverHandler, SubscriptionID},
|
||||
hierarchy::Hierarchy,
|
||||
log_store::ImportContext,
|
||||
|
@ -121,6 +122,41 @@ pub enum ContainerIdRaw {
|
|||
Normal { id: ID },
|
||||
}
|
||||
|
||||
pub trait IntoContainerId {
|
||||
fn into_container_id(self, arena: &SharedArena, kind: ContainerType) -> ContainerID;
|
||||
}
|
||||
|
||||
impl IntoContainerId for String {
|
||||
fn into_container_id(self, _arena: &SharedArena, kind: ContainerType) -> ContainerID {
|
||||
ContainerID::Root {
|
||||
name: InternalString::from(self.as_str()),
|
||||
container_type: kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoContainerId for &'a str {
|
||||
fn into_container_id(self, _arena: &SharedArena, kind: ContainerType) -> ContainerID {
|
||||
ContainerID::Root {
|
||||
name: InternalString::from(self),
|
||||
container_type: kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoContainerId for ContainerID {
|
||||
fn into_container_id(self, _arena: &SharedArena, _kind: ContainerType) -> ContainerID {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoContainerId for ContainerIdx {
|
||||
fn into_container_id(self, arena: &SharedArena, kind: ContainerType) -> ContainerID {
|
||||
assert_eq!(self.get_type(), kind);
|
||||
arena.get_container_id(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ContainerIdRaw {
|
||||
fn from(value: String) -> Self {
|
||||
ContainerIdRaw::Root { name: value.into() }
|
||||
|
|
|
@ -582,7 +582,6 @@ impl List {
|
|||
self.container_idx
|
||||
}
|
||||
|
||||
/// Inserts an element at position index within the List
|
||||
pub fn insert<T: Transact, P: Prelim>(
|
||||
&mut self,
|
||||
txn: &T,
|
||||
|
|
|
@ -213,13 +213,13 @@ impl Actionable for Vec<LoroDoc> {
|
|||
let site = &mut self[*site as usize];
|
||||
let mut txn = site.txn().unwrap();
|
||||
let text = txn.get_text("text");
|
||||
text.insert(&mut txn, *pos, &content.to_string());
|
||||
text.insert(&mut txn, *pos, &content.to_string()).unwrap();
|
||||
}
|
||||
Action::Del { pos, len, site } => {
|
||||
let site = &mut self[*site as usize];
|
||||
let mut txn = site.txn().unwrap();
|
||||
let text = txn.get_text("text");
|
||||
text.delete(&mut txn, *pos, *len);
|
||||
text.delete(&mut txn, *pos, *len).unwrap();
|
||||
}
|
||||
Action::Sync { from, to } => {
|
||||
if from != to {
|
||||
|
|
|
@ -504,13 +504,17 @@ impl Actionable for Vec<Actor> {
|
|||
let mut txn = actor.loro.txn().unwrap();
|
||||
match value {
|
||||
FuzzValue::Null => {
|
||||
container.delete(&mut txn, &key.to_string());
|
||||
container.delete(&mut txn, &key.to_string()).unwrap();
|
||||
}
|
||||
FuzzValue::I32(i) => {
|
||||
container.insert(&mut txn, &key.to_string(), LoroValue::from(*i));
|
||||
container
|
||||
.insert(&mut txn, &key.to_string(), LoroValue::from(*i))
|
||||
.unwrap();
|
||||
}
|
||||
FuzzValue::Container(c) => {
|
||||
let idx = container.insert_container(&mut txn, &key.to_string(), *c);
|
||||
let idx = container
|
||||
.insert_container(&mut txn, &key.to_string(), *c)
|
||||
.unwrap();
|
||||
actor.add_new_container(idx, *c);
|
||||
}
|
||||
};
|
||||
|
@ -534,13 +538,17 @@ impl Actionable for Vec<Actor> {
|
|||
let mut txn = actor.loro.txn().unwrap();
|
||||
match value {
|
||||
FuzzValue::Null => {
|
||||
container.delete(&mut txn, *key as usize, 1);
|
||||
container.delete(&mut txn, *key as usize, 1).unwrap();
|
||||
}
|
||||
FuzzValue::I32(i) => {
|
||||
container.insert(&mut txn, *key as usize, LoroValue::from(*i));
|
||||
container
|
||||
.insert(&mut txn, *key as usize, LoroValue::from(*i))
|
||||
.unwrap();
|
||||
}
|
||||
FuzzValue::Container(c) => {
|
||||
let idx = container.insert_container(&mut txn, *key as usize, *c);
|
||||
let idx = container
|
||||
.insert_container(&mut txn, *key as usize, *c)
|
||||
.unwrap();
|
||||
actor.add_new_container(idx, *c);
|
||||
}
|
||||
};
|
||||
|
@ -563,9 +571,13 @@ impl Actionable for Vec<Actor> {
|
|||
};
|
||||
let mut txn = actor.loro.txn().unwrap();
|
||||
if *is_del {
|
||||
container.delete(&mut txn, *pos as usize, *value as usize);
|
||||
container
|
||||
.delete(&mut txn, *pos as usize, *value as usize)
|
||||
.unwrap();
|
||||
} else {
|
||||
container.insert(&mut txn, *pos as usize, &(format!("[{}]", value)));
|
||||
container
|
||||
.insert(&mut txn, *pos as usize, &(format!("[{}]", value)))
|
||||
.unwrap();
|
||||
}
|
||||
drop(txn);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ pub mod log_store;
|
|||
pub mod op;
|
||||
pub mod refactor;
|
||||
pub mod version;
|
||||
pub use refactor::*;
|
||||
|
||||
mod error;
|
||||
#[cfg(feature = "test_utils")]
|
||||
|
|
|
@ -17,7 +17,7 @@ pub struct ContainerDiff {
|
|||
pub diff: Diff,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DiffEvent<'a> {
|
||||
/// whether the event comes from the children of the container.
|
||||
pub from_children: bool,
|
||||
|
@ -73,3 +73,30 @@ impl<'a> InternalDocDiff<'a> {
|
|||
self.origin == other.origin && self.local == other.local
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::LoroDoc;
|
||||
|
||||
#[test]
|
||||
fn test_text_event() {
|
||||
let loro = LoroDoc::new();
|
||||
loro.subscribe_deep(Arc::new(|event| {
|
||||
assert_eq!(
|
||||
&event.container.diff.as_text().unwrap().vec[0]
|
||||
.as_insert()
|
||||
.unwrap()
|
||||
.0,
|
||||
&"h223ello"
|
||||
);
|
||||
dbg!(event);
|
||||
}));
|
||||
let mut txn = loro.txn().unwrap();
|
||||
let text = loro.get_text("id");
|
||||
text.insert(&mut txn, 0, "hello").unwrap();
|
||||
text.insert(&mut txn, 1, "223").unwrap();
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
use super::{state::DocState, txn::Transaction};
|
||||
use crate::container::{
|
||||
list::list_op::{DeleteSpan, ListOp},
|
||||
registry::ContainerIdx,
|
||||
text::text_content::ListSlice,
|
||||
use crate::{
|
||||
container::{
|
||||
list::list_op::{DeleteSpan, ListOp},
|
||||
registry::ContainerIdx,
|
||||
text::text_content::ListSlice,
|
||||
},
|
||||
txn::EventHint,
|
||||
};
|
||||
use loro_common::{ContainerID, ContainerType, LoroValue};
|
||||
use loro_common::{ContainerID, ContainerType, LoroResult, LoroValue};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
sync::{Mutex, Weak},
|
||||
|
@ -81,15 +84,15 @@ impl TextHandler {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(features = "wasm"))]
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
impl TextHandler {
|
||||
#[inline(always)]
|
||||
pub fn insert(&self, txn: &mut Transaction, pos: usize, s: &str) {
|
||||
pub fn insert(&self, txn: &mut Transaction, pos: usize, s: &str) -> LoroResult<()> {
|
||||
self.insert_utf8(txn, pos, s)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn delete(&self, txn: &mut Transaction, pos: usize, len: usize) {
|
||||
pub fn delete(&self, txn: &mut Transaction, pos: usize, len: usize) -> LoroResult<()> {
|
||||
self.delete_utf8(txn, pos, len)
|
||||
}
|
||||
|
||||
|
@ -98,9 +101,9 @@ impl TextHandler {
|
|||
self.len_utf8()
|
||||
}
|
||||
|
||||
pub fn insert_utf8(&self, txn: &mut Transaction, pos: usize, s: &str) {
|
||||
pub fn insert_utf8(&self, txn: &mut Transaction, pos: usize, s: &str) -> LoroResult<()> {
|
||||
if s.is_empty() {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
txn.apply_local_op(
|
||||
|
@ -110,12 +113,12 @@ impl TextHandler {
|
|||
pos,
|
||||
}),
|
||||
None,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
pub fn delete_utf8(&self, txn: &mut Transaction, pos: usize, len: usize) {
|
||||
pub fn delete_utf8(&self, txn: &mut Transaction, pos: usize, len: usize) -> LoroResult<()> {
|
||||
if len == 0 {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
txn.apply_local_op(
|
||||
|
@ -125,12 +128,12 @@ impl TextHandler {
|
|||
len: len as isize,
|
||||
})),
|
||||
None,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
pub fn insert_utf16(&self, txn: &mut Transaction, pos: usize, s: &str) {
|
||||
pub fn insert_utf16(&self, txn: &mut Transaction, pos: usize, s: &str) -> LoroResult<()> {
|
||||
if s.is_empty() {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let start =
|
||||
|
@ -152,12 +155,14 @@ impl TextHandler {
|
|||
pos: start,
|
||||
}),
|
||||
None,
|
||||
);
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_utf16(&self, txn: &mut Transaction, pos: usize, del: usize) {
|
||||
pub fn delete_utf16(&self, txn: &mut Transaction, pos: usize, del: usize) -> LoroResult<()> {
|
||||
if del == 0 {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (start, end) =
|
||||
|
@ -178,11 +183,11 @@ impl TextHandler {
|
|||
len: (end - start) as isize,
|
||||
})),
|
||||
None,
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(features = "wasm")]
|
||||
#[cfg(feature = "wasm")]
|
||||
impl TextHandler {
|
||||
#[inline(always)]
|
||||
pub fn len(&self) -> usize {
|
||||
|
@ -190,29 +195,18 @@ impl TextHandler {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn delete(&self, txn: &mut Transaction, pos: usize, del: usize) {
|
||||
pub fn delete(&self, txn: &mut Transaction, pos: usize, del: usize) -> LoroResult<()> {
|
||||
self.delete_utf16(txn, pos, del)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn insert(&self, txn: &mut Transaction, pos: usize, s: &str) {
|
||||
pub fn insert(&self, txn: &mut Transaction, pos: usize, s: &str) -> LoroResult<()> {
|
||||
self.insert_utf16(txn, pos, s)
|
||||
}
|
||||
|
||||
pub fn len_utf16(&self) -> usize {
|
||||
self.state
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.with_state(self.container_idx, |state| {
|
||||
state.as_text_state().as_ref().unwrap().len_wchars()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn insert_utf16(&self, txn: &mut Transaction, pos: usize, s: &str) {
|
||||
pub fn insert_utf16(&self, txn: &mut Transaction, pos: usize, s: &str) -> LoroResult<()> {
|
||||
if s.is_empty() {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let start =
|
||||
|
@ -234,12 +228,12 @@ impl TextHandler {
|
|||
pos: start,
|
||||
}),
|
||||
Some(EventHint::Utf16 { pos, len: 0 }),
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
pub fn delete_utf16(&self, txn: &mut Transaction, pos: usize, del: usize) {
|
||||
pub fn delete_utf16(&self, txn: &mut Transaction, pos: usize, del: usize) -> LoroResult<()> {
|
||||
if del == 0 {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (start, end) =
|
||||
|
@ -260,7 +254,7 @@ impl TextHandler {
|
|||
len: (end - start) as isize,
|
||||
})),
|
||||
Some(EventHint::Utf16 { pos, len: del }),
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -273,10 +267,10 @@ impl ListHandler {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn insert(&self, txn: &mut Transaction, pos: usize, v: LoroValue) {
|
||||
pub fn insert(&self, txn: &mut Transaction, pos: usize, v: LoroValue) -> LoroResult<()> {
|
||||
if let Some(container) = v.as_container() {
|
||||
self.insert_container(txn, pos, container.container_type());
|
||||
return;
|
||||
self.insert_container(txn, pos, container.container_type())?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
txn.apply_local_op(
|
||||
|
@ -286,7 +280,7 @@ impl ListHandler {
|
|||
pos,
|
||||
}),
|
||||
None,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
pub fn insert_container(
|
||||
|
@ -294,7 +288,7 @@ impl ListHandler {
|
|||
txn: &mut Transaction,
|
||||
pos: usize,
|
||||
c_type: ContainerType,
|
||||
) -> ContainerIdx {
|
||||
) -> LoroResult<ContainerIdx> {
|
||||
let id = txn.next_id();
|
||||
let container_id = ContainerID::new_normal(id, c_type);
|
||||
let child_idx = txn.arena.register_container(&container_id);
|
||||
|
@ -307,13 +301,13 @@ impl ListHandler {
|
|||
pos,
|
||||
}),
|
||||
None,
|
||||
);
|
||||
child_idx
|
||||
)?;
|
||||
Ok(child_idx)
|
||||
}
|
||||
|
||||
pub fn delete(&self, txn: &mut Transaction, pos: usize, len: usize) {
|
||||
pub fn delete(&self, txn: &mut Transaction, pos: usize, len: usize) -> LoroResult<()> {
|
||||
if len == 0 {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
txn.apply_local_op(
|
||||
|
@ -323,10 +317,10 @@ impl ListHandler {
|
|||
len: len as isize,
|
||||
})),
|
||||
None,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
pub fn len(&self) -> usize {
|
||||
self.state
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
|
@ -337,7 +331,7 @@ impl ListHandler {
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
|
@ -360,6 +354,18 @@ impl ListHandler {
|
|||
.idx_to_id(self.container_idx)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn get(&self, index: usize) -> Option<LoroValue> {
|
||||
self.state
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.with_state(self.container_idx, |state| {
|
||||
let a = state.as_list_state().unwrap();
|
||||
a.get(index).cloned()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl MapHandler {
|
||||
|
@ -371,10 +377,10 @@ impl MapHandler {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn insert(&self, txn: &mut Transaction, key: &str, value: LoroValue) {
|
||||
pub fn insert(&self, txn: &mut Transaction, key: &str, value: LoroValue) -> LoroResult<()> {
|
||||
if let Some(value) = value.as_container() {
|
||||
self.insert_container(txn, key, value.container_type());
|
||||
return;
|
||||
self.insert_container(txn, key, value.container_type())?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
txn.apply_local_op(
|
||||
|
@ -384,7 +390,7 @@ impl MapHandler {
|
|||
value,
|
||||
}),
|
||||
None,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
pub fn insert_container(
|
||||
|
@ -392,7 +398,7 @@ impl MapHandler {
|
|||
txn: &mut Transaction,
|
||||
key: &str,
|
||||
c_type: ContainerType,
|
||||
) -> ContainerIdx {
|
||||
) -> LoroResult<ContainerIdx> {
|
||||
let id = txn.next_id();
|
||||
let container_id = ContainerID::new_normal(id, c_type);
|
||||
let child_idx = txn.arena.register_container(&container_id);
|
||||
|
@ -404,11 +410,11 @@ impl MapHandler {
|
|||
value: LoroValue::Container(container_id),
|
||||
}),
|
||||
None,
|
||||
);
|
||||
child_idx
|
||||
)?;
|
||||
Ok(child_idx)
|
||||
}
|
||||
|
||||
pub fn delete(&self, txn: &mut Transaction, key: &str) {
|
||||
pub fn delete(&self, txn: &mut Transaction, key: &str) -> LoroResult<()> {
|
||||
txn.apply_local_op(
|
||||
self.container_idx,
|
||||
crate::op::RawOpContent::Map(crate::container::map::MapSet {
|
||||
|
@ -417,7 +423,7 @@ impl MapHandler {
|
|||
value: LoroValue::Null,
|
||||
}),
|
||||
None,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> LoroValue {
|
||||
|
@ -429,6 +435,18 @@ impl MapHandler {
|
|||
.get_value_by_idx(self.container_idx)
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &str) -> Option<LoroValue> {
|
||||
self.state
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.with_state(self.container_idx, |state| {
|
||||
let a = state.as_map_state().unwrap();
|
||||
a.get(key).cloned()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn id(&self) -> ContainerID {
|
||||
self.state
|
||||
.upgrade()
|
||||
|
@ -439,6 +457,21 @@ impl MapHandler {
|
|||
.idx_to_id(self.container_idx)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.state
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.with_state(self.container_idx, |state| {
|
||||
state.as_map_state().as_ref().unwrap().len()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -451,14 +484,14 @@ mod test {
|
|||
let loro = LoroDoc::new();
|
||||
let mut txn = loro.txn().unwrap();
|
||||
let text = txn.get_text("hello");
|
||||
text.insert(&mut txn, 0, "hello");
|
||||
text.insert(&mut txn, 0, "hello").unwrap();
|
||||
assert_eq!(&**text.get_value().as_string().unwrap(), "hello");
|
||||
text.insert(&mut txn, 2, " kk ");
|
||||
text.insert(&mut txn, 2, " kk ").unwrap();
|
||||
assert_eq!(&**text.get_value().as_string().unwrap(), "he kk llo");
|
||||
txn.abort();
|
||||
let mut txn = loro.txn().unwrap();
|
||||
assert_eq!(&**text.get_value().as_string().unwrap(), "");
|
||||
text.insert(&mut txn, 0, "hi");
|
||||
text.insert(&mut txn, 0, "hi").unwrap();
|
||||
txn.commit().unwrap();
|
||||
assert_eq!(&**text.get_value().as_string().unwrap(), "hi");
|
||||
}
|
||||
|
@ -472,14 +505,14 @@ mod test {
|
|||
|
||||
let mut txn = loro.txn().unwrap();
|
||||
let text = txn.get_text("hello");
|
||||
text.insert(&mut txn, 0, "hello");
|
||||
text.insert(&mut txn, 0, "hello").unwrap();
|
||||
txn.commit().unwrap();
|
||||
let exported = loro.export_from(&Default::default());
|
||||
loro2.import(&exported).unwrap();
|
||||
let mut txn = loro2.txn().unwrap();
|
||||
let text = txn.get_text("hello");
|
||||
assert_eq!(&**text.get_value().as_string().unwrap(), "hello");
|
||||
text.insert(&mut txn, 5, " world");
|
||||
text.insert(&mut txn, 5, " world").unwrap();
|
||||
assert_eq!(&**text.get_value().as_string().unwrap(), "hello world");
|
||||
txn.commit().unwrap();
|
||||
loro.import(&loro2.export_from(&Default::default()))
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
cmp::Ordering,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use debug_log::debug_dbg;
|
||||
use loro_common::{ContainerID, ContainerType, LoroValue};
|
||||
use loro_common::{ContainerID, ContainerType, LoroResult, LoroValue};
|
||||
|
||||
use crate::{
|
||||
container::{registry::ContainerIdx, ContainerIdRaw},
|
||||
arena::SharedArena,
|
||||
container::{registry::ContainerIdx, IntoContainerId},
|
||||
id::PeerID,
|
||||
log_store::encoding::{ConcreteEncodeMode, ENCODE_SCHEMA_VERSION, MAGIC_BYTES},
|
||||
version::Frontiers,
|
||||
EncodeMode, InternalString, LoroError, VersionVector,
|
||||
};
|
||||
|
||||
|
@ -44,6 +47,7 @@ use super::{
|
|||
pub struct LoroDoc {
|
||||
oplog: Arc<Mutex<OpLog>>,
|
||||
state: Arc<Mutex<DocState>>,
|
||||
arena: SharedArena,
|
||||
observer: Arc<Observer>,
|
||||
detached: bool,
|
||||
}
|
||||
|
@ -58,7 +62,8 @@ impl LoroDoc {
|
|||
oplog: Arc::new(Mutex::new(oplog)),
|
||||
state,
|
||||
detached: false,
|
||||
observer: Arc::new(Observer::new(arena)),
|
||||
observer: Arc::new(Observer::new(arena.clone())),
|
||||
arena,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,6 +74,7 @@ impl LoroDoc {
|
|||
pub(super) fn from_existing(oplog: OpLog, state: DocState) -> Self {
|
||||
let obs = Observer::new(oplog.arena.clone());
|
||||
Self {
|
||||
arena: oplog.arena.clone(),
|
||||
observer: Arc::new(obs),
|
||||
oplog: Arc::new(Mutex::new(oplog)),
|
||||
state: Arc::new(Mutex::new(state)),
|
||||
|
@ -76,6 +82,10 @@ impl LoroDoc {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn peer_id(&self) -> PeerID {
|
||||
self.state.lock().unwrap().peer
|
||||
}
|
||||
|
||||
pub fn set_peer_id(&self, peer: PeerID) {
|
||||
self.state.lock().unwrap().peer = peer;
|
||||
}
|
||||
|
@ -105,18 +115,23 @@ impl LoroDoc {
|
|||
});
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn txn(&self) -> Result<Transaction, LoroError> {
|
||||
if self.state.lock().unwrap().is_in_txn() {
|
||||
return Err(LoroError::DuplicatedTransactionError);
|
||||
self.txn_with_origin("")
|
||||
}
|
||||
|
||||
pub fn txn_with_origin(&self, origin: &str) -> Result<Transaction, LoroError> {
|
||||
let mut txn =
|
||||
Transaction::new_with_origin(self.state.clone(), self.oplog.clone(), origin.into());
|
||||
if self.state.lock().unwrap().is_recording() {
|
||||
let obs = self.observer.clone();
|
||||
txn.set_on_commit(Box::new(move |state| {
|
||||
let events = state.lock().unwrap().take_events();
|
||||
for event in events {
|
||||
obs.emit(event);
|
||||
}
|
||||
}));
|
||||
}
|
||||
let mut txn = Transaction::new(self.state.clone(), self.oplog.clone());
|
||||
let obs = self.observer.clone();
|
||||
txn.set_on_commit(Box::new(move |state| {
|
||||
let events = state.lock().unwrap().take_events();
|
||||
for event in events {
|
||||
obs.emit(event);
|
||||
}
|
||||
}));
|
||||
|
||||
Ok(txn)
|
||||
}
|
||||
|
@ -228,21 +243,21 @@ impl LoroDoc {
|
|||
|
||||
/// id can be a str, ContainerID, or ContainerIdRaw.
|
||||
/// if it's str it will use Root container, which will not be None
|
||||
pub fn get_text<I: Into<ContainerIdRaw>>(&self, id: I) -> TextHandler {
|
||||
pub fn get_text<I: IntoContainerId>(&self, id: I) -> TextHandler {
|
||||
let idx = self.get_container_idx(id, ContainerType::Text);
|
||||
TextHandler::new(idx, Arc::downgrade(&self.state))
|
||||
}
|
||||
|
||||
/// id can be a str, ContainerID, or ContainerIdRaw.
|
||||
/// if it's str it will use Root container, which will not be None
|
||||
pub fn get_list<I: Into<ContainerIdRaw>>(&self, id: I) -> ListHandler {
|
||||
pub fn get_list<I: IntoContainerId>(&self, id: I) -> ListHandler {
|
||||
let idx = self.get_container_idx(id, ContainerType::List);
|
||||
ListHandler::new(idx, Arc::downgrade(&self.state))
|
||||
}
|
||||
|
||||
/// id can be a str, ContainerID, or ContainerIdRaw.
|
||||
/// if it's str it will use Root container, which will not be None
|
||||
pub fn get_map<I: Into<ContainerIdRaw>>(&self, id: I) -> MapHandler {
|
||||
pub fn get_map<I: IntoContainerId>(&self, id: I) -> MapHandler {
|
||||
let idx = self.get_container_idx(id, ContainerType::Map);
|
||||
MapHandler::new(idx, Arc::downgrade(&self.state))
|
||||
}
|
||||
|
@ -251,26 +266,20 @@ impl LoroDoc {
|
|||
self.oplog().lock().unwrap().diagnose_size();
|
||||
}
|
||||
|
||||
fn get_container_idx<I: Into<ContainerIdRaw>>(
|
||||
&self,
|
||||
id: I,
|
||||
c_type: ContainerType,
|
||||
) -> ContainerIdx {
|
||||
let id: ContainerIdRaw = id.into();
|
||||
match id {
|
||||
ContainerIdRaw::Root { name } => self.oplog().lock().unwrap().arena.register_container(
|
||||
&crate::container::ContainerID::Root {
|
||||
name,
|
||||
container_type: c_type,
|
||||
},
|
||||
),
|
||||
ContainerIdRaw::Normal { id: _ } => self
|
||||
.oplog()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.arena
|
||||
.register_container(&id.with_type(c_type)),
|
||||
}
|
||||
fn get_container_idx<I: IntoContainerId>(&self, id: I, c_type: ContainerType) -> ContainerIdx {
|
||||
let id = id.into_container_id(&self.arena, c_type);
|
||||
self.arena.register_container(&id)
|
||||
}
|
||||
|
||||
pub fn frontiers(&self) -> Frontiers {
|
||||
self.oplog().lock().unwrap().frontiers().clone()
|
||||
}
|
||||
|
||||
/// - Ordering::Less means self is less than target or parallel
|
||||
/// - Ordering::Equal means versions equal
|
||||
/// - Ordering::Greater means self's version is greater than target
|
||||
pub fn cmp_frontiers(&self, other: &Frontiers) -> Ordering {
|
||||
self.oplog().lock().unwrap().cmp_frontiers(other)
|
||||
}
|
||||
|
||||
pub fn subscribe_deep(&self, callback: Subscriber) -> SubID {
|
||||
|
@ -294,6 +303,19 @@ impl LoroDoc {
|
|||
pub fn unsubscribe(&self, id: SubID) {
|
||||
self.observer.unsubscribe(id);
|
||||
}
|
||||
|
||||
// PERF: opt
|
||||
pub fn import_batch(&self, bytes: &[Vec<u8>]) -> LoroResult<()> {
|
||||
for data in bytes.iter() {
|
||||
self.import(data)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn to_json(&self) -> LoroValue {
|
||||
self.state.lock().unwrap().get_deep_value()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LoroDoc {
|
||||
|
|
|
@ -4,7 +4,11 @@ pub(super) mod arena;
|
|||
mod container;
|
||||
pub(super) mod diff_calc;
|
||||
pub mod handler;
|
||||
pub use event::{ContainerDiff, DiffEvent, DocDiff};
|
||||
pub use handler::{ListHandler, MapHandler, TextHandler};
|
||||
pub use loro::LoroDoc;
|
||||
pub use oplog::OpLog;
|
||||
pub use state::DocState;
|
||||
pub mod event;
|
||||
pub mod loro;
|
||||
pub mod obs;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
atomic::{AtomicU32, AtomicUsize, Ordering},
|
||||
Arc, Mutex,
|
||||
};
|
||||
|
||||
|
@ -25,12 +25,22 @@ struct ObserverInner {
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct SubID(usize);
|
||||
pub struct SubID(u32);
|
||||
|
||||
impl SubID {
|
||||
pub fn into_u32(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn from_u32(id: u32) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Observer {
|
||||
inner: Mutex<ObserverInner>,
|
||||
arena: SharedArena,
|
||||
next_sub_id: AtomicUsize,
|
||||
next_sub_id: AtomicU32,
|
||||
taken_times: AtomicUsize,
|
||||
}
|
||||
|
||||
|
@ -38,7 +48,7 @@ impl Observer {
|
|||
pub fn new(arena: SharedArena) -> Self {
|
||||
Self {
|
||||
arena,
|
||||
next_sub_id: AtomicUsize::new(0),
|
||||
next_sub_id: AtomicU32::new(0),
|
||||
taken_times: AtomicUsize::new(0),
|
||||
inner: Mutex::new(ObserverInner {
|
||||
subscribers: Default::default(),
|
||||
|
@ -223,14 +233,14 @@ mod test {
|
|||
if text.get_value().as_string().unwrap().len() > 10 {
|
||||
return;
|
||||
}
|
||||
text.insert(&mut txn, 0, "123");
|
||||
text.insert(&mut txn, 0, "123").unwrap();
|
||||
txn.commit().unwrap();
|
||||
}));
|
||||
|
||||
let loro = loro_cp;
|
||||
let mut txn = loro.txn().unwrap();
|
||||
let text = loro.get_text("id");
|
||||
text.insert(&mut txn, 0, "123");
|
||||
text.insert(&mut txn, 0, "123").unwrap();
|
||||
txn.commit().unwrap();
|
||||
let count = count.load(Ordering::SeqCst);
|
||||
assert!(count > 2, "{}", count);
|
||||
|
@ -250,20 +260,20 @@ mod test {
|
|||
assert_eq!(count.load(Ordering::SeqCst), 0);
|
||||
{
|
||||
let mut txn = loro.txn().unwrap();
|
||||
text.insert(&mut txn, 0, "123");
|
||||
text.insert(&mut txn, 0, "123").unwrap();
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
assert_eq!(count.load(Ordering::SeqCst), 1);
|
||||
{
|
||||
let mut txn = loro.txn().unwrap();
|
||||
text.insert(&mut txn, 0, "123");
|
||||
text.insert(&mut txn, 0, "123").unwrap();
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
assert_eq!(count.load(Ordering::SeqCst), 2);
|
||||
loro.unsubscribe(sub);
|
||||
{
|
||||
let mut txn = loro.txn().unwrap();
|
||||
text.insert(&mut txn, 0, "123");
|
||||
text.insert(&mut txn, 0, "123").unwrap();
|
||||
txn.commit().unwrap();
|
||||
}
|
||||
assert_eq!(count.load(Ordering::SeqCst), 2);
|
||||
|
|
|
@ -2,6 +2,7 @@ pub(crate) mod dag;
|
|||
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::cmp::Ordering;
|
||||
use std::rc::Rc;
|
||||
|
||||
use fxhash::FxHashMap;
|
||||
|
@ -234,6 +235,13 @@ impl OpLog {
|
|||
&self.dag.frontiers
|
||||
}
|
||||
|
||||
/// - Ordering::Less means self is less than target or parallel
|
||||
/// - Ordering::Equal means versions equal
|
||||
/// - Ordering::Greater means self's version is greater than target
|
||||
pub fn cmp_frontiers(&self, other: &Frontiers) -> Ordering {
|
||||
self.dag.cmp_frontiers(other)
|
||||
}
|
||||
|
||||
pub(crate) fn export_changes_from(&self, from: &VersionVector) -> RemoteClientChanges {
|
||||
let mut changes = RemoteClientChanges::default();
|
||||
for (&peer, &cnt) in self.vv().iter() {
|
||||
|
|
|
@ -200,4 +200,17 @@ impl AppDag {
|
|||
pub fn get_frontiers(&self) -> &Frontiers {
|
||||
&self.frontiers
|
||||
}
|
||||
|
||||
/// - Ordering::Less means self is less than target or parallel
|
||||
/// - Ordering::Equal means versions equal
|
||||
/// - Ordering::Greater means self's version is greater than target
|
||||
pub fn cmp_frontiers(&self, other: &Frontiers) -> Ordering {
|
||||
if &self.frontiers == other {
|
||||
Ordering::Equal
|
||||
} else if other.iter().all(|id| self.vv.includes_id(*id)) {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Less
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -800,7 +800,7 @@ mod test {
|
|||
let app = LoroDoc::new();
|
||||
let mut txn = app.txn().unwrap();
|
||||
let text = txn.get_text("id");
|
||||
text.insert(&mut txn, 0, "hello");
|
||||
text.insert(&mut txn, 0, "hello").unwrap();
|
||||
txn.commit().unwrap();
|
||||
let snapshot = app.export_snapshot();
|
||||
let app2 = LoroDoc::new();
|
||||
|
@ -818,7 +818,7 @@ mod test {
|
|||
// test import snapshot to a LoroApp that is already changed
|
||||
let mut txn = app2.txn().unwrap();
|
||||
let text = txn.get_text("id");
|
||||
text.insert(&mut txn, 2, " ");
|
||||
text.insert(&mut txn, 2, " ").unwrap();
|
||||
txn.commit().unwrap();
|
||||
debug_log::group!("app2 export");
|
||||
let snapshot = app2.export_snapshot();
|
||||
|
|
|
@ -4,11 +4,10 @@ use debug_log::debug_dbg;
|
|||
use enum_as_inner::EnumAsInner;
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use loro_common::ContainerID;
|
||||
use ring::rand::SystemRandom;
|
||||
use loro_common::{ContainerID, LoroResult};
|
||||
|
||||
use crate::{
|
||||
configure::SecureRandomGenerator,
|
||||
configure::{DefaultRandom, SecureRandomGenerator},
|
||||
container::{registry::ContainerIdx, ContainerIdRaw},
|
||||
delta::{Delta, DeltaItem},
|
||||
event::{Diff, Index, Utf16Meta},
|
||||
|
@ -105,7 +104,7 @@ impl State {
|
|||
impl DocState {
|
||||
#[inline]
|
||||
pub fn new(arena: SharedArena) -> Self {
|
||||
let peer = SystemRandom::new().next_u64();
|
||||
let peer = DefaultRandom.next_u64();
|
||||
// TODO: maybe we should switch to certain version in oplog?
|
||||
Self {
|
||||
peer,
|
||||
|
@ -242,7 +241,7 @@ impl DocState {
|
|||
debug_dbg!(self.get_deep_value());
|
||||
}
|
||||
|
||||
pub fn apply_local_op(&mut self, op: RawOp) {
|
||||
pub fn apply_local_op(&mut self, op: RawOp) -> LoroResult<()> {
|
||||
let state = self
|
||||
.states
|
||||
.entry(op.container)
|
||||
|
@ -253,7 +252,9 @@ impl DocState {
|
|||
self.changed_idx_in_txn.insert(op.container);
|
||||
}
|
||||
|
||||
// TODO: make apply_op return a result
|
||||
state.apply_op(op, &self.arena);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn start_txn(&mut self, origin: InternalString, local: bool) {
|
||||
|
@ -539,7 +540,6 @@ impl DocState {
|
|||
|
||||
match idx.get_type() {
|
||||
ContainerType::Text => {
|
||||
let state = self.states.get(&idx).unwrap().as_text_state().unwrap();
|
||||
let mut ans: Delta<String, Utf16Meta> = Delta::new();
|
||||
let mut index = 0;
|
||||
for span in seq.iter() {
|
||||
|
@ -554,10 +554,12 @@ impl DocState {
|
|||
crate::delta::DeltaItem::Insert { value, .. } => {
|
||||
let len = value.0.iter().fold(0, |acc, cur| acc + cur.0.len());
|
||||
let mut s = String::with_capacity(len);
|
||||
for sub in state.slice(index..index + len) {
|
||||
s.push_str(sub);
|
||||
for slice in value.0.iter() {
|
||||
let bytes = self
|
||||
.arena
|
||||
.slice_bytes(slice.0.start as usize..slice.0.end as usize);
|
||||
s.push_str(std::str::from_utf8(&bytes).unwrap());
|
||||
}
|
||||
|
||||
ans.push(DeltaItem::Insert {
|
||||
value: s,
|
||||
meta: Utf16Meta { utf16_len: None },
|
||||
|
|
|
@ -247,6 +247,15 @@ impl ListState {
|
|||
}
|
||||
ans
|
||||
}
|
||||
|
||||
pub fn get(&self, index: usize) -> Option<&LoroValue> {
|
||||
let result = self.list.query::<LengthFinder>(&index);
|
||||
if result.found {
|
||||
Some(result.elem(&self.list).unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContainerState for ListState {
|
||||
|
|
|
@ -142,7 +142,7 @@ impl MapState {
|
|||
self.map.iter()
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
pub fn len(&self) -> usize {
|
||||
self.map.len()
|
||||
}
|
||||
|
||||
|
@ -160,4 +160,14 @@ impl MapState {
|
|||
}
|
||||
ans
|
||||
}
|
||||
|
||||
pub fn get(&self, k: &str) -> Option<&LoroValue> {
|
||||
match self.map.get(&k.into()) {
|
||||
Some(value) => match &value.value {
|
||||
Some(v) => Some(v),
|
||||
None => None,
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -233,7 +233,7 @@ impl TextState {
|
|||
self.rope.slice_substrings(range)
|
||||
}
|
||||
|
||||
#[cfg(not(features = "wasm"))]
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
fn apply_seq_raw(
|
||||
&mut self,
|
||||
delta: &mut Delta<SliceRanges>,
|
||||
|
@ -261,7 +261,7 @@ impl TextState {
|
|||
None
|
||||
}
|
||||
|
||||
#[cfg(features = "wasm")]
|
||||
#[cfg(feature = "wasm")]
|
||||
fn apply_seq_raw(
|
||||
&mut self,
|
||||
delta: &mut Delta<SliceRanges>,
|
||||
|
@ -283,14 +283,14 @@ impl TextState {
|
|||
let start_utf16_len = self.len_wchars();
|
||||
for value in value.0.iter() {
|
||||
let s = arena.slice_bytes(value.0.start as usize..value.0.end as usize);
|
||||
self.insert(index, std::str::from_utf8(&s).unwrap());
|
||||
self.insert_utf8(index, std::str::from_utf8(&s).unwrap());
|
||||
index += s.len();
|
||||
}
|
||||
utf16_index += self.len_wchars() - start_utf16_len;
|
||||
}
|
||||
DeltaItem::Delete { len, .. } => {
|
||||
let start_utf16_len = self.len_wchars();
|
||||
self.delete(index..index + len);
|
||||
self.delete_utf8(index..index + len);
|
||||
new_delta = new_delta.delete(start_utf16_len - self.len_wchars());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,10 @@ use std::{
|
|||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use debug_log::debug_dbg;
|
||||
use enum_as_inner::EnumAsInner;
|
||||
use fxhash::FxHashMap;
|
||||
use loro_common::ContainerType;
|
||||
use loro_common::{ContainerType, LoroResult};
|
||||
use rle::{HasLength, RleVec};
|
||||
use smallvec::smallvec;
|
||||
|
||||
|
@ -14,7 +15,7 @@ use crate::{
|
|||
change::{Change, Lamport},
|
||||
container::{
|
||||
list::list_op::InnerListOp, registry::ContainerIdx, text::text_content::SliceRanges,
|
||||
ContainerIdRaw,
|
||||
IntoContainerId,
|
||||
},
|
||||
delta::{Delta, MapValue},
|
||||
event::Diff,
|
||||
|
@ -68,6 +69,10 @@ impl Transaction {
|
|||
origin: InternalString,
|
||||
) -> Self {
|
||||
let mut state_lock = state.lock().unwrap();
|
||||
if state_lock.is_in_txn() {
|
||||
panic!("Cannot start a transaction while another one is in progress");
|
||||
}
|
||||
|
||||
let oplog_lock = oplog.lock().unwrap();
|
||||
state_lock.start_txn(origin, true);
|
||||
let arena = state_lock.arena.clone();
|
||||
|
@ -186,7 +191,7 @@ impl Transaction {
|
|||
content: RawOpContent,
|
||||
// we need extra hint to reduce calculation for utf16 text op
|
||||
hint: Option<EventHint>,
|
||||
) {
|
||||
) -> LoroResult<()> {
|
||||
let len = content.content_len();
|
||||
let op = RawOp {
|
||||
id: ID {
|
||||
|
@ -198,14 +203,16 @@ impl Transaction {
|
|||
content,
|
||||
};
|
||||
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.apply_local_op(op.clone())?;
|
||||
drop(state);
|
||||
if let Some(hint) = hint {
|
||||
self.event_hints.insert(op.id.counter, hint);
|
||||
}
|
||||
self.push_local_op_to_log(&op);
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.apply_local_op(op);
|
||||
self.next_counter += len as Counter;
|
||||
self.next_lamport += len as Lamport;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_local_op_to_log(&mut self, op: &RawOp) {
|
||||
|
@ -215,43 +222,28 @@ impl Transaction {
|
|||
|
||||
/// id can be a str, ContainerID, or ContainerIdRaw.
|
||||
/// if it's str it will use Root container, which will not be None
|
||||
pub fn get_text<I: Into<ContainerIdRaw>>(&self, id: I) -> TextHandler {
|
||||
pub fn get_text<I: IntoContainerId>(&self, id: I) -> TextHandler {
|
||||
let idx = self.get_container_idx(id, ContainerType::Text);
|
||||
TextHandler::new(idx, Arc::downgrade(&self.state))
|
||||
}
|
||||
|
||||
/// id can be a str, ContainerID, or ContainerIdRaw.
|
||||
/// if it's str it will use Root container, which will not be None
|
||||
pub fn get_list<I: Into<ContainerIdRaw>>(&self, id: I) -> ListHandler {
|
||||
pub fn get_list<I: IntoContainerId>(&self, id: I) -> ListHandler {
|
||||
let idx = self.get_container_idx(id, ContainerType::List);
|
||||
ListHandler::new(idx, Arc::downgrade(&self.state))
|
||||
}
|
||||
|
||||
/// id can be a str, ContainerID, or ContainerIdRaw.
|
||||
/// if it's str it will use Root container, which will not be None
|
||||
pub fn get_map<I: Into<ContainerIdRaw>>(&self, id: I) -> MapHandler {
|
||||
pub fn get_map<I: IntoContainerId>(&self, id: I) -> MapHandler {
|
||||
let idx = self.get_container_idx(id, ContainerType::Map);
|
||||
MapHandler::new(idx, Arc::downgrade(&self.state))
|
||||
}
|
||||
|
||||
fn get_container_idx<I: Into<ContainerIdRaw>>(
|
||||
&self,
|
||||
id: I,
|
||||
c_type: ContainerType,
|
||||
) -> ContainerIdx {
|
||||
let id: ContainerIdRaw = id.into();
|
||||
match id {
|
||||
ContainerIdRaw::Root { name } => {
|
||||
self.arena
|
||||
.register_container(&crate::container::ContainerID::Root {
|
||||
name,
|
||||
container_type: c_type,
|
||||
})
|
||||
}
|
||||
ContainerIdRaw::Normal { id: _ } => {
|
||||
self.arena.register_container(&id.with_type(c_type))
|
||||
}
|
||||
}
|
||||
fn get_container_idx<I: IntoContainerId>(&self, id: I, c_type: ContainerType) -> ContainerIdx {
|
||||
let id = id.into_container_id(&self.arena, c_type);
|
||||
self.arena.register_container(&id)
|
||||
}
|
||||
|
||||
pub fn get_value_by_idx(&self, idx: ContainerIdx) -> LoroValue {
|
||||
|
@ -351,5 +343,7 @@ fn change_to_diff(
|
|||
lamport += op.content_len() as Lamport;
|
||||
diff.push(diff_op);
|
||||
}
|
||||
|
||||
debug_dbg!(&diff);
|
||||
diff
|
||||
}
|
||||
|
|
|
@ -288,7 +288,7 @@ pub mod wasm {
|
|||
use wasm_bindgen::{JsValue, __rt::IntoJsResult};
|
||||
|
||||
use crate::{
|
||||
delta::{Delta, DeltaItem, MapDiff},
|
||||
delta::{Delta, DeltaItem, MapDelta, MapDiff},
|
||||
event::{Diff, Index, Utf16Meta},
|
||||
LoroValue,
|
||||
};
|
||||
|
@ -385,12 +385,7 @@ pub mod wasm {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
js_sys::Reflect::set(
|
||||
&obj,
|
||||
&JsValue::from_str("diff"),
|
||||
&serde_wasm_bindgen::to_value(&map).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
js_sys::Reflect::set(&obj, &JsValue::from_str("updated"), &map.into()).unwrap();
|
||||
}
|
||||
Diff::SeqRaw(text) => {
|
||||
// set type as "text"
|
||||
|
@ -431,6 +426,22 @@ pub mod wasm {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<MapDelta> for JsValue {
|
||||
fn from(value: MapDelta) -> Self {
|
||||
let obj = Object::new();
|
||||
for (key, value) in value.updated.iter() {
|
||||
js_sys::Reflect::set(
|
||||
&obj,
|
||||
&JsValue::from_str(key),
|
||||
&JsValue::from(value.value.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
obj.into_js_result().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MapDiff<LoroValue>> for JsValue {
|
||||
fn from(value: MapDiff<LoroValue>) -> Self {
|
||||
let obj = Object::new();
|
||||
|
@ -508,7 +519,7 @@ pub mod wasm {
|
|||
fn from(value: DeltaItem<String, Utf16Meta>) -> Self {
|
||||
let obj = Object::new();
|
||||
match value {
|
||||
DeltaItem::Retain { len: _len, meta } => {
|
||||
DeltaItem::Retain { len, meta: _ } => {
|
||||
js_sys::Reflect::set(
|
||||
&obj,
|
||||
&JsValue::from_str("type"),
|
||||
|
@ -518,7 +529,7 @@ pub mod wasm {
|
|||
js_sys::Reflect::set(
|
||||
&obj,
|
||||
&JsValue::from_str("len"),
|
||||
&JsValue::from_f64(meta.utf16_len.unwrap() as f64),
|
||||
&JsValue::from_f64(len as f64),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
@ -537,7 +548,7 @@ pub mod wasm {
|
|||
)
|
||||
.unwrap();
|
||||
}
|
||||
DeltaItem::Delete { len: _len, meta } => {
|
||||
DeltaItem::Delete { len, meta: _ } => {
|
||||
js_sys::Reflect::set(
|
||||
&obj,
|
||||
&JsValue::from_str("type"),
|
||||
|
@ -547,7 +558,7 @@ pub mod wasm {
|
|||
js_sys::Reflect::set(
|
||||
&obj,
|
||||
&JsValue::from_str("len"),
|
||||
&JsValue::from_f64(meta.utf16_len.unwrap() as f64),
|
||||
&JsValue::from_f64(len as f64),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ loro-internal = { path = "../loro-internal", features = ["wasm"] }
|
|||
wasm-bindgen = "0.2.83"
|
||||
serde-wasm-bindgen = { version = "0.5.0" }
|
||||
console_error_panic_hook = { version = "0.1.6", optional = true }
|
||||
getrandom = { version = "0.2.10", features = ["js"] }
|
||||
debug-log = { version = "0.2.1", features = ["wasm"] }
|
||||
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
|
|
|
@ -19,12 +19,13 @@ const LoroWasmDir = resolve(__dirname, "..");
|
|||
console.log(LoroWasmDir);
|
||||
async function build() {
|
||||
await cargoBuild();
|
||||
if (Deno.args[1] != null) {
|
||||
if (!TARGETS.includes(Deno.args[1])) {
|
||||
throw new Error(`Invalid target ${Deno.args[1]}`);
|
||||
const target = Deno.args[1];
|
||||
if (target != null) {
|
||||
if (!TARGETS.includes(target)) {
|
||||
throw new Error(`Invalid target ${target}`);
|
||||
}
|
||||
|
||||
buildTarget(Deno.args[1]);
|
||||
buildTarget(target);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -74,13 +75,29 @@ async function buildTarget(target: string) {
|
|||
try {
|
||||
await Deno.remove(targetDirPath, { recursive: true });
|
||||
console.log("Clear directory " + targetDirPath);
|
||||
} catch (e) {}
|
||||
} catch (_e) {
|
||||
//
|
||||
}
|
||||
|
||||
const cmd =
|
||||
`wasm-bindgen --weak-refs --target ${target} --out-dir ${target} ../../target/wasm32-unknown-unknown/${profileDir}/loro_wasm.wasm`;
|
||||
console.log(">", cmd);
|
||||
await Deno.run({ cmd: cmd.split(" "), cwd: LoroWasmDir }).status();
|
||||
console.log();
|
||||
|
||||
if (target === "nodejs") {
|
||||
console.log("🔨 Patching nodejs target");
|
||||
const patch = await Deno.readTextFile(
|
||||
resolve(__dirname, "./nodejs_patch.js"),
|
||||
);
|
||||
const wasm = await Deno.readTextFile(
|
||||
resolve(targetDirPath, "loro_wasm.js"),
|
||||
);
|
||||
await Deno.writeTextFile(
|
||||
resolve(targetDirPath, "loro_wasm.js"),
|
||||
wasm + "\n" + patch,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
build();
|
||||
|
|
2
crates/loro-wasm/scripts/nodejs_patch.js
Normal file
2
crates/loro-wasm/scripts/nodejs_patch.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
const { webcrypto } = require("crypto");
|
||||
globalThis.crypto = webcrypto;
|
|
@ -1,23 +1,20 @@
|
|||
use js_sys::{Array, Object, Promise, Reflect, Uint8Array};
|
||||
use js_sys::{Array, Promise, Uint8Array};
|
||||
use loro_internal::{
|
||||
configure::{Configure, SecureRandomGenerator},
|
||||
container::{registry::ContainerWrapper, ContainerID},
|
||||
context::Context,
|
||||
configure::SecureRandomGenerator,
|
||||
container::ContainerID,
|
||||
event::{Diff, Path},
|
||||
log_store::GcConfig,
|
||||
obs::SubID,
|
||||
refactor::handler::{ListHandler, MapHandler, TextHandler},
|
||||
refactor::txn::Transaction as Txn,
|
||||
version::Frontiers,
|
||||
ContainerType, List, LoroCore, Map, Origin, Text, Transact, TransactionWrap, VersionVector,
|
||||
ContainerType, DiffEvent, LoroDoc, VersionVector,
|
||||
};
|
||||
use std::{cell::RefCell, cmp::Ordering, ops::Deref, rc::Rc, sync::Arc};
|
||||
use wasm_bindgen::{
|
||||
__rt::{IntoJsResult, RefMut},
|
||||
prelude::*,
|
||||
};
|
||||
use wasm_bindgen::{__rt::IntoJsResult, prelude::*};
|
||||
mod log;
|
||||
mod prelim;
|
||||
pub use prelim::{PrelimList, PrelimMap, PrelimText};
|
||||
|
||||
use crate::convert::js_try_to_prelim;
|
||||
mod convert;
|
||||
|
||||
#[wasm_bindgen(js_name = setPanicHook)]
|
||||
|
@ -32,13 +29,18 @@ pub fn set_panic_hook() {
|
|||
console_error_panic_hook::set_once();
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = setDebug)]
|
||||
pub fn set_debug(filter: &str) {
|
||||
debug_log::set_debug(filter)
|
||||
}
|
||||
|
||||
type JsResult<T> = Result<T, JsValue>;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Loro(RefCell<LoroCore>);
|
||||
pub struct Loro(RefCell<LoroDoc>);
|
||||
|
||||
impl Deref for Loro {
|
||||
type Target = RefCell<LoroCore>;
|
||||
type Target = RefCell<LoroDoc>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
|
@ -79,6 +81,8 @@ mod observer {
|
|||
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
use crate::JsResult;
|
||||
|
||||
/// We need to wrap the observer function in a struct so that we can implement Send for it.
|
||||
/// But it's not Send essentially, so we need to check it manually in runtime.
|
||||
#[derive(Clone)]
|
||||
|
@ -95,9 +99,9 @@ mod observer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn call1(&self, arg: &JsValue) {
|
||||
pub fn call1(&self, arg: &JsValue) -> JsResult<JsValue> {
|
||||
if std::thread::current().id() == self.thread {
|
||||
self.f.call1(&JsValue::NULL, arg).unwrap();
|
||||
self.f.call1(&JsValue::NULL, arg)
|
||||
} else {
|
||||
panic!("Observer called from different thread")
|
||||
}
|
||||
|
@ -111,17 +115,20 @@ mod observer {
|
|||
impl Loro {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
let cfg: Configure = Configure {
|
||||
gc: GcConfig::default().with_gc(false),
|
||||
get_time: || js_sys::Date::now() as i64,
|
||||
rand: Arc::new(MathRandom),
|
||||
};
|
||||
Self(RefCell::new(LoroCore::new(cfg, None)))
|
||||
Self(RefCell::new(LoroDoc::new()))
|
||||
}
|
||||
|
||||
pub fn txn(&self) -> Transaction {
|
||||
Transaction(self.0.borrow().txn().unwrap())
|
||||
}
|
||||
|
||||
pub fn txn_with_origin(&self, origin: &str) -> Transaction {
|
||||
Transaction(self.0.borrow().txn_with_origin(origin).unwrap())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "clientId", method, getter)]
|
||||
pub fn client_id(&self) -> u64 {
|
||||
self.0.borrow().client_id()
|
||||
self.0.borrow().peer_id()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "getText")]
|
||||
|
@ -146,26 +153,20 @@ impl Loro {
|
|||
pub fn get_container_by_id(&self, container_id: JsContainerID) -> JsResult<JsValue> {
|
||||
let container_id: ContainerID = container_id.to_owned().try_into()?;
|
||||
let ty = container_id.container_type();
|
||||
let container = self.0.borrow_mut().get_container(&container_id);
|
||||
if let Some(container) = container {
|
||||
let client_id = self.0.borrow().client_id();
|
||||
Ok(match ty {
|
||||
ContainerType::Text => {
|
||||
let text: Text = Text::from_instance(container, client_id);
|
||||
LoroText(text).into()
|
||||
}
|
||||
ContainerType::Map => {
|
||||
let map: Map = Map::from_instance(container, client_id);
|
||||
LoroMap(map).into()
|
||||
}
|
||||
ContainerType::List => {
|
||||
let list: List = List::from_instance(container, client_id);
|
||||
LoroList(list).into()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(JsValue::from_str("Container not found"))
|
||||
}
|
||||
Ok(match ty {
|
||||
ContainerType::Text => {
|
||||
let text = self.0.borrow().get_text(container_id);
|
||||
LoroText(text).into()
|
||||
}
|
||||
ContainerType::Map => {
|
||||
let map = self.0.borrow().get_map(container_id);
|
||||
LoroMap(map).into()
|
||||
}
|
||||
ContainerType::List => {
|
||||
let list = self.0.borrow().get_list(container_id);
|
||||
LoroList(list).into()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -194,7 +195,7 @@ impl Loro {
|
|||
|
||||
#[wasm_bindgen(js_name = "exportSnapshot")]
|
||||
pub fn export_snapshot(&self) -> JsResult<Vec<u8>> {
|
||||
Ok(self.0.borrow().encode_all())
|
||||
Ok(self.0.borrow().export_snapshot())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(skip_typescript, js_name = "exportFrom")]
|
||||
|
@ -211,11 +212,11 @@ impl Loro {
|
|||
None => Default::default(),
|
||||
};
|
||||
|
||||
Ok(self.0.borrow().encode_from(vv))
|
||||
Ok(self.0.borrow().export_from(&vv))
|
||||
}
|
||||
|
||||
pub fn import(&self, update_or_snapshot: &[u8]) -> JsResult<()> {
|
||||
self.0.borrow_mut().decode(update_or_snapshot)?;
|
||||
self.0.borrow_mut().import(update_or_snapshot)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -231,7 +232,7 @@ impl Loro {
|
|||
if data.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
Ok(self.0.borrow_mut().decode_batch(&data)?)
|
||||
Ok(self.0.borrow_mut().import_batch(&data)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "toJson")]
|
||||
|
@ -243,45 +244,55 @@ impl Loro {
|
|||
// TODO: convert event and event sub config
|
||||
pub fn subscribe(&self, f: js_sys::Function) -> u32 {
|
||||
let observer = observer::Observer::new(f);
|
||||
self.0.borrow_mut().subscribe_deep(Box::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e);
|
||||
}))
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.subscribe_deep(Arc::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e);
|
||||
}))
|
||||
.into_u32()
|
||||
}
|
||||
|
||||
pub fn unsubscribe(&self, subscription: u32) {
|
||||
self.0.borrow_mut().unsubscribe_deep(subscription)
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.unsubscribe(SubID::from_u32(subscription))
|
||||
}
|
||||
|
||||
/// It's the caller's responsibility to commit and free the transaction
|
||||
#[wasm_bindgen(js_name = "__raw__transactionWithOrigin")]
|
||||
pub fn transaction_with_origin(&self, origin: &JsOrigin, f: js_sys::Function) -> JsResult<()> {
|
||||
let origin = origin.as_string().map(Origin::from);
|
||||
let txn = self.0.borrow().transact_with(origin);
|
||||
pub fn transaction_with_origin(
|
||||
&self,
|
||||
origin: &JsOrigin,
|
||||
f: js_sys::Function,
|
||||
) -> JsResult<JsValue> {
|
||||
let origin = origin.as_string().unwrap();
|
||||
debug_log::group!("transaction with origin: {}", origin);
|
||||
let txn = self.0.borrow().txn_with_origin(&origin)?;
|
||||
let js_txn = JsValue::from(Transaction(txn));
|
||||
f.call1(&JsValue::NULL, &js_txn)?;
|
||||
Ok(())
|
||||
let ans = f.call1(&JsValue::NULL, &js_txn);
|
||||
debug_log::group_end!();
|
||||
ans
|
||||
}
|
||||
}
|
||||
|
||||
fn call_after_micro_task(ob: observer::Observer, e: &loro_internal::event::Event) {
|
||||
let e = e.clone();
|
||||
fn call_after_micro_task(ob: observer::Observer, e: DiffEvent) {
|
||||
let promise = Promise::resolve(&JsValue::NULL);
|
||||
type C = Closure<dyn FnMut(JsValue)>;
|
||||
let drop_handler: Rc<RefCell<Option<C>>> = Rc::new(RefCell::new(None));
|
||||
let copy = drop_handler.clone();
|
||||
let event = Event {
|
||||
local: e.doc.local,
|
||||
origin: e.doc.origin.to_string(),
|
||||
target: e.container.id.clone(),
|
||||
diff: Either::A(e.container.diff.to_owned()),
|
||||
path: Either::A(e.container.path.iter().map(|x| x.1.clone()).collect()),
|
||||
};
|
||||
let closure = Closure::once(move |_: JsValue| {
|
||||
ob.call1(
|
||||
&Event {
|
||||
local: e.local,
|
||||
origin: e.origin.clone(),
|
||||
target: e.target.clone(),
|
||||
diff: Either::A(e.diff),
|
||||
path: Either::A(e.absolute_path.clone()),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
let ans = ob.call1(&event.into());
|
||||
drop(copy);
|
||||
if let Err(e) = ans {
|
||||
console_log!("Error when calling observer: {:#?}", e);
|
||||
}
|
||||
});
|
||||
let _ = promise.then(&closure);
|
||||
drop_handler.borrow_mut().replace(closure);
|
||||
|
@ -301,7 +312,7 @@ enum Either<A, B> {
|
|||
#[wasm_bindgen]
|
||||
pub struct Event {
|
||||
pub local: bool,
|
||||
origin: Option<Origin>,
|
||||
origin: String,
|
||||
target: ContainerID,
|
||||
diff: Either<Diff, JsValue>,
|
||||
path: Either<Path, JsValue>,
|
||||
|
@ -310,10 +321,8 @@ pub struct Event {
|
|||
#[wasm_bindgen]
|
||||
impl Event {
|
||||
#[wasm_bindgen(js_name = "origin", method, getter)]
|
||||
pub fn origin(&self) -> Option<JsOrigin> {
|
||||
self.origin
|
||||
.as_ref()
|
||||
.map(|o| JsValue::from_str(o.as_str()).into())
|
||||
pub fn origin(&self) -> String {
|
||||
self.origin.clone()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter)]
|
||||
|
@ -351,62 +360,38 @@ impl Event {
|
|||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Transaction(TransactionWrap);
|
||||
pub struct Transaction(Txn);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Transaction {
|
||||
pub fn commit(&self) -> JsResult<()> {
|
||||
pub fn commit(self) -> JsResult<()> {
|
||||
self.0.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_transaction_mut(txn: &JsTransaction) -> TransactionWrap {
|
||||
use wasm_bindgen::convert::RefMutFromWasmAbi;
|
||||
let js: &JsValue = txn.as_ref();
|
||||
if js.is_undefined() || js.is_null() {
|
||||
panic!("you should input Transaction");
|
||||
} else {
|
||||
let ctor_name = Object::get_prototype_of(js).constructor().name();
|
||||
if ctor_name == "Transaction" {
|
||||
let ptr = Reflect::get(js, &JsValue::from_str("ptr")).unwrap();
|
||||
let ptr = ptr.as_f64().ok_or(JsValue::NULL).unwrap() as u32;
|
||||
let txn: RefMut<Transaction> = unsafe { Transaction::ref_mut_from_abi(ptr) };
|
||||
txn.0.transact()
|
||||
} else if ctor_name == "Loro" {
|
||||
let ptr = Reflect::get(js, &JsValue::from_str("ptr")).unwrap();
|
||||
let ptr = ptr.as_f64().ok_or(JsValue::NULL).unwrap() as u32;
|
||||
let loro: RefMut<Loro> = unsafe { Loro::ref_mut_from_abi(ptr) };
|
||||
let loro = loro.0.borrow();
|
||||
loro.transact()
|
||||
} else {
|
||||
panic!("you should input Transaction");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct LoroText(Text);
|
||||
pub struct LoroText(TextHandler);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl LoroText {
|
||||
pub fn __loro_insert(&mut self, txn: &Loro, index: usize, content: &str) -> JsResult<()> {
|
||||
self.0.insert_utf16(&*txn.0.borrow(), index, content)?;
|
||||
pub fn __txn_insert(
|
||||
&mut self,
|
||||
txn: &mut Transaction,
|
||||
index: usize,
|
||||
content: &str,
|
||||
) -> JsResult<()> {
|
||||
self.0.insert_utf16(&mut txn.0, index, content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn __loro_delete(&mut self, txn: &Loro, index: usize, len: usize) -> JsResult<()> {
|
||||
self.0.delete_utf16(&*txn.0.borrow(), index, len)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn __txn_insert(&mut self, txn: &Transaction, index: usize, content: &str) -> JsResult<()> {
|
||||
self.0.insert_utf16(&txn.0, index, content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn __txn_delete(&mut self, txn: &Transaction, index: usize, len: usize) -> JsResult<()> {
|
||||
self.0.delete_utf16(&txn.0, index, len)?;
|
||||
pub fn __txn_delete(
|
||||
&mut self,
|
||||
txn: &mut Transaction,
|
||||
index: usize,
|
||||
len: usize,
|
||||
) -> JsResult<()> {
|
||||
self.0.delete_utf16(&mut txn.0, index, len)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -427,69 +412,44 @@ impl LoroText {
|
|||
self.0.len()
|
||||
}
|
||||
|
||||
pub fn subscribe(&self, txn: &JsTransaction, f: js_sys::Function) -> JsResult<u32> {
|
||||
pub fn subscribe(&self, loro: &Loro, f: js_sys::Function) -> JsResult<u32> {
|
||||
let observer = observer::Observer::new(f);
|
||||
let txn = get_transaction_mut(txn);
|
||||
let ans = self.0.subscribe(
|
||||
&txn,
|
||||
Box::new(move |e| {
|
||||
let ans = loro.0.borrow_mut().subscribe(
|
||||
&self.0.id(),
|
||||
Arc::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e);
|
||||
}),
|
||||
)?;
|
||||
Ok(ans)
|
||||
);
|
||||
|
||||
Ok(ans.into_u32())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "subscribeOnce")]
|
||||
pub fn subscribe_once(&self, txn: &JsTransaction, f: js_sys::Function) -> JsResult<u32> {
|
||||
let observer = observer::Observer::new(f);
|
||||
let txn = get_transaction_mut(txn);
|
||||
let ans = self.0.subscribe_once(
|
||||
&txn,
|
||||
Box::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e);
|
||||
}),
|
||||
)?;
|
||||
Ok(ans)
|
||||
}
|
||||
|
||||
pub fn unsubscribe(&self, txn: &JsTransaction, subscription: u32) -> JsResult<()> {
|
||||
let txn = get_transaction_mut(txn);
|
||||
self.0.unsubscribe(&txn, subscription)?;
|
||||
pub fn unsubscribe(&self, loro: &Loro, subscription: u32) -> JsResult<()> {
|
||||
loro.0
|
||||
.borrow_mut()
|
||||
.unsubscribe(SubID::from_u32(subscription));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct LoroMap(Map);
|
||||
pub struct LoroMap(MapHandler);
|
||||
const CONTAINER_TYPE_ERR: &str = "Invalid container type, only supports Text, Map, List";
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl LoroMap {
|
||||
pub fn __loro_insert(&mut self, txn: &Loro, key: &str, value: JsValue) -> JsResult<()> {
|
||||
if let Some(v) = js_try_to_prelim(&value) {
|
||||
self.0.insert(&*txn.0.borrow(), key, v)?;
|
||||
} else {
|
||||
self.0.insert(&*txn.0.borrow(), key, value)?;
|
||||
};
|
||||
pub fn __txn_insert(
|
||||
&mut self,
|
||||
txn: &mut Transaction,
|
||||
key: &str,
|
||||
value: JsValue,
|
||||
) -> JsResult<()> {
|
||||
self.0.insert(&mut txn.0, key, value.into())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn __txn_insert(&mut self, txn: &Transaction, key: &str, value: JsValue) -> JsResult<()> {
|
||||
if let Some(v) = js_try_to_prelim(&value) {
|
||||
self.0.insert(&txn.0, key, v)?;
|
||||
} else {
|
||||
self.0.insert(&txn.0, key, value)?;
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn __loro_delete(&mut self, txn: &Loro, key: &str) -> JsResult<()> {
|
||||
self.0.delete(&*txn.0.borrow(), key)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn __txn_delete(&mut self, txn: &Transaction, key: &str) -> JsResult<()> {
|
||||
self.0.delete(&txn.0, key)?;
|
||||
pub fn __txn_delete(&mut self, txn: &mut Transaction, key: &str) -> JsResult<()> {
|
||||
self.0.delete(&mut txn.0, key)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -510,85 +470,44 @@ impl LoroMap {
|
|||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "getValueDeep")]
|
||||
pub fn get_value_deep(&self, ctx: &Loro) -> JsValue {
|
||||
self.0.get_value_deep(ctx.deref()).into()
|
||||
pub fn get_value_deep(&self) -> JsValue {
|
||||
todo!()
|
||||
// self.0.get_value_deep(ctx.deref()).into()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "insertContainer")]
|
||||
pub fn insert_container(
|
||||
&mut self,
|
||||
txn: &JsTransaction,
|
||||
txn: &mut Transaction,
|
||||
key: &str,
|
||||
container_type: &str,
|
||||
) -> JsResult<JsValue> {
|
||||
let txn = get_transaction_mut(txn);
|
||||
let type_ = match container_type {
|
||||
"text" | "Text" => ContainerType::Text,
|
||||
"map" | "Map" => ContainerType::Map,
|
||||
"list" | "List" => ContainerType::List,
|
||||
_ => return Err(JsValue::from_str(CONTAINER_TYPE_ERR)),
|
||||
};
|
||||
let idx = self.0.insert(&txn, key, type_)?.unwrap();
|
||||
let idx = self.0.insert_container(&mut txn.0, key, type_)?;
|
||||
|
||||
let container = match type_ {
|
||||
ContainerType::Text => {
|
||||
let x = txn.get_text_by_idx(idx).unwrap();
|
||||
LoroText(x).into()
|
||||
}
|
||||
ContainerType::Map => {
|
||||
let x = txn.get_map_by_idx(idx).unwrap();
|
||||
LoroMap(x).into()
|
||||
}
|
||||
ContainerType::List => {
|
||||
let x = txn.get_list_by_idx(idx).unwrap();
|
||||
LoroList(x).into()
|
||||
}
|
||||
ContainerType::Text => LoroText(txn.0.get_text(idx)).into(),
|
||||
ContainerType::Map => LoroMap(txn.0.get_map(idx)).into(),
|
||||
ContainerType::List => LoroList(txn.0.get_list(idx)).into(),
|
||||
};
|
||||
Ok(container)
|
||||
}
|
||||
|
||||
pub fn subscribe(&self, txn: &JsTransaction, f: js_sys::Function) -> JsResult<u32> {
|
||||
pub fn subscribe(&self, loro: &Loro, f: js_sys::Function) -> JsResult<u32> {
|
||||
let observer = observer::Observer::new(f);
|
||||
let txn = get_transaction_mut(txn);
|
||||
let ans = self.0.subscribe(
|
||||
&txn,
|
||||
Box::new(move |e| {
|
||||
let id = loro.0.borrow_mut().subscribe(
|
||||
&self.0.id(),
|
||||
Arc::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e);
|
||||
}),
|
||||
)?;
|
||||
Ok(ans)
|
||||
}
|
||||
);
|
||||
|
||||
#[wasm_bindgen(js_name = "subscribeOnce")]
|
||||
pub fn subscribe_once(&self, txn: &JsTransaction, f: js_sys::Function) -> JsResult<u32> {
|
||||
let observer = observer::Observer::new(f);
|
||||
let txn = get_transaction_mut(txn);
|
||||
let ans = self.0.subscribe_once(
|
||||
&txn,
|
||||
Box::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e);
|
||||
}),
|
||||
)?;
|
||||
Ok(ans)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "subscribeDeep")]
|
||||
pub fn subscribe_deep(&self, txn: &JsTransaction, f: js_sys::Function) -> JsResult<u32> {
|
||||
let observer = observer::Observer::new(f);
|
||||
let txn = get_transaction_mut(txn);
|
||||
let ans = self.0.subscribe_deep(
|
||||
&txn,
|
||||
Box::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e);
|
||||
}),
|
||||
)?;
|
||||
Ok(ans)
|
||||
}
|
||||
|
||||
pub fn unsubscribe(&self, txn: &JsTransaction, subscription: u32) -> JsResult<()> {
|
||||
let txn = get_transaction_mut(txn);
|
||||
self.0.unsubscribe(&txn, subscription)?;
|
||||
Ok(())
|
||||
Ok(id.into_u32())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "size", method, getter)]
|
||||
|
@ -598,40 +517,27 @@ impl LoroMap {
|
|||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct LoroList(List);
|
||||
pub struct LoroList(ListHandler);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl LoroList {
|
||||
pub fn __loro_insert(&mut self, loro: &Loro, index: usize, value: JsValue) -> JsResult<()> {
|
||||
if let Some(v) = js_try_to_prelim(&value) {
|
||||
self.0.insert(&*loro.0.borrow(), index, v)?;
|
||||
} else {
|
||||
self.0.insert(&*loro.0.borrow(), index, value)?;
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn __txn_insert(
|
||||
&mut self,
|
||||
txn: &Transaction,
|
||||
txn: &mut Transaction,
|
||||
index: usize,
|
||||
value: JsValue,
|
||||
) -> JsResult<()> {
|
||||
if let Some(v) = js_try_to_prelim(&value) {
|
||||
self.0.insert(&txn.0, index, v)?;
|
||||
} else {
|
||||
self.0.insert(&txn.0, index, value)?;
|
||||
};
|
||||
self.0.insert(&mut txn.0, index, value.into())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn __loro_delete(&mut self, loro: &Loro, index: usize, len: usize) -> JsResult<()> {
|
||||
self.0.delete(&*loro.0.borrow(), index, len)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn __txn_delete(&mut self, loro: &Transaction, index: usize, len: usize) -> JsResult<()> {
|
||||
self.0.delete(&loro.0, index, len)?;
|
||||
pub fn __txn_delete(
|
||||
&mut self,
|
||||
txn: &mut Transaction,
|
||||
index: usize,
|
||||
len: usize,
|
||||
) -> JsResult<()> {
|
||||
self.0.delete(&mut txn.0, index, len)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -651,85 +557,43 @@ impl LoroList {
|
|||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "getValueDeep")]
|
||||
pub fn get_value_deep(&self, ctx: &Loro) -> JsValue {
|
||||
let value = self.0.get_value_deep(ctx.deref());
|
||||
value.into()
|
||||
pub fn get_value_deep(&self) -> JsValue {
|
||||
todo!()
|
||||
// let value = self.0.get_value_deep(ctx.deref());
|
||||
// value.into()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "insertContainer")]
|
||||
pub fn insert_container(
|
||||
&mut self,
|
||||
txn: &JsTransaction,
|
||||
txn: &mut Transaction,
|
||||
pos: usize,
|
||||
container: &str,
|
||||
) -> JsResult<JsValue> {
|
||||
let txn = get_transaction_mut(txn);
|
||||
let _type = match container {
|
||||
"text" | "Text" => ContainerType::Text,
|
||||
"map" | "Map" => ContainerType::Map,
|
||||
"list" | "List" => ContainerType::List,
|
||||
_ => return Err(JsValue::from_str(CONTAINER_TYPE_ERR)),
|
||||
};
|
||||
let idx = self.0.insert(&txn, pos, _type)?.unwrap();
|
||||
let idx = self.0.insert_container(&mut txn.0, pos, _type)?;
|
||||
let container = match _type {
|
||||
ContainerType::Text => {
|
||||
let x = txn.get_text_by_idx(idx).unwrap();
|
||||
LoroText(x).into()
|
||||
}
|
||||
ContainerType::Map => {
|
||||
let x = txn.get_map_by_idx(idx).unwrap();
|
||||
LoroMap(x).into()
|
||||
}
|
||||
ContainerType::List => {
|
||||
let x = txn.get_list_by_idx(idx).unwrap();
|
||||
LoroList(x).into()
|
||||
}
|
||||
ContainerType::Text => LoroText(txn.0.get_text(idx)).into(),
|
||||
ContainerType::Map => LoroMap(txn.0.get_map(idx)).into(),
|
||||
ContainerType::List => LoroList(txn.0.get_list(idx)).into(),
|
||||
};
|
||||
Ok(container)
|
||||
}
|
||||
|
||||
pub fn subscribe(&self, txn: &JsTransaction, f: js_sys::Function) -> JsResult<u32> {
|
||||
pub fn subscribe(&self, loro: &Loro, f: js_sys::Function) -> JsResult<u32> {
|
||||
let observer = observer::Observer::new(f);
|
||||
let txn = get_transaction_mut(txn);
|
||||
let ans = self.0.subscribe(
|
||||
&txn,
|
||||
Box::new(move |e| {
|
||||
let ans = loro.0.borrow_mut().subscribe(
|
||||
&self.0.id(),
|
||||
Arc::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e);
|
||||
}),
|
||||
)?;
|
||||
Ok(ans)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "subscribeOnce")]
|
||||
pub fn subscribe_once(&self, txn: &JsTransaction, f: js_sys::Function) -> JsResult<u32> {
|
||||
let observer = observer::Observer::new(f);
|
||||
let txn = get_transaction_mut(txn);
|
||||
let ans = self.0.subscribe_once(
|
||||
&txn,
|
||||
Box::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e);
|
||||
}),
|
||||
)?;
|
||||
Ok(ans)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "subscribeDeep")]
|
||||
pub fn subscribe_deep(&self, txn: &JsTransaction, f: js_sys::Function) -> JsResult<u32> {
|
||||
let observer = observer::Observer::new(f);
|
||||
let txn = get_transaction_mut(txn);
|
||||
let ans = self.0.subscribe_deep(
|
||||
&txn,
|
||||
Box::new(move |e| {
|
||||
call_after_micro_task(observer.clone(), e);
|
||||
}),
|
||||
)?;
|
||||
Ok(ans)
|
||||
}
|
||||
|
||||
pub fn unsubscribe(&self, txn: &JsTransaction, subscription: u32) -> JsResult<()> {
|
||||
let txn = get_transaction_mut(txn);
|
||||
self.0.unsubscribe(&txn, subscription)?;
|
||||
Ok(())
|
||||
);
|
||||
Ok(ans.into_u32())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "length", method, getter)]
|
||||
|
|
7
loro-js/.vscode/settings.json
vendored
7
loro-js/.vscode/settings.json
vendored
|
@ -1,3 +1,8 @@
|
|||
{
|
||||
"deno.enable": false
|
||||
"deno.enable": false,
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features",
|
||||
"editor.formatOnSave": true,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,10 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@typescript-eslint/parser": "^6.2.0",
|
||||
"esbuild": "^0.17.12",
|
||||
"eslint": "^8.46.0",
|
||||
"prettier": "^3.0.0",
|
||||
"rollup": "^3.20.1",
|
||||
"rollup-plugin-dts": "^5.3.0",
|
||||
"rollup-plugin-esbuild": "^5.0.0",
|
||||
|
|
|
@ -23,11 +23,10 @@ import {
|
|||
export type { ContainerID, ContainerType } from "loro-wasm";
|
||||
|
||||
Loro.prototype.transact = function (cb, origin) {
|
||||
this.__raw__transactionWithOrigin(origin, (txn: Transaction) => {
|
||||
return this.__raw__transactionWithOrigin(origin || "", (txn: Transaction) => {
|
||||
try {
|
||||
cb(txn);
|
||||
return cb(txn);
|
||||
} finally {
|
||||
txn.commit();
|
||||
txn.free();
|
||||
}
|
||||
});
|
||||
|
@ -55,51 +54,27 @@ LoroMap.prototype.getTyped = function (loro, key) {
|
|||
LoroMap.prototype.setTyped = LoroMap.prototype.set;
|
||||
|
||||
LoroText.prototype.insert = function (txn, pos, text) {
|
||||
if (txn instanceof Loro) {
|
||||
this.__loro_insert(txn, pos, text);
|
||||
} else {
|
||||
this.__txn_insert(txn, pos, text);
|
||||
}
|
||||
this.__txn_insert(txn, pos, text);
|
||||
};
|
||||
|
||||
LoroText.prototype.delete = function (txn, pos, len) {
|
||||
if (txn instanceof Loro) {
|
||||
this.__loro_delete(txn, pos, len);
|
||||
} else {
|
||||
this.__txn_delete(txn, pos, len);
|
||||
}
|
||||
this.__txn_delete(txn, pos, len);
|
||||
};
|
||||
|
||||
LoroList.prototype.insert = function (txn, pos, len) {
|
||||
if (txn instanceof Loro) {
|
||||
this.__loro_insert(txn, pos, len);
|
||||
} else {
|
||||
this.__txn_insert(txn, pos, len);
|
||||
}
|
||||
this.__txn_insert(txn, pos, len);
|
||||
};
|
||||
|
||||
LoroList.prototype.delete = function (txn, pos, len) {
|
||||
if (txn instanceof Loro) {
|
||||
this.__loro_delete(txn, pos, len);
|
||||
} else {
|
||||
this.__txn_delete(txn, pos, len);
|
||||
}
|
||||
this.__txn_delete(txn, pos, len);
|
||||
};
|
||||
|
||||
LoroMap.prototype.set = function (txn, key, value) {
|
||||
if (txn instanceof Loro) {
|
||||
this.__loro_insert(txn, key, value);
|
||||
} else {
|
||||
this.__txn_insert(txn, key, value);
|
||||
}
|
||||
this.__txn_insert(txn, key, value);
|
||||
};
|
||||
|
||||
LoroMap.prototype.delete = function (txn, key) {
|
||||
if (txn instanceof Loro) {
|
||||
this.__loro_delete(txn, key);
|
||||
} else {
|
||||
this.__txn_delete(txn, key);
|
||||
}
|
||||
this.__txn_delete(txn, key);
|
||||
};
|
||||
|
||||
export type Value =
|
||||
|
@ -136,14 +111,7 @@ export type TextDiff = {
|
|||
|
||||
export type MapDiff = {
|
||||
type: "map";
|
||||
diff: {
|
||||
added: Record<string, Value>;
|
||||
deleted: Record<string, Value>;
|
||||
updated: Record<string, {
|
||||
old: Value;
|
||||
new: Value;
|
||||
}>;
|
||||
};
|
||||
updated: Record<string, Value | undefined>;
|
||||
};
|
||||
|
||||
export type Diff = ListDiff | TextDiff | MapDiff;
|
||||
|
@ -191,7 +159,7 @@ export { Loro };
|
|||
declare module "loro-wasm" {
|
||||
interface Loro {
|
||||
subscribe(listener: Listener): number;
|
||||
transact(f: (tx: Transaction) => void, origin?: string): void;
|
||||
transact<T>(f: (tx: Transaction) => T, origin?: string): T;
|
||||
}
|
||||
|
||||
interface Loro<T extends Record<string, any> = Record<string, any>> {
|
||||
|
@ -205,22 +173,22 @@ declare module "loro-wasm" {
|
|||
|
||||
interface LoroList<T extends any[] = any[]> {
|
||||
insertContainer(
|
||||
txn: Transaction | Loro,
|
||||
txn: Transaction,
|
||||
pos: number,
|
||||
container: "Map",
|
||||
): LoroMap;
|
||||
insertContainer(
|
||||
txn: Transaction | Loro,
|
||||
txn: Transaction,
|
||||
pos: number,
|
||||
container: "List",
|
||||
): LoroList;
|
||||
insertContainer(
|
||||
txn: Transaction | Loro,
|
||||
txn: Transaction,
|
||||
pos: number,
|
||||
container: "Text",
|
||||
): LoroText;
|
||||
insertContainer(
|
||||
txn: Transaction | Loro,
|
||||
txn: Transaction,
|
||||
pos: number,
|
||||
container: string,
|
||||
): never;
|
||||
|
@ -228,35 +196,33 @@ declare module "loro-wasm" {
|
|||
get(index: number): Value;
|
||||
getTyped<Key extends (keyof T) & number>(loro: Loro, index: Key): T[Key];
|
||||
insertTyped<Key extends (keyof T) & number>(
|
||||
txn: Transaction | Loro,
|
||||
txn: Transaction,
|
||||
pos: Key,
|
||||
value: T[Key],
|
||||
): void;
|
||||
insert(txn: Transaction | Loro, pos: number, value: Value | Prelim): void;
|
||||
delete(txn: Transaction | Loro, pos: number, len: number): void;
|
||||
subscribe(txn: Transaction | Loro, listener: Listener): number;
|
||||
subscribeDeep(txn: Transaction | Loro, listener: Listener): number;
|
||||
subscribeOnce(txn: Transaction | Loro, listener: Listener): number;
|
||||
insert(txn: Transaction, pos: number, value: Value | Prelim): void;
|
||||
delete(txn: Transaction, pos: number, len: number): void;
|
||||
subscribe(txn: Loro, listener: Listener): number;
|
||||
}
|
||||
|
||||
interface LoroMap<T extends Record<string, any> = Record<string, any>> {
|
||||
insertContainer(
|
||||
txn: Transaction | Loro,
|
||||
txn: Transaction,
|
||||
key: string,
|
||||
container_type: "Map",
|
||||
): LoroMap;
|
||||
insertContainer(
|
||||
txn: Transaction | Loro,
|
||||
txn: Transaction,
|
||||
key: string,
|
||||
container_type: "List",
|
||||
): LoroList;
|
||||
insertContainer(
|
||||
txn: Transaction | Loro,
|
||||
txn: Transaction,
|
||||
key: string,
|
||||
container_type: "Text",
|
||||
): LoroText;
|
||||
insertContainer(
|
||||
txn: Transaction | Loro,
|
||||
txn: Transaction,
|
||||
key: string,
|
||||
container_type: string,
|
||||
): never;
|
||||
|
@ -266,23 +232,19 @@ declare module "loro-wasm" {
|
|||
txn: Loro,
|
||||
key: Key,
|
||||
): T[Key];
|
||||
set(txn: Transaction | Loro, key: string, value: Value | Prelim): void;
|
||||
set(txn: Transaction, key: string, value: Value | Prelim): void;
|
||||
setTyped<Key extends (keyof T) & string>(
|
||||
txn: Transaction | Loro,
|
||||
txn: Transaction,
|
||||
key: Key,
|
||||
value: T[Key],
|
||||
): void;
|
||||
delete(txn: Transaction | Loro, key: string): void;
|
||||
subscribe(txn: Transaction | Loro, listener: Listener): number;
|
||||
subscribeDeep(txn: Transaction | Loro, listener: Listener): number;
|
||||
subscribeOnce(txn: Transaction | Loro, listener: Listener): number;
|
||||
delete(txn: Transaction, key: string): void;
|
||||
subscribe(txn: Loro, listener: Listener): number;
|
||||
}
|
||||
|
||||
interface LoroText {
|
||||
insert(txn: Transaction | Loro, pos: number, text: string): void;
|
||||
delete(txn: Transaction | Loro, pos: number, len: number): void;
|
||||
subscribe(txn: Transaction | Loro, listener: Listener): number;
|
||||
subscribeDeep(txn: Transaction | Loro, listener: Listener): number;
|
||||
subscribeOnce(txn: Transaction | Loro, listener: Listener): number;
|
||||
insert(txn: Transaction, pos: number, text: string): void;
|
||||
delete(txn: Transaction, pos: number, len: number): void;
|
||||
subscribe(txn: Loro, listener: Listener): number;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ import {
|
|||
LoroEvent,
|
||||
MapDiff as MapDiff,
|
||||
TextDiff,
|
||||
setPanicHook
|
||||
} from "../src";
|
||||
|
||||
setPanicHook();
|
||||
describe("event", () => {
|
||||
it("target", async () => {
|
||||
const loro = new Loro();
|
||||
|
@ -17,7 +19,9 @@ describe("event", () => {
|
|||
});
|
||||
const text = loro.getText("text");
|
||||
const id = text.id;
|
||||
text.insert(loro, 0, "123");
|
||||
loro.transact((tx) => {
|
||||
text.insert(tx, 0, "123");
|
||||
});
|
||||
await zeroMs();
|
||||
expect(lastEvent?.target).toEqual(id);
|
||||
});
|
||||
|
@ -29,15 +33,24 @@ describe("event", () => {
|
|||
lastEvent = event;
|
||||
});
|
||||
const map = loro.getMap("map");
|
||||
const subMap = map.insertContainer(loro, "sub", "Map");
|
||||
subMap.set(loro, "0", "1");
|
||||
const subMap = loro.transact((tx) => {
|
||||
const subMap = map.insertContainer(tx, "sub", "Map");
|
||||
subMap.set(tx, "0", "1");
|
||||
return subMap;
|
||||
});
|
||||
|
||||
await zeroMs();
|
||||
expect(lastEvent?.path).toStrictEqual(["map", "sub"]);
|
||||
const list = subMap.insertContainer(loro, "list", "List");
|
||||
list.insert(loro, 0, "2");
|
||||
const text = list.insertContainer(loro, 1, "Text");
|
||||
const text = loro.transact((tx) => {
|
||||
const list = subMap.insertContainer(tx, "list", "List");
|
||||
list.insert(tx, 0, "2");
|
||||
const text = list.insertContainer(tx, 1, "Text");
|
||||
return text;
|
||||
});
|
||||
await zeroMs();
|
||||
text.insert(loro, 0, "3");
|
||||
loro.transact((tx) => {
|
||||
text.insert(tx, 0, "3");
|
||||
});
|
||||
await zeroMs();
|
||||
expect(lastEvent?.path).toStrictEqual(["map", "sub", "list", 1]);
|
||||
});
|
||||
|
@ -49,12 +62,16 @@ describe("event", () => {
|
|||
lastEvent = event;
|
||||
});
|
||||
const text = loro.getText("t");
|
||||
text.insert(loro, 0, "3");
|
||||
loro.transact(tx => {
|
||||
text.insert(tx, 0, "3");
|
||||
})
|
||||
await zeroMs();
|
||||
expect(lastEvent?.diff).toStrictEqual(
|
||||
{ type: "text", diff: [{ type: "insert", value: "3" }] } as TextDiff,
|
||||
);
|
||||
text.insert(loro, 1, "12");
|
||||
loro.transact(tx => {
|
||||
text.insert(tx, 1, "12");
|
||||
})
|
||||
await zeroMs();
|
||||
expect(lastEvent?.diff).toStrictEqual(
|
||||
{
|
||||
|
@ -71,12 +88,16 @@ describe("event", () => {
|
|||
lastEvent = event;
|
||||
});
|
||||
const text = loro.getList("l");
|
||||
text.insert(loro, 0, "3");
|
||||
loro.transact(tx => {
|
||||
text.insert(tx, 0, "3");
|
||||
})
|
||||
await zeroMs();
|
||||
expect(lastEvent?.diff).toStrictEqual(
|
||||
{ type: "list", diff: [{ type: "insert", value: ["3"] }] } as ListDiff,
|
||||
);
|
||||
text.insert(loro, 1, "12");
|
||||
loro.transact(tx => {
|
||||
text.insert(tx, 1, "12");
|
||||
})
|
||||
await zeroMs();
|
||||
expect(lastEvent?.diff).toStrictEqual(
|
||||
{
|
||||
|
@ -101,14 +122,10 @@ describe("event", () => {
|
|||
expect(lastEvent?.diff).toStrictEqual(
|
||||
{
|
||||
type: "map",
|
||||
diff: {
|
||||
added: {
|
||||
"0": "3",
|
||||
"1": "2",
|
||||
},
|
||||
deleted: {},
|
||||
updated: {},
|
||||
},
|
||||
updated: {
|
||||
"0": "3",
|
||||
"1": "2",
|
||||
}
|
||||
} as MapDiff,
|
||||
);
|
||||
loro.transact((tx) => {
|
||||
|
@ -119,13 +136,9 @@ describe("event", () => {
|
|||
expect(lastEvent?.diff).toStrictEqual(
|
||||
{
|
||||
type: "map",
|
||||
diff: {
|
||||
added: {},
|
||||
updated: {
|
||||
"0": { old: "3", new: "0" },
|
||||
"1": { old: "2", new: "1" },
|
||||
},
|
||||
deleted: {},
|
||||
updated: {
|
||||
"0": "0",
|
||||
"1": "1"
|
||||
},
|
||||
} as MapDiff,
|
||||
);
|
||||
|
@ -136,10 +149,6 @@ describe("event", () => {
|
|||
const loro = new Loro();
|
||||
const text = loro.getText("text");
|
||||
let ran = 0;
|
||||
let oneTimeRan = 0;
|
||||
text.subscribeOnce(loro, (_) => {
|
||||
oneTimeRan += 1;
|
||||
});
|
||||
const sub = text.subscribe(loro, (event) => {
|
||||
if (!ran) {
|
||||
expect(event.diff.diff).toStrictEqual(
|
||||
|
@ -149,18 +158,24 @@ describe("event", () => {
|
|||
ran += 1;
|
||||
expect(event.target).toBe(text.id);
|
||||
});
|
||||
text.insert(loro, 0, "123");
|
||||
text.insert(loro, 1, "456");
|
||||
|
||||
loro.transact(tx => {
|
||||
text.insert(tx, 0, "123");
|
||||
});
|
||||
loro.transact(tx => {
|
||||
text.insert(tx, 1, "456");
|
||||
});
|
||||
await zeroMs();
|
||||
expect(ran).toBeTruthy();
|
||||
// subscribeOnce test
|
||||
expect(oneTimeRan).toBe(1);
|
||||
expect(text.toString()).toEqual("145623");
|
||||
|
||||
// unsubscribe
|
||||
const oldRan = ran;
|
||||
text.unsubscribe(loro, sub);
|
||||
text.insert(loro, 0, "789");
|
||||
loro.transact(tx => {
|
||||
text.insert(tx, 0, "789");
|
||||
})
|
||||
expect(ran).toBe(oldRan);
|
||||
});
|
||||
|
||||
|
@ -168,23 +183,27 @@ describe("event", () => {
|
|||
const loro = new Loro();
|
||||
const map = loro.getMap("map");
|
||||
let times = 0;
|
||||
const sub = map.subscribeDeep(loro, (event) => {
|
||||
const sub = map.subscribe(loro, (event) => {
|
||||
times += 1;
|
||||
});
|
||||
|
||||
const subMap = map.insertContainer(loro, "sub", "Map");
|
||||
|
||||
const subMap =
|
||||
loro.transact(tx =>
|
||||
map.insertContainer(tx, "sub", "Map")
|
||||
);
|
||||
await zeroMs();
|
||||
expect(times).toBe(1);
|
||||
const text = subMap.insertContainer(loro, "k", "Text");
|
||||
const text = loro.transact(tx => subMap.insertContainer(tx, "k", "Text"));
|
||||
await zeroMs();
|
||||
expect(times).toBe(2);
|
||||
text.insert(loro, 0, "123");
|
||||
loro.transact(tx => text.insert(tx, 0, "123"));
|
||||
await zeroMs();
|
||||
expect(times).toBe(3);
|
||||
|
||||
// unsubscribe
|
||||
map.unsubscribe(loro, sub);
|
||||
text.insert(loro, 0, "123");
|
||||
loro.unsubscribe(sub);
|
||||
loro.transact(tx => text.insert(tx, 0, "123"));
|
||||
await zeroMs();
|
||||
expect(times).toBe(3);
|
||||
});
|
||||
|
@ -193,20 +212,20 @@ describe("event", () => {
|
|||
const loro = new Loro();
|
||||
const list = loro.getList("list");
|
||||
let times = 0;
|
||||
const sub = list.subscribeDeep(loro, (_) => {
|
||||
const sub = list.subscribe(loro, (_) => {
|
||||
times += 1;
|
||||
});
|
||||
|
||||
const text = list.insertContainer(loro, 0, "Text");
|
||||
const text = loro.transact(tx => list.insertContainer(tx, 0, "Text"));
|
||||
await zeroMs();
|
||||
expect(times).toBe(1);
|
||||
text.insert(loro, 0, "123");
|
||||
loro.transact(tx => text.insert(tx, 0, "123"));
|
||||
await zeroMs();
|
||||
expect(times).toBe(2);
|
||||
|
||||
// unsubscribe
|
||||
list.unsubscribe(loro, sub);
|
||||
text.insert(loro, 0, "123");
|
||||
loro.unsubscribe(sub);
|
||||
loro.transact(tx => text.insert(tx, 0, "123"));
|
||||
await zeroMs();
|
||||
expect(times).toBe(2);
|
||||
});
|
||||
|
@ -237,19 +256,19 @@ describe("event", () => {
|
|||
string = newString + string.slice(pos);
|
||||
}
|
||||
});
|
||||
text.insert(loro, 0, "你好");
|
||||
loro.transact(tx => text.insert(tx, 0, "你好"));
|
||||
await zeroMs();
|
||||
expect(text.toString()).toBe(string);
|
||||
|
||||
text.insert(loro, 1, "世界");
|
||||
loro.transact(tx => text.insert(tx, 1, "世界"));
|
||||
await zeroMs();
|
||||
expect(text.toString()).toBe(string);
|
||||
|
||||
text.insert(loro, 2, "👍");
|
||||
loro.transact(tx => text.insert(tx, 2, "👍"));
|
||||
await zeroMs();
|
||||
expect(text.toString()).toBe(string);
|
||||
|
||||
text.insert(loro, 4, "♪(^∇^*)");
|
||||
loro.transact(tx => text.insert(tx, 2, "♪(^∇^*)"));
|
||||
await zeroMs();
|
||||
expect(text.toString()).toBe(string);
|
||||
});
|
||||
|
|
|
@ -5,22 +5,31 @@ import {
|
|||
Loro,
|
||||
LoroEvent,
|
||||
MapDiff as MapDiff,
|
||||
setPanicHook,
|
||||
TextDiff,
|
||||
} from "../src";
|
||||
|
||||
setPanicHook();
|
||||
describe("Frontiers", () => {
|
||||
it("two clients", () => {
|
||||
const doc = new Loro();
|
||||
const text = doc.getText("text");
|
||||
text.insert(doc, 0, "0");
|
||||
const txn = doc.txn();
|
||||
text.insert(txn, 0, "0");
|
||||
txn.commit();
|
||||
|
||||
const v0 = doc.frontiers();
|
||||
const docB = new Loro();
|
||||
docB.import(doc.exportFrom());
|
||||
expect(docB.cmpFrontiers(v0)).toBe(0);
|
||||
text.insert(doc, 1, "0");
|
||||
doc.transact((t) => {
|
||||
text.insert(t, 1, "0");
|
||||
});
|
||||
expect(docB.cmpFrontiers(doc.frontiers())).toBe(-1);
|
||||
const textB = docB.getText("text");
|
||||
textB.insert(docB, 0, "0");
|
||||
docB.transact((t) => {
|
||||
textB.insert(t, 0, "0");
|
||||
});
|
||||
expect(docB.cmpFrontiers(doc.frontiers())).toBe(-1);
|
||||
docB.import(doc.exportFrom());
|
||||
expect(docB.cmpFrontiers(doc.frontiers())).toBe(1);
|
||||
|
|
1087
pnpm-lock.yaml
1087
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue