Skip to content

Commit

Permalink
update parents of parents!
Browse files Browse the repository at this point in the history
  • Loading branch information
pjechris committed Aug 28, 2024
1 parent be7ed2c commit 57f533b
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 25 deletions.
22 changes: 10 additions & 12 deletions Sources/CohesionKit/EntityStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,7 @@ public class EntityStore {
logger?.didFailedToStore(T.self, id: entity.id, error: error)
}

for parentRef in node.metadata.parentsRefs {
guard let parentNode = storage[parentRef]?.unwrap() as? AnyEntityNode else {
continue
}

parentNode.updateEntityRelationship(node)
parentNode.enqueue(in: registry)
}
updateParents(of: node)

return node
}
Expand All @@ -191,7 +184,7 @@ public class EntityStore {
}

for (childRef, _) in node.metadata.childrenRefs {
guard let childNode = storage[childRef]?.unwrap() as? AnyEntityNode else {
guard let childNode = storage[childRef]?.unwrap() as? any AnyEntityNode else {
continue
}

Expand All @@ -216,16 +209,21 @@ public class EntityStore {
logger?.didFailedToStore(T.self, id: entity.id, error: error)
}

updateParents(of: node)

return node
}

func updateParents(of node: some AnyEntityNode) {
for parentRef in node.metadata.parentsRefs {
guard let parentNode = storage[parentRef]?.unwrap() as? AnyEntityNode else {
guard let parentNode = storage[parentRef]?.unwrap() as? any AnyEntityNode ?? refAliases[parentRef] else {
continue
}

parentNode.updateEntityRelationship(node)
parentNode.enqueue(in: registry)
updateParents(of: parentNode)
}

return node
}

private func storeAlias<T>(content: T?, key: AliasKey<T>, modifiedAt: Stamp?) {
Expand Down
5 changes: 3 additions & 2 deletions Sources/CohesionKit/Storage/AliasStorage.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// Keep a strong reference on each aliased node
typealias AliasStorage = [String: AnyEntityNode]
typealias AliasStorage = [String: any AnyEntityNode]

extension AliasStorage {
subscript<T>(_ aliasKey: AliasKey<T>) -> EntityNode<AliasContainer<T>>? {
Expand All @@ -9,7 +9,8 @@ extension AliasStorage {

subscript<T>(safe key: AliasKey<T>, onChange onChange: ((EntityNode<AliasContainer<T>>) -> Void)? = nil) -> EntityNode<AliasContainer<T>> {
mutating get {
self[key: key, default: EntityNode(AliasContainer(key: key), modifiedAt: nil, onChange: onChange)]
let storeKey = buildKey(for: T.self, key: key)
return self[key: key, default: EntityNode(AliasContainer(key: key), key: storeKey, modifiedAt: nil, onChange: onChange)]
}
}

Expand Down
26 changes: 18 additions & 8 deletions Sources/CohesionKit/Storage/EntityNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,36 @@ struct EntityMetadata {

/// Typed erased protocol
protocol AnyEntityNode: AnyObject {
associatedtype Value

var ref: Observable<Value> { get }
var value: Any { get }
var metadata: EntityMetadata { get }
var storageKey: String { get }

func nullify() -> Bool
func removeParent(_ node: AnyEntityNode)
func updateEntityRelationship<T>(_ node: EntityNode<T>)
func removeParent(_ node: any AnyEntityNode)
func updateEntityRelationship(_ node: some AnyEntityNode)
func enqueue(in: ObserverRegistry)
}

/// A graph node representing a entity of type `T` and its children. Anytime one of its children is updated the node
/// will reflect the change on its own value.
class EntityNode<T>: AnyEntityNode {
typealias Value = T
/// A child subscription used by its EntityNode parent
struct SubscribedChild {
/// the child subscription. Use it to unsubscribe to child upates
let subscription: Subscription
/// the child node value
let node: AnyEntityNode
let node: any AnyEntityNode
}

var value: Any { ref.value }

var metadata = EntityMetadata()
// FIXME: to delete, it's "just" to have a strong ref and avoid nodes to be deleted. Need a better memory management
private var childrenNodes: [AnyEntityNode] = []
private var childrenNodes: [any AnyEntityNode] = []

var applyChildrenChanges = true
/// An observable entity reference
Expand Down Expand Up @@ -110,20 +114,26 @@ class EntityNode<T>: AnyEntityNode {
childrenNodes = []
}

func removeParent(_ node: AnyEntityNode) {
func removeParent(_ node: any AnyEntityNode) {
metadata.parentsRefs.remove(node.storageKey)
}

func updateEntityRelationship<U>(_ node: EntityNode<U>) {
func updateEntityRelationship<U: AnyEntityNode>(_ node: U) {
guard let keyPath = metadata.childrenRefs[node.storageKey] else {
return
}

guard let writableKeyPath = keyPath as? WritableKeyPath<T, U> else {
if let writableKeyPath = keyPath as? WritableKeyPath<T, U.Value> {
ref.value[keyPath: writableKeyPath] = node.ref.value
return
}

if let optionalWritableKeyPath = keyPath as? WritableKeyPath<T, U.Value?> {
ref.value[keyPath: optionalWritableKeyPath] = node.ref.value
return
}

ref.value[keyPath: writableKeyPath] = node.ref.value
print("CohesionKit: cannot convert \(type(of: keyPath)) to WritableKeyPath<\(T.self), \(U.Value.self)>")
}

func enqueue(in registry: ObserverRegistry) {
Expand Down
2 changes: 1 addition & 1 deletion Tests/CohesionKitTests/EntityStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ extension EntityStoreTests {
XCTAssertTrue(registry.hasPendingChange(for: AliasContainer<RootFixture>.self))
}

func test_update_entityIsIndirectlyUsedByAlias_itEnqueuesAliasInRegistry() {
func test_update_entityIsInsideAggregagte_aggreateIsAliased_itEnqueuesAliasInRegistry() {
let aggregate = RootFixture(id: 1, primitive: "", singleNode: SingleNodeFixture(id: 1), listNodes: [])
let registry = ObserverRegistryStub()
let entityStore = EntityStore(registry: registry)
Expand Down
4 changes: 2 additions & 2 deletions Tests/CohesionKitTests/Visitor/EntityStoreVisitorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ class EntityStoreStoreVisitorTests: XCTestCase {
}

private class EntityNodeStub<T>: EntityNode<T> {
var observeChildKeyPathCalled: (AnyEntityNode, PartialKeyPath<T>) -> Void = { _, _ in }
var observeChildKeyPathOptionalCalled: (AnyEntityNode, PartialKeyPath<T>) -> Void = { _, _ in }
var observeChildKeyPathCalled: (any AnyEntityNode, PartialKeyPath<T>) -> Void = { _, _ in }
var observeChildKeyPathOptionalCalled: (any AnyEntityNode, PartialKeyPath<T>) -> Void = { _, _ in }

override func observeChild<C>(_ childNode: EntityNode<C>, for keyPath: WritableKeyPath<T, C>) {
observeChildKeyPathCalled(childNode, keyPath)
Expand Down

0 comments on commit 57f533b

Please sign in to comment.