mirror of
https://github.com/loro-dev/loro.git
synced 2025-01-22 21:07:43 +00:00
feat: use ContainerTrait
fix: op counter
This commit is contained in:
parent
6fd81c8d20
commit
9fefd75fb6
18 changed files with 1702 additions and 130 deletions
|
@ -5,15 +5,17 @@
|
|||
//! Every [Container] can take a [Snapshot], which contains [crate::LoroValue] that describes the state.
|
||||
//!
|
||||
use crate::{
|
||||
container::registry::ContainerWrapper,
|
||||
event::{Observer, ObserverHandler, SubscriptionID},
|
||||
hierarchy::Hierarchy,
|
||||
log_store::ImportContext,
|
||||
op::{InnerContent, Op, RemoteContent, RichOp},
|
||||
transaction::op::TransactionOp,
|
||||
version::PatchedVersionVector,
|
||||
InternalString, LogStore, LoroError, LoroValue, ID,
|
||||
InternalString, List, LogStore, LoroCore, LoroError, LoroValue, Map, Text, ID,
|
||||
};
|
||||
|
||||
use enum_as_inner::EnumAsInner;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
|
@ -46,6 +48,49 @@ pub enum ContainerType {
|
|||
// Custom(u16),
|
||||
}
|
||||
|
||||
#[derive(Debug, EnumAsInner)]
|
||||
pub enum Container {
|
||||
Text(Text),
|
||||
Map(Map),
|
||||
List(List),
|
||||
}
|
||||
|
||||
impl Container {
|
||||
pub fn idx(&self) -> ContainerIdx {
|
||||
match self {
|
||||
Container::List(x) => x.idx(),
|
||||
Container::Map(x) => x.idx(),
|
||||
Container::Text(x) => x.idx(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_(&self) -> ContainerType {
|
||||
match self {
|
||||
Container::List(_x) => ContainerType::List,
|
||||
Container::Map(_x) => ContainerType::Map,
|
||||
Container::Text(_x) => ContainerType::Text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<List> for Container {
|
||||
fn from(value: List) -> Self {
|
||||
Container::List(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Map> for Container {
|
||||
fn from(value: Map) -> Self {
|
||||
Container::Map(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Text> for Container {
|
||||
fn from(value: Text) -> Self {
|
||||
Container::Text(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ContainerType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
|
@ -71,7 +116,7 @@ impl TryFrom<&str> for ContainerType {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait Container: Debug + Any + Unpin + Send + Sync {
|
||||
pub trait ContainerTrait: Debug + Any + Unpin + Send + Sync {
|
||||
fn id(&self) -> &ContainerID;
|
||||
fn idx(&self) -> ContainerIdx;
|
||||
fn type_(&self) -> ContainerType;
|
||||
|
|
|
@ -20,19 +20,20 @@ use crate::{
|
|||
text_content::{ListSlice, SliceRange},
|
||||
tracker::{Effect, Tracker},
|
||||
},
|
||||
Container, ContainerID, ContainerType,
|
||||
ContainerID, ContainerTrait, ContainerType,
|
||||
},
|
||||
context::Context,
|
||||
delta::{Delta, DeltaItem},
|
||||
event::{Diff, Index},
|
||||
hierarchy::Hierarchy,
|
||||
id::{ClientID, Counter},
|
||||
id::{ClientID, Counter, ID},
|
||||
log_store::ImportContext,
|
||||
op::{InnerContent, Op, RemoteContent, RichOp},
|
||||
prelim::Prelim,
|
||||
transaction::op::{ListTxnOps, TransactionOp},
|
||||
value::LoroValue,
|
||||
version::PatchedVersionVector,
|
||||
LogStore, LoroError, Transact,
|
||||
Container, LogStore, LoroCore, LoroError, Map, Text, Transact,
|
||||
};
|
||||
|
||||
use super::list_op::InnerListOp;
|
||||
|
@ -64,18 +65,24 @@ impl ListContainer {
|
|||
fn apply_txn_op_impl(&mut self, store: &mut LogStore, op: &ListTxnOps) -> Vec<Op> {
|
||||
let mut index = 0;
|
||||
let mut ops = Vec::new();
|
||||
let id = store.next_id();
|
||||
let mut offset = 0;
|
||||
for item in op.items() {
|
||||
let item = item.clone().into_event_format();
|
||||
match item {
|
||||
DeltaItem::Retain { len, .. } => index += len,
|
||||
DeltaItem::Insert { value, .. } => {
|
||||
let len = value.len();
|
||||
let op = self.apply_batch_insert(index, value, store);
|
||||
let id = id.inc(offset);
|
||||
offset += 1;
|
||||
let op = self.apply_batch_insert(index, value, id);
|
||||
index += len;
|
||||
ops.push(op);
|
||||
}
|
||||
DeltaItem::Delete(len) => {
|
||||
let op = self.apply_delete(index, len, store);
|
||||
let id = id.inc(offset);
|
||||
offset += 1;
|
||||
let op = self.apply_delete(index, len, id);
|
||||
ops.push(op);
|
||||
}
|
||||
}
|
||||
|
@ -83,13 +90,7 @@ impl ListContainer {
|
|||
ops
|
||||
}
|
||||
|
||||
fn apply_batch_insert(
|
||||
&mut self,
|
||||
pos: usize,
|
||||
values: Vec<LoroValue>,
|
||||
store: &mut LogStore,
|
||||
) -> Op {
|
||||
let id = store.next_id();
|
||||
fn apply_batch_insert(&mut self, pos: usize, values: Vec<LoroValue>, id: ID) -> Op {
|
||||
let slice = self.raw_data.alloc_arr(values);
|
||||
self.state.insert(pos, slice.clone().into());
|
||||
Op::new(
|
||||
|
@ -102,8 +103,7 @@ impl ListContainer {
|
|||
)
|
||||
}
|
||||
|
||||
fn apply_delete(&mut self, pos: usize, len: usize, store: &mut LogStore) -> Op {
|
||||
let id = store.next_id();
|
||||
fn apply_delete(&mut self, pos: usize, len: usize, id: ID) -> Op {
|
||||
self.state.delete_range(Some(pos), Some(pos + len));
|
||||
Op::new(
|
||||
id,
|
||||
|
@ -191,7 +191,7 @@ impl ListContainer {
|
|||
}
|
||||
}
|
||||
|
||||
impl Container for ListContainer {
|
||||
impl ContainerTrait for ListContainer {
|
||||
#[inline(always)]
|
||||
fn id(&self) -> &ContainerID {
|
||||
&self.id
|
||||
|
@ -495,7 +495,10 @@ pub struct List {
|
|||
}
|
||||
|
||||
impl List {
|
||||
pub fn from_instance(instance: Weak<Mutex<ContainerInstance>>, client_id: ClientID) -> Self {
|
||||
pub(crate) fn from_instance(
|
||||
instance: Weak<Mutex<ContainerInstance>>,
|
||||
client_id: ClientID,
|
||||
) -> Self {
|
||||
let container_idx = instance.upgrade().unwrap().try_lock().unwrap().idx();
|
||||
Self {
|
||||
container: ContainerInner::from(instance),
|
||||
|
@ -504,7 +507,7 @@ impl List {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn from_idx(idx: ContainerIdx, client_id: ClientID) -> Self {
|
||||
pub(crate) fn from_idx(idx: ContainerIdx, client_id: ClientID) -> Self {
|
||||
Self {
|
||||
container: ContainerInner::from(ContainerTemp::new(idx, ContainerType::List)),
|
||||
client_id,
|
||||
|
@ -523,18 +526,23 @@ impl List {
|
|||
txn: &T,
|
||||
pos: usize,
|
||||
value: P,
|
||||
) -> Result<Option<ContainerTemp>, LoroError> {
|
||||
) -> Result<Option<Container>, LoroError> {
|
||||
self.with_transaction_checked(txn, |txn, _x| {
|
||||
let (value, maybe_container) = value.convert_value()?;
|
||||
if let Some(prelim) = maybe_container {
|
||||
let idx = txn.next_container_idx();
|
||||
let type_ = value.into_container().unwrap();
|
||||
let idx = txn.next_container_idx();
|
||||
txn.push(
|
||||
TransactionOp::insert_list_container(self.idx(), pos, type_, idx),
|
||||
Some(idx),
|
||||
)?;
|
||||
prelim.integrate(txn, idx)?;
|
||||
Ok(Some(ContainerTemp::new(idx, type_)))
|
||||
let container = match type_ {
|
||||
ContainerType::List => Container::from(List::from_idx(idx, self.client_id)),
|
||||
ContainerType::Map => Container::from(Map::from_idx(idx, self.client_id)),
|
||||
ContainerType::Text => Container::from(Text::from_idx(idx, self.client_id)),
|
||||
};
|
||||
Ok(Some(container))
|
||||
} else {
|
||||
let value = value.into_value().unwrap();
|
||||
txn.push(
|
||||
|
@ -566,7 +574,7 @@ impl List {
|
|||
&mut self,
|
||||
txn: &T,
|
||||
value: P,
|
||||
) -> Result<Option<ContainerTemp>, LoroError> {
|
||||
) -> Result<Option<Container>, LoroError> {
|
||||
let pos = self.len();
|
||||
self.insert(txn, pos, value)
|
||||
}
|
||||
|
@ -576,7 +584,7 @@ impl List {
|
|||
&mut self,
|
||||
txn: &T,
|
||||
value: P,
|
||||
) -> Result<Option<ContainerTemp>, LoroError> {
|
||||
) -> Result<Option<Container>, LoroError> {
|
||||
let pos = 0;
|
||||
self.insert(txn, pos, value)
|
||||
}
|
||||
|
@ -670,6 +678,10 @@ impl ContainerWrapper for List {
|
|||
Ok(f(list))
|
||||
}
|
||||
|
||||
fn is_instance(&self) -> bool {
|
||||
matches!(self.container, ContainerInner::Instance(_))
|
||||
}
|
||||
|
||||
fn client_id(&self) -> ClientID {
|
||||
self.client_id
|
||||
}
|
||||
|
@ -677,6 +689,14 @@ impl ContainerWrapper for List {
|
|||
fn container_inner(&self) -> &ContainerInner {
|
||||
&self.container
|
||||
}
|
||||
|
||||
fn try_to_update(&mut self, loro: &LoroCore) {
|
||||
if !self.is_instance() {
|
||||
let idx = self.idx();
|
||||
let new = loro.get_list_by_idx(&idx).unwrap();
|
||||
*self = new;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -8,9 +8,10 @@ use crate::{
|
|||
temp::ContainerTemp,
|
||||
},
|
||||
delta::MapDiff,
|
||||
id::ID,
|
||||
op::OwnedRichOp,
|
||||
transaction::op::{MapTxnOps, TransactionOp},
|
||||
LogStore, LoroError, Transact,
|
||||
Container, List, LogStore, LoroCore, LoroError, Text, Transact,
|
||||
};
|
||||
use fxhash::FxHashMap;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
@ -18,7 +19,7 @@ use smallvec::{smallvec, SmallVec};
|
|||
use crate::{
|
||||
container::{
|
||||
registry::{ContainerInstance, ContainerWrapper},
|
||||
Container, ContainerID, ContainerType,
|
||||
ContainerID, ContainerTrait, ContainerType,
|
||||
},
|
||||
event::{Diff, Index},
|
||||
hierarchy::Hierarchy,
|
||||
|
@ -67,8 +68,13 @@ impl MapContainer {
|
|||
}
|
||||
}
|
||||
|
||||
fn apply_insert(&mut self, store: &mut LogStore, key: InternalString, value: LoroValue) -> Op {
|
||||
let id = store.next_id();
|
||||
fn apply_insert(
|
||||
&mut self,
|
||||
key: InternalString,
|
||||
value: LoroValue,
|
||||
id: ID,
|
||||
store: &mut LogStore,
|
||||
) -> Op {
|
||||
let value_index = self.pool.alloc(value).start;
|
||||
let order = TotalOrderStamp {
|
||||
client_id: store.this_client_id,
|
||||
|
@ -94,11 +100,17 @@ impl MapContainer {
|
|||
fn apply_txn_op_impl(&mut self, store: &mut LogStore, ops: &MapTxnOps) -> Vec<Op> {
|
||||
let ops = ops.clone();
|
||||
let mut store_ops = Vec::with_capacity(ops.added.len() + ops.deleted.len());
|
||||
let mut offset = 0;
|
||||
let id = store.next_id();
|
||||
for (k, v) in ops.added.into_iter() {
|
||||
store_ops.push(self.apply_insert(store, k, v.into_value().unwrap()));
|
||||
let id = id.inc(offset);
|
||||
offset += 1;
|
||||
store_ops.push(self.apply_insert(k, v.into_value().unwrap(), id, store));
|
||||
}
|
||||
for k in ops.deleted {
|
||||
store_ops.push(self.apply_insert(store, k, LoroValue::Null));
|
||||
let id = id.inc(offset);
|
||||
offset += 1;
|
||||
store_ops.push(self.apply_insert(k, LoroValue::Null, id, store));
|
||||
}
|
||||
store_ops
|
||||
}
|
||||
|
@ -141,6 +153,10 @@ impl MapContainer {
|
|||
self.get_value().resolve_deep(reg)
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.state.len()
|
||||
}
|
||||
|
||||
pub fn keys(&self) -> Vec<InternalString> {
|
||||
self.state.keys().cloned().collect()
|
||||
}
|
||||
|
@ -157,7 +173,7 @@ impl MapContainer {
|
|||
}
|
||||
}
|
||||
|
||||
impl Container for MapContainer {
|
||||
impl ContainerTrait for MapContainer {
|
||||
#[inline(always)]
|
||||
fn id(&self) -> &ContainerID {
|
||||
&self.id
|
||||
|
@ -390,7 +406,10 @@ pub struct Map {
|
|||
}
|
||||
|
||||
impl Map {
|
||||
pub fn from_instance(instance: Weak<Mutex<ContainerInstance>>, client_id: ClientID) -> Self {
|
||||
pub(crate) fn from_instance(
|
||||
instance: Weak<Mutex<ContainerInstance>>,
|
||||
client_id: ClientID,
|
||||
) -> Self {
|
||||
let container_idx = instance.upgrade().unwrap().try_lock().unwrap().idx();
|
||||
Self {
|
||||
container: ContainerInner::from(instance),
|
||||
|
@ -399,7 +418,7 @@ impl Map {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn from_idx(idx: ContainerIdx, client_id: ClientID) -> Self {
|
||||
pub(crate) fn from_idx(idx: ContainerIdx, client_id: ClientID) -> Self {
|
||||
Self {
|
||||
container: ContainerInner::from(ContainerTemp::new(idx, ContainerType::Map)),
|
||||
client_id,
|
||||
|
@ -417,7 +436,7 @@ impl Map {
|
|||
txn: &T,
|
||||
key: &str,
|
||||
value: V,
|
||||
) -> Result<Option<ContainerTemp>, LoroError> {
|
||||
) -> Result<Option<Container>, LoroError> {
|
||||
self.with_transaction_checked(txn, |txn, _| {
|
||||
let (value, maybe_container) = value.convert_value()?;
|
||||
if let Some(prelim) = maybe_container {
|
||||
|
@ -428,7 +447,12 @@ impl Map {
|
|||
Some(idx),
|
||||
)?;
|
||||
prelim.integrate(txn, idx)?;
|
||||
Ok(Some(ContainerTemp::new(idx, type_)))
|
||||
let container = match type_ {
|
||||
ContainerType::List => Container::from(List::from_idx(idx, self.client_id)),
|
||||
ContainerType::Map => Container::from(Map::from_idx(idx, self.client_id)),
|
||||
ContainerType::Text => Container::from(Text::from_idx(idx, self.client_id)),
|
||||
};
|
||||
Ok(Some(container))
|
||||
} else {
|
||||
let value = value.into_value().unwrap();
|
||||
txn.push(
|
||||
|
@ -458,8 +482,12 @@ impl Map {
|
|||
self.with_container(|map| map.get(&key.into()).cloned())
|
||||
}
|
||||
|
||||
pub fn keys(&self) -> Vec<String> {
|
||||
todo!()
|
||||
/// Need clone
|
||||
pub fn keys(&self) -> Vec<InternalString> {
|
||||
match &self.container {
|
||||
ContainerInner::Instance(_) => self.with_container(|x| x.keys()).unwrap(),
|
||||
ContainerInner::Temp(temp) => temp.as_map().unwrap().keys(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn values(&self) -> Result<Vec<LoroValue>, LoroError> {
|
||||
|
@ -491,7 +519,10 @@ impl Map {
|
|||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
todo!()
|
||||
match &self.container {
|
||||
ContainerInner::Instance(_) => self.with_container(|x| x.len()).unwrap(),
|
||||
ContainerInner::Temp(temp) => temp.as_map().unwrap().len(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
@ -507,6 +538,18 @@ impl ContainerWrapper for Map {
|
|||
self.client_id
|
||||
}
|
||||
|
||||
fn is_instance(&self) -> bool {
|
||||
matches!(self.container, ContainerInner::Instance(_))
|
||||
}
|
||||
|
||||
fn try_to_update(&mut self, loro: &LoroCore) {
|
||||
if !self.is_instance() {
|
||||
let idx = self.idx();
|
||||
let new = loro.get_map_by_idx(&idx).unwrap();
|
||||
*self = new;
|
||||
}
|
||||
}
|
||||
|
||||
fn container_inner(&self) -> &ContainerInner {
|
||||
&self.container
|
||||
}
|
||||
|
|
|
@ -22,12 +22,12 @@ use crate::{
|
|||
op::{Op, RemoteContent, RichOp},
|
||||
transaction::{op::TransactionOp, Transaction},
|
||||
version::PatchedVersionVector,
|
||||
LogStore, LoroError, LoroValue, Transact,
|
||||
LogStore, LoroCore, LoroError, LoroValue, Transact,
|
||||
};
|
||||
|
||||
use super::{
|
||||
list::ListContainer, map::MapContainer, pool_mapping::StateContent, temp::ContainerTemp,
|
||||
text::TextContainer, Container, ContainerID, ContainerType,
|
||||
text::TextContainer, ContainerID, ContainerTrait, ContainerType,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, EnumAsInner)]
|
||||
|
@ -45,15 +45,6 @@ impl ContainerInner {
|
|||
}
|
||||
}
|
||||
|
||||
// impl Clone for ContainerInner {
|
||||
// fn clone(&self) -> Self {
|
||||
// match self {
|
||||
// ContainerInner::Instance(weak) => ContainerInner::Instance(Weak::clone(weak)),
|
||||
// ContainerInner::Temp(x) => ContainerInner::Temp(*x.clone()),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl From<Weak<Mutex<ContainerInstance>>> for ContainerInner {
|
||||
fn from(value: Weak<Mutex<ContainerInstance>>) -> Self {
|
||||
Self::Instance(value)
|
||||
|
@ -85,16 +76,16 @@ pub enum ContainerInstance {
|
|||
List(Box<ListContainer>),
|
||||
Text(Box<TextContainer>),
|
||||
Map(Box<MapContainer>),
|
||||
Dyn(Box<dyn Container>),
|
||||
Dyn(Box<dyn ContainerTrait>),
|
||||
}
|
||||
|
||||
impl Container for ContainerInstance {
|
||||
impl ContainerTrait for ContainerInstance {
|
||||
fn id(&self) -> &ContainerID {
|
||||
match self {
|
||||
ContainerInstance::Map(x) => x.id(),
|
||||
ContainerInstance::Text(x) => x.id(),
|
||||
ContainerInstance::Dyn(x) => x.id(),
|
||||
ContainerInstance::List(x) => x.id(),
|
||||
ContainerInstance::Dyn(x) => x.id(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,8 +93,8 @@ impl Container for ContainerInstance {
|
|||
match self {
|
||||
ContainerInstance::Map(x) => x.idx(),
|
||||
ContainerInstance::Text(x) => x.idx(),
|
||||
ContainerInstance::Dyn(x) => x.idx(),
|
||||
ContainerInstance::List(x) => x.idx(),
|
||||
ContainerInstance::Dyn(x) => x.idx(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,8 +112,8 @@ impl Container for ContainerInstance {
|
|||
match self {
|
||||
ContainerInstance::Map(x) => x.tracker_init(vv),
|
||||
ContainerInstance::Text(x) => x.tracker_init(vv),
|
||||
ContainerInstance::Dyn(x) => x.tracker_init(vv),
|
||||
ContainerInstance::List(x) => x.tracker_init(vv),
|
||||
ContainerInstance::Dyn(x) => x.tracker_init(vv),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,8 +122,8 @@ impl Container for ContainerInstance {
|
|||
match self {
|
||||
ContainerInstance::Map(x) => x.tracker_checkout(vv),
|
||||
ContainerInstance::Text(x) => x.tracker_checkout(vv),
|
||||
ContainerInstance::Dyn(x) => x.tracker_checkout(vv),
|
||||
ContainerInstance::List(x) => x.tracker_checkout(vv),
|
||||
ContainerInstance::Dyn(x) => x.tracker_checkout(vv),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,8 +132,8 @@ impl Container for ContainerInstance {
|
|||
match self {
|
||||
ContainerInstance::Map(x) => x.get_value(),
|
||||
ContainerInstance::Text(x) => x.get_value(),
|
||||
ContainerInstance::Dyn(x) => x.get_value(),
|
||||
ContainerInstance::List(x) => x.get_value(),
|
||||
ContainerInstance::Dyn(x) => x.get_value(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,8 +147,8 @@ impl Container for ContainerInstance {
|
|||
match self {
|
||||
ContainerInstance::Map(x) => x.update_state_directly(hierarchy, op, context),
|
||||
ContainerInstance::Text(x) => x.update_state_directly(hierarchy, op, context),
|
||||
ContainerInstance::Dyn(x) => x.update_state_directly(hierarchy, op, context),
|
||||
ContainerInstance::List(x) => x.update_state_directly(hierarchy, op, context),
|
||||
ContainerInstance::Dyn(x) => x.update_state_directly(hierarchy, op, context),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,8 +157,8 @@ impl Container for ContainerInstance {
|
|||
match self {
|
||||
ContainerInstance::Map(x) => x.track_apply(hierarchy, op, ctx),
|
||||
ContainerInstance::Text(x) => x.track_apply(hierarchy, op, ctx),
|
||||
ContainerInstance::Dyn(x) => x.track_apply(hierarchy, op, ctx),
|
||||
ContainerInstance::List(x) => x.track_apply(hierarchy, op, ctx),
|
||||
ContainerInstance::Dyn(x) => x.track_apply(hierarchy, op, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,8 +171,8 @@ impl Container for ContainerInstance {
|
|||
match self {
|
||||
ContainerInstance::Map(x) => x.apply_tracked_effects_from(h, import_context),
|
||||
ContainerInstance::Text(x) => x.apply_tracked_effects_from(h, import_context),
|
||||
ContainerInstance::Dyn(x) => x.apply_tracked_effects_from(h, import_context),
|
||||
ContainerInstance::List(x) => x.apply_tracked_effects_from(h, import_context),
|
||||
ContainerInstance::Dyn(x) => x.apply_tracked_effects_from(h, import_context),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,8 +185,8 @@ impl Container for ContainerInstance {
|
|||
match self {
|
||||
ContainerInstance::Map(x) => x.to_export(content, gc),
|
||||
ContainerInstance::Text(x) => x.to_export(content, gc),
|
||||
ContainerInstance::Dyn(x) => x.to_export(content, gc),
|
||||
ContainerInstance::List(x) => x.to_export(content, gc),
|
||||
ContainerInstance::Dyn(x) => x.to_export(content, gc),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,8 +195,8 @@ impl Container for ContainerInstance {
|
|||
match self {
|
||||
ContainerInstance::Map(x) => x.to_import(content),
|
||||
ContainerInstance::Text(x) => x.to_import(content),
|
||||
ContainerInstance::Dyn(x) => x.to_import(content),
|
||||
ContainerInstance::List(x) => x.to_import(content),
|
||||
ContainerInstance::Dyn(x) => x.to_import(content),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -217,8 +208,8 @@ impl Container for ContainerInstance {
|
|||
match self {
|
||||
ContainerInstance::Map(x) => x.to_export_snapshot(content, gc),
|
||||
ContainerInstance::Text(x) => x.to_export_snapshot(content, gc),
|
||||
ContainerInstance::Dyn(x) => x.to_export_snapshot(content, gc),
|
||||
ContainerInstance::List(x) => x.to_export_snapshot(content, gc),
|
||||
ContainerInstance::Dyn(x) => x.to_export_snapshot(content, gc),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,8 +217,8 @@ impl Container for ContainerInstance {
|
|||
match self {
|
||||
ContainerInstance::Map(x) => x.initialize_pool_mapping(),
|
||||
ContainerInstance::Text(x) => x.initialize_pool_mapping(),
|
||||
ContainerInstance::Dyn(x) => x.initialize_pool_mapping(),
|
||||
ContainerInstance::List(x) => x.initialize_pool_mapping(),
|
||||
ContainerInstance::Dyn(x) => x.initialize_pool_mapping(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,8 +226,8 @@ impl Container for ContainerInstance {
|
|||
match self {
|
||||
ContainerInstance::Map(x) => x.encode_and_release_pool_mapping(),
|
||||
ContainerInstance::Text(x) => x.encode_and_release_pool_mapping(),
|
||||
ContainerInstance::Dyn(x) => x.encode_and_release_pool_mapping(),
|
||||
ContainerInstance::List(x) => x.encode_and_release_pool_mapping(),
|
||||
ContainerInstance::Dyn(x) => x.encode_and_release_pool_mapping(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,8 +240,8 @@ impl Container for ContainerInstance {
|
|||
match self {
|
||||
ContainerInstance::Map(x) => x.to_import_snapshot(state_content, hierarchy, ctx),
|
||||
ContainerInstance::Text(x) => x.to_import_snapshot(state_content, hierarchy, ctx),
|
||||
ContainerInstance::Dyn(x) => x.to_import_snapshot(state_content, hierarchy, ctx),
|
||||
ContainerInstance::List(x) => x.to_import_snapshot(state_content, hierarchy, ctx),
|
||||
ContainerInstance::Dyn(x) => x.to_import_snapshot(state_content, hierarchy, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -368,8 +359,7 @@ impl ContainerRegistry {
|
|||
#[inline(always)]
|
||||
pub(crate) fn next_idx_and_add_1(&self) -> ContainerIdx {
|
||||
let idx = self.next_container_idx.fetch_add(1, Ordering::SeqCst);
|
||||
let ans = ContainerIdx::from_u32(idx);
|
||||
ans
|
||||
ContainerIdx::from_u32(idx)
|
||||
}
|
||||
|
||||
pub(crate) fn register(&mut self, id: &ContainerID) -> ContainerIdx {
|
||||
|
@ -438,7 +428,7 @@ impl ContainerRegistry {
|
|||
pub(crate) fn export(&self) -> (&FxHashMap<ContainerID, ContainerIdx>, Vec<ContainerID>) {
|
||||
(
|
||||
&self.container_to_idx,
|
||||
self.containers.iter().map(|(_, x)| x.id.clone()).collect(),
|
||||
self.containers.values().map(|x| x.id.clone()).collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -488,7 +478,12 @@ pub trait LockContainer {
|
|||
}
|
||||
|
||||
pub trait ContainerWrapper {
|
||||
type Container: Container;
|
||||
type Container: ContainerTrait;
|
||||
|
||||
fn is_instance(&self) -> bool;
|
||||
|
||||
/// If Container is temporary, convert it to ContainerInstance
|
||||
fn try_to_update(&mut self, loro: &LoroCore);
|
||||
|
||||
fn container_inner(&self) -> &ContainerInner;
|
||||
|
||||
|
|
|
@ -79,8 +79,12 @@ impl MapTemp {
|
|||
checker: MapChecker::from_idx(idx),
|
||||
}
|
||||
}
|
||||
pub(crate) fn keys(&self) -> &FxHashSet<InternalString> {
|
||||
&self.checker.keys
|
||||
pub(crate) fn keys(&self) -> Vec<InternalString> {
|
||||
self.checker.keys.iter().cloned().collect()
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.checker.keys.len()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,18 +11,18 @@ use crate::{
|
|||
pool_mapping::{PoolMapping, StateContent},
|
||||
registry::{ContainerIdx, ContainerInner, ContainerInstance, ContainerWrapper},
|
||||
temp::ContainerTemp,
|
||||
Container, ContainerID, ContainerType,
|
||||
ContainerID, ContainerTrait, ContainerType,
|
||||
},
|
||||
delta::{Delta, DeltaItem},
|
||||
event::Diff,
|
||||
hierarchy::Hierarchy,
|
||||
id::{ClientID, Counter},
|
||||
id::{ClientID, Counter, ID},
|
||||
log_store::ImportContext,
|
||||
op::{InnerContent, Op, RemoteContent, RichOp},
|
||||
transaction::op::{TextTxnOps, TransactionOp},
|
||||
value::LoroValue,
|
||||
version::PatchedVersionVector,
|
||||
LogStore, LoroError, Transact,
|
||||
LogStore, LoroCore, LoroError, Transact,
|
||||
};
|
||||
|
||||
use super::{
|
||||
|
@ -57,17 +57,23 @@ impl TextContainer {
|
|||
pub(crate) fn apply_txn_op_impl(&mut self, store: &mut LogStore, op: &TextTxnOps) -> Vec<Op> {
|
||||
let mut index = 0;
|
||||
let mut ops = Vec::new();
|
||||
let mut offset = 0;
|
||||
let id = store.next_id();
|
||||
for item in op.items() {
|
||||
match item {
|
||||
DeltaItem::Retain { len, .. } => index += len,
|
||||
DeltaItem::Insert { value, .. } => {
|
||||
let len = value.len();
|
||||
let op = self.apply_insert(index, value, store);
|
||||
let id = id.inc(offset);
|
||||
offset += 1;
|
||||
let op = self.apply_insert(index, value, id);
|
||||
index += len;
|
||||
ops.push(op);
|
||||
}
|
||||
DeltaItem::Delete(len) => {
|
||||
let op = self.apply_delete(index, *len, store);
|
||||
let id = id.inc(offset);
|
||||
offset += 1;
|
||||
let op = self.apply_delete(index, *len, id);
|
||||
ops.push(op);
|
||||
}
|
||||
}
|
||||
|
@ -75,11 +81,10 @@ impl TextContainer {
|
|||
ops
|
||||
}
|
||||
|
||||
pub(crate) fn apply_insert(&mut self, pos: usize, text: &str, store: &mut LogStore) -> Op {
|
||||
pub(crate) fn apply_insert(&mut self, pos: usize, text: &str, id: ID) -> Op {
|
||||
if self.state.len() < pos {
|
||||
panic!("insert index out of range");
|
||||
}
|
||||
let id = store.next_id();
|
||||
let slice = self.raw_str.alloc(text);
|
||||
let op_slice = SliceRange::from_pool_string(&slice);
|
||||
self.state.insert(pos, slice);
|
||||
|
@ -93,11 +98,10 @@ impl TextContainer {
|
|||
)
|
||||
}
|
||||
|
||||
pub(crate) fn apply_delete(&mut self, pos: usize, len: usize, store: &mut LogStore) -> Op {
|
||||
pub(crate) fn apply_delete(&mut self, pos: usize, len: usize, id: ID) -> Op {
|
||||
if self.state.len() < pos + len {
|
||||
panic!("deletion out of range");
|
||||
}
|
||||
let id = store.next_id();
|
||||
self.state.delete_range(Some(pos), Some(pos + len));
|
||||
Op::new(
|
||||
id,
|
||||
|
@ -132,7 +136,7 @@ impl TextContainer {
|
|||
}
|
||||
}
|
||||
|
||||
impl Container for TextContainer {
|
||||
impl ContainerTrait for TextContainer {
|
||||
#[inline(always)]
|
||||
fn id(&self) -> &ContainerID {
|
||||
&self.id
|
||||
|
@ -567,6 +571,18 @@ impl ContainerWrapper for Text {
|
|||
self.client_id
|
||||
}
|
||||
|
||||
fn is_instance(&self) -> bool {
|
||||
matches!(self.container, ContainerInner::Instance(_))
|
||||
}
|
||||
|
||||
fn try_to_update(&mut self, loro: &LoroCore) {
|
||||
if !self.is_instance() {
|
||||
let idx = self.idx();
|
||||
let new = loro.get_text_by_idx(&idx).unwrap();
|
||||
*self = new;
|
||||
}
|
||||
}
|
||||
|
||||
fn container_inner(&self) -> &ContainerInner {
|
||||
&self.container
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ use debug_log::debug_log;
|
|||
use enum_as_inner::EnumAsInner;
|
||||
use tabled::{TableIteratorExt, Tabled};
|
||||
pub mod recursive;
|
||||
pub mod recursive_txn;
|
||||
|
||||
use crate::{
|
||||
array_mut_ref, id::ClientID, log_store::EncodeConfig, LoroCore, Transact, VersionVector,
|
||||
|
|
|
@ -16,7 +16,7 @@ use crate::{
|
|||
event::{Diff, Observer},
|
||||
id::ClientID,
|
||||
log_store::EncodeConfig,
|
||||
ContainerType, List, LoroCore, LoroValue, Map, Text, Transact,
|
||||
Container, ContainerType, List, LoroCore, LoroValue, Map, Text, Transact,
|
||||
};
|
||||
|
||||
#[derive(Arbitrary, EnumAsInner, Clone, PartialEq, Eq, Debug)]
|
||||
|
@ -262,7 +262,7 @@ trait Actionable {
|
|||
}
|
||||
|
||||
impl Actor {
|
||||
fn add_new_container(&mut self, container: ContainerTemp) {
|
||||
fn add_new_container(&mut self, container: Container) {
|
||||
let new = container.idx();
|
||||
let type_ = container.type_();
|
||||
let store = self.loro.log_store.try_read().unwrap();
|
||||
|
|
1440
crates/loro-internal/src/fuzz/recursive_txn.rs
Normal file
1440
crates/loro-internal/src/fuzz/recursive_txn.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -42,7 +42,7 @@ pub(crate) use op::{ContentType, InsertContentTrait, Op};
|
|||
|
||||
// TODO: rename as Key?
|
||||
pub(crate) type InternalString = DefaultAtom;
|
||||
pub(crate) use container::Container;
|
||||
pub use container::{Container, ContainerTrait};
|
||||
|
||||
pub use container::{list::List, map::Map, text::Text, ContainerType};
|
||||
pub use log_store::LogStore;
|
||||
|
|
|
@ -15,7 +15,7 @@ use crate::{
|
|||
map::{InnerMapSet, ValueSlot},
|
||||
pool_mapping::StateContent,
|
||||
registry::ContainerIdx,
|
||||
Container, ContainerID,
|
||||
ContainerID, ContainerTrait,
|
||||
},
|
||||
dag::remove_included_frontiers,
|
||||
event::RawEvent,
|
||||
|
|
|
@ -16,7 +16,7 @@ use fxhash::{FxHashMap, FxHashSet};
|
|||
use rle::{slice_vec_by, HasLength, RleVecWithIndex};
|
||||
|
||||
use crate::{
|
||||
container::{registry::ContainerInstance, Container, ContainerID},
|
||||
container::{registry::ContainerInstance, ContainerID, ContainerTrait},
|
||||
dag::{remove_included_frontiers, DagUtils},
|
||||
op::RichOp,
|
||||
span::{HasCounter, HasIdSpan, HasLamportSpan, IdSpan},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
use crate::{
|
||||
container::{temp::ContainerTemp, ContainerID},
|
||||
container::{registry::ContainerIdx, temp::ContainerTemp, ContainerID},
|
||||
context::Context,
|
||||
event::{ObserverHandler, RawEvent},
|
||||
hierarchy::Hierarchy,
|
||||
|
@ -76,21 +76,21 @@ impl LoroCore {
|
|||
Text::from_instance(instance, cid)
|
||||
}
|
||||
|
||||
pub fn get_list_by_temp(&self, temp_container: ContainerTemp) -> Option<List> {
|
||||
pub fn get_list_by_idx(&self, idx: &ContainerIdx) -> Option<List> {
|
||||
let cid = self.client_id();
|
||||
self.get_container_by_idx(&temp_container.idx())
|
||||
self.get_container_by_idx(idx)
|
||||
.map(|i| List::from_instance(i, cid))
|
||||
}
|
||||
|
||||
pub fn get_map_by_temp(&self, temp_container: ContainerTemp) -> Option<Map> {
|
||||
pub fn get_map_by_idx(&self, idx: &ContainerIdx) -> Option<Map> {
|
||||
let cid = self.client_id();
|
||||
self.get_container_by_idx(&temp_container.idx())
|
||||
self.get_container_by_idx(idx)
|
||||
.map(|i| Map::from_instance(i, cid))
|
||||
}
|
||||
|
||||
pub fn get_text_by_temp(&self, temp_container: ContainerTemp) -> Option<Text> {
|
||||
pub fn get_text_by_idx(&self, idx: &ContainerIdx) -> Option<Text> {
|
||||
let cid = self.client_id();
|
||||
self.get_container_by_idx(&temp_container.idx())
|
||||
self.get_container_by_idx(idx)
|
||||
.map(|i| Text::from_instance(i, cid))
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
|||
change::{Change, Lamport, Timestamp},
|
||||
container::{
|
||||
registry::{ContainerIdx, ContainerInstance},
|
||||
Container, ContainerID,
|
||||
ContainerID, ContainerTrait,
|
||||
},
|
||||
id::{ClientID, Counter, ID},
|
||||
span::{HasCounter, HasId, HasLamport},
|
||||
|
|
|
@ -16,11 +16,11 @@ pub const PROPTEST_FACTOR_1: usize = 0;
|
|||
fn size_of() {
|
||||
use crate::change::Change;
|
||||
use crate::{
|
||||
container::{map::MapSet, text::text_content::ListSlice, ContainerID},
|
||||
container::{map::MapSet, text::text_content::ListSlice, ContainerID, ContainerTrait},
|
||||
id::ID,
|
||||
op::{Op, RemoteContent},
|
||||
span::IdSpan,
|
||||
Container, InternalString,
|
||||
InternalString,
|
||||
};
|
||||
use std::ops::Range;
|
||||
|
||||
|
@ -31,7 +31,7 @@ fn size_of() {
|
|||
println!("InsertContent {}", std::mem::size_of::<RemoteContent>());
|
||||
println!("MapSet {}", std::mem::size_of::<MapSet>());
|
||||
println!("ListSlice {}", std::mem::size_of::<ListSlice>());
|
||||
println!("Box {}", std::mem::size_of::<Box<dyn Container>>());
|
||||
println!("Box {}", std::mem::size_of::<Box<dyn ContainerTrait>>());
|
||||
println!("LoroValue {}", std::mem::size_of::<LoroValue>());
|
||||
println!("ID {}", std::mem::size_of::<ID>());
|
||||
println!("Vec {}", std::mem::size_of::<Vec<ID>>());
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::{
|
|||
use fxhash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::{
|
||||
container::{registry::ContainerIdx, Container, ContainerID},
|
||||
container::{registry::ContainerIdx, ContainerID, ContainerTrait},
|
||||
event::{Diff, RawEvent},
|
||||
hierarchy::Hierarchy,
|
||||
id::ClientID,
|
||||
|
@ -104,19 +104,6 @@ impl Transaction {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn get_container_idx_by_id(&self, id: &ContainerID) -> Option<ContainerIdx> {
|
||||
self.with_store(|store| store.reg.get_idx(id))
|
||||
}
|
||||
|
||||
fn with_store<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&LogStore) -> R,
|
||||
{
|
||||
let store = self.store.upgrade().unwrap();
|
||||
let store = store.read().unwrap();
|
||||
f(&store)
|
||||
}
|
||||
|
||||
fn with_store_hierarchy_mut<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&Self, &mut LogStore, &mut Hierarchy) -> R,
|
||||
|
@ -130,6 +117,7 @@ impl Transaction {
|
|||
|
||||
fn apply_ops_emit_event(&mut self) {
|
||||
let compressed_op = std::mem::take(&mut self.compressed_op);
|
||||
println!("compressed op {:?}", compressed_op);
|
||||
let events = self.with_store_hierarchy_mut(|_txn, store, hierarchy| {
|
||||
let mut events = Vec::with_capacity(compressed_op.len());
|
||||
for op in compressed_op {
|
||||
|
@ -140,6 +128,7 @@ impl Transaction {
|
|||
let container_id = container.id().clone();
|
||||
let type_ = container_id.container_type();
|
||||
let store_ops = container.apply_txn_op(store, &op);
|
||||
println!("store ops: {:?}", store_ops);
|
||||
drop(container);
|
||||
let (old_version, new_version) = store.append_local_ops(&store_ops);
|
||||
let new_version = new_version.into();
|
||||
|
@ -209,7 +198,13 @@ impl Transaction {
|
|||
) -> ContainerID {
|
||||
self.with_store_hierarchy_mut(|_txn, s, h| {
|
||||
let id = s.next_id();
|
||||
let container_id = ContainerID::new_normal(id, type_);
|
||||
let mut container_id = ContainerID::new_normal(id, type_);
|
||||
|
||||
while s.reg.contains(&container_id) {
|
||||
if let ContainerID::Normal { id, .. } = &mut container_id {
|
||||
id.counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let parent_id = s.reg.get_id(parent_idx).unwrap();
|
||||
h.add_child(parent_id, &container_id);
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
|||
container::{registry::ContainerRegistry, ContainerID},
|
||||
delta::DeltaItem,
|
||||
event::{Diff, Index, Path},
|
||||
Container,
|
||||
ContainerTrait,
|
||||
};
|
||||
|
||||
/// [LoroValue] is used to represents the state of CRDT at a given version
|
||||
|
|
|
@ -6,7 +6,7 @@ use loro_internal::context::Context;
|
|||
use loro_internal::id::ID;
|
||||
|
||||
use loro_internal::log_store::EncodeConfig;
|
||||
use loro_internal::{ContainerType, LoroCore, Text, Transact, VersionVector};
|
||||
use loro_internal::{ContainerType, LoroCore, Text, VersionVector};
|
||||
|
||||
#[test]
|
||||
fn send_sync() {
|
||||
|
@ -47,13 +47,18 @@ fn example() {
|
|||
let mut doc = LoroCore::default();
|
||||
let mut list = doc.get_list("list");
|
||||
list.insert(&doc, 0, 123).unwrap();
|
||||
let map_id = list.insert(&doc, 1, ContainerType::Map).unwrap().unwrap();
|
||||
let mut map = doc.get_map_by_temp(map_id).unwrap();
|
||||
let map_id = list
|
||||
.insert(&doc, 1, ContainerType::Map)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.idx();
|
||||
let mut map = doc.get_map_by_idx(&map_id).unwrap();
|
||||
let text = map
|
||||
.insert(&doc, "map_b", ContainerType::Text)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let mut text = doc.get_text_by_temp(text).unwrap();
|
||||
.unwrap()
|
||||
.idx();
|
||||
let mut text = doc.get_text_by_idx(&text).unwrap();
|
||||
text.insert(&doc, 0, "world!").unwrap();
|
||||
text.insert(&doc, 0, "hello ").unwrap();
|
||||
assert_eq!(
|
||||
|
@ -91,10 +96,15 @@ fn text_observe() {
|
|||
let list = map
|
||||
.insert(&doc, "to-dos", ContainerType::List)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let mut list = doc.get_list_by_temp(list).unwrap();
|
||||
let todo_item = list.insert(&doc, 0, ContainerType::Map).unwrap().unwrap();
|
||||
let mut todo_item = doc.get_map_by_temp(todo_item).unwrap();
|
||||
.unwrap()
|
||||
.idx();
|
||||
let mut list = doc.get_list_by_idx(&list).unwrap();
|
||||
let todo_item = list
|
||||
.insert(&doc, 0, ContainerType::Map)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.idx();
|
||||
let mut todo_item = doc.get_map_by_idx(&todo_item).unwrap();
|
||||
todo_item.insert(&doc, "todo", "coding").unwrap();
|
||||
assert_eq!(&doc.to_json(), &*track_value.lock().unwrap());
|
||||
let mut text = doc.get_text("text");
|
||||
|
@ -123,8 +133,9 @@ fn list() {
|
|||
let map_id = list_b
|
||||
.insert(&loro_b, 1, loro_internal::ContainerType::Map)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let mut map = loro_b.get_map_by_temp(map_id).unwrap();
|
||||
.unwrap()
|
||||
.idx();
|
||||
let mut map = loro_b.get_map_by_idx(&map_id).unwrap();
|
||||
map.insert(&loro_b, "map_b", 123).unwrap();
|
||||
println!("{}", list_a.get_value().to_json());
|
||||
println!("{}", list_b.get_value().to_json());
|
||||
|
@ -158,11 +169,9 @@ fn map() {
|
|||
let map_id = root
|
||||
.insert(&loro, "map", loro_internal::ContainerType::Map)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.idx();
|
||||
.unwrap();
|
||||
drop(root);
|
||||
let sub_map = loro.get_container_by_idx(&map_id).unwrap();
|
||||
let mut sub_map = Map::from_instance(sub_map, loro.client_id());
|
||||
let mut sub_map = loro.get_map_by_idx(&map_id.idx()).unwrap();
|
||||
sub_map.insert(&loro, "sub", false).unwrap();
|
||||
drop(sub_map);
|
||||
let root = loro.get_map("root");
|
||||
|
@ -284,8 +293,12 @@ fn encode_hierarchy() {
|
|||
|
||||
let mut c1 = LoroCore::default();
|
||||
let mut map = c1.get_map("map");
|
||||
let list_id = map.insert(&c1, "a", ContainerType::List).unwrap().unwrap();
|
||||
let mut list = c1.get_list_by_temp(list_id).unwrap();
|
||||
let list_id = map
|
||||
.insert(&c1, "a", ContainerType::List)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.idx();
|
||||
let mut list = c1.get_list_by_idx(&list_id).unwrap();
|
||||
let idx = list
|
||||
.insert(&c1, 0, ContainerType::Text)
|
||||
.unwrap()
|
||||
|
|
Loading…
Reference in a new issue