Add CRDTArray (#12)

This commit is contained in:
원형식 2022-09-30 15:12:57 +09:00 committed by GitHub
parent bef7ac59f2
commit 8970794cb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 402 additions and 61 deletions

View File

@ -16,6 +16,7 @@ opt_in_rules:
disabled_rules:
- line_length
- file_length
- opening_brace
# Rewrited rules
cyclomatic_complexity:
warning: 25

View File

@ -0,0 +1,218 @@
/*
* Copyright 2022 The Yorkie Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License")
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
/**
* `CRDTArray` represents Array data type containing logical clocks.
*
*/
class CRDTArray: CRDTContainer {
private var elements: RGATreeList
init(createdAt: TimeTicket, elements: RGATreeList = RGATreeList()) {
self.elements = elements
super.init(createdAt: createdAt)
}
/**
* `subPath` returns subPath of JSONPath of the given `createdAt` element.
*/
override func subPath(createdAt: TimeTicket) throws -> String {
return try self.elements.subPath(createdAt: createdAt)
}
/**
* `purge` physically purge child element.
*/
override func purge(element: CRDTElement) throws {
try self.elements.purge(element)
}
/**
* `insert` inserts the given element after the given previous element.
*/
func insert(value: CRDTElement, afterCreatedAt: TimeTicket) throws {
try self.elements.insert(value, afterCreatedAt: afterCreatedAt)
}
/**
* `move` moves the given `createdAt` element after the `prevCreatedAt`.
*/
func move(createdAt: TimeTicket, afterCreatedAt: TimeTicket, executedAt: TimeTicket) throws {
try self.elements.move(createdAt: createdAt, afterCreatedAt: afterCreatedAt, executedAt: executedAt)
}
/**
* `get` returns the element of the given createAt.
*/
func get(createdAt: TimeTicket) throws -> CRDTElement {
guard let node = try? self.elements.get(createdAt: createdAt), node.isRemoved() == false else {
let log = "can't find the given node: \(createdAt)"
Logger.fatal(log)
throw YorkieError.unexpected(message: log)
}
return node
}
/**
* `get` returns the element of the given index.
*/
func get(index: Int) throws -> CRDTElement {
let node = try self.elements.getNode(index: index)
return node.getValue()
}
/**
* `getHead` returns dummy head element.
*/
func getHead() -> CRDTElement {
return self.elements.getHead()
}
/**
* `getLast` returns last element.
*/
func getLast() -> CRDTElement {
return self.elements.getLast()
}
/**
* `getPreviousCreatedAt` returns the creation time of
* the previous element of the given element.
*/
func getPreviousCreatedAt(createdAt: TimeTicket) throws -> TimeTicket {
return try self.elements.getPreviousCreatedAt(ofCreatedAt: createdAt)
}
/**
* `remove` removes the element of the given index.
*/
@discardableResult
func remove(createdAt: TimeTicket, editedAt: TimeTicket) throws -> CRDTElement {
return try self.elements.remove(createdAt: createdAt, editedAt: editedAt)
}
/**
* `remove` removes the element of given index and editedAt.
*/
func remove(index: Int, editedAt: TimeTicket) throws -> CRDTElement {
return try self.elements.remove(index: index, editedAt: editedAt)
}
/**
* `getLastCreatedAt` get last created element.
*/
func getLastCreatedAt() -> TimeTicket {
return self.elements.getLastCreatedAt()
}
/**
* `length` returns length of this elements.
*/
var length: Int {
return self.elements.length
}
/**
* `getDescendants` traverse the descendants of this array.
*/
override func getDescendants(callback: (_ element: CRDTElement, _ parent: CRDTContainer) -> Bool) {
for node in self.elements {
let element = node.getValue()
if callback(element, self) {
return
}
if let element = element as? CRDTContainer {
element.getDescendants(callback: callback)
}
}
}
/**
* `toJSON` returns the JSON encoding of this array.
*/
override func toJSON() -> String {
let json = self.elements.map { $0.getValue().toJSON() }
return "[\(json.joined(separator: ","))]"
}
/**
* `toSortedJSON` returns the sorted JSON encoding of this array.
*/
override func toSortedJSON() -> String {
return self.toJSON()
}
/**
* `getElements` returns an array of elements contained in this RGATreeList.
*/
func getElements() -> RGATreeList {
return self.elements
}
/**
* `deepcopy` copies itself deeply.
*/
override func deepcopy() -> CRDTArray {
let result = CRDTArray(createdAt: self.getCreatedAt())
for node in self.elements {
try? result.elements.insert(node.getValue().deepcopy(), afterCreatedAt: result.getLastCreatedAt())
}
result.remove(self.getRemovedAt())
return result
}
}
extension CRDTArray: Sequence {
typealias Element = CRDTElement
func makeIterator() -> CRDTArrayIterator {
return CRDTArrayIterator(self.elements.makeIterator().next())
}
}
class CRDTArrayIterator: IteratorProtocol {
private weak var iteratorNext: RGATreeListNode?
init(_ firstNode: RGATreeListNode?) {
self.iteratorNext = firstNode
}
func next() -> CRDTElement? {
defer {
self.iteratorNext = self.iteratorNext?.getNext()
}
repeat {
guard self.iteratorNext != nil else {
break
}
if let result = self.iteratorNext, result.isRemoved() == false {
return result.getValue()
}
self.iteratorNext = self.iteratorNext?.getNext()
} while true
return nil
}
}

View File

@ -125,13 +125,19 @@ class CRDTElement {
}
}
extension CRDTElement: Equatable {
static func == (lhs: CRDTElement, rhs: CRDTElement) -> Bool {
return lhs.createdAt == rhs.createdAt && lhs.movedAt == rhs.movedAt && lhs.removedAt == rhs.removedAt
}
}
/**
*
* `CRDTContainer` represents CRDTArray or CRDtObject.
* @internal
*/
class CRDTContainer: CRDTElement {
func subPath(createdAt: TimeTicket) -> String? {
func subPath(createdAt: TimeTicket) throws -> String {
fatalError("Must be implemented.")
}

View File

@ -50,7 +50,7 @@ class Primitive: CRDTElement {
case .null:
return "null"
case .boolean(let value):
return "\(value)"
return "\"\(value)\""
case .integer(let value):
return "\(value)"
case .double(let value):

View File

@ -115,7 +115,7 @@ class RGATreeListNode: SplayNode<CRDTElement> {
/**
* `RGATreeList` is replicated growable array.
*/
class RGATreeList: Sequence, IteratorProtocol {
class RGATreeList {
private let dummyHead: RGATreeListNode
private var last: RGATreeListNode
private var nodeMapByIndex: SplayTree<CRDTElement>
@ -331,9 +331,9 @@ class RGATreeList: Sequence, IteratorProtocol {
}
/**
* `delete` deletes the node of the given creation time.
* `remove` removes the node of the given creation time.
*/
func delete(createdAt: TimeTicket, editedAt: TimeTicket) throws -> CRDTElement {
func remove(createdAt: TimeTicket, editedAt: TimeTicket) throws -> CRDTElement {
guard let node = self.nodeMapByCreatedAt[createdAt] else {
let log = "can't find the given node: \(createdAt)"
Logger.fatal(log)
@ -348,9 +348,9 @@ class RGATreeList: Sequence, IteratorProtocol {
}
/**
* `delete` deletes the node of the given index.
* `remove` removes the node of the given index.
*/
func delete(index: Int, editedAt: TimeTicket) throws -> CRDTElement? {
func remove(index: Int, editedAt: TimeTicket) throws -> CRDTElement {
let node = try self.getNode(index: index)
if node.remove(editedAt) {
@ -398,15 +398,21 @@ class RGATreeList: Sequence, IteratorProtocol {
return result.joined(separator: "-")
}
}
// MARK: - Iterator
extension RGATreeList: Sequence {
typealias Element = RGATreeListNode
func makeIterator() -> RGATreeListIterator {
return RGATreeListIterator(self.dummyHead.getNext())
}
}
class RGATreeListIterator: IteratorProtocol {
private weak var iteratorNext: RGATreeListNode?
func makeIterator() -> RGATreeList {
self.iteratorNext = self.dummyHead.getNext()
return self
init(_ firstNode: RGATreeListNode?) {
self.iteratorNext = firstNode
}
func next() -> RGATreeListNode? {

View File

@ -20,7 +20,7 @@ import Foundation
* `TimeTicket` is a timestamp of the logical clock. Ticket is immutable.
* It is created by `ChangeID`.
*/
struct TimeTicket {
struct TimeTicket: Comparable {
private enum InitialValue {
static let initialDelimiter: UInt32 = 0
static let maxDelemiter: UInt32 = .max
@ -100,42 +100,19 @@ struct TimeTicket {
* `after` returns whether the given ticket was created later.
*/
func after(_ other: TimeTicket) -> Bool {
return self.compare(other) == .orderedDescending
return self > other
}
/**
* `equals` returns whether the given ticket was created.
*/
func equals(other: TimeTicket) -> Bool {
return self.compare(other) == .orderedSame
}
/**
* `compare` returns an integer comparing two Ticket.
* The result will be 0 if id==other, -1 if `id < other`, and +1 if `id > other`.
* If the receiver or argument is nil, it would panic at runtime.
*/
func compare(_ other: TimeTicket) -> ComparisonResult {
if self.lamport > other.lamport {
return .orderedDescending
} else if self.lamport < other.lamport {
return .orderedAscending
static func < (lhs: TimeTicket, rhs: TimeTicket) -> Bool {
if lhs.lamport != rhs.lamport {
return lhs.lamport < rhs.lamport
}
if let actorID = actorID, let otherActorID = other.actorID {
let compare = actorID.localizedCompare(otherActorID)
if compare != .orderedSame {
return compare
}
if let lhsActorID = lhs.actorID, let rhsActorID = rhs.actorID, lhsActorID.localizedCompare(rhsActorID) != .orderedSame {
return lhsActorID.localizedCompare(rhsActorID) == .orderedAscending
}
if self.delimiter > other.delimiter {
return .orderedDescending
} else if other.delimiter > self.delimiter {
return .orderedAscending
}
return .orderedSame
return lhs.delimiter < rhs.delimiter
}
}

View File

@ -371,14 +371,14 @@ class SplayTree<V> {
}
/**
* `deleteRange` separates the range between given 2 boundaries from this Tree.
* `removeRange` separates the range between given 2 boundaries from this Tree.
* This function separates the range to delete as a subtree
* by splaying outer boundary nodes.
* leftBoundary must exist because of 0-indexed initial dummy node of tree,
* but rightBoundary can be nil means range to delete includes the end of tree.
* Refer to the design document in https://github.com/yorkie-team/yorkie/tree/main/design
*/
func deleteRange(leftBoundary: SplayNode<V>, rightBoundary: SplayNode<V>? = nil) {
func removeRange(leftBoundary: SplayNode<V>, rightBoundary: SplayNode<V>? = nil) {
guard let rightBoundary = rightBoundary else {
self.splayNode(leftBoundary)
self.cutOffRight(root: leftBoundary)

View File

@ -0,0 +1,128 @@
/*
* Copyright 2022 The Yorkie Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import XCTest
@testable import Yorkie
class CRDTArrayTests: XCTestCase {
private let actorId = "actor-1"
func test_get_by_createdAt_returns_a_non_removed_value() throws {
let time = TimeTicket(lamport: 1, delimiter: 999, actorID: actorId)
let target = CRDTArray(createdAt: time)
let e1 = Primitive(value: .string("11"), createdAt: TimeTicket(lamport: 1, delimiter: 0, actorID: actorId))
try target.insert(value: e1, afterCreatedAt: TimeTicket.initialTimeTicket)
let e2 = Primitive(value: .string("22"), createdAt: TimeTicket(lamport: 2, delimiter: 0, actorID: actorId))
try target.insert(value: e2, afterCreatedAt: e1.getCreatedAt())
let e3 = Primitive(value: .string("33"), createdAt: TimeTicket(lamport: 3, delimiter: 0, actorID: actorId))
try target.insert(value: e3, afterCreatedAt: e2.getCreatedAt())
XCTAssertEqual(target.toJSON(), "[\"11\",\"22\",\"33\"]")
let resultE2 = try target.get(createdAt: e2.getCreatedAt())
XCTAssertNotNil(resultE2)
let deletedTime = TimeTicket(lamport: 4, delimiter: 0, actorID: actorId)
try target.remove(createdAt: e2.getCreatedAt(), editedAt: deletedTime)
let resultRemovedE2 = try? target.get(createdAt: e2.getCreatedAt())
XCTAssertNil(resultRemovedE2)
}
func test_getDescendants_traverse_all_descendants() throws {
let time = TimeTicket(lamport: 1, delimiter: 999, actorID: actorId)
let target = CRDTArray(createdAt: time)
let e1 = Primitive(value: .string("11"), createdAt: TimeTicket(lamport: 1, delimiter: 0, actorID: actorId))
try target.insert(value: e1, afterCreatedAt: TimeTicket.initialTimeTicket)
let e2 = Primitive(value: .string("22"), createdAt: TimeTicket(lamport: 2, delimiter: 0, actorID: actorId))
try target.insert(value: e2, afterCreatedAt: e1.getCreatedAt())
let e3 = Primitive(value: .string("33"), createdAt: TimeTicket(lamport: 3, delimiter: 0, actorID: actorId))
try target.insert(value: e3, afterCreatedAt: e2.getCreatedAt())
var elemetJsons: [String] = []
target.getDescendants { element, _ in
elemetJsons.append(element.toJSON())
return false
}
XCTAssertEqual(elemetJsons.joined(separator: ", "), "\"11\", \"22\", \"33\"")
}
func test_getDescendants_traverse_one_descendant() throws {
let time = TimeTicket(lamport: 1, delimiter: 999, actorID: actorId)
let target = CRDTArray(createdAt: time)
let e1 = Primitive(value: .string("11"), createdAt: TimeTicket(lamport: 1, delimiter: 0, actorID: actorId))
try target.insert(value: e1, afterCreatedAt: TimeTicket.initialTimeTicket)
let e2 = Primitive(value: .string("22"), createdAt: TimeTicket(lamport: 2, delimiter: 0, actorID: actorId))
try target.insert(value: e2, afterCreatedAt: e1.getCreatedAt())
let e3 = Primitive(value: .string("33"), createdAt: TimeTicket(lamport: 3, delimiter: 0, actorID: actorId))
try target.insert(value: e3, afterCreatedAt: e2.getCreatedAt())
var elemetJsons: [String] = []
target.getDescendants { element, _ in
elemetJsons.append(element.toJSON())
return true
}
XCTAssertEqual(elemetJsons.joined(separator: ", "), "\"11\"")
}
func test_toJSON() throws {
let time = TimeTicket(lamport: 1, delimiter: 999, actorID: actorId)
let target = CRDTArray(createdAt: time)
let e1 = Primitive(value: .string("11"), createdAt: TimeTicket(lamport: 1, delimiter: 0, actorID: actorId))
try target.insert(value: e1, afterCreatedAt: TimeTicket.initialTimeTicket)
let e2 = Primitive(value: .string("22"), createdAt: TimeTicket(lamport: 2, delimiter: 0, actorID: actorId))
try target.insert(value: e2, afterCreatedAt: e1.getCreatedAt())
let e3 = Primitive(value: .string("33"), createdAt: TimeTicket(lamport: 3, delimiter: 0, actorID: actorId))
try target.insert(value: e3, afterCreatedAt: e2.getCreatedAt())
XCTAssertEqual(target.toJSON(), "[\"11\",\"22\",\"33\"]")
}
func test_iterator() throws {
let time = TimeTicket(lamport: 1, delimiter: 999, actorID: actorId)
let target = CRDTArray(createdAt: time)
let e1 = Primitive(value: .string("11"), createdAt: TimeTicket(lamport: 1, delimiter: 0, actorID: actorId))
try target.insert(value: e1, afterCreatedAt: TimeTicket.initialTimeTicket)
let e2 = Primitive(value: .string("22"), createdAt: TimeTicket(lamport: 2, delimiter: 0, actorID: actorId))
try target.insert(value: e2, afterCreatedAt: e1.getCreatedAt())
let e3 = Primitive(value: .string("33"), createdAt: TimeTicket(lamport: 3, delimiter: 0, actorID: actorId))
try target.insert(value: e3, afterCreatedAt: e2.getCreatedAt())
var elemetJsons: [String] = []
for each in target {
elemetJsons.append(each.toJSON())
}
XCTAssertEqual(elemetJsons.joined(separator: ", "), "\"11\", \"22\", \"33\"")
}
}

View File

@ -26,7 +26,7 @@ class CRDTElementTests: XCTestCase {
let movedResult = target.setMovedAt(small)
XCTAssertEqual(movedResult, true)
XCTAssertEqual(target.getMovedAt()?.compare(small), .orderedSame)
XCTAssertTrue(target.getMovedAt() == small)
}
func test_can_set_bigger_movedAt_when_movedAt_is_nil() {
@ -37,7 +37,7 @@ class CRDTElementTests: XCTestCase {
let movedResult = target.setMovedAt(big)
XCTAssertEqual(movedResult, true)
XCTAssertEqual(target.getMovedAt()?.compare(big), .orderedSame)
XCTAssertTrue(target.getMovedAt() == big)
}
func test_can_not_set_bigger_movedAt_when_movedAt_is_non_nil() {
@ -51,7 +51,8 @@ class CRDTElementTests: XCTestCase {
let movedResult = target.setMovedAt(timeTicket)
XCTAssertEqual(movedResult, false)
XCTAssertEqual(target.getMovedAt()?.compare(small), .orderedDescending)
XCTAssertTrue(target.getMovedAt()! > small)
}
func test_can_not_remove_when_nil() {

View File

@ -265,7 +265,7 @@ class RGATreeListTests: XCTestCase {
XCTAssertEqual(target.getStructureAsString(),
"[1:999:0:\"A1\"]-[2:999:0:\"B12\"]-[3:999:0:\"C123\"]")
let result = try target.delete(createdAt: e2.getCreatedAt(), editedAt: TimeTicket(lamport: 4, delimiter: 0, actorID: self.actorId))
let result = try target.remove(createdAt: e2.getCreatedAt(), editedAt: TimeTicket(lamport: 4, delimiter: 0, actorID: self.actorId))
XCTAssertEqual(result.isRemoved(), true)
XCTAssertEqual(target.getStructureAsString(),
@ -286,9 +286,9 @@ class RGATreeListTests: XCTestCase {
XCTAssertEqual(target.getStructureAsString(),
"[1:999:0:\"A1\"]-[2:999:0:\"B12\"]-[3:999:0:\"C123\"]")
let result = try target.delete(index: 1, editedAt: TimeTicket(lamport: 4, delimiter: 0, actorID: self.actorId))
let result = try target.remove(index: 1, editedAt: TimeTicket(lamport: 4, delimiter: 0, actorID: self.actorId))
XCTAssertEqual(result?.isRemoved(), true)
XCTAssertEqual(result.isRemoved(), true)
XCTAssertEqual(target.getStructureAsString(),
"[1:999:0:\"A1\"]-{2:999:0:\"B12\"}-[3:999:0:\"C123\"]")
}

View File

@ -22,20 +22,20 @@ class TimeTicketTests: XCTestCase {
let small = TimeTicket.initialTimeTicket
let big = TimeTicket.maxTimeTicket
XCTAssertEqual(small.compare(big), .orderedAscending)
XCTAssertTrue(small < big)
}
func test_compare_with_a_small_thing() {
let big = TimeTicket.maxTimeTicket
let small = TimeTicket.initialTimeTicket
XCTAssertEqual(big.compare(small), .orderedDescending)
XCTAssertTrue(big > small)
}
func test_compare_with_a_same_thing() {
let big = TimeTicket.maxTimeTicket
let big2 = TimeTicket.maxTimeTicket
XCTAssertEqual(big.compare(big2), .orderedSame)
XCTAssertTrue(big == big2)
}
}

View File

@ -144,7 +144,7 @@ class SplayTreeTests: XCTestCase {
XCTAssertEqual("[1,1]A[3,2]BB[6,3]CCC[10,4]DDDD[15,5]EEEEE[19,4]FFFF[22,3]GGG[24,2]HH[25,1]I", testTree.tree.getStructureAsString())
self.removeNodes(testTree.nodes, from: 7, to: 8)
XCTAssertEqual("[1,1]A[3,2]BB[6,3]CCC[10,4]DDDD[15,5]EEEEE[19,4]FFFF[22,3]GGG[24,0]HH[25,0]I", testTree.tree.getStructureAsString())
testTree.tree.deleteRange(leftBoundary: testTree.nodes[6])
testTree.tree.removeRange(leftBoundary: testTree.nodes[6])
XCTAssertEqual(testTree.tree.indexOf(testTree.nodes[6]), 19)
XCTAssertEqual("[1,1]A[3,2]BB[6,3]CCC[10,4]DDDD[15,5]EEEEE[19,4]FFFF[22,3]GGG[0,0]HH[0,0]I", testTree.tree.getStructureAsString())
XCTAssertTrue(testTree.nodes[6] === testTree.tree.getRoot())
@ -156,7 +156,7 @@ class SplayTreeTests: XCTestCase {
let testTree = self.sampleTree
// check the case 1 of rangeDelete
self.removeNodes(testTree.nodes, from: 3, to: 6)
testTree.tree.deleteRange(leftBoundary: testTree.nodes[2], rightBoundary: testTree.nodes[7])
testTree.tree.removeRange(leftBoundary: testTree.nodes[2], rightBoundary: testTree.nodes[7])
XCTAssertTrue(testTree.nodes[7] === testTree.tree.getRoot())
XCTAssertTrue(testTree.nodes[2] === testTree.tree.getRoot()?.getLeft())
XCTAssertEqual(testTree.nodes[7].getWeight(), 9)
@ -170,7 +170,7 @@ class SplayTreeTests: XCTestCase {
testTree.tree.splayNode(testTree.nodes[2])
// check the case 2 of rangeDelete
self.removeNodes(testTree.nodes, from: 3, to: 7)
testTree.tree.deleteRange(leftBoundary: testTree.nodes[2], rightBoundary: testTree.nodes[8])
testTree.tree.removeRange(leftBoundary: testTree.nodes[2], rightBoundary: testTree.nodes[8])
XCTAssertTrue(testTree.nodes[8] === testTree.tree.getRoot())
XCTAssertTrue(testTree.nodes[2] === testTree.tree.getRoot()?.getLeft())
XCTAssertEqual(testTree.nodes[8].getWeight(), 7)

View File

@ -27,7 +27,8 @@
CE7B997828E178EF00D56198 /* PrimitiveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B997128E1750000D56198 /* PrimitiveTests.swift */; };
CE7B997A28E2820200D56198 /* RGATreeList.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B997928E2820200D56198 /* RGATreeList.swift */; };
CE7B997C28E2DB5000D56198 /* RGATreeListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B997B28E2DB5000D56198 /* RGATreeListTests.swift */; };
CE7B997E28E2FD7000D56198 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B997D28E2FD7000D56198 /* Array.swift */; };
CE7B997E28E2FD7000D56198 /* CRDTArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B997D28E2FD7000D56198 /* CRDTArray.swift */; };
CE7B998628E520D600D56198 /* CRDTArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B998528E520D600D56198 /* CRDTArrayTests.swift */; };
CE8C22EF28C9E85900432DE5 /* Change.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22EE28C9E85900432DE5 /* Change.swift */; };
CE8C22F328C9E87800432DE5 /* Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22F228C9E87800432DE5 /* Object.swift */; };
CE8C22F528C9E88500432DE5 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22F428C9E88500432DE5 /* Operation.swift */; };
@ -78,7 +79,8 @@
CE7B997528E1773A00D56198 /* GRPCTypeAlias.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GRPCTypeAlias.swift; sourceTree = "<group>"; };
CE7B997928E2820200D56198 /* RGATreeList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RGATreeList.swift; sourceTree = "<group>"; };
CE7B997B28E2DB5000D56198 /* RGATreeListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RGATreeListTests.swift; sourceTree = "<group>"; };
CE7B997D28E2FD7000D56198 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = "<group>"; };
CE7B997D28E2FD7000D56198 /* CRDTArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRDTArray.swift; sourceTree = "<group>"; };
CE7B998528E520D600D56198 /* CRDTArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRDTArrayTests.swift; sourceTree = "<group>"; };
CE8C22EE28C9E85900432DE5 /* Change.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Change.swift; sourceTree = "<group>"; };
CE8C22F228C9E87800432DE5 /* Object.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Object.swift; sourceTree = "<group>"; };
CE8C22F428C9E88500432DE5 /* Operation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = "<group>"; };
@ -194,6 +196,7 @@
CE3EC97128D4042D009471BC /* CRDTElementTests.swift */,
CE7B997128E1750000D56198 /* PrimitiveTests.swift */,
CE7B997B28E2DB5000D56198 /* RGATreeListTests.swift */,
CE7B998528E520D600D56198 /* CRDTArrayTests.swift */,
);
path = CRDT;
sourceTree = "<group>";
@ -277,7 +280,7 @@
CE3EC96628D30E74009471BC /* CRDTElement.swift */,
CE3EC97428D41903009471BC /* Primitive.swift */,
CE7B997928E2820200D56198 /* RGATreeList.swift */,
CE7B997D28E2FD7000D56198 /* Array.swift */,
CE7B997D28E2FD7000D56198 /* CRDTArray.swift */,
);
path = CRDT;
sourceTree = "<group>";
@ -519,7 +522,7 @@
CE8C22F928C9E8CA00432DE5 /* Heap.swift in Sources */,
CE8C22EF28C9E85900432DE5 /* Change.swift in Sources */,
CEEB17E328C84D26004988DD /* yorkie.pb.swift in Sources */,
CE7B997E28E2FD7000D56198 /* Array.swift in Sources */,
CE7B997E28E2FD7000D56198 /* CRDTArray.swift in Sources */,
CE3EC94F28D1922E009471BC /* RedBlackTree.swift in Sources */,
CE7B997028E1453E00D56198 /* Strings.swift in Sources */,
CE8C230528C9F1BD00432DE5 /* Client.swift in Sources */,
@ -545,6 +548,7 @@
CE7B997C28E2DB5000D56198 /* RGATreeListTests.swift in Sources */,
CE3EC97328D40498009471BC /* CRDTElementTests.swift in Sources */,
CE3EC96E28D3FFF0009471BC /* TimeTicketTests.swift in Sources */,
CE7B998628E520D600D56198 /* CRDTArrayTests.swift in Sources */,
96DA809128C5B7B400E2C1DA /* GRPCTests.swift in Sources */,
CE3EC94D28D189EF009471BC /* HeapTests.swift in Sources */,
CE3EC95228D195E4009471BC /* RedBlackTreeTests.swift in Sources */,