414 lines
12 KiB
Swift
414 lines
12 KiB
Swift
/*
|
|
* 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
|
|
|
|
/**
|
|
* `RGATreeListNode` is a node of RGATreeList.
|
|
*/
|
|
final class RGATreeListNode: SplayNode<CRDTElement> {
|
|
fileprivate private(set) var previous: RGATreeListNode?
|
|
fileprivate var next: RGATreeListNode?
|
|
|
|
override init(_ value: CRDTElement) {
|
|
super.init(value)
|
|
}
|
|
|
|
/**
|
|
* `create` creates a new node after the previous node.
|
|
*/
|
|
static func create(with value: CRDTElement, previousNode: RGATreeListNode) -> RGATreeListNode {
|
|
let newNode = RGATreeListNode(value)
|
|
let prevNext = previousNode.next
|
|
previousNode.next = newNode
|
|
newNode.previous = previousNode
|
|
newNode.next = prevNext
|
|
if let prevNext = prevNext {
|
|
prevNext.previous = newNode
|
|
}
|
|
|
|
return newNode
|
|
}
|
|
|
|
/**
|
|
* `length` returns the length of this node.
|
|
*/
|
|
override var length: Int {
|
|
return self.value.isRemoved ? 0 : 1
|
|
}
|
|
|
|
/**
|
|
* `remove` removes value based on removing time.
|
|
*/
|
|
fileprivate func remove(_ removedAt: TimeTicket) -> Bool {
|
|
return self.value.remove(removedAt)
|
|
}
|
|
|
|
/**
|
|
* `getCreatedAt` returns creation time of this value
|
|
*/
|
|
fileprivate var createdAt: TimeTicket {
|
|
return self.value.createdAt
|
|
}
|
|
|
|
/**
|
|
* `getPositionedAt` returns time this element was positioned in the array.
|
|
*/
|
|
fileprivate var positionedAt: TimeTicket {
|
|
if let movedAt = self.value.movedAt {
|
|
return movedAt
|
|
}
|
|
|
|
return self.value.createdAt
|
|
}
|
|
|
|
/**
|
|
* `delete` deletes prev and next node.
|
|
*/
|
|
fileprivate func delete() {
|
|
if let previous = self.previous {
|
|
previous.next = self.next
|
|
}
|
|
if let next = self.next {
|
|
next.previous = self.previous
|
|
}
|
|
self.previous = nil
|
|
self.next = nil
|
|
}
|
|
|
|
/**
|
|
* `isRemoved` checks if the value was removed.
|
|
*/
|
|
fileprivate var isRemoved: Bool {
|
|
return self.value.isRemoved
|
|
}
|
|
}
|
|
|
|
/**
|
|
* `RGATreeList` is replicated growable array.
|
|
*/
|
|
class RGATreeList {
|
|
private let dummyHead: RGATreeListNode
|
|
private var last: RGATreeListNode
|
|
private let nodeMapByIndex: SplayTree<CRDTElement>
|
|
private var nodeMapByCreatedAt: [TimeTicket: RGATreeListNode]
|
|
|
|
init() {
|
|
let dummyValue = Primitive(value: .null, createdAt: .initial)
|
|
dummyValue.removedAt = .initial
|
|
self.dummyHead = RGATreeListNode(dummyValue)
|
|
self.last = self.dummyHead
|
|
self.nodeMapByIndex = SplayTree()
|
|
self.nodeMapByIndex.insert(self.dummyHead)
|
|
self.nodeMapByCreatedAt = [self.dummyHead.createdAt: self.dummyHead]
|
|
}
|
|
|
|
/**
|
|
* `length` returns size of RGATreeList.
|
|
*/
|
|
var length: Int {
|
|
return self.nodeMapByIndex.length
|
|
}
|
|
|
|
/**
|
|
* `findNode` returns the node by the given createdAt and
|
|
* executedAt. It passes through nodes created after executedAt from the
|
|
* given node and returns the next node.
|
|
*
|
|
* - Parameters:
|
|
* - createdAt: created time
|
|
* - executedAt: executed time
|
|
* - Returns: next node
|
|
*/
|
|
private func findNode(fromCreatedAt createdAt: TimeTicket, executedAt: TimeTicket) throws -> RGATreeListNode {
|
|
guard var node = self.nodeMapByCreatedAt[createdAt] else {
|
|
let log = "can't find the given node: \(createdAt)"
|
|
Logger.critical(log)
|
|
throw YorkieError.unexpected(message: log)
|
|
}
|
|
|
|
while true {
|
|
guard let next = node.next, next.positionedAt.after(executedAt) else {
|
|
break
|
|
}
|
|
node = next
|
|
}
|
|
|
|
return node
|
|
}
|
|
|
|
private func delete(node: RGATreeListNode) {
|
|
if self.last === node, let previousNode = node.previous {
|
|
self.last = previousNode
|
|
}
|
|
|
|
node.delete()
|
|
self.nodeMapByIndex.delete(node)
|
|
self.nodeMapByCreatedAt.removeValue(forKey: node.value.createdAt)
|
|
}
|
|
|
|
/**
|
|
* `insert` adds a new node with the value after the given node.
|
|
*/
|
|
func insert(_ value: CRDTElement, afterCreatedAt createdAt: TimeTicket, executedAt: TimeTicket? = nil) throws {
|
|
let executedAt: TimeTicket = executedAt ?? value.createdAt
|
|
|
|
let previousNode = try findNode(fromCreatedAt: createdAt, executedAt: executedAt)
|
|
let newNode = RGATreeListNode.create(with: value, previousNode: previousNode)
|
|
if previousNode === self.last {
|
|
self.last = newNode
|
|
}
|
|
|
|
self.nodeMapByIndex.insert(previousNode: previousNode, newNode: newNode)
|
|
self.nodeMapByCreatedAt[newNode.createdAt] = newNode
|
|
}
|
|
|
|
/**
|
|
* `move` moves the given `createdAt` element
|
|
* after the `previousCreatedAt` element.
|
|
*/
|
|
func move(createdAt: TimeTicket, afterCreatedAt: TimeTicket, executedAt: TimeTicket) throws {
|
|
guard let previsousNode = self.nodeMapByCreatedAt[afterCreatedAt] else {
|
|
let log = "can't find the given node: \(afterCreatedAt)"
|
|
Logger.critical(log)
|
|
throw YorkieError.unexpected(message: log)
|
|
}
|
|
|
|
guard let movingNode = self.nodeMapByCreatedAt[createdAt] else {
|
|
let log = "can't find the given node: \(createdAt)"
|
|
Logger.critical(log)
|
|
throw YorkieError.unexpected(message: log)
|
|
}
|
|
|
|
guard previsousNode !== movingNode else {
|
|
return
|
|
}
|
|
|
|
var needToMove = false
|
|
if movingNode.value.movedAt == nil {
|
|
needToMove = true
|
|
} else if let movedAt = movingNode.value.movedAt, executedAt.after(movedAt) {
|
|
needToMove = true
|
|
}
|
|
|
|
guard needToMove else {
|
|
return
|
|
}
|
|
|
|
self.delete(node: movingNode)
|
|
try self.insert(movingNode.value, afterCreatedAt: previsousNode.createdAt, executedAt: executedAt)
|
|
movingNode.value.setMovedAt(executedAt)
|
|
}
|
|
|
|
/**
|
|
* `insert` adds the given element after the last node.
|
|
*/
|
|
func insert(_ value: CRDTElement) throws {
|
|
try self.insert(value, afterCreatedAt: self.last.createdAt)
|
|
}
|
|
|
|
/**
|
|
* `get` returns the element of the given creation time.
|
|
*/
|
|
func get(createdAt: TimeTicket) throws -> CRDTElement {
|
|
guard let node = self.nodeMapByCreatedAt[createdAt] else {
|
|
let log = "can't find the given node: \(createdAt)"
|
|
Logger.critical(log)
|
|
throw YorkieError.unexpected(message: log)
|
|
}
|
|
|
|
return node.value
|
|
}
|
|
|
|
/**
|
|
* `subPath` returns the sub path of the given element.
|
|
*/
|
|
func subPath(createdAt: TimeTicket) throws -> String {
|
|
guard let node = self.nodeMapByCreatedAt[createdAt] else {
|
|
let log = "can't find the given node: \(createdAt)"
|
|
Logger.critical(log)
|
|
throw YorkieError.unexpected(message: log)
|
|
}
|
|
|
|
return String(self.nodeMapByIndex.indexOf(node))
|
|
}
|
|
|
|
/**
|
|
* `delete` physically purges element.
|
|
*/
|
|
func delete(_ value: CRDTElement) throws {
|
|
guard let node = self.nodeMapByCreatedAt[value.createdAt] else {
|
|
let log = "failed to find the given createdAt: \(value.createdAt)"
|
|
Logger.critical(log)
|
|
throw YorkieError.unexpected(message: log)
|
|
}
|
|
|
|
self.delete(node: node)
|
|
}
|
|
|
|
/**
|
|
* `getNode` returns node of the given index.
|
|
*/
|
|
func getNode(index: Int) throws -> RGATreeListNode {
|
|
guard index < self.length else {
|
|
let log = "length is smaller than or equal to: \(index)"
|
|
Logger.critical(log)
|
|
throw YorkieError.unexpected(message: log)
|
|
}
|
|
|
|
let (node, offset) = self.nodeMapByIndex.find(index)
|
|
guard let rgaNode = node as? RGATreeListNode else {
|
|
let log = "failed to find the given index: \(index)"
|
|
Logger.critical(log)
|
|
throw YorkieError.unexpected(message: log)
|
|
}
|
|
|
|
guard (index == 0 && rgaNode === self.dummyHead) || offset >= 1 else {
|
|
return rgaNode
|
|
}
|
|
|
|
var nextRgaNode: RGATreeListNode? = rgaNode
|
|
repeat {
|
|
if nextRgaNode == nil {
|
|
break
|
|
}
|
|
nextRgaNode = nextRgaNode?.next
|
|
|
|
} while nextRgaNode?.isRemoved == true
|
|
|
|
guard let nextRgaNode else {
|
|
let log = "failed to find the given index: \(index)"
|
|
Logger.critical(log)
|
|
throw YorkieError.unexpected(message: log)
|
|
}
|
|
|
|
return nextRgaNode
|
|
}
|
|
|
|
/**
|
|
* `getPreviousCreatedAt` returns a creation time of the previous node.
|
|
*/
|
|
func getPreviousCreatedAt(ofCreatedAt createdAt: TimeTicket) throws -> TimeTicket {
|
|
guard let node = self.nodeMapByCreatedAt[createdAt] else {
|
|
let log = "can't find the given node: \(createdAt)"
|
|
Logger.critical(log)
|
|
throw YorkieError.unexpected(message: log)
|
|
}
|
|
var previousNode: RGATreeListNode? = node
|
|
repeat {
|
|
previousNode = previousNode?.previous
|
|
} while self.dummyHead !== previousNode && previousNode?.isRemoved == true
|
|
|
|
return previousNode?.value.createdAt ?? self.getHead().createdAt
|
|
}
|
|
|
|
/**
|
|
* `remove` removes the node of the given creation time.
|
|
*/
|
|
func remove(createdAt: TimeTicket, executedAt: TimeTicket) throws -> CRDTElement {
|
|
guard let node = self.nodeMapByCreatedAt[createdAt] else {
|
|
let log = "can't find the given node: \(createdAt)"
|
|
Logger.critical(log)
|
|
throw YorkieError.unexpected(message: log)
|
|
}
|
|
|
|
let alreadyRemoved = node.isRemoved
|
|
if node.remove(executedAt), alreadyRemoved == false {
|
|
self.nodeMapByIndex.splayNode(node)
|
|
}
|
|
return node.value
|
|
}
|
|
|
|
/**
|
|
* `remove` removes the node of the given index.
|
|
*/
|
|
func remove(index: Int, executedAt: TimeTicket) throws -> CRDTElement {
|
|
let node = try self.getNode(index: index)
|
|
|
|
if node.remove(executedAt) {
|
|
self.nodeMapByIndex.splayNode(node)
|
|
}
|
|
return node.value
|
|
}
|
|
|
|
/**
|
|
* `getHead` returns the value of head elements.
|
|
*/
|
|
func getHead() -> CRDTElement {
|
|
return self.dummyHead.value
|
|
}
|
|
|
|
/**
|
|
* `getLast` returns the value of last elements.
|
|
*/
|
|
func getLast() -> CRDTElement {
|
|
return self.last.value
|
|
}
|
|
|
|
/**
|
|
* `getLastCreatedAt` returns the creation time of last element.
|
|
*/
|
|
func getLastCreatedAt() -> TimeTicket {
|
|
return self.last.createdAt
|
|
}
|
|
|
|
/**
|
|
* `structureAsString` returns a String containing the meta data of the node id
|
|
* for debugging purpose.
|
|
*/
|
|
var structureAsString: String {
|
|
var result: [String] = []
|
|
|
|
for node in self {
|
|
let value = "\(node.createdAt):\(node.value.toJSON())"
|
|
if node.isRemoved {
|
|
result.append("{\(value)}")
|
|
} else {
|
|
result.append("[\(value)]")
|
|
}
|
|
}
|
|
|
|
return result.joined(separator: "-")
|
|
}
|
|
}
|
|
|
|
extension RGATreeList: Sequence {
|
|
typealias Element = RGATreeListNode
|
|
|
|
func makeIterator() -> RGATreeListIterator {
|
|
return RGATreeListIterator(self.dummyHead.next)
|
|
}
|
|
}
|
|
|
|
class RGATreeListIterator: IteratorProtocol {
|
|
private weak var iteratorNext: RGATreeListNode?
|
|
|
|
init(_ firstNode: RGATreeListNode?) {
|
|
self.iteratorNext = firstNode
|
|
}
|
|
|
|
func next() -> RGATreeListNode? {
|
|
guard let result = self.iteratorNext else {
|
|
return nil
|
|
}
|
|
|
|
defer {
|
|
self.iteratorNext = result.next
|
|
}
|
|
return result
|
|
}
|
|
}
|