Skip to content

Commit

Permalink
Merge pull request #4 from nomad/anchor-conversion
Browse files Browse the repository at this point in the history
Add `Replica::create_anchor()` and `Replica::resolve_anchor()`
  • Loading branch information
noib3 authored Jan 9, 2024
2 parents d9a5481 + 604600f commit 9c2edce
Show file tree
Hide file tree
Showing 8 changed files with 632 additions and 159 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## [Unreleased]

### Added

- a new `Replica::create_anchor()` method to create an `Anchor` from an offset
and an `AnchorBias`;

- a new `Replica::resolve_anchor()` method to resolve an `Anchor` into an
offset;

## [0.2.1] - Jan 2 2024

### Added
Expand Down
146 changes: 146 additions & 0 deletions src/anchor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use crate::*;

/// A stable reference to a position in a [`Replica`].
///
/// After its creation, an `Anchor` can be given to a `Replica` to
/// retrieve the current offset of the position it refers to, taking into
/// account all the edits that have been applied to the `Replica` in the
/// meantime.
///
/// This property makes `Anchor`s useful to implement things like cursors and
/// selections in collaborative editing environments.
//
/// For more information, see the documentation of
/// [`Replica::create_anchor()`][crate::Replica::create_anchor] and
/// [`Replica::resolve_anchor()`][crate::Replica::resolve_anchor].
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(
any(feature = "encode", feature = "serde"),
derive(serde::Serialize, serde::Deserialize)
)]
pub struct Anchor {
/// TODO: docs
inner: InnerAnchor,

bias: AnchorBias,
}

impl Anchor {
#[inline(always)]
pub(crate) fn bias(&self) -> AnchorBias {
self.bias
}

#[inline(always)]
pub(crate) fn end_of_document() -> Self {
Self::new(InnerAnchor::zero(), AnchorBias::Right)
}

#[inline(always)]
pub(crate) fn inner(&self) -> InnerAnchor {
self.inner
}

#[inline(always)]
pub(crate) fn is_end_of_document(&self) -> bool {
self.inner.is_zero() && self.bias == AnchorBias::Right
}

#[inline(always)]
pub(crate) fn is_start_of_document(&self) -> bool {
self.inner.is_zero() && self.bias == AnchorBias::Left
}

#[inline(always)]
pub(crate) fn new(inner: InnerAnchor, bias: AnchorBias) -> Self {
Self { inner, bias }
}

#[inline(always)]
pub(crate) fn start_of_document() -> Self {
Self::new(InnerAnchor::zero(), AnchorBias::Left)
}
}

/// TODO: docs
#[derive(Copy, Clone, PartialEq, Eq)]
#[cfg_attr(
any(feature = "encode", feature = "serde"),
derive(serde::Serialize, serde::Deserialize)
)]
pub(crate) struct InnerAnchor {
/// TODO: docs
replica_id: ReplicaId,

/// The [`RunTs`] of the [`EditRun`] containing this [`Anchor`].
contained_in: RunTs,

/// TODO: docs
offset: Length,
}

impl core::fmt::Debug for InnerAnchor {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if self == &Self::zero() {
write!(f, "zero")
} else {
write!(f, "{:x}.{}", self.replica_id, self.offset)
}
}
}

impl InnerAnchor {
#[inline(always)]
pub(crate) fn is_zero(&self) -> bool {
self.replica_id == 0
}

#[inline(always)]
pub(crate) fn new(
replica_id: ReplicaId,
offset: Length,
run_ts: RunTs,
) -> Self {
Self { replica_id, offset, contained_in: run_ts }
}

#[inline(always)]
pub(crate) fn offset(&self) -> Length {
self.offset
}

#[inline(always)]
pub(crate) fn replica_id(&self) -> ReplicaId {
self.replica_id
}

#[inline(always)]
pub(crate) fn run_ts(&self) -> RunTs {
self.contained_in
}

/// A special value used to create an anchor at the start of the document.
#[inline]
pub const fn zero() -> Self {
Self { replica_id: 0, offset: 0, contained_in: 0 }
}
}

/// A bias to use when creating an [`Anchor`].
///
/// This is used in the
/// [`Replica::create_anchor()`][crate::Replica::create_anchor] method to
/// create a new [`Anchor`]. See the documentation of that method for more
/// information.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(
any(feature = "encode", feature = "serde"),
derive(serde::Serialize, serde::Deserialize)
)]
pub enum AnchorBias {
/// The anchor should attach to the left.
Left,

/// The anchor should attach to the right.
Right,
}
45 changes: 6 additions & 39 deletions src/crdt_edit.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::anchor::InnerAnchor as Anchor;
use crate::*;

/// An insertion in CRDT coordinates.
Expand All @@ -17,9 +18,6 @@ pub struct Insertion {
/// The anchor point of the insertion.
anchor: Anchor,

/// The run timestamp of the [`EditRun`] containing the anchor.
anchor_ts: RunTs,

/// Contains the replica that made the insertion and the temporal range
/// of the text that was inserted.
text: Text,
Expand All @@ -37,11 +35,6 @@ impl Insertion {
self.anchor
}

#[inline(always)]
pub(crate) fn anchor_ts(&self) -> RunTs {
self.anchor_ts
}

#[inline(always)]
pub(crate) fn end(&self) -> Length {
self.text.range.end
Expand Down Expand Up @@ -75,17 +68,16 @@ impl Insertion {
#[inline]
pub(crate) fn new(
anchor: Anchor,
anchor_ts: RunTs,
text: Text,
lamport_ts: LamportTs,
run_ts: RunTs,
) -> Self {
Self { anchor, anchor_ts, text, lamport_ts, run_ts }
Self { anchor, text, lamport_ts, run_ts }
}

#[inline]
pub(crate) fn no_op() -> Self {
Self::new(Anchor::zero(), 0, Text::new(0, 0..0), 0, 0)
Self::new(Anchor::zero(), Text::new(0, 0..0), 0, 0)
}

#[inline]
Expand Down Expand Up @@ -117,15 +109,9 @@ pub struct Deletion {
/// The anchor point of the start of the deleted range.
start: Anchor,

/// The run timestamp of the [`EditRun`] containing the start `Anchor`.
start_ts: RunTs,

/// The anchor point of the end of the deleted range.
end: Anchor,

/// The run timestamp of the [`EditRun`] containing the end `Anchor`.
end_ts: RunTs,

/// The version map of the replica at the time of the deletion. This is
/// used by a `Replica` merging this deletion to determine:
///
Expand All @@ -137,7 +123,7 @@ pub struct Deletion {
/// of the deletion.
version_map: VersionMap,

/// The deletion timestamp of this insertion.
/// The timestamp of this deletion.
deletion_ts: DeletionTs,
}

Expand All @@ -157,11 +143,6 @@ impl Deletion {
self.end
}

#[inline(always)]
pub(crate) fn end_ts(&self) -> RunTs {
self.end_ts
}

#[inline]
pub(crate) fn is_no_op(&self) -> bool {
self.end.is_zero()
Expand All @@ -170,37 +151,23 @@ impl Deletion {
#[inline]
pub(crate) fn new(
start: Anchor,
start_ts: RunTs,
end: Anchor,
end_ts: RunTs,
version_map: VersionMap,
deletion_ts: DeletionTs,
) -> Self {
Deletion { start, start_ts, end, end_ts, version_map, deletion_ts }
Deletion { start, end, version_map, deletion_ts }
}

#[inline]
pub(crate) fn no_op() -> Self {
Self::new(
Anchor::zero(),
0,
Anchor::zero(),
0,
VersionMap::new(0, 0),
0,
)
Self::new(Anchor::zero(), Anchor::zero(), VersionMap::new(0, 0), 0)
}

#[inline(always)]
pub(crate) fn start(&self) -> Anchor {
self.start
}

#[inline(always)]
pub(crate) fn start_ts(&self) -> RunTs {
self.start_ts
}

#[inline(always)]
pub(crate) fn version_map(&self) -> &VersionMap {
&self.version_map
Expand Down
7 changes: 5 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@

extern crate alloc;

mod anchor;
mod backlog;
mod crdt_edit;
mod gtree;
Expand All @@ -137,6 +138,8 @@ mod text_edit;
mod utils;
mod version_map;

use anchor::*;
pub use anchor::{Anchor, AnchorBias};
use backlog::Backlog;
pub use backlog::{BackloggedDeletions, BackloggedInsertions};
pub use crdt_edit::{Deletion, Insertion};
Expand All @@ -145,7 +148,7 @@ pub use replica::Replica;
use replica::*;
pub use replica_id::ReplicaId;
use replica_id::{ReplicaIdMap, ReplicaIdMapValuesMut};
use run_indices::{AnchorBias, RunIndices};
use run_indices::RunIndices;
use run_tree::*;
pub use text_edit::Text;
use utils::*;
Expand Down Expand Up @@ -267,4 +270,4 @@ pub type Length = usize;
///
/// See [`ProtocolVersion`] for more infos.
#[cfg(feature = "encode")]
const PROTOCOL_VERSION: ProtocolVersion = 0;
const PROTOCOL_VERSION: ProtocolVersion = 1;
Loading

0 comments on commit 9c2edce

Please sign in to comment.