/// <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); } } }
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); }
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"); } } }
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); }
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))); } }