Skip to content

Commit

Permalink
AsyncReducer (#313)
Browse files Browse the repository at this point in the history
* ⚡ WIP

* 🌲 Update

* 🌲 Update

* 🌲 Update

* 🌲 Update

* 🌲 Update

* 🌲 Update
  • Loading branch information
muukii authored Nov 6, 2022
1 parent fc3e533 commit 1d55741
Show file tree
Hide file tree
Showing 7 changed files with 359 additions and 2 deletions.
24 changes: 24 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/Verge-Package.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,20 @@
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "VergeTaskManager"
BuildableName = "VergeTaskManager"
BlueprintName = "VergeTaskManager"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
Expand Down Expand Up @@ -218,6 +232,16 @@
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "VergeTaskManagerTests"
BuildableName = "VergeTaskManagerTests"
BlueprintName = "VergeTaskManagerTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
Expand Down
9 changes: 9 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@
"version" : "6.5.0"
}
},
{
"identity" : "swift-atomics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-atomics.git",
"state" : {
"revision" : "919eb1d83e02121cdb434c7bfc1f0c66ef17febe",
"version" : "1.0.2"
}
},
{
"identity" : "swift-docc-plugin",
"kind" : "remoteSourceControl",
Expand Down
13 changes: 11 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ let package = Package(
],
products: [
.library(name: "Verge", targets: ["Verge"]),
.library(name: "VergeTaskManager", targets: ["VergeTaskManager"]),
.library(name: "VergeTiny", targets: ["VergeTiny"]),
.library(name: "VergeORM", targets: ["VergeORM"]),
.library(name: "VergeRx", targets: ["VergeRx"]),
Expand All @@ -19,11 +20,18 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/ReactiveX/RxSwift.git", from: "6.0.0"),
.package(url: "https://github.com/apple/swift-docc-plugin.git", branch: "main"),
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"),
],
targets: [
.target(
name: "VergeTaskManager",
dependencies: [
// .product(name: "Atomics", package: "swift-atomics")
]
),
.target(name: "VergeObjcBridge", dependencies: []),
.target(name: "VergeTiny", dependencies: []),
.target(name: "Verge", dependencies: ["VergeObjcBridge"]),
.target(name: "Verge", dependencies: ["VergeObjcBridge", "VergeTaskManager"]),
.target(
name: "VergeClassic",
dependencies: [
Expand All @@ -41,7 +49,8 @@ let package = Package(
.product(name: "RxCocoa", package: "RxSwift"),
]
),
// .testTarget(name: "AsyncVergeTests", dependencies: ["AsyncVerge"]),
// .testTarget(name: "AsyncVergeTests", dependencies: ["AsyncVerge"]),
.testTarget(name: "VergeTaskManagerTests", dependencies: ["VergeTaskManager"]),
.testTarget(name: "VergeClassicTests", dependencies: ["VergeClassic"]),
.testTarget(name: "VergeORMTests", dependencies: ["VergeORM"]),
.testTarget(name: "VergeRxTests", dependencies: ["VergeRx", "VergeClassic", "VergeORM"]),
Expand Down
40 changes: 40 additions & 0 deletions Sources/Verge/Library/AsyncReducer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Foundation
import VergeTaskManager

open class AsyncReducer<Result: Equatable, Partial>: StoreComponentType {

public let store: Store<Result, Never>

public init(initialResult: Result) {

self.store = .init(initialState: initialResult)

}

open func reduce(result: inout InoutRef<Result>, partial: Partial) {

}

public func task(
key: VergeTaskManager.TaskKey = .distinct(),
mode: VergeTaskManager.TaskManager.Mode = .dropCurrent,
priority: TaskPriority = .userInitiated,
_ operation: @Sendable @escaping () async -> Partial
) {

store.task(key: .distinct(), mode: .dropCurrent, priority: .userInitiated) { [weak self] in

let partial = await operation()

guard Task.isCancelled else {
return
}

self?.store.commit { state in
self?.reduce(result: &state, partial: partial)
}
}

}

}
23 changes: 23 additions & 0 deletions Sources/Verge/Store/Store.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import Foundation
import os.log
import VergeTaskManager

#if canImport(Combine)
import Combine
Expand Down Expand Up @@ -567,5 +568,27 @@ Latest Version (%d): (%@)

}

// MARK: - Task

public let taskManager: TaskManager = .init()

/**
Adds an asynchronous task to perform.

Use this function to perform an asynchronous task with a lifetime that matches that of this store.
If this store is deallocated ealier than the given task finished, that asynchronous task will be cancelled.

Carefully use this function - If the task retains this store, it will continue to live until the task is finished.
*/
public func task(
key: VergeTaskManager.TaskKey = .distinct(),
mode: VergeTaskManager.TaskManager.Mode = .dropCurrent,
priority: TaskPriority = .userInitiated,
_ action: @Sendable @escaping () async -> Void
) {

taskManager.task(key: key, mode: mode, priority: priority, action)

}

}
185 changes: 185 additions & 0 deletions Sources/VergeTaskManager/VergeTaskManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import Foundation

public protocol TaskKeyType {

}

public struct TaskKey: Hashable {

private struct TypedKey: Hashable {

static func == (lhs: Self, rhs: Self) -> Bool {
lhs.metatype == rhs.metatype
}

func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(metatype))
}

let metatype: Any.Type

init<T>(base: T.Type) {
self.metatype = base
}

}

private enum Node: Hashable {
case customString(String)
case type(TypedKey)
}

private let node: Node

public init<Key: TaskKeyType>(_ key: Key.Type) {
self.node = .type(.init(base: Key.self))
}

public init(_ customString: String) {
self.node = .customString(customString)
}

public static func distinct() -> Self {
.init(UUID().uuidString)
}

}

public final class TaskManager: @unchecked Sendable, CustomReflectable {

public struct Configuration {

public init() {

}
}

private struct TaskID: Hashable {
var raw: String

init(_ id: String) {
self.raw = id
}

static func distinct() -> Self {
.init(UUID().uuidString)
}
}

private struct DistinctID: Hashable {
let key: TaskKey
let internalID: TaskID
}

public enum Mode {
case dropCurrent
// case waitCurrent
}

// MARK: Lifecycle

public init(configuration: Configuration = .init()) {
self.configuration = configuration
}

deinit {
cancelAll()
}

// MARK: Public

public let configuration: Configuration

/// Number of counts in current managing tasks
public var count: Int {
tasks.count
}

public var customMirror: Mirror {
Mirror.init(
self,
children: [
("taskCount", tasks.count.description),
("tasks", tasks.description)
],
displayStyle: .struct,
ancestorRepresentation: .generated
)
}

public func isRunning(key: TaskKey) -> Bool {
return tasks.first(where: { $0.0.key == key }) != nil
}

public func task(
key: TaskKey,
mode: Mode,
priority: TaskPriority = .userInitiated,
_ action: @Sendable @escaping () async -> Void
) {

lock.lock()
defer {
lock.unlock()
}

let internalID = TaskID.distinct()

weak var weakSelf = self

let task = Task.detached(priority: priority) { [weakSelf] in

await withTaskCancellationHandler {
await action()
weakSelf?.unmanageTask(internalID: internalID)
} onCancel: {
weakSelf?.unmanageTask(internalID: internalID)
}

}

let anyTask = task as _Verge_TaskType

if let item = tasks.first(where: { $0.0.key == key }) {
switch mode {
case .dropCurrent:
unmanageTask(internalID: item.0.internalID)
item.1.cancel()
}
}

tasks.append((.init(key: key, internalID: internalID), anyTask))

}

public func cancelAll() {

lock.lock()
defer {
lock.unlock()
}

for (_, task) in tasks {
task.cancel()
}

tasks.removeAll()
}

// MARK: Private

private let lock = NSRecursiveLock()

private var tasks: ContiguousArray<(DistinctID, any _Verge_TaskType)> = .init()

private func unmanageTask(internalID: TaskID) {
tasks.removeAll { $0.0.internalID == internalID }
}

}

public protocol _Verge_TaskType {
func cancel()
}

extension _Concurrency.Task: _Verge_TaskType {}
Loading

0 comments on commit 1d55741

Please sign in to comment.