From d68e896d1a7c8f5b29d1da66cc59be6f1192cbf4 Mon Sep 17 00:00:00 2001 From: Hiroshi Kimura Date: Fri, 6 Sep 2024 09:15:04 +0900 Subject: [PATCH 1/2] TypedComparator (#491) --- Sources/Verge/Store/Changes.swift | 24 +++++++------- Sources/Verge/Utility/Edge.swift | 4 +-- ...Comparator.swift => TypedComparator.swift} | 31 +++++++++---------- ...ift => NormalizedStorageComparators.swift} | 6 ++-- .../DispatcherType+.swift | 8 ++--- Sources/VergeNormalizationDerived/Query.swift | 2 +- Sources/VergeRx/Store+Rx.swift | 10 +++--- Tests/VergeTests/ComparerTests.swift | 2 +- Tests/VergeTests/FilterTests.swift | 4 +-- 9 files changed, 45 insertions(+), 46 deletions(-) rename Sources/VergeComparator/{Comparator.swift => TypedComparator.swift} (74%) rename Sources/VergeNormalization/{Comparison.swift => NormalizedStorageComparators.swift} (87%) diff --git a/Sources/Verge/Store/Changes.swift b/Sources/Verge/Store/Changes.swift index 84802c195a..a05b5ce9ad 100644 --- a/Sources/Verge/Store/Changes.swift +++ b/Sources/Verge/Store/Changes.swift @@ -298,7 +298,7 @@ extension Changes { @inline(__always) public func takeIfChanged( _ compose: (Value) throws -> Composed, - _ comparer: some Comparison + _ comparer: some TypedComparator ) rethrows -> Composed? { try _takeIfChanged(compose, comparer) } @@ -306,7 +306,7 @@ extension Changes { @inline(__always) fileprivate func _takeIfChanged( _ compose: (Value) throws -> (repeat each Element), - _ comparer: consuming some Comparison<(repeat each Element)> + _ comparer: consuming some TypedComparator<(repeat each Element)> ) rethrows -> (repeat each Element)? { let current = self.primitive @@ -356,7 +356,7 @@ extension Changes { @inline(__always) fileprivate func _takeIfChanged_packed_nonEquatable( _ compose: (Value) throws -> (repeat each Element), - comparator: some Comparison<(repeat each Element)> + comparator: some TypedComparator<(repeat each Element)> ) rethrows -> (repeat each Element)? { let current = self.primitive @@ -391,7 +391,7 @@ extension Changes { @inline(__always) public func ifChanged( _ compose: (Value) -> Composed, - _ comparer: some Comparison, + _ comparer: some TypedComparator, _ perform: (Composed) throws -> Result ) rethrows -> Result? { guard let result = takeIfChanged(compose, comparer) else { @@ -419,7 +419,7 @@ extension Changes { @available(*, deprecated, message: "Use another function that returns IfChangedBox") public func ifChanged( _ selector: ChangesKeyPath, - _ comparer: some Comparison, + _ comparer: some TypedComparator, _ perform: (T) throws -> Result ) rethrows -> Result? { guard let value = takeIfChanged({ $0[keyPath: selector] }, comparer) else { @@ -459,7 +459,7 @@ extension Changes { public borrowing func ifChanged( _ compose: (borrowing Value) -> (repeat each Element), - comparator: some Comparison<(repeat each Element)> + comparator: some TypedComparator<(repeat each Element)> ) -> IfChangedBox<(repeat each Element)> { guard let result = _takeIfChanged_packed_nonEquatable(compose, comparator: comparator) else { return .init() @@ -611,14 +611,14 @@ extension Changes { /// Returns boolean that indicates value specified by keyPath contains changes with compared old and new. @inline(__always) public func hasChanges(_ keyPath: ChangesKeyPath) -> Bool { - hasChanges(keyPath, EqualityComparison()) + hasChanges(keyPath, EqualityComparator()) } /// Returns boolean that indicates value specified by keyPath contains changes with compared old and new. @inline(__always) public func hasChanges( _ keyPath: ChangesKeyPath, - _ comparer: some Comparison + _ comparer: some TypedComparator ) -> Bool { hasChanges({ $0[keyPath: keyPath] }, comparer) } @@ -627,13 +627,13 @@ extension Changes { public func hasChanges( _ compose: (Value) -> Composed ) -> Bool { - hasChanges(compose, EqualityComparison()) + hasChanges(compose, EqualityComparator()) } @inline(__always) public func hasChanges( _ compose: (Value) -> Composed, - _ comparer: some Comparison + _ comparer: some TypedComparator ) -> Bool { takeIfChanged(compose, comparer) != nil } @@ -646,12 +646,12 @@ extension Changes { /// Returns boolean that indicates value specified by keyPath contains **NO** changes with compared old and new. @inline(__always) public func noChanges(_ keyPath: ChangesKeyPath) -> Bool { - !hasChanges(keyPath, EqualityComparison()) + !hasChanges(keyPath, EqualityComparator()) } /// Returns boolean that indicates value specified by keyPath contains **NO** changes with compared old and new. @inline(__always) - public func noChanges(_ keyPath: ChangesKeyPath, _ comparer: some Comparison) -> Bool { + public func noChanges(_ keyPath: ChangesKeyPath, _ comparer: some TypedComparator) -> Bool { !hasChanges(keyPath, comparer) } } diff --git a/Sources/Verge/Utility/Edge.swift b/Sources/Verge/Utility/Edge.swift index 20c913dccb..b8da6bd81b 100644 --- a/Sources/Verge/Utility/Edge.swift +++ b/Sources/Verge/Utility/Edge.swift @@ -228,7 +228,7 @@ extension Edge where Value : Equatable { extension Edge { - public struct VersionComparison: Comparison { + public struct VersionComparison: TypedComparator { public func callAsFunction(_ lhs: Edge, _ rhs: Edge) -> Bool { lhs.globalID == rhs.globalID && lhs.version == rhs.version @@ -237,7 +237,7 @@ extension Edge { } -extension Comparison { +extension TypedComparator { public static func versionEquals() -> Self where Self == Edge.VersionComparison { .init() diff --git a/Sources/VergeComparator/Comparator.swift b/Sources/VergeComparator/TypedComparator.swift similarity index 74% rename from Sources/VergeComparator/Comparator.swift rename to Sources/VergeComparator/TypedComparator.swift index 101b640e1c..288c13c4f8 100644 --- a/Sources/VergeComparator/Comparator.swift +++ b/Sources/VergeComparator/TypedComparator.swift @@ -22,8 +22,7 @@ import Foundation import os.log -// TODO: will rename as Comparator -public protocol Comparison: Sendable { +public protocol TypedComparator: Sendable { associatedtype Input @Sendable @@ -32,49 +31,49 @@ public protocol Comparison: Sendable { struct NotEqual: Error {} -extension Comparison { +extension TypedComparator { - public static func equality() -> Self where Self == EqualityComparison { + public static func equality() -> Self where Self == EqualityComparator { .init() } /** TODO: Use typed comparison instead of AnyEqualityComparison. */ - public static func equality() -> Self where Self == AnyEqualityComparison<(repeat each T)> { + public static func equality() -> Self where Self == AnyEqualityComparator<(repeat each T)> { return .init { a, b in areEqual((repeat each a), (repeat each b)) } } - public static func any(_ isEqual: @escaping @Sendable (T, T) -> Bool) -> Self where Self == AnyEqualityComparison { + public static func any(_ isEqual: @escaping @Sendable (T, T) -> Bool) -> Self where Self == AnyEqualityComparator { .init(isEqual) } - public static func any(selector: @escaping @Sendable (T) -> U) -> Self where Self == AnyEqualityComparison { + public static func any(selector: @escaping @Sendable (T) -> U) -> Self where Self == AnyEqualityComparator { .init { selector($0) == selector($1) } } - public static func alwaysFalse() -> Self where Self == FalseComparison { + public static func alwaysFalse() -> Self where Self == FalseComparator { .init() } } -extension Comparison { +extension TypedComparator { - public func and(_ otherExpression: C) -> AndComparison { + public func and(_ otherExpression: C) -> AndComparator { .init(self, otherExpression) } - public func or(_ otherExpression: C) -> OrComparison { + public func or(_ otherExpression: C) -> OrComparator { .init(self, otherExpression) } } -public struct FalseComparison: Comparison { +public struct FalseComparator: TypedComparator { public init() {} public func callAsFunction(_ lhs: Input, _ rhs: Input) -> Bool { @@ -82,7 +81,7 @@ public struct FalseComparison: Comparison { } } -public struct EqualityComparison: Comparison { +public struct EqualityComparator: TypedComparator { public init() {} @@ -93,7 +92,7 @@ public struct EqualityComparison: Comparison { } -public struct AnyEqualityComparison: Comparison { +public struct AnyEqualityComparator: TypedComparator { private let closure: @Sendable (Input, Input) -> Bool @@ -107,7 +106,7 @@ public struct AnyEqualityComparison: Comparison { } -public struct AndComparison: Comparison where C1.Input == Input, C2.Input == Input { +public struct AndComparator: TypedComparator where C1.Input == Input, C2.Input == Input { public let c1: C1 public let c2: C2 @@ -122,7 +121,7 @@ public struct AndComparison: Comparison w } } -public struct OrComparison: Comparison where C1.Input == Input, C2.Input == Input { +public struct OrComparator: TypedComparator where C1.Input == Input, C2.Input == Input { public let c1: C1 public let c2: C2 diff --git a/Sources/VergeNormalization/Comparison.swift b/Sources/VergeNormalization/NormalizedStorageComparators.swift similarity index 87% rename from Sources/VergeNormalization/Comparison.swift rename to Sources/VergeNormalization/NormalizedStorageComparators.swift index ca559895bc..5eee323d22 100644 --- a/Sources/VergeNormalization/Comparison.swift +++ b/Sources/VergeNormalization/NormalizedStorageComparators.swift @@ -1,9 +1,9 @@ import VergeComparator -public enum NormalizedStorageComparisons { +public enum NormalizedStorageComparators { /// True indicates database is not changed - public struct StorageComparison: Comparison { + public struct StorageComparator: TypedComparator { public typealias Input = Storage public init() {} @@ -14,7 +14,7 @@ public enum NormalizedStorageComparisons { } /// Returns true if the table of the entity in database has no changes. - public struct TableComparison: Comparison { + public struct TableComparator: TypedComparator { public typealias Input = Table diff --git a/Sources/VergeNormalizationDerived/DispatcherType+.swift b/Sources/VergeNormalizationDerived/DispatcherType+.swift index d19da2eab3..da534f38a7 100644 --- a/Sources/VergeNormalizationDerived/DispatcherType+.swift +++ b/Sources/VergeNormalizationDerived/DispatcherType+.swift @@ -357,11 +357,11 @@ where _StorageSelector.Storage == _TableSelector.Storage { return .new(yield(input)) } - if NormalizedStorageComparisons.StorageComparison()(selector.storage(source: input.primitive), selector.storage(source: previous.primitive)) { + if NormalizedStorageComparators.StorageComparator()(selector.storage(source: input.primitive), selector.storage(source: previous.primitive)) { return .noUpdates } - if NormalizedStorageComparisons.TableComparison<_TableSelector.Table>()(selector.table(source: input.primitive), selector.table(source: previous.primitive)) { + if NormalizedStorageComparators.TableComparator<_TableSelector.Table>()(selector.table(source: input.primitive), selector.table(source: previous.primitive)) { return .noUpdates } @@ -418,11 +418,11 @@ where _StorageSelector.Storage == _TableSelector.Storage { return .new(yield(input)) } - if NormalizedStorageComparisons.StorageComparison()(selector.storage(source: input.primitive), selector.storage(source: previous.primitive)) { + if NormalizedStorageComparators.StorageComparator()(selector.storage(source: input.primitive), selector.storage(source: previous.primitive)) { return .noUpdates } - if NormalizedStorageComparisons.TableComparison<_TableSelector.Table>()(selector.table(source: input.primitive), selector.table(source: previous.primitive)) { + if NormalizedStorageComparators.TableComparator<_TableSelector.Table>()(selector.table(source: input.primitive), selector.table(source: previous.primitive)) { return .noUpdates } diff --git a/Sources/VergeNormalizationDerived/Query.swift b/Sources/VergeNormalizationDerived/Query.swift index 80fe7a0357..86693612ea 100644 --- a/Sources/VergeNormalizationDerived/Query.swift +++ b/Sources/VergeNormalizationDerived/Query.swift @@ -34,7 +34,7 @@ struct QueryPipeline< } // check if the storage has been updated - if NormalizedStorageComparisons.StorageComparison()(storageSelector.select(source: input.primitive), storageSelector.select(source: previous.primitive)) { + if NormalizedStorageComparators.StorageComparator()(storageSelector.select(source: input.primitive), storageSelector.select(source: previous.primitive)) { return .noUpdates } diff --git a/Sources/VergeRx/Store+Rx.swift b/Sources/VergeRx/Store+Rx.swift index 65aa0567d5..88c627a28d 100644 --- a/Sources/VergeRx/Store+Rx.swift +++ b/Sources/VergeRx/Store+Rx.swift @@ -119,8 +119,8 @@ extension ObservableType where Element : ChangesType { /// - selector: /// - compare: /// - Returns: Returns an observable sequence that contains only changed elements according to the `comparer`. - public func changed(_ selector: @escaping (Element.Value) -> S, _ comparer: some Comparison) -> Observable { - + public func changed(_ selector: @escaping (Element.Value) -> S, _ comparer: some TypedComparator) -> Observable { + return flatMap { changes -> Observable in let _r = changes.asChanges().ifChanged(selector, comparer) { value in return value @@ -138,7 +138,7 @@ extension ObservableType where Element : ChangesType { /// - comparer: /// - Returns: Returns an observable sequence that contains only changed elements according to the `comparer`. public func changed(_ selector: @escaping (Element.Value) -> S) -> Observable { - return changed(selector, EqualityComparison()) + return changed(selector, EqualityComparator()) } /// Returns an observable sequence that contains only changed elements according to the `comparer`. @@ -149,7 +149,7 @@ extension ObservableType where Element : ChangesType { /// - selector: /// - compare: /// - Returns: Returns an observable sequence that contains only changed elements according to the `comparer`. - public func changedDriver(_ selector: @escaping (Element.Value) -> S, _ comparer: some Comparison) -> Driver { + public func changedDriver(_ selector: @escaping (Element.Value) -> S, _ comparer: some TypedComparator) -> Driver { changed(selector, comparer) .asDriver(onErrorRecover: { _ in .empty() }) } @@ -163,7 +163,7 @@ extension ObservableType where Element : ChangesType { /// - comparer: /// - Returns: Returns an observable sequence that contains only changed elements according to the `comparer`. public func changedDriver(_ selector: @escaping (Element.Value) -> S) -> Driver { - return changedDriver(selector, EqualityComparison()) + return changedDriver(selector, EqualityComparator()) } } diff --git a/Tests/VergeTests/ComparerTests.swift b/Tests/VergeTests/ComparerTests.swift index e14f490719..50cc636416 100644 --- a/Tests/VergeTests/ComparerTests.swift +++ b/Tests/VergeTests/ComparerTests.swift @@ -26,7 +26,7 @@ final class ComparerTests: XCTestCase { func testPerfomance_new() { - let base = EqualityComparison() + let base = EqualityComparator() measure(metrics: [XCTMemoryMetric(), XCTCPUMetric()]) { for _ in 0..<10000 { diff --git a/Tests/VergeTests/FilterTests.swift b/Tests/VergeTests/FilterTests.swift index af5f849e1a..0549f44580 100644 --- a/Tests/VergeTests/FilterTests.swift +++ b/Tests/VergeTests/FilterTests.swift @@ -22,7 +22,7 @@ final class FilterTests: XCTestCase { var c = 0 } - let comparison = OrComparison( + let comparison = OrComparator( .any { @Sendable in $0.a == 1 && $1.a == 1 }, .any { @Sendable in $0.b == 1 && $1.b == 1 } ) @@ -60,7 +60,7 @@ final class FilterTests: XCTestCase { var c = 0 } - let comparison = AndComparison( + let comparison = AndComparator( .any { @Sendable in $0.a == 1 && $1.a == 1 }, .any { @Sendable in $0.b == 1 && $1.b == 1 } ) From 073e699c3fddbc480a83df2d02dbc94296582ae2 Mon Sep 17 00:00:00 2001 From: Hiroshi Kimura Date: Thu, 12 Sep 2024 16:21:38 +0900 Subject: [PATCH 2/2] fix memory leak (#492) --- Sources/Verge/Library/StoreStateSubscription.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Verge/Library/StoreStateSubscription.swift b/Sources/Verge/Library/StoreStateSubscription.swift index d62afd605f..d32d5a64ab 100644 --- a/Sources/Verge/Library/StoreStateSubscription.swift +++ b/Sources/Verge/Library/StoreStateSubscription.swift @@ -167,5 +167,6 @@ public final class StoreStateSubscription: Hashable, Cancellable, @unchecked Sen deinit { cancel() + _ = source.dispose() } }