205 lines
5.9 KiB
Swift
205 lines
5.9 KiB
Swift
/*
|
|
* Copyright 2023 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
|
|
|
|
/**
|
|
* `ElementRHTNode` is a node of ElementRHT.
|
|
*/
|
|
struct ElementRHTNode: Equatable {
|
|
let key: String
|
|
let value: CRDTElement
|
|
|
|
fileprivate init(key: String, value: CRDTElement) {
|
|
self.key = key
|
|
self.value = value
|
|
}
|
|
|
|
/**
|
|
* `isRemoved` checks whether this value was removed.
|
|
*/
|
|
var isRemoved: Bool {
|
|
return self.value.isRemoved
|
|
}
|
|
|
|
/**
|
|
* `remove` removes a value base on removing time.
|
|
*/
|
|
@discardableResult
|
|
fileprivate func remove(removedAt: TimeTicket) -> Bool {
|
|
return self.value.remove(removedAt)
|
|
}
|
|
|
|
static func == (lhs: ElementRHTNode, rhs: ElementRHTNode) -> Bool {
|
|
return lhs.key == rhs.key && lhs.value.equals(rhs.value)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ElementRHT is replicated hash table with priority queue by creation time.
|
|
*/
|
|
class ElementRHT {
|
|
// nodeMapByKey is a map with values of nodes by key.
|
|
private var nodeMapByKey: [String: ElementRHTNode] = [:]
|
|
// nodeMapByCreatedAt is a map with values of nodes by creation time.
|
|
private var nodeMapByCreatedAt: [TimeTicket: ElementRHTNode] = [:]
|
|
|
|
/**
|
|
* `set` sets the value of the given key.
|
|
*/
|
|
@discardableResult
|
|
func set(key: String, value: CRDTElement) -> CRDTElement? {
|
|
var removed: CRDTElement?
|
|
|
|
let node = self.nodeMapByKey[key]
|
|
if node != nil, node!.isRemoved == false, node!.remove(removedAt: value.createdAt) {
|
|
removed = node!.value
|
|
}
|
|
|
|
let newNode = ElementRHTNode(key: key, value: value)
|
|
self.nodeMapByCreatedAt[value.createdAt] = newNode
|
|
|
|
if node == nil || value.createdAt.after(node!.value.createdAt) {
|
|
self.nodeMapByKey[key] = newNode
|
|
}
|
|
|
|
return removed
|
|
}
|
|
|
|
/**
|
|
* `remove` removes the Element of the given creation time
|
|
*/
|
|
@discardableResult
|
|
func remove(createdAt: TimeTicket, executedAt: TimeTicket) throws -> CRDTElement {
|
|
guard let node = nodeMapByCreatedAt[createdAt] else {
|
|
throw YorkieError.noSuchElement(message: "Can't find node of given createdAt [\(createdAt)] or executedAt [\(executedAt)]")
|
|
}
|
|
|
|
node.remove(removedAt: executedAt)
|
|
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 node.key
|
|
}
|
|
|
|
/**
|
|
* purge physically purge child element.
|
|
*/
|
|
func purge(element: CRDTElement) {
|
|
guard let node = nodeMapByCreatedAt[element.createdAt] else {
|
|
Logger.critical("fail to find: \(element.createdAt)")
|
|
return
|
|
}
|
|
|
|
if node == self.nodeMapByKey[node.key] {
|
|
self.nodeMapByKey.removeValue(forKey: self.nodeMapByKey[node.key]!.key)
|
|
}
|
|
|
|
self.nodeMapByCreatedAt.removeValue(forKey: node.value.createdAt)
|
|
}
|
|
|
|
/**
|
|
* `remove` removes the Element of the given key.
|
|
*/
|
|
@discardableResult
|
|
func remove(key: String, executedAt: TimeTicket) throws -> CRDTElement {
|
|
guard let node = nodeMapByKey[key] else {
|
|
throw YorkieError.noSuchElement(message: "Can't find node of given key [\(key)] or executedAt [\(executedAt)]")
|
|
}
|
|
|
|
node.remove(removedAt: executedAt)
|
|
|
|
return node.value
|
|
}
|
|
|
|
/**
|
|
* `has` returns whether the element exists of the given key or not.
|
|
*/
|
|
func has(key: String) -> Bool {
|
|
if let node = nodeMapByKey[key] {
|
|
return node.isRemoved == false
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* `get` returns the value of the given key.
|
|
*/
|
|
func get(key: String) throws -> CRDTElement {
|
|
if let node = nodeMapByKey[key] {
|
|
return node.value
|
|
}
|
|
|
|
throw YorkieError.noSuchElement(message: "Can't find node of given key [\(key)]")
|
|
}
|
|
|
|
/**
|
|
* `delete` physically deletes child element.
|
|
*/
|
|
func delete(value: CRDTElement) throws {
|
|
guard let node = self.nodeMapByCreatedAt[value.createdAt] else {
|
|
let log = "can't find the given node: \(value.createdAt)"
|
|
Logger.critical(log)
|
|
throw YorkieError.unexpected(message: log)
|
|
}
|
|
|
|
self.nodeMapByKey.removeValue(forKey: node.key)
|
|
self.nodeMapByCreatedAt.removeValue(forKey: node.value.createdAt)
|
|
}
|
|
}
|
|
|
|
extension ElementRHT: Sequence {
|
|
typealias Element = ElementRHTNode
|
|
|
|
func makeIterator() -> ElementRHTIterator {
|
|
return ElementRHTIterator(self.nodeMapByKey)
|
|
}
|
|
}
|
|
|
|
class ElementRHTIterator: IteratorProtocol {
|
|
private var target: [ElementRHTNode]
|
|
private var currentNodes: [ElementRHTNode] = []
|
|
|
|
init(_ target: [String: ElementRHTNode]) {
|
|
self.target = Array(target.values)
|
|
}
|
|
|
|
func next() -> ElementRHTNode? {
|
|
while true {
|
|
guard self.currentNodes.isEmpty else {
|
|
return self.currentNodes.removeFirst()
|
|
}
|
|
|
|
guard self.target.isEmpty == false else {
|
|
return nil
|
|
}
|
|
|
|
self.currentNodes.append(self.target.removeFirst())
|
|
}
|
|
}
|
|
}
|