Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix incorrect index returned when using posRangeToIndexRange #156

Merged
merged 3 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.yorkie.document.json

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.gson.reflect.TypeToken
import dev.yorkie.TreeBasicTest
import dev.yorkie.TreeTest
import dev.yorkie.core.Client
Expand All @@ -17,7 +18,9 @@ import dev.yorkie.document.json.TreeBuilder.text
import dev.yorkie.document.operation.OperationInfo
import dev.yorkie.document.operation.OperationInfo.SetOpInfo
import dev.yorkie.document.operation.OperationInfo.TreeEditOpInfo
import dev.yorkie.gson
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand Down Expand Up @@ -1743,6 +1746,65 @@ class JsonTreeTest {
}
}

@Test
fun test_returning_range_from_index_correctly_withint_document_events() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fun test_returning_range_from_index_correctly_withint_document_events() {
fun test_returning_range_from_index_correctly_within_document_events() {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 34eea6e.

withTwoClientsAndDocuments { c1, c2, d1, d2, _ ->
updateAndSync(
Updater(c1, d1) { root, _ ->
root.setNewTree(
"t",
element("doc") {
element("p") {
text { "hello" }
}
},
)
},
Updater(c2, d2),
)
assertTreesXmlEquals("<doc><p>hello</p></doc>", d1)
assertTreesXmlEquals("<doc><p>hello</p></doc>", d2)

updateAndSync(
Updater(c1, d1) { root, presence ->
root.rootTree().edit(1, 1, text { "a" })
val posSelection = root.rootTree().indexRangeToPosRange(2 to 2)
presence.put(mapOf("selection" to gson.toJson(posSelection)))
},
Updater(c2, d2),
)
assertTreesXmlEquals("<doc><p>ahello</p></doc>", d1)
assertTreesXmlEquals("<doc><p>ahello</p></doc>", d2)
val selectionType = object : TypeToken<TreePosStructRange>() {}.type
val selection = gson.fromJson<TreePosStructRange>(
d1.presences.value[c1.requireClientId()]!!["selection"],
selectionType,
)
assertEquals(2 to 2, d1.getRoot().rootTree().posRangeToIndexRange(selection))

val d1Events = mutableListOf<Document.Event>()
val job = launch(start = CoroutineStart.UNDISPATCHED) {
d1.events.collect(d1Events::add)
}
updateAndSync(
Updater(c1, d1),
Updater(c2, d2) { root, _ ->
root.rootTree().edit(2, 2, text { "b" })
},
)
assertTreesXmlEquals("<doc><p>abhello</p></doc>", d1)
assertTreesXmlEquals("<doc><p>abhello</p></doc>", d2)

withTimeout(GENERAL_TIMEOUT) {
while (d1Events.isEmpty()) {
delay(50)
}
}
assertIs<RemoteChange>(d1Events.first())
job.cancel()
}
}

companion object {

fun JsonObject.rootTree() = getAs<JsonTree>("t")
Expand Down
36 changes: 20 additions & 16 deletions yorkie/src/main/kotlin/dev/yorkie/document/crdt/CrdtTree.kt
Original file line number Diff line number Diff line change
Expand Up @@ -415,10 +415,13 @@ internal class CrdtTree(
*
* [CrdtTreePos] is a position in the CRDT perspective. This is
* different from [TreePos] which is a position of the tree in the local perspective.
*
* If [executedAt] is given, then it is used to find the appropriate left node
* for concurrent insertion.
*/
fun findNodesAndSplitText(
pos: CrdtTreePos,
executedAt: TimeTicket,
executedAt: TimeTicket? = null,
): Pair<CrdtTreeNode, CrdtTreeNode> {
// 01. Find the parent and left sibling node of the given position.
val (parent, leftSibling) = pos.toTreeNodes(this)
Expand All @@ -438,14 +441,18 @@ internal class CrdtTree(
// 04. Find the appropriate left node. If some nodes are inserted at the
// same position concurrently, then we need to find the appropriate left
// node. This is similar to RGA.
val allChildren = realParent.allChildren
val index = if (isLeftMost) 0 else allChildren.indexOf(leftSibling) + 1

var updatedLeftSiblingNode = leftSibling
for (node in allChildren.drop(index)) {
if (node.id.createdAt <= executedAt) break
updatedLeftSiblingNode = node
executedAt?.let {
val allChildren = realParent.allChildren
val index = if (isLeftMost) 0 else allChildren.indexOf(leftSibling) + 1

for (node in allChildren.drop(index)) {
if (node.id.createdAt <= executedAt) break
updatedLeftSiblingNode = node
}
updatedLeftSiblingNode
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this line needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed in a9b50ca

}

return realParent to updatedLeftSiblingNode
}

Expand Down Expand Up @@ -590,21 +597,18 @@ internal class CrdtTree(
/**
* Converts the given position [range] to the path range.
*/
fun posRangeToPathRange(
range: TreePosRange,
executedAt: TimeTicket,
): Pair<List<Int>, List<Int>> {
val (fromParent, fromLeft) = findNodesAndSplitText(range.first, executedAt)
val (toParent, toLeft) = findNodesAndSplitText(range.second, executedAt)
fun posRangeToPathRange(range: TreePosRange): Pair<List<Int>, List<Int>> {
val (fromParent, fromLeft) = findNodesAndSplitText(range.first)
val (toParent, toLeft) = findNodesAndSplitText(range.second)
return toPath(fromParent, fromLeft) to toPath(toParent, toLeft)
}

/**
* Converts the given position range to the path range.
*/
fun posRangeToIndexRange(range: TreePosRange, executedAt: TimeTicket): Pair<Int, Int> {
val (fromParent, fromLeft) = findNodesAndSplitText(range.first, executedAt)
val (toParent, toLeft) = findNodesAndSplitText(range.second, executedAt)
fun posRangeToIndexRange(range: TreePosRange): Pair<Int, Int> {
val (fromParent, fromLeft) = findNodesAndSplitText(range.first)
val (toParent, toLeft) = findNodesAndSplitText(range.second)
return toIndex(fromParent, fromLeft) to toIndex(toParent, toLeft)
}

Expand Down
4 changes: 2 additions & 2 deletions yorkie/src/main/kotlin/dev/yorkie/document/json/JsonTree.kt
Original file line number Diff line number Diff line change
Expand Up @@ -228,15 +228,15 @@ public class JsonTree internal constructor(
*/
public fun posRangeToIndexRange(range: TreePosStructRange): Pair<Int, Int> {
val posRange = range.first.toOriginal() to range.second.toOriginal()
return target.posRangeToIndexRange(posRange, context.lastTimeTicket)
return target.posRangeToIndexRange(posRange)
}

/**
* Converts the position [range] into the path range.
*/
public fun posRangeToPathRange(range: TreePosStructRange): Pair<List<Int>, List<Int>> {
val posRange = range.first.toOriginal() to range.second.toOriginal()
return target.posRangeToPathRange(posRange, context.lastTimeTicket)
return target.posRangeToPathRange(posRange)
}

companion object {
Expand Down
Loading