Skip to content

Commit

Permalink
tech(alias): Create a container to handle aliased values (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
pjechris authored Sep 28, 2023
1 parent d2ab63e commit 534112b
Show file tree
Hide file tree
Showing 12 changed files with 261 additions and 340 deletions.
94 changes: 50 additions & 44 deletions Sources/CohesionKit/Identity/IdentityStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,8 @@ public class IdentityMap {

let node = nodeStore(entity: entity, modifiedAt: modifiedAt)

if let alias = named {
refAliases.insert(node, key: alias)
logger?.didRegisterAlias(alias)
if let key = named {
storeAlias(content: entity, key: key, modifiedAt: modifiedAt)
}

return EntityObserver(node: node, registry: registry)
Expand Down Expand Up @@ -80,9 +79,8 @@ public class IdentityMap {

let node = nodeStore(entity: entity, modifiedAt: modifiedAt)

if let alias = named {
refAliases.insert(node, key: alias)
logger?.didRegisterAlias(alias)
if let key = named {
storeAlias(content: entity, key: key, modifiedAt: modifiedAt)
}

return EntityObserver(node: node, registry: registry)
Expand All @@ -95,9 +93,8 @@ public class IdentityMap {
transaction {
let nodes = entities.map { nodeStore(entity: $0, modifiedAt: modifiedAt) }

if let alias = named {
refAliases.insert(nodes, key: alias)
logger?.didRegisterAlias(alias)
if let key = named {
storeAlias(content: entities, key: key, modifiedAt: modifiedAt)
}

return EntityObserver(nodes: nodes, registry: registry)
Expand All @@ -110,9 +107,8 @@ public class IdentityMap {
transaction {
let nodes = entities.map { nodeStore(entity: $0, modifiedAt: modifiedAt) }

if let alias = named {
refAliases.insert(nodes, key: alias)
logger?.didRegisterAlias(alias)
if let key = named {
storeAlias(content: entities, key: key, modifiedAt: modifiedAt)
}

return EntityObserver(nodes: nodes, registry: registry)
Expand All @@ -135,17 +131,19 @@ public class IdentityMap {

/// Try to find an entity/aggregate registered under `named` alias
/// - Parameter named: the alias to look for
public func find<T: Identifiable>(named: AliasKey<T>) -> AliasObserver<T> {
public func find<T: Identifiable>(named: AliasKey<T>) -> EntityObserver<T?> {
identityQueue.sync {
AliasObserver(alias: refAliases[named], registry: registry)
let node = refAliases[safe: named]
return EntityObserver(alias: node, registry: registry)
}
}

/// Try to find a collected registered under `named` alias
/// - Returns: an observer returning the alias value. Note that the value will be an Array
public func find<C: Collection>(named: AliasKey<C>) -> AliasObserver<[C.Element]> {
public func find<C: Collection>(named: AliasKey<C>) -> EntityObserver<C?> {
identityQueue.sync {
AliasObserver(alias: refAliases[named], registry: registry)
let node = refAliases[safe: named]
return EntityObserver(alias: node, registry: registry)
}
}

Expand Down Expand Up @@ -196,6 +194,20 @@ public class IdentityMap {
return node
}

private func storeAlias<T>(content: T, key: AliasKey<T>, modifiedAt: Stamp?) {
let aliasNode = refAliases[safe: key]

do {
try aliasNode.updateEntity(AliasContainer(key: key, content: content), modifiedAt: modifiedAt)

registry.enqueueChange(for: aliasNode)
logger?.didRegisterAlias(key)
}
catch {

}
}

private func transaction<T>(_ body: () -> T) -> T {
identityQueue.sync(flags: .barrier) {
let returnValue = body()
Expand Down Expand Up @@ -260,16 +272,15 @@ extension IdentityMap {
@discardableResult
public func update<T: Identifiable>(named: AliasKey<T>, modifiedAt: Stamp? = nil, update: Update<T>) -> Bool {
transaction {
guard let entity = refAliases[named].value else {
guard let aliasNode = refAliases[named], var content = aliasNode.ref.value.content else {
return false
}

var value = entity.ref.value
update(&value)
let node = nodeStore(entity: value, modifiedAt: modifiedAt)
update(&content)

// ref might have changed
refAliases.insert(node, key: named)
_ = nodeStore(entity: content, modifiedAt: modifiedAt)

storeAlias(content: content, key: named, modifiedAt: modifiedAt)

return true
}
Expand All @@ -282,16 +293,15 @@ extension IdentityMap {
@discardableResult
public func update<T: Aggregate>(named: AliasKey<T>, modifiedAt: Stamp? = nil, update: Update<T>) -> Bool {
transaction {
guard let entity = refAliases[named].value else {
guard let aliasNode = refAliases[named], var content = aliasNode.ref.value.content else {
return false
}

var value = entity.ref.value
update(&value)
let node = nodeStore(entity: value, modifiedAt: modifiedAt)
update(&content)

_ = nodeStore(entity: content, modifiedAt: modifiedAt)

// ref might have changed
refAliases.insert(node, key: named)
storeAlias(content: content, key: named, modifiedAt: modifiedAt)

return true
}
Expand All @@ -302,20 +312,18 @@ extension IdentityMap {
/// the change was applied
/// - Returns: true if entity exists and might be updated, false otherwise. The update might **not** be applied if modifiedAt is too old
@discardableResult
public func update<C: Collection>(named: AliasKey<C>, modifiedAt: Stamp? = nil, update: Update<[C.Element]>)
public func update<C: Collection>(named: AliasKey<C>, modifiedAt: Stamp? = nil, update: Update<C>)
-> Bool where C.Element: Identifiable {
transaction {
guard let entities = refAliases[named].value else {
guard let aliasNode = refAliases[named], var content = aliasNode.ref.value.content else {
return false
}

var values = entities.map(\.ref.value)
update(&values)
update(&content)

let nodes = values.map { nodeStore(entity: $0, modifiedAt: modifiedAt) }
_ = content.map { nodeStore(entity: $0, modifiedAt: modifiedAt) }

// update alias because `update` may have added/removed entities
refAliases.insert(nodes, key: named)
storeAlias(content: content, key: named, modifiedAt: modifiedAt)

return true
}
Expand All @@ -326,20 +334,18 @@ extension IdentityMap {
/// the change was applied
/// - Returns: true if entity exists and might be updated, false otherwise. The update might **not** be applied if modifiedAt is too old
@discardableResult
public func update<C: Collection>(named: AliasKey<C>, modifiedAt: Stamp? = nil, update: Update<[C.Element]>)
public func update<C: Collection>(named: AliasKey<C>, modifiedAt: Stamp? = nil, update: Update<C>)
-> Bool where C.Element: Aggregate {
transaction {
guard let entities = refAliases[named].value else {
guard let aliasNode = refAliases[named], var content = aliasNode.ref.value.content else {
return false
}

var values = entities.map(\.ref.value)
update(&values)
update(&content)

let nodes = values.map { nodeStore(entity: $0, modifiedAt: modifiedAt) }
_ = content.map { nodeStore(entity: $0, modifiedAt: modifiedAt) }

// update alias because `update` may have added/removed entities
refAliases.insert(nodes, key: named)
storeAlias(content: content, key: named, modifiedAt: modifiedAt)

return true
}
Expand All @@ -351,13 +357,13 @@ extension IdentityMap {
extension IdentityMap {
/// Removes an alias from the storage
public func removeAlias<T>(named: AliasKey<T>) {
refAliases.remove(for: named)
refAliases[named] = nil
logger?.didUnregisterAlias(named)
}

/// Removes an alias from the storage
public func removeAlias<C: Collection>(named: AliasKey<C>) {
refAliases.remove(for: named)
refAliases[named] = nil
logger?.didUnregisterAlias(named)
}

Expand Down
24 changes: 24 additions & 0 deletions Sources/CohesionKit/KeyPath/PartialIdentifiableKeyPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ public struct PartialIdentifiableKeyPath<Root> {
}
}

public init<C: MutableCollection>(_ keyPath: WritableKeyPath<Root, C?>) where C.Element: Identifiable, C.Index: Hashable {
self.keyPath = keyPath
self.accept = { parent, root, stamp, visitor in
if let entities = root[keyPath: keyPath] {
visitor.visit(
context: EntityContext(parent: parent, keyPath: keyPath.unwrapped(), stamp: stamp),
entities: entities
)
}
}
}

public init<C: MutableCollection>(_ keyPath: WritableKeyPath<Root, C>) where C.Element: Aggregate, C.Index: Hashable {
self.keyPath = keyPath
self.accept = { parent, root, stamp, visitor in
Expand All @@ -68,6 +80,18 @@ public struct PartialIdentifiableKeyPath<Root> {
}
}

public init<C: MutableCollection>(_ keyPath: WritableKeyPath<Root, C?>) where C.Element: Aggregate, C.Index: Hashable {
self.keyPath = keyPath
self.accept = { parent, root, stamp, visitor in
if let entities = root[keyPath: keyPath] {
visitor.visit(
context: EntityContext(parent: parent, keyPath: keyPath.unwrapped(), stamp: stamp),
entities: entities
)
}
}
}

public init<W: EntityEnumWrapper>(wrapper keyPath: WritableKeyPath<Root, W>) {
self.keyPath = keyPath
self.accept = { parent, root, stamp, visitor in
Expand Down
85 changes: 0 additions & 85 deletions Sources/CohesionKit/Observer/AliasObserver.swift

This file was deleted.

14 changes: 14 additions & 0 deletions Sources/CohesionKit/Observer/EntityObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ public struct EntityObserver<T>: Observer {
}
}

init<Wrapped>(alias node: EntityNode<AliasContainer<Wrapped>>, registry: ObserverRegistry)
where T == Optional<Wrapped> {
self.init(value: node.ref.value.content) { onChange in
registry.addObserver(node: node, initial: true, onChange: { container in
onChange(container.content)
})
}
}

init(value: T, createObserver: @escaping (@escaping OnChange) -> Subscription) {
self.value = value
self.createObserver = createObserver
}

public func observe(onChange: @escaping OnChange) -> Subscription {
createObserver(onChange)
}
Expand Down
39 changes: 39 additions & 0 deletions Sources/CohesionKit/Storage/AliasContainer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

/// a container to store an aliased object
struct AliasContainer<T>: Identifiable, Aggregate {
var id: String { key.name }

let key: AliasKey<T>

var content: T?
}

extension AliasContainer where T: Aggregate {
var nestedEntitiesKeyPaths: [PartialIdentifiableKeyPath<AliasContainer<T>>] {
[.init(\.content)]
}
}

extension AliasContainer where T: Identifiable {
var nestedEntitiesKeyPaths: [PartialIdentifiableKeyPath<AliasContainer<T>>] {
[.init(\.content)]
}
}

extension AliasContainer where T: MutableCollection, T.Element: Aggregate, T.Index: Hashable {
var nestedEntitiesKeyPaths: [PartialIdentifiableKeyPath<AliasContainer<T>>] {
[.init(\.content)]
}
}

extension AliasContainer where T: MutableCollection, T.Element: Identifiable, T.Index: Hashable {
var nestedEntitiesKeyPaths: [PartialIdentifiableKeyPath<AliasContainer<T>>] {
[.init(\.content)]
}
}

extension AliasContainer {
var nestedEntitiesKeyPaths: [PartialIdentifiableKeyPath<AliasContainer<T>>] {
[]
}
}
Loading

0 comments on commit 534112b

Please sign in to comment.