jmbl 0.5.0

A high performance CRDT
Documentation
use crate::{
    io::SrcID,
    mem_use::{MemUsage, MemUser},
};
use serde::Deserialize;
use serde_derive::{Deserialize, Serialize};
use std::hash::Hash;
use thiserror::Error;
use ti64::MsSinceEpoch;

#[derive(Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct OpID {
    pub src_id: SrcID,
    pub sequence_idx: u32,
}

impl MemUser for OpID {
    fn mem_use(&self) -> MemUsage {
        MemUsage::Struct {
            name: "OpID",
            fields: vec![
                ("src_id", self.src_id.mem_use()),
                ("sequence_idx", self.sequence_idx.mem_use()),
            ],
        }
    }
}

impl std::fmt::Display for OpID {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}:{}", self.src_id, self.sequence_idx)
    }
}

impl std::fmt::Debug for OpID {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}:{}", self.src_id, self.sequence_idx)
    }
}

impl std::str::FromStr for OpID {
    type Err = OpIDParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // TODO: we know exactly where the split should be
        let mut parts = s.split(':');
        let src_id = parts.next().ok_or(OpIDParseError::MissingLogID)?.parse()?;
        let sequence_idx = parts
            .next()
            .ok_or(OpIDParseError::MissingSequenceIdx)?
            .parse()?;
        Ok(OpID {
            src_id: src_id,
            sequence_idx,
        })
    }
}

#[derive(Error, Debug)]
pub enum OpIDParseError {
    #[error("Missing LogID")]
    MissingLogID,
    #[error("Missing sequence index")]
    MissingSequenceIdx,
    #[error("Invalid LogID in OpID {0}")]
    LogIDParseError(#[from] litl::TaggedDataError),
    #[error("Invalid sequence index in OpID {0}")]
    SequenceIdxParseError(#[from] std::num::ParseIntError),
}

#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
pub struct ObjID {
    pub init_op: OpID,
}

impl ObjID {
    pub fn from_first_op_id(first_op_id: OpID) -> ObjID {
        ObjID {
            init_op: first_op_id,
        }
    }
}

impl MemUser for ObjID {
    fn mem_use(&self) -> MemUsage {
        self.init_op.mem_use()
    }
}

#[allow(clippy::derive_hash_xor_eq)]
#[derive(Clone, Debug, Hash)]
pub struct Op {
    pub id: OpID,
    pub time: MsSinceEpoch,
    pub prev: OpID,
    pub kind: OpKind,
    pub val: Option<litl::Val>,
}

impl PartialEq for Op {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

impl Eq for Op {}

impl Op {
    pub fn new(id: OpID, prev: OpID, kind: OpKind, val: Option<litl::Val>) -> Op {
        Op {
            id,
            prev,
            time: ti64::now(),
            kind,
            val,
        }
    }

    pub fn new_initial(id: OpID, kind: OpKind, val: Option<litl::Val>) -> Op {
        Self::new(id, id, kind, val)
    }
}

impl MemUser for Op {
    fn mem_use(&self) -> MemUsage {
        MemUsage::Struct {
            name: "Op",
            fields: vec![
                ("id", self.id.mem_use()),
                ("time", self.time.0.mem_use()),
                ("prev", self.prev.mem_use()),
                ("kind", self.kind.mem_use()),
            ],
        }
    }
}

#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
#[repr(u8)]
#[serde(rename_all = "camelCase")]
pub enum OpKind {
    ListCreate,
    MapCreate,
    TextCreate,
    SetRoot,
    Undo,
    Redo,
    ListInsertAfter,
    MapSet,
}

impl MemUser for OpKind {
    fn mem_use(&self) -> MemUsage {
        MemUsage::Enum {
            name: "OpKind",
            discriminant: std::mem::size_of::<u8>(),
            min_size: std::mem::size_of::<OpKind>(),
            variant: Box::new(match self {
                OpKind::ListCreate => ("ListCreate", MemUsage::Simple(0)),
                OpKind::MapCreate => ("MapCreate", MemUsage::Simple(0)),
                OpKind::TextCreate => ("MapCreate", MemUsage::Simple(0)),
                OpKind::SetRoot => ("MapCreate", MemUsage::Simple(0)),
                OpKind::Undo => ("Undo", MemUsage::Simple(0)),
                OpKind::Redo => ("Redo", MemUsage::Simple(0)),
                OpKind::ListInsertAfter => ("ListInsertAfter", MemUsage::Simple(0)),
                OpKind::MapSet => ("MapSet", MemUsage::Simple(0)),
            }),
        }
    }
}

#[derive(Clone, Hash, Debug, PartialEq, Eq)]
pub struct OpWithTarget {
    pub target_obj_id: ObjID,
    pub op: Op,
}

impl OpWithTarget {
    pub fn dependencies(&self) -> impl Iterator<Item = OpID> {
        let depends_on_target = self.target_obj_id.init_op != self.op.id;

        if depends_on_target {
            Some(self.target_obj_id.init_op)
        } else {
            None
        }
        .into_iter()
        .chain(if self.op.prev != self.op.id {
            Some(self.op.prev)
        } else {
            None
        })
        .chain(match self.op.kind {
            OpKind::ListInsertAfter | OpKind::MapSet | OpKind::SetRoot => {
                match self.op.val.as_ref().map(ObjID::deserialize) {
                    Some(Ok(ObjID {
                        init_op: first_op_id,
                    })) => Some(first_op_id),
                    Some(Err(_)) | None => None,
                }
            }
            _ => None,
        })
    }
}