Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tech(registry): Add key parameter #70

Merged
merged 5 commits into from
Aug 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 39 additions & 11 deletions Sources/CohesionKit/Observer/ObserverRegistry.swift
Original file line number Diff line number Diff line change
@@ -1,23 +1,41 @@
import Foundation

/// a unique hash identifying an object
typealias ObjectKey = Int

extension ObjectKey {
init<T>(of type: T.Type, id: Any) {
let key = "\(type)-\(id)"

self.init(key.hashValue)
}

init<T>(_ node: EntityNode<T>) {
self.init(of: T.self, id: node.hashValue)
}
}

/// Registers observers associated to an ``EntityNode``.
/// The registry will handle notifying observers when a node is marked as changed
class ObserverRegistry {
private typealias Hash = Int

let queue: DispatchQueue
/// registered observer handlers
private var handlers: [Hash: Set<Handler>] = [:]
private var handlers: [ObjectKey: Set<Handler>] = [:]
/// nodes waiting for notifiying their observes about changes
private var pendingChanges: [Hash: AnyWeak] = [:]
private var pendingChanges: [ObjectKey: AnyWeak] = [:]

init(queue: DispatchQueue? = nil) {
self.queue = queue ?? DispatchQueue.main
}

func addObserver<T>(node: EntityNode<T>, initial: Bool = false, onChange: @escaping (T) -> Void) -> Subscription {
addObserver(node: node, key: ObjectKey(node), initial: initial, onChange: onChange)
}

/// register an observer to observe changes on an entity node. Everytime `ObserverRegistry` is notified about changes
/// to this node `onChange` will be called.
func addObserver<T>(node: EntityNode<T>, initial: Bool = false, onChange: @escaping (T) -> Void) -> Subscription {
func addObserver<T>(node: EntityNode<T>, key: ObjectKey, initial: Bool = false, onChange: @escaping (T) -> Void) -> Subscription {
let handler = Handler { onChange($0.ref.value) }

if initial {
Expand All @@ -31,7 +49,7 @@ class ObserverRegistry {
}
}

return subscribeHandler(handler, for: node)
return subscribeHandler(handler, for: node, key: key)
}

/// Add an observer handler to multiple nodes.
Expand All @@ -53,7 +71,7 @@ class ObserverRegistry {
}
}

let subscriptions = nodes.map { node in subscribeHandler(handler, for: node) }
let subscriptions = nodes.map { node in subscribeHandler(handler, for: node, key: ObjectKey(node)) }

return Subscription {
subscriptions.forEach { $0.unsubscribe() }
Expand All @@ -62,11 +80,19 @@ class ObserverRegistry {

/// Mark a node as changed. Observers won't be notified of the change until ``postChanges`` is called
func enqueueChange<T>(for node: EntityNode<T>) {
pendingChanges[node.hashValue] = Weak(value: node)
enqueueChange(for: node, key: ObjectKey(node))
}

func enqueueChange<T>(for node: EntityNode<T>, key: ObjectKey) {
pendingChanges[key] = Weak(value: node)
}

func hasPendingChange<T>(for node: EntityNode<T>) -> Bool {
pendingChanges[node.hashValue] != nil
hasPendingChange(for: ObjectKey(node))
}

func hasPendingChange(for key: ObjectKey) -> Bool {
pendingChanges[key] != nil
}

/// Notify observers of all queued changes. Once notified pending changes are cleared out.
Expand Down Expand Up @@ -97,12 +123,14 @@ class ObserverRegistry {
}
}

private func subscribeHandler<T>(_ handler: Handler, for node: EntityNode<T>) -> Subscription {
handlers[node.hashValue, default: []].insert(handler)
private func subscribeHandler<T>(_ handler: Handler, for node: EntityNode<T>, key: ObjectKey) -> Subscription {
handlers[key, default: []].insert(handler)

// subscription keeps a strong ref to node, avoiding it from being released somehow while suscription is running
return Subscription { [node] in
self.handlers[node.hashValue]?.remove(handler)
withExtendedLifetime(node) { }

self.handlers[key]?.remove(handler)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ platform :ios do
scheme: "Example",
configuration: "Debug",
build: true,
destination: "platform=iOS Simulator,name=iPhone SE (2nd generation)"
destination: "platform=iOS Simulator,name=iPhone SE (3rd generation)"
)
end
end
Loading