diff --git a/Sources/Verge/Library/InoutRef.swift b/Sources/Verge/Library/InoutRef.swift index 0de87996fe..4325f67d1f 100644 --- a/Sources/Verge/Library/InoutRef.swift +++ b/Sources/Verge/Library/InoutRef.swift @@ -39,7 +39,9 @@ public final class InoutRef { */ @dynamicMemberLookup public enum Modification: Hashable, CustomDebugStringConvertible { - case determinate(keyPaths: Set>) + case determinate( + keyPaths: Set> + ) case indeterminate public var debugDescription: String { @@ -89,7 +91,10 @@ public final class InoutRef { return .indeterminate } - return .determinate(keyPaths: nonatomic_modifiedKeyPaths) + return .determinate( + keyPaths: nonatomic_modifiedKeyPaths + ) + } private(set) var nonatomic_hasModified = false @@ -228,7 +233,14 @@ public final class InoutRef { } } - private func maskAsModified(on keyPath: PartialKeyPath) { + @inline(__always) + private func maskAsModified(on keyPath: KeyPath) { + nonatomic_modifiedKeyPaths.insert(keyPath) + nonatomic_hasModified = true + } + + @inline(__always) + private func maskAsModified(on keyPath: KeyPath) { nonatomic_modifiedKeyPaths.insert(keyPath) nonatomic_hasModified = true } diff --git a/Sources/Verge/Store/Changes.swift b/Sources/Verge/Store/Changes.swift index 06e0d7d5bd..c130114717 100644 --- a/Sources/Verge/Store/Changes.swift +++ b/Sources/Verge/Store/Changes.swift @@ -266,7 +266,7 @@ public final class Changes: @unchecked Sendable, ChangesType, // MARK: - Primitive methods extension Changes { - + /// Takes a composed value if it's changed from old value. @inline(__always) public func takeIfChanged( diff --git a/Sources/Verge/Store/Pipeline.swift b/Sources/Verge/Store/Pipeline.swift index 16635b2747..10561bab4b 100644 --- a/Sources/Verge/Store/Pipeline.swift +++ b/Sources/Verge/Store/Pipeline.swift @@ -112,7 +112,7 @@ extension _MapPipelineType { This will be helpful in performance. Therefore most type parameters require Equatable. */ public enum Pipelines { - + /// KeyPath based pipeline, light weight operation just take value from source. public struct SelectPipeline: _SelectPipelineType { @@ -131,6 +131,19 @@ public enum Pipelines { public func yieldContinuously(_ input: Input) -> ContinuousResult { +// if let modification = input.modification { +// switch modification { +// case .indeterminate: +// break +// case .determinate(_, let changesKeyPaths): +// +// if changesKeyPaths.contains(keyPath) == false { +// return .noUpdates +// } +// +// } +// } + guard let previous = input.previous else { return .new(input[keyPath: keyPath]) } @@ -220,12 +233,57 @@ public enum Pipelines { } } + + public struct BasicMapPipeline: _PipelineType { + + // MARK: - Properties + + public let map: (Input) -> Output + public let additionalDropCondition: ((Input) -> Bool)? + + public init( + map: @escaping (Input) -> Output, + additionalDropCondition: ((Input) -> Bool)? + ) { + self.map = map + self.additionalDropCondition = additionalDropCondition + } + + // MARK: - Functions + + public func yieldContinuously(_ input: Input) -> ContinuousResult { + + guard let additionalDropCondition = additionalDropCondition, additionalDropCondition(input) else { + return .new(yield(input)) + } + + return .noUpdates + + } + + public func yield(_ input: Input) -> Output { + map(input) + } + + public func drop(while predicate: @escaping (Input) -> Bool) -> Self { + return .init( + map: map, + additionalDropCondition: additionalDropCondition.map { currentCondition in + { input in + currentCondition(input) || predicate(input) + } + } ?? predicate + ) + } + + } } extension PipelineType { /** + For Changes input Produces output values using KeyPath-based projection. exactly same with ``PipelineType/select(_:)`` @@ -236,6 +294,7 @@ extension PipelineType { } /** + For Changes input Produces output values using KeyPath-based projection. exactly same with ``PipelineType/map(_:)-7xvom`` @@ -249,6 +308,7 @@ extension PipelineType { extension PipelineType { /** + For Changes input Produces output values using closure-based projection. `map` closure takes the value projected from `using` closure which is intermediate value. If the intermediate value is not changed, map closure won't perform. @@ -266,6 +326,7 @@ extension PipelineType { } /** + For Changes input Produces output values using closure-based projection. Using Edge as intermediate, output value will be unwrapped value from the Edge. */ @@ -281,6 +342,7 @@ extension PipelineType { } /** + For Changes input Produces output values using closure-based projection. ## 💡Tips diff --git a/Sources/Verge/Store/StoreMiddleware.swift b/Sources/Verge/Store/StoreMiddleware.swift index 4fa9827bf5..5d86619253 100644 --- a/Sources/Verge/Store/StoreMiddleware.swift +++ b/Sources/Verge/Store/StoreMiddleware.swift @@ -24,7 +24,7 @@ import Foundation /** Middleware enables us to do extra operations according to dispatched commits in Store. */ -open class StoreMiddleware { +open class StoreMiddleware { open func mutate(state: inout InoutRef) { @@ -42,7 +42,7 @@ open class StoreMiddleware { /** A closure-based middleware. It enables us to create middleware without creating sub-class. */ -public final class AnonymousStoreMiddleware: StoreMiddleware { +public final class AnonymousStoreMiddleware: StoreMiddleware { private let _mutate: (inout InoutRef) -> Void diff --git a/Sources/Verge/Utility/BatchCommit.swift b/Sources/Verge/Utility/BatchCommit.swift deleted file mode 100644 index fb8fced736..0000000000 --- a/Sources/Verge/Utility/BatchCommit.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// Copyright (c) 2019 muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import Foundation - -/** - A context to batch multiple mutations - */ -public struct BatchCommitContext { - - typealias Mutation = (MutationTrace, (inout InoutRef) -> Void) - - private(set) var mutations: [Mutation] = [] - - let scope: WritableKeyPath - - init(scope: WritableKeyPath) { - self.scope = scope - } - - /// Registers Mutation that created inline - public mutating func commit( - _ name: String = "", - _ file: StaticString = #file, - _ function: StaticString = #function, - _ line: UInt = #line, - mutation: @escaping (inout InoutRef) -> Void - ) { - - let trace = MutationTrace( - name: name, - file: file, - function: function, - line: line - ) - - mutations.append((trace, { [scope] state in - state.map(keyPath: scope) { (ref) -> Void in - mutation(&ref) - } - })) - - } - - /// Registers Mutation that created inline - public mutating func commit( - _ name: String = "", - _ file: StaticString = #file, - _ function: StaticString = #function, - _ line: UInt = #line, - scope: WritableKeyPath, - mutation: @escaping (inout InoutRef) -> Void - ) { - - let trace = MutationTrace( - name: name, - file: file, - function: function, - line: line - ) - - mutations.append((trace, { state in - state.map(keyPath: scope) { (ref: inout InoutRef) -> Void in - mutation(&ref) - } - })) - - } - - /// Registers Mutation that created inline - public mutating func commit( - _ name: String = "", - _ file: StaticString = #file, - _ function: StaticString = #function, - _ line: UInt = #line, - scope: WritableKeyPath, - mutation: @escaping (inout InoutRef?) -> Void - ) { - - let trace = MutationTrace( - name: name, - file: file, - function: function, - line: line - ) - - mutations.append((trace, { state in - state.map(keyPath: scope) { (ref: inout InoutRef?) -> Void in - mutation(&ref) - } - })) - - } - - -} - -extension DispatcherType { - - /** - Performs multiple commits at once. - From calling this method a transaction starts, register mutation into `BatchUpdate` instance. - If it has no mutations, batch updating won't be executed. - */ - @available(*, deprecated, message: "Use commit in favor of https://github.com/VergeGroup/Verge/pull/180") - public func batchCommit(_ perform: (_ context: inout BatchCommitContext) -> Void) { - - var context = BatchCommitContext(scope: scope) - - perform(&context) - - guard !context.mutations.isEmpty else { - return - } - - commit("BatchUpdating", scope: \.self) { (state) -> Void in - for mutation in context.mutations { - mutation.1(&state) - } - } - - } - -} diff --git a/Sources/VergeORM/Derived+ORM.swift b/Sources/VergeORM/Derived+ORM.swift index 1cb4b69284..09f2df4dd9 100644 --- a/Sources/VergeORM/Derived+ORM.swift +++ b/Sources/VergeORM/Derived+ORM.swift @@ -544,7 +544,7 @@ extension DatabaseContext { /// Experimental /// TODO: More performant public func _derivedQueriedEntities( - ids: @escaping (IndexesPropertyAdapter) -> any Collection, + ids: @escaping (IndexesPropertyAdapter) -> AnyCollection, queue: TargetQueueType = .passthrough ) -> Derived<[Entity.Derived]> { @@ -572,13 +572,13 @@ struct _DatabaseMultipleEntityPipeline - private let index: (IndexesPropertyAdapter) -> any Collection + private let index: (IndexesPropertyAdapter) -> AnyCollection private let storage: CachedMapStorage = .init(keySelector: \.raw) private let makeDerived: (Entity.EntityID) -> Entity.Derived init( keyPathToDatabase: KeyPath, - index: @escaping (IndexesPropertyAdapter) -> any Collection, + index: @escaping (IndexesPropertyAdapter) -> AnyCollection, makeDerived: @escaping (Entity.EntityID) -> Entity.Derived ) { diff --git a/Tests/VergeORMTests/DerivedCollectionTests.swift b/Tests/VergeORMTests/DerivedCollectionTests.swift index 457ef94313..3c62651c46 100644 --- a/Tests/VergeORMTests/DerivedCollectionTests.swift +++ b/Tests/VergeORMTests/DerivedCollectionTests.swift @@ -30,7 +30,7 @@ class DerivedCollectionTests: XCTestCase { } let d = store.databases.db._derivedQueriedEntities(ids: { index in - index.allAuthros.prefix(3) + .init(index.allAuthros.prefix(3)) }) // FIXME: this fails, since the middleware doesn't care the order @@ -86,7 +86,7 @@ class DerivedCollectionTests: XCTestCase { } let d = store.databases.db._derivedQueriedEntities(ids: { index in - index.allAuthros.filter { $0.raw.first == "1" } + .init(index.allAuthros.filter { $0.raw.first == "1" }) }) XCTAssertEqual(d.value.map { $0.value.wrapped?.entityID.raw }, ["1"]) @@ -118,7 +118,7 @@ class DerivedCollectionTests: XCTestCase { } let d = store.databases.db._derivedQueriedEntities(ids: { index in - index.allAuthros.filter { _ in return true } + .init(index.allAuthros.filter { _ in return true }) }) let tmp = d.value.primitive @@ -145,7 +145,7 @@ class DerivedCollectionTests: XCTestCase { var updateCount = 0 let d = store.databases.db._derivedQueriedEntities(ids: { index in - index.allAuthros.filter { _ in return true } + .init(index.allAuthros.filter { _ in return true }) }) d.sinkValue { _ in diff --git a/Tests/VergeTests/VergeStoreTests.swift b/Tests/VergeTests/VergeStoreTests.swift index b56061789c..6fd1ede41e 100644 --- a/Tests/VergeTests/VergeStoreTests.swift +++ b/Tests/VergeTests/VergeStoreTests.swift @@ -516,42 +516,8 @@ final class VergeStoreTests: XCTestCase { withExtendedLifetime(subscription) {} wait(for: [expect], timeout: 1) } - - func testBatchCommits() { - - let store1 = DemoStore() - - XCTAssertEqual(store1.state.version, 0) - - store1.batchCommit { (context) in - context.commit { - $0.count += 1 - } - } - - XCTAssertEqual(store1.state.version, 1) - - } - - func testBatchCommitsNoCommits() { - - let store1 = DemoStore() - - XCTAssertEqual(store1.state.version, 0) - - store1.batchCommit { (context) in - if false { - context.commit { - $0.count += 1 - } - } - } - - XCTAssertEqual(store1.state.version, 0) - - } - - func testChangesBetaMap() { + + func testMapIfPresent() { let store = Store() @@ -561,7 +527,7 @@ final class VergeStoreTests: XCTestCase { let state = store.state - if let _ = state._beta_map(\.optionalNested) { + if let _ = state.mapIfPresent(\.optionalNested) { XCTFail() }