/// <summary>
        /// Applies an operation based on another site document (defined by a list of ids of operations that were applied prior to this one)
        /// and adjusts the supplied caret index as appropriate
        /// </summary>
        /// <param name="appliedOperation"></param>
        /// <param name="caretIndex"></param>
        public void ApplyTransform(AppliedOperation appliedOperation, ref int caretIndex)
        {
            var missingRemoteTransformIds = _AppliedOperationsOrder.Except(appliedOperation.PriorStateTransformIds);

            var operation = appliedOperation.Operation;

            foreach (var id in missingRemoteTransformIds.Reverse())
            {
                operation = OperationTransformer.Transform(operation, _AppliedOperations[id]);
            }

            TransformState(operation);

            if (operation.Position < caretIndex)
            {
                if (operation is InsertOperation)
                {
                    caretIndex += operation.Length;
                    caretIndex  = Math.Min(caretIndex, CurrentState.Length);
                }
                else if (operation is DeleteOperation)
                {
                    caretIndex -= operation.Length;
                    caretIndex  = Math.Max(caretIndex, 0);
                }
            }
        }
示例#2
0
        private IRequest TransformRequest(IRequest request)
        {
            if (request.Revision == _revision)
            {
                return(request);
            }
            if (request.Revision > _revision)
            {
                throw new System.ApplicationException("Request revision is ahead of server revision.");
            }

            IRequest tempRequest = request;

            // get operations from revision log since request revision number
            var logOperations = _revisionLog
                                .Where(pair => pair.Key >= tempRequest.Revision)
                                .OrderBy(pair => pair.Key).ToList();

            List <IOperation> transformedOperations = new List <IOperation>(tempRequest.Operations);

            foreach (var(revision, operations) in logOperations)
            {
                List <IOperation> temp = OperationTransformer.Transform(transformedOperations, operations).ToList();
                transformedOperations = new List <IOperation>(temp);
                tempRequest           = RequestFactory.CreateRequest(tempRequest.ClientId, revision, transformedOperations);
            }

            return(tempRequest);
        }
示例#3
0
        private void HandleReceivedRequests()
        {
            if (_receivedRequests.IsEmpty)
            {
                return;
            }
            if (!_receivedRequests.TryDequeue(out IRequest request))
            {
                return;
            }

            lock (_locker)
            {
                // check if request is acknowledgment for the awaiting request
                if (request.IsAcknowledged &&
                    request.ClientId == ClientId &&
                    _awaitingRequests.Count() != 0 &&
                    request.Operations.All(o1 => _awaitingRequests.First().Operations.Any(o2 => o1.Timestamp == o2.Timestamp)))
                {
                    _awaitingRequests.Clear();
                    _revision = request.Revision;

                    // logging
                    if (_logginEnabled)
                    {
                        _logger.LogWriteLine($"Client with ID: '{ClientId}' recieved ack message");
                    }
                    return;
                }

                IRequest transformedRequest = request;

                // transform operations in buffer according to the received request operations
                if (!_operationsBuffer.IsEmpty)
                {
                    List <IOperation> transformedOperations = OperationTransformer.Transform(_operationsBuffer.ToList(), request.Operations.ToList()).ToList();
                    _operationsBuffer.Clear();
                    foreach (IOperation operation in transformedOperations)
                    {
                        _operationsBuffer.Enqueue(operation);
                    }
                }

                // transform incomming request operations according to the awating request operations
                if (_awaitingRequests.Count() != 0)
                {
                    List <IOperation> transformedOperations = OperationTransformer.Transform(request.Operations.ToList(), _awaitingRequests.First().Operations.ToList()).ToList();
                    transformedRequest = RequestFactory.CreateRequest(request.ClientId, request.Revision, transformedOperations, request.IsAcknowledged);
                }

                _revision = request.Revision;

                ApplyOperations(transformedRequest.Operations.ToList());

                if (_logginEnabled)
                {
                    _logger.LogWriteLine($"Client with ID: '{ClientId}' processed incoming request");
                }
            }
        }
示例#4
0
        public void OperationTransformer_TransformDeleteDelete_LocalEqualToRemote()
        {
            var state = new DocumentState(1, "123456789");

            var localOperation  = new DeleteOperation(state, 2);
            var remoteOperation = new DeleteOperation(state, 2);

            var transformed = OperationTransformer.Transform(remoteOperation, localOperation);

            var stateStr = state.CurrentState;

            stateStr = localOperation.ApplyTransform(stateStr);
            stateStr = transformed.ApplyTransform(stateStr);

            Assert.AreEqual("12456789", stateStr);
        }
        public void OperationTransformer_TransformInsertDelete_LocalBeforeRemote()
        {
            var state = new DocumentState(1, "123456789");

            var localOperation  = new InsertOperation(state, 2, 'a');
            var remoteOperation = new DeleteOperation(state, 4);

            var transformed = OperationTransformer.Transform(remoteOperation, localOperation);

            var stateStr = state.CurrentState;

            stateStr = localOperation.ApplyTransform(stateStr);
            stateStr = transformed.ApplyTransform(stateStr);


            Assert.AreEqual("12a346789", stateStr);
        }
示例#6
0
        public void OperationTransformer_TransformInsertInsert_LocalEqualToRemote()
        {
            var localState  = new DocumentState(0, "123456789");
            var remoteState = new DocumentState(1, "123456789");

            var localOperation  = new InsertOperation(localState, 2, 'a');
            var remoteOperation = new InsertOperation(remoteState, 2, 'b');

            var transformed = OperationTransformer.Transform(remoteOperation, localOperation);

            var stateStr = localState.CurrentState;

            stateStr = localOperation.ApplyTransform(stateStr);
            stateStr = transformed.ApplyTransform(stateStr);

            Assert.AreEqual("12ba3456789", stateStr);
        }
        public void OperationTransformer_Transform_CP1TP1Satisfied()
        {
            var site1Str = "abcd";
            var site2Str = "abcd";
            var site1    = new DocumentState(1, site1Str);
            var site2    = new DocumentState(2, site2Str);

            var op1 = new InsertOperation(site1, 0, '1');
            var op2 = new InsertOperation(site2, 0, '2');

            site1Str = op2.ApplyTransform(site1Str);
            site1Str = OperationTransformer.Transform(op1, op2).ApplyTransform(site1Str);

            site2Str = op1.ApplyTransform(site2Str);
            site2Str = OperationTransformer.Transform(op2, op1).ApplyTransform(site2Str);

            Assert.AreEqual(site1Str, site2Str);
        }
        public void OperationTransformer_Transform_CP2TP2Satisfied()
        {
            var site1Str = "abcd";
            var site2Str = "abcd";
            var site3Str = "abcd";
            var site1    = new DocumentState(1, site1Str);
            var site2    = new DocumentState(2, site2Str);
            var site3    = new DocumentState(3, site3Str);

            var op1 = new InsertOperation(site1, 0, '1');
            var op2 = new InsertOperation(site2, 1, '2');
            var op3 = new InsertOperation(site3, 2, '3');

            var op2dash   = OperationTransformer.Transform(op2, op1);
            var op3dashV1 = OperationTransformer.Transform(OperationTransformer.Transform(op3, op1), op2dash);

            var op1dash   = OperationTransformer.Transform(op1, op2);
            var op3dashV2 = OperationTransformer.Transform(OperationTransformer.Transform(op3, op2), op1dash);

            Assert.IsTrue(op3dashV1.IdenticalOperation(op3dashV2));
        }
        [TestMethod][Ignore] // TODO: Get IP2 succeeding (if needed)
        public void OperationTransformer_Transform_IP2Satisfied()
        {
            var output = new List <(OperationBase op1, OperationBase op2, string message)>();

            foreach (var opPair in GetOperationPairs())
            {
                var opx = opPair.op1;
                var op  = opPair.op2;

                OperationBase opUndo = op.CreateInverse(opPair.ds2);
                if (!opx.IdenticalOperation(OperationTransformer.Transform(opx, OperationTransformer.Transform(op, opUndo))))
                {
                    output.Add((opx, op,
                                $"{Environment.NewLine}IP2 failed with opx = {opx} and op = {op}"));
                    // TODO: This needs to pass to support undo properly. Basically this is ensuring that transforming op2 by op1 then op1undone is the same as op2 on its own
                }
            }

            if (output.Count != 0)
            {
                Assert.Fail(string.Join(string.Empty, output.Select(o => o.message)));
            }
        }