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

Implement yorkie.Tree for text editors using tree model #90

Merged
merged 50 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
682a135
Change the value of XXXChange to Change in Document.subscribe
humdrum Jun 21, 2023
b2203af
initial commit
humdrum Jul 7, 2023
e650339
add Test codes.
humdrum Jul 13, 2023
8e6691a
Cleanup of test-related terminology
humdrum Jul 20, 2023
d5a6a02
Expose pathToIndex API
humdrum Jul 20, 2023
89f1ba1
Bump up protobuf
humdrum Jul 20, 2023
acd3244
Merge branch 'main' into tree
humdrum Jul 21, 2023
1b83a5c
modify action script
humdrum Jul 21, 2023
bdd9335
correct script
humdrum Jul 21, 2023
82edba2
disable todo lint rule
humdrum Jul 24, 2023
3c29ef0
Revert "Expose pathToIndex API"
humdrum Jul 20, 2023
35629c7
Revert "Bump up protobuf "
humdrum Jul 20, 2023
d71708e
set yorkie server version to 0.4.4
humdrum Jul 24, 2023
9c07921
set yorkie server to 0.4.4
humdrum Jul 24, 2023
b17ad7a
fix sync() concurrency
humdrum Jul 26, 2023
3b047b2
bump to 0.4.4
humdrum Jul 26, 2023
318dc04
Update swift-integration.yml
humdrum Jul 26, 2023
aaadec1
Revert "Update swift-integration.yml"
humdrum Jul 26, 2023
0b62a67
add more debug info.
humdrum Jul 26, 2023
db6d07f
Update swift-integration.yml
humdrum Jul 26, 2023
7ac7096
fix typo
humdrum Jul 26, 2023
d265dde
change params to underscore
humdrum Jul 26, 2023
d984cf1
fix compile error
humdrum Jul 26, 2023
ba2c979
Revert "Update swift-integration.yml"
humdrum Jul 26, 2023
252465b
fix misused idString
humdrum Jul 27, 2023
004f29a
change the lamport in the TimeTicketStruct to string
humdrum Jul 27, 2023
c2a85e0
cleanup codes
humdrum Jul 27, 2023
417c418
complete == function
humdrum Jul 28, 2023
0eb26ca
change throw exception to assert
humdrum Jul 28, 2023
65f0ea2
remove unnecessary code
humdrum Jul 28, 2023
77a859a
change throw exception to precondition
humdrum Jul 28, 2023
d7ed8ba
open ElementNode init()
humdrum Aug 1, 2023
6def578
rename JSON nodes
humdrum Aug 1, 2023
b1d841e
revert edit() parameter to 0.4.4
humdrum Aug 2, 2023
2e55f6e
remove forced unwrap
humdrum Aug 3, 2023
1490a6e
fix lint error
humdrum Aug 3, 2023
09c4504
fix JSONTree iterration
humdrum Aug 3, 2023
166da15
fix tc validation function
humdrum Aug 4, 2023
f5120da
change docker action
humdrum Aug 4, 2023
d2654ed
Revert "change docker action "
humdrum Aug 4, 2023
c1e7a2d
Change JSONTree attributes value type to Any
humdrum Aug 4, 2023
c340202
remove forced unwrap
humdrum Aug 7, 2023
b761b93
fix bool converting
humdrum Aug 8, 2023
d6db66d
Revert "fix bool converting"
humdrum Aug 8, 2023
2b5f2cf
remove duplicated codes
humdrum Aug 9, 2023
382372d
Update swift-integration.yml
humdrum Aug 10, 2023
888190d
cleanup ci action
humdrum Aug 11, 2023
f56dda3
Update swift-integration.yml
humdrum Aug 11, 2023
7004e26
Update swift-integration.yml
humdrum Aug 11, 2023
6d84ab0
Fix compile error
humdrum Aug 11, 2023
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
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ disabled_rules:
- file_length
- opening_brace
- large_tuple
- todo
opt_in_rules:
- empty_count
# Rewrited rules
Expand Down
9 changes: 9 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@
"version" : "1.13.1"
}
},
{
"identity" : "semaphore",
"kind" : "remoteSourceControl",
"location" : "https://github.com/groue/Semaphore.git",
"state" : {
"revision" : "f1c4a0acabeb591068dea6cffdd39660b86dec28",
"version" : "0.0.8"
}
},
{
"identity" : "swift-atomics",
"kind" : "remoteSourceControl",
Expand Down
6 changes: 4 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ let package = Package(
.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.9.0"),
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.19.0"),
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.0")
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.0"),
.package(url: "https://github.com/groue/Semaphore.git", from: "0.0.8")
],
targets: [
.target(
name: "Yorkie",
dependencies: [.product(name: "GRPC", package: "grpc-swift"),
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
.product(name: "Logging", package: "swift-log")],
.product(name: "Logging", package: "swift-log"),
.product(name: "Semaphore", package: "Semaphore")],
path: "Sources",
exclude: ["Info.plist",
"API/V1/yorkie/v1/resources.proto",
Expand Down
231 changes: 227 additions & 4 deletions Sources/API/Converter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,13 @@ extension Converter {
} else if let counter = element as? CRDTCounter<Int64> {
pbElementSimple.type = .longCnt
pbElementSimple.value = counter.toBytes()
} else if let tree = element as? CRDTTree {
pbElementSimple.type = .tree
do {
pbElementSimple.value = try treeToBytes(tree)
} catch {
fatalError("Can't convert CRDTTree to bytes.")
}
}

pbElementSimple.createdAt = toTimeTicket(element.createdAt)
Expand Down Expand Up @@ -276,6 +283,8 @@ extension Converter {
}

return CRDTCounter<Int64>(value: value, createdAt: fromTimeTicket(pbElementSimple.createdAt))
case .tree:
return try bytesToTree(bytes: pbElementSimple.value)
default:
throw YorkieError.unimplemented(message: "unimplemented element: \(pbElementSimple)")
}
Expand Down Expand Up @@ -394,6 +403,25 @@ extension Converter {
pbIncreaseOperation.value = toElementSimple(increaseOperation.value)
pbIncreaseOperation.executedAt = toTimeTicket(increaseOperation.executedAt)
pbOperation.increase = pbIncreaseOperation
} else if let treeEditOperation = operation as? TreeEditOperation {
var pbTreeEditOperation = PbOperation.TreeEdit()
pbTreeEditOperation.parentCreatedAt = toTimeTicket(treeEditOperation.parentCreatedAt)
pbTreeEditOperation.from = toTreePos(treeEditOperation.fromPos)
pbTreeEditOperation.to = toTreePos(treeEditOperation.toPos)
pbTreeEditOperation.content = toTreeNodes(treeEditOperation.contents![0])
pbTreeEditOperation.executedAt = toTimeTicket(treeEditOperation.executedAt)
pbOperation.treeEdit = pbTreeEditOperation
} else if let treeStyleOperation = operation as? TreeStyleOperation {
var pbTreeStyleOperation = PbOperation.TreeStyle()
pbTreeStyleOperation.parentCreatedAt = toTimeTicket(treeStyleOperation.parentCreatedAt)
pbTreeStyleOperation.from = toTreePos(treeStyleOperation.fromPos)
pbTreeStyleOperation.to = toTreePos(treeStyleOperation.toPos)

treeStyleOperation.attributes.forEach { key, value in
pbTreeStyleOperation.attributes[key] = value
}
pbTreeStyleOperation.executedAt = toTimeTicket(treeStyleOperation.executedAt)
pbOperation.treeStyle = pbTreeStyleOperation
} else {
throw YorkieError.unimplemented(message: "unimplemented operation \(operation)")
}
Expand Down Expand Up @@ -457,6 +485,18 @@ extension Converter {
return IncreaseOperation(parentCreatedAt: fromTimeTicket(pbIncreaseOperation.parentCreatedAt),
value: try fromElementSimple(pbElementSimple: pbIncreaseOperation.value),
executedAt: fromTimeTicket(pbIncreaseOperation.executedAt))
} else if case let .treeEdit(pbTreeEditOperation) = pbOperation.body {
return try TreeEditOperation(parentCreatedAt: fromTimeTicket(pbTreeEditOperation.parentCreatedAt),
fromPos: fromTreePos(pbTreeEditOperation.from),
toPos: fromTreePos(pbTreeEditOperation.to),
contents: [fromTreeNodes(pbTreeEditOperation.content)!],
executedAt: fromTimeTicket(pbTreeEditOperation.executedAt))
} else if case let .treeStyle(pbTreeStyleOperation) = pbOperation.body {
return TreeStyleOperation(parentCreatedAt: fromTimeTicket(pbTreeStyleOperation.parentCreatedAt),
fromPos: fromTreePos(pbTreeStyleOperation.from),
toPos: fromTreePos(pbTreeStyleOperation.to),
attributes: pbTreeStyleOperation.attributes,
executedAt: fromTimeTicket(pbTreeStyleOperation.executedAt))
} else {
throw YorkieError.unimplemented(message: "unimplemented operation \(pbOperation)")
}
Expand Down Expand Up @@ -709,6 +749,39 @@ extension Converter {
}
}

/**
* `fromTree` converts the given Protobuf format to model format.
*/
static func fromTree(_ pbTree: PbJSONElement.Tree) throws -> CRDTTree {
guard let root = try fromTreeNodes(pbTree.nodes) else {
throw YorkieError.unexpected(message: "Can't get root from PbJSONElement.Tree")
}
return CRDTTree(root: root, createdAt: fromTimeTicket(pbTree.createdAt))
}

/**
* `toTree` converts the given model to Protobuf format.
*/
static func toTree(_ tree: CRDTTree) -> PbJSONElement {
var pbTree = PbJSONElement.Tree()
pbTree.nodes = toTreeNodes(tree.root)
pbTree.createdAt = toTimeTicket(tree.createdAt)
if let ticket = tree.movedAt {
pbTree.movedAt = toTimeTicket(ticket)
} else {
pbTree.clearMovedAt()
}
if let ticket = tree.removedAt {
pbTree.removedAt = toTimeTicket(ticket)
} else {
pbTree.clearRemovedAt()
}

var pbElement = PbJSONElement()
pbElement.tree = pbTree
return pbElement
}

/**
* `toElement` converts the given model to Protobuf format.
*/
Expand All @@ -725,6 +798,8 @@ extension Converter {
return toCounter(element)
} else if let element = element as? CRDTCounter<Int64> {
return toCounter(element)
} else if let element = element as? CRDTTree {
return toTree(element)
} else {
throw YorkieError.unimplemented(message: "unimplemented element: \(element)")
}
Expand All @@ -750,6 +825,136 @@ extension Converter {
}
}

// MARK: Tree
extension Converter {
/**
* `toTreePos` converts the given model to Protobuf format.
*/
static func toTreePos(_ pos: CRDTTreePos) -> PbTreePos {
var pbTreePos = PbTreePos()
pbTreePos.createdAt = toTimeTicket(pos.createdAt)
pbTreePos.offset = pos.offset

return pbTreePos
}
/*
/**
* `toTreeNodesWhenEdit` converts the given model to Protobuf format.
*/
static func toTreeNodesWhenEdit(_ nodes: [CRDTTreeNode]?) -> [PbTreeNode] {
guard let nodes else {
return []
}

return nodes.compactMap {
var pbTreeNodes = PbTreeNodes()
pbTreeNodes.content = toTreeNodes($0)

return pbTreeNodes
}
}
*/
/**
* `toTreeNodes` converts the given model to Protobuf format.
*/
static func toTreeNodes(_ node: CRDTTreeNode) -> [PbTreeNode] {
var pbTreeNodes = [PbTreeNode]()

traverse(node: node) { node, depth in
var pbTreeNode = PbTreeNode()
pbTreeNode.pos = toTreePos(node.pos)
pbTreeNode.type = node.type
if node.isText {
pbTreeNode.value = node.value
}
if let ticket = node.removedAt {
pbTreeNode.removedAt = toTimeTicket(ticket)
} else {
pbTreeNode.clearRemovedAt()
}
pbTreeNode.depth = depth

node.attrs?.forEach { rhtNode in
var pbNodeAttr = PbNodeAttr()
pbNodeAttr.value = rhtNode.value
pbNodeAttr.updatedAt = toTimeTicket(rhtNode.updatedAt)
pbTreeNode.attributes[rhtNode.key] = pbNodeAttr
}

pbTreeNodes.append(pbTreeNode)
}

return pbTreeNodes
}

/**
* `fromTreePos` converts the given Protobuf format to model format.
*/
static func fromTreePos(_ pbTreePos: PbTreePos) -> CRDTTreePos {
CRDTTreePos(createdAt: fromTimeTicket(pbTreePos.createdAt), offset: pbTreePos.offset)
}
/*
/**
* `fromTreeNodesWhenEdit` converts the given Protobuf format to model format.
*/
static func fromTreeNodesWhenEdit(_ pbTreeNodes: [PbTreeNode]) -> [CRDTTreeNode]? {
guard pbTreeNodes.isEmpty == false else {
return nil
}

return pbTreeNodes.compactMap { try? fromTreeNodes($0.content) }
}

*/
/**
* `fromTreeNodes` converts the given Protobuf format to model format.
*/
static func fromTreeNodes(_ pbTreeNodes: [PbTreeNode]) throws -> CRDTTreeNode? {
guard pbTreeNodes.isEmpty == false else {
return nil
}

let nodes = pbTreeNodes.compactMap { fromTreeNode($0) }

let root = nodes[nodes.count - 1]

for index in stride(from: nodes.count - 2, to: -1, by: -1) {
var parent: CRDTTreeNode?
for index2 in index + 1 ..< nodes.count {
if pbTreeNodes[index].depth - 1 == pbTreeNodes[index2].depth {
parent = nodes[index2]
break
}
}

try parent?.prepend(contentsOf: [nodes[index]])
}

// build CRDTTree from the root to construct the links between nodes.
return CRDTTree(root: root, createdAt: TimeTicket.initial).root
}

/**
* `fromTreeNode` converts the given Protobuf format to model format.
*/
static func fromTreeNode(_ pbTreeNode: PbTreeNode) -> CRDTTreeNode {
let pos = fromTreePos(pbTreeNode.pos)
let node = CRDTTreeNode(pos: pos, type: pbTreeNode.type)

if node.isText {
node.value = pbTreeNode.value
} else {
node.attrs = RHT()

pbTreeNode.attributes.forEach { key, value in
node.attrs?.set(key: key, value: value.value, executedAt: fromTimeTicket(value.updatedAt))
}
}

return node
}
}

// MARK: TextNode
extension Converter {
/**
Expand All @@ -762,7 +967,7 @@ extension Converter {
pbTextNode.id = toTextNodeID(id: textNode.id)
pbTextNode.value = String(describing: textNode.value.content)
textNode.value.getAttributes().forEach { key, value in
var attr = PbTextNodeAttr()
var attr = PbNodeAttr()
attr.value = value.value
attr.updatedAt = toTimeTicket(value.updatedAt)
pbTextNode.attributes[key] = attr
Expand Down Expand Up @@ -818,10 +1023,9 @@ extension Converter {
static func fromChangePack(_ pbPack: PbChangePack) throws -> ChangePack {
ChangePack(key: pbPack.documentKey,
checkpoint: fromCheckpoint(pbPack.checkpoint),
changes: try fromChanges(pbPack.changes),
isRemoved: pbPack.isRemoved, changes: try fromChanges(pbPack.changes),
snapshot: pbPack.snapshot.isEmpty ? nil : pbPack.snapshot,
minSyncedTicket: pbPack.hasMinSyncedTicket ? fromTimeTicket(pbPack.minSyncedTicket) : nil,
isRemoved: pbPack.isRemoved)
minSyncedTicket: pbPack.hasMinSyncedTicket ? fromTimeTicket(pbPack.minSyncedTicket) : nil)
}

}
Expand Down Expand Up @@ -862,6 +1066,25 @@ extension Converter {

// MARK: Bytes.
extension Converter {
/**
* `bytesToTree` creates an CRDTTree from the given bytes.
*/
static func bytesToTree(bytes: Data) throws -> CRDTTree {
guard bytes.isEmpty == false else {
return CRDTTree(root: CRDTTreeNode(pos: CRDTTreePos.initial, type: DefaultTreeNodeType.root.rawValue), createdAt: TimeTicket.initial)
}

let pbElement = try PbJSONElement(serializedData: bytes)
return try fromTree(pbElement.tree)
}

/**
* `treeToBytes` converts the given tree to bytes.
*/
static func treeToBytes(_ tree: CRDTTree) throws -> Data {
try toTree(tree).serializedData()
}

/**
* `bytesToObject` creates an JSONObject from the given byte array.
*/
Expand Down
5 changes: 4 additions & 1 deletion Sources/API/GRPCTypeAlias.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,7 @@ typealias PbTextNodeID = Yorkie_V1_TextNodeID
typealias PbTimeTicket = Yorkie_V1_TimeTicket
typealias PbValueType = Yorkie_V1_ValueType
typealias PbTextNodePos = Yorkie_V1_TextNodePos
typealias PbTextNodeAttr = Yorkie_V1_TextNodeAttr
typealias PbNodeAttr = Yorkie_V1_NodeAttr
typealias PbTreeNode = Yorkie_V1_TreeNode
typealias PbTreePos = Yorkie_V1_TreePos
//typealias PbTreeNodes = Yorkie_V1_TreeNodes
Loading
Loading