Implement basic types for CRDT data structure (#10)
This commit implements primitives for creating CRDT datatypes such as TimeTicket, ActorID, CRDTElement, and so on. We found a problem with the JS SDK using lamport as Uint64 and this commit fixed it with Int64.
This commit is contained in:
parent
f51c46f2f3
commit
86446ee7ac
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
enum Converter {
|
||||
|
||||
/**
|
||||
* parses the given bytes into value.
|
||||
*/
|
||||
static func valueFrom(valueType: ValueType, data: Data) throws -> PrimitiveValue {
|
||||
switch valueType {
|
||||
case .null:
|
||||
return .null
|
||||
case .boolean:
|
||||
return .boolean(data[0] == 1)
|
||||
case .integer:
|
||||
let result = data.withUnsafeBytes { $0.load(as: Int32.self) }
|
||||
return .integer(result)
|
||||
case .double:
|
||||
let result = data.withUnsafeBytes { $0.load(as: Double.self) }
|
||||
return .double(result)
|
||||
case .string:
|
||||
return .string(String(decoding: data, as: UTF8.self))
|
||||
case .long:
|
||||
let result = data.withUnsafeBytes { $0.load(as: Int64.self) }
|
||||
return .long(result)
|
||||
case .bytes:
|
||||
return .bytes(data)
|
||||
case .date:
|
||||
let milliseconds = data.withUnsafeBytes { $0.load(as: Double.self) }
|
||||
return .date(Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000))
|
||||
default:
|
||||
throw YorkieError.unimplemented(message: String(describing: valueType))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Copyright 2022 The Yorkie Authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* 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
|
||||
*
|
||||
|
@ -14,4 +14,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Use a Swift typealias to remap the type names of Protobuf locally
|
||||
// Swift Protobuf Guide: https://github.com/apple/swift-protobuf/blob/main/Documentation/API.md#generated-struct-name
|
||||
import Foundation
|
||||
|
||||
typealias ValueType = Yorkie_V1_ValueType
|
||||
typealias YorkieServiceNIOClient = Yorkie_V1_YorkieServiceNIOClient
|
||||
typealias ActivateClientRequest = Yorkie_V1_ActivateClientRequest
|
||||
typealias DeactivateClientRequest = Yorkie_V1_DeactivateClientRequest
|
||||
typealias YorkieServiceAsyncClient = Yorkie_V1_YorkieServiceAsyncClient
|
||||
|
|
@ -21,6 +21,7 @@ enum LogLevel: String {
|
|||
case info = "INFO"
|
||||
case warn = "WARN"
|
||||
case error = "ERROR"
|
||||
case fatal = "FATAL"
|
||||
}
|
||||
|
||||
enum Logger {
|
||||
|
@ -40,6 +41,10 @@ enum Logger {
|
|||
self.log(level: .error, message, error: error, filename: filename, function: function, line: line)
|
||||
}
|
||||
|
||||
static func fatal(_ message: String, error: Error? = nil, filename: String = #file, function: String = #function, line: Int = #line) {
|
||||
self.log(level: .fatal, message, error: error, filename: filename, function: function, line: line)
|
||||
}
|
||||
|
||||
static func log(level: LogLevel, _ message: String, error: Error? = nil, filename: String = #file, function: String = #function, line: Int = #line) {
|
||||
let file = URL(fileURLWithPath: filename).lastPathComponent
|
||||
let log = message + (error?.localizedDescription ?? "")
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* `CRDTElement` represents element type containing logical clock.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class CRDTElement {
|
||||
private var createdAt: TimeTicket
|
||||
private var movedAt: TimeTicket?
|
||||
private var removedAt: TimeTicket?
|
||||
|
||||
init(createdAt: TimeTicket) {
|
||||
self.createdAt = createdAt
|
||||
}
|
||||
|
||||
/**
|
||||
* `getCreatedAt` returns the creation time of this element.
|
||||
*/
|
||||
func getCreatedAt() -> TimeTicket {
|
||||
return self.createdAt
|
||||
}
|
||||
|
||||
/**
|
||||
* `getID` returns the creation time of this element.
|
||||
*/
|
||||
func getID() -> TimeTicket {
|
||||
return self.createdAt
|
||||
}
|
||||
|
||||
/**
|
||||
* `getMovedAt` returns the move time of this element.
|
||||
*/
|
||||
func getMovedAt() -> TimeTicket? {
|
||||
return self.movedAt
|
||||
}
|
||||
|
||||
/**
|
||||
* `getRemovedAt` returns the removal time of this element.
|
||||
*/
|
||||
func getRemovedAt() -> TimeTicket? {
|
||||
return self.removedAt
|
||||
}
|
||||
|
||||
/**
|
||||
* `setMovedAt` sets the move time of this element.
|
||||
*/
|
||||
@discardableResult
|
||||
func setMovedAt(_ movedAt: TimeTicket?) -> Bool {
|
||||
guard let currentMoveAt = self.movedAt else {
|
||||
self.movedAt = movedAt
|
||||
return true
|
||||
}
|
||||
|
||||
if let movedAt = movedAt, movedAt.after(currentMoveAt) {
|
||||
self.movedAt = movedAt
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* `setRemovedAt` sets the remove time of this element.
|
||||
*/
|
||||
func setRemovedAt(_ removedAt: TimeTicket?) {
|
||||
self.removedAt = removedAt
|
||||
}
|
||||
|
||||
/**
|
||||
* `remove` removes this element.
|
||||
*/
|
||||
func remove(_ removedAt: TimeTicket?) -> Bool {
|
||||
guard let removedAt = removedAt, removedAt.after(self.createdAt) else {
|
||||
return false
|
||||
}
|
||||
|
||||
if self.removedAt == nil {
|
||||
self.removedAt = removedAt
|
||||
return true
|
||||
}
|
||||
|
||||
if let currentRemovedAt = self.removedAt, removedAt.after(currentRemovedAt) {
|
||||
self.removedAt = removedAt
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* `isRemoved` check if this element was removed.
|
||||
*/
|
||||
func isRemoved() -> Bool {
|
||||
return self.removedAt != nil
|
||||
}
|
||||
|
||||
func toJSON() -> String {
|
||||
fatalError("Must be implemented.")
|
||||
}
|
||||
|
||||
func toSortedJSON() -> String {
|
||||
fatalError("Must be implemented.")
|
||||
}
|
||||
|
||||
func deepcopy() -> CRDTElement {
|
||||
fatalError("Must be implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* `CRDTContainer` represents CRDTArray or CRDtObject.
|
||||
* @internal
|
||||
*/
|
||||
class CRDTContainer: CRDTElement {
|
||||
func keyOf(createdAt: TimeTicket) -> String? {
|
||||
fatalError("Must be implemented.")
|
||||
}
|
||||
|
||||
func purge(element: CRDTElement) {
|
||||
fatalError("Must be implemented.")
|
||||
}
|
||||
|
||||
func delete(createdAt: TimeTicket, executedAt: TimeTicket) -> CRDTElement {
|
||||
fatalError("Must be implemented.")
|
||||
}
|
||||
|
||||
func getDescendants(callback: (_ elem: CRDTElement, _ parent: CRDTContainer) -> Bool) {
|
||||
fatalError("Must be implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `CRDTTextElement` represents CRDTText or CRDTRichText.
|
||||
*/
|
||||
class CRDTTextElement: CRDTElement {
|
||||
func getRemovedNodesLen() -> Int {
|
||||
fatalError("Must be implemented.")
|
||||
}
|
||||
|
||||
func purgeTextNodesWithGarbage(ticket: TimeTicket) -> Int {
|
||||
fatalError("Must be implemented.")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
enum PrimitiveValue {
|
||||
case null
|
||||
case boolean(Bool)
|
||||
case integer(Int32)
|
||||
case long(Int64)
|
||||
case double(Double)
|
||||
case string(String)
|
||||
case bytes(Data)
|
||||
case date(Date)
|
||||
}
|
||||
|
||||
/**
|
||||
* `Primitive` represents primitive data type including logical clock.
|
||||
* It has a type and a value.
|
||||
*/
|
||||
class Primitive: CRDTElement {
|
||||
let value: PrimitiveValue
|
||||
|
||||
init(value: PrimitiveValue, createdAt: TimeTicket) {
|
||||
self.value = value
|
||||
super.init(createdAt: createdAt)
|
||||
}
|
||||
|
||||
/**
|
||||
* `toJSON` returns the JSON encoding of the value.
|
||||
*
|
||||
* TODOs: We need to consider the case where the value is
|
||||
* a byte array and a date.
|
||||
*/
|
||||
var toJSON: String {
|
||||
switch self.value {
|
||||
case .null:
|
||||
return "null"
|
||||
case .boolean(let value):
|
||||
return "\(value)"
|
||||
case .integer(let value):
|
||||
return "\(value)"
|
||||
case .double(let value):
|
||||
return "\(value)"
|
||||
case .string(let value):
|
||||
return "\(value.escaped())"
|
||||
case .long(let value):
|
||||
return "\(value)"
|
||||
case .bytes(let value):
|
||||
return "\(value)"
|
||||
case .date(let value):
|
||||
return "\(value.timeIntervalSince1970 * 1000)"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `toSortedJSON` returns the sorted JSON encoding of the value.
|
||||
*/
|
||||
var toSortedJSON: String {
|
||||
return self.toJSON
|
||||
}
|
||||
|
||||
/**
|
||||
* `deepcopy` copies itself deeply.
|
||||
*/
|
||||
var deepcopy: Primitive {
|
||||
let primitive = Primitive(value: self.value, createdAt: self.getCreatedAt())
|
||||
primitive.setMovedAt(self.getMovedAt())
|
||||
return primitive
|
||||
}
|
||||
|
||||
/**
|
||||
* `getPrimitiveType` returns the primitive type of the value.
|
||||
*/
|
||||
static func type(of value: Any?) -> PrimitiveValue? {
|
||||
guard let value = value else {
|
||||
return .null
|
||||
}
|
||||
|
||||
switch value {
|
||||
case let casted as Bool:
|
||||
return .boolean(casted)
|
||||
case let casted as Int32:
|
||||
return .integer(casted)
|
||||
case let casted as Int64:
|
||||
return .long(casted)
|
||||
case let casted as Double:
|
||||
return .double(casted)
|
||||
case let casted as String:
|
||||
return .string(casted)
|
||||
case let casted as Data:
|
||||
return .bytes(casted)
|
||||
case let casted as Date:
|
||||
return .date(casted)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `isSupport` check if the given value is supported type.
|
||||
*/
|
||||
static func isSupport(value: Any) -> Bool {
|
||||
return Primitive.type(of: value) != nil
|
||||
}
|
||||
|
||||
/**
|
||||
* `isNumericType` checks numeric type by JSONPrimitive
|
||||
*/
|
||||
var isNumericType: Bool {
|
||||
switch self.value {
|
||||
case .integer, .long, .double:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `toBytes` creates an array representing the value.
|
||||
*/
|
||||
func toBytes() throws -> Data {
|
||||
switch self.value {
|
||||
case .null:
|
||||
return Data()
|
||||
case .boolean(let value):
|
||||
var valueInInt = Int(exactly: NSNumber(value: value))
|
||||
return Data(bytes: &valueInInt, count: MemoryLayout.size(ofValue: valueInInt))
|
||||
case .integer(let value):
|
||||
return withUnsafeBytes(of: value) { Data($0) }
|
||||
case .double(let value):
|
||||
return withUnsafeBytes(of: value) { Data($0) }
|
||||
case .string(let value):
|
||||
return value.data(using: .utf8) ?? Data()
|
||||
case .long(let value):
|
||||
return withUnsafeBytes(of: value) { Data($0) }
|
||||
case .bytes(let value):
|
||||
return value
|
||||
case .date(let value):
|
||||
let milliseconds = value.timeIntervalSince1970 * 1000
|
||||
return withUnsafeBytes(of: milliseconds) { Data($0) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* `EscapeString` escapes string.
|
||||
*/
|
||||
extension String {
|
||||
static let escapeSequences = [
|
||||
(original: "\\", escaped: "\\\\"),
|
||||
(original: "\"", escaped: "\\\""),
|
||||
(original: "'", escaped: "\\'"),
|
||||
(original: "\n", escaped: "\\n"),
|
||||
(original: "\r", escaped: "\\r"),
|
||||
(original: "\t", escaped: "\\t"),
|
||||
(original: "\u{2028}", escaped: "\\u{2028}"),
|
||||
(original: "\u{2029}", escaped: "\\u{2029}")
|
||||
]
|
||||
|
||||
func escaped() -> String {
|
||||
return String.escapeSequences.reduce(self) { string, seq in
|
||||
string.replacingOccurrences(of: seq.original, with: seq.escaped)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright 2020 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
|
||||
|
||||
/**
|
||||
* `ActorID` is used to identify who is making changes to the document.
|
||||
* It is hexadecimal string and should be generated by unique value.
|
||||
*
|
||||
*/
|
||||
typealias ActorID = String
|
||||
|
||||
enum ActorIds {
|
||||
/**
|
||||
* `InitialActorID` is the initial value of ActorID.
|
||||
*/
|
||||
static let initialActorID = "000000000000000000000000"
|
||||
|
||||
/**
|
||||
* `MaxActorID` is the maximum value of ActorID.
|
||||
*/
|
||||
static let maxActorID = "FFFFFFFFFFFFFFFFFFFFFFFF"
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* `TimeTicket` is a timestamp of the logical clock. Ticket is immutable.
|
||||
* It is created by `ChangeID`.
|
||||
*/
|
||||
class TimeTicket {
|
||||
private enum InitialValue {
|
||||
static let initialDelimiter: UInt32 = 0
|
||||
static let maxDelemiter: UInt32 = .max
|
||||
static let maxLamport: Int64 = .max
|
||||
}
|
||||
|
||||
static let initialTimeTicket = TimeTicket(lamport: 0, delimiter: InitialValue.initialDelimiter, actorID: ActorIds.initialActorID)
|
||||
static let maxTimeTicket = TimeTicket(lamport: InitialValue.maxLamport, delimiter: InitialValue.maxDelemiter, actorID: ActorIds.maxActorID)
|
||||
|
||||
private var lamport: Int64
|
||||
private var delimiter: UInt32
|
||||
private var actorID: ActorID?
|
||||
|
||||
/** @hideconstructor */
|
||||
init(lamport: Int64, delimiter: UInt32, actorID: ActorID?) {
|
||||
self.lamport = lamport
|
||||
self.delimiter = delimiter
|
||||
self.actorID = actorID
|
||||
}
|
||||
|
||||
/**
|
||||
* `of` creates an instance of Ticket.
|
||||
*/
|
||||
public static func of(lamport: Int64, delimiter: UInt32, actorID: ActorID?) -> TimeTicket {
|
||||
return TimeTicket(lamport: lamport, delimiter: delimiter, actorID: actorID)
|
||||
}
|
||||
|
||||
/**
|
||||
* `toIDString` returns the lamport string for this Ticket.
|
||||
*/
|
||||
func toIDString() -> String {
|
||||
guard let actorID = self.actorID else {
|
||||
return "\(self.lamport):nil:\(self.delimiter)"
|
||||
}
|
||||
return "\(self.lamport):\(actorID):\(self.delimiter)"
|
||||
}
|
||||
|
||||
/**
|
||||
* `getStructureAsString` returns a string containing the meta data of the ticket
|
||||
* for debugging purpose.
|
||||
*/
|
||||
func getStructureAsString() -> String {
|
||||
guard let actorID = self.actorID else {
|
||||
return "\(self.lamport):nil:\(self.delimiter)"
|
||||
}
|
||||
return "\(self.lamport):\(actorID):\(self.delimiter)"
|
||||
}
|
||||
|
||||
/**
|
||||
* `setActor` creates a new instance of Ticket with the given actorID.
|
||||
*/
|
||||
func setActor(actorID: ActorID) -> TimeTicket {
|
||||
return TimeTicket(lamport: self.lamport, delimiter: self.delimiter, actorID: actorID)
|
||||
}
|
||||
|
||||
/**
|
||||
* `getLamportAsString` returns the lamport string.
|
||||
*/
|
||||
func getLamportAsString() -> String {
|
||||
return "\(self.lamport)"
|
||||
}
|
||||
|
||||
/**
|
||||
* `getDelimiter` returns delimiter.
|
||||
*/
|
||||
func getDelimiter() -> UInt32 {
|
||||
return self.delimiter
|
||||
}
|
||||
|
||||
/**
|
||||
* `getActorID` returns actorID.
|
||||
*/
|
||||
func getActorID() -> String? {
|
||||
return self.actorID
|
||||
}
|
||||
|
||||
/**
|
||||
* `after` returns whether the given ticket was created later.
|
||||
*/
|
||||
func after(_ other: TimeTicket) -> Bool {
|
||||
return self.compare(other) == .orderedDescending
|
||||
}
|
||||
|
||||
/**
|
||||
* `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
|
||||
}
|
||||
|
||||
if let actorID = actorID, let otherActorID = other.actorID {
|
||||
let compare = actorID.localizedCompare(otherActorID)
|
||||
if compare != .orderedSame {
|
||||
return compare
|
||||
}
|
||||
}
|
||||
|
||||
if self.delimiter > other.delimiter {
|
||||
return .orderedDescending
|
||||
} else if other.delimiter > self.delimiter {
|
||||
return .orderedAscending
|
||||
}
|
||||
|
||||
return .orderedSame
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Copyright 2022 The Yorkie Authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* 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
|
||||
*
|
||||
|
@ -15,3 +15,8 @@
|
|||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
enum YorkieError: Error {
|
||||
case unexpected(message: String)
|
||||
case unimplemented(message: String)
|
||||
}
|
|
@ -33,8 +33,7 @@ class SplayNode<V> {
|
|||
}
|
||||
|
||||
func getLength() -> Int {
|
||||
assertionFailure("Must be implemented.")
|
||||
return 0
|
||||
fatalError("Must be implemented.")
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -248,6 +247,7 @@ class SplayTree<V> {
|
|||
/**
|
||||
* `insertAfter` inserts the node after the given previous node.
|
||||
*/
|
||||
@discardableResult
|
||||
func insertAfter(_ target: SplayNode<V>?, newNode: SplayNode<V>) -> SplayNode<V> {
|
||||
guard let target = target else {
|
||||
self.root = newNode
|
||||
|
@ -404,10 +404,10 @@ class SplayTree<V> {
|
|||
}
|
||||
|
||||
/**
|
||||
* `getAnnotatedString` returns a string containing the meta data of the Node
|
||||
* `getStructureAsString` returns a string containing the meta data of the Node
|
||||
* for debugging purpose.
|
||||
*/
|
||||
func getAnnotatedString() -> String {
|
||||
func getStructureAsString() -> String {
|
||||
var metaString: [SplayNode<V>] = []
|
||||
self.traverseInorder(self.root!, stack: &metaString)
|
||||
return metaString
|
||||
|
|
|
@ -35,8 +35,8 @@ class GRPCTests: XCTestCase {
|
|||
return
|
||||
}
|
||||
|
||||
let client = Yorkie_V1_YorkieServiceNIOClient(channel: channel)
|
||||
var activateRequest = Yorkie_V1_ActivateClientRequest()
|
||||
let client = YorkieServiceNIOClient(channel: channel)
|
||||
var activateRequest = ActivateClientRequest()
|
||||
activateRequest.clientKey = testClientKey
|
||||
let activateResponse = try? client.activateClient(activateRequest, callOptions: nil).response.wait()
|
||||
guard let activateResponse = activateResponse else {
|
||||
|
@ -46,7 +46,7 @@ class GRPCTests: XCTestCase {
|
|||
|
||||
XCTAssertEqual(activateResponse.clientKey, testClientKey)
|
||||
|
||||
var deactivateRequest = Yorkie_V1_DeactivateClientRequest()
|
||||
var deactivateRequest = DeactivateClientRequest()
|
||||
deactivateRequest.clientID = activateResponse.clientID
|
||||
guard let deactivatedResponse = try? client.deactivateClient(deactivateRequest).response.wait() else {
|
||||
XCTFail("The response of deactivate is nil.")
|
||||
|
@ -71,8 +71,8 @@ class GRPCTests: XCTestCase {
|
|||
return
|
||||
}
|
||||
|
||||
let client = Yorkie_V1_YorkieServiceAsyncClient(channel: channel)
|
||||
var activateRequest = Yorkie_V1_ActivateClientRequest()
|
||||
let client = YorkieServiceAsyncClient(channel: channel)
|
||||
var activateRequest = ActivateClientRequest()
|
||||
activateRequest.clientKey = testClientKey
|
||||
let activateResponse = try? await client.activateClient(activateRequest, callOptions: nil)
|
||||
guard let activateResponse = activateResponse else {
|
||||
|
@ -82,7 +82,7 @@ class GRPCTests: XCTestCase {
|
|||
|
||||
XCTAssertEqual(activateResponse.clientKey, testClientKey)
|
||||
|
||||
var deactivateRequest = Yorkie_V1_DeactivateClientRequest()
|
||||
var deactivateRequest = DeactivateClientRequest()
|
||||
deactivateRequest.clientID = activateResponse.clientID
|
||||
guard let deactivatedResponse = try? await client.deactivateClient(deactivateRequest) else {
|
||||
XCTFail("The response of deactivate is nil.")
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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 CRDTElementTests: XCTestCase {
|
||||
func test_can_set_smaller_movedAt() {
|
||||
let small = TimeTicket.initialTimeTicket
|
||||
let big = TimeTicket.maxTimeTicket
|
||||
|
||||
let target = CRDTElement(createdAt: big)
|
||||
let movedResult = target.setMovedAt(small)
|
||||
|
||||
XCTAssertEqual(movedResult, true)
|
||||
XCTAssertEqual(target.getMovedAt()?.compare(small), .orderedSame)
|
||||
}
|
||||
|
||||
func test_can_set_bigger_movedAt_when_movedAt_is_nil() {
|
||||
let small = TimeTicket.initialTimeTicket
|
||||
let big = TimeTicket.maxTimeTicket
|
||||
|
||||
let target = CRDTElement(createdAt: small)
|
||||
let movedResult = target.setMovedAt(big)
|
||||
|
||||
XCTAssertEqual(movedResult, true)
|
||||
XCTAssertEqual(target.getMovedAt()?.compare(big), .orderedSame)
|
||||
}
|
||||
|
||||
func test_can_not_set_bigger_movedAt_when_movedAt_is_non_nil() {
|
||||
let small = TimeTicket.initialTimeTicket
|
||||
let big = TimeTicket.maxTimeTicket
|
||||
|
||||
let target = CRDTElement(createdAt: small)
|
||||
target.setMovedAt(big)
|
||||
|
||||
let timeTicket = TimeTicket(lamport: 10, delimiter: 10, actorID: ActorIds.initialActorID)
|
||||
let movedResult = target.setMovedAt(timeTicket)
|
||||
|
||||
XCTAssertEqual(movedResult, false)
|
||||
XCTAssertEqual(target.getMovedAt()?.compare(small), .orderedDescending)
|
||||
}
|
||||
|
||||
func test_can_not_remove_when_nil() {
|
||||
let target = CRDTElement(createdAt: TimeTicket.initialTimeTicket)
|
||||
|
||||
XCTAssertEqual(target.remove(nil), false)
|
||||
}
|
||||
|
||||
func test_can_not_remove_when_removeAt_is_before_createdAt() {
|
||||
let target = CRDTElement(createdAt: TimeTicket.maxTimeTicket)
|
||||
|
||||
XCTAssertEqual(target.remove(TimeTicket.initialTimeTicket), false)
|
||||
}
|
||||
|
||||
func test_can_remove_when_current_removeAt_is_nil() {
|
||||
let target = CRDTElement(createdAt: TimeTicket.initialTimeTicket)
|
||||
|
||||
XCTAssertEqual(target.remove(TimeTicket.maxTimeTicket), true)
|
||||
}
|
||||
|
||||
func test_can_remove_when_current_removeAt_is_not_nil_and_samll() {
|
||||
let target = CRDTElement(createdAt: TimeTicket.initialTimeTicket)
|
||||
target.setRemovedAt(TimeTicket.initialTimeTicket)
|
||||
|
||||
XCTAssertEqual(target.remove(TimeTicket.maxTimeTicket), true)
|
||||
}
|
||||
|
||||
func test_can_not_remove_when_current_removeAt_is_not_nil_and_big() {
|
||||
let target = CRDTElement(createdAt: TimeTicket.initialTimeTicket)
|
||||
target.setRemovedAt(TimeTicket.maxTimeTicket)
|
||||
|
||||
let timeTicket = TimeTicket(lamport: 10, delimiter: 10, actorID: ActorIds.initialActorID)
|
||||
XCTAssertEqual(target.remove(timeTicket), false)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* 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 PrimitiveTests: XCTestCase {
|
||||
func test_value_is_null() throws {
|
||||
let primitiveValue = Primitive(value: .null, createdAt: TimeTicket.initialTimeTicket)
|
||||
let valueFromData = try Converter.valueFrom(valueType: .null, data: primitiveValue.toBytes())
|
||||
switch valueFromData {
|
||||
case .null:
|
||||
()
|
||||
default:
|
||||
XCTFail("Type error.")
|
||||
}
|
||||
}
|
||||
|
||||
func test_value_is_bool() throws {
|
||||
let primitiveValue = Primitive(value: .boolean(true), createdAt: TimeTicket.initialTimeTicket)
|
||||
let valueFromData = try Converter.valueFrom(valueType: .boolean, data: primitiveValue.toBytes())
|
||||
switch valueFromData {
|
||||
case .boolean(let value):
|
||||
XCTAssertEqual(value, true)
|
||||
default:
|
||||
XCTFail("Type error.")
|
||||
}
|
||||
}
|
||||
|
||||
func test_value_is_integer() throws {
|
||||
let primitiveValue = Primitive(value: .integer(12345), createdAt: TimeTicket.initialTimeTicket)
|
||||
let valueFromData = try Converter.valueFrom(valueType: .integer, data: primitiveValue.toBytes())
|
||||
switch valueFromData {
|
||||
case .integer(let value):
|
||||
XCTAssertEqual(value, 12345)
|
||||
default:
|
||||
XCTFail("Type error.")
|
||||
}
|
||||
}
|
||||
|
||||
func test_value_is_long() throws {
|
||||
let primitiveValue = Primitive(value: .long(1_234_567_890), createdAt: TimeTicket.initialTimeTicket)
|
||||
let valueFromData = try Converter.valueFrom(valueType: .long, data: primitiveValue.toBytes())
|
||||
switch valueFromData {
|
||||
case .long(let value):
|
||||
XCTAssertEqual(value, 1_234_567_890)
|
||||
default:
|
||||
XCTFail("Type error.")
|
||||
}
|
||||
}
|
||||
|
||||
func test_value_is_double() throws {
|
||||
let primitiveValue = Primitive(value: .double(-123_456_789), createdAt: TimeTicket.initialTimeTicket)
|
||||
let valueFromData = try Converter.valueFrom(valueType: .double, data: primitiveValue.toBytes())
|
||||
switch valueFromData {
|
||||
case .double(let value):
|
||||
XCTAssertEqual(value, -123_456_789)
|
||||
default:
|
||||
XCTFail("Type error.")
|
||||
}
|
||||
}
|
||||
|
||||
func test_value_is_string() throws {
|
||||
let primitiveValue = Primitive(value: .string("ABCDEFG"), createdAt: TimeTicket.initialTimeTicket)
|
||||
let valueFromData = try Converter.valueFrom(valueType: .string, data: primitiveValue.toBytes())
|
||||
switch valueFromData {
|
||||
case .string(let value):
|
||||
XCTAssertEqual(value, "ABCDEFG")
|
||||
default:
|
||||
XCTFail("Type error.")
|
||||
}
|
||||
}
|
||||
|
||||
func test_value_is_bytes() throws {
|
||||
let testData = "abcdefg".data(using: .utf8)!
|
||||
let primitiveValue = Primitive(value: .bytes(testData), createdAt: TimeTicket.initialTimeTicket)
|
||||
let valueFromData = try Converter.valueFrom(valueType: .bytes, data: primitiveValue.toBytes())
|
||||
switch valueFromData {
|
||||
case .bytes(let value):
|
||||
XCTAssertEqual(String(decoding: value, as: UTF8.self), "abcdefg")
|
||||
default:
|
||||
XCTFail("Type error.")
|
||||
}
|
||||
}
|
||||
|
||||
func test_value_is_date() throws {
|
||||
let testDate = Date()
|
||||
print(testDate.timeIntervalSince1970)
|
||||
let primitiveValue = Primitive(value: .date(testDate), createdAt: TimeTicket.initialTimeTicket)
|
||||
let valueFromData = try Converter.valueFrom(valueType: .date, data: primitiveValue.toBytes())
|
||||
switch valueFromData {
|
||||
case .date(let value):
|
||||
XCTAssertEqual(value.timeIntervalSince1970, testDate.timeIntervalSince1970)
|
||||
default:
|
||||
XCTFail("Type error.")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 TimeTicketTests: XCTestCase {
|
||||
func test_compare_with_a_big_thing() {
|
||||
let small = TimeTicket.initialTimeTicket
|
||||
let big = TimeTicket.maxTimeTicket
|
||||
|
||||
XCTAssertEqual(small.compare(big), .orderedAscending)
|
||||
}
|
||||
|
||||
func test_compare_with_a_small_thing() {
|
||||
let big = TimeTicket.maxTimeTicket
|
||||
let small = TimeTicket.initialTimeTicket
|
||||
|
||||
XCTAssertEqual(big.compare(small), .orderedDescending)
|
||||
}
|
||||
|
||||
func test_compare_with_a_same_thing() {
|
||||
let big = TimeTicket.maxTimeTicket
|
||||
let big2 = TimeTicket.maxTimeTicket
|
||||
|
||||
XCTAssertEqual(big.compare(big2), .orderedSame)
|
||||
}
|
||||
}
|
|
@ -40,16 +40,16 @@ class SplayTreeTests: XCTestCase {
|
|||
let tree = SplayTree<String>()
|
||||
|
||||
let nodeA = tree.insert(StringNode.create("A2"))
|
||||
XCTAssertEqual(tree.getAnnotatedString(), "[2,2]A2")
|
||||
XCTAssertEqual(tree.getStructureAsString(), "[2,2]A2")
|
||||
XCTAssertEqual(tree.getRoot()?.value, "A2")
|
||||
let nodeB = tree.insert(StringNode.create("B23"))
|
||||
XCTAssertEqual(tree.getAnnotatedString(), "[2,2]A2[5,3]B23")
|
||||
XCTAssertEqual(tree.getStructureAsString(), "[2,2]A2[5,3]B23")
|
||||
XCTAssertEqual(tree.getRoot()?.value, "B23")
|
||||
let nodeC = tree.insert(StringNode.create("C234"))
|
||||
XCTAssertEqual(tree.getAnnotatedString(), "[2,2]A2[5,3]B23[9,4]C234")
|
||||
XCTAssertEqual(tree.getStructureAsString(), "[2,2]A2[5,3]B23[9,4]C234")
|
||||
XCTAssertEqual(tree.getRoot()?.value, "C234")
|
||||
let nodeD = tree.insert(StringNode.create("D2345"))
|
||||
XCTAssertEqual(tree.getAnnotatedString(), "[2,2]A2[5,3]B23[9,4]C234[14,5]D2345")
|
||||
XCTAssertEqual(tree.getStructureAsString(), "[2,2]A2[5,3]B23[9,4]C234[14,5]D2345")
|
||||
XCTAssertEqual(tree.getRoot()?.value, "D2345")
|
||||
|
||||
XCTAssertEqual(tree.indexOf(nodeA), 0)
|
||||
|
@ -66,16 +66,16 @@ class SplayTreeTests: XCTestCase {
|
|||
let tree = SplayTree<String>()
|
||||
|
||||
let nodeH = tree.insert(StringNode.create("H"))
|
||||
XCTAssertEqual(tree.getAnnotatedString(), "[1,1]H")
|
||||
XCTAssertEqual(tree.getStructureAsString(), "[1,1]H")
|
||||
let nodeE = tree.insert(StringNode.create("E"))
|
||||
XCTAssertEqual(tree.getAnnotatedString(), "[1,1]H[2,1]E")
|
||||
XCTAssertEqual(tree.getStructureAsString(), "[1,1]H[2,1]E")
|
||||
let nodeL = tree.insert(StringNode.create("LL"))
|
||||
XCTAssertEqual(tree.getAnnotatedString(), "[1,1]H[2,1]E[4,2]LL")
|
||||
XCTAssertEqual(tree.getStructureAsString(), "[1,1]H[2,1]E[4,2]LL")
|
||||
let nodeO = tree.insert(StringNode.create("O"))
|
||||
XCTAssertEqual(tree.getAnnotatedString(), "[1,1]H[2,1]E[4,2]LL[5,1]O")
|
||||
XCTAssertEqual(tree.getStructureAsString(), "[1,1]H[2,1]E[4,2]LL[5,1]O")
|
||||
|
||||
tree.delete(nodeE)
|
||||
XCTAssertEqual(tree.getAnnotatedString(), "[4,1]H[3,2]LL[1,1]O")
|
||||
XCTAssertEqual(tree.getStructureAsString(), "[4,1]H[3,2]LL[1,1]O")
|
||||
|
||||
XCTAssertEqual(tree.indexOf(nodeH), 0)
|
||||
XCTAssertEqual(tree.indexOf(nodeE), -1)
|
||||
|
@ -113,12 +113,12 @@ class SplayTreeTests: XCTestCase {
|
|||
func test_can_delete_range_between_the_given_2_boundary_nodes_first() {
|
||||
let testTree = self.sampleTree
|
||||
// check the filtering of rangeDelete
|
||||
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.getAnnotatedString())
|
||||
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.getAnnotatedString())
|
||||
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])
|
||||
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.getAnnotatedString())
|
||||
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())
|
||||
XCTAssertEqual(testTree.nodes[6].getWeight(), 22)
|
||||
XCTAssertEqual(self.sumOfWeight(testTree.nodes, from: 7, to: 8), 0)
|
||||
|
|
|
@ -14,12 +14,21 @@
|
|||
CE3EC95228D195E4009471BC /* RedBlackTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3EC95028D195E0009471BC /* RedBlackTreeTests.swift */; };
|
||||
CE3EC95F28D2AAA1009471BC /* SplayTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3EC95D28D2AA9C009471BC /* SplayTreeTests.swift */; };
|
||||
CE3EC96128D2D626009471BC /* SplayTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3EC96028D2D626009471BC /* SplayTree.swift */; };
|
||||
CE3EC96728D30E74009471BC /* CRDTElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3EC96628D30E74009471BC /* CRDTElement.swift */; };
|
||||
CE3EC96928D30FEF009471BC /* ActorId.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3EC96828D30FEE009471BC /* ActorId.swift */; };
|
||||
CE3EC96E28D3FFF0009471BC /* TimeTicketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3EC96C28D3FFED009471BC /* TimeTicketTests.swift */; };
|
||||
CE3EC97328D40498009471BC /* CRDTElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3EC97128D4042D009471BC /* CRDTElementTests.swift */; };
|
||||
CE3EC97528D41903009471BC /* Primitive.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3EC97428D41903009471BC /* Primitive.swift */; };
|
||||
CE6071E528C5D7EE00A8783E /* CONTRIBUTING.md in Resources */ = {isa = PBXBuildFile; fileRef = CE6071E428C5D7EE00A8783E /* CONTRIBUTING.md */; };
|
||||
CE7B996E28E142A300D56198 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B996D28E142A300D56198 /* Errors.swift */; };
|
||||
CE7B997028E1453E00D56198 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B996F28E1453E00D56198 /* Strings.swift */; };
|
||||
CE7B997428E1766F00D56198 /* Converter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B997328E1766F00D56198 /* Converter.swift */; };
|
||||
CE7B997628E1773A00D56198 /* GRPCTypeAlias.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B997528E1773A00D56198 /* GRPCTypeAlias.swift */; };
|
||||
CE7B997828E178EF00D56198 /* PrimitiveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B997128E1750000D56198 /* PrimitiveTests.swift */; };
|
||||
CE8C22EF28C9E85900432DE5 /* Change.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22EE28C9E85900432DE5 /* Change.swift */; };
|
||||
CE8C22F128C9E86A00432DE5 /* Root.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22F028C9E86A00432DE5 /* Root.swift */; };
|
||||
CE8C22F328C9E87800432DE5 /* Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22F228C9E87800432DE5 /* Object.swift */; };
|
||||
CE8C22F528C9E88500432DE5 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22F428C9E88500432DE5 /* Operation.swift */; };
|
||||
CE8C22F728C9E89100432DE5 /* Ticket.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22F628C9E89100432DE5 /* Ticket.swift */; };
|
||||
CE8C22F728C9E89100432DE5 /* TimeTicket.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22F628C9E89100432DE5 /* TimeTicket.swift */; };
|
||||
CE8C22F928C9E8CA00432DE5 /* Heap.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22F828C9E8CA00432DE5 /* Heap.swift */; };
|
||||
CE8C230528C9F1BD00432DE5 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C230428C9F1BD00432DE5 /* Client.swift */; };
|
||||
CE8C230728D1514900432DE5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C230628D1514900432DE5 /* Logger.swift */; };
|
||||
|
@ -53,12 +62,21 @@
|
|||
CE3EC95028D195E0009471BC /* RedBlackTreeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedBlackTreeTests.swift; sourceTree = "<group>"; };
|
||||
CE3EC95D28D2AA9C009471BC /* SplayTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplayTreeTests.swift; sourceTree = "<group>"; };
|
||||
CE3EC96028D2D626009471BC /* SplayTree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplayTree.swift; sourceTree = "<group>"; };
|
||||
CE3EC96628D30E74009471BC /* CRDTElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRDTElement.swift; sourceTree = "<group>"; };
|
||||
CE3EC96828D30FEE009471BC /* ActorId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActorId.swift; sourceTree = "<group>"; };
|
||||
CE3EC96C28D3FFED009471BC /* TimeTicketTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeTicketTests.swift; sourceTree = "<group>"; };
|
||||
CE3EC97128D4042D009471BC /* CRDTElementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRDTElementTests.swift; sourceTree = "<group>"; };
|
||||
CE3EC97428D41903009471BC /* Primitive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Primitive.swift; sourceTree = "<group>"; };
|
||||
CE6071E428C5D7EE00A8783E /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = "<group>"; };
|
||||
CE7B996D28E142A300D56198 /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = "<group>"; };
|
||||
CE7B996F28E1453E00D56198 /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = "<group>"; };
|
||||
CE7B997128E1750000D56198 /* PrimitiveTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimitiveTests.swift; sourceTree = "<group>"; };
|
||||
CE7B997328E1766F00D56198 /* Converter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Converter.swift; sourceTree = "<group>"; };
|
||||
CE7B997528E1773A00D56198 /* GRPCTypeAlias.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GRPCTypeAlias.swift; sourceTree = "<group>"; };
|
||||
CE8C22EE28C9E85900432DE5 /* Change.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Change.swift; sourceTree = "<group>"; };
|
||||
CE8C22F028C9E86A00432DE5 /* Root.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Root.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>"; };
|
||||
CE8C22F628C9E89100432DE5 /* Ticket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ticket.swift; sourceTree = "<group>"; };
|
||||
CE8C22F628C9E89100432DE5 /* TimeTicket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeTicket.swift; sourceTree = "<group>"; };
|
||||
CE8C22F828C9E8CA00432DE5 /* Heap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Heap.swift; sourceTree = "<group>"; };
|
||||
CE8C230428C9F1BD00432DE5 /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = "<group>"; };
|
||||
CE8C230628D1514900432DE5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Logger.swift; path = Sources/Core/Logger.swift; sourceTree = SOURCE_ROOT; };
|
||||
|
@ -128,6 +146,7 @@
|
|||
96DA808F28C5B7B400E2C1DA /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CE3EC97028D40421009471BC /* Document */,
|
||||
CE6071EB28C5ECA900A8783E /* API */,
|
||||
CE8C230828D15F5200432DE5 /* Core */,
|
||||
CE3EC94A28D1885F009471BC /* Util */,
|
||||
|
@ -155,10 +174,38 @@
|
|||
path = Util;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CE3EC96B28D3FFDB009471BC /* Time */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CE3EC96C28D3FFED009471BC /* TimeTicketTests.swift */,
|
||||
);
|
||||
path = Time;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CE3EC96F28D40415009471BC /* CRDT */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CE3EC97128D4042D009471BC /* CRDTElementTests.swift */,
|
||||
CE7B997128E1750000D56198 /* PrimitiveTests.swift */,
|
||||
);
|
||||
path = CRDT;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CE3EC97028D40421009471BC /* Document */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CE3EC96F28D40415009471BC /* CRDT */,
|
||||
CE3EC96B28D3FFDB009471BC /* Time */,
|
||||
);
|
||||
path = Document;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CE6071E828C5EC2500A8783E /* API */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CEEB17CF28C8493F004988DD /* V1 */,
|
||||
CE7B997328E1766F00D56198 /* Converter.swift */,
|
||||
CE7B997528E1773A00D56198 /* GRPCTypeAlias.swift */,
|
||||
);
|
||||
path = API;
|
||||
sourceTree = "<group>";
|
||||
|
@ -204,6 +251,7 @@
|
|||
CE8C22F828C9E8CA00432DE5 /* Heap.swift */,
|
||||
CE3EC94E28D1922E009471BC /* RedBlackTree.swift */,
|
||||
CE3EC96028D2D626009471BC /* SplayTree.swift */,
|
||||
CE7B996D28E142A300D56198 /* Errors.swift */,
|
||||
);
|
||||
path = Util;
|
||||
sourceTree = "<group>";
|
||||
|
@ -219,7 +267,8 @@
|
|||
CE8C22E728C9E55800432DE5 /* CRDT */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CE8C22F028C9E86A00432DE5 /* Root.swift */,
|
||||
CE3EC96628D30E74009471BC /* CRDTElement.swift */,
|
||||
CE3EC97428D41903009471BC /* Primitive.swift */,
|
||||
);
|
||||
path = CRDT;
|
||||
sourceTree = "<group>";
|
||||
|
@ -228,6 +277,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
CE8C22F228C9E87800432DE5 /* Object.swift */,
|
||||
CE7B996F28E1453E00D56198 /* Strings.swift */,
|
||||
);
|
||||
path = Json;
|
||||
sourceTree = "<group>";
|
||||
|
@ -243,7 +293,8 @@
|
|||
CE8C22EA28C9E56B00432DE5 /* Time */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CE8C22F628C9E89100432DE5 /* Ticket.swift */,
|
||||
CE8C22F628C9E89100432DE5 /* TimeTicket.swift */,
|
||||
CE3EC96828D30FEE009471BC /* ActorId.swift */,
|
||||
);
|
||||
path = Time;
|
||||
sourceTree = "<group>";
|
||||
|
@ -451,18 +502,24 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
CE3EC96128D2D626009471BC /* SplayTree.swift in Sources */,
|
||||
CE3EC96928D30FEF009471BC /* ActorId.swift in Sources */,
|
||||
CE8C230728D1514900432DE5 /* Logger.swift in Sources */,
|
||||
CE3EC97528D41903009471BC /* Primitive.swift in Sources */,
|
||||
CEEB17E428C84D26004988DD /* resources.pb.swift in Sources */,
|
||||
CE8C22F928C9E8CA00432DE5 /* Heap.swift in Sources */,
|
||||
CE8C22EF28C9E85900432DE5 /* Change.swift in Sources */,
|
||||
CEEB17E328C84D26004988DD /* yorkie.pb.swift in Sources */,
|
||||
CE3EC94F28D1922E009471BC /* RedBlackTree.swift in Sources */,
|
||||
CE7B997028E1453E00D56198 /* Strings.swift in Sources */,
|
||||
CE8C230528C9F1BD00432DE5 /* Client.swift in Sources */,
|
||||
CE8C22F528C9E88500432DE5 /* Operation.swift in Sources */,
|
||||
CE8C22F728C9E89100432DE5 /* Ticket.swift in Sources */,
|
||||
CE7B997428E1766F00D56198 /* Converter.swift in Sources */,
|
||||
CE8C22F728C9E89100432DE5 /* TimeTicket.swift in Sources */,
|
||||
CE7B997628E1773A00D56198 /* GRPCTypeAlias.swift in Sources */,
|
||||
CE7B996E28E142A300D56198 /* Errors.swift in Sources */,
|
||||
CE8C22F328C9E87800432DE5 /* Object.swift in Sources */,
|
||||
CE3EC96728D30E74009471BC /* CRDTElement.swift in Sources */,
|
||||
CEEB17E528C84D26004988DD /* yorkie.grpc.swift in Sources */,
|
||||
CE8C22F128C9E86A00432DE5 /* Root.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -473,6 +530,9 @@
|
|||
CECCCB8428C96CD600544204 /* XCTestCase+Extension.swift in Sources */,
|
||||
CE3EC95F28D2AAA1009471BC /* SplayTreeTests.swift in Sources */,
|
||||
CE8C230B28D15FF200432DE5 /* ClientTests.swift in Sources */,
|
||||
CE7B997828E178EF00D56198 /* PrimitiveTests.swift in Sources */,
|
||||
CE3EC97328D40498009471BC /* CRDTElementTests.swift in Sources */,
|
||||
CE3EC96E28D3FFF0009471BC /* TimeTicketTests.swift in Sources */,
|
||||
96DA809128C5B7B400E2C1DA /* GRPCTests.swift in Sources */,
|
||||
CE3EC94D28D189EF009471BC /* HeapTests.swift in Sources */,
|
||||
CE3EC95228D195E4009471BC /* RedBlackTreeTests.swift in Sources */,
|
||||
|
|
Loading…
Reference in New Issue