Beispiel #1
0
        private void ReceiveOperation(OtOperation operation)
        {
            var remoteOrigin  = operation.Origin;
            var transformedOp = operation;

            switch (operation.Kind)
            {
            case OtOperationKind.Normal:
            {
                var newDiff = new List <OtOperation>();
                foreach (var localChange in myDiff)
                {
                    var result = OtFramework.Transform(localChange, transformedOp);
                    newDiff.Add(result.NewLocalDiff);
                    transformedOp = result.LocalizedApplyToDocument;
                }

                myDiff = newDiff;

                break;
            }

            case OtOperationKind.Reset:
            {
                myDiff = new List <OtOperation>();
                break;
            }

            default:
                throw new ArgumentOutOfRangeException();
            }

            BufferVersion = remoteOrigin == RdChangeOrigin.Master
        ? BufferVersion.IncrementMaster()
        : BufferVersion.IncrementSlave();

            var timestamp = operation.Timestamp;

            Assertion.Assert(remoteOrigin == RdChangeOrigin.Master
        ? BufferVersion.Master == timestamp
        : BufferVersion.Slave == timestamp, "operation.Timestamp == BufferVersion");

            var changes = transformedOp.ToRdTextChanges();

            for (var i = 0; i < changes.Count; i++)
            {
                using (new ComplexChangeCookie(this, i < changes.Count - 1))
                {
                    var change = changes[i];
                    myTextChanged.SetValue(change);
                }
            }

            if (operation.Kind == OtOperationKind.Normal)
            {
                SendAck(timestamp);
            }
        }
Beispiel #2
0
        public static OtOperation ToOperation(this RdTextChange textChange, RdChangeOrigin origin, int ts)
        {
            var changes = new List <OtChange>();

            changes.Add(new Retain(textChange.StartOffset));
            switch (textChange.Kind)
            {
            case RdTextChangeKind.Insert:
                changes.Add(new InsertText(textChange.New));
                break;

            case RdTextChangeKind.Remove:
                changes.Add(new DeleteText(textChange.Old));
                break;

            case RdTextChangeKind.Replace:
            {
                changes.Add(new InsertText(textChange.New));
                changes.Add(new DeleteText(textChange.Old));
                break;
            }

            case RdTextChangeKind.Reset:
                changes.Add(new InsertText(textChange.New));
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            var currentOffset = changes.Sum(x => x.GetTextLengthAfter());

            changes.Add(new Retain(textChange.FullTextLength - currentOffset));

            var kind      = textChange.Kind == RdTextChangeKind.Reset ? OtOperationKind.Reset : OtOperationKind.Normal;
            var operation = new OtOperation(changes, origin, ts, kind);

            Assertion.Assert(operation.DocumentLengthAfter() == textChange.FullTextLength,
                             "operation.DocumentLengthAfter() == textChange.FullTextLength");
            return(operation);
        }
Beispiel #3
0
        public static List <RdTextChange> ToRdTextChanges(this OtOperation op)
        {
            var        documentLength = op.DocumentLengthBefore();
            var        currOffset     = 0;
            InsertText lastInsert     = null;
            DeleteText lastDelete     = null;
            var        res            = new List <RdTextChange>();

            foreach (var change in op.Changes)
            {
                switch (change)
                {
                case Retain _:
                {
                    if (lastDelete != null || lastInsert != null)
                    {
                        res.Add(CreateTextChange(currOffset, lastInsert, lastDelete, documentLength));
                        lastDelete = null;
                        lastInsert = null;
                    }

                    break;
                }

                case InsertText i:
                {
                    if (lastInsert != null)
                    {
                        res.Add(CreateTextChange(currOffset, lastInsert, lastDelete, documentLength));
                        lastDelete = null;
                    }

                    documentLength += change.GetTextLengthAfter();
                    lastInsert      = i;
                    break;
                }

                case DeleteText d:
                {
                    if (lastDelete != null)
                    {
                        res.Add(CreateTextChange(currOffset, lastInsert, lastDelete, documentLength));
                        lastInsert = null;
                    }

                    documentLength -= change.GetTextLengthBefore();
                    lastDelete      = d;
                    break;
                }
                }

                currOffset += change.GetTextLengthAfter();
            }

            if (lastDelete != null || lastInsert != null)
            {
                res.Add(CreateTextChange(currOffset, lastInsert, lastDelete, documentLength));
            }

            return(res);
        }
Beispiel #4
0
 private void SendOperation(OtOperation operation)
 {
     Delegate.Operation.SetValue(operation);
 }
Beispiel #5
0
 protected bool Equals(OtOperation other)
 {
     return(Origin == other.Origin && Timestamp == other.Timestamp && Kind == other.Kind && Equals(Changes, other.Changes));
 }
Beispiel #6
0
/*
 *  public static OtOperation Compose(this OtOperation o1, OtOperation o2)
 *  {
 *    var after = o1.DocumentLengthAfter();
 *    var before = o2.DocumentLengthBefore();
 *
 *    Assertion.Assert(after == before, "after == before");
 *    Assertion.Assert(o1.Origin == o2.Origin, "o1.Role == o2.Role");
 *    Assertion.Assert(o1.Timestamp == o2.Timestamp, "o1.Timestamp == o2.Timestamp");
 *    Assertion.Assert(o1.Kind == o2.Kind, "o1.Kind == o2.Kind");
 *
 *
 *    var ops1 = new Stack<OtChange>(Enumerable.Reverse(o1.Changes));
 *    var ops2 = new Stack<OtChange>(Enumerable.Reverse(o2.Changes));
 *    var acc = new List<OtChange>();
 *
 *    while (true)
 *    {
 *      var op1 = ops1.Count != 0 ? ops1.Peek() : null;
 *      var op2 = ops2.Count != 0 ? ops2.Peek() : null;
 *
 *      if (op1 == null && op2 == null)
 *        break;
 *
 *      if (op1 != null && op2 == null)
 *      {
 *        acc.Add(op1);
 *        ops1.Pop();
 *        continue;
 *      }
 *
 *      if (op1 == null && op2 != null)
 *      {
 *        acc.Add(op2);
 *        ops2.Pop();
 *        continue;
 *      }
 *
 *      switch (op1)
 *      {
 *        case DeleteText d1 when op2 is InsertText d2 && d1.Text == d2.Text:
 *        {
 *          var length = d1.Text.Length;
 *          acc.Add(new Retain(length));
 *          ops1.Pop();
 *          ops2.Pop();
 *          break;
 *        }
 *        case OtChange _ when op2 is InsertText:
 *        {
 *          acc.Add(op2);
 *          ops2.Pop();
 *          break;
 *        }
 *        case DeleteText _ when op2 != null:
 *        {
 *          acc.Add(op1);
 *          ops1.Pop();
 *          break;
 *        }
 *        case Retain r1 when op2 is Retain r2:
 *        {
 *          var offset1 = r1.Offset;
 *          var offset2 = r2.Offset;
 *          if (offset1 > offset2)
 *          {
 *            acc.Add(op2);
 *            ops1.Pop();
 *            ops2.Pop();
 *            ops1.Push(new Retain(offset1 - offset2));
 *          }
 *          else if (offset1 == offset2)
 *          {
 *            acc.Add(op1);
 *            ops1.Pop();
 *            ops2.Pop();
 *          }
 *          else if (offset1 < offset2)
 *          {
 *            acc.Add(op1);
 *            ops1.Pop();
 *            ops2.Pop();
 *            ops2.Push(new Retain(offset2 - offset1));
 *          }
 *
 *          break;
 *        }
 *        case InsertText i1 when op2 is DeleteText d2:
 *        {
 *          var text1 = i1.Text;
 *          var text2 = d2.Text;
 *          if (text1.Length > text2.Length)
 *          {
 *            Assertion.Assert(text1.StartsWith(text2), "text1.StartsWith(text2)");
 *            ops1.Pop();
 *            ops2.Pop();
 *            ops1.Push(new InsertText(text1.Substring(text2.Length)));
 *          }
 *          else if (text1.Length == text2.Length)
 *          {
 *            Assertion.Assert(text1 == text2, "text1 == text2");
 *            ops1.Pop();
 *            ops2.Pop();
 *          }
 *          else if (text1.Length < text2.Length)
 *          {
 *            Assertion.Assert(text2.StartsWith(text1), "text2.StartsWith(text1)");
 *            ops1.Pop();
 *            ops2.Pop();
 *            ops2.Push(new DeleteText(text2.Substring(text1.Length)));
 *          }
 *
 *          break;
 *        }
 *        case InsertText i1 when op2 is Retain r2:
 *        {
 *          var text1 = i1.Text;
 *          var offset2 = r2.Offset;
 *          if (text1.Length > offset2)
 *          {
 *            acc.Add(new InsertText(text1.Substring(0, offset2)));
 *            ops1.Pop();
 *            ops2.Pop();
 *            ops1.Push(new InsertText(text1.Substring(offset2)));
 *          }
 *          else if (text1.Length == offset2)
 *          {
 *            acc.Add(op1);
 *            ops1.Pop();
 *            ops2.Pop();
 *          }
 *          else if (text1.Length < offset2)
 *          {
 *            acc.Add(op1);
 *            ops1.Pop();
 *            ops2.Pop();
 *            ops2.Push(new Retain(offset2 - text1.Length));
 *          }
 *
 *          break;
 *        }
 *        case Retain r1 when op2 is DeleteText d2:
 *        {
 *          var offset1 = r1.Offset;
 *          var text2 = d2.Text;
 *          if (offset1 > text2.Length)
 *          {
 *            acc.Add(op2);
 *            ops1.Pop();
 *            ops2.Pop();
 *            ops1.Push(new Retain(offset1 - text2.Length));
 *          }
 *          else if (offset1 == text2.Length)
 *          {
 *            acc.Add(op2);
 *            ops1.Pop();
 *            ops2.Pop();
 *          }
 *          else if (offset1 < text2.Length)
 *          {
 *            acc.Add(new DeleteText(text2.Substring(0, offset1)));
 *            ops1.Pop();
 *            ops2.Pop();
 *            ops2.Push(new DeleteText(text2.Substring(offset1)));
 *          }
 *
 *          break;
 *        }
 *        default: throw new ArgumentOutOfRangeException($"Not matched pair: (op1 = {op1}, op2 = {op2})");
 *      }
 *    }
 *
 *    return new OtOperation(acc, o1.Origin, o1.Timestamp, o1.Kind);
 *  }
 */

        // Ressel’s transformation function
        public static OtTransformResult Transform(OtOperation localDiff, OtOperation remoteApplyToDocument)
        {
            Assertion.Assert(localDiff.DocumentLengthBefore() == remoteApplyToDocument.DocumentLengthBefore(),
                             "localDiff.DocumentLengthBefore() == remoteApplyToDocument.DocumentLengthBefore()");
            Assertion.Assert(localDiff.Origin != remoteApplyToDocument.Origin, "localDiff.Role != remoteApplyToDocument.Role");
            Assertion.Assert(localDiff.Kind == OtOperationKind.Normal && remoteApplyToDocument.Kind == OtOperationKind.Normal,
                             "localDiff.Kind == OtOperationKind.Normal && remoteApplyToDocument.Kind == OtOperationKind.Normal");

            var resOp1 = new List <OtChange>();
            var resOp2 = new List <OtChange>();
            var ops1   = new Stack <OtChange>(Enumerable.Reverse(localDiff.Changes));
            var ops2   = new Stack <OtChange>(Enumerable.Reverse(remoteApplyToDocument.Changes));

            while (true)
            {
                var op1 = ops1.Count != 0 ? ops1.Peek() : null;
                var op2 = ops2.Count != 0 ? ops2.Peek() : null;

                if (op1 == null && op2 == null)
                {
                    break;
                }

                if (op1 == null && op2 != null)
                {
                    var offset = op2.GetTextLengthAfter();
                    if (offset > 0)
                    {
                        resOp1.Add(new Retain(offset));
                        resOp2.Add(op2);
                        ops2.Pop();
                    }
                    else
                    {
                        resOp2.Add(op2);
                        ops2.Pop();
                    }

                    continue;
                }

                if (op1 != null && op2 == null)
                {
                    var offset = op1.GetTextLengthAfter();
                    if (offset > 0)
                    {
                        resOp1.Add(op1);
                        resOp2.Add(new Retain(offset));
                        ops1.Pop();
                    }
                    else
                    {
                        resOp2.Add(op1);
                        ops1.Pop();
                    }

                    continue;
                }


                switch (op1)
                {
                case InsertText i1 when op2 is InsertText i2:
                {
                    if (localDiff.Origin < remoteApplyToDocument.Origin)
                    {
                        resOp1.Add(i1);
                        resOp2.Add(new Retain(i1.Text.Length));
                        ops1.Pop();
                    }
                    else
                    {
                        resOp1.Add(new Retain(i2.Text.Length));
                        resOp2.Add(op2);
                        ops2.Pop();
                    }

                    break;
                }

                case InsertText i1:
                {
                    resOp1.Add(i1);
                    resOp2.Add(new Retain(i1.Text.Length));
                    ops1.Pop();
                    break;
                }

                case var _ when op2 is InsertText i2:
                {
                    resOp1.Add(new Retain(i2.Text.Length));
                    resOp2.Add(i2);
                    ops2.Pop();
                    break;
                }

                case Retain r1 when op2 is Retain r2:
                {
                    var offset1 = r1.Offset;
                    var offset2 = r2.Offset;
                    if (offset1 > offset2)
                    {
                        resOp1.Add(op2);
                        resOp2.Add(op2);
                        ops1.Pop();
                        ops2.Pop();
                        ops1.Push(new Retain(offset1 - offset2));
                    }
                    else if (offset1 == offset2)
                    {
                        resOp1.Add(op1);
                        resOp2.Add(op1);
                        ops1.Pop();
                        ops2.Pop();
                    }
                    else if (offset1 < offset2)
                    {
                        resOp1.Add(op1);
                        resOp2.Add(op1);
                        ops1.Pop();
                        ops2.Pop();
                        ops2.Push(new Retain(offset2 - offset1));
                    }

                    break;
                }

                case DeleteText d1 when op2 is DeleteText d2:
                {
                    var text1 = d1.Text;
                    var text2 = d2.Text;
                    if (text1.Length > text2.Length)
                    {
                        ops1.Pop();
                        ops2.Pop();
                        ops1.Push(new DeleteText(text1.Substring(text2.Length)));
                    }
                    else if (text1.Length == text2.Length)
                    {
                        Assertion.Assert(text1 == text2, "text1 == text2");
                        ops1.Pop();
                        ops2.Pop();
                    }
                    else if (text1.Length < text2.Length)
                    {
                        ops1.Pop();
                        ops2.Pop();
                        ops2.Push(new DeleteText(text2.Substring(text1.Length)));
                    }

                    break;
                }

                case DeleteText d1 when op2 is Retain r2:
                {
                    var text1   = d1.Text;
                    var offset2 = r2.Offset;
                    if (text1.Length > offset2)
                    {
                        resOp1.Add(new DeleteText(text1.Substring(0, offset2)));
                        ops1.Pop();
                        ops2.Pop();
                        ops1.Push(new DeleteText(text1.Substring(offset2)));
                    }
                    else if (text1.Length == offset2)
                    {
                        resOp1.Add(op1);
                        ops1.Pop();
                        ops2.Pop();
                    }
                    else if (text1.Length < offset2)
                    {
                        resOp1.Add(op1);
                        ops1.Pop();
                        ops2.Pop();
                        ops2.Push(new Retain(offset2 - text1.Length));
                    }

                    break;
                }

                case Retain r1 when op2 is DeleteText r2:
                {
                    var offset1 = r1.Offset;
                    var text2   = r2.Text;
                    if (offset1 > text2.Length)
                    {
                        resOp2.Add(op2);
                        ops1.Pop();
                        ops2.Pop();
                        ops1.Push(new Retain(offset1 - text2.Length));
                    }
                    else if (offset1 == text2.Length)
                    {
                        resOp2.Add(op2);
                        ops1.Pop();
                        ops2.Pop();
                    }
                    else if (offset1 < text2.Length)
                    {
                        resOp2.Add(new DeleteText(text2.Substring(0, offset1)));
                        ops1.Pop();
                        ops2.Pop();
                        ops2.Push(new DeleteText(text2.Substring(offset1)));
                    }
                    break;
                }

                default: throw new ArgumentOutOfRangeException($"Not matched pair: (op1 = {op1}, op2 = {op2})");
                }
            }

            return(new OtTransformResult(
                       new OtOperation(resOp1, localDiff.Origin, localDiff.Timestamp, OtOperationKind.Normal),
                       new OtOperation(resOp2, remoteApplyToDocument.Origin, remoteApplyToDocument.Timestamp, OtOperationKind.Normal)));
        }
Beispiel #7
0
 public OtTransformResult(OtOperation newLocalDiff, OtOperation localizedApplyToDocument)
 {
     NewLocalDiff             = newLocalDiff;
     LocalizedApplyToDocument = localizedApplyToDocument;
 }