Skip to content

Commit 1e11462

Browse files
committed
ProgressManager v6 implementation
restore conflict files Restore files to 26.E version Restore files to 26.E version restore preferences.h file restore preferences.m file restore attributedString file revert attributed string remove additional lines
1 parent a8bee5b commit 1e11462

17 files changed

+4473
-2
lines changed

Package.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ let package = Package(
140140
"ProcessInfo/CMakeLists.txt",
141141
"FileManager/CMakeLists.txt",
142142
"URL/CMakeLists.txt",
143-
"NotificationCenter/CMakeLists.txt"
143+
"NotificationCenter/CMakeLists.txt",
144+
"ProgressManager/CMakeLists.txt",
144145
],
145146
cSettings: [
146147
.define("_GNU_SOURCE", .when(platforms: [.linux]))
@@ -185,7 +186,8 @@ let package = Package(
185186
"Locale/CMakeLists.txt",
186187
"Calendar/CMakeLists.txt",
187188
"CMakeLists.txt",
188-
"Predicate/CMakeLists.txt"
189+
"Predicate/CMakeLists.txt",
190+
"ProgressManager/CMakeLists.txt",
189191
],
190192
cSettings: wasiLibcCSettings,
191193
swiftSettings: [

Sources/FoundationEssentials/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ add_subdirectory(Locale)
4545
add_subdirectory(NotificationCenter)
4646
add_subdirectory(Predicate)
4747
add_subdirectory(ProcessInfo)
48+
add_subdirectory(ProgressManager)
4849
add_subdirectory(PropertyList)
4950
add_subdirectory(String)
5051
add_subdirectory(TimeZone)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
##===----------------------------------------------------------------------===##
2+
##
3+
## This source file is part of the Swift open source project
4+
##
5+
## Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
## Licensed under Apache License v2.0
7+
##
8+
## See LICENSE.txt for license information
9+
## See CONTRIBUTORS.md for the list of Swift project authors
10+
##
11+
## SPDX-License-Identifier: Apache-2.0
12+
##
13+
##===----------------------------------------------------------------------===##
14+
target_sources(FoundationEssentials PRIVATE
15+
ProgressFraction.swift
16+
ProgressManager.swift
17+
ProgressManager+Interop.swift
18+
ProgressManager+Properties+Accessors.swift
19+
ProgressManager+Properties+Definitions.swift
20+
ProgressManager+Properties+Helpers.swift
21+
ProgressManager+State.swift
22+
ProgressReporter.swift
23+
Subprogress.swift)
Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
#if FOUNDATION_FRAMEWORK
13+
internal import _ForSwiftFoundation
14+
#endif
15+
16+
internal struct ProgressFraction : Sendable, Equatable, CustomDebugStringConvertible {
17+
var completed : Int
18+
var total : Int?
19+
private(set) var overflowed : Bool
20+
21+
init() {
22+
completed = 0
23+
total = nil
24+
overflowed = false
25+
}
26+
27+
init(double: Double, overflow: Bool = false) {
28+
if double == 0 {
29+
self.completed = 0
30+
self.total = 1
31+
} else if double == 1 {
32+
self.completed = 1
33+
self.total = 1
34+
} else {
35+
(self.completed, self.total) = ProgressFraction._fromDouble(double)
36+
}
37+
self.overflowed = overflow
38+
}
39+
40+
init(completed: Int, total: Int?) {
41+
self.total = total
42+
self.completed = completed
43+
self.overflowed = false
44+
}
45+
46+
// ----
47+
48+
#if FOUNDATION_FRAMEWORK
49+
// Glue code for _NSProgressFraction and ProgressFraction
50+
init(nsProgressFraction: _NSProgressFraction) {
51+
self.init(completed: Int(nsProgressFraction.completed), total: Int(nsProgressFraction.total))
52+
}
53+
#endif
54+
55+
internal mutating func simplify() {
56+
guard let total = self.total, total != 0 else {
57+
return
58+
}
59+
60+
(self.completed, self.total) = ProgressFraction._simplify(completed, total)
61+
}
62+
63+
internal func simplified() -> ProgressFraction? {
64+
if let total = self.total {
65+
let simplified = ProgressFraction._simplify(completed, total)
66+
return ProgressFraction(completed: simplified.0, total: simplified.1)
67+
} else {
68+
return nil
69+
}
70+
}
71+
72+
static private func _math(lhs: ProgressFraction, rhs: ProgressFraction, whichOperator: (_ lhs : Double, _ rhs : Double) -> Double, whichOverflow : (_ lhs: Int, _ rhs: Int) -> (Int, overflow: Bool)) -> ProgressFraction {
73+
// Mathematically, it is nonsense to add or subtract something with a denominator of 0. However, for the purposes of implementing Progress' fractions, we just assume that a zero-denominator fraction is "weightless" and return the other value. We still need to check for the case where they are both nonsense though.
74+
precondition(!(lhs.total == 0 && rhs.total == 0), "Attempt to add or subtract invalid fraction")
75+
guard let lhsTotal = lhs.total, lhsTotal != 0 else {
76+
return rhs
77+
}
78+
guard let rhsTotal = rhs.total, rhsTotal != 0 else {
79+
return lhs
80+
}
81+
82+
guard !lhs.overflowed && !rhs.overflowed else {
83+
// If either has overflowed already, we preserve that
84+
return ProgressFraction(double: whichOperator(lhs.fractionCompleted, rhs.fractionCompleted), overflow: true)
85+
}
86+
87+
//TODO: rdar://148758226 Overflow check
88+
if let lcm = _leastCommonMultiple(lhsTotal, rhsTotal) {
89+
let result = whichOverflow(lhs.completed * (lcm / lhsTotal), rhs.completed * (lcm / rhsTotal))
90+
if result.overflow {
91+
return ProgressFraction(double: whichOperator(lhs.fractionCompleted, rhs.fractionCompleted), overflow: true)
92+
} else {
93+
return ProgressFraction(completed: result.0, total: lcm)
94+
}
95+
} else {
96+
// Overflow - simplify and then try again
97+
let lhsSimplified = lhs.simplified()
98+
let rhsSimplified = rhs.simplified()
99+
100+
guard let lhsSimplified = lhsSimplified,
101+
let rhsSimplified = rhsSimplified,
102+
let lhsSimplifiedTotal = lhsSimplified.total,
103+
let rhsSimplifiedTotal = rhsSimplified.total else {
104+
// Simplification failed, fall back to double math
105+
return ProgressFraction(double: whichOperator(lhs.fractionCompleted, rhs.fractionCompleted), overflow: true)
106+
}
107+
108+
if let lcm = _leastCommonMultiple(lhsSimplifiedTotal, rhsSimplifiedTotal) {
109+
let result = whichOverflow(lhsSimplified.completed * (lcm / lhsSimplifiedTotal), rhsSimplified.completed * (lcm / rhsSimplifiedTotal))
110+
if result.overflow {
111+
// Use original lhs/rhs here
112+
return ProgressFraction(double: whichOperator(lhs.fractionCompleted, rhs.fractionCompleted), overflow: true)
113+
} else {
114+
return ProgressFraction(completed: result.0, total: lcm)
115+
}
116+
} else {
117+
// Still overflow
118+
return ProgressFraction(double: whichOperator(lhs.fractionCompleted, rhs.fractionCompleted), overflow: true)
119+
}
120+
}
121+
}
122+
123+
static internal func +(lhs: ProgressFraction, rhs: ProgressFraction) -> ProgressFraction {
124+
return _math(lhs: lhs, rhs: rhs, whichOperator: +, whichOverflow: { $0.addingReportingOverflow($1) })
125+
}
126+
127+
static internal func -(lhs: ProgressFraction, rhs: ProgressFraction) -> ProgressFraction {
128+
return _math(lhs: lhs, rhs: rhs, whichOperator: -, whichOverflow: { $0.subtractingReportingOverflow($1) })
129+
}
130+
131+
static internal func *(lhs: ProgressFraction, rhs: ProgressFraction) -> ProgressFraction? {
132+
guard !lhs.overflowed && !rhs.overflowed else {
133+
// If either has overflowed already, we preserve that
134+
return ProgressFraction(double: lhs.fractionCompleted * rhs.fractionCompleted, overflow: true)
135+
}
136+
137+
guard let lhsTotal = lhs.total, let rhsTotal = rhs.total else {
138+
return nil
139+
}
140+
141+
let newCompleted = lhs.completed.multipliedReportingOverflow(by: rhs.completed)
142+
let newTotal = lhsTotal.multipliedReportingOverflow(by: rhsTotal)
143+
144+
if newCompleted.overflow || newTotal.overflow {
145+
// Try simplifying, then do it again
146+
let lhsSimplified = lhs.simplified()
147+
let rhsSimplified = rhs.simplified()
148+
149+
guard let lhsSimplified = lhsSimplified,
150+
let rhsSimplified = rhsSimplified,
151+
let lhsSimplifiedTotal = lhsSimplified.total,
152+
let rhsSimplifiedTotal = rhsSimplified.total else {
153+
return nil
154+
}
155+
156+
let newCompletedSimplified = lhsSimplified.completed.multipliedReportingOverflow(by: rhsSimplified.completed)
157+
let newTotalSimplified = lhsSimplifiedTotal.multipliedReportingOverflow(by: rhsSimplifiedTotal)
158+
159+
if newCompletedSimplified.overflow || newTotalSimplified.overflow {
160+
// Still overflow
161+
return ProgressFraction(double: lhs.fractionCompleted * rhs.fractionCompleted, overflow: true)
162+
} else {
163+
return ProgressFraction(completed: newCompletedSimplified.0, total: newTotalSimplified.0)
164+
}
165+
} else {
166+
return ProgressFraction(completed: newCompleted.0, total: newTotal.0)
167+
}
168+
}
169+
170+
static internal func /(lhs: ProgressFraction, rhs: Int) -> ProgressFraction? {
171+
guard !lhs.overflowed else {
172+
// If lhs has overflowed, we preserve that
173+
return ProgressFraction(double: lhs.fractionCompleted / Double(rhs), overflow: true)
174+
}
175+
176+
guard let lhsTotal = lhs.total else {
177+
return nil
178+
}
179+
180+
let newTotal = lhsTotal.multipliedReportingOverflow(by: rhs)
181+
182+
if newTotal.overflow {
183+
let simplified = lhs.simplified()
184+
185+
guard let simplified = simplified,
186+
let simplifiedTotal = simplified.total else {
187+
return nil
188+
}
189+
190+
let newTotalSimplified = simplifiedTotal.multipliedReportingOverflow(by: rhs)
191+
192+
if newTotalSimplified.overflow {
193+
// Still overflow
194+
return ProgressFraction(double: lhs.fractionCompleted / Double(rhs), overflow: true)
195+
} else {
196+
return ProgressFraction(completed: lhs.completed, total: newTotalSimplified.0)
197+
}
198+
} else {
199+
return ProgressFraction(completed: lhs.completed, total: newTotal.0)
200+
}
201+
}
202+
203+
static internal func ==(lhs: ProgressFraction, rhs: ProgressFraction) -> Bool {
204+
if lhs.isNaN || rhs.isNaN {
205+
// NaN fractions are never equal
206+
return false
207+
} else if lhs.total == rhs.total {
208+
// Direct comparison of numerator
209+
return lhs.completed == rhs.completed
210+
} else if lhs.total == nil && rhs.total != nil {
211+
return false
212+
} else if lhs.total != nil && rhs.total == nil {
213+
return false
214+
} else if lhs.completed == 0 && rhs.completed == 0 {
215+
return true
216+
} else if lhs.completed == lhs.total && rhs.completed == rhs.total {
217+
// Both finished (1)
218+
return true
219+
} else if (lhs.completed == 0 && rhs.completed != 0) || (lhs.completed != 0 && rhs.completed == 0) {
220+
// One 0, one not 0
221+
return false
222+
} else {
223+
// Cross-multiply
224+
guard let lhsTotal = lhs.total, let rhsTotal = rhs.total else {
225+
return false
226+
}
227+
228+
let left = lhs.completed.multipliedReportingOverflow(by: rhsTotal)
229+
let right = lhsTotal.multipliedReportingOverflow(by: rhs.completed)
230+
231+
if !left.overflow && !right.overflow {
232+
if left.0 == right.0 {
233+
return true
234+
}
235+
} else {
236+
// Try simplifying then cross multiply again
237+
let lhsSimplified = lhs.simplified()
238+
let rhsSimplified = rhs.simplified()
239+
240+
guard let lhsSimplified = lhsSimplified,
241+
let rhsSimplified = rhsSimplified,
242+
let lhsSimplifiedTotal = lhsSimplified.total,
243+
let rhsSimplifiedTotal = rhsSimplified.total else {
244+
// Simplification failed, fall back to doubles
245+
return lhs.fractionCompleted == rhs.fractionCompleted
246+
}
247+
248+
let leftSimplified = lhsSimplified.completed.multipliedReportingOverflow(by: rhsSimplifiedTotal)
249+
let rightSimplified = lhsSimplifiedTotal.multipliedReportingOverflow(by: rhsSimplified.completed)
250+
251+
if !leftSimplified.overflow && !rightSimplified.overflow {
252+
if leftSimplified.0 == rightSimplified.0 {
253+
return true
254+
}
255+
} else {
256+
// Ok... fallback to doubles. This doesn't use an epsilon
257+
return lhs.fractionCompleted == rhs.fractionCompleted
258+
}
259+
}
260+
}
261+
262+
return false
263+
}
264+
265+
// ----
266+
267+
internal var isFinished: Bool {
268+
guard let total else {
269+
return false
270+
}
271+
return completed >= total && completed > 0 && total > 0
272+
}
273+
274+
internal var isIndeterminate: Bool {
275+
return total == nil
276+
}
277+
278+
279+
internal var fractionCompleted : Double {
280+
guard let total else {
281+
return 0.0
282+
}
283+
return Double(completed) / Double(total)
284+
}
285+
286+
287+
internal var isNaN : Bool {
288+
return total == 0
289+
}
290+
291+
internal var debugDescription : String {
292+
return "\(completed) / \(total) (\(fractionCompleted)), overflowed: \(overflowed)"
293+
}
294+
295+
// ----
296+
297+
private static func _fromDouble(_ d : Double) -> (Int, Int) {
298+
// This simplistic algorithm could someday be replaced with something better.
299+
// Basically - how many 1/Nths is this double?
300+
var denominator: Int
301+
switch Int.bitWidth {
302+
case 32: denominator = 1048576 // 2^20 - safe for 32-bit
303+
case 64: denominator = 1073741824 // 2^30 - high precision for 64-bit
304+
default: denominator = 131072 // 2^17 - ultra-safe fallback
305+
}
306+
let numerator = Int(d / (1.0 / Double(denominator)))
307+
return (numerator, denominator)
308+
}
309+
310+
private static func _greatestCommonDivisor(_ inA : Int, _ inB : Int) -> Int {
311+
// This is Euclid's algorithm. There are faster ones, like Knuth, but this is the simplest one for now.
312+
var a = inA
313+
var b = inB
314+
repeat {
315+
let tmp = b
316+
b = a % b
317+
a = tmp
318+
} while (b != 0)
319+
return a
320+
}
321+
322+
private static func _leastCommonMultiple(_ a : Int, _ b : Int) -> Int? {
323+
// This division always results in an integer value because gcd(a,b) is a divisor of a.
324+
// lcm(a,b) == (|a|/gcd(a,b))*b == (|b|/gcd(a,b))*a
325+
let result = (a / _greatestCommonDivisor(a, b)).multipliedReportingOverflow(by: b)
326+
if result.overflow {
327+
return nil
328+
} else {
329+
return result.0
330+
}
331+
}
332+
333+
private static func _simplify(_ n : Int, _ d : Int) -> (Int, Int) {
334+
let gcd = _greatestCommonDivisor(n, d)
335+
return (n / gcd, d / gcd)
336+
}
337+
}

0 commit comments

Comments
 (0)