cojson 0.6.0

Collaborative JSON (A high performance CRDT)
Documentation
use std::{cell::RefCell, ops::Deref};

use crate::{
    mem_use::{MemUsage, MemUser},
    object::ObjectState,
    ops::{Op, OpWithTarget},
    sparse_vector::SparseOpIDSet,
    text::text_state::TextState,
    view::jmbl_view::{JMBLView, JMBLViewRef},
    Value,
};

use super::{
    list::list_state::ListState,
    map::map_state::MapState,
    object::AnyObjectState,
    ops::{ObjID, OpKind},
};

#[derive(Clone)]
pub struct ReadableJMBL {
    pub(crate) state: JMBLState,
    read_view: RefCell<Option<JMBLViewRef>>,
}

impl ReadableJMBL {
    pub fn new() -> ReadableJMBL {
        ReadableJMBL {
            state: JMBLState::new(),
            read_view: RefCell::new(None),
        }
    }

    pub(crate) fn new_from(inner: JMBLState) -> ReadableJMBL {
        ReadableJMBL {
            state: inner,
            read_view: RefCell::new(None),
        }
    }

    pub fn get_object(&self, obj_id: &ObjID) -> Value {
        self.read_view
            .borrow_mut()
            .get_or_insert_with(|| JMBLViewRef::new(JMBLView::new(None, self.state.clone())))
            .obj_id_to_value(obj_id)
    }

    pub fn get_root(&self) -> Value {
        self.root
            .as_ref()
            .map(|(root, _)| self.get_object(root))
            .unwrap_or_else(|| Value::plain(litl::Val::null()))
    }

    pub fn with_ops_applied<'a, I: IntoIterator<Item = &'a OpWithTarget>>(&self, ops: I) -> Self {
        ReadableJMBL::new_from(self.state.with_ops_applied(ops))
    }
}

impl Deref for ReadableJMBL {
    type Target = JMBLState;

    fn deref(&self) -> &Self::Target {
        &self.state
    }
}

impl std::fmt::Debug for ReadableJMBL {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.state.fmt(f)
    }
}

impl Default for ReadableJMBL {
    fn default() -> Self {
        ReadableJMBL::new()
    }
}

#[derive(Clone, Debug)]
pub struct JMBLState {
    pub(crate) objects: im::HashMap<ObjID, AnyObjectState>,
    seen_ops: SparseOpIDSet,
    pub(crate) root: Option<(ObjID, Op)>,
    disconnected: Vec<OpWithTarget>,
}

impl JMBLState {
    pub fn new() -> Self {
        JMBLState {
            objects: Default::default(),
            seen_ops: Default::default(),
            root: None,
            disconnected: vec![],
        }
    }

    pub fn with_ops_applied<'a, I: IntoIterator<Item = &'a OpWithTarget>>(&self, ops: I) -> Self {
        let mut curr = self.clone();

        let mut made_progress_with_disconnected = true;

        // TODO(optimization): avoid clone in happy case
        let mut disconnected = ops.into_iter().cloned().collect::<Vec<_>>();
        disconnected.extend(std::mem::take(&mut curr.disconnected).into_iter());

        while made_progress_with_disconnected {
            made_progress_with_disconnected = false;

            disconnected.retain(|op_with_target| {
                if curr.seen_ops.contains(&op_with_target.op.id) {
                    made_progress_with_disconnected = true;
                    return false;
                };

                if op_with_target
                    .dependencies()
                    .all(|dep| curr.seen_ops.contains(&dep))
                {
                    made_progress_with_disconnected = true;

                    curr.mutably_insert_op(op_with_target);

                    false
                } else {
                    // still disconnected
                    true
                }
            })
        }

        curr.disconnected = disconnected;

        curr
    }

    fn mutably_insert_op(&mut self, op_with_target: &OpWithTarget) {
        let OpWithTarget { target_obj_id, op } = op_with_target;
        match op.kind {
            OpKind::SetRoot => {
                let root_id = litl::from_val(op.val.clone().expect("Expected root object id"))
                    .expect("Expected root value to be ObjID");
                self.root = Some((root_id, op.clone()));
            }
            OpKind::ListCreate | OpKind::TextCreate | OpKind::MapCreate => {
                let mut object_state = match op.kind {
                    OpKind::ListCreate => AnyObjectState::List(ListState::new()),
                    OpKind::TextCreate => AnyObjectState::Text(TextState::new()),
                    OpKind::MapCreate => AnyObjectState::Map(MapState::new()),
                    _ => unreachable!("Has to be create op here"),
                };

                object_state = object_state.apply_ops(Some(op.clone()));

                self.objects.insert(*target_obj_id, object_state);
            }
            _ => {
                let target = self
                    .objects
                    .get_mut(target_obj_id)
                    .expect("Should have object");
                assert!(self.seen_ops.contains(&op.prev));
                *target = target.apply_ops(Some(op.clone()));
            }
        }

        self.seen_ops.insert(op.id);
    }

    pub fn ops_since<'a>(&'a self, other: &'a Self) -> impl Iterator<Item = OpWithTarget> + 'a {
        self.objects.iter().flat_map(move |(obj_id, obj_state)| {
            let ops_since = obj_state.ops_since(other.objects.get(obj_id));

            ops_since
                .into_iter()
                .map(|op| OpWithTarget {
                    target_obj_id: *obj_id,
                    op,
                })
                .collect::<Vec<_>>()
        })
    }
}

impl Default for JMBLState {
    fn default() -> Self {
        JMBLState::new()
    }
}

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