Use SmolStr in place of String ()

Most of the strings are small and so fit nicely in a SmolStr. When they
don't it just reverts to using a normal String.
This commit is contained in:
Andrew Jeffery 2021-06-19 16:28:51 +01:00 committed by GitHub
parent ff8b8613d5
commit fc1b8f87fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 260 additions and 205 deletions

View file

@ -25,6 +25,7 @@ itertools = "0.9.0"
tracing = { version = "0.1.25", features = ["log"] }
flate2 = "1.0.20"
nonzero_ext = "^0.2.0"
smol_str = "0.1.17"
[dependencies.web-sys]
version = "0.3"

View file

@ -1,6 +1,7 @@
use std::cmp::Ordering;
use automerge_protocol as amp;
use smol_str::SmolStr;
use crate::{
expanded_op::ExpandedOp,
@ -13,7 +14,7 @@ pub(crate) struct ActorMap(Vec<amp::ActorId>);
impl ActorMap {
pub fn import_key(&mut self, key: &amp::Key) -> Key {
match key {
amp::Key::Map(string) => Key::Map(string.to_string()),
amp::Key::Map(string) => Key::Map(string.clone()),
amp::Key::Seq(eid) => Key::Seq(self.import_element_id(eid)),
}
}
@ -94,18 +95,22 @@ impl ActorMap {
}
}
pub fn opid_to_string(&self, id: &OpId) -> String {
format!("{}@{}", id.0, self.export_actor(id.1).to_hex_string())
pub fn opid_to_string(&self, id: &OpId) -> SmolStr {
SmolStr::new(format!(
"{}@{}",
id.0,
self.export_actor(id.1).to_hex_string()
))
}
pub fn elementid_to_string(&self, eid: &ElementId) -> String {
pub fn elementid_to_string(&self, eid: &ElementId) -> SmolStr {
match eid {
ElementId::Head => "_head".into(),
ElementId::Id(id) => self.opid_to_string(id),
}
}
pub fn key_to_string(&self, key: &Key) -> String {
pub fn key_to_string(&self, key: &Key) -> SmolStr {
match &key {
Key::Map(s) => s.clone(),
Key::Seq(eid) => self.elementid_to_string(eid),

View file

@ -11,6 +11,7 @@ use std::{
use automerge_protocol as amp;
use flate2::bufread::DeflateDecoder;
use smol_str::SmolStr;
use tracing::instrument;
use crate::{
@ -324,7 +325,7 @@ pub struct KeyIterator<'a> {
pub(crate) actors: &'a [amp::ActorId],
pub(crate) actor: RleDecoder<'a, usize>,
pub(crate) ctr: DeltaDecoder<'a>,
pub(crate) str: RleDecoder<'a, String>,
pub(crate) str: RleDecoder<'a, SmolStr>,
}
pub struct ValueIterator<'a> {
@ -435,7 +436,7 @@ impl<'a> Iterator for ValueIterator<'a> {
let len = v >> 4;
let data = self.val_raw.read_bytes(len).ok()?;
let s = str::from_utf8(data).ok()?;
Some(amp::ScalarValue::Str(s.to_string()))
Some(amp::ScalarValue::Str(SmolStr::new(s)))
}
v if v % 16 == VALUE_TYPE_BYTES => {
let len = v >> 4;
@ -643,7 +644,7 @@ impl ValEncoder {
struct KeyEncoder {
actor: RleEncoder<usize>,
ctr: DeltaEncoder,
str: RleEncoder<String>,
str: RleEncoder<SmolStr>,
}
impl KeyEncoder {
@ -1347,7 +1348,7 @@ mod tests {
let col_op = ColumnOp {
action: InternalOpType::Set(ScalarValue::Null),
obj: Cow::Owned(amp::ObjectId::Root),
key: Cow::Owned(Key::Map("r".to_owned())),
key: Cow::Owned(Key::Map("r".into())),
pred: vec![actor.op_id_at(1), actor2.op_id_at(1)],
insert: false,
};
@ -1359,7 +1360,7 @@ mod tests {
let col_op2 = ColumnOp {
action: InternalOpType::Set(ScalarValue::Null),
obj: Cow::Owned(amp::ObjectId::Root),
key: Cow::Owned(Key::Map("r".to_owned())),
key: Cow::Owned(Key::Map("r".into())),
pred: vec![actor2.op_id_at(1), actor.op_id_at(1)],
insert: false,
};

View file

@ -2,6 +2,7 @@ use core::fmt::Debug;
use std::{borrow::Cow, convert::TryFrom, io, io::Read, str};
use automerge_protocol as amp;
use smol_str::SmolStr;
/// The error type for decoding operations.
#[derive(Debug, thiserror::Error)]
@ -362,6 +363,17 @@ impl Decodable for Vec<u8> {
Some(buffer)
}
}
impl Decodable for SmolStr {
fn decode<R>(bytes: &mut R) -> Option<SmolStr>
where
R: Read,
{
let buffer = Vec::decode(bytes)?;
str::from_utf8(&buffer).map(|t| t.into()).ok()
}
}
impl Decodable for String {
fn decode<R>(bytes: &mut R) -> Option<String>
where

View file

@ -7,6 +7,7 @@ use std::{
use automerge_protocol as amp;
use flate2::{bufread::DeflateEncoder, Compression};
use smol_str::SmolStr;
use crate::columnar::COLUMN_TYPE_DEFLATE;
@ -254,6 +255,15 @@ pub(crate) trait Encodable {
fn encode<R: Write>(&self, buf: &mut R) -> io::Result<usize>;
}
impl Encodable for SmolStr {
fn encode<R: Write>(&self, buf: &mut R) -> io::Result<usize> {
let bytes = self.as_bytes();
let head = bytes.len().encode(buf)?;
buf.write_all(bytes)?;
Ok(head + bytes.len())
}
}
impl Encodable for String {
fn encode<R: Write>(&self, buf: &mut R) -> io::Result<usize> {
let bytes = self.as_bytes();

View file

@ -204,8 +204,8 @@ mod tests {
},
Op {
action: OpType::MultiSet(vec![
ScalarValue::Str("hi ".to_owned()),
ScalarValue::Str("world".to_owned()),
ScalarValue::Str("hi ".into()),
ScalarValue::Str("world".into()),
]),
obj: ObjectId::Id(OpId(1, actor.clone())),
key: Key::Seq(ElementId::Id(OpId(4, actor.clone()))),
@ -239,14 +239,14 @@ mod tests {
insert: true
},
ExpandedOp {
action: InternalOpType::Set(ScalarValue::Str("hi ".to_owned())),
action: InternalOpType::Set(ScalarValue::Str("hi ".into())),
obj: Cow::Owned(ObjectId::Id(OpId(1, actor.clone()))),
key: Cow::Owned(Key::Seq(ElementId::Id(OpId(4, actor.clone())))),
pred: Cow::Owned(vec![]),
insert: true
},
ExpandedOp {
action: InternalOpType::Set(ScalarValue::Str("world".to_owned())),
action: InternalOpType::Set(ScalarValue::Str("world".into())),
obj: Cow::Owned(ObjectId::Id(OpId(1, actor.clone()))),
key: Cow::Owned(Key::Seq(ElementId::Id(OpId(5, actor)))),
pred: Cow::Owned(vec![]),

View file

@ -1,5 +1,6 @@
use automerge_protocol as amp;
use nonzero_ext::nonzero;
use smol_str::SmolStr;
#[derive(Eq, PartialEq, Hash, Debug, Clone, Copy)]
pub(crate) struct ActorId(pub usize);
@ -21,7 +22,7 @@ pub(crate) enum ElementId {
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
pub(crate) enum Key {
Map(String),
Map(SmolStr),
Seq(ElementId),
}

View file

@ -10,6 +10,7 @@ use std::collections::{HashMap, HashSet};
use automerge_protocol as amp;
use fxhash::FxBuildHasher;
use smol_str::SmolStr;
use tracing::instrument;
use crate::{
@ -335,7 +336,7 @@ impl<'a> PatchWorkshop for PatchWorkshopImpl<'a> {
})
}
fn key_to_string(&self, key: &crate::internal::Key) -> String {
fn key_to_string(&self, key: &crate::internal::Key) -> SmolStr {
self.actors.key_to_string(key)
}

View file

@ -1,4 +1,5 @@
use automerge_protocol as amp;
use smol_str::SmolStr;
use crate::{
internal::{Key, ObjectId, OpId},
@ -14,7 +15,7 @@ use crate::{
/// building of the patch. It's just where some tools to make the patch can be
/// found
pub(crate) trait PatchWorkshop {
fn key_to_string(&self, key: &Key) -> String;
fn key_to_string(&self, key: &Key) -> SmolStr;
fn find_cursor(&self, opid: &amp::OpId) -> Option<amp::CursorDiff>;
fn get_obj(&self, object_id: &ObjectId) -> Option<&ObjState>;
fn make_external_objid(&self, object_id: &ObjectId) -> amp::ObjectId;

View file

@ -996,7 +996,7 @@ fn test_handle_changes_within_conflicted_lists() {
value: Diff::Map(MapDiff{
object_id: actor1.op_id_at(3).into(),
props: hashmap!{
"done".to_string() => hashmap!{
"done".into() => hashmap!{
actor1.op_id_at(6) => Diff::Value(true.into())
}
}

View file

@ -19,6 +19,7 @@ thiserror = "1.0.16"
im-rc = "15.0.0"
unicode-segmentation = "1.7.1"
arbitrary = { version = "1", features = ["derive"], optional = true }
smol_str = "0.1.17"
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
getrandom = { version = "0.2.2", features=["js"] }

View file

@ -1,6 +1,7 @@
use automerge_frontend::{Frontend, InvalidChangeRequest, LocalChange, Path, Value};
use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion};
use rand::{thread_rng, Rng};
use smol_str::SmolStr;
use unicode_segmentation::UnicodeSegmentation;
pub fn insert_long_string(c: &mut Criterion) {
@ -8,7 +9,7 @@ pub fn insert_long_string(c: &mut Criterion) {
b.iter_batched(
|| {
let doc = Frontend::new();
let random_string: String = thread_rng()
let random_string: SmolStr = thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(6000)
.map(char::from)
@ -21,7 +22,7 @@ pub fn insert_long_string(c: &mut Criterion) {
doc.change::<_, _, InvalidChangeRequest>(None, |d| {
d.add_change(LocalChange::set(
Path::root().key("text"),
Value::Text(string.graphemes(true).map(|s| s.to_owned()).collect()),
Value::Text(string.graphemes(true).map(|s| s.into()).collect()),
))
})
.unwrap()

View file

@ -16,7 +16,7 @@ pub fn sequential_inserts_in_multiple_patches(c: &mut Criterion) {
pending_changes: 0,
diffs: RootDiff {
props: hashmap! {
"text".to_string() => hashmap!{
"text".into() => hashmap!{
make_list_opid.clone() => amp::Diff::Text(amp::TextDiff{
object_id: make_list_opid.clone().into(),
edits: Vec::new(),
@ -37,14 +37,14 @@ pub fn sequential_inserts_in_multiple_patches(c: &mut Criterion) {
pending_changes: 0,
diffs: RootDiff {
props: hashmap! {
"text".to_string() => hashmap!{
"text".into() => hashmap!{
make_list_opid.clone() => amp::Diff::Text(amp::TextDiff{
object_id: make_list_opid.clone().into(),
edits: vec![amp::DiffEdit::SingleElementInsert{
index,
elem_id: this_op_id.clone().into(),
op_id: this_op_id.clone(),
value: amp::Diff::Value(amp::ScalarValue::Str("c".to_string())),
value: amp::Diff::Value(amp::ScalarValue::Str("c".into())),
}],
})
}
@ -86,7 +86,7 @@ pub fn sequential_inserts_in_single_patch(c: &mut Criterion) {
index,
elem_id: this_op_id.clone().into(),
op_id: this_op_id.clone(),
value: amp::Diff::Value(amp::ScalarValue::Str("c".to_string())),
value: amp::Diff::Value(amp::ScalarValue::Str("c".into())),
});
}
let patch: amp::Patch = amp::Patch {
@ -98,7 +98,7 @@ pub fn sequential_inserts_in_single_patch(c: &mut Criterion) {
pending_changes: 0,
diffs: RootDiff {
props: hashmap! {
"text".to_string() => hashmap!{
"text".into() => hashmap!{
make_list_opid.clone() => amp::Diff::Text(amp::TextDiff{
object_id: make_list_opid.into(),
edits,

View file

@ -410,7 +410,7 @@ impl Frontend {
&front.actor_id,
max_op,
ObjectId::Root,
&k.into(),
&amp::Key::Map(k.clone()),
v,
false,
);

View file

@ -131,7 +131,7 @@ impl<'a> MutationTracker<'a> {
match value {
Value::Map(kvs) => {
for (k, v) in kvs.iter() {
self.add_change(LocalChange::set(Path::root().key(k), v.clone()))?;
self.add_change(LocalChange::set(Path::root().key(k.clone()), v.clone()))?;
}
Ok(())
}
@ -467,7 +467,7 @@ impl<'a> MutableDocument for MutationTracker<'a> {
actor: &self.actor_id.clone(),
value,
};
let (old, res) = root_target.set_key(k, payload);
let (old, res) = root_target.set_key(k.clone(), payload);
Ok((LocalOperationForRollback::Set { old }, res))
}
(PathElement::Key(ref k), ResolvedPathMut::Map(ref mut maptarget)) => {
@ -476,7 +476,7 @@ impl<'a> MutableDocument for MutationTracker<'a> {
actor: &self.actor_id.clone(),
value,
};
let (old, res) = maptarget.set_key(k, payload);
let (old, res) = maptarget.set_key(k.clone(), payload);
Ok((LocalOperationForRollback::Set { old }, res))
}
(
@ -488,7 +488,7 @@ impl<'a> MutableDocument for MutationTracker<'a> {
actor: &self.actor_id.clone(),
value,
};
let (old, res) = tabletarget.set_key(k, payload);
let (old, res) = tabletarget.set_key(k.clone(), payload);
Ok((LocalOperationForRollback::Set { old }, res))
}
// In this case we are trying to modify a key in something which is not

View file

@ -1,8 +1,10 @@
use std::fmt;
use smol_str::SmolStr;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) enum PathElement {
Key(String),
Key(SmolStr),
Index(u32),
}
@ -19,7 +21,7 @@ impl Path {
self
}
pub fn key<S: Into<String>>(mut self, key: S) -> Path {
pub fn key<S: Into<SmolStr>>(mut self, key: S) -> Path {
self.0.push(PathElement::Key(key.into()));
self
}

View file

@ -5,6 +5,7 @@ use automerge_protocol as amp;
use automerge_protocol::RootDiff;
use diffable_sequence::DiffableSequence;
use multivalue::NewValueRequest;
use smol_str::SmolStr;
use crate::{error, Cursor, Path, PathElement, Primitive, Value};
@ -28,7 +29,7 @@ pub(crate) struct LocalOperationResult {
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct StateTree {
root_props: HashMap<String, MultiValue>,
root_props: HashMap<SmolStr, MultiValue>,
cursors: Cursors,
}
@ -327,9 +328,12 @@ impl StateTreeComposite {
Self::List(StateTreeList {
elements: elems, ..
}) => Value::Sequence(elems.iter().map(|e| e.default_value()).collect()),
Self::Text(StateTreeText { graphemes, .. }) => {
Value::Text(graphemes.iter().map(|c| c.default_grapheme()).collect())
}
Self::Text(StateTreeText { graphemes, .. }) => Value::Text(
graphemes
.iter()
.map(|c| c.default_grapheme().clone())
.collect(),
),
}
}
@ -464,13 +468,13 @@ impl StateTreeValue {
#[derive(Debug, Clone, PartialEq)]
struct StateTreeMap {
object_id: amp::ObjectId,
props: HashMap<String, MultiValue>,
props: HashMap<SmolStr, MultiValue>,
}
impl StateTreeMap {
fn check_diff(
&self,
prop_diffs: &HashMap<String, HashMap<amp::OpId, amp::Diff>>,
prop_diffs: &HashMap<SmolStr, HashMap<amp::OpId, amp::Diff>>,
) -> Result<(), error::InvalidPatch> {
for (prop, prop_diff) in prop_diffs {
let mut diff_iter = prop_diff.iter();
@ -494,7 +498,7 @@ impl StateTreeMap {
Ok(())
}
fn apply_diff(&mut self, prop_diffs: HashMap<String, HashMap<amp::OpId, amp::Diff>>) {
fn apply_diff(&mut self, prop_diffs: HashMap<SmolStr, HashMap<amp::OpId, amp::Diff>>) {
for (prop, prop_diff) in prop_diffs {
let mut diff_iter = prop_diff.into_iter();
match diff_iter.next() {
@ -531,7 +535,7 @@ impl StateTreeMap {
.get(key)
.map(|mv| mv.update_default(StateTreeValue::Leaf(cursor)));
if let Some(new_mv) = new_mv {
self.props.insert(key.to_string(), new_mv);
self.props.insert(key.into(), new_mv);
}
}
@ -564,13 +568,13 @@ impl StateTreeMap {
#[derive(Debug, Clone, PartialEq)]
struct StateTreeTable {
object_id: amp::ObjectId,
props: HashMap<String, MultiValue>,
props: HashMap<SmolStr, MultiValue>,
}
impl StateTreeTable {
fn check_diff(
&self,
prop_diffs: &HashMap<String, HashMap<amp::OpId, amp::Diff>>,
prop_diffs: &HashMap<SmolStr, HashMap<amp::OpId, amp::Diff>>,
) -> Result<(), error::InvalidPatch> {
for (prop, prop_diff) in prop_diffs {
let mut diff_iter = prop_diff.iter();
@ -594,7 +598,7 @@ impl StateTreeTable {
Ok(())
}
fn apply_diff(&mut self, prop_diffs: HashMap<String, HashMap<amp::OpId, amp::Diff>>) {
fn apply_diff(&mut self, prop_diffs: HashMap<SmolStr, HashMap<amp::OpId, amp::Diff>>) {
for (prop, prop_diff) in prop_diffs {
let mut diff_iter = prop_diff.into_iter();
match diff_iter.next() {
@ -625,13 +629,13 @@ impl StateTreeTable {
.unwrap_or_else(Vec::new)
}
pub fn mutably_update_cursor(&mut self, key: &str, cursor: Primitive) {
pub fn mutably_update_cursor(&mut self, key: &SmolStr, cursor: Primitive) {
let new_mv = self
.props
.get(key)
.map(|mv| mv.update_default(StateTreeValue::Leaf(cursor)));
if let Some(new_mv) = new_mv {
self.props.insert(key.to_string(), new_mv);
self.props.insert(key.clone(), new_mv);
}
}
@ -699,7 +703,7 @@ impl StateTreeText {
pub(crate) fn elem_at(
&self,
index: usize,
) -> Result<(&amp::OpId, String), error::MissingIndexError> {
) -> Result<(&amp::OpId, &SmolStr), error::MissingIndexError> {
self.graphemes
.get(index)
.map(|mc| (mc.0, mc.1.default_grapheme()))

View file

@ -1,6 +1,7 @@
use std::{cmp::Ordering, collections::HashMap, iter::Iterator};
use automerge_protocol as amp;
use smol_str::SmolStr;
use unicode_segmentation::UnicodeSegmentation;
use super::{
@ -344,12 +345,12 @@ impl NewValue {
/// sequences of grapheme clusters
#[derive(Debug, Clone, PartialEq, Default)]
pub struct MultiGrapheme {
winning_value: (amp::OpId, String),
conflicts: HashMap<amp::OpId, String>,
winning_value: (amp::OpId, SmolStr),
conflicts: HashMap<amp::OpId, SmolStr>,
}
impl MultiGrapheme {
pub(super) fn new_from_grapheme_cluster(opid: amp::OpId, s: String) -> MultiGrapheme {
pub(super) fn new_from_grapheme_cluster(opid: amp::OpId, s: SmolStr) -> MultiGrapheme {
debug_assert_eq!(s.graphemes(true).count(), 1);
MultiGrapheme {
winning_value: (opid, s),
@ -450,7 +451,7 @@ impl MultiGrapheme {
}
}
fn update(&mut self, key: &amp::OpId, value: String) {
fn update(&mut self, key: &amp::OpId, value: SmolStr) {
match key.cmp(&self.winning_value.0) {
Ordering::Equal => {
self.winning_value.1 = value;
@ -467,15 +468,15 @@ impl MultiGrapheme {
}
}
pub(super) fn default_grapheme(&self) -> String {
self.winning_value.1.clone()
pub(super) fn default_grapheme(&self) -> &SmolStr {
&self.winning_value.1
}
pub fn default_opid(&self) -> &amp::OpId {
&self.winning_value.0
}
fn iter(&self) -> impl std::iter::Iterator<Item = (&amp::OpId, &String)> {
fn iter(&self) -> impl std::iter::Iterator<Item = (&amp::OpId, &SmolStr)> {
std::iter::once((&(self.winning_value).0, &(self.winning_value.1)))
.chain(self.conflicts.iter())
}
@ -568,7 +569,7 @@ where
fn new_map_or_table(
self,
props: std::collections::HashMap<String, Value>,
props: std::collections::HashMap<SmolStr, Value>,
map_type: amp::MapType,
) -> NewValue {
let make_op_id = amp::OpId(self.start_op, self.actor.clone());
@ -584,7 +585,7 @@ where
ops.push(make_op);
let mut current_max_op = self.start_op;
let mut cursors = Cursors::new();
let mut result_props: HashMap<String, MultiValue> = HashMap::with_capacity(props.len());
let mut result_props: HashMap<SmolStr, MultiValue> = HashMap::with_capacity(props.len());
for (prop, value) in props {
let context = NewValueContext {
actor: self.actor,
@ -669,7 +670,7 @@ where
}
}
fn new_text(self, graphemes: Vec<String>) -> NewValue {
fn new_text(self, graphemes: Vec<SmolStr>) -> NewValue {
let make_text_opid = self.actor.op_id_at(self.start_op);
let make_op = amp::Op {
action: amp::OpType::Make(amp::ObjType::Text),

View file

@ -1,6 +1,7 @@
use std::{convert::TryInto, num::NonZeroU32};
use automerge_protocol as amp;
use smol_str::SmolStr;
use super::{
random_op_id, LocalOperationResult, MultiGrapheme, MultiValue, NewValueRequest, StateTree,
@ -108,9 +109,9 @@ impl<'a> ResolvedPath<'a> {
ResolvedPath::Text(texttarget) => texttarget.multivalue.default_value(),
ResolvedPath::Counter(countertarget) => countertarget.multivalue.default_value(),
ResolvedPath::Primitive(p) => p.multivalue.default_value(),
ResolvedPath::Character(ctarget) => {
Value::Primitive(Primitive::Str(ctarget.multivalue.default_grapheme()))
}
ResolvedPath::Character(ctarget) => Value::Primitive(Primitive::Str(
ctarget.multivalue.default_grapheme().clone(),
)),
}
}
@ -257,9 +258,9 @@ impl<'a> ResolvedPathMut<'a> {
ResolvedPathMut::Text(texttarget) => texttarget.multivalue.default_value(),
ResolvedPathMut::Counter(countertarget) => countertarget.multivalue.default_value(),
ResolvedPathMut::Primitive(p) => p.multivalue.default_value(),
ResolvedPathMut::Character(ctarget) => {
Value::Primitive(Primitive::Str(ctarget.multivalue.default_grapheme()))
}
ResolvedPathMut::Character(ctarget) => Value::Primitive(Primitive::Str(
ctarget.multivalue.default_grapheme().clone(),
)),
}
}
}
@ -281,24 +282,24 @@ pub struct ResolvedRootMut<'a> {
impl<'a> ResolvedRootMut<'a> {
pub(crate) fn set_key(
&mut self,
key: &str,
key: SmolStr,
payload: SetOrInsertPayload<Value>,
) -> (Option<MultiValue>, LocalOperationResult) {
let newvalue = MultiValue::new_from_value_2(NewValueRequest {
actor: payload.actor,
start_op: payload.start_op,
key: key.into(),
key: amp::Key::Map(key.clone()),
parent_obj: &amp::ObjectId::Root,
value: payload.value,
insert: false,
pred: self
.root
.get(key)
.get(&key)
.map(|mv| vec![mv.default_opid()])
.unwrap_or_else(Vec::new),
});
let (multivalue, new_ops, _new_cursors) = newvalue.finish();
let old = self.root.root_props.insert(key.to_string(), multivalue);
let old = self.root.root_props.insert(key, multivalue);
(old, LocalOperationResult { new_ops })
}
@ -325,7 +326,7 @@ impl<'a> ResolvedRootMut<'a> {
)
}
pub(crate) fn rollback_set(&mut self, key: String, value: Option<MultiValue>) {
pub(crate) fn rollback_set(&mut self, key: SmolStr, value: Option<MultiValue>) {
match value {
Some(old) => {
self.root.root_props.insert(key, old);
@ -336,7 +337,7 @@ impl<'a> ResolvedRootMut<'a> {
}
}
pub(crate) fn rollback_delete(&mut self, key: String, value: MultiValue) {
pub(crate) fn rollback_delete(&mut self, key: SmolStr, value: MultiValue) {
self.root.root_props.insert(key, value);
}
}
@ -393,7 +394,7 @@ pub struct ResolvedMapMut<'a> {
impl<'a> ResolvedMapMut<'a> {
pub(crate) fn set_key(
&mut self,
key: &str,
key: SmolStr,
payload: SetOrInsertPayload<Value>,
) -> (Option<MultiValue>, LocalOperationResult) {
let state_tree_map = match self.multivalue.default_statetree_value_mut() {
@ -404,13 +405,13 @@ impl<'a> ResolvedMapMut<'a> {
actor: payload.actor,
start_op: payload.start_op,
parent_obj: &state_tree_map.object_id,
key: key.into(),
key: amp::Key::Map(key.clone()),
value: payload.value,
insert: false,
pred: state_tree_map.pred_for_key(key),
pred: state_tree_map.pred_for_key(&key),
});
let (multivalue, new_ops, _new_cursors) = newvalue.finish();
let old = state_tree_map.props.insert(key.to_string(), multivalue);
let old = state_tree_map.props.insert(key, multivalue);
(old, LocalOperationResult { new_ops })
}
@ -437,7 +438,7 @@ impl<'a> ResolvedMapMut<'a> {
)
}
pub(crate) fn rollback_set(&mut self, key: String, value: Option<MultiValue>) {
pub(crate) fn rollback_set(&mut self, key: SmolStr, value: Option<MultiValue>) {
let state_tree_map = match self.multivalue.default_statetree_value_mut() {
StateTreeValue::Composite(StateTreeComposite::Map(map)) => map,
_ => unreachable!(),
@ -452,7 +453,7 @@ impl<'a> ResolvedMapMut<'a> {
}
}
pub(crate) fn rollback_delete(&mut self, key: String, value: MultiValue) {
pub(crate) fn rollback_delete(&mut self, key: SmolStr, value: MultiValue) {
let state_tree_map = match self.multivalue.default_statetree_value_mut() {
StateTreeValue::Composite(StateTreeComposite::Map(map)) => map,
_ => unreachable!(),
@ -474,7 +475,7 @@ pub struct ResolvedTableMut<'a> {
impl<'a> ResolvedTableMut<'a> {
pub(crate) fn set_key(
&mut self,
key: &str,
key: SmolStr,
payload: SetOrInsertPayload<Value>,
) -> (Option<MultiValue>, LocalOperationResult) {
let state_tree_table = match self.multivalue.default_statetree_value_mut() {
@ -485,13 +486,13 @@ impl<'a> ResolvedTableMut<'a> {
actor: payload.actor,
start_op: payload.start_op,
parent_obj: &state_tree_table.object_id,
key: key.into(),
key: amp::Key::Map(key.clone()),
value: payload.value,
insert: false,
pred: state_tree_table.pred_for_key(key),
pred: state_tree_table.pred_for_key(&key),
});
let (multivalue, new_ops, _new_cursors) = newvalue.finish();
let old = state_tree_table.props.insert(key.to_owned(), multivalue);
let old = state_tree_table.props.insert(key, multivalue);
(old, LocalOperationResult { new_ops })
}
@ -518,7 +519,7 @@ impl<'a> ResolvedTableMut<'a> {
)
}
pub(crate) fn rollback_set(&mut self, key: String, value: Option<MultiValue>) {
pub(crate) fn rollback_set(&mut self, key: SmolStr, value: Option<MultiValue>) {
let state_tree_map = match self.multivalue.default_statetree_value_mut() {
StateTreeValue::Composite(StateTreeComposite::Table(map)) => map,
_ => unreachable!(),
@ -533,7 +534,7 @@ impl<'a> ResolvedTableMut<'a> {
}
}
pub(crate) fn rollback_delete(&mut self, key: String, value: MultiValue) {
pub(crate) fn rollback_delete(&mut self, key: SmolStr, value: MultiValue) {
let state_tree_map = match self.multivalue.default_statetree_value_mut() {
StateTreeValue::Composite(StateTreeComposite::Table(map)) => map,
_ => unreachable!(),
@ -557,7 +558,7 @@ impl<'a> ResolvedTextMut<'a> {
pub(crate) fn insert(
&mut self,
index: u32,
payload: SetOrInsertPayload<String>,
payload: SetOrInsertPayload<SmolStr>,
) -> Result<LocalOperationResult, error::MissingIndexError> {
let state_tree_text = match self.multivalue.default_statetree_value_mut() {
StateTreeValue::Composite(StateTreeComposite::Text(text)) => text,
@ -590,7 +591,7 @@ impl<'a> ResolvedTextMut<'a> {
payload: SetOrInsertPayload<I>,
) -> Result<LocalOperationResult, error::MissingIndexError>
where
I: ExactSizeIterator<Item = String>,
I: ExactSizeIterator<Item = SmolStr>,
{
let state_tree_text = match self.multivalue.default_statetree_value_mut() {
StateTreeValue::Composite(StateTreeComposite::Text(text)) => text,
@ -607,7 +608,7 @@ impl<'a> ResolvedTextMut<'a> {
let mut chars: Vec<amp::ScalarValue> = Vec::with_capacity(payload.value.len());
for (i, c) in payload.value.enumerate() {
let insert_op = amp::OpId::new(payload.start_op + i as u64, payload.actor);
chars.push(amp::ScalarValue::Str(c.to_string()));
chars.push(amp::ScalarValue::Str(c.clone()));
let c = MultiGrapheme::new_from_grapheme_cluster(insert_op, c);
values.push(c)
}
@ -630,7 +631,7 @@ impl<'a> ResolvedTextMut<'a> {
pub(crate) fn set(
&mut self,
index: u32,
payload: SetOrInsertPayload<String>,
payload: SetOrInsertPayload<SmolStr>,
) -> Result<(MultiGrapheme, LocalOperationResult), error::MissingIndexError> {
let state_tree_text = match self.multivalue.default_statetree_value_mut() {
StateTreeValue::Composite(StateTreeComposite::Text(text)) => text,

View file

@ -1,10 +1,8 @@
use std::{
borrow::{Borrow, Cow},
collections::HashMap,
};
use std::{borrow::Cow, collections::HashMap};
use automerge_protocol as amp;
use serde::Serialize;
use smol_str::SmolStr;
use crate::path::PathElement;
@ -21,11 +19,11 @@ impl From<HashMap<amp::OpId, Value>> for Conflicts {
#[cfg_attr(feature = "derive-arbitrary", derive(arbitrary::Arbitrary))]
#[serde(untagged)]
pub enum Value {
Map(HashMap<String, Value>),
Table(HashMap<String, Value>),
Map(HashMap<SmolStr, Value>),
Table(HashMap<SmolStr, Value>),
Sequence(Vec<Value>),
/// Sequence of grapheme clusters
Text(Vec<String>),
Text(Vec<SmolStr>),
Primitive(Primitive),
}
@ -33,7 +31,7 @@ pub enum Value {
#[cfg_attr(feature = "derive-arbitrary", derive(arbitrary::Arbitrary))]
pub enum Primitive {
Bytes(Vec<u8>),
Str(String),
Str(SmolStr),
Int(i64),
Uint(u64),
F64(f64),
@ -95,7 +93,7 @@ impl From<Primitive> for Value {
impl From<&str> for Value {
fn from(s: &str) -> Self {
Value::Primitive(Primitive::Str(s.to_string()))
Value::Primitive(Primitive::Str(SmolStr::new(s)))
}
}
@ -111,7 +109,7 @@ impl From<&amp::CursorDiff> for Primitive {
impl From<char> for Value {
fn from(c: char) -> Value {
Value::Primitive(Primitive::Str(c.to_string()))
Value::Primitive(Primitive::Str(SmolStr::new(c.to_string())))
}
}
@ -133,12 +131,12 @@ impl From<i64> for Value {
impl<T, K> From<HashMap<K, T>> for Value
where
T: Into<Value>,
K: Borrow<str>,
K: AsRef<str>,
{
fn from(h: HashMap<K, T>) -> Self {
Value::Map(
h.into_iter()
.map(|(k, v)| (k.borrow().to_string(), v.into()))
.map(|(k, v)| (SmolStr::new(k), v.into()))
.collect(),
)
}
@ -154,16 +152,16 @@ impl Value {
pub fn from_json(json: &serde_json::Value) -> Value {
match json {
serde_json::Value::Object(kvs) => {
let result: HashMap<String, Value> = kvs
let result: HashMap<SmolStr, Value> = kvs
.iter()
.map(|(k, v)| (k.clone(), Value::from_json(v)))
.map(|(k, v)| (SmolStr::new(k), Value::from_json(v)))
.collect();
Value::Map(result)
}
serde_json::Value::Array(vs) => {
Value::Sequence(vs.iter().map(Value::from_json).collect())
}
serde_json::Value::String(s) => Value::Primitive(Primitive::Str(s.clone())),
serde_json::Value::String(s) => Value::Primitive(Primitive::Str(SmolStr::new(s))),
serde_json::Value::Number(n) => {
Value::Primitive(Primitive::F64(n.as_f64().unwrap_or(0.0)))
}
@ -175,13 +173,17 @@ impl Value {
pub fn to_json(&self) -> serde_json::Value {
match self {
Value::Map(map) => {
let result: serde_json::map::Map<String, serde_json::Value> =
map.iter().map(|(k, v)| (k.clone(), v.to_json())).collect();
let result: serde_json::map::Map<String, serde_json::Value> = map
.iter()
.map(|(k, v)| (k.to_string(), v.to_json()))
.collect();
serde_json::Value::Object(result)
}
Value::Table(map) => {
let result: serde_json::map::Map<String, serde_json::Value> =
map.iter().map(|(k, v)| (k.clone(), v.to_json())).collect();
let result: serde_json::map::Map<String, serde_json::Value> = map
.iter()
.map(|(k, v)| (k.to_string(), v.to_json()))
.collect();
serde_json::Value::Object(result)
}
Value::Sequence(elements) => {
@ -313,7 +315,7 @@ pub(crate) fn value_to_op_requests(
let mut op_num = start_op + 1;
for c in chars.iter() {
insert_ops.push(amp::Op {
action: amp::OpType::Set(amp::ScalarValue::Str(c.to_string())),
action: amp::OpType::Set(amp::ScalarValue::Str(c.clone())),
obj: amp::ObjectId::from(make_text_op.clone()),
key: last_elemid.clone().into(),
insert: true,
@ -400,15 +402,15 @@ mod tests {
#[test]
fn get_value() {
let v = Value::Map(hashmap! {
"hello".to_owned() => Value::Primitive(Primitive::Str("world".to_owned())),
"again".to_owned() => Value::Sequence(vec![Value::Primitive(Primitive::Int(2))])
"hello".into() => Value::Primitive(Primitive::Str("world".into())),
"again".into() => Value::Sequence(vec![Value::Primitive(Primitive::Int(2))])
});
assert_eq!(v.get_value(Path::root()), Some(Cow::Borrowed(&v)));
assert_eq!(
v.get_value(Path::root().key("hello")),
Some(Cow::Borrowed(&Value::Primitive(Primitive::Str(
"world".to_owned()
"world".into()
))))
);
assert_eq!(v.get_value(Path::root().index(0)), None);

View file

@ -600,7 +600,7 @@ fn apply_updates_inside_list_conflicts() {
assert_eq!(
frontend.state(),
&Into::<Value>::into(
hashmap! {"birds" => vec![hashmap!{"species" => Primitive::Str("lapwing".to_string()), "numSeen" => Primitive::Int(2)}]}
hashmap! {"birds" => vec![hashmap!{"species" => Primitive::Str("lapwing".into()), "numSeen" => Primitive::Int(2)}]}
)
);
@ -669,7 +669,7 @@ fn apply_updates_inside_list_conflicts() {
assert_eq!(
frontend.state(),
&Into::<Value>::into(
hashmap! {"birds" => vec![hashmap!{"species" => Primitive::Str("lapwing".to_string()), "numSeen" => Primitive::Int(2)}]}
hashmap! {"birds" => vec![hashmap!{"species" => Primitive::Str("lapwing".into()), "numSeen" => Primitive::Int(2)}]}
)
);
@ -726,7 +726,7 @@ fn apply_updates_inside_list_conflicts() {
assert_eq!(
frontend.state(),
&Into::<Value>::into(
hashmap! {"birds" => vec![hashmap!{"species" => Primitive::Str("lapwing".to_string()), "numSeen" => Primitive::Int(2)}]}
hashmap! {"birds" => vec![hashmap!{"species" => Primitive::Str("lapwing".into()), "numSeen" => Primitive::Int(2)}]}
)
);
@ -976,7 +976,7 @@ fn test_text_objects() {
assert_eq!(
frontend.state(),
&Into::<Value>::into(
hashmap! {"name" => Value::Text("ben".graphemes(true).map(|s|s.to_owned()).collect())}
hashmap! {"name" => Value::Text("ben".graphemes(true).map(|s|s.into()).collect())}
)
);
@ -999,7 +999,7 @@ fn test_text_objects() {
amp::DiffEdit::Update{
index: 1,
op_id: actor.op_id_at(5),
value: amp::Diff::Value(amp::ScalarValue::Str("i".to_string())),
value: amp::Diff::Value(amp::ScalarValue::Str("i".into())),
}
],
})
@ -1013,7 +1013,7 @@ fn test_text_objects() {
assert_eq!(
frontend.state(),
&Into::<Value>::into(
hashmap! {"name" => Value::Text("bi".graphemes(true).map(|s|s.to_owned()).collect())}
hashmap! {"name" => Value::Text("bi".graphemes(true).map(|s|s.into()).collect())}
)
);
}
@ -1030,7 +1030,7 @@ fn test_unchanged_diff_creates_empty_objects() {
pending_changes: 0,
diffs: RootDiff {
props: hashmap! {
"text".to_string() => hashmap!{
"text".into() => hashmap!{
"1@cfe5fefb771f4c15a716d488012cbf40".try_into().unwrap() => amp::Diff::Text(amp::TextDiff{
object_id: "1@cfe5fefb771f4c15a716d488012cbf40".try_into().unwrap(),
edits: Vec::new(),
@ -1042,6 +1042,6 @@ fn test_unchanged_diff_creates_empty_objects() {
doc.apply_patch(patch).unwrap();
assert_eq!(
doc.state(),
&Value::Map(hashmap! {"text".to_string() => Value::Text(Vec::new())},),
&Value::Map(hashmap! {"text".into() => Value::Text(Vec::new())},),
);
}

View file

@ -289,7 +289,7 @@ fn dont_allow_out_of_order_request_patches() {
deps: Vec::new(),
diffs: RootDiff {
props: hashmap! {
"partridges".to_string() => hashmap!{
"partridges".into() => hashmap!{
random_op_id() => amp::Diff::Value(amp::ScalarValue::Int(1))
}
},
@ -335,7 +335,7 @@ fn handle_concurrent_insertions_into_lists() {
deps: Vec::new(),
diffs: RootDiff {
props: hashmap! {
"birds".to_string() => hashmap!{
"birds".into() => hashmap!{
doc.actor_id.op_id_at(1) => amp::Diff::List(amp::ListDiff{
object_id: birds_id.clone(),
edits: vec![amp::DiffEdit::SingleElementInsert{
@ -435,7 +435,7 @@ fn handle_concurrent_insertions_into_lists() {
deps: Vec::new(),
diffs: RootDiff {
props: hashmap! {
"birds".to_string() => hashmap!{
"birds".into() => hashmap!{
doc.actor_id.op_id_at(1) => amp::Diff::List(amp::ListDiff{
object_id: birds_id,
edits: vec![

View file

@ -51,7 +51,7 @@ fn test_allow_cursor_on_text_element() {
.change::<_, _, InvalidChangeRequest>(None, |d| {
d.add_change(LocalChange::set(
Path::root().key("list"),
Value::Text("123".graphemes(true).map(|s| s.to_owned()).collect()),
Value::Text("123".graphemes(true).map(|s| s.into()).collect()),
))?;
let cursor = d
.cursor_to_path(&Path::root().key("list").index(1))
@ -90,7 +90,7 @@ fn test_do_not_allow_index_past_end_of_list() {
.change::<_, _, InvalidChangeRequest>(None, |d| {
d.add_change(LocalChange::set(
Path::root().key("list"),
Value::Text("123".graphemes(true).map(|s| s.to_owned()).collect()),
Value::Text("123".graphemes(true).map(|s| s.into()).collect()),
))?;
let cursor = d.cursor_to_path(&Path::root().key("list").index(10));
assert_eq!(cursor, None);
@ -106,7 +106,7 @@ fn test_do_not_allow_index_past_end_of_list() {
// .change::<_, _, InvalidChangeRequest>(None, |d| {
// d.add_change(LocalChange::set(
// Path::root().key("list"),
// Value::Text("123".graphemes(true).map(|s| s.to_owned()).collect()),
// Value::Text("123".graphemes(true).map(|s| s.into()).collect()),
// ))?;
// let cursor = d
// .cursor_to_path(&Path::root().key("list").index(1))
@ -121,7 +121,7 @@ fn test_do_not_allow_index_past_end_of_list() {
// d.add_change(LocalChange::insert(
// Path::root().key("list").index(0),
// Value::Primitive(Primitive::Str("0".to_string())),
// Value::Primitive(Primitive::Str("0".into())),
// ))?;
// let cursor_the_third = d.value_at_path(&Path::root().key("cursor"));
// if let Some(Value::Primitive(Primitive::Cursor(c))) = cursor_the_third {
@ -147,7 +147,7 @@ fn test_set_cursor_to_new_element_in_diff() {
pending_changes: 0,
diffs: RootDiff {
props: hashmap! {
"list".to_string() => hashmap!{
"list".into() => hashmap!{
actor.op_id_at(1) => amp::Diff::List(amp::ListDiff{
object_id: actor.op_id_at(1).into(),
edits: vec![
@ -166,7 +166,7 @@ fn test_set_cursor_to_new_element_in_diff() {
],
}),
},
"cursor".to_string() => hashmap!{
"cursor".into() => hashmap!{
actor.op_id_at(4) => amp::Diff::Cursor(amp::CursorDiff{
elem_id: actor.op_id_at(3),
index: 1,
@ -187,7 +187,7 @@ fn test_set_cursor_to_new_element_in_diff() {
pending_changes: 0,
diffs: RootDiff {
props: hashmap! {
"cursor".to_string() => hashmap!{
"cursor".into() => hashmap!{
actor.op_id_at(4) => amp::Diff::Cursor(amp::CursorDiff{
elem_id: actor.op_id_at(2),
index: 0,
@ -222,7 +222,7 @@ fn test_set_cursor_to_new_element_in_diff() {
// .change::<_, _, InvalidChangeRequest>(None, |d| {
// d.add_change(LocalChange::set(
// Path::root().key("list"),
// Value::Text("123".graphemes(true).map(|s| s.to_owned()).collect()),
// Value::Text("123".graphemes(true).map(|s| s.into()).collect()),
// ))?;
// let cursor = d
// .cursor_to_path(&Path::root().key("list").index(1))
@ -237,11 +237,11 @@ fn test_set_cursor_to_new_element_in_diff() {
// d.add_change(LocalChange::insert(
// Path::root().key("list").index(0),
// Value::Primitive(Primitive::Str("0".to_string())),
// Value::Primitive(Primitive::Str("0".into())),
// ))?;
// d.add_change(LocalChange::insert(
// Path::root().key("list").index(0),
// Value::Primitive(Primitive::Str("1".to_string())),
// Value::Primitive(Primitive::Str("1".into())),
// ))?;
// let cursor = d
// .cursor_to_path(&Path::root().key("list").index(2))
@ -268,7 +268,7 @@ fn test_delete_cursor_and_adding_again() {
.change::<_, _, InvalidChangeRequest>(None, |d| {
d.add_change(LocalChange::set(
Path::root().key("list"),
Value::Text("123".graphemes(true).map(|s| s.to_owned()).collect()),
Value::Text("123".graphemes(true).map(|s| s.into()).collect()),
))?;
let cursor = d
.cursor_to_path(&Path::root().key("list").index(1))

View file

@ -50,7 +50,7 @@ fn test_set_root_object_properties() {
.change::<_, _, InvalidChangeRequest>(Some("set root object".into()), |doc| {
doc.add_change(LocalChange::set(
Path::root().key("bird"),
Value::Primitive(Primitive::Str("magpie".to_string())),
Value::Primitive(Primitive::Str("magpie".into())),
))?;
Ok(())
})
@ -70,7 +70,7 @@ fn test_set_root_object_properties() {
hash: None,
deps: Vec::new(),
operations: vec![amp::Op {
action: amp::OpType::Set(amp::ScalarValue::Str("magpie".to_string())),
action: amp::OpType::Set(amp::ScalarValue::Str("magpie".into())),
obj: "_root".try_into().unwrap(),
key: "bird".into(),
insert: false,
@ -687,7 +687,7 @@ fn test_sets_characters_in_text() {
doc.change::<_, _, InvalidChangeRequest>(None, |doc| {
doc.add_change(LocalChange::set(
Path::root().key("text"),
Value::Text("some".graphemes(true).map(|s| s.to_owned()).collect()),
Value::Text("some".graphemes(true).map(|s| s.into()).collect()),
))?;
Ok(())
})
@ -727,7 +727,7 @@ fn test_sets_characters_in_text() {
let value = doc.get_value(&Path::root()).unwrap();
let expected_value: Value = Value::Map(hashmap! {
"text".into() => Value::Text(vec!["s".to_owned(), "a".to_owned(), "m".to_owned(), "e".to_owned()]),
"text".into() => Value::Text(vec!["s".into(), "a".into(), "m".into(), "e".into()]),
});
assert_eq!(value, expected_value);
}
@ -738,7 +738,7 @@ fn test_inserts_characters_in_text() {
doc.change::<_, _, InvalidChangeRequest>(None, |doc| {
doc.add_change(LocalChange::set(
Path::root().key("text"),
Value::Text("same".graphemes(true).map(|s| s.to_owned()).collect()),
Value::Text("same".graphemes(true).map(|s| s.into()).collect()),
))?;
Ok(())
})
@ -781,7 +781,7 @@ fn test_inserts_characters_in_text() {
let value = doc.get_value(&Path::root()).unwrap();
let expected_value: Value = Value::Map(hashmap! {
"text".into() => Value::Text(vec!["s".to_owned(), "h".to_owned(), "a".to_owned(), "m".to_owned(), "e".to_owned()]),
"text".into() => Value::Text(vec!["s".into(), "h".into(), "a".into(), "m".into(), "e".into()]),
});
assert_eq!(value, expected_value);
}
@ -835,7 +835,7 @@ fn test_inserts_characters_at_start_of_text() {
let value = doc.get_value(&Path::root()).unwrap();
let expected_value: Value = Value::Map(hashmap! {
"text".into() => Value::Text(vec!["i".to_owned()]),
"text".into() => Value::Text(vec!["i".into()]),
});
assert_eq!(value, expected_value);
}

View file

@ -14,6 +14,7 @@ uuid = { version = "^0.8.2", features=["v4"] }
thiserror = "1.0.16"
serde = { version = "^1.0", features=["derive"] }
arbitrary = { version = "1", features = ["derive"], optional = true }
smol_str = { version = "0.1.17", features = ["serde"] }
[dev-dependencies]
maplit = "^1.0.2"

View file

@ -4,6 +4,7 @@ mod utility_impls;
use std::{collections::HashMap, convert::TryFrom, fmt, num::NonZeroU32};
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;
#[derive(Eq, PartialEq, Hash, Clone, PartialOrd, Ord, Default)]
#[cfg_attr(feature = "derive-arbitrary", derive(arbitrary::Arbitrary))]
@ -169,7 +170,7 @@ impl ElementId {
#[derive(Serialize, PartialEq, Eq, Debug, Hash, Clone)]
#[serde(untagged)]
pub enum Key {
Map(String),
Map(SmolStr),
Seq(ElementId),
}
@ -231,7 +232,7 @@ impl DataType {
#[serde(untagged)]
pub enum ScalarValue {
Bytes(Vec<u8>),
Str(String),
Str(SmolStr),
Int(i64),
Uint(u64),
F64(f64),
@ -433,14 +434,14 @@ impl Diff {
#[serde(rename_all = "camelCase")]
pub struct MapDiff {
pub object_id: ObjectId,
pub props: HashMap<String, HashMap<OpId, Diff>>,
pub props: HashMap<SmolStr, HashMap<OpId, Diff>>,
}
#[derive(Deserialize, Debug, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct TableDiff {
pub object_id: ObjectId,
pub props: HashMap<String, HashMap<OpId, Diff>>,
pub props: HashMap<SmolStr, HashMap<OpId, Diff>>,
}
#[derive(Deserialize, Debug, PartialEq, Clone)]
@ -537,7 +538,7 @@ pub struct Patch {
/// A custom MapDiff that implicitly has the object_id Root and is a map object.
#[derive(Debug, PartialEq, Clone, Default)]
pub struct RootDiff {
pub props: HashMap<String, HashMap<OpId, Diff>>,
pub props: HashMap<SmolStr, HashMap<OpId, Diff>>,
}
#[derive(Deserialize, Serialize, Debug, Clone)]

View file

@ -6,6 +6,7 @@ use serde::{
ser::SerializeStruct,
Deserialize, Deserializer, Serialize, Serializer,
};
use smol_str::SmolStr;
use super::read_field;
use crate::{
@ -147,7 +148,7 @@ impl<'de> Deserialize<'de> for Diff {
let mut object_id: Option<ObjectId> = None;
let mut diff_type: Option<RawDiffType> = None;
//let mut obj_type: Option<ObjType> = None;
let mut props: Option<HashMap<String, HashMap<OpId, Diff>>> = None;
let mut props: Option<HashMap<SmolStr, HashMap<OpId, Diff>>> = None;
let mut value: Option<ScalarValue> = None;
let mut datatype: Option<DataType> = None;
let mut elem_id: Option<OpId> = None;
@ -304,7 +305,7 @@ mod tests {
let diff = Diff::Map(MapDiff {
object_id: ObjectId::from_str("1@6121f8757d5d46609b665218b2b3a141").unwrap(),
props: hashmap! {
"key".to_string() => hashmap!{
"key".into() => hashmap!{
OpId::from_str("1@4a093244de2b4fd0a4203724e15dfc16").unwrap() => "value".into()
}
},

View file

@ -1,6 +1,7 @@
use std::str::FromStr;
use serde::{Deserialize, Deserializer};
use smol_str::SmolStr;
use crate::{ElementId, Key};
@ -9,7 +10,7 @@ impl<'de> Deserialize<'de> for Key {
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let s = SmolStr::deserialize(deserializer)?;
if let Ok(eid) = ElementId::from_str(&s) {
Ok(Key::Seq(eid))
} else {

View file

@ -311,7 +311,7 @@ mod tests {
"pred": []
}),
expected: Ok(Op {
action: OpType::Set(ScalarValue::Str("somestring".to_string())),
action: OpType::Set(ScalarValue::Str("somestring".into())),
obj: ObjectId::Root,
key: "somekey".into(),
insert: false,

View file

@ -1,4 +1,5 @@
use serde::{de, Deserialize, Deserializer};
use smol_str::SmolStr;
use crate::ScalarValue;
@ -47,7 +48,7 @@ impl<'de> Deserialize<'de> for ScalarValue {
where
E: de::Error,
{
Ok(ScalarValue::Str(value.to_string()))
Ok(ScalarValue::Str(SmolStr::new(value)))
}
fn visit_none<E>(self) -> Result<ScalarValue, E>

View file

@ -1,5 +1,7 @@
use std::cmp::{Ordering, PartialOrd};
use smol_str::SmolStr;
use crate::{ElementId, Key, OpId};
impl PartialOrd for Key {
@ -42,6 +44,6 @@ where
S: AsRef<str>,
{
fn from(s: S) -> Self {
Key::Map(s.as_ref().to_string())
Key::Map(SmolStr::new(s))
}
}

View file

@ -1,5 +1,7 @@
use std::fmt;
use smol_str::SmolStr;
use crate::ScalarValue;
impl From<&str> for ScalarValue {
@ -34,7 +36,7 @@ impl From<bool> for ScalarValue {
impl From<char> for ScalarValue {
fn from(c: char) -> Self {
ScalarValue::Str(c.to_string())
ScalarValue::Str(SmolStr::new(c.to_string()))
}
}

View file

@ -14,7 +14,7 @@ fn arb_objtype() -> impl Strategy<Value = amp::ObjType> {
fn arb_scalar_value() -> impl Strategy<Value = amp::ScalarValue> {
prop_oneof![
any::<String>().prop_map(amp::ScalarValue::Str),
any::<String>().prop_map(|s| amp::ScalarValue::Str(s.into())),
any::<i64>().prop_map(amp::ScalarValue::Int),
//This is necessary because we don't support integers larger than i64 in the JSON protocol
//any::<i64>().prop_map(|i| amp::ScalarValue::Uint(i as u64)),
@ -61,7 +61,7 @@ fn arb_elemid() -> impl Strategy<Value = amp::ElementId> {
fn arb_key() -> impl Strategy<Value = amp::Key> {
prop_oneof![
any::<String>().prop_map(amp::Key::Map),
any::<String>().prop_map(|s| amp::Key::Map(s.into())),
arb_elemid().prop_map(amp::Key::Seq),
]
}

View file

@ -33,6 +33,7 @@ tracing = "0.1.25"
tracing-subscriber = {version = "0.2", features = ["chrono", "env-filter", "fmt"]}
unicode-segmentation = "1.7.1"
maplit = "^1.0.2"
smol_str = "0.1.17"
[[bench]]
name = "crdt_benchmarks"

View file

@ -3,6 +3,7 @@ use std::{collections::HashMap, convert::TryInto, default::Default};
use automerge::{Backend, Frontend, InvalidChangeRequest, LocalChange, Path, Primitive, Value};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use rand::{thread_rng, Rng};
use smol_str::SmolStr;
use unicode_segmentation::UnicodeSegmentation;
pub fn b1_1(c: &mut Criterion) {
@ -100,15 +101,12 @@ pub fn b1_2(c: &mut Criterion) {
.unwrap();
doc2.apply_patch(patch2).unwrap();
let random_string: String = thread_rng()
let random_string: SmolStr = thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(6000)
.map(char::from)
.collect();
let chars: Vec<_> = random_string
.graphemes(true)
.map(|s| s.to_owned())
.collect();
let chars: Vec<_> = random_string.graphemes(true).map(|s| s.into()).collect();
let text = Value::Text(chars);
(doc1, backend1, doc2, backend2, text)
},

View file

@ -8,7 +8,7 @@ fn small_change_backend() -> Backend {
.change::<_, _, InvalidChangeRequest>(None, |doc| {
doc.add_change(LocalChange::set(
Path::root().key("a"),
Value::Primitive(Primitive::Str("hello world".to_owned())),
Value::Primitive(Primitive::Str("hello world".into())),
))?;
Ok(())
})
@ -28,11 +28,11 @@ fn medium_change_backend() -> Backend {
Value::Map(
vec![
(
"\u{0}\u{0}".to_owned(),
"\u{0}\u{0}".into(),
Value::Sequence(vec![
Value::Primitive(Primitive::Str("".to_owned())),
Value::Primitive(Primitive::Str("".into())),
Value::Primitive(Primitive::Counter(0)),
Value::Primitive(Primitive::Str("".to_owned())),
Value::Primitive(Primitive::Str("".into())),
Value::Primitive(Primitive::Boolean(false)),
Value::Primitive(Primitive::Timestamp(0)),
Value::Primitive(Primitive::Int(0)),
@ -50,20 +50,20 @@ fn medium_change_backend() -> Backend {
]),
),
(
"\u{2}".to_owned(),
"\u{2}".into(),
Value::Sequence(vec![
Value::Primitive(Primitive::Null),
Value::Primitive(Primitive::Uint(0)),
Value::Primitive(Primitive::Str("".to_owned())),
Value::Primitive(Primitive::Str("".into())),
Value::Primitive(Primitive::Counter(0)),
Value::Primitive(Primitive::Str("".to_owned())),
Value::Primitive(Primitive::Str("".into())),
]),
),
(
"\u{0}".to_owned(),
"\u{0}".into(),
Value::Sequence(vec![
Value::Primitive(Primitive::Counter(0)),
Value::Primitive(Primitive::Str("".to_owned())),
Value::Primitive(Primitive::Str("".into())),
Value::Primitive(Primitive::Uint(0)),
Value::Primitive(Primitive::Timestamp(0)),
Value::Primitive(Primitive::Int(0)),
@ -76,7 +76,7 @@ fn medium_change_backend() -> Backend {
Value::Primitive(Primitive::Uint(0)),
Value::Primitive(Primitive::Null),
Value::Primitive(Primitive::Uint(0)),
Value::Primitive(Primitive::Str("".to_owned())),
Value::Primitive(Primitive::Str("".into())),
Value::Primitive(Primitive::Null),
Value::Primitive(Primitive::Timestamp(0)),
Value::Primitive(Primitive::Timestamp(0)),
@ -84,11 +84,11 @@ fn medium_change_backend() -> Backend {
Value::Primitive(Primitive::Counter(0)),
Value::Primitive(Primitive::Uint(0)),
Value::Primitive(Primitive::F32(0.0)),
Value::Primitive(Primitive::Str("".to_owned())),
Value::Primitive(Primitive::Str("".into())),
]),
),
(
"".to_owned(),
"".into(),
Value::Sequence(vec![
Value::Primitive(Primitive::Null),
Value::Primitive(Primitive::Uint(0)),
@ -99,7 +99,7 @@ fn medium_change_backend() -> Backend {
Value::Primitive(Primitive::Uint(0)),
Value::Primitive(Primitive::F64(0.0)),
Value::Primitive(Primitive::Timestamp(0)),
Value::Primitive(Primitive::Str("".to_owned())),
Value::Primitive(Primitive::Str("".into())),
Value::Primitive(Primitive::Boolean(false)),
Value::Primitive(Primitive::Counter(0)),
Value::Primitive(Primitive::Int(0)),
@ -112,9 +112,9 @@ fn medium_change_backend() -> Backend {
]),
),
(
"\u{1}".to_owned(),
"\u{1}".into(),
Value::Table(
vec![("".to_owned(), Value::Primitive(Primitive::F64(0.0)))]
vec![("".into(), Value::Primitive(Primitive::F64(0.0)))]
.into_iter()
.collect(),
),

View file

@ -13,7 +13,7 @@ use test_env_log::test;
fn test_frontend_uses_correct_elem_ids() {
let mut hm = HashMap::new();
hm.insert(
"a".to_owned(),
"a".into(),
automerge::Value::Sequence(vec![automerge::Value::Primitive(Primitive::Null)]),
);
let mut backend = automerge::Backend::new();
@ -44,7 +44,7 @@ fn test_frontend_uses_correct_elem_ids() {
let mut ehm = HashMap::new();
ehm.insert(
"a".to_owned(),
"a".into(),
automerge::Value::Sequence(vec![
automerge::Value::Primitive(automerge::Primitive::Int(0)),
automerge::Value::Primitive(automerge::Primitive::Boolean(false)),
@ -75,7 +75,7 @@ fn test_multi_insert_expands_to_correct_indices() {
Op {
action: OpType::Make(ObjType::List),
obj: ObjectId::Root,
key: Key::Map("a".to_owned()),
key: Key::Map("a".into()),
pred: vec![],
insert: false,
},
@ -112,7 +112,7 @@ fn test_multi_insert_expands_to_correct_indices() {
};
let val = Value::Map(hashmap! {
"a".to_owned() => Value::Sequence(
"a".into() => Value::Sequence(
vec![
Value::Sequence(
vec![],
@ -162,12 +162,12 @@ fn test_frontend_doesnt_wait_for_empty_changes() {
let vals = vec![
Value::Map(hashmap! {}),
Value::Map(hashmap! {
"0".to_owned() => Value::Map(
"0".into() => Value::Map(
hashmap! {},
),
"a".to_owned() => Value::Map(
"a".into() => Value::Map(
hashmap!{
"b".to_owned() => Value::Map(
"b".into() => Value::Map(
hashmap!{},
),
},
@ -182,7 +182,7 @@ fn test_frontend_doesnt_wait_for_empty_changes() {
LocalChange::set(Path::root().key("0"), Value::Map(HashMap::new())),
LocalChange::set(
Path::root().key("a"),
Value::Map(hashmap! {"b".to_owned() => Value::Map(HashMap::new() )}),
Value::Map(hashmap! {"b".into() => Value::Map(HashMap::new() )}),
),
],
vec![

View file

@ -17,11 +17,11 @@ fn missing_object_error_flaky_null_rle_decoding() {
Value::Map(
vec![
(
"\u{0}\u{0}".to_owned(),
"\u{0}\u{0}".into(),
Value::Sequence(vec![
Value::Primitive(Primitive::Str("".to_owned())),
Value::Primitive(Primitive::Str("".into())),
Value::Primitive(Primitive::Counter(0)),
Value::Primitive(Primitive::Str("".to_owned())),
Value::Primitive(Primitive::Str("".into())),
Value::Primitive(Primitive::Boolean(false)),
Value::Primitive(Primitive::Timestamp(0)),
Value::Primitive(Primitive::Int(0)),
@ -39,20 +39,20 @@ fn missing_object_error_flaky_null_rle_decoding() {
]),
),
(
"\u{2}".to_owned(),
"\u{2}".into(),
Value::Sequence(vec![
Value::Primitive(Primitive::Null),
Value::Primitive(Primitive::Uint(0)),
Value::Primitive(Primitive::Str("".to_owned())),
Value::Primitive(Primitive::Str("".into())),
Value::Primitive(Primitive::Counter(0)),
Value::Primitive(Primitive::Str("".to_owned())),
Value::Primitive(Primitive::Str("".into())),
]),
),
(
"\u{0}".to_owned(),
"\u{0}".into(),
Value::Sequence(vec![
Value::Primitive(Primitive::Counter(0)),
Value::Primitive(Primitive::Str("".to_owned())),
Value::Primitive(Primitive::Str("".into())),
Value::Primitive(Primitive::Uint(0)),
Value::Primitive(Primitive::Timestamp(0)),
Value::Primitive(Primitive::Int(0)),
@ -65,7 +65,7 @@ fn missing_object_error_flaky_null_rle_decoding() {
Value::Primitive(Primitive::Uint(0)),
Value::Primitive(Primitive::Null),
Value::Primitive(Primitive::Uint(0)),
Value::Primitive(Primitive::Str("".to_owned())),
Value::Primitive(Primitive::Str("".into())),
Value::Primitive(Primitive::Null),
Value::Primitive(Primitive::Timestamp(0)),
Value::Primitive(Primitive::Timestamp(0)),
@ -73,11 +73,11 @@ fn missing_object_error_flaky_null_rle_decoding() {
Value::Primitive(Primitive::Counter(0)),
Value::Primitive(Primitive::Uint(0)),
Value::Primitive(Primitive::F32(0.0)),
Value::Primitive(Primitive::Str("".to_owned())),
Value::Primitive(Primitive::Str("".into())),
]),
),
(
"".to_owned(),
"".into(),
Value::Sequence(vec![
Value::Primitive(Primitive::Null),
Value::Primitive(Primitive::Uint(0)),
@ -88,7 +88,7 @@ fn missing_object_error_flaky_null_rle_decoding() {
Value::Primitive(Primitive::Uint(0)),
Value::Primitive(Primitive::F64(0.0)),
Value::Primitive(Primitive::Timestamp(0)),
Value::Primitive(Primitive::Str("".to_owned())),
Value::Primitive(Primitive::Str("".into())),
Value::Primitive(Primitive::Boolean(false)),
Value::Primitive(Primitive::Counter(0)),
Value::Primitive(Primitive::Int(0)),
@ -101,9 +101,9 @@ fn missing_object_error_flaky_null_rle_decoding() {
]),
),
(
"\u{1}".to_owned(),
"\u{1}".into(),
Value::Table(
vec![("".to_owned(), Value::Primitive(Primitive::F64(0.0)))]
vec![("".into(), Value::Primitive(Primitive::F64(0.0)))]
.into_iter()
.collect(),
),
@ -199,7 +199,7 @@ fn missing_object_error_null_rle_decoding() {
Op {
action: OpType::Make(ObjType::List),
obj: ObjectId::Root,
key: Key::Map("b".to_owned()),
key: Key::Map("b".into()),
pred: vec![],
insert: false,
},
@ -311,7 +311,7 @@ fn missing_object_error_null_rle_decoding() {
Op {
action: OpType::Make(ObjType::List),
obj: ObjectId::Root,
key: Key::Map("\u{0}".to_owned()),
key: Key::Map("\u{0}".into()),
pred: vec![],
insert: false,
},
@ -661,14 +661,14 @@ fn missing_object_error_null_rle_decoding() {
Op {
action: OpType::Make(ObjType::Map),
obj: ObjectId::Root,
key: Key::Map("\u{1}".to_owned()),
key: Key::Map("\u{1}".into()),
pred: vec![],
insert: false,
},
Op {
action: OpType::Set(ScalarValue::Null),
obj: ObjectId::Id(OpId(67, actor_id.clone())),
key: Key::Map("a".to_owned()),
key: Key::Map("a".into()),
pred: vec![],
insert: false,
},
@ -678,7 +678,7 @@ fn missing_object_error_null_rle_decoding() {
seq: 1,
start_op: 1,
time: 0,
message: Some("".to_owned()),
message: Some("".into()),
deps: vec![],
extra_bytes: vec![],
};

View file

@ -1,11 +1,12 @@
use smol_str::SmolStr;
use unicode_segmentation::UnicodeSegmentation;
#[test]
fn create_frontend_with_grapheme_clusters() {
let mut hm = std::collections::HashMap::new();
hm.insert(
String::new(),
automerge::Value::Text("\u{80}".graphemes(true).map(|s| s.to_owned()).collect()),
SmolStr::default(),
automerge::Value::Text("\u{80}".graphemes(true).map(|s| s.into()).collect()),
);
let (mut f, c) =
automerge::Frontend::new_with_initial_state(automerge::Value::Map(hm)).unwrap();

View file

@ -14,3 +14,4 @@ maplit = "^1.0.2"
unicode-segmentation = "1.7.1"
serde = "1.0.126"
serde_json = "1.0.64"
smol_str = "0.1.17"

View file

@ -8,6 +8,7 @@ use automerge::{Backend, Frontend, InvalidChangeRequest, LocalChange, Path, Prim
use automerge_frontend::Value;
use maplit::hashmap;
use rand::Rng;
use smol_str::SmolStr;
fn f() {
let mut doc = Frontend::new();
@ -16,15 +17,15 @@ fn f() {
let start = Instant::now();
let m = hashmap! {
"a".to_owned() =>
"a".into() =>
Value::Map(hashmap!{
"b".to_owned()=>
"b".into()=>
Value::Map(
hashmap! {
"abc".to_owned() => Value::Primitive(Primitive::Str("hello world".to_owned()))
"abc".into() => Value::Primitive(Primitive::Str("hello world".into()))
},
),
"d".to_owned() => Value::Primitive(Primitive::Uint(20)),
"d".into() => Value::Primitive(Primitive::Uint(20)),
},)
};
@ -92,7 +93,7 @@ fn g() {
let iterations = 10_000;
for i in 0..iterations {
let random_string: String = rand::thread_rng()
let random_string: SmolStr = rand::thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(1)
.map(char::from)
@ -284,7 +285,7 @@ fn trace(edits: Vec<(u32, u32, Option<String>)>) {
if let Some(c) = edit.2.clone() {
d.add_change(LocalChange::insert(
Path::root().key("text").index(edit.0),
Value::Primitive(Primitive::Str(c)),
Value::Primitive(Primitive::Str(SmolStr::new(c))),
))?;
}
}