Refactor reference counting

sync3
Laurent 5 months ago
parent 0a83bddaf6
commit a3ec0820e4
  1. 12
      LeStorage/Relationship.swift
  2. 31
      LeStorage/Store.swift
  3. 53
      LeStorage/StoreCenter.swift
  4. 25
      LeStorage/StoredCollection.swift
  5. 33
      LeStorage/SyncedCollection.swift

@ -5,12 +5,18 @@
// Created by Laurent Morvillier on 27/11/2024.
//
public enum StoreLookup {
case same
case main
case child
}
public struct Relationship {
public init(type: any Storable.Type, keyPath: AnyKeyPath, mainStoreLookup: Bool) {
public init(type: any Storable.Type, keyPath: AnyKeyPath, storeLookup: StoreLookup) {
self.type = type
self.keyPath = keyPath
self.mainStoreLookup = mainStoreLookup
self.storeLookup = storeLookup
}
/// The type of the relationship
@ -20,6 +26,6 @@ public struct Relationship {
var keyPath: AnyKeyPath
/// Indicates whether the linked object is on the main Store
var mainStoreLookup: Bool
var storeLookup: StoreLookup
}

@ -19,6 +19,7 @@ public enum StoreError: Error, LocalizedError {
case synchronizationInactive
case storeNotRegistered(id: String)
case castIssue(type: String)
case invalidStoreLookup(from: any Storable.Type, to: any Storable.Type)
public var errorDescription: String? {
switch self {
@ -42,6 +43,8 @@ public enum StoreError: Error, LocalizedError {
return "The store with identifier \(id) is not registered"
case .castIssue(let type):
return "Can't cast to \(type)"
case .invalidStoreLookup(let from, let to):
return "invalid store lookup from \(from) to \(to)"
}
}
@ -275,19 +278,27 @@ final public class Store {
}
/// Calls deleteById from the collection corresponding to the instance
func referenceCount<T: SyncedStorable>(type: T.Type, id: String) -> Int {
var count: Int = 0
for collection in self._collections.values {
count += collection.referenceCount(type: type, id: id)
// func referenceCount<T: SyncedStorable>(type: T.Type, id: String) -> Int {
// var count: Int = 0
// for collection in self._collections.values {
// count += collection.referenceCount(type: type, id: id)
// }
// return count
// }
func isReferenced<T: Storable, S: Storable>(collectionType: S.Type, type: T.Type, id: String) -> Bool {
if let collection = self._baseCollections[S.resourceName()] {
return collection.isReferenced(type: type, id: id)
} else {
return false
}
return count
}
public func deleteUnusedSharedIfNecessary<T: SyncedStorable>(_ instance: T) {
if self.referenceCount(type: T.self, id: instance.stringId) == 0 {
public func deleteUnusedGrantedIfNecessary<T: SyncedStorable>(_ instance: T, originStoreId: String?) {
if !self.storeCenter.isReferenced(instance: instance) {
do {
let collection: SyncedCollection<T> = try self.syncedCollection()
collection.deleteUnusedShared(instance: instance)
collection.deleteUnusedGranted(instance: instance)
} catch {
Logger.error(error)
}
@ -320,11 +331,11 @@ final public class Store {
do {
for item in items {
guard item.sharing != nil else { continue }
if self.referenceCount(type: T.self, id: item.stringId) == 0 {
if !self.storeCenter.isReferenced(instance: item) {
// Only delete if the shared item has no references
item.deleteUnusedSharedDependencies(store: self)
let collection: SyncedCollection<T> = try self.syncedCollection()
collection.deleteUnusedShared(instance: item)
collection.deleteUnusedGranted(instance: item)
}
}
} catch {

@ -227,7 +227,7 @@ public class StoreCenter {
}
}
fileprivate func _store(id: String?) -> Store? {
fileprivate func _store(id: String?) -> Store {
if let storeId = id, let store = self._stores[storeId] {
return store
} else {
@ -782,7 +782,7 @@ public class StoreCenter {
@MainActor
func synchronizationDelete<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) {
do {
try self._store(id: storeId)?.deleteNoSyncNoCascade(type: type, id: id)
try self._store(id: storeId).deleteNoSyncNoCascade(type: type, id: id)
} catch {
Logger.error(error)
}
@ -794,23 +794,34 @@ public class StoreCenter {
func synchronizationRevoke<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) {
do {
if self._instanceShared(id: id, type: type) {
let count = self.mainStore.referenceCount(type: type, id: id)
if count == 0 {
try self._store(id: storeId)?.deleteNoSyncNoCascade(type: type, id: id)
if let instance = self._instance(id: id, type: type, storeId: storeId) {
if instance.sharing != nil && !self.isReferenced(instance: instance) {
try self._store(id: storeId).deleteNoSyncNoCascade(type: type, id: id)
}
}
// if self._instanceShared(id: id, type: type, storeId: storeId) {
// let count = self.isReferenced(type: type, id: id)
// if count == 0 {
// try self._store(id: storeId).deleteNoSyncNoCascade(type: type, id: id)
// }
// }
} catch {
Logger.error(error)
}
}
/// Returns whether an instance has been shared with the user
fileprivate func _instanceShared<T: SyncedStorable>(id: String, type: T.Type) -> Bool {
fileprivate func _instance<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) -> T? {
let realId: T.ID = T.buildRealId(id: id)
let instance: T? = self.mainStore.findById(realId)
return instance?.sharing != nil
return self._store(id: storeId).findById(realId)
}
/// Returns whether an instance has been shared with the user
// fileprivate func _instanceShared<T: SyncedStorable>(id: String, type: T.Type, storeId: String?) -> Bool {
//// let realId: T.ID = T.buildRealId(id: id)
// let instance: T? = self._instance(id: id, type: type, storeId: storeId)
// return instance?.sharing != nil
// }
/// Deletes a data log by data id
fileprivate func _cleanupDataLog(dataId: String) {
@ -826,6 +837,28 @@ public class StoreCenter {
self._deleteLogs.addOrUpdate(instance: dataLog)
}
func relationshipStore<T: SyncedStorable>(instance: T, relationship: Relationship) -> Store? {
switch relationship.storeLookup {
case .main: return Store.main
case .child: return self._stores[instance.stringId]
case .same: return instance.store
}
}
func isReferenced<T: SyncedStorable>(instance: T) -> Bool {
let relationships = T.relationships()
for relationship in relationships {
if let store = self.relationshipStore(instance: instance, relationship: relationship) {
if store.isReferenced(collectionType: relationship.type, type: T.self, id: instance.stringId) {
return true
}
} else {
Logger.w("missing store for instance \(instance)")
}
}
return false
}
// MARK: - Sync data conversion
func decodeObjectIdentifierDictionary(_ dictionary: [String: Any]) throws -> [ObjectIdentifierArray] {

@ -17,7 +17,7 @@ public protocol SomeCollection<Item>: Identifiable {
var type: any Storable.Type { get }
func reset()
func referenceCount<S: Storable>(type: S.Type, id: String) -> Int
func isReferenced<S: Storable>(type: S.Type, id: String) -> Bool
var items: [Item] { get }
@ -528,16 +528,25 @@ public class StoredCollection<T: Storable>: SomeCollection {
// MARK: - Reference count
/// Counts the references to an object - given its type and id - inside the collection
public func referenceCount<S: Storable>(type: S.Type, id: String) -> Int {
public func isReferenced<S: Storable>(type: S.Type, id: String) -> Bool {
let relationships = T.relationships().filter { $0.type == type }
guard relationships.count > 0 else { return 0 }
guard relationships.count > 0 else { return false }
return self.items.reduce(0) { count, item in
count
+ relationships.filter { relationship in
(item[keyPath: relationship.keyPath] as? String) == id
}.count
for item in self.items {
for relationship in relationships {
if item[keyPath: relationship.keyPath] as? String == id {
return true
}
}
}
return false
// return self.items.reduce(0) { count, item in
// count
// + relationships.filter { relationship in
// (item[keyPath: relationship.keyPath] as? String) == id
// }.count
// }
}
// MARK: - for Synced Collection

@ -229,7 +229,7 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
for relationship in T.relationships() {
if let syncedType = relationship.type as? (any SyncedStorable.Type) {
do {
try self._deleteUnusedSharedInstances(relationship: relationship, type: syncedType)
try self._deleteUnusedSharedInstances(relationship: relationship, type: syncedType, originStoreId: self.storeId)
} catch {
Logger.error(error)
}
@ -237,24 +237,31 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
}
}
fileprivate func _deleteUnusedSharedInstances<S: SyncedStorable>(relationship: Relationship, type: S.Type) throws {
fileprivate func _deleteUnusedSharedInstances<S: SyncedStorable>(relationship: Relationship, type: S.Type, originStoreId: String?) throws {
let store: Store
if relationship.mainStoreLookup {
store = self.store.storeCenter.mainStore
} else {
store = self.store
switch relationship.storeLookup {
case .main: store = self.store.storeCenter.mainStore
case .same: store = self.store
case .child:
throw StoreError.invalidStoreLookup(from: type, to: relationship.type)
}
// if relationship.storeLookup {
// store = self.store.storeCenter.mainStore
// } else {
// store = self.store
// }
let collection: SyncedCollection<S> = try store.syncedCollection()
collection._deleteUnusedSharedInstances()
collection._deleteUnusedGrantedInstances(originStoreId: originStoreId)
}
fileprivate func _deleteUnusedSharedInstances() {
fileprivate func _deleteUnusedGrantedInstances(originStoreId: String?) {
let sharedItems = self.collection.items.filter { $0.sharing != nil }
let sharedItems = self.collection.items.filter { $0.sharing == .granted }
for sharedItem in sharedItems {
self.store.deleteUnusedSharedIfNecessary(sharedItem)
self.store.deleteUnusedGrantedIfNecessary(sharedItem, originStoreId: originStoreId
)
}
}
@ -329,7 +336,7 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
self.collection.delete(contentOfs: sequence)
}
func deleteUnusedShared(instance: T) {
func deleteUnusedGranted(instance: T) {
guard instance.sharing != nil else { return }
@ -451,8 +458,8 @@ public class SyncedCollection<T : SyncedStorable>: SomeSyncedCollection, Collect
public var type: any Storable.Type { return T.self }
public func referenceCount<S>(type: S.Type, id: String) -> Int where S : Storable {
return self.collection.referenceCount(type: type, id: id)
public func isReferenced<S>(type: S.Type, id: String) -> Bool where S : Storable {
return self.collection.isReferenced(type: type, id: id)
}
public func reset() {

Loading…
Cancel
Save