mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
267 lines
9.9 KiB
Swift
267 lines
9.9 KiB
Swift
import Foundation
|
|
|
|
protocol ReverseIndexReference: Comparable, Hashable {
|
|
static func decodeArray(_ buffer: MemoryBuffer) -> [Self]
|
|
static func encodeArray(_ array: [Self]) -> MemoryBuffer
|
|
}
|
|
|
|
private final class ReverseIndexReferencesEntry<T: ReverseIndexReference> {
|
|
var orderedReferences: [T] = []
|
|
|
|
init() {
|
|
}
|
|
|
|
init(buffer: MemoryBuffer) {
|
|
self.orderedReferences = T.decodeArray(buffer)
|
|
}
|
|
|
|
private func index(of reference: T) -> Int? {
|
|
var lowerIndex = 0
|
|
var upperIndex = self.orderedReferences.count - 1
|
|
|
|
if lowerIndex > upperIndex {
|
|
return nil
|
|
}
|
|
|
|
while (true) {
|
|
let currentIndex = (lowerIndex + upperIndex) / 2
|
|
if self.orderedReferences[currentIndex] == reference {
|
|
return currentIndex
|
|
} else if lowerIndex > upperIndex {
|
|
return nil
|
|
} else {
|
|
if self.orderedReferences[currentIndex] > reference {
|
|
upperIndex = currentIndex - 1
|
|
} else {
|
|
lowerIndex = currentIndex + 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func remove(_ reference: T) -> Bool {
|
|
if let index = self.index(of: reference) {
|
|
self.orderedReferences.remove(at: index)
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
func insert(_ reference: T) -> Bool {
|
|
let insertItem = reference
|
|
var lo = 0
|
|
var hi = self.orderedReferences.count - 1
|
|
while lo <= hi {
|
|
let mid = (lo + hi)/2
|
|
if self.orderedReferences[mid] < insertItem {
|
|
lo = mid + 1
|
|
} else if insertItem < self.orderedReferences[mid] {
|
|
hi = mid - 1
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
self.orderedReferences.insert(insertItem, at: lo)
|
|
return true
|
|
}
|
|
|
|
func withMemoryBuffer(_ f: (MemoryBuffer) -> Void) {
|
|
f(T.encodeArray(self.orderedReferences))
|
|
}
|
|
}
|
|
|
|
struct ReverseIndexNamespace: Hashable {
|
|
let value: Int32?
|
|
|
|
init(_ value: Int32?) {
|
|
self.value = value
|
|
}
|
|
}
|
|
|
|
final class ReverseIndexReferenceTable<T: ReverseIndexReference>: Table {
|
|
private var cachedEntriesByNamespace: [ReverseIndexNamespace: [ValueBoxKey: ReverseIndexReferencesEntry<T>]] = [:]
|
|
private var updatedCachedEntriesByNamespace: [ReverseIndexNamespace: Set<ValueBoxKey>] = [:]
|
|
|
|
static func tableSpec(_ id: Int32) -> ValueBoxTable {
|
|
return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: true)
|
|
}
|
|
|
|
private func key(namespace: ReverseIndexNamespace, token: ValueBoxKey) -> ValueBoxKey {
|
|
if let value = namespace.value {
|
|
let key = ValueBoxKey(length: 4 + token.length)
|
|
key.setInt32(0, value: value)
|
|
memcpy(key.memory.advanced(by: 4), token.memory, token.length)
|
|
return key
|
|
} else {
|
|
return token
|
|
}
|
|
}
|
|
|
|
private func getEntry(namespace: ReverseIndexNamespace, token: ValueBoxKey) -> ReverseIndexReferencesEntry<T>? {
|
|
if let cachedNamespace = self.cachedEntriesByNamespace[namespace], let cached = cachedNamespace[token] {
|
|
return cached
|
|
} else {
|
|
if let value = self.valueBox.get(self.table, key: self.key(namespace: namespace, token: token)) {
|
|
let entry = ReverseIndexReferencesEntry<T>(buffer: value)
|
|
if self.cachedEntriesByNamespace[namespace] == nil {
|
|
self.cachedEntriesByNamespace[namespace] = [token: entry]
|
|
} else {
|
|
self.cachedEntriesByNamespace[namespace]![token] = entry
|
|
}
|
|
return entry
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
private func add(namespace: ReverseIndexNamespace, token: ValueBoxKey, reference: T) {
|
|
let entry: ReverseIndexReferencesEntry<T>
|
|
var addToCache = false
|
|
if let current = self.getEntry(namespace: namespace, token: token) {
|
|
entry = current
|
|
} else {
|
|
entry = ReverseIndexReferencesEntry<T>()
|
|
addToCache = true
|
|
}
|
|
if entry.insert(reference) {
|
|
if self.updatedCachedEntriesByNamespace[namespace] == nil {
|
|
self.updatedCachedEntriesByNamespace[namespace] = Set()
|
|
}
|
|
self.updatedCachedEntriesByNamespace[namespace]!.insert(token)
|
|
if addToCache {
|
|
if self.cachedEntriesByNamespace[namespace] == nil {
|
|
self.cachedEntriesByNamespace[namespace] = [token: entry]
|
|
} else {
|
|
self.cachedEntriesByNamespace[namespace]![token] = entry
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func remove(namespace: ReverseIndexNamespace, token: ValueBoxKey, reference: T) {
|
|
let entry: ReverseIndexReferencesEntry<T>
|
|
var addToCache = false
|
|
if let current = self.getEntry(namespace: namespace, token: token) {
|
|
entry = current
|
|
} else {
|
|
entry = ReverseIndexReferencesEntry<T>()
|
|
addToCache = true
|
|
}
|
|
if entry.remove(reference) {
|
|
if self.updatedCachedEntriesByNamespace[namespace] == nil {
|
|
self.updatedCachedEntriesByNamespace[namespace] = Set()
|
|
}
|
|
self.updatedCachedEntriesByNamespace[namespace]!.insert(token)
|
|
if addToCache {
|
|
if self.cachedEntriesByNamespace[namespace] == nil {
|
|
self.cachedEntriesByNamespace[namespace] = [token: entry]
|
|
} else {
|
|
self.cachedEntriesByNamespace[namespace]![token] = entry
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func add(namespace: ReverseIndexNamespace, reference: T, tokens: [ValueBoxKey]) {
|
|
for token in tokens {
|
|
self.add(namespace: namespace, token: token, reference: reference)
|
|
}
|
|
}
|
|
|
|
func remove(namespace: ReverseIndexNamespace, reference: T, tokens: [ValueBoxKey]) {
|
|
for token in tokens {
|
|
self.remove(namespace: namespace, token: token, reference: reference)
|
|
}
|
|
}
|
|
|
|
func matchingReferences(namespace: ReverseIndexNamespace, tokens: [ValueBoxKey], union: Bool = false) -> Set<T> {
|
|
var references: Set<T>?
|
|
for token in tokens {
|
|
if let references = references, references.isEmpty {
|
|
return Set()
|
|
}
|
|
var currentReferences = Set<T>()
|
|
self.valueBox.range(self.table, start: self.key(namespace: namespace, token: token).predecessor, end: self.key(namespace: namespace, token: token).successor, values: { key, value in
|
|
if let cachedNamespace = self.cachedEntriesByNamespace[namespace], let cached = cachedNamespace[token] {
|
|
for reference in cached.orderedReferences {
|
|
currentReferences.insert(reference)
|
|
}
|
|
} else {
|
|
for reference in ReverseIndexReferencesEntry<T>(buffer: value).orderedReferences {
|
|
currentReferences.insert(reference)
|
|
}
|
|
}
|
|
return true
|
|
}, limit: 0)
|
|
if let previousReferences = references {
|
|
if union {
|
|
references = previousReferences.union(currentReferences)
|
|
} else {
|
|
references = previousReferences.intersection(currentReferences)
|
|
}
|
|
} else {
|
|
references = currentReferences
|
|
}
|
|
}
|
|
if let references = references {
|
|
return references
|
|
} else {
|
|
return Set()
|
|
}
|
|
}
|
|
|
|
func exactReferences(namespace: ReverseIndexNamespace, token: ValueBoxKey) -> [T] {
|
|
if let value = self.valueBox.get(self.table, key: self.key(namespace: namespace, token: token)) {
|
|
var currentReferences: [T] = []
|
|
if let cachedNamespace = self.cachedEntriesByNamespace[namespace], let cached = cachedNamespace[token] {
|
|
for reference in cached.orderedReferences {
|
|
currentReferences.append(reference)
|
|
}
|
|
} else {
|
|
for reference in ReverseIndexReferencesEntry<T>(buffer: value).orderedReferences {
|
|
currentReferences.append(reference)
|
|
}
|
|
}
|
|
return currentReferences
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
override func clearMemoryCache() {
|
|
for (_, cachedEntries) in self.cachedEntriesByNamespace {
|
|
assert(cachedEntries.isEmpty)
|
|
}
|
|
for (_, updatedCachedEntries) in self.updatedCachedEntriesByNamespace {
|
|
assert(updatedCachedEntries.isEmpty)
|
|
}
|
|
}
|
|
|
|
override func beforeCommit() {
|
|
if !self.updatedCachedEntriesByNamespace.isEmpty {
|
|
for (namespace, updatedCachedEntries) in self.updatedCachedEntriesByNamespace {
|
|
for token in updatedCachedEntries {
|
|
if let cachedNamespace = self.cachedEntriesByNamespace[namespace], let cached = cachedNamespace[token] {
|
|
cached.withMemoryBuffer { buffer in
|
|
if buffer.length == 0 {
|
|
self.valueBox.remove(self.table, key: self.key(namespace: namespace, token: token), secure: false)
|
|
} else {
|
|
self.valueBox.set(self.table, key: self.key(namespace: namespace, token: token), value: buffer)
|
|
}
|
|
}
|
|
} else {
|
|
assertionFailure()
|
|
}
|
|
}
|
|
}
|
|
self.updatedCachedEntriesByNamespace.removeAll()
|
|
}
|
|
|
|
if !self.cachedEntriesByNamespace.isEmpty {
|
|
self.cachedEntriesByNamespace.removeAll()
|
|
}
|
|
}
|
|
}
|