From 88003bdffee01a5d41058e5044d8d9a6b8c77158 Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Sun, 30 Jul 2023 16:30:41 +0800 Subject: [PATCH] fix: get deep value & throw mismatched context err --- crates/loro-internal/src/refactor/handler.rs | 26 ++++ crates/loro-internal/src/refactor/txn.rs | 11 +- crates/loro-wasm/src/lib.rs | 16 +-- crates/loro-wasm/src/log.rs | 9 ++ loro-js/src/index.ts | 10 +- loro-js/tests/misc.test.ts | 136 ++++++++++++++----- 6 files changed, 159 insertions(+), 49 deletions(-) diff --git a/crates/loro-internal/src/refactor/handler.rs b/crates/loro-internal/src/refactor/handler.rs index 9064f532..3f313466 100644 --- a/crates/loro-internal/src/refactor/handler.rs +++ b/crates/loro-internal/src/refactor/handler.rs @@ -228,6 +228,7 @@ impl TextHandler { pos: start, }), Some(EventHint::Utf16 { pos, len: 0 }), + &self.state, ) } @@ -254,6 +255,7 @@ impl TextHandler { len: (end - start) as isize, })), Some(EventHint::Utf16 { pos, len: del }), + &self.state, ) } } @@ -280,6 +282,7 @@ impl ListHandler { pos, }), None, + &self.state, ) } @@ -301,6 +304,7 @@ impl ListHandler { pos, }), None, + &self.state, )?; Ok(child_idx) } @@ -317,6 +321,7 @@ impl ListHandler { len: len as isize, })), None, + &self.state, ) } @@ -344,6 +349,15 @@ impl ListHandler { .get_value_by_idx(self.container_idx) } + pub fn get_deep_value(&self) -> LoroValue { + self.state + .upgrade() + .unwrap() + .lock() + .unwrap() + .get_container_deep_value(self.container_idx) + } + pub fn id(&self) -> ContainerID { self.state .upgrade() @@ -390,6 +404,7 @@ impl MapHandler { value, }), None, + &self.state, ) } @@ -410,6 +425,7 @@ impl MapHandler { value: LoroValue::Container(container_id), }), None, + &self.state, )?; Ok(child_idx) } @@ -423,6 +439,7 @@ impl MapHandler { value: LoroValue::Null, }), None, + &self.state, ) } @@ -435,6 +452,15 @@ impl MapHandler { .get_value_by_idx(self.container_idx) } + pub fn get_deep_value(&self) -> LoroValue { + self.state + .upgrade() + .unwrap() + .lock() + .unwrap() + .get_container_deep_value(self.container_idx) + } + pub fn get(&self, key: &str) -> Option { self.state .upgrade() diff --git a/crates/loro-internal/src/refactor/txn.rs b/crates/loro-internal/src/refactor/txn.rs index 22998045..2218a111 100644 --- a/crates/loro-internal/src/refactor/txn.rs +++ b/crates/loro-internal/src/refactor/txn.rs @@ -1,7 +1,7 @@ use std::{ borrow::Cow, mem::take, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, Weak}, }; use debug_log::debug_dbg; @@ -191,7 +191,16 @@ impl Transaction { content: RawOpContent, // we need extra hint to reduce calculation for utf16 text op hint: Option, + // check whther context and txn are refering to the same state context + state_ref: &Weak>, ) -> LoroResult<()> { + if Arc::as_ptr(&self.state) != Weak::as_ptr(state_ref) { + return Err(LoroError::UnmatchedContext { + expected: self.state.lock().unwrap().peer, + found: state_ref.upgrade().unwrap().lock().unwrap().peer, + }); + } + let len = content.content_len(); let op = RawOp { id: ID { diff --git a/crates/loro-wasm/src/lib.rs b/crates/loro-wasm/src/lib.rs index 8f2e899a..6d082646 100644 --- a/crates/loro-wasm/src/lib.rs +++ b/crates/loro-wasm/src/lib.rs @@ -291,7 +291,7 @@ fn call_after_micro_task(ob: observer::Observer, e: DiffEvent) { let ans = ob.call1(&event.into()); drop(copy); if let Err(e) = ans { - console_log!("Error when calling observer: {:#?}", e); + console_error!("Error when calling observer: {:#?}", e); } }); let _ = promise.then(&closure); @@ -469,10 +469,9 @@ impl LoroMap { value.into() } - #[wasm_bindgen(js_name = "getValueDeep")] + #[wasm_bindgen(js_name = "getDeepValue")] pub fn get_value_deep(&self) -> JsValue { - todo!() - // self.0.get_value_deep(ctx.deref()).into() + self.0.get_deep_value().into() } #[wasm_bindgen(js_name = "insertContainer")] @@ -556,11 +555,10 @@ impl LoroList { self.0.get_value().into() } - #[wasm_bindgen(js_name = "getValueDeep")] - pub fn get_value_deep(&self) -> JsValue { - todo!() - // let value = self.0.get_value_deep(ctx.deref()); - // value.into() + #[wasm_bindgen(js_name = "getDeepValue")] + pub fn get_deep_value(&self) -> JsValue { + let value = self.0.get_deep_value(); + value.into() } #[wasm_bindgen(js_name = "insertContainer")] diff --git a/crates/loro-wasm/src/log.rs b/crates/loro-wasm/src/log.rs index 02623f42..4c39f365 100644 --- a/crates/loro-wasm/src/log.rs +++ b/crates/loro-wasm/src/log.rs @@ -6,6 +6,8 @@ extern "C" { // `log(..)` #[wasm_bindgen(js_namespace = console)] pub fn log(s: &str); + #[wasm_bindgen(js_namespace = console)] + pub fn error(s: &str); } #[macro_export] @@ -14,3 +16,10 @@ macro_rules! console_log { // `bare_bones` ($($t:tt)*) => ($crate::log::log(&format_args!($($t)*).to_string())) } + +#[macro_export] +macro_rules! console_error { + // Note that this is using the `log` function imported above during + // `bare_bones` + ($($t:tt)*) => ($crate::log::error(&format_args!($($t)*).to_string())) +} diff --git a/loro-js/src/index.ts b/loro-js/src/index.ts index c2ddae51..48f12073 100644 --- a/loro-js/src/index.ts +++ b/loro-js/src/index.ts @@ -32,8 +32,8 @@ Loro.prototype.transact = function (cb, origin) { }); }; -Loro.prototype.getTypedMap = Loro.prototype.getMap; -Loro.prototype.getTypedList = Loro.prototype.getList; +Loro.prototype.getTypedMap = function (...args) { return this.getMap(...args) }; +Loro.prototype.getTypedList = function (...args) { return this.getList(...args) }; LoroList.prototype.getTyped = function (loro, index) { const value = this.get(index); if (typeof value === "string" && isContainerId(value)) { @@ -42,7 +42,9 @@ LoroList.prototype.getTyped = function (loro, index) { return value; } }; -LoroList.prototype.insertTyped = LoroList.prototype.insert; +LoroList.prototype.insertTyped = function (...args) { + return this.insert(...args) +} LoroMap.prototype.getTyped = function (loro, key) { const value = this.get(key); if (typeof value === "string" && isContainerId(value)) { @@ -51,7 +53,7 @@ LoroMap.prototype.getTyped = function (loro, key) { return value; } }; -LoroMap.prototype.setTyped = LoroMap.prototype.set; +LoroMap.prototype.setTyped = function (...args) { return this.set(...args) }; LoroText.prototype.insert = function (txn, pos, text) { this.__txn_insert(txn, pos, text); diff --git a/loro-js/tests/misc.test.ts b/loro-js/tests/misc.test.ts index 223bcf18..c38741a8 100644 --- a/loro-js/tests/misc.test.ts +++ b/loro-js/tests/misc.test.ts @@ -69,19 +69,29 @@ describe("subscribe", () => { let i = 1; const sub = loro.subscribe(() => { if (i > 0) { - list.insert(loro, 0, i); - i--; + loro.transact(txn => { + list.insert(txn, 0, i); + i--; + }) } + count += 1; }); - text.insert(loro, 0, "hello world"); + loro.transact((txn) => { + text.insert(txn, 0, "hello world"); + }) + await one_ms(); assertEquals(count, 2); - text.insert(loro, 0, "hello world"); + loro.transact((txn) => { + text.insert(txn, 0, "hello world"); + }); await one_ms(); assertEquals(count, 3); loro.unsubscribe(sub); - text.insert(loro, 0, "hello world"); + loro.transact(txn => { + text.insert(txn, 0, "hello world"); + }) await one_ms(); assertEquals(count, 3); }); @@ -95,10 +105,16 @@ describe("subscribe", () => { loro.unsubscribe(sub); }); assertEquals(count, 0); - text.insert(loro, 0, "hello world"); + loro.transact(txn => { + text.insert(txn, 0, "hello world"); + }) + await one_ms(); assertEquals(count, 1); - text.insert(loro, 0, "hello world"); + loro.transact(txn => { + text.insert(txn, 0, "hello world"); + }) + assertEquals(count, 1); }); @@ -109,14 +125,20 @@ describe("subscribe", () => { const sub = loro.subscribe(() => { count += 1; }); - text.insert(loro, 0, "hello world"); + loro.transact(loro => { + text.insert(loro, 0, "hello world"); + }) await one_ms(); assertEquals(count, 1); - text.insert(loro, 0, "hello world"); + loro.transact(loro => { + text.insert(loro, 0, "hello world"); + }) await one_ms(); assertEquals(count, 2); loro.unsubscribe(sub); - text.insert(loro, 0, "hello world"); + loro.transact(loro => { + text.insert(loro, 0, "hello world"); + }) await one_ms(); assertEquals(count, 2); }); @@ -144,7 +166,10 @@ describe("sync", () => { }); const aText = a.getText("text"); const bText = b.getText("text"); - aText.insert(a, 0, "abc"); + a.transact(txn => { + aText.insert(txn, 0, "abc"); + }); + await one_ms(); assertEquals(aText.toString(), bText.toString()); }); @@ -152,17 +177,26 @@ describe("sync", () => { it("sync", () => { const loro = new Loro(); const text = loro.getText("text"); - text.insert(loro, 0, "hello world"); + loro.transact(txn => { + text.insert(txn, 0, "hello world"); + }); + const loro_bk = new Loro(); loro_bk.import(loro.exportFrom(undefined)); assertEquals(loro_bk.toJson(), loro.toJson()); const text_bk = loro_bk.getText("text"); assertEquals(text_bk.toString(), "hello world"); - text_bk.insert(loro_bk, 0, "a "); + loro_bk.transact(txn => { + text_bk.insert(txn, 0, "a "); + }); + loro.import(loro_bk.exportFrom(undefined)); assertEquals(text.toString(), "a hello world"); const map = loro.getMap("map"); - map.set(loro, "key", "value"); + loro.transact(txn => { + map.set(txn, "key", "value"); + }); + }); }); @@ -198,10 +232,13 @@ describe("prelim", () => { }); it("prelim map integrate", () => { - map.set(loro, "text", prelim_text); - map.set(loro, "map", prelim_map); - map.set(loro, "list", prelim_list); - assertEquals(map.getValueDeep(loro), { + loro.transact(txn => { + map.set(txn, "text", prelim_text); + map.set(txn, "map", prelim_map); + map.set(txn, "list", prelim_list); + }); + + assertEquals(map.getDeepValue(), { text: "hello everyone", map: { a: 1, ab: 123 }, list: [0, { a: 4 }], @@ -212,10 +249,13 @@ describe("prelim", () => { const prelim_text = new PrelimText("ttt"); const prelim_map = new PrelimMap({ a: 1, b: 2 }); const prelim_list = new PrelimList([1, "2", { a: 4 }]); - list.insert(loro, 0, prelim_text); - list.insert(loro, 1, prelim_map); - list.insert(loro, 2, prelim_list); - assertEquals(list.getValueDeep(loro), ["ttt", { a: 1, b: 2 }, [1, "2", { + loro.transact(txn => { + list.insert(txn, 0, prelim_text); + list.insert(txn, 1, prelim_map); + list.insert(txn, 2, prelim_list); + }); + + assertEquals(list.getDeepValue(), ["ttt", { a: 1, b: 2 }, [1, "2", { a: 4, }]]); }); @@ -225,27 +265,40 @@ describe("prelim", () => { describe("wasm", () => { const loro = new Loro(); const a = loro.getText("ha"); - a.insert(loro, 0, "hello world"); - a.delete(loro, 6, 5); - a.insert(loro, 6, "everyone"); - const b = loro.getMap("ha"); - b.set(loro, "ab", 123); + loro.transact(txn => { + a.insert(txn, 0, "hello world"); - const bText = b.insertContainer(loro, "hh", "Text"); + a.delete(txn, 6, 5); + a.insert(txn, 6, "everyone"); + }); + const b = loro.getMap("ha"); + loro.transact(txn => { + b.set(txn, "ab", 123); + }); + + const bText = loro.transact(txn => { + return b.insertContainer(txn, "hh", "Text") + }); it("map get", () => { assertEquals(b.get("ab"), 123); }); it("getValueDeep", () => { - bText.insert(loro, 0, "hello world Text"); - assertEquals(b.getValueDeep(loro), { ab: 123, hh: "hello world Text" }); + loro.transact(txn => { + bText.insert(txn, 0, "hello world Text"); + }); + + assertEquals(b.getDeepValue(), { ab: 123, hh: "hello world Text" }); }); it("should throw error when using the wrong context", () => { expect(() => { const loro2 = new Loro(); - bText.insert(loro2, 0, "hello world Text"); + loro2.transact(txn => { + bText.insert(txn, 0, "hello world Text"); + }); + }).toThrow(); }); @@ -254,7 +307,10 @@ describe("wasm", () => { const b2 = loro.getContainerById(id) as LoroMap; assertEquals(b2.value, b.value); assertEquals(b2.id, id); - b2.set(loro, "0", 12); + loro.transact(txn => { + b2.set(txn, "0", 12); + }); + assertEquals(b2.value, b.value); }); }); @@ -270,7 +326,10 @@ describe("type", () => { it("test recursive map type", () => { const loro = new Loro<{ map: LoroMap<{ map: LoroMap<{ name: "he" }> }> }>(); const map = loro.getTypedMap("map"); - map.insertContainer(loro, "map", "Map"); + loro.transact(txn => { + map.insertContainer(txn, "map", "Map"); + }); + const subMap = map.getTyped(loro, "map"); const name = subMap.getTyped(loro, "name"); expectTypeOf(name).toEqualTypeOf<"he">(); @@ -279,8 +338,15 @@ describe("type", () => { it("works for list type", () => { const loro = new Loro<{ list: LoroList<[string, number]> }>(); const list = loro.getTypedList("list"); - list.insertTyped(loro, 0, "123"); - list.insertTyped(loro, 1, 123); + console.dir((list as any).__proto__); + loro.transact(txn => { + list.insertTyped(txn, 0, "123"); + }); + + loro.transact(txn => { + list.insertTyped(txn, 1, 123); + }); + const v0 = list.getTyped(loro, 0); expectTypeOf(v0).toEqualTypeOf(); const v1 = list.getTyped(loro, 1);