Skip to content

Commit

Permalink
Update reference transformation to only transform related operations
Browse files Browse the repository at this point in the history
  • Loading branch information
mmacfadden committed Jul 13, 2021
1 parent d21bed2 commit 896f319
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 16 deletions.
Expand Up @@ -153,7 +153,7 @@ private[model] final class RealtimeModel(val domainId: DomainId,
val ShareReference(domainFqn, _, _, id, key, values, contextVersion) = share

val refVal: ReferenceValue[ModelReferenceValues] = ReferenceValue(values, contextVersion)
this.cc.processRemoteReferenceSet(session.sessionId, refVal) match {
this.cc.processRemoteReferenceSet(session.sessionId, valueId, refVal) match {
case Some(xformed) =>
val setRef: SetReference = SetReference(domainFqn, modelId, session, id, key, xformed.referenceValues, xformed.contextVersion.toInt)
realTimeValue.processReferenceEvent(setRef, session).map { _ =>
Expand All @@ -172,7 +172,7 @@ private[model] final class RealtimeModel(val domainId: DomainId,

case set: SetReference =>
val refVal: ReferenceValue[ModelReferenceValues] = ReferenceValue(set.values, set.contextVersion)
this.cc.processRemoteReferenceSet(session.sessionId, refVal) match {
this.cc.processRemoteReferenceSet(session.sessionId, valueId, refVal) match {
case Some(xFormed) =>
val setRef: SetReference = SetReference(domainId, modelId, session, set.valueId, set.key, xFormed.referenceValues, xFormed.contextVersion.toInt)
realTimeValue.processReferenceEvent(setRef, session).map { _ =>
Expand All @@ -190,8 +190,8 @@ private[model] final class RealtimeModel(val domainId: DomainId,
}
case None =>
// TODO we just drop the event because we don't have a RTV with this id.
// later on I would like to keep some history to know if we ever had
// an RTV with this id, else throw an error.
// later on I would like to keep some history to know if we ever had
// an RTV with this id, else throw an error.
Success(None)
}
}
Expand Down
Expand Up @@ -61,14 +61,14 @@ private[model] final class ServerConcurrencyControl private(operationTransformer
}
}

def processRemoteReferenceSet[V <: ModelReferenceValues](clientId: String, reference: ReferenceValue[V]): Option[ReferenceValue[V]] = {
def processRemoteReferenceSet[V <: ModelReferenceValues](clientId: String, referenceOwnerValueId: String, reference: ReferenceValue[V]): Option[ReferenceValue[V]] = {
val clientState = clientStates(clientId)
val newStatePath = getCurrentClientStatePath(clientState, reference.contextVersion)

var xFormedValues: Option[V] = Some(reference.referenceValues)

newStatePath.foreach { event =>
xFormedValues = xFormedValues.flatMap { v => referenceTransformer.transform(event.operation, v) }
xFormedValues = xFormedValues.flatMap { v => referenceTransformer.transform(event.operation, referenceOwnerValueId, v) }
}

clientStates(clientId) = clientState.copy(
Expand Down
Expand Up @@ -18,35 +18,41 @@ import scala.util.control.Breaks._

class ReferenceTransformer(tfr: TransformationFunctionRegistry) {

def transform[V <: ModelReferenceValues](op: Operation, values: V): Option[V] = {
def transform[V <: ModelReferenceValues](op: Operation, valueId: String, values: V): Option[V] = {
op match {
case c: CompoundOperation =>
transform(c, values)
transform(c, valueId, values)
case d: DiscreteOperation =>
transform[V](d, values)
transform[V](d, valueId, values)
}
}

private[this] def transform[V <: ModelReferenceValues](op: CompoundOperation, values: V): Option[V] = {
private[this] def transform[V <: ModelReferenceValues](op: CompoundOperation, valueId: String, values: V): Option[V] = {
var result: Option[V] = Some(values)

breakable {
op.operations.foreach { op =>
if (result.isEmpty) {
break()
}
result = result.flatMap(v => transform(op, v))
result = result.flatMap(v => transform(op, valueId, v))
}
}

result
}

private[this] def transform[V <: ModelReferenceValues](op: DiscreteOperation, values: V): Option[V] = {
tfr.getReferenceTransformationFunction(op, values) match {
case Some(tf) => tf.transform(op, values)
case None => throw new IllegalArgumentException(
s"No reference transformation function found for operation and reference pair (${op.getClass.getSimpleName},${values.getClass.getSimpleName})")
private[this] def transform[V <: ModelReferenceValues](op: DiscreteOperation, referenceOwnerValueId: String, values: V): Option[V] = {
if (referenceOwnerValueId != op.id) {
// The operation does not target the real time value
// that this reference is for.
Some(values)
} else {
tfr.getReferenceTransformationFunction(op, values) match {
case Some(tf) => tf.transform(op, values)
case None => throw new IllegalArgumentException(
s"No reference transformation function found for operation and reference pair (${op.getClass.getSimpleName},${values.getClass.getSimpleName})")
}
}
}
}
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2021 - Convergence Labs, Inc.
*
* This file is part of the Convergence Server, which is released under
* the terms of the GNU General Public License version 3 (GPLv3). A copy
* of the GPLv3 should have been provided along with this file, typically
* located in the "LICENSE" file, which is part of this source code package.
* Alternatively, see <https://www.gnu.org/licenses/gpl-3.0.html> for the
* full text of the GPLv3 license, if it was not provided.
*/

package com.convergencelabs.convergence.server.backend.services.domain.model.ot.xform

import com.convergencelabs.convergence.server.backend.services.domain.model.ot._
import com.convergencelabs.convergence.server.model.domain.model.{IndexReferenceValues, ModelReferenceValues}
import org.mockito.Matchers.anyObject
import org.mockito.Mockito
import org.mockito.Mockito.{spy, times, verify, when}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import org.scalatestplus.mockito.MockitoSugar


// scalastyle:off multiple.string.literals
class ReferenceTransformerSpec
extends AnyWordSpec
with Matchers
with MockitoSugar {

val valueId = "testId"
val otherValueId = "otherId"

"A ReferenceTransformer" when {

"transforming reference values against an operation" must {

"not transform references against operations targeted at other elements" in new WithIdentityTransform {
val s: StringSpliceOperation = StringSpliceOperation(valueId, noOp = false, 1, 0, "s")
val values: IndexReferenceValues = IndexReferenceValues(List(10, 20))
transformer.transform(s, otherValueId, values)
verify(otfSpy, times(0)).transform(s, values)
}

"transform references against operations that target the same element" in new WithIdentityTransform {
val s: StringSpliceOperation = StringSpliceOperation(valueId, noOp = false, 1, 0, "s")
val values: IndexReferenceValues = IndexReferenceValues(List(10, 20))
transformer.transform(s, valueId, values)
verify(otfSpy, times(1)).transform(s, values)
}
}
}

trait TestFixture {
val tfRegistry: TransformationFunctionRegistry = mock[TransformationFunctionRegistry]
val otfSpy: IdentityTransform = spy(new IdentityTransform())
val transformer = new ReferenceTransformer(tfRegistry)
}

trait WithIdentityTransform extends TestFixture {
when(tfRegistry.getReferenceTransformationFunction(anyObject[DiscreteOperation](), anyObject[ModelReferenceValues]())).thenReturn(Some(otfSpy))
}

class IdentityTransform extends ReferenceTransformationFunction[DiscreteOperation, ModelReferenceValues] {
override def transform(serverOp: DiscreteOperation, values: ModelReferenceValues): Option[ModelReferenceValues] = Some(values)
}
}

0 comments on commit 896f319

Please sign in to comment.