Skip to content

Commit

Permalink
Few improvements here and there (#43)
Browse files Browse the repository at this point in the history
* Few improvements here and there

Some name changes.
Fix TimeTicket comparison error.
Use ByteString instead of ByteArray in CrdtPrimitive.

* Add more tests
  • Loading branch information
skhugh committed Nov 8, 2022
1 parent 69b3059 commit bc4581c
Show file tree
Hide file tree
Showing 20 changed files with 441 additions and 144 deletions.
43 changes: 21 additions & 22 deletions yorkie/src/main/kotlin/dev/yorkie/api/ElementConverter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import dev.yorkie.document.crdt.CrdtCounter.CounterType
import dev.yorkie.document.crdt.CrdtElement
import dev.yorkie.document.crdt.CrdtObject
import dev.yorkie.document.crdt.CrdtPrimitive
import dev.yorkie.document.crdt.PrimitiveType
import dev.yorkie.document.crdt.RgaTreeList
import dev.yorkie.document.crdt.RhtPQMap

Expand Down Expand Up @@ -102,16 +101,16 @@ internal fun PBPrimitive.toCrdtPrimitive(): CrdtPrimitive {
)
}

internal fun PBValueType.toPrimitiveType(): PrimitiveType {
internal fun PBValueType.toPrimitiveType(): CrdtPrimitive.Type {
return when (this) {
PBValueType.VALUE_TYPE_NULL -> PrimitiveType.Null
PBValueType.VALUE_TYPE_BOOLEAN -> PrimitiveType.Boolean
PBValueType.VALUE_TYPE_INTEGER -> PrimitiveType.Integer
PBValueType.VALUE_TYPE_LONG -> PrimitiveType.Long
PBValueType.VALUE_TYPE_DOUBLE -> PrimitiveType.Double
PBValueType.VALUE_TYPE_STRING -> PrimitiveType.String
PBValueType.VALUE_TYPE_BYTES -> PrimitiveType.Bytes
PBValueType.VALUE_TYPE_DATE -> PrimitiveType.Date
PBValueType.VALUE_TYPE_NULL -> CrdtPrimitive.Type.Null
PBValueType.VALUE_TYPE_BOOLEAN -> CrdtPrimitive.Type.Boolean
PBValueType.VALUE_TYPE_INTEGER -> CrdtPrimitive.Type.Integer
PBValueType.VALUE_TYPE_LONG -> CrdtPrimitive.Type.Long
PBValueType.VALUE_TYPE_DOUBLE -> CrdtPrimitive.Type.Double
PBValueType.VALUE_TYPE_STRING -> CrdtPrimitive.Type.String
PBValueType.VALUE_TYPE_BYTES -> CrdtPrimitive.Type.Bytes
PBValueType.VALUE_TYPE_DATE -> CrdtPrimitive.Type.Date
else -> error("unimplemented type $this")
}
}
Expand Down Expand Up @@ -157,7 +156,7 @@ internal fun CrdtObject.toPBJsonObject(): PBJsonElement {
}
}

internal fun List<RhtPQMap.RhtPQMapNode<CrdtElement>>.toPBRhtNodes(): List<PBRhtNode> {
internal fun List<RhtPQMap.Node<CrdtElement>>.toPBRhtNodes(): List<PBRhtNode> {
return map {
rHTNode {
key = it.strKey
Expand Down Expand Up @@ -189,24 +188,24 @@ internal fun CrdtPrimitive.toPBPrimitive(): PBJsonElement {
return jSONElement {
primitive = primitive {
type = crdtPrimitive.type.toPBValueType()
value = crdtPrimitive.toBytes().toByteString()
value = crdtPrimitive.toBytes()
createdAt = crdtPrimitive.createdAt.toPBTimeTicket()
crdtPrimitive.movedAt?.let { movedAt = it.toPBTimeTicket() }
crdtPrimitive.removedAt?.let { removedAt = it.toPBTimeTicket() }
}
}
}

internal fun PrimitiveType.toPBValueType(): PBValueType {
internal fun CrdtPrimitive.Type.toPBValueType(): PBValueType {
return when (this) {
PrimitiveType.Null -> PBValueType.VALUE_TYPE_NULL
PrimitiveType.Boolean -> PBValueType.VALUE_TYPE_BOOLEAN
PrimitiveType.Integer -> PBValueType.VALUE_TYPE_INTEGER
PrimitiveType.Long -> PBValueType.VALUE_TYPE_LONG
PrimitiveType.Double -> PBValueType.VALUE_TYPE_DOUBLE
PrimitiveType.String -> PBValueType.VALUE_TYPE_STRING
PrimitiveType.Bytes -> PBValueType.VALUE_TYPE_BYTES
PrimitiveType.Date -> PBValueType.VALUE_TYPE_DATE
CrdtPrimitive.Type.Null -> PBValueType.VALUE_TYPE_NULL
CrdtPrimitive.Type.Boolean -> PBValueType.VALUE_TYPE_BOOLEAN
CrdtPrimitive.Type.Integer -> PBValueType.VALUE_TYPE_INTEGER
CrdtPrimitive.Type.Long -> PBValueType.VALUE_TYPE_LONG
CrdtPrimitive.Type.Double -> PBValueType.VALUE_TYPE_DOUBLE
CrdtPrimitive.Type.String -> PBValueType.VALUE_TYPE_STRING
CrdtPrimitive.Type.Bytes -> PBValueType.VALUE_TYPE_BYTES
CrdtPrimitive.Type.Date -> PBValueType.VALUE_TYPE_DATE
}
}

Expand Down Expand Up @@ -267,7 +266,7 @@ internal fun CrdtElement.toPBJsonElementSimple(): PBJsonElementSimple {
is CrdtArray -> type = PBValueType.VALUE_TYPE_JSON_ARRAY
is CrdtPrimitive -> {
type = element.type.toPBValueType()
value = element.toBytes().toByteString()
value = element.toBytes()
}
is CrdtCounter -> {
type = element.type.toPBCounterType()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal data class CheckPoint(
return if (increase == 0) {
this
} else {
CheckPoint(serverSeq, clientSeq + increase)
copy(clientSeq = clientSeq + increase)
}
}

Expand Down
9 changes: 5 additions & 4 deletions yorkie/src/main/kotlin/dev/yorkie/document/crdt/CrdtArray.kt
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,12 @@ internal data class CrdtArray(
}

override fun deepCopy(): CrdtElement {
val clone = copy(elements = RgaTreeList())
elements.forEach { node ->
clone.elements.insertAfter(clone.lastCreatedAt, node.value.deepCopy())
val elementsClone = RgaTreeList().apply {
elements.forEach { node ->
insertAfter(lastCreatedAt, node.value.deepCopy())
}
}
return clone
return copy(elements = elementsClone)
}

override fun iterator(): Iterator<CrdtElement> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ internal data class CrdtCounter private constructor(
}

override fun deepCopy(): CrdtElement {
return CrdtCounter(value, createdAt, _movedAt, _removedAt)
return copy()
}

companion object {
Expand All @@ -66,7 +66,7 @@ internal data class CrdtCounter private constructor(
_removedAt: TimeTicket? = null,
) = CrdtCounter(value.sanitized(), createdAt, _movedAt, _removedAt)

fun CounterValue.sanitized(): Number = when (counterType()) {
private fun CounterValue.sanitized(): Number = when (counterType()) {
CounterType.IntegerCnt -> toInt()
CounterType.LongCnt -> toLong()
CounterType.DoubleCnt -> toDouble()
Expand Down
11 changes: 6 additions & 5 deletions yorkie/src/main/kotlin/dev/yorkie/document/crdt/CrdtObject.kt
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,12 @@ internal data class CrdtObject(
* Copies itself deeply.
*/
override fun deepCopy(): CrdtObject {
val clone = CrdtObject(createdAt, _movedAt, _removedAt, RhtPQMap())
rht.forEach {
clone.rht[it.strKey] = it.value.deepCopy()
val rhtClone = RhtPQMap<CrdtElement>().apply {
rht.forEach { (strKey, value) ->
set(strKey, value)
}
}
return clone
return copy(rht = rhtClone)
}

/**
Expand Down Expand Up @@ -106,7 +107,7 @@ internal data class CrdtObject(
val node = nodes[index]
if (!keySet.contains(node.strKey)) {
keySet.add(node.strKey)
if (!node.isRemoved()) return true
if (!node.isRemoved) return true
}
index++
}
Expand Down
130 changes: 88 additions & 42 deletions yorkie/src/main/kotlin/dev/yorkie/document/crdt/CrdtPrimitive.kt
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
package dev.yorkie.document.crdt

import com.google.protobuf.ByteString
import com.google.protobuf.kotlin.toByteString
import com.google.protobuf.kotlin.toByteStringUtf8
import dev.yorkie.document.time.TimeTicket
import java.nio.ByteBuffer
import java.util.Date

internal data class CrdtPrimitive(
val value: Any?,
@Suppress("DataClassPrivateConstructor")
internal data class CrdtPrimitive private constructor(
private val _value: Any?,
override val createdAt: TimeTicket,
override var _movedAt: TimeTicket? = null,
override var _removedAt: TimeTicket? = null,
) : CrdtElement() {
val value: Any? = _value.sanitized()

val type = when (value) {
is Boolean -> PrimitiveType.Boolean
is Int -> PrimitiveType.Integer
is Long -> PrimitiveType.Long
is Double -> PrimitiveType.Double
is String -> PrimitiveType.String
is ByteArray -> PrimitiveType.Bytes
is Date -> PrimitiveType.Date
else -> PrimitiveType.Null
is Boolean -> Type.Boolean
is Int -> Type.Integer
is Long -> Type.Long
is Double -> Type.Double
is String -> Type.String
is ByteString -> Type.Bytes
is Date -> Type.Date
else -> Type.Null
}

val isNumericType = type in NUMERIC_TYPES
Expand All @@ -28,57 +33,98 @@ internal data class CrdtPrimitive(
* Copies itself deeply.
*/
override fun deepCopy(): CrdtElement {
return CrdtPrimitive(value, createdAt, _movedAt, _removedAt)
return when (value) {
is ByteString -> {
copy(_value = value.toByteArray().toByteString())
}
is Date -> {
copy(_value = value.clone())
}
else -> copy()
}
}

fun toBytes(): ByteArray {
fun toBytes(): ByteString {
return when (type) {
PrimitiveType.Null -> byteArrayOf()
PrimitiveType.Boolean -> byteArrayOf(if (value == true) 1 else 0)
PrimitiveType.Integer -> {
ByteBuffer.allocate(Int.SIZE_BYTES).putInt(value as Int).array()
Type.Null -> ByteString.EMPTY
Type.Boolean -> byteArrayOf(if (value == true) 1 else 0).toByteString()
Type.Integer -> {
ByteBuffer.allocate(Int.SIZE_BYTES)
.putInt(value as Int)
.array()
.toByteString()
}
PrimitiveType.Long -> {
ByteBuffer.allocate(Long.SIZE_BYTES).putLong(value as Long).array()
Type.Long -> {
ByteBuffer.allocate(Long.SIZE_BYTES)
.putLong(value as Long)
.array()
.toByteString()
}
PrimitiveType.Double -> {
ByteBuffer.allocate(Double.SIZE_BYTES).putDouble(value as Double).array()
Type.Double -> {
ByteBuffer.allocate(Double.SIZE_BYTES)
.putDouble(value as Double)
.array()
.toByteString()
}
PrimitiveType.String -> (value as String).toByteArray()
PrimitiveType.Bytes -> value as ByteArray
PrimitiveType.Date -> {
ByteBuffer.allocate(Long.SIZE_BYTES).putLong((value as Date).time).array()
Type.String -> (value as String).toByteStringUtf8()
Type.Bytes -> value as ByteString
Type.Date -> {
ByteBuffer.allocate(Long.SIZE_BYTES)
.putLong((value as Date).time)
.array()
.toByteString()
}
}
}

companion object {
private val NUMERIC_TYPES = setOf(
PrimitiveType.Integer,
PrimitiveType.Long,
PrimitiveType.Double,
Type.Integer,
Type.Long,
Type.Double,
)

fun fromBytes(type: PrimitiveType, bytes: ByteString): Any? {
operator fun invoke(
value: Any?,
createdAt: TimeTicket,
_movedAt: TimeTicket? = null,
_removedAt: TimeTicket? = null,
) = CrdtPrimitive(value.sanitized(), createdAt, _movedAt, _removedAt)

private fun Any?.sanitized(): Any? = when (this) {
is Boolean -> this
is Byte -> toInt()
is Short -> toInt()
is Int -> this
is Long -> this
is Number -> toDouble()
is CharSequence -> toString()
is ByteArray -> toByteString()
is ByteString -> this
is Date -> this
else -> null
}

fun fromBytes(type: Type, bytes: ByteString): Any? {
fun ByteString.asByteBuffer() = ByteBuffer.wrap(toByteArray())

return when (type) {
PrimitiveType.Null -> null
PrimitiveType.Boolean -> bytes.first().toInt() == 1
PrimitiveType.Integer -> bytes.asByteBuffer().int
PrimitiveType.Long -> bytes.asByteBuffer().long
PrimitiveType.Double -> bytes.asByteBuffer().double
PrimitiveType.String -> bytes.toStringUtf8()
PrimitiveType.Bytes -> bytes.toByteArray()
PrimitiveType.Date -> Date(bytes.asByteBuffer().long)
Type.Null -> null
Type.Boolean -> bytes.first().toInt() == 1
Type.Integer -> bytes.asByteBuffer().int
Type.Long -> bytes.asByteBuffer().long
Type.Double -> bytes.asByteBuffer().double
Type.String -> bytes.toStringUtf8()
Type.Bytes -> bytes
Type.Date -> Date(bytes.asByteBuffer().long)
}
}
}
}

/**
* Primitive is a CRDT element that represents a primitive value.
*/
internal enum class PrimitiveType {
Null, Boolean, Integer, Long, Double, String, Bytes, Date
/**
* Primitive is a CRDT element that represents a primitive value.
*/
internal enum class Type {
Null, Boolean, Integer, Long, Double, String, Bytes, Date
}
}
4 changes: 2 additions & 2 deletions yorkie/src/main/kotlin/dev/yorkie/document/crdt/CrdtRoot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ internal class CrdtRoot(val rootObject: CrdtObject) {
/**
* Returns length of nodes which can be garbage collected.
*/
fun getGarbageLen(): Int {
fun getGarbageLength(): Int {
var count = 0
removedElementSetByCreatedAt.forEach { createdAt ->
count++
Expand All @@ -110,7 +110,7 @@ internal class CrdtRoot(val rootObject: CrdtObject) {
textWithGarbageSetByCreatedAt.forEach { createdAt ->
val pair = elementPairMapByCreatedAt[createdAt] ?: return@forEach
val text = pair.element as CrdtTextElement
count += text.getRemovedNodesLen()
count += text.getRemovedNodesLength()
}

return count
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import dev.yorkie.document.time.TimeTicket
*/
internal abstract class CrdtTextElement : CrdtElement() {

abstract fun getRemovedNodesLen(): Int
abstract fun getRemovedNodesLength(): Int

abstract fun deleteTextNodesWithGarbage(executedAt: TimeTicket): Int
}
Loading

0 comments on commit bc4581c

Please sign in to comment.