diff --git a/CRDT.cs b/CRDT.cs index 98926d3..061c3f1 100644 --- a/CRDT.cs +++ b/CRDT.cs @@ -4,6 +4,8 @@ #nullable enable +using CrdtNodeId = ulong; + // Namespace for CRDT implementation namespace ForSync.CRDT { /// @@ -52,10 +54,10 @@ public void SetTime(ulong t) { public struct ColumnVersion { public ulong ColVersion { get; set; } public ulong DbVersion { get; set; } - public ulong NodeId { get; set; } + public CrdtNodeId NodeId { get; set; } public ulong LocalDbVersion { get; set; } - public ColumnVersion(ulong c, ulong d, ulong n, ulong ldbVer = 0) { + public ColumnVersion(ulong c, ulong d, CrdtNodeId n, ulong ldbVer = 0) { ColVersion = c; DbVersion = d; NodeId = n; @@ -151,14 +153,15 @@ public class Change { public V? Value { get; set; } // null represents deletion of the column, not the record public ulong ColVersion { get; set; } public ulong DbVersion { get; set; } - public ulong NodeId { get; set; } + public CrdtNodeId NodeId { get; set; } public ulong LocalDbVersion { get; set; } public Change() { RecordId = default!; + Value = default!; } - public Change(K recordId, ColName? colName, V? value, ulong colVersion, ulong dbVersion, ulong nodeId, ulong localDbVersion = 0) { + public Change(K recordId, ColName? colName, V? value, ulong colVersion, ulong dbVersion, CrdtNodeId nodeId, ulong localDbVersion = 0) { RecordId = recordId; ColName = colName; Value = value; @@ -200,7 +203,7 @@ public int Compare(Change? a, Change? b) { return a.DbVersion > b.DbVersion ? -1 : 1; if (a.NodeId != b.NodeId) - return a.NodeId > b.NodeId ? -1 : 1; + return a.NodeId.CompareTo(b.NodeId); return 0; // Consider equal if all fields match } @@ -210,7 +213,7 @@ public int Compare(Change? a, Change? b) { /// Represents the CRDT structure, generic over key (K) and value (V) types. /// public class CRDT { - private ulong _nodeId; + private CrdtNodeId _nodeId; private LogicalClock _clock; private Dictionary> _data; private HashSet _tombstones; @@ -218,11 +221,11 @@ public class CRDT { private CRDT? _parent; private ulong _baseVersion; - public ulong NodeId => _nodeId; + public CrdtNodeId NodeId => _nodeId; public ulong Clock => _clock.CurrentTime; public ulong BaseVersion => _baseVersion; - public CRDT(ulong nodeId, CRDT? parent = null) { + public CRDT(CrdtNodeId nodeId, CRDT? parent = null) { _nodeId = nodeId; _clock = new LogicalClock(); _data = new Dictionary>(); @@ -245,7 +248,7 @@ public CRDT(ulong nodeId, CRDT? parent = null) { /// /// Creates a CRDT from a list of changes (e.g., loaded from disk). /// - public CRDT(ulong nodeId, List> changes) { + public CRDT(CrdtNodeId nodeId, List> changes) { _nodeId = nodeId; _clock = new LogicalClock(); _data = new Dictionary>(); @@ -278,7 +281,7 @@ public List> InvertChanges(List> changes, bool usePare if (colName == null) { // The change was a record deletion (tombstone) - Record? recordPtr = useParent ? GetRecordPtr(recordId, true) : GetRecordPtr(recordId); + Record? recordPtr = useParent ? GetRecord(recordId, true) : GetRecord(recordId); if (recordPtr != null) { // Restore all fields from the record @@ -304,7 +307,7 @@ public List> InvertChanges(List> changes, bool usePare } else { // The change was an insertion or update of a column ColName col = colName.Value; - Record? recordPtr = useParent ? GetRecordPtr(recordId, true) : GetRecordPtr(recordId); + Record? recordPtr = useParent ? GetRecord(recordId, true) : GetRecord(recordId); if (recordPtr != null) { if (recordPtr.Fields.TryGetValue(col, out V existingValue)) { @@ -497,14 +500,14 @@ public List> MergeChanges(List> changes, bool ignorePa ColName? colName = change.ColName; ulong remoteColVersion = change.ColVersion; ulong remoteDbVersion = change.DbVersion; - ulong remoteNodeId = change.NodeId; + CrdtNodeId remoteNodeId = change.NodeId; V? remoteValue = change.Value; // Update the logical clock ulong newLocalDbVersion = _clock.Update(remoteDbVersion); // Retrieve local column version information - Record? recordPtr = GetRecordPtr(recordId, ignoreParent); + Record? recordPtr = GetRecord(recordId, ignoreParent); ColumnVersion? localColInfo = null; if (recordPtr != null) { @@ -535,7 +538,7 @@ public List> MergeChanges(List> changes, bool ignorePa shouldAccept = false; } else { // db_version is equal; use node_id for final tiebreaking - shouldAccept = remoteNodeId > localColInfo.Value.NodeId; + shouldAccept = remoteNodeId.CompareTo(localColInfo.Value.NodeId) > 0; } } } @@ -704,7 +707,7 @@ private void ApplyChanges(List> changes) { ColName? colName = change.ColName; ulong remoteColVersion = change.ColVersion; ulong remoteDbVersion = change.DbVersion; - ulong remoteNodeId = change.NodeId; + CrdtNodeId remoteNodeId = change.NodeId; ulong remoteLocalDbVersion = change.LocalDbVersion; V? remoteValue = change.Value; @@ -757,12 +760,12 @@ private bool IsRecordTombstoned(K recordId, bool ignoreParent = false) { /// /// Retrieves a record pointer. /// - private Record? GetRecordPtr(K recordId, bool ignoreParent = false) { + public Record? GetRecord(K recordId, bool ignoreParent = false) { if (_data.TryGetValue(recordId, out Record? record)) return record; if (_parent != null && !ignoreParent) - return _parent.GetRecordPtr(recordId); + return _parent.GetRecord(recordId); return null; } @@ -775,7 +778,7 @@ private Record GetOrCreateRecordUnchecked(K recordId, bool ignoreParent = _data[recordId] = new Record(); if (_parent != null && !ignoreParent) { - Record? parentRecord = _parent.GetRecordPtr(recordId); + Record? parentRecord = _parent.GetRecord(recordId); if (parentRecord != null) _data[recordId] = new Record(new Dictionary, V>(parentRecord.Fields), new Dictionary, ColumnVersion>(parentRecord.ColumnVersions));