From 2be0ee8becc1a3887d17a795dba3b8df9aa4ed0b Mon Sep 17 00:00:00 2001 From: chloe-yeo Date: Fri, 5 Sep 2025 16:24:56 -0700 Subject: [PATCH 01/15] add v6 update + rename file to ProgressManager --- ...s-reporter.md => 0023-progress-manager.md} | 726 +++++++++++++++--- 1 file changed, 598 insertions(+), 128 deletions(-) rename Proposals/{0023-progress-reporter.md => 0023-progress-manager.md} (64%) diff --git a/Proposals/0023-progress-reporter.md b/Proposals/0023-progress-manager.md similarity index 64% rename from Proposals/0023-progress-reporter.md rename to Proposals/0023-progress-manager.md index c41810f60..195afffd9 100644 --- a/Proposals/0023-progress-reporter.md +++ b/Proposals/0023-progress-manager.md @@ -28,18 +28,24 @@ - Introduced `ProgressReporter` type and `assign(count:to:)` for alternative use cases, including multi-parent support - Specified Behavior of `ProgressManager` for `Task` cancellation - Redesigned implementation of custom properties to support both holding values of custom property of `self` and of descendants, and multi-parent support - - Introduced `values(of:)` and `total(of:)` methods to dislay and aggregate values of custom properties in a subtree + - Introduced `values(of:)` and `total(of:)` methods to display and aggregate values of custom properties in a subtree - Restructured examples in Proposed Solution to show the use of `Subprogress` and `ProgressReporter` in different cases and enforce use of `subprogress` as parameter label for methods reporting progress and use of `progressReporter` as property name when returning `ProgressReporter` from a library - Expanded Future Directions - Expanded Alternatives Considered - Moving `FormatStyle` to separate future proposal * **v5** Minor Updates: - - Renamed `manager(totalCount:)` method to `start(totalCount)` + - Renamed `manager(totalCount:)` method to `start(totalCount:)` - Changed the return type of `values(of:)` to be an array of non-optional values - Clarified cycle-detection behavior in `assign(count:to:)` at runtime - Added `CustomStringConvertible` and `CustomDebugStringConvertible` conformance to `ProgressManager` and `ProgressReporter` - Expanded Future Directions - Expanded Alternatives Considered +* **v6** Minor Updates: + - Changed behavior of API so that additional properties are restricted to either `Int`, `Double`, `String?`, `URL?`, or `UInt64` types instead of `any Sendable` types + - Added requirements to `ProgressManager.Property` protocol to define summarization and termination (deinit) behavior + - Added overloads for `subscript(dynamicMember:)` in `ProgressManager.Values` to account for currently-allowed types + - Removed `values(of:)` method + - Replaced `total(of:)` with overloads for `summary(of:)` to account for all available types ## Table of Contents @@ -66,7 +72,7 @@ This proposal aims to introduce a new Progress Reporting API —— `ProgressMan 3. **Error-Resistant Architecture**: One common mistake/footgun when it comes to progress reporting is reusing the [same progress reporting instance](#advantages-of-using-subprogress-as-currency-type). This tends to lead to mistakenly overwriting its expected unit of work after previous caller has set it, or "over completing" / "double finishing" the report after it's been completed. This API prevents this by introducing strong types with different roles. Additionally, it handles progress delegation, accumulation, and nested reporting automatically, eliminating race conditions and progress calculation errors. -4. **Decoupled Progress and Task Control**: This API focuses exclusively on progress reporting, clearly separating it from task control mechanisms like cancellation, which remain the responsibility of Swift's native concurrency primitives for a more coherent programming model. While this API does not assume any control over tasks, it needs to be consistently handling non-completion of progress so it will react to cancellation by completing the progress upon `deinit`. +4. **Decoupled Progress and Task Control**: This API focuses exclusively on progress reporting, clearly separating it from task control mechanisms like cancellation, which remain the responsibility of Swift's native concurrency primitives for a more coherent programming model. While this API does not assume any control over tasks, it needs to consistently handle non-completion of progress so it will react to cancellation by completing the progress upon `deinit`. 5. **Swift Observation Framework Support**: This API leverages the `@Observable` macro to make progress information automatically bindable to UI components, enabling reactive updates with minimal boilerplate code. The Observation framework also provides a way for developers to observe values of `@Observable` APIs via `AsyncSequence`. @@ -115,7 +121,7 @@ public func makeSalad() async { public func chopFruits() async -> Progress {} ``` -We are forced to await the `chopFruits()` call before returning the `Progress` instance. However, the `Progress` instance that is returned from `chopFruits` already has its `completedUnitCount` equal to `totalUnitCount`. Since the `chopSubprogress` would have been completed before being added as a child to its parent `Progress`, it fails to show incremental progress as the code runs to completion within the method. +We are forced to await the `chopFruits()` call before receiving the `Progress` instance. However, the `Progress` instance that is returned from `chopFruits` already has its `completedUnitCount` equal to `totalUnitCount`. Since the `chopSubprogress` would have been completed before being added as a child to its parent `Progress`, it fails to show incremental progress as the code runs to completion within the method. While it may be possible to use the existing `Progress` to report progress in an `async` function to show incremental progress, by passing `Progress` as an argument to the function reporting progress, it is more error-prone, as shown below: @@ -352,30 +358,70 @@ deadlineTracker.assign(count: 1, to: examCountdown.progressReporter) ### Reporting Progress With Type-Safe Custom Properties -You can define additional properties specific to the operations you are reporting progress on with `@dynamicMemberLookup`. For instance, we pre-define additional file-related properties on `ProgressManager` by extending `ProgressManager` for reporting progress on file operations. +You can define additional properties specific to the operations you are reporting progress on with `@dynamicMemberLookup`. -We can declare a custom additional property as follows: - -```swift -struct Filename: ProgressManager.Property { - typealias Value = String +The currently allowed (Value, Summary) pairs for custom properties are: +- (`Int`, `Int`) +- (`Double`, `Double`) +- (`String?`, `[String?]`) +- (`URL?`, `[URL?]`) +- (`UInt64`, `[UInt64]`) - static var defaultValue: String { "" } -} +You can declare a custom additional property that has a `String?` `Value` type and `[String?]` `Summary` type as follows: +```swift extension ProgressManager.Properties { var filename: Filename.Type { Filename.self } + + struct Filename: Sendable, ProgressManager.Property { + + typealias Value = String? + + typealias Summary = [String?] + + static var key: String { return "ExampleApp.Filename" } + + static var defaultValue: String? { return nil } + + static var defaultSummary: [String?] { return [] } + + static func reduce(into summary: inout [String?], value: String?) { + summary.append(value) + } + + static func merge(_ summary1: [String?], _ summary2: [String?]) -> [String?] { + summary1 + summary2 + } + + static func finalSummary(_ parentSummary: [String?], _ selfSummary: [String?]) -> [String?] { + parentSummary + } + } } ``` You can report custom properties using `ProgressManager` as follows: ```swift -let manager: ProgressManager = ... +let manager: ProgressManager = ProgressManager(totalCount: 2) manager.withProperties { properties in + properties.completedCount = 1 // set completed count in closure, equivalent to calling `complete(count: 1)` properties.filename = "Capybara.jpg" // using self-defined custom property properties.totalByteCount = 1000000 // using pre-defined file-related property } + +await doSomething(subprogress: manager.subprogress(assigningCount: 1)) + +func doSomething(subprogress: consuming Subprogress? = nil) async { + let manager = subprogress?.start(totalCount: 1) + + manager?.withProperties { properties in + properties.filename = "Snail.jpg" // use self-defined custom property in child `ProgressManager` + } +} + +let filenames = manager.summary(of: ProgressManager.Properties.Filename.self) // get summary of filename in subtree +print(filenames) // ["Capybara.jpg", "Snail.jpg"] ``` ### Interoperability with Existing `Progress` @@ -453,7 +499,7 @@ overall.addChild(subprogressThree, withPendingUnitCount: 1) ```swift /// An object that conveys ongoing progress to the user for a specified task. -@available(FoundationPreview 6.2, *) +@available(FoundationPreview 6.2.3, *) @Observable public final class ProgressManager : Sendable, Hashable, Equatable, CustomStringConvertible, CustomDebugStringConvertible { /// The total units of work. @@ -485,28 +531,6 @@ overall.addChild(subprogressThree, withPendingUnitCount: 1) /// A debug description. public var debugDescription: String { get } - /// A type that conveys additional task-specific information on progress. - public protocol Property { - - associatedtype Value : Sendable - - /// The default value to return when property is not set to a specific value. - static var defaultValue: Value { get } - } - - /// A container that holds values for properties that convey information about progress. - @dynamicMemberLookup public struct Values : Sendable { - - /// The total units of work. - public var totalCount: Int? { mutating get set } - - /// The completed units of work. - public var completedCount: Int { mutating get set } - - /// Returns a property value that a key path indicates. If value is not defined, returns property's `defaultValue`. - public subscript(dynamicMember key: KeyPath) -> Property.Value { get set } - } - /// Initializes `self` with `totalCount`. /// /// If `totalCount` is set to `nil`, `self` is indeterminate. @@ -527,32 +551,13 @@ overall.addChild(subprogressThree, withPendingUnitCount: 1) /// If a cycle is detected, this will cause a crash at runtime. /// /// - Parameters: - /// - output: A `ProgressReporter` instance. - /// - count: The portion of `totalCount` to be delegated to the `ProgressReporter`. + /// - count: Number of units delegated from `self`'s `totalCount` to `reporter`. + /// - reporter: A `ProgressReporter` instance. public func assign(count: Int, to reporter: ProgressReporter) /// Increases `completedCount` by `count`. /// - Parameter count: Units of work. public func complete(count: Int) - - /// Accesses or mutates any properties that convey additional information about progress. - public func withProperties( - _ closure: (inout sending Values) throws(E) -> sending T - ) throws(E) -> sending T - - /// Returns an array of values for specified property in subtree. - /// - /// - Parameter property: Type of property. - /// - Returns: Array of values for property. - public func values(of property: P.Type) -> [P.Value] - - /// Returns the aggregated result of values where type of property is `AdditiveArithmetic`. - /// All values are added together. - /// - /// - Parameters: - /// - property: Type of property. - /// - values: Sum of values. - public func total(of property: P.Type) -> P.Value where P.Value : AdditiveArithmetic } ``` @@ -563,7 +568,7 @@ You call `ProgressManager`'s `subprogress(assigningCount:)` to create a `Subprog The callee will consume `Subprogress` and get the `ProgressManager` by calling `start(totalCount:)`. That `ProgressManager` is used for the function's own progress updates. ```swift -@available(FoundationPreview 6.2, *) +@available(FoundationPreview 6.2.3, *) /// Subprogress is used to establish parent-child relationship between two instances of `ProgressManager`. /// /// Subprogress is returned from a call to `subprogress(assigningCount:)` by a parent ProgressManager. @@ -578,63 +583,106 @@ public struct Subprogress: ~Copyable, Sendable { } ``` -### `ProgressReporter` +### `ProgressManager.Property` -```swift -@available(FoundationPreview 6.2, *) -/// ProgressReporter is used to observe progress updates from a `ProgressManager`. It may also be used to incorporate those updates into another `ProgressManager`. -/// -/// It is read-only and can be added as a child of another ProgressManager. -@Observable public final class ProgressReporter : Sendable, CustomStringConvertible, CustomDebugStringConvertible { - - /// The total units of work. - public var totalCount: Int? { get } - - /// The completed units of work. - /// If `self` is indeterminate, the value will be 0. - public var completedCount: Int { get } - - /// The proportion of work completed. - /// This takes into account the fraction completed in its children instances if children are present. - /// If `self` is indeterminate, the value will be 0. - public var fractionCompleted: Double { get } - - /// The state of initialization of `totalCount`. - /// If `totalCount` is `nil`, the value will be `true`. - public var isIndeterminate: Bool { get } +`ProgressManager` contains a protocol `Property` that outlines the requirements for declaring a custom additional property. The currently allowed (Value, Summary) pairs are as follows: +- (`Int`, `Int`) +- (`Double`, `Double`) +- (`String?`, `[String?]`) +- (`URL?`, `[URL?]`) +- (`UInt64`, `[UInt64]`) - /// The state of completion of work. - /// If `completedCount` >= `totalCount`, the value will be `true`. - public var isFinished: Bool { get } - - /// A description. - public var description: String { get } - - /// A debug description. - public var debugDescription: String { get } +This list of allowed (Value, Summary) pairs may be expanded in the future. Based on pre-existing use cases of additional properties on `Progress`, we believe that the currently allowed (Value, Summary) pairs should suffice for most use cases. - /// Reads properties that convey additional information about progress. - public func withProperties( - _ closure: (sending ProgressManager.Values) throws(E) -> sending T - ) throws(E) -> T - - /// Returns an array of values for specified additional property in subtree. - /// The specified property refers to a declared type representing additional progress-related properties - /// that conform to the `ProgressManager.Property` protocol. - /// - /// - Parameter property: Type of property. - /// - Returns: Array of values for property. - public func values(of property: P.Type) -> [P.Value] +```swift +@available(FoundationPreview 6.2.3, *) +extension ProgressManager { - /// Returns the aggregated result of values for specified `AdditiveArithmetic` property in subtree. - /// The specified property refers to a declared type representing additional progress-related properties - /// that conform to the `ProgressManager.Property` protocol. - /// The specified property also has to be an `AdditiveArithmetic`. For non-`AdditiveArithmetic` types, you should - /// write your own method to aggregate values. + /// A type that conveys additional task-specific information on progress. /// - /// - Parameters property: Type of property. - /// - Returns: Aggregated result of values for property. - public func total(of property: P.Type) -> P.Value where P.Value : AdditiveArithmetic + /// The `Property` protocol defines custom properties that can be associated with progress tracking. + /// These properties allow you to store and aggregate additional information alongside the + /// standard progress metrics such as `totalCount` and `completedCount`. + public protocol Property: SendableMetatype { + + /// The type used for individual values of this property. + /// + /// This associated type represents the type of property values + /// that can be set on progress managers. Must be `Sendable` and `Equatable`. + /// The currently allowed types are `Int`, `Double`, `String?`, `URL?` or `UInt64`. + associatedtype Value: Sendable, Equatable + + /// The type used for aggregated summaries of this property. + /// + /// This associated type represents the type used when summarizing property values + /// across multiple progress managers in a subtree. + /// The currently allowed types are `Int`, `Double`, `[String?]`, `[URL?]` or `[UInt64]`. + associatedtype Summary: Sendable, Equatable + + /// A unique identifier for this property type. + /// + /// The key should use reverse DNS style notation to ensure uniqueness across different + /// frameworks and applications. + /// + /// - Returns: A unique string identifier for this property type. + static var key: String { get } + + /// The default value to return when property is not set to a specific value. + /// + /// This value is used when a progress manager doesn't have an explicit value set + /// for this property type. + /// + /// - Returns: The default value for this property type. + static var defaultValue: Value { get } + + /// The default summary value for this property type. + /// + /// This value is used as the initial summary when no property values have been + /// aggregated yet. + /// + /// - Returns: The default summary value for this property type. + static var defaultSummary: Summary { get } + + /// Reduces a property value into an accumulating summary. + /// + /// This method is called to incorporate individual property values into a summary + /// that represents the aggregated state across multiple progress managers. + /// + /// - Parameters: + /// - summary: The accumulating summary value to modify. + /// - value: The individual property value to incorporate into the summary. + static func reduce(into summary: inout Summary, value: Value) + + /// Merges two summary values into a single combined summary. + /// + /// This method is called to combine summary values from different branches + /// of the progress manager hierarchy into a unified summary. + /// + /// - Parameters: + /// - summary1: The first summary to merge. + /// - summary2: The second summary to merge. + /// - Returns: A new summary that represents the combination of both input summaries. + static func merge(_ summary1: Summary, _ summary2: Summary) -> Summary + + /// Determines how to handle summary data when a progress manager is deinitialized. + /// + /// This method is used when a progress manager in the hierarchy is being + /// deinitialized and its accumulated summary needs to be processed in relation to + /// its parent's summary. The behavior can vary depending on the property type: + /// + /// - For additive properties (like file counts, byte counts): The self summary + /// is typically added to the parent summary to preserve the accumulated progress. + /// - For max-based properties (like estimated time remaining): The parent summary + /// is typically preserved as it represents an existing estimate. + /// - For collection-based properties (like file URLs): The self summary may be + /// discarded to avoid accumulating stale references. + /// + /// - Parameters: + /// - parentSummary: The current summary value of the parent progress manager. + /// - selfSummary: The final summary value from the progress manager being deinitialized. + /// - Returns: The updated summary that replaces the parent's current summary. + static func finalSummary(_ parentSummary: Summary, _ selfSummary: Summary) -> Summary + } } ``` @@ -644,12 +692,10 @@ public struct Subprogress: ~Copyable, Sendable { We pre-declare some of these additional properties that are commonly desired in use cases of progress reporting, including and not limited to, `totalFileCount` and `totalByteCount`. -If you would like to report additional metadata or properties that are not part of the pre-declared additional properties, you can declare additional properties into `ProgressManager.Properties`, similar to how the pre-declared additional properties are declared. - -Additionally, the additional metadata or properties of each `ProgressManager` can be read by calling the `values(of:)` method defined in `ProgressManager`. The `values(of:)` method returns an array of values for each specified property in a subtree. If you would like to get an aggregated value of a property that is an `AdditiveArithmetic` type, you can call the `total(of:)` method defined in `ProgressManager`. +If you would like to report additional metadata or properties that are not part of the pre-declared additional properties, you can declare additional properties into `ProgressManager.Properties`, similar to how the pre-declared additional properties are declared. These additional properties can only have `Value` - `Summary` pairs that are either `Int` - `Int`, `Double` - `Double`, `String?` - `[String?]`, `URL?` - `[URL?]`, or `UInt64` - `[UInt64]`. ```swift -@available(FoundationPreview 6.2, *) +@available(FoundationPreview 6.2.3, *) extension ProgressManager { public struct Properties { @@ -657,66 +703,487 @@ extension ProgressManager { /// The total number of files. public var totalFileCount: TotalFileCount.Type { get } - public struct TotalFileCount : Property { + public struct TotalFileCount : Sendable, Property { public typealias Value = Int + public typealias Summary = Int + + public static var key: String { get } + public static var defaultValue: Int { get } + + public static var defaultSummary: Int { get } + + public static func reduce(into summary: inout Int, value: Int) + + public static func merge(_ summary1: Int, _ summary2: Int) -> Int + + public static func finalSummary(_ parentSummary: Int, _ selfSummary: Int) -> Int } /// The number of completed files. public var completedFileCount: CompletedFileCount.Type { get } - public struct CompletedFileCount : Property { + public struct CompletedFileCount : Sendable, Property { public typealias Value = Int + public typealias Summary = Int + + public static var key: String { get } + public static var defaultValue: Int { get } + + public static var defaultSummary: Int { get } + + public static func reduce(into summary: inout Int, value: Int) + + public static func merge(_ summary1: Int, _ summary2: Int) -> Int + + public static func finalSummary(_ parentSummary: Int, _ selfSummary: Int) -> Int } /// The total number of bytes. public var totalByteCount: TotalByteCount.Type { get } - public struct TotalByteCount : Property { + public struct TotalByteCount : Sendable, Property { public typealias Value = UInt64 + public typealias Summary = UInt64 + + public static var key: String { get } + public static var defaultValue: UInt64 { get } + + public static var defaultSummary: UInt64 { get } + + public static func reduce(into summary: inout UInt64, value: UInt64) + + public static func merge(_ summary1: UInt64, _ summary2: UInt64) -> UInt64 + + public static func finalSummary(_ parentSummary: UInt64, _ selfSummary: UInt64) -> UInt64 } /// The number of completed bytes. public var completedByteCount: CompletedByteCount.Type { get } - public struct CompletedByteCount : Property { + public struct CompletedByteCount : Sendable, Property { public typealias Value = UInt64 + public typealias Summary = UInt64 + + public static var key: String { get } + public static var defaultValue: UInt64 { get } + + public static var defaultSummary: UInt64 { get } + + public static func reduce(into summary: inout UInt64, value: UInt64) + + public static func merge(_ summary1: UInt64, _ summary2: UInt64) -> UInt64 + + public static func finalSummary(_ parentSummary: UInt64, _ selfSummary: UInt64) -> UInt64 } /// The throughput, in bytes per second. public var throughput: Throughput.Type { get } - public struct Throughput : Property { + public struct Throughput : Sendable, Property { public typealias Value = UInt64 + public typealias Summary = [UInt64] + + public static var key: String { get } + public static var defaultValue: UInt64 { get } + + public static var defaultSummary: [UInt64] { get } + + public static func reduce(into summary: inout [UInt64], value: UInt64) + + public static func merge(_ summary1: [UInt64], _ summary2: [UInt64]) -> [UInt64] + + public static func finalSummary(_ parentSummary: [UInt64], _ selfSummary: [UInt64]) -> [UInt64] } /// The amount of time remaining in the processing of files. public var estimatedTimeRemaining: EstimatedTimeRemaining.Type { get } - public struct EstimatedTimeRemaining : Property { + public struct EstimatedTimeRemaining : Sendable, Property { public typealias Value = Duration + public typealias Summary = Duration + + public static var key: String { get } + public static var defaultValue: Duration { get } + + public static var defaultSummary: Duration { get } + + public static func reduce(into summary: inout Duration, value: Duration) + + public static func merge(_ summary1: Duration, _ summary2: Duration) -> Duration + + public static func finalSummary(_ parentSummary: Duration, _ selfSummary: Duration) -> Duration } } } ``` +`ProgressManager` contains a method that reads and writes additional properties on a single `ProgressManager`. + +```swift +@available(FoundationPreview 6.2.3, *) +extension ProgressManager { + + /// Accesses or mutates any properties that convey additional information about progress. + public func withProperties( + _ closure: (inout sending Values) throws(E) -> sending T + ) throws(E) -> sending T +} +``` + +`ProgressManager` contains a struct `Values` that uses `@dynamicMemberLookup` to access or mutate, at runtime, custom `ProgressManager.Property` types that you may declare. `Values` is only accessed or mutated from within the `withProperties` closure. + +```swift +@available(FoundationPreview 6.2.3, *) +extension ProgressManager { + + /// A container that holds values for properties that convey information about progress. + @dynamicMemberLookup public struct Values : Sendable { + + /// The total units of work. + public var totalCount: Int? { mutating get set } + + /// The completed units of work. + public var completedCount: Int { mutating get set } + + /// Gets or sets custom integer properties. + /// + /// This subscript provides read-write access to custom progress properties where both the value + /// and summary types are `Int`. If the property has not been set, the getter returns the + /// property's default value. + /// + /// - Parameter key: A key path to the custom integer property type. + public subscript(dynamicMember key: KeyPath) -> Int where P.Value == Int, P.Summary == Int { get set } + + /// Gets or sets custom double properties. + /// + /// This subscript provides read-write access to custom progress properties where both the value + /// and summary types are `Double`. If the property has not been set, the getter returns the + /// property's default value. + /// + /// - Parameter key: A key path to the custom double property type. + public subscript(dynamicMember key: KeyPath) -> Double where P.Value == Double, P.Summary == Double { get set } + + /// Gets or sets custom string properties. + /// + /// This subscript provides read-write access to custom progress properties where the value + /// type is `String?` and the summary type is `[String?]`. If the property has not been set, + /// the getter returns the property's default value. + /// + /// - Parameter key: A key path to the custom string property type. + public subscript(dynamicMember key: KeyPath) -> String? where P.Value == String?, P.Summary == [String?] { get set } + + /// Gets or sets custom URL properties. + /// + /// This subscript provides read-write access to custom progress properties where the value + /// type is `URL?` and the summary type is `[URL?]`. If the property has not been set, + /// the getter returns the property's default value. + /// + /// - Parameter key: A key path to the custom URL property type. + public subscript(dynamicMember key: KeyPath) -> URL? where P.Value == URL?, P.Summary == [URL?] { get set } + + /// Gets or sets custom UInt64 properties. + /// + /// This subscript provides read-write access to custom progress properties where the value + /// type is `UInt64` and the summary type is `[UInt64]`. If the property has not been set, + /// the getter returns the property's default value. + /// + /// - Parameter key: A key path to the custom UInt64 property type. + public subscript(dynamicMember key: KeyPath) -> UInt64 where P.Value == UInt64, P.Summary == [UInt64] { get set } + + /// Gets or sets the total file count property. + /// - Parameter key: A key path to the `TotalFileCount` property type. + public subscript(dynamicMember key: KeyPath) -> Int { get set } + + /// Gets or sets the completed file count property. + /// - Parameter key: A key path to the `CompletedFileCount` property type. + public subscript(dynamicMember key: KeyPath) -> Int { get set } + + /// Gets or sets the total byte count property. + /// - Parameter key: A key path to the `TotalByteCount` property type. + public subscript(dynamicMember key: KeyPath) -> UInt64 { get set } + + /// Gets or sets the completed byte count property. + /// - Parameter key: A key path to the `CompletedByteCount` property type. + public subscript(dynamicMember key: KeyPath) -> UInt64 { get set } + + /// Gets or sets the throughput property. + /// - Parameter key: A key path to the `Throughput` property type. + public subscript(dynamicMember key: KeyPath) -> UInt64 { get set } + + /// Gets or sets the estimated time remaining property. + /// - Parameter key: A key path to the `EstimatedTimeRemaining` property type. + public subscript(dynamicMember key: KeyPath) -> Duration { get set } + } +} +``` + +`ProgressManager` contains methods that summarize additional properties of across a subtree rooted by the `ProgressManager` they are called from. + +```swift +@available(FoundationPreview 6.2.3, *) +extension ProgressManager { + + /// Returns a summary for a custom integer property across the progress subtree. + /// + /// This method aggregates the values of a custom integer property from this progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the integer property to summarize. Must be a property + /// where both the value and summary types are `Int`. + /// - Returns: An `Int` summary value for the specified property. + public func summary(of property: P.Type) -> P.Summary where P.Value == Int, P.Summary == Int + + /// Returns a summary for a custom double property across the progress subtree. + /// + /// This method aggregates the values of a custom double property from this progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the double property to summarize. Must be a property + /// where both the value and summary types are `Double`. + /// - Returns: A `Double` summary value for the specified property. + public func summary(of property: P.Type) -> P.Summary where P.Value == Double, P.Summary == Double + + /// Returns a summary for a custom string property across the progress subtree. + /// + /// This method aggregates the values of a custom string property from this progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the string property to summarize. Must be a property + /// where both the value type is `String?` and the summary type is `[String?]`. + /// - Returns: A `[String?]` summary value for the specified property. + public func summary(of property: P.Type) -> P.Summary where P.Value == String?, P.Summary == [String?] + + /// Returns a summary for a custom URL property across the progress subtree. + /// + /// This method aggregates the values of a custom URL property from this progress manager + /// and all its children, returning a consolidated summary value as an array of URLs. + /// + /// - Parameter property: The type of the URL property to summarize. Must be a property + /// where the value type is `URL?` and the summary type is `[URL?]`. + /// - Returns: A `[URL?]` summary value for the specified property. + public func summary(of property: P.Type) -> P.Summary where P.Value == URL?, P.Summary == [URL?] + + /// Returns a summary for a custom UInt64 property across the progress subtree. + /// + /// This method aggregates the values of a custom UInt64 property from this progress manager + /// and all its children, returning a consolidated summary value as an array of UInt64 values. + /// + /// - Parameter property: The type of the UInt64 property to summarize. Must be a property + /// where the value type is `UInt64` and the summary type is `[UInt64]`. + /// - Returns: A `[UInt64]` summary value for the specified property. + public func summary(of property: P.Type) -> P.Summary where P.Value == UInt64, P.Summary == [UInt64] + + /// Returns the total file count across the progress subtree. + /// + /// This includes the total file count of `ProgressManager`s that are finished. + /// + /// - Parameter property: The `TotalFileCount` property type. + /// - Returns: An `Int` summary value for total file count. + public func summary(of property: ProgressManager.Properties.TotalFileCount.Type) -> Int + + /// Returns the completed file count across the progress subtree. + /// + /// This includes the completed file count of `ProgressManager`s that are finished. + /// + /// - Parameter property: The `CompletedFileCount` property type. + /// - Returns: An `Int` summary value for completed file count. + public func summary(of property: ProgressManager.Properties.CompletedFileCount.Type) -> Int + + /// Returns the total byte count across the progress subtree. + /// + /// This includes the total byte count of `ProgressManager`s that are finished. + /// + /// - Parameter property: The `TotalByteCount` property type. + /// - Returns: A `UInt64` summary value for total byte count. + public func summary(of property: ProgressManager.Properties.TotalByteCount.Type) -> UInt64 + + /// Returns the completed byte count across the progress subtree. + /// + /// This includes the completed byte count of `ProgressManager`s that are finished. + /// + /// - Parameter property: The `CompletedByteCount` property type. + /// - Returns: A `UInt64` summary value for completed byte count. + public func summary(of property: ProgressManager.Properties.CompletedByteCount.Type) -> UInt64 + + /// Returns throughput values across the progress subtree. + /// + /// This includes the throughput of `ProgressManager`s that are finished. + /// + /// - Parameter property: The `Throughput` property type. + /// - Returns: A `[UInt64]` summary value for throughput. + public func summary(of property: ProgressManager.Properties.Throughput.Type) -> [UInt64] + + /// Returns the maximum estimated time remaining for completion across the progress subtree. + /// + /// This does not include the estimated time remaining of `ProgressManager`s that are finished. + /// + /// - Parameter property: The `EstimatedTimeRemaining` property type. + /// - Returns: A `Duration` summary value for estimated time remaining to completion. + public func summary(of property: ProgressManager.Properties.EstimatedTimeRemaining.Type) -> Duration +} +``` + +### `ProgressReporter` + +`ProgressReporter` is a read-only instance of its underlying `ProgressManager`. It is also used as an adapter to add a `ProgressManager` as a child to more than one parent `ProgressManager` by calling the `assign(count:to:)` method on a parent `ProgressManager`. + +```swift +@available(FoundationPreview 6.2.3, *) +/// ProgressReporter is used to observe progress updates from a `ProgressManager`. It may also be used to incorporate those updates into another `ProgressManager`. +/// +/// It is read-only and can be added as a child of another ProgressManager. +@Observable public final class ProgressReporter : Sendable, Hashable, Equatable, CustomStringConvertible, CustomDebugStringConvertible { + + /// The total units of work. + public var totalCount: Int? { get } + + /// The completed units of work. + /// If `self` is indeterminate, the value will be 0. + public var completedCount: Int { get } + + /// The proportion of work completed. + /// This takes into account the fraction completed in its children instances if children are present. + /// If `self` is indeterminate, the value will be 0. + public var fractionCompleted: Double { get } + + /// The state of initialization of `totalCount`. + /// If `totalCount` is `nil`, the value will be `true`. + public var isIndeterminate: Bool { get } + + /// The state of completion of work. + /// If `completedCount` >= `totalCount`, the value will be `true`. + public var isFinished: Bool { get } + + /// A description. + public var description: String { get } + + /// A debug description. + public var debugDescription: String { get } + + /// Reads properties that convey additional information about progress. + public func withProperties( + _ closure: (sending ProgressManager.Values) throws(E) -> sending T + ) throws(E) -> T + + /// Returns a summary for a custom integer property across the progress subtree. + /// + /// This method aggregates the values of a custom integer property from the underlying progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the integer property to summarize. Must be a property + /// where both the value and summary types are `Int`. + /// - Returns: An `Int` summary value for the specified property. + public func summary(of property: P.Type) -> Int where P.Value == Int, P.Summary == Int + + /// Returns a summary for a custom double property across the progress subtree. + /// + /// This method aggregates the values of a custom double property from the underlying progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the double property to summarize. Must be a property + /// where both the value and summary types are `Double`. + /// - Returns: A `Double` summary value for the specified property. + public func summary(of property: P.Type) -> Double where P.Value == Double, P.Summary == Double + + /// Returns a summary for a custom string property across the progress subtree. + /// + /// This method aggregates the values of a custom string property from the underlying progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the string property to summarize. Must be a property + /// where both the value type is `String?` and the summary type is `[String?]`. + /// - Returns: A `[String?]` summary value for the specified property. + public func summary(of property: P.Type) -> [String?] where P.Value == String?, P.Summary == [String?] + + + /// Returns a summary for a custom URL property across the progress subtree. + /// + /// This method aggregates the values of a custom URL property from the underlying progress manager + /// and all its children, returning a consolidated summary value as an array of URLs. + /// + /// - Parameter property: The type of the URL property to summarize. Must be a property + /// where the value type is `URL?` and the summary type is `[URL?]`. + /// - Returns: A `[URL?]` summary value for the specified property. + public func summary(of property: P.Type) -> [URL?] where P.Value == URL?, P.Summary == [URL?] + + /// Returns a summary for a custom UInt64 property across the progress subtree. + /// + /// This method aggregates the values of a custom UInt64 property from the underlying progress manager + /// and all its children, returning a consolidated summary value as an array of UInt64 values. + /// + /// - Parameter property: The type of the UInt64 property to summarize. Must be a property + /// where the value type is `UInt64` and the summary type is `[UInt64]`. + /// - Returns: A `[UInt64]` summary value for the specified property. + public func summary(of property: P.Type) -> [UInt64] where P.Value == UInt64, P.Summary == [UInt64] + + /// Returns the total file count across the progress subtree. + /// + /// This includes the total file count of `ProgressManager`s that are finished. + /// + /// - Parameter property: The `TotalFileCount` property type. + /// - Returns: The sum of all total file counts across the entire progress subtree. + public func summary(of property: ProgressManager.Properties.TotalFileCount.Type) -> Int + + /// Returns the completed file count across the progress subtree. + /// + /// This includes the completed file count of `ProgressManager`s that are finished. + /// + /// - Parameter property: The `CompletedFileCount` property type. + /// - Returns: The sum of all completed file counts across the entire progress subtree. + public func summary(of property: ProgressManager.Properties.CompletedFileCount.Type) -> Int + + /// Returns the total byte count across the progress subtree. + /// + /// This includes the total byte count of `ProgressManager`s that are finished. + /// + /// - Parameter property: The `TotalByteCount` property type. + /// - Returns: The sum of all total byte counts across the entire progress subtree, in bytes. + public func summary(of property: ProgressManager.Properties.TotalByteCount.Type) -> UInt64 + + /// Returns the completed byte count across the progress subtree. + /// + /// This includes the completed byte count of `ProgressManager`s that are finished. + /// + /// - Parameter property: The `CompletedByteCount` property type. + /// - Returns: The sum of all completed byte counts across the entire progress subtree, in bytes. + public func summary(of property: ProgressManager.Properties.CompletedByteCount.Type) -> UInt64 + + /// Returns the average throughput across the progress subtree. + /// + /// This includes the throughput of `ProgressManager`s that are finished. + /// + /// - Parameter property: The `Throughput` property type. + /// - Returns: An array of throughput across the entire progress subtree, in bytes per second. + public func summary(of property: ProgressManager.Properties.Throughput.Type) -> [UInt64] + + /// Returns the maximum estimated time remaining for completion across the progress subtree. + /// + /// This does not include the estimated time remaining of `ProgressManager`s that are finished. + /// + /// - Parameter property: The `EstimatedTimeRemaining` property type. + /// - Returns: The estimated duration until completion for the entire progress subtree. + public func summary(of property: ProgressManager.Properties.EstimatedTimeRemaining.Type) -> Duration +} +``` + ### Cancellation in `ProgressManager` While this API does not assume any control over tasks, it needs to react to a task being cancelled, or a `Subprogress` not being consumed. @@ -748,12 +1215,12 @@ To allow frameworks which may have dependencies on the pre-existing progress-rep #### `ProgressManager` (Parent) - `Foundation.Progress` (Child) -To add an instance of `Foundation.Progress` as a child to an instance of `ProgressManager`, we pass an `Int` for the portion of `ProgressManager`'s `totalCount` `Foundation.Progress` should take up and a `Foundation.Progress` instance to `assign(count: to:)`. The `ProgressManager` instance will track the `Foundation.Progress` instance just like any of its `ProgressManager` children. +To add an instance of `Foundation.Progress` as a child to an instance of `ProgressManager`, we pass an `Int` for the portion of `ProgressManager`'s `totalCount` `Foundation.Progress` should take up and a `Foundation.Progress` instance to `assign(count:to:)`. The `ProgressManager` instance will track the `Foundation.Progress` instance just like any of its `ProgressManager` children. >The choice of naming the interop method as `subprogress(assigningCount: to:)` is to keep the syntax consistent with the method used to add a `ProgressManager` instance to the progress tree using this new API, `subprogress(assigningCount:)`. ```swift -@available(FoundationPreview 6.2, *) +@available(FoundationPreview 6.2.3, *) extension ProgressManager { /// Adds a Foundation's `Progress` instance as a child which constitutes a certain `count` of `self`'s `totalCount`. /// @@ -766,12 +1233,12 @@ extension ProgressManager { #### `Foundation.Progress` (Parent) - `ProgressManager` (Child) -To add an instance of `ProgressManager` as a child to an instance of the existing `Foundation.Progress`, the `Foundation.Progress` instance calls `makeChild(count:)` to get a `Subprogress` instance that can be passed as a parameter to a function that reports progress. The `Foundation.Progress` instance will track the `ProgressManager` instance as a child, just like any of its `Progress` children. +To add an instance of `ProgressManager` as a child to an instance of the existing `Foundation.Progress`, the `Foundation.Progress` instance calls `makeChild(withPendingUnitCount:)` to get a `Subprogress` instance that can be passed as a parameter to a function that reports progress. The `Foundation.Progress` instance will track the `ProgressManager` instance as a child, just like any of its `Progress` children. >The choice of naming the interop methods as `makeChild(withPendingUnitCount:)` and `addChild(_:withPendingUnitCount` is to keep the syntax consistent with the method used to add a `Foundation.Progress` instance as a child to another `Foundation.Progress`. ```swift -@available(FoundationPreview 6.2, *) +@available(FoundationPreview 6.2.3, *) extension Progress { /// Returns a Subprogress which can be passed to any method that reports progress /// and can be initialized into a child `ProgressManager` to the `self`. @@ -787,7 +1254,7 @@ extension Progress { /// Adds a ProgressReporter as a child to a Foundation.Progress. /// /// - Parameters: - /// - output: A `ProgressReporter` instance. + /// - reporter: A `ProgressReporter` instance. /// - count: Number of units delegated from `self`'s `totalCount` to Progress Reporter. public func addChild(_ reporter: ProgressReporter, withPendingUnitCount count: Int) } @@ -867,7 +1334,7 @@ func g() async { // App code func f() async { - var progressManager = ProgressManager(totalUnitCount: 1) + var progressManager = ProgressManager(totalCount: 1) await g() // progress consumed } @@ -894,7 +1361,7 @@ func f() async { Additionally, progress reporting being directly integrated into the structured concurrency model would also introduce a non-trivial trade-off. Supporting multi-parent use cases, or the ability to construct an acyclic graph for progress is a heavily-desired feature for this API, but structured concurrency, which assumes a tree structure, would inevitably break this use case. ### Add Convenience Method to Existing `Progress` for Easier Instantiation of Child Progress -While the explicit model has concurrency support via completion handlers, the usage pattern does not fit well with async/await, because which an instance of `Progress` returned by an asynchronous function would return after code is executed to completion. In the explicit model, to add a child to a parent progress, we pass an instantiated child progress object into the `addChild(child:withPendingUnitCount:)` method. In this alternative, we add a convenience method that bears the function signature `makeChild(pendingUnitCount:)` to the `Progress` class. This method instantiates an empty progress and adds itself as a child, allowing developers to add a child progress to a parent progress without having to instantiate a child progress themselves. The additional method reads as follows: +While the explicit model has concurrency support via completion handlers, the usage pattern does not fit well with async/await, because an instance of `Progress` returned by an asynchronous function would return after code is executed to completion. In the explicit model, to add a child to a parent progress, we pass an instantiated child progress object into the `addChild(child:withPendingUnitCount:)` method. In this alternative, we add a convenience method that bears the function signature `makeChild(pendingUnitCount:)` to the `Progress` class. This method instantiates an empty progress and adds itself as a child, allowing developers to add a child progress to a parent progress without having to instantiate a child progress themselves. The additional method reads as follows: ```swift extension Progress { @@ -983,7 +1450,10 @@ We considered using `UInt64` as the type for `totalCount` and `completedCount` t We previously considered making `totalCount` a settable property on `ProgressManager`, but this would introduce a race condition that is common among cases in which `Sendable` types have settable properties. This is because two threads can try to mutate `totalCount` at the same time, but since `ProgressManager` is `Sendable`, we cannot guarantee the order of how the operations will interleave, thus creating a race condition. This results in `totalCount` either reflecting both the mutations, or one of the mutations indeterministically. Therefore, we changed it so that `totalCount` is a read-only property on `ProgressManager`, and is only mutable within the `withProperties` closure to prevent this race condition. ### Representation of Indeterminate state in `ProgressManager` -There were discussions about representing indeterminate state in `ProgressManager` alternatively, for example, using enums. However, since `totalCount` is an optional and can be set to `nil` to represent indeterminate state, we think that this is straightforward and sufficient to represent indeterminate state for cases where developers do not know `totalCount` at the start of an operation they want to report progress for. A `ProgressManager` becomes determinate once its `totalCount` is set to an `Int`. +There were discussions about representing indeterminate state in `ProgressManager` alternatively, for example, using enums. However, since `totalCount` is an optional and can be set to `nil` to represent indeterminate state, we think that this is straightforward and sufficient to represent indeterminate state for cases where developers do not know `totalCount` at the start of an operation they want to report progress for. A `ProgressManager` becomes determinate once its `totalCount` is set to an `Int`. + +### Allow declared custom additional property to be any type that can be casted as `any Sendable` +We initially allowed the full flexibility of allowing developers to declare `ProgressManager.Property` types to be of any type, including structs. However, we realized that this has a severely negative impact on performance of the API. Thus, for now, we allow developers to only declare `ProgressManager.Property` with only certain `Value` and `Summary` types. ## Acknowledgements Thanks to From 7eb048b01cb67894b63218254ddec360594906f9 Mon Sep 17 00:00:00 2001 From: chloe-yeo Date: Fri, 5 Sep 2025 17:15:22 -0700 Subject: [PATCH 02/15] add support for UInt64 and Duration types --- Proposals/0023-progress-manager.md | 66 +++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/Proposals/0023-progress-manager.md b/Proposals/0023-progress-manager.md index 195afffd9..5b2da072b 100644 --- a/Proposals/0023-progress-manager.md +++ b/Proposals/0023-progress-manager.md @@ -41,7 +41,7 @@ - Expanded Future Directions - Expanded Alternatives Considered * **v6** Minor Updates: - - Changed behavior of API so that additional properties are restricted to either `Int`, `Double`, `String?`, `URL?`, or `UInt64` types instead of `any Sendable` types + - Changed behavior of API so that additional properties are restricted to either `Int`, `Double`, `String?`, `URL?`, `UInt64` or `Duration` types instead of `any Sendable` types - Added requirements to `ProgressManager.Property` protocol to define summarization and termination (deinit) behavior - Added overloads for `subscript(dynamicMember:)` in `ProgressManager.Values` to account for currently-allowed types - Removed `values(of:)` method @@ -362,10 +362,12 @@ You can define additional properties specific to the operations you are reportin The currently allowed (Value, Summary) pairs for custom properties are: - (`Int`, `Int`) +- (`UInt64`, `UInt64`) - (`Double`, `Double`) - (`String?`, `[String?]`) - (`URL?`, `[URL?]`) - (`UInt64`, `[UInt64]`) +- (`Duration`, `Duration`) You can declare a custom additional property that has a `String?` `Value` type and `[String?]` `Summary` type as follows: @@ -587,10 +589,12 @@ public struct Subprogress: ~Copyable, Sendable { `ProgressManager` contains a protocol `Property` that outlines the requirements for declaring a custom additional property. The currently allowed (Value, Summary) pairs are as follows: - (`Int`, `Int`) +- (`UInt64`, `UInt64`) - (`Double`, `Double`) - (`String?`, `[String?]`) - (`URL?`, `[URL?]`) - (`UInt64`, `[UInt64]`) +- (`Duration`, `Duration`) This list of allowed (Value, Summary) pairs may be expanded in the future. Based on pre-existing use cases of additional properties on `Progress`, we believe that the currently allowed (Value, Summary) pairs should suffice for most use cases. @@ -871,6 +875,15 @@ extension ProgressManager { /// /// - Parameter key: A key path to the custom integer property type. public subscript(dynamicMember key: KeyPath) -> Int where P.Value == Int, P.Summary == Int { get set } + + /// Gets or sets custom unsigned integer properties. + /// + /// This subscript provides read-write access to custom progress properties where both the value + /// and summary types are `UInt64`. If the property has not been set, the getter returns the + /// property's default value. + /// + /// - Parameter key: A key path to the custom integer property type. + public subscript(dynamicMember key: KeyPath) -> UInt64 where P.Value == UInt64, P.Summary == UInt64 { get set } /// Gets or sets custom double properties. /// @@ -908,6 +921,15 @@ extension ProgressManager { /// - Parameter key: A key path to the custom UInt64 property type. public subscript(dynamicMember key: KeyPath) -> UInt64 where P.Value == UInt64, P.Summary == [UInt64] { get set } + /// Gets or sets custom duration properties. + /// + /// This subscript provides read-write access to custom progress properties where both the value + /// and summary types are `Duration`. If the property has not been set, the getter returns the + /// property's default value. + /// + /// - Parameter key: A key path to the custom double property type. + public subscript(dynamicMember key: KeyPath) -> Duration where P.Value == Duration, P.Summary == Duration { get set } + /// Gets or sets the total file count property. /// - Parameter key: A key path to the `TotalFileCount` property type. public subscript(dynamicMember key: KeyPath) -> Int { get set } @@ -950,6 +972,16 @@ extension ProgressManager { /// where both the value and summary types are `Int`. /// - Returns: An `Int` summary value for the specified property. public func summary(of property: P.Type) -> P.Summary where P.Value == Int, P.Summary == Int + + /// Returns a summary for a custom unsigned integer property across the progress subtree. + /// + /// This method aggregates the values of a custom integer property from this progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the integer property to summarize. Must be a property + /// where both the value and summary types are `UInt64`. + /// - Returns: An `UInt64` summary value for the specified property. + public func summary(of property: P.Type) -> P.Summary where P.Value == UInt64, P.Summary == UInt64 /// Returns a summary for a custom double property across the progress subtree. /// @@ -991,6 +1023,16 @@ extension ProgressManager { /// - Returns: A `[UInt64]` summary value for the specified property. public func summary(of property: P.Type) -> P.Summary where P.Value == UInt64, P.Summary == [UInt64] + /// Returns a summary for a custom duration property across the progress subtree. + /// + /// This method aggregates the values of a custom integer property from this progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the integer property to summarize. Must be a property + /// where both the value and summary types are `Duration`. + /// - Returns: An `Duration` summary value for the specified property. + public func summary(of property: P.Type) -> P.Summary where P.Value == Duration, P.Summary == Duration + /// Returns the total file count across the progress subtree. /// /// This includes the total file count of `ProgressManager`s that are finished. @@ -1092,6 +1134,16 @@ extension ProgressManager { /// where both the value and summary types are `Int`. /// - Returns: An `Int` summary value for the specified property. public func summary(of property: P.Type) -> Int where P.Value == Int, P.Summary == Int + + /// Returns a summary for a custom unsigned integer property across the progress subtree. + /// + /// This method aggregates the values of a custom integer property from the underlying progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the integer property to summarize. Must be a property + /// where both the value and summary types are `UInt64`. + /// - Returns: An `UInt64` summary value for the specified property. + public func summary(of property: P.Type) -> UInt64 where P.Value == UInt64, P.Summary == UInt64 /// Returns a summary for a custom double property across the progress subtree. /// @@ -1134,6 +1186,16 @@ extension ProgressManager { /// - Returns: A `[UInt64]` summary value for the specified property. public func summary(of property: P.Type) -> [UInt64] where P.Value == UInt64, P.Summary == [UInt64] + /// Returns a summary for a custom duration property across the progress subtree. + /// + /// This method aggregates the values of a custom integer property from the underlying progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the integer property to summarize. Must be a property + /// where both the value and summary types are `Duration`. + /// - Returns: An `Duraton` summary value for the specified property. + public func summary(of property: P.Type) -> Duration where P.Value == Duration, P.Summary == Duration + /// Returns the total file count across the progress subtree. /// /// This includes the total file count of `ProgressManager`s that are finished. @@ -1453,7 +1515,7 @@ We previously considered making `totalCount` a settable property on `ProgressMan There were discussions about representing indeterminate state in `ProgressManager` alternatively, for example, using enums. However, since `totalCount` is an optional and can be set to `nil` to represent indeterminate state, we think that this is straightforward and sufficient to represent indeterminate state for cases where developers do not know `totalCount` at the start of an operation they want to report progress for. A `ProgressManager` becomes determinate once its `totalCount` is set to an `Int`. ### Allow declared custom additional property to be any type that can be casted as `any Sendable` -We initially allowed the full flexibility of allowing developers to declare `ProgressManager.Property` types to be of any type, including structs. However, we realized that this has a severely negative impact on performance of the API. Thus, for now, we allow developers to only declare `ProgressManager.Property` with only certain `Value` and `Summary` types. +We initially allowed the full flexibility of allowing developers to declare `ProgressManager.Property` types to be of any type, including structs. However, we realized that this has a severely negative impact on performance of the API. Thus, for now, we allow developers to only declare `ProgressManager.Property` with only certain `Value` and `Summary` types. ## Acknowledgements Thanks to From 7a0267aabfc29e578d4215b6b5fc27c3a7ff2fac Mon Sep 17 00:00:00 2001 From: chloe-yeo Date: Mon, 8 Sep 2025 12:32:07 -0700 Subject: [PATCH 03/15] remove duplicate summary methods --- Proposals/0023-progress-manager.md | 96 ------------------------------ 1 file changed, 96 deletions(-) diff --git a/Proposals/0023-progress-manager.md b/Proposals/0023-progress-manager.md index 5b2da072b..ff0e780b2 100644 --- a/Proposals/0023-progress-manager.md +++ b/Proposals/0023-progress-manager.md @@ -1032,54 +1032,6 @@ extension ProgressManager { /// where both the value and summary types are `Duration`. /// - Returns: An `Duration` summary value for the specified property. public func summary(of property: P.Type) -> P.Summary where P.Value == Duration, P.Summary == Duration - - /// Returns the total file count across the progress subtree. - /// - /// This includes the total file count of `ProgressManager`s that are finished. - /// - /// - Parameter property: The `TotalFileCount` property type. - /// - Returns: An `Int` summary value for total file count. - public func summary(of property: ProgressManager.Properties.TotalFileCount.Type) -> Int - - /// Returns the completed file count across the progress subtree. - /// - /// This includes the completed file count of `ProgressManager`s that are finished. - /// - /// - Parameter property: The `CompletedFileCount` property type. - /// - Returns: An `Int` summary value for completed file count. - public func summary(of property: ProgressManager.Properties.CompletedFileCount.Type) -> Int - - /// Returns the total byte count across the progress subtree. - /// - /// This includes the total byte count of `ProgressManager`s that are finished. - /// - /// - Parameter property: The `TotalByteCount` property type. - /// - Returns: A `UInt64` summary value for total byte count. - public func summary(of property: ProgressManager.Properties.TotalByteCount.Type) -> UInt64 - - /// Returns the completed byte count across the progress subtree. - /// - /// This includes the completed byte count of `ProgressManager`s that are finished. - /// - /// - Parameter property: The `CompletedByteCount` property type. - /// - Returns: A `UInt64` summary value for completed byte count. - public func summary(of property: ProgressManager.Properties.CompletedByteCount.Type) -> UInt64 - - /// Returns throughput values across the progress subtree. - /// - /// This includes the throughput of `ProgressManager`s that are finished. - /// - /// - Parameter property: The `Throughput` property type. - /// - Returns: A `[UInt64]` summary value for throughput. - public func summary(of property: ProgressManager.Properties.Throughput.Type) -> [UInt64] - - /// Returns the maximum estimated time remaining for completion across the progress subtree. - /// - /// This does not include the estimated time remaining of `ProgressManager`s that are finished. - /// - /// - Parameter property: The `EstimatedTimeRemaining` property type. - /// - Returns: A `Duration` summary value for estimated time remaining to completion. - public func summary(of property: ProgressManager.Properties.EstimatedTimeRemaining.Type) -> Duration } ``` @@ -1195,54 +1147,6 @@ extension ProgressManager { /// where both the value and summary types are `Duration`. /// - Returns: An `Duraton` summary value for the specified property. public func summary(of property: P.Type) -> Duration where P.Value == Duration, P.Summary == Duration - - /// Returns the total file count across the progress subtree. - /// - /// This includes the total file count of `ProgressManager`s that are finished. - /// - /// - Parameter property: The `TotalFileCount` property type. - /// - Returns: The sum of all total file counts across the entire progress subtree. - public func summary(of property: ProgressManager.Properties.TotalFileCount.Type) -> Int - - /// Returns the completed file count across the progress subtree. - /// - /// This includes the completed file count of `ProgressManager`s that are finished. - /// - /// - Parameter property: The `CompletedFileCount` property type. - /// - Returns: The sum of all completed file counts across the entire progress subtree. - public func summary(of property: ProgressManager.Properties.CompletedFileCount.Type) -> Int - - /// Returns the total byte count across the progress subtree. - /// - /// This includes the total byte count of `ProgressManager`s that are finished. - /// - /// - Parameter property: The `TotalByteCount` property type. - /// - Returns: The sum of all total byte counts across the entire progress subtree, in bytes. - public func summary(of property: ProgressManager.Properties.TotalByteCount.Type) -> UInt64 - - /// Returns the completed byte count across the progress subtree. - /// - /// This includes the completed byte count of `ProgressManager`s that are finished. - /// - /// - Parameter property: The `CompletedByteCount` property type. - /// - Returns: The sum of all completed byte counts across the entire progress subtree, in bytes. - public func summary(of property: ProgressManager.Properties.CompletedByteCount.Type) -> UInt64 - - /// Returns the average throughput across the progress subtree. - /// - /// This includes the throughput of `ProgressManager`s that are finished. - /// - /// - Parameter property: The `Throughput` property type. - /// - Returns: An array of throughput across the entire progress subtree, in bytes per second. - public func summary(of property: ProgressManager.Properties.Throughput.Type) -> [UInt64] - - /// Returns the maximum estimated time remaining for completion across the progress subtree. - /// - /// This does not include the estimated time remaining of `ProgressManager`s that are finished. - /// - /// - Parameter property: The `EstimatedTimeRemaining` property type. - /// - Returns: The estimated duration until completion for the entire progress subtree. - public func summary(of property: ProgressManager.Properties.EstimatedTimeRemaining.Type) -> Duration } ``` From 3e1f465bb1d41518e62a330d3c63cd07c0805146 Mon Sep 17 00:00:00 2001 From: chloe-yeo Date: Mon, 8 Sep 2025 14:55:23 -0700 Subject: [PATCH 04/15] remove duplicate subscript methods --- Proposals/0023-progress-manager.md | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/Proposals/0023-progress-manager.md b/Proposals/0023-progress-manager.md index ff0e780b2..562ffeb33 100644 --- a/Proposals/0023-progress-manager.md +++ b/Proposals/0023-progress-manager.md @@ -929,30 +929,6 @@ extension ProgressManager { /// /// - Parameter key: A key path to the custom double property type. public subscript(dynamicMember key: KeyPath) -> Duration where P.Value == Duration, P.Summary == Duration { get set } - - /// Gets or sets the total file count property. - /// - Parameter key: A key path to the `TotalFileCount` property type. - public subscript(dynamicMember key: KeyPath) -> Int { get set } - - /// Gets or sets the completed file count property. - /// - Parameter key: A key path to the `CompletedFileCount` property type. - public subscript(dynamicMember key: KeyPath) -> Int { get set } - - /// Gets or sets the total byte count property. - /// - Parameter key: A key path to the `TotalByteCount` property type. - public subscript(dynamicMember key: KeyPath) -> UInt64 { get set } - - /// Gets or sets the completed byte count property. - /// - Parameter key: A key path to the `CompletedByteCount` property type. - public subscript(dynamicMember key: KeyPath) -> UInt64 { get set } - - /// Gets or sets the throughput property. - /// - Parameter key: A key path to the `Throughput` property type. - public subscript(dynamicMember key: KeyPath) -> UInt64 { get set } - - /// Gets or sets the estimated time remaining property. - /// - Parameter key: A key path to the `EstimatedTimeRemaining` property type. - public subscript(dynamicMember key: KeyPath) -> Duration { get set } } } ``` From 03012687fded4ace2e837aac5d0599ed89f3990a Mon Sep 17 00:00:00 2001 From: chloe-yeo Date: Mon, 8 Sep 2025 15:22:05 -0700 Subject: [PATCH 05/15] fix example --- Proposals/0023-progress-manager.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Proposals/0023-progress-manager.md b/Proposals/0023-progress-manager.md index 562ffeb33..12c87cbc3 100644 --- a/Proposals/0023-progress-manager.md +++ b/Proposals/0023-progress-manager.md @@ -396,7 +396,7 @@ extension ProgressManager.Properties { } static func finalSummary(_ parentSummary: [String?], _ selfSummary: [String?]) -> [String?] { - parentSummary + parentSummary + selfSummary } } } @@ -423,7 +423,7 @@ func doSomething(subprogress: consuming Subprogress? = nil) async { } let filenames = manager.summary(of: ProgressManager.Properties.Filename.self) // get summary of filename in subtree -print(filenames) // ["Capybara.jpg", "Snail.jpg"] +print(filenames) // get ["Capybara.jpg", "Snail.jpg"] since we defined `finalSummary` to include filename cumulatively ``` ### Interoperability with Existing `Progress` From 9cc680f9c0d94345c4936819159015bd966797e5 Mon Sep 17 00:00:00 2001 From: chloe-yeo Date: Mon, 8 Sep 2025 16:14:14 -0700 Subject: [PATCH 06/15] update availability --- Proposals/0023-progress-manager.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Proposals/0023-progress-manager.md b/Proposals/0023-progress-manager.md index 12c87cbc3..b1b98ffbf 100644 --- a/Proposals/0023-progress-manager.md +++ b/Proposals/0023-progress-manager.md @@ -501,7 +501,7 @@ overall.addChild(subprogressThree, withPendingUnitCount: 1) ```swift /// An object that conveys ongoing progress to the user for a specified task. -@available(FoundationPreview 6.2.3, *) +@available(FoundationPreview 6.3, *) @Observable public final class ProgressManager : Sendable, Hashable, Equatable, CustomStringConvertible, CustomDebugStringConvertible { /// The total units of work. @@ -570,7 +570,7 @@ You call `ProgressManager`'s `subprogress(assigningCount:)` to create a `Subprog The callee will consume `Subprogress` and get the `ProgressManager` by calling `start(totalCount:)`. That `ProgressManager` is used for the function's own progress updates. ```swift -@available(FoundationPreview 6.2.3, *) +@available(FoundationPreview 6.3, *) /// Subprogress is used to establish parent-child relationship between two instances of `ProgressManager`. /// /// Subprogress is returned from a call to `subprogress(assigningCount:)` by a parent ProgressManager. @@ -599,7 +599,7 @@ public struct Subprogress: ~Copyable, Sendable { This list of allowed (Value, Summary) pairs may be expanded in the future. Based on pre-existing use cases of additional properties on `Progress`, we believe that the currently allowed (Value, Summary) pairs should suffice for most use cases. ```swift -@available(FoundationPreview 6.2.3, *) +@available(FoundationPreview 6.3, *) extension ProgressManager { /// A type that conveys additional task-specific information on progress. @@ -699,7 +699,7 @@ We pre-declare some of these additional properties that are commonly desired in If you would like to report additional metadata or properties that are not part of the pre-declared additional properties, you can declare additional properties into `ProgressManager.Properties`, similar to how the pre-declared additional properties are declared. These additional properties can only have `Value` - `Summary` pairs that are either `Int` - `Int`, `Double` - `Double`, `String?` - `[String?]`, `URL?` - `[URL?]`, or `UInt64` - `[UInt64]`. ```swift -@available(FoundationPreview 6.2.3, *) +@available(FoundationPreview 6.3, *) extension ProgressManager { public struct Properties { @@ -842,7 +842,7 @@ extension ProgressManager { `ProgressManager` contains a method that reads and writes additional properties on a single `ProgressManager`. ```swift -@available(FoundationPreview 6.2.3, *) +@available(FoundationPreview 6.3, *) extension ProgressManager { /// Accesses or mutates any properties that convey additional information about progress. @@ -855,7 +855,7 @@ extension ProgressManager { `ProgressManager` contains a struct `Values` that uses `@dynamicMemberLookup` to access or mutate, at runtime, custom `ProgressManager.Property` types that you may declare. `Values` is only accessed or mutated from within the `withProperties` closure. ```swift -@available(FoundationPreview 6.2.3, *) +@available(FoundationPreview 6.3, *) extension ProgressManager { /// A container that holds values for properties that convey information about progress. @@ -936,7 +936,7 @@ extension ProgressManager { `ProgressManager` contains methods that summarize additional properties of across a subtree rooted by the `ProgressManager` they are called from. ```swift -@available(FoundationPreview 6.2.3, *) +@available(FoundationPreview 6.3, *) extension ProgressManager { /// Returns a summary for a custom integer property across the progress subtree. @@ -1016,7 +1016,7 @@ extension ProgressManager { `ProgressReporter` is a read-only instance of its underlying `ProgressManager`. It is also used as an adapter to add a `ProgressManager` as a child to more than one parent `ProgressManager` by calling the `assign(count:to:)` method on a parent `ProgressManager`. ```swift -@available(FoundationPreview 6.2.3, *) +@available(FoundationPreview 6.3, *) /// ProgressReporter is used to observe progress updates from a `ProgressManager`. It may also be used to incorporate those updates into another `ProgressManager`. /// /// It is read-only and can be added as a child of another ProgressManager. @@ -1162,7 +1162,7 @@ To add an instance of `Foundation.Progress` as a child to an instance of `Progre >The choice of naming the interop method as `subprogress(assigningCount: to:)` is to keep the syntax consistent with the method used to add a `ProgressManager` instance to the progress tree using this new API, `subprogress(assigningCount:)`. ```swift -@available(FoundationPreview 6.2.3, *) +@available(FoundationPreview 6.3, *) extension ProgressManager { /// Adds a Foundation's `Progress` instance as a child which constitutes a certain `count` of `self`'s `totalCount`. /// @@ -1180,7 +1180,7 @@ To add an instance of `ProgressManager` as a child to an instance of the existin >The choice of naming the interop methods as `makeChild(withPendingUnitCount:)` and `addChild(_:withPendingUnitCount` is to keep the syntax consistent with the method used to add a `Foundation.Progress` instance as a child to another `Foundation.Progress`. ```swift -@available(FoundationPreview 6.2.3, *) +@available(FoundationPreview 6.3, *) extension Progress { /// Returns a Subprogress which can be passed to any method that reports progress /// and can be initialized into a child `ProgressManager` to the `self`. From 426d4fb3ec64cb565c808b9d03bc0075b56f6e90 Mon Sep 17 00:00:00 2001 From: chloe-yeo Date: Mon, 8 Sep 2025 17:33:16 -0700 Subject: [PATCH 07/15] sync progress manager code documentation --- Proposals/0023-progress-manager.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Proposals/0023-progress-manager.md b/Proposals/0023-progress-manager.md index b1b98ffbf..216a3940c 100644 --- a/Proposals/0023-progress-manager.md +++ b/Proposals/0023-progress-manager.md @@ -508,12 +508,11 @@ overall.addChild(subprogressThree, withPendingUnitCount: 1) public var totalCount: Int? { get } /// The completed units of work. - /// If `self` is indeterminate, the value will be 0. public var completedCount: Int { get } /// The proportion of work completed. /// This takes into account the fraction completed in its children instances if children are present. - /// If `self` is indeterminate, the value will be 0. + /// If `self` is indeterminate, the value will be 0.0. public var fractionCompleted: Double { get } /// The state of initialization of `totalCount`. From d64f126fc3fcec717e0f02722dd04ea0ff72c8c6 Mon Sep 17 00:00:00 2001 From: chloe-yeo Date: Tue, 9 Sep 2025 11:39:11 -0700 Subject: [PATCH 08/15] fix code documentation --- Proposals/0023-progress-manager.md | 132 ++++++++++++++--------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/Proposals/0023-progress-manager.md b/Proposals/0023-progress-manager.md index 216a3940c..44f943ccb 100644 --- a/Proposals/0023-progress-manager.md +++ b/Proposals/0023-progress-manager.md @@ -569,11 +569,11 @@ You call `ProgressManager`'s `subprogress(assigningCount:)` to create a `Subprog The callee will consume `Subprogress` and get the `ProgressManager` by calling `start(totalCount:)`. That `ProgressManager` is used for the function's own progress updates. ```swift -@available(FoundationPreview 6.3, *) /// Subprogress is used to establish parent-child relationship between two instances of `ProgressManager`. /// /// Subprogress is returned from a call to `subprogress(assigningCount:)` by a parent ProgressManager. /// A child ProgressManager is then returned by calling `start(totalCount:)` on a Subprogress. +@available(FoundationPreview 6.3, *) public struct Subprogress: ~Copyable, Sendable { /// Instantiates a ProgressManager which is a child to the parent ProgressManager from which the Subprogress is created. @@ -881,9 +881,27 @@ extension ProgressManager { /// and summary types are `UInt64`. If the property has not been set, the getter returns the /// property's default value. /// - /// - Parameter key: A key path to the custom integer property type. + /// - Parameter key: A key path to the custom unsigned integer property type. public subscript(dynamicMember key: KeyPath) -> UInt64 where P.Value == UInt64, P.Summary == UInt64 { get set } - + + /// Gets or sets custom UInt64 properties. + /// + /// This subscript provides read-write access to custom progress properties where the value + /// type is `UInt64` and the summary type is `[UInt64]`. If the property has not been set, + /// the getter returns the property's default value. + /// + /// - Parameter key: A key path to the custom unsigned integer property type. + public subscript(dynamicMember key: KeyPath) -> UInt64 where P.Value == UInt64, P.Summary == [UInt64] { get set } + + /// Gets or sets custom duration properties. + /// + /// This subscript provides read-write access to custom progress properties where both the value + /// and summary types are `Duration`. If the property has not been set, the getter returns the + /// property's default value. + /// + /// - Parameter key: A key path to the custom duration property type. + public subscript(dynamicMember key: KeyPath) -> Duration where P.Value == Duration, P.Summary == Duration { get set } + /// Gets or sets custom double properties. /// /// This subscript provides read-write access to custom progress properties where both the value @@ -910,24 +928,6 @@ extension ProgressManager { /// /// - Parameter key: A key path to the custom URL property type. public subscript(dynamicMember key: KeyPath) -> URL? where P.Value == URL?, P.Summary == [URL?] { get set } - - /// Gets or sets custom UInt64 properties. - /// - /// This subscript provides read-write access to custom progress properties where the value - /// type is `UInt64` and the summary type is `[UInt64]`. If the property has not been set, - /// the getter returns the property's default value. - /// - /// - Parameter key: A key path to the custom UInt64 property type. - public subscript(dynamicMember key: KeyPath) -> UInt64 where P.Value == UInt64, P.Summary == [UInt64] { get set } - - /// Gets or sets custom duration properties. - /// - /// This subscript provides read-write access to custom progress properties where both the value - /// and summary types are `Duration`. If the property has not been set, the getter returns the - /// property's default value. - /// - /// - Parameter key: A key path to the custom double property type. - public subscript(dynamicMember key: KeyPath) -> Duration where P.Value == Duration, P.Summary == Duration { get set } } } ``` @@ -957,7 +957,27 @@ extension ProgressManager { /// where both the value and summary types are `UInt64`. /// - Returns: An `UInt64` summary value for the specified property. public func summary(of property: P.Type) -> P.Summary where P.Value == UInt64, P.Summary == UInt64 - + + /// Returns a summary for a custom unsigned integer property across the progress subtree. + /// + /// This method aggregates the values of a custom unsigned integer property from this progress manager + /// and all its children, returning a consolidated summary value as an array of unsigned integer values. + /// + /// - Parameter property: The type of the unsigned integer property to summarize. Must be a property + /// where the value type is `UInt64` and the summary type is `[UInt64]`. + /// - Returns: A `[UInt64]` summary value for the specified property. + public func summary(of property: P.Type) -> P.Summary where P.Value == UInt64, P.Summary == [UInt64] + + /// Returns a summary for a custom duration property across the progress subtree. + /// + /// This method aggregates the values of a custom duration property from this progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the duration property to summarize. Must be a property + /// where both the value and summary types are `Duration`. + /// - Returns: An `Duration` summary value for the specified property. + public func summary(of property: P.Type) -> P.Summary where P.Value == Duration, P.Summary == Duration + /// Returns a summary for a custom double property across the progress subtree. /// /// This method aggregates the values of a custom double property from this progress manager @@ -987,26 +1007,6 @@ extension ProgressManager { /// where the value type is `URL?` and the summary type is `[URL?]`. /// - Returns: A `[URL?]` summary value for the specified property. public func summary(of property: P.Type) -> P.Summary where P.Value == URL?, P.Summary == [URL?] - - /// Returns a summary for a custom UInt64 property across the progress subtree. - /// - /// This method aggregates the values of a custom UInt64 property from this progress manager - /// and all its children, returning a consolidated summary value as an array of UInt64 values. - /// - /// - Parameter property: The type of the UInt64 property to summarize. Must be a property - /// where the value type is `UInt64` and the summary type is `[UInt64]`. - /// - Returns: A `[UInt64]` summary value for the specified property. - public func summary(of property: P.Type) -> P.Summary where P.Value == UInt64, P.Summary == [UInt64] - - /// Returns a summary for a custom duration property across the progress subtree. - /// - /// This method aggregates the values of a custom integer property from this progress manager - /// and all its children, returning a consolidated summary value. - /// - /// - Parameter property: The type of the integer property to summarize. Must be a property - /// where both the value and summary types are `Duration`. - /// - Returns: An `Duration` summary value for the specified property. - public func summary(of property: P.Type) -> P.Summary where P.Value == Duration, P.Summary == Duration } ``` @@ -1015,10 +1015,10 @@ extension ProgressManager { `ProgressReporter` is a read-only instance of its underlying `ProgressManager`. It is also used as an adapter to add a `ProgressManager` as a child to more than one parent `ProgressManager` by calling the `assign(count:to:)` method on a parent `ProgressManager`. ```swift -@available(FoundationPreview 6.3, *) /// ProgressReporter is used to observe progress updates from a `ProgressManager`. It may also be used to incorporate those updates into another `ProgressManager`. /// /// It is read-only and can be added as a child of another ProgressManager. +@available(FoundationPreview 6.3, *) @Observable public final class ProgressReporter : Sendable, Hashable, Equatable, CustomStringConvertible, CustomDebugStringConvertible { /// The total units of work. @@ -1064,14 +1064,34 @@ extension ProgressManager { /// Returns a summary for a custom unsigned integer property across the progress subtree. /// - /// This method aggregates the values of a custom integer property from the underlying progress manager + /// This method aggregates the values of a custom unsigned integer property from the underlying progress manager /// and all its children, returning a consolidated summary value. /// - /// - Parameter property: The type of the integer property to summarize. Must be a property + /// - Parameter property: The type of the unsigned integer property to summarize. Must be a property /// where both the value and summary types are `UInt64`. /// - Returns: An `UInt64` summary value for the specified property. public func summary(of property: P.Type) -> UInt64 where P.Value == UInt64, P.Summary == UInt64 - + + /// Returns a summary for a custom unsigned integer property across the progress subtree. + /// + /// This method aggregates the values of a custom unsigned integer property from the underlying progress manager + /// and all its children, returning a consolidated summary value as an array of unsigned integer values. + /// + /// - Parameter property: The type of the unsigned integer property to summarize. Must be a property + /// where the value type is `UInt64` and the summary type is `[UInt64]`. + /// - Returns: A `[UInt64]` summary value for the specified property. + public func summary(of property: P.Type) -> [UInt64] where P.Value == UInt64, P.Summary == [UInt64] + + /// Returns a summary for a custom duration property across the progress subtree. + /// + /// This method aggregates the values of a custom duration property from the underlying progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the duration property to summarize. Must be a property + /// where both the value and summary types are `Duration`. + /// - Returns: An `Duraton` summary value for the specified property. + public func summary(of property: P.Type) -> Duration where P.Value == Duration, P.Summary == Duration + /// Returns a summary for a custom double property across the progress subtree. /// /// This method aggregates the values of a custom double property from the underlying progress manager @@ -1102,26 +1122,6 @@ extension ProgressManager { /// where the value type is `URL?` and the summary type is `[URL?]`. /// - Returns: A `[URL?]` summary value for the specified property. public func summary(of property: P.Type) -> [URL?] where P.Value == URL?, P.Summary == [URL?] - - /// Returns a summary for a custom UInt64 property across the progress subtree. - /// - /// This method aggregates the values of a custom UInt64 property from the underlying progress manager - /// and all its children, returning a consolidated summary value as an array of UInt64 values. - /// - /// - Parameter property: The type of the UInt64 property to summarize. Must be a property - /// where the value type is `UInt64` and the summary type is `[UInt64]`. - /// - Returns: A `[UInt64]` summary value for the specified property. - public func summary(of property: P.Type) -> [UInt64] where P.Value == UInt64, P.Summary == [UInt64] - - /// Returns a summary for a custom duration property across the progress subtree. - /// - /// This method aggregates the values of a custom integer property from the underlying progress manager - /// and all its children, returning a consolidated summary value. - /// - /// - Parameter property: The type of the integer property to summarize. Must be a property - /// where both the value and summary types are `Duration`. - /// - Returns: An `Duraton` summary value for the specified property. - public func summary(of property: P.Type) -> Duration where P.Value == Duration, P.Summary == Duration } ``` From 226e59c1ff8f181cd5b778b833d8fc4fae28803e Mon Sep 17 00:00:00 2001 From: chloe-yeo Date: Tue, 9 Sep 2025 13:04:58 -0700 Subject: [PATCH 09/15] fix typo --- Proposals/0023-progress-manager.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Proposals/0023-progress-manager.md b/Proposals/0023-progress-manager.md index 44f943ccb..ac866c29e 100644 --- a/Proposals/0023-progress-manager.md +++ b/Proposals/0023-progress-manager.md @@ -884,7 +884,7 @@ extension ProgressManager { /// - Parameter key: A key path to the custom unsigned integer property type. public subscript(dynamicMember key: KeyPath) -> UInt64 where P.Value == UInt64, P.Summary == UInt64 { get set } - /// Gets or sets custom UInt64 properties. + /// Gets or sets custom unsigned integer properties. /// /// This subscript provides read-write access to custom progress properties where the value /// type is `UInt64` and the summary type is `[UInt64]`. If the property has not been set, From 43f237102e84d674731356f0558a632c91dc57f3 Mon Sep 17 00:00:00 2001 From: chloe-yeo Date: Tue, 9 Sep 2025 14:45:28 -0700 Subject: [PATCH 10/15] change from struct to enum + fix formatting --- Proposals/0023-progress-manager.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Proposals/0023-progress-manager.md b/Proposals/0023-progress-manager.md index ac866c29e..65c50433d 100644 --- a/Proposals/0023-progress-manager.md +++ b/Proposals/0023-progress-manager.md @@ -121,7 +121,7 @@ public func makeSalad() async { public func chopFruits() async -> Progress {} ``` -We are forced to await the `chopFruits()` call before receiving the `Progress` instance. However, the `Progress` instance that is returned from `chopFruits` already has its `completedUnitCount` equal to `totalUnitCount`. Since the `chopSubprogress` would have been completed before being added as a child to its parent `Progress`, it fails to show incremental progress as the code runs to completion within the method. +We are forced to await the `chopFruits()` call before receiving the `Progress` instance. However, the `Progress` instance that is returned from `chopFruits` already has its `completedUnitCount` equal to `totalUnitCount`. Since the `chopSubprogress` would have been completed before being added as a child to its parent `Progress`, it fails to show incremental progress as the code runs to completion within the method. While it may be possible to use the existing `Progress` to report progress in an `async` function to show incremental progress, by passing `Progress` as an argument to the function reporting progress, it is more error-prone, as shown below: @@ -701,12 +701,12 @@ If you would like to report additional metadata or properties that are not part @available(FoundationPreview 6.3, *) extension ProgressManager { - public struct Properties { + public enum Properties { /// The total number of files. public var totalFileCount: TotalFileCount.Type { get } - public struct TotalFileCount : Sendable, Property { + public enum TotalFileCount : Sendable, Property { public typealias Value = Int @@ -728,7 +728,7 @@ extension ProgressManager { /// The number of completed files. public var completedFileCount: CompletedFileCount.Type { get } - public struct CompletedFileCount : Sendable, Property { + public enum CompletedFileCount : Sendable, Property { public typealias Value = Int @@ -750,7 +750,7 @@ extension ProgressManager { /// The total number of bytes. public var totalByteCount: TotalByteCount.Type { get } - public struct TotalByteCount : Sendable, Property { + public enum TotalByteCount : Sendable, Property { public typealias Value = UInt64 @@ -772,7 +772,7 @@ extension ProgressManager { /// The number of completed bytes. public var completedByteCount: CompletedByteCount.Type { get } - public struct CompletedByteCount : Sendable, Property { + public enum CompletedByteCount : Sendable, Property { public typealias Value = UInt64 @@ -794,7 +794,7 @@ extension ProgressManager { /// The throughput, in bytes per second. public var throughput: Throughput.Type { get } - public struct Throughput : Sendable, Property { + public enum Throughput : Sendable, Property { public typealias Value = UInt64 @@ -813,10 +813,10 @@ extension ProgressManager { public static func finalSummary(_ parentSummary: [UInt64], _ selfSummary: [UInt64]) -> [UInt64] } - /// The amount of time remaining in the processing of files. + /// The amount of time remaining in operation. public var estimatedTimeRemaining: EstimatedTimeRemaining.Type { get } - public struct EstimatedTimeRemaining : Sendable, Property { + public enum EstimatedTimeRemaining : Sendable, Property { public typealias Value = Duration @@ -901,7 +901,7 @@ extension ProgressManager { /// /// - Parameter key: A key path to the custom duration property type. public subscript(dynamicMember key: KeyPath) -> Duration where P.Value == Duration, P.Summary == Duration { get set } - + /// Gets or sets custom double properties. /// /// This subscript provides read-write access to custom progress properties where both the value @@ -977,7 +977,7 @@ extension ProgressManager { /// where both the value and summary types are `Duration`. /// - Returns: An `Duration` summary value for the specified property. public func summary(of property: P.Type) -> P.Summary where P.Value == Duration, P.Summary == Duration - + /// Returns a summary for a custom double property across the progress subtree. /// /// This method aggregates the values of a custom double property from this progress manager @@ -1025,7 +1025,6 @@ extension ProgressManager { public var totalCount: Int? { get } /// The completed units of work. - /// If `self` is indeterminate, the value will be 0. public var completedCount: Int { get } /// The proportion of work completed. From 08e5fdf7f56a87e4fbc4ba6168cf706b3465ae09 Mon Sep 17 00:00:00 2001 From: chloe-yeo Date: Tue, 9 Sep 2025 15:01:49 -0700 Subject: [PATCH 11/15] remove ProgressManager prefix for Values and Property --- Proposals/0023-progress-manager.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Proposals/0023-progress-manager.md b/Proposals/0023-progress-manager.md index 65c50433d..dcb882ee5 100644 --- a/Proposals/0023-progress-manager.md +++ b/Proposals/0023-progress-manager.md @@ -1048,7 +1048,7 @@ extension ProgressManager { /// Reads properties that convey additional information about progress. public func withProperties( - _ closure: (sending ProgressManager.Values) throws(E) -> sending T + _ closure: (sending Values) throws(E) -> sending T ) throws(E) -> T /// Returns a summary for a custom integer property across the progress subtree. @@ -1059,7 +1059,7 @@ extension ProgressManager { /// - Parameter property: The type of the integer property to summarize. Must be a property /// where both the value and summary types are `Int`. /// - Returns: An `Int` summary value for the specified property. - public func summary(of property: P.Type) -> Int where P.Value == Int, P.Summary == Int + public func summary(of property: P.Type) -> Int where P.Value == Int, P.Summary == Int /// Returns a summary for a custom unsigned integer property across the progress subtree. /// @@ -1069,7 +1069,7 @@ extension ProgressManager { /// - Parameter property: The type of the unsigned integer property to summarize. Must be a property /// where both the value and summary types are `UInt64`. /// - Returns: An `UInt64` summary value for the specified property. - public func summary(of property: P.Type) -> UInt64 where P.Value == UInt64, P.Summary == UInt64 + public func summary(of property: P.Type) -> UInt64 where P.Value == UInt64, P.Summary == UInt64 /// Returns a summary for a custom unsigned integer property across the progress subtree. /// @@ -1079,7 +1079,7 @@ extension ProgressManager { /// - Parameter property: The type of the unsigned integer property to summarize. Must be a property /// where the value type is `UInt64` and the summary type is `[UInt64]`. /// - Returns: A `[UInt64]` summary value for the specified property. - public func summary(of property: P.Type) -> [UInt64] where P.Value == UInt64, P.Summary == [UInt64] + public func summary(of property: P.Type) -> [UInt64] where P.Value == UInt64, P.Summary == [UInt64] /// Returns a summary for a custom duration property across the progress subtree. /// @@ -1089,7 +1089,7 @@ extension ProgressManager { /// - Parameter property: The type of the duration property to summarize. Must be a property /// where both the value and summary types are `Duration`. /// - Returns: An `Duraton` summary value for the specified property. - public func summary(of property: P.Type) -> Duration where P.Value == Duration, P.Summary == Duration + public func summary(of property: P.Type) -> Duration where P.Value == Duration, P.Summary == Duration /// Returns a summary for a custom double property across the progress subtree. /// @@ -1099,7 +1099,7 @@ extension ProgressManager { /// - Parameter property: The type of the double property to summarize. Must be a property /// where both the value and summary types are `Double`. /// - Returns: A `Double` summary value for the specified property. - public func summary(of property: P.Type) -> Double where P.Value == Double, P.Summary == Double + public func summary(of property: P.Type) -> Double where P.Value == Double, P.Summary == Double /// Returns a summary for a custom string property across the progress subtree. /// @@ -1109,7 +1109,7 @@ extension ProgressManager { /// - Parameter property: The type of the string property to summarize. Must be a property /// where both the value type is `String?` and the summary type is `[String?]`. /// - Returns: A `[String?]` summary value for the specified property. - public func summary(of property: P.Type) -> [String?] where P.Value == String?, P.Summary == [String?] + public func summary(of property: P.Type) -> [String?] where P.Value == String?, P.Summary == [String?] /// Returns a summary for a custom URL property across the progress subtree. @@ -1120,7 +1120,7 @@ extension ProgressManager { /// - Parameter property: The type of the URL property to summarize. Must be a property /// where the value type is `URL?` and the summary type is `[URL?]`. /// - Returns: A `[URL?]` summary value for the specified property. - public func summary(of property: P.Type) -> [URL?] where P.Value == URL?, P.Summary == [URL?] + public func summary(of property: P.Type) -> [URL?] where P.Value == URL?, P.Summary == [URL?] } ``` From cc52e6dd33a2c58d72da4c43b5f5085816b9dc46 Mon Sep 17 00:00:00 2001 From: chloe-yeo Date: Tue, 9 Sep 2025 15:37:17 -0700 Subject: [PATCH 12/15] add typealiases to ProgressReporter --- Proposals/0023-progress-manager.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Proposals/0023-progress-manager.md b/Proposals/0023-progress-manager.md index dcb882ee5..6e4bb77f3 100644 --- a/Proposals/0023-progress-manager.md +++ b/Proposals/0023-progress-manager.md @@ -1021,6 +1021,9 @@ extension ProgressManager { @available(FoundationPreview 6.3, *) @Observable public final class ProgressReporter : Sendable, Hashable, Equatable, CustomStringConvertible, CustomDebugStringConvertible { + public typealias Values = ProgressManager.Values + public typealias Property = ProgressManager.Property + /// The total units of work. public var totalCount: Int? { get } From c9f479eb0af3df046a161cb4f3d3d9255ad7f17d Mon Sep 17 00:00:00 2001 From: chloe-yeo Date: Thu, 11 Sep 2025 11:53:43 -0700 Subject: [PATCH 13/15] make enums frozen --- Proposals/0023-progress-manager.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Proposals/0023-progress-manager.md b/Proposals/0023-progress-manager.md index 6e4bb77f3..674061b96 100644 --- a/Proposals/0023-progress-manager.md +++ b/Proposals/0023-progress-manager.md @@ -701,11 +701,13 @@ If you would like to report additional metadata or properties that are not part @available(FoundationPreview 6.3, *) extension ProgressManager { + @frozen public enum Properties { /// The total number of files. public var totalFileCount: TotalFileCount.Type { get } + @frozen public enum TotalFileCount : Sendable, Property { public typealias Value = Int @@ -727,7 +729,8 @@ extension ProgressManager { /// The number of completed files. public var completedFileCount: CompletedFileCount.Type { get } - + + @frozen public enum CompletedFileCount : Sendable, Property { public typealias Value = Int @@ -750,6 +753,7 @@ extension ProgressManager { /// The total number of bytes. public var totalByteCount: TotalByteCount.Type { get } + @frozen public enum TotalByteCount : Sendable, Property { public typealias Value = UInt64 @@ -772,6 +776,7 @@ extension ProgressManager { /// The number of completed bytes. public var completedByteCount: CompletedByteCount.Type { get } + @frozen public enum CompletedByteCount : Sendable, Property { public typealias Value = UInt64 @@ -794,6 +799,7 @@ extension ProgressManager { /// The throughput, in bytes per second. public var throughput: Throughput.Type { get } + @frozen public enum Throughput : Sendable, Property { public typealias Value = UInt64 @@ -816,6 +822,7 @@ extension ProgressManager { /// The amount of time remaining in operation. public var estimatedTimeRemaining: EstimatedTimeRemaining.Type { get } + @frozen public enum EstimatedTimeRemaining : Sendable, Property { public typealias Value = Duration From 7c796bf7e196010769dcfeb060f792e38db56170 Mon Sep 17 00:00:00 2001 From: chloe-yeo Date: Fri, 12 Sep 2025 15:25:42 -0700 Subject: [PATCH 14/15] update example to use enum --- Proposals/0023-progress-manager.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Proposals/0023-progress-manager.md b/Proposals/0023-progress-manager.md index 674061b96..f9c8e3241 100644 --- a/Proposals/0023-progress-manager.md +++ b/Proposals/0023-progress-manager.md @@ -375,7 +375,7 @@ You can declare a custom additional property that has a `String?` `Value` type a extension ProgressManager.Properties { var filename: Filename.Type { Filename.self } - struct Filename: Sendable, ProgressManager.Property { + enum Filename: Sendable, ProgressManager.Property { typealias Value = String? From f7c2a3032412859539ff43c2b8e6af19f19ed5c9 Mon Sep 17 00:00:00 2001 From: chloe-yeo Date: Mon, 22 Sep 2025 13:32:08 -0700 Subject: [PATCH 15/15] update proposal: setCounts + moving dynamicMemberLookup to ProgressManager --- Proposals/0023-progress-manager.md | 431 +++++++++++++++-------------- 1 file changed, 230 insertions(+), 201 deletions(-) diff --git a/Proposals/0023-progress-manager.md b/Proposals/0023-progress-manager.md index f9c8e3241..dd01c4b28 100644 --- a/Proposals/0023-progress-manager.md +++ b/Proposals/0023-progress-manager.md @@ -41,11 +41,13 @@ - Expanded Future Directions - Expanded Alternatives Considered * **v6** Minor Updates: + - Replaced `withProperties` method with `setCounts` + - Removed `ProgressManager.Values` struct + - Made `ProgressManager` conform to `@dynamicMemberLookup` and moved `subscript(dynamicMember:)` methods from `ProgressManager.Values` to `ProgressManager` - Changed behavior of API so that additional properties are restricted to either `Int`, `Double`, `String?`, `URL?`, `UInt64` or `Duration` types instead of `any Sendable` types - - Added requirements to `ProgressManager.Property` protocol to define summarization and termination (deinit) behavior - - Added overloads for `subscript(dynamicMember:)` in `ProgressManager.Values` to account for currently-allowed types - - Removed `values(of:)` method - - Replaced `total(of:)` with overloads for `summary(of:)` to account for all available types + - Added overloads for `subscript(dynamicMember:)` to account for currently-allowed types + - Added requirements to `ProgressManager.Property` protocol to define summarization and termination (deinit) behavior + - Replaced `total(of:)` with overloads for `summary(of:)` to account for all available types and removed `values(of:)` method ## Table of Contents @@ -406,23 +408,19 @@ You can report custom properties using `ProgressManager` as follows: ```swift let manager: ProgressManager = ProgressManager(totalCount: 2) -manager.withProperties { properties in - properties.completedCount = 1 // set completed count in closure, equivalent to calling `complete(count: 1)` - properties.filename = "Capybara.jpg" // using self-defined custom property - properties.totalByteCount = 1000000 // using pre-defined file-related property -} +manager.complete(count: 1) +manager.filename = "Capybara.jpg" // using self-defined custom property +manager.totalByteCount = 1000000 // using pre-defined file-related property await doSomething(subprogress: manager.subprogress(assigningCount: 1)) func doSomething(subprogress: consuming Subprogress? = nil) async { let manager = subprogress?.start(totalCount: 1) - manager?.withProperties { properties in - properties.filename = "Snail.jpg" // use self-defined custom property in child `ProgressManager` - } + manager?.filename = "Snail.jpg" // use self-defined custom property in child `ProgressManager` } -let filenames = manager.summary(of: ProgressManager.Properties.Filename.self) // get summary of filename in subtree +let filenames = manager.summary(of: ProgressManager.Properties.Filename.self) // get Array type summary of filename in subtree since we defined `Summary` to be `[String?]` print(filenames) // get ["Capybara.jpg", "Snail.jpg"] since we defined `finalSummary` to include filename cumulatively ``` @@ -497,11 +495,12 @@ overall.addChild(subprogressThree, withPendingUnitCount: 1) ### `ProgressManager` -`ProgressManager` is an `Observable` and `Sendable` class that developers use to report progress. Specifically, an instance of `ProgressManager` can be used to either track progress of a single task, or track progress of a graph of `ProgressManager` instances. +`ProgressManager` is an `Observable` and `Sendable` class, that you can use to report progress. Specifically, an instance of `ProgressManager` can be used to either track progress of a single task, or track progress of a graph of `ProgressManager` instances. Additionally, `ProgressManager` also uses the `@dynamicMemberLookup` attribute to access or mutate, at runtime, custom `ProgressManager.Property` types that you may declare to track additional properties that add context to progress being reported. ```swift /// An object that conveys ongoing progress to the user for a specified task. -@available(FoundationPreview 6.3, *) +@available(FoundationPreview 6.4, *) +@dynamicMemberLookup @Observable public final class ProgressManager : Sendable, Hashable, Equatable, CustomStringConvertible, CustomDebugStringConvertible { /// The total units of work. @@ -559,6 +558,151 @@ overall.addChild(subprogressThree, withPendingUnitCount: 1) /// Increases `completedCount` by `count`. /// - Parameter count: Units of work. public func complete(count: Int) + + /// Atomically modifies `completedCount` and `totalCount` together to ensure consistency. + /// - Parameter counts: A closure that receives inout references to both count values. + public func setCounts(_ counts: (_ completed: inout Int, _ total: inout Int?) -> Void) + + /// Gets or sets custom integer properties. + /// + /// This subscript provides read-write access to custom progress properties where both the value + /// and summary types are `Int`. If the property has not been set, the getter returns the + /// property's default value. + /// + /// - Parameter key: A key path to the custom integer property type. + public subscript(dynamicMember key: KeyPath) -> Int where P.Value == Int, P.Summary == Int { get set } + + /// Gets or sets custom unsigned integer properties. + /// + /// This subscript provides read-write access to custom progress properties where both the value + /// and summary types are `UInt64`. If the property has not been set, the getter returns the + /// property's default value. + /// + /// - Parameter key: A key path to the custom unsigned integer property type. + public subscript(dynamicMember key: KeyPath) -> UInt64 where P.Value == UInt64, P.Summary == UInt64 { get set } + + /// Gets or sets custom unsigned integer properties. + /// + /// This subscript provides read-write access to custom progress properties where the value + /// type is `UInt64` and the summary type is `[UInt64]`. If the property has not been set, + /// the getter returns the property's default value. + /// + /// - Parameter key: A key path to the custom unsigned integer property type. + public subscript(dynamicMember key: KeyPath) -> UInt64 where P.Value == UInt64, P.Summary == [UInt64] { get set } + + /// Gets or sets custom duration properties. + /// + /// This subscript provides read-write access to custom progress properties where both the value + /// and summary types are `Duration`. If the property has not been set, the getter returns the + /// property's default value. + /// + /// - Parameter key: A key path to the custom duration property type. + public subscript(dynamicMember key: KeyPath) -> Duration where P.Value == Duration, P.Summary == Duration { get set } + + /// Gets or sets custom double properties. + /// + /// This subscript provides read-write access to custom progress properties where both the value + /// and summary types are `Double`. If the property has not been set, the getter returns the + /// property's default value. + /// + /// - Parameter key: A key path to the custom double property type. + public subscript(dynamicMember key: KeyPath) -> Double where P.Value == Double, P.Summary == Double { get set } + + /// Gets or sets custom string properties. + /// + /// This subscript provides read-write access to custom progress properties where the value + /// type is `String?` and the summary type is `[String?]`. If the property has not been set, + /// the getter returns the property's default value. + /// + /// - Parameter key: A key path to the custom string property type. + public subscript(dynamicMember key: KeyPath) -> String? where P.Value == String?, P.Summary == [String?] { get set } + + /// Gets or sets custom URL properties. + /// + /// This subscript provides read-write access to custom progress properties where the value + /// type is `URL?` and the summary type is `[URL?]`. If the property has not been set, + /// the getter returns the property's default value. + /// + /// - Parameter key: A key path to the custom URL property type. + public subscript(dynamicMember key: KeyPath) -> URL? where P.Value == URL?, P.Summary == [URL?] { get set } +} +``` + +`ProgressManager` also contains methods that summarize additional properties of across a subtree rooted by the `ProgressManager` they are called from. + +```swift +@available(FoundationPreview 6.4, *) +extension ProgressManager { + + /// Returns a summary for a custom integer property across the progress subtree. + /// + /// This method aggregates the values of a custom integer property from this progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the integer property to summarize. Must be a property + /// where both the value and summary types are `Int`. + /// - Returns: An `Int` summary value for the specified property. + public func summary(of property: P.Type) -> P.Summary where P.Value == Int, P.Summary == Int + + /// Returns a summary for a custom unsigned integer property across the progress subtree. + /// + /// This method aggregates the values of a custom integer property from this progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the integer property to summarize. Must be a property + /// where both the value and summary types are `UInt64`. + /// - Returns: An `UInt64` summary value for the specified property. + public func summary(of property: P.Type) -> P.Summary where P.Value == UInt64, P.Summary == UInt64 + + /// Returns a summary for a custom unsigned integer property across the progress subtree. + /// + /// This method aggregates the values of a custom unsigned integer property from this progress manager + /// and all its children, returning a consolidated summary value as an array of unsigned integer values. + /// + /// - Parameter property: The type of the unsigned integer property to summarize. Must be a property + /// where the value type is `UInt64` and the summary type is `[UInt64]`. + /// - Returns: A `[UInt64]` summary value for the specified property. + public func summary(of property: P.Type) -> P.Summary where P.Value == UInt64, P.Summary == [UInt64] + + /// Returns a summary for a custom duration property across the progress subtree. + /// + /// This method aggregates the values of a custom duration property from this progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the duration property to summarize. Must be a property + /// where both the value and summary types are `Duration`. + /// - Returns: An `Duration` summary value for the specified property. + public func summary(of property: P.Type) -> P.Summary where P.Value == Duration, P.Summary == Duration + + /// Returns a summary for a custom double property across the progress subtree. + /// + /// This method aggregates the values of a custom double property from this progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the double property to summarize. Must be a property + /// where both the value and summary types are `Double`. + /// - Returns: A `Double` summary value for the specified property. + public func summary(of property: P.Type) -> P.Summary where P.Value == Double, P.Summary == Double + + /// Returns a summary for a custom string property across the progress subtree. + /// + /// This method aggregates the values of a custom string property from this progress manager + /// and all its children, returning a consolidated summary value. + /// + /// - Parameter property: The type of the string property to summarize. Must be a property + /// where both the value type is `String?` and the summary type is `[String?]`. + /// - Returns: A `[String?]` summary value for the specified property. + public func summary(of property: P.Type) -> P.Summary where P.Value == String?, P.Summary == [String?] + + /// Returns a summary for a custom URL property across the progress subtree. + /// + /// This method aggregates the values of a custom URL property from this progress manager + /// and all its children, returning a consolidated summary value as an array of URLs. + /// + /// - Parameter property: The type of the URL property to summarize. Must be a property + /// where the value type is `URL?` and the summary type is `[URL?]`. + /// - Returns: A `[URL?]` summary value for the specified property. + public func summary(of property: P.Type) -> P.Summary where P.Value == URL?, P.Summary == [URL?] } ``` @@ -573,7 +717,7 @@ The callee will consume `Subprogress` and get the `ProgressManager` by calling ` /// /// Subprogress is returned from a call to `subprogress(assigningCount:)` by a parent ProgressManager. /// A child ProgressManager is then returned by calling `start(totalCount:)` on a Subprogress. -@available(FoundationPreview 6.3, *) +@available(FoundationPreview 6.4, *) public struct Subprogress: ~Copyable, Sendable { /// Instantiates a ProgressManager which is a child to the parent ProgressManager from which the Subprogress is created. @@ -598,7 +742,7 @@ public struct Subprogress: ~Copyable, Sendable { This list of allowed (Value, Summary) pairs may be expanded in the future. Based on pre-existing use cases of additional properties on `Progress`, we believe that the currently allowed (Value, Summary) pairs should suffice for most use cases. ```swift -@available(FoundationPreview 6.3, *) +@available(FoundationPreview 6.4, *) extension ProgressManager { /// A type that conveys additional task-specific information on progress. @@ -698,7 +842,7 @@ We pre-declare some of these additional properties that are commonly desired in If you would like to report additional metadata or properties that are not part of the pre-declared additional properties, you can declare additional properties into `ProgressManager.Properties`, similar to how the pre-declared additional properties are declared. These additional properties can only have `Value` - `Summary` pairs that are either `Int` - `Int`, `Double` - `Double`, `String?` - `[String?]`, `URL?` - `[URL?]`, or `UInt64` - `[UInt64]`. ```swift -@available(FoundationPreview 6.3, *) +@available(FoundationPreview 6.4, *) extension ProgressManager { @frozen @@ -845,178 +989,6 @@ extension ProgressManager { } ``` -`ProgressManager` contains a method that reads and writes additional properties on a single `ProgressManager`. - -```swift -@available(FoundationPreview 6.3, *) -extension ProgressManager { - - /// Accesses or mutates any properties that convey additional information about progress. - public func withProperties( - _ closure: (inout sending Values) throws(E) -> sending T - ) throws(E) -> sending T -} -``` - -`ProgressManager` contains a struct `Values` that uses `@dynamicMemberLookup` to access or mutate, at runtime, custom `ProgressManager.Property` types that you may declare. `Values` is only accessed or mutated from within the `withProperties` closure. - -```swift -@available(FoundationPreview 6.3, *) -extension ProgressManager { - - /// A container that holds values for properties that convey information about progress. - @dynamicMemberLookup public struct Values : Sendable { - - /// The total units of work. - public var totalCount: Int? { mutating get set } - - /// The completed units of work. - public var completedCount: Int { mutating get set } - - /// Gets or sets custom integer properties. - /// - /// This subscript provides read-write access to custom progress properties where both the value - /// and summary types are `Int`. If the property has not been set, the getter returns the - /// property's default value. - /// - /// - Parameter key: A key path to the custom integer property type. - public subscript(dynamicMember key: KeyPath) -> Int where P.Value == Int, P.Summary == Int { get set } - - /// Gets or sets custom unsigned integer properties. - /// - /// This subscript provides read-write access to custom progress properties where both the value - /// and summary types are `UInt64`. If the property has not been set, the getter returns the - /// property's default value. - /// - /// - Parameter key: A key path to the custom unsigned integer property type. - public subscript(dynamicMember key: KeyPath) -> UInt64 where P.Value == UInt64, P.Summary == UInt64 { get set } - - /// Gets or sets custom unsigned integer properties. - /// - /// This subscript provides read-write access to custom progress properties where the value - /// type is `UInt64` and the summary type is `[UInt64]`. If the property has not been set, - /// the getter returns the property's default value. - /// - /// - Parameter key: A key path to the custom unsigned integer property type. - public subscript(dynamicMember key: KeyPath) -> UInt64 where P.Value == UInt64, P.Summary == [UInt64] { get set } - - /// Gets or sets custom duration properties. - /// - /// This subscript provides read-write access to custom progress properties where both the value - /// and summary types are `Duration`. If the property has not been set, the getter returns the - /// property's default value. - /// - /// - Parameter key: A key path to the custom duration property type. - public subscript(dynamicMember key: KeyPath) -> Duration where P.Value == Duration, P.Summary == Duration { get set } - - /// Gets or sets custom double properties. - /// - /// This subscript provides read-write access to custom progress properties where both the value - /// and summary types are `Double`. If the property has not been set, the getter returns the - /// property's default value. - /// - /// - Parameter key: A key path to the custom double property type. - public subscript(dynamicMember key: KeyPath) -> Double where P.Value == Double, P.Summary == Double { get set } - - /// Gets or sets custom string properties. - /// - /// This subscript provides read-write access to custom progress properties where the value - /// type is `String?` and the summary type is `[String?]`. If the property has not been set, - /// the getter returns the property's default value. - /// - /// - Parameter key: A key path to the custom string property type. - public subscript(dynamicMember key: KeyPath) -> String? where P.Value == String?, P.Summary == [String?] { get set } - - /// Gets or sets custom URL properties. - /// - /// This subscript provides read-write access to custom progress properties where the value - /// type is `URL?` and the summary type is `[URL?]`. If the property has not been set, - /// the getter returns the property's default value. - /// - /// - Parameter key: A key path to the custom URL property type. - public subscript(dynamicMember key: KeyPath) -> URL? where P.Value == URL?, P.Summary == [URL?] { get set } - } -} -``` - -`ProgressManager` contains methods that summarize additional properties of across a subtree rooted by the `ProgressManager` they are called from. - -```swift -@available(FoundationPreview 6.3, *) -extension ProgressManager { - - /// Returns a summary for a custom integer property across the progress subtree. - /// - /// This method aggregates the values of a custom integer property from this progress manager - /// and all its children, returning a consolidated summary value. - /// - /// - Parameter property: The type of the integer property to summarize. Must be a property - /// where both the value and summary types are `Int`. - /// - Returns: An `Int` summary value for the specified property. - public func summary(of property: P.Type) -> P.Summary where P.Value == Int, P.Summary == Int - - /// Returns a summary for a custom unsigned integer property across the progress subtree. - /// - /// This method aggregates the values of a custom integer property from this progress manager - /// and all its children, returning a consolidated summary value. - /// - /// - Parameter property: The type of the integer property to summarize. Must be a property - /// where both the value and summary types are `UInt64`. - /// - Returns: An `UInt64` summary value for the specified property. - public func summary(of property: P.Type) -> P.Summary where P.Value == UInt64, P.Summary == UInt64 - - /// Returns a summary for a custom unsigned integer property across the progress subtree. - /// - /// This method aggregates the values of a custom unsigned integer property from this progress manager - /// and all its children, returning a consolidated summary value as an array of unsigned integer values. - /// - /// - Parameter property: The type of the unsigned integer property to summarize. Must be a property - /// where the value type is `UInt64` and the summary type is `[UInt64]`. - /// - Returns: A `[UInt64]` summary value for the specified property. - public func summary(of property: P.Type) -> P.Summary where P.Value == UInt64, P.Summary == [UInt64] - - /// Returns a summary for a custom duration property across the progress subtree. - /// - /// This method aggregates the values of a custom duration property from this progress manager - /// and all its children, returning a consolidated summary value. - /// - /// - Parameter property: The type of the duration property to summarize. Must be a property - /// where both the value and summary types are `Duration`. - /// - Returns: An `Duration` summary value for the specified property. - public func summary(of property: P.Type) -> P.Summary where P.Value == Duration, P.Summary == Duration - - /// Returns a summary for a custom double property across the progress subtree. - /// - /// This method aggregates the values of a custom double property from this progress manager - /// and all its children, returning a consolidated summary value. - /// - /// - Parameter property: The type of the double property to summarize. Must be a property - /// where both the value and summary types are `Double`. - /// - Returns: A `Double` summary value for the specified property. - public func summary(of property: P.Type) -> P.Summary where P.Value == Double, P.Summary == Double - - /// Returns a summary for a custom string property across the progress subtree. - /// - /// This method aggregates the values of a custom string property from this progress manager - /// and all its children, returning a consolidated summary value. - /// - /// - Parameter property: The type of the string property to summarize. Must be a property - /// where both the value type is `String?` and the summary type is `[String?]`. - /// - Returns: A `[String?]` summary value for the specified property. - public func summary(of property: P.Type) -> P.Summary where P.Value == String?, P.Summary == [String?] - - /// Returns a summary for a custom URL property across the progress subtree. - /// - /// This method aggregates the values of a custom URL property from this progress manager - /// and all its children, returning a consolidated summary value as an array of URLs. - /// - /// - Parameter property: The type of the URL property to summarize. Must be a property - /// where the value type is `URL?` and the summary type is `[URL?]`. - /// - Returns: A `[URL?]` summary value for the specified property. - public func summary(of property: P.Type) -> P.Summary where P.Value == URL?, P.Summary == [URL?] -} -``` - ### `ProgressReporter` `ProgressReporter` is a read-only instance of its underlying `ProgressManager`. It is also used as an adapter to add a `ProgressManager` as a child to more than one parent `ProgressManager` by calling the `assign(count:to:)` method on a parent `ProgressManager`. @@ -1025,10 +997,10 @@ extension ProgressManager { /// ProgressReporter is used to observe progress updates from a `ProgressManager`. It may also be used to incorporate those updates into another `ProgressManager`. /// /// It is read-only and can be added as a child of another ProgressManager. -@available(FoundationPreview 6.3, *) +@available(FoundationPreview 6.4, *) +@dynamicMemberLookup @Observable public final class ProgressReporter : Sendable, Hashable, Equatable, CustomStringConvertible, CustomDebugStringConvertible { - public typealias Values = ProgressManager.Values public typealias Property = ProgressManager.Property /// The total units of work. @@ -1056,10 +1028,68 @@ extension ProgressManager { /// A debug description. public var debugDescription: String { get } - /// Reads properties that convey additional information about progress. - public func withProperties( - _ closure: (sending Values) throws(E) -> sending T - ) throws(E) -> T + /// Gets custom integer properties. + /// + /// This subscript provides read-write access to custom progress properties where both the value + /// and summary types are `Int`. If the property has not been set, the getter returns the + /// property's default value. + /// + /// - Parameter key: A key path to the custom integer property type. + public subscript(dynamicMember key: KeyPath) -> Int { get } + + /// Gets custom unsigned integer properties. + /// + /// This subscript provides read-write access to custom progress properties where both the value + /// and summary types are `UInt64`. If the property has not been set, the getter returns the + /// property's default value. + /// + /// - Parameter key: A key path to the custom unsigned integer property type. + public subscript(dynamicMember key: KeyPath) -> UInt64 { get } + + /// Gets custom unsigned integer properties. + /// + /// This subscript provides read-write access to custom progress properties where the value + /// type is `UInt64` and the summary type is `[UInt64]`. If the property has not been set, + /// the getter returns the property's default value. + /// + /// - Parameter key: A key path to the custom unsigned integer property type. + public subscript(dynamicMember key: KeyPath) -> UInt64 { get } + + /// Gets custom duration properties. + /// + /// This subscript provides read-write access to custom progress properties where the value + /// type is `Duration` and the summary type is `Duration`. If the property has not been set, + /// the getter returns the property's default value. + /// + /// - Parameter key: A key path to the custom duration property type. + public subscript(dynamicMember key: KeyPath) -> Duration { get } + + /// Gets custom double properties. + /// + /// This subscript provides read-write access to custom progress properties where both the value + /// and summary types are `Double`. If the property has not been set, the getter returns the + /// property's default value. + /// + /// - Parameter key: A key path to the custom double property type. + public subscript(dynamicMember key: KeyPath) -> Double { get } + + /// Gets custom string properties. + /// + /// This subscript provides read-write access to custom progress properties where the value + /// type is `String?` and the summary type is `[String?]`. If the property has not been set, + /// the getter returns the property's default value. + /// + /// - Parameter key: A key path to the custom string property type. + public subscript(dynamicMember key: KeyPath) -> String? { get } + + /// Gets custom URL properties. + /// + /// This subscript provides read-write access to custom progress properties where the value + /// type is `URL?` and the summary type is `[URL?]`. If the property has not been set, + /// the getter returns the property's default value. + /// + /// - Parameter key: A key path to the custom URL property type. + public subscript(dynamicMember key: KeyPath) -> URL? { get } /// Returns a summary for a custom integer property across the progress subtree. /// @@ -1121,7 +1151,6 @@ extension ProgressManager { /// - Returns: A `[String?]` summary value for the specified property. public func summary(of property: P.Type) -> [String?] where P.Value == String?, P.Summary == [String?] - /// Returns a summary for a custom URL property across the progress subtree. /// /// This method aggregates the values of a custom URL property from the underlying progress manager @@ -1170,7 +1199,7 @@ To add an instance of `Foundation.Progress` as a child to an instance of `Progre >The choice of naming the interop method as `subprogress(assigningCount: to:)` is to keep the syntax consistent with the method used to add a `ProgressManager` instance to the progress tree using this new API, `subprogress(assigningCount:)`. ```swift -@available(FoundationPreview 6.3, *) +@available(FoundationPreview 6.4, *) extension ProgressManager { /// Adds a Foundation's `Progress` instance as a child which constitutes a certain `count` of `self`'s `totalCount`. /// @@ -1188,7 +1217,7 @@ To add an instance of `ProgressManager` as a child to an instance of the existin >The choice of naming the interop methods as `makeChild(withPendingUnitCount:)` and `addChild(_:withPendingUnitCount` is to keep the syntax consistent with the method used to add a `Foundation.Progress` instance as a child to another `Foundation.Progress`. ```swift -@available(FoundationPreview 6.3, *) +@available(FoundationPreview 6.4, *) extension Progress { /// Returns a Subprogress which can be passed to any method that reports progress /// and can be initialized into a child `ProgressManager` to the `self`. @@ -1397,9 +1426,9 @@ We considered adding a `Task.isCancelled` check in the `complete(count:)` method We considered using `UInt64` as the type for `totalCount` and `completedCount` to support the case where developers use `totalCount` and `completedCount` to track downloads of larger files on 32-bit platforms byte-by-byte. However, developers are not encouraged to update progress byte-by-byte, and should instead set the counts to the granularity at which they want progress to be visibly updated. For instance, instead of updating the download progress of a 10,000 bytes file in a byte-by-byte fashion, developers can instead update the count by 1 for every 1,000 bytes that has been downloaded. In this case, developers set the `totalCount` to 10 instead of 10,000. To account for cases in which developers may want to report the current number of bytes downloaded, we added `totalByteCount` and `completedByteCount` to `ProgressManager.Properties`, which developers can set and display using format style. ### Make `totalCount` a settable property on `ProgressManager` -We previously considered making `totalCount` a settable property on `ProgressManager`, but this would introduce a race condition that is common among cases in which `Sendable` types have settable properties. This is because two threads can try to mutate `totalCount` at the same time, but since `ProgressManager` is `Sendable`, we cannot guarantee the order of how the operations will interleave, thus creating a race condition. This results in `totalCount` either reflecting both the mutations, or one of the mutations indeterministically. Therefore, we changed it so that `totalCount` is a read-only property on `ProgressManager`, and is only mutable within the `withProperties` closure to prevent this race condition. +We previously considered making `totalCount` a settable property on `ProgressManager`, but this would introduce a race condition that is common among cases in which `Sendable` types have settable properties. This is because two threads can try to mutate `totalCount` at the same time, but since `ProgressManager` is `Sendable`, we cannot guarantee the order of how the operations will interleave, thus creating a race condition. This results in `totalCount` either reflecting both the mutations, or one of the mutations indeterministically. Therefore, we changed it so that `totalCount` is a read-only property on `ProgressManager`, and is only mutable within the `setCounts` closure to prevent this race condition. -### Representation of Indeterminate state in `ProgressManager` +### Representation of indeterminate state in `ProgressManager` There were discussions about representing indeterminate state in `ProgressManager` alternatively, for example, using enums. However, since `totalCount` is an optional and can be set to `nil` to represent indeterminate state, we think that this is straightforward and sufficient to represent indeterminate state for cases where developers do not know `totalCount` at the start of an operation they want to report progress for. A `ProgressManager` becomes determinate once its `totalCount` is set to an `Int`. ### Allow declared custom additional property to be any type that can be casted as `any Sendable`