Skip to content

Commit 98ed32f

Browse files
committed
move files to FoundationPreview
1 parent 80588e9 commit 98ed32f

File tree

7 files changed

+1211
-0
lines changed

7 files changed

+1211
-0
lines changed
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
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+
internal import _ForSwiftFoundation
13+
14+
internal struct _ProgressFraction : Sendable, Equatable, CustomDebugStringConvertible {
15+
var completed : Int
16+
var total : Int
17+
private(set) var overflowed : Bool
18+
19+
init() {
20+
completed = 0
21+
total = 0
22+
overflowed = false
23+
}
24+
25+
init(double: Double, overflow: Bool = false) {
26+
if double == 0 {
27+
self.completed = 0
28+
self.total = 1
29+
} else if double == 1 {
30+
self.completed = 1
31+
self.total = 1
32+
}
33+
(self.completed, self.total) = _ProgressFraction._fromDouble(double)
34+
self.overflowed = overflow
35+
}
36+
37+
init(completed: Int, total: Int?) {
38+
if let total {
39+
self.total = total
40+
self.completed = completed
41+
} else {
42+
self.total = 0
43+
self.completed = completed
44+
}
45+
self.overflowed = false
46+
}
47+
48+
// ----
49+
50+
// Glue code for _NSProgressFraction and _ProgressFraction
51+
init(nsProgressFraction: _NSProgressFraction) {
52+
self.init(completed: Int(nsProgressFraction.completed), total: Int(nsProgressFraction.total))
53+
}
54+
55+
internal mutating func simplify() {
56+
if self.total == 0 {
57+
return
58+
}
59+
60+
(self.completed, self.total) = _ProgressFraction._simplify(completed, total)
61+
}
62+
63+
internal func simplified() -> _ProgressFraction {
64+
let simplified = _ProgressFraction._simplify(completed, total)
65+
return _ProgressFraction(completed: simplified.0, total: simplified.1)
66+
}
67+
68+
static private func _math(lhs: _ProgressFraction, rhs: _ProgressFraction, whichOperator: (_ lhs : Double, _ rhs : Double) -> Double, whichOverflow : (_ lhs: Int, _ rhs: Int) -> (Int, overflow: Bool)) -> _ProgressFraction {
69+
// 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.
70+
precondition(!(lhs.total == 0 && rhs.total == 0), "Attempt to add or subtract invalid fraction")
71+
guard lhs.total != 0 else {
72+
return rhs
73+
}
74+
guard rhs.total != 0 else {
75+
return lhs
76+
}
77+
78+
guard !lhs.overflowed && !rhs.overflowed else {
79+
// If either has overflowed already, we preserve that
80+
return _ProgressFraction(double: whichOperator(lhs.fractionCompleted, rhs.fractionCompleted), overflow: true)
81+
}
82+
83+
//TODO: rdar://148758226 Overflow check
84+
if let lcm = _leastCommonMultiple(lhs.total, rhs.total) {
85+
let result = whichOverflow(lhs.completed * (lcm / lhs.total), rhs.completed * (lcm / rhs.total))
86+
if result.overflow {
87+
return _ProgressFraction(double: whichOperator(lhs.fractionCompleted, rhs.fractionCompleted), overflow: true)
88+
} else {
89+
return _ProgressFraction(completed: result.0, total: lcm)
90+
}
91+
} else {
92+
// Overflow - simplify and then try again
93+
let lhsSimplified = lhs.simplified()
94+
let rhsSimplified = rhs.simplified()
95+
96+
if let lcm = _leastCommonMultiple(lhsSimplified.total, rhsSimplified.total) {
97+
let result = whichOverflow(lhsSimplified.completed * (lcm / lhsSimplified.total), rhsSimplified.completed * (lcm / rhsSimplified.total))
98+
if result.overflow {
99+
// Use original lhs/rhs here
100+
return _ProgressFraction(double: whichOperator(lhs.fractionCompleted, rhs.fractionCompleted), overflow: true)
101+
} else {
102+
return _ProgressFraction(completed: result.0, total: lcm)
103+
}
104+
} else {
105+
// Still overflow
106+
return _ProgressFraction(double: whichOperator(lhs.fractionCompleted, rhs.fractionCompleted), overflow: true)
107+
}
108+
}
109+
}
110+
111+
static internal func +(lhs: _ProgressFraction, rhs: _ProgressFraction) -> _ProgressFraction {
112+
return _math(lhs: lhs, rhs: rhs, whichOperator: +, whichOverflow: { $0.addingReportingOverflow($1) })
113+
}
114+
115+
static internal func -(lhs: _ProgressFraction, rhs: _ProgressFraction) -> _ProgressFraction {
116+
return _math(lhs: lhs, rhs: rhs, whichOperator: -, whichOverflow: { $0.subtractingReportingOverflow($1) })
117+
}
118+
119+
static internal func *(lhs: _ProgressFraction, rhs: _ProgressFraction) -> _ProgressFraction {
120+
guard !lhs.overflowed && !rhs.overflowed else {
121+
// If either has overflowed already, we preserve that
122+
return _ProgressFraction(double: rhs.fractionCompleted * rhs.fractionCompleted, overflow: true)
123+
}
124+
125+
let newCompleted = lhs.completed.multipliedReportingOverflow(by: rhs.completed)
126+
let newTotal = lhs.total.multipliedReportingOverflow(by: rhs.total)
127+
128+
if newCompleted.overflow || newTotal.overflow {
129+
// Try simplifying, then do it again
130+
let lhsSimplified = lhs.simplified()
131+
let rhsSimplified = rhs.simplified()
132+
133+
let newCompletedSimplified = lhsSimplified.completed.multipliedReportingOverflow(by: rhsSimplified.completed)
134+
let newTotalSimplified = lhsSimplified.total.multipliedReportingOverflow(by: rhsSimplified.total)
135+
136+
if newCompletedSimplified.overflow || newTotalSimplified.overflow {
137+
// Still overflow
138+
return _ProgressFraction(double: lhs.fractionCompleted * rhs.fractionCompleted, overflow: true)
139+
} else {
140+
return _ProgressFraction(completed: newCompletedSimplified.0, total: newTotalSimplified.0)
141+
}
142+
} else {
143+
return _ProgressFraction(completed: newCompleted.0, total: newTotal.0)
144+
}
145+
}
146+
147+
static internal func /(lhs: _ProgressFraction, rhs: Int) -> _ProgressFraction {
148+
guard !lhs.overflowed else {
149+
// If lhs has overflowed, we preserve that
150+
return _ProgressFraction(double: lhs.fractionCompleted / Double(rhs), overflow: true)
151+
}
152+
153+
let newTotal = lhs.total.multipliedReportingOverflow(by: rhs)
154+
155+
if newTotal.overflow {
156+
let simplified = lhs.simplified()
157+
158+
let newTotalSimplified = simplified.total.multipliedReportingOverflow(by: rhs)
159+
160+
if newTotalSimplified.overflow {
161+
// Still overflow
162+
return _ProgressFraction(double: lhs.fractionCompleted / Double(rhs), overflow: true)
163+
} else {
164+
return _ProgressFraction(completed: lhs.completed, total: newTotalSimplified.0)
165+
}
166+
} else {
167+
return _ProgressFraction(completed: lhs.completed, total: newTotal.0)
168+
}
169+
}
170+
171+
static internal func ==(lhs: _ProgressFraction, rhs: _ProgressFraction) -> Bool {
172+
if lhs.isNaN || rhs.isNaN {
173+
// NaN fractions are never equal
174+
return false
175+
} else if lhs.completed == rhs.completed && lhs.total == rhs.total {
176+
return true
177+
} else if lhs.total == rhs.total {
178+
// Direct comparison of numerator
179+
return lhs.completed == rhs.completed
180+
} else if lhs.completed == 0 && rhs.completed == 0 {
181+
return true
182+
} else if lhs.completed == lhs.total && rhs.completed == rhs.total {
183+
// Both finished (1)
184+
return true
185+
} else if (lhs.completed == 0 && rhs.completed != 0) || (lhs.completed != 0 && rhs.completed == 0) {
186+
// One 0, one not 0
187+
return false
188+
} else {
189+
// Cross-multiply
190+
let left = lhs.completed.multipliedReportingOverflow(by: rhs.total)
191+
let right = lhs.total.multipliedReportingOverflow(by: rhs.completed)
192+
193+
if !left.overflow && !right.overflow {
194+
if left.0 == right.0 {
195+
return true
196+
}
197+
} else {
198+
// Try simplifying then cross multiply again
199+
let lhsSimplified = lhs.simplified()
200+
let rhsSimplified = rhs.simplified()
201+
202+
let leftSimplified = lhsSimplified.completed.multipliedReportingOverflow(by: rhsSimplified.total)
203+
let rightSimplified = lhsSimplified.total.multipliedReportingOverflow(by: rhsSimplified.completed)
204+
205+
if !leftSimplified.overflow && !rightSimplified.overflow {
206+
if leftSimplified.0 == rightSimplified.0 {
207+
return true
208+
}
209+
} else {
210+
// Ok... fallback to doubles. This doesn't use an epsilon
211+
return lhs.fractionCompleted == rhs.fractionCompleted
212+
}
213+
}
214+
}
215+
216+
return false
217+
}
218+
219+
// ----
220+
221+
internal var isFinished: Bool {
222+
return completed >= total && completed > 0 && total > 0
223+
}
224+
225+
226+
internal var fractionCompleted : Double {
227+
return Double(completed) / Double(total)
228+
}
229+
230+
231+
internal var isNaN : Bool {
232+
return total == 0
233+
}
234+
235+
internal var debugDescription : String {
236+
return "\(completed) / \(total) (\(fractionCompleted))"
237+
}
238+
239+
// ----
240+
241+
private static func _fromDouble(_ d : Double) -> (Int, Int) {
242+
// This simplistic algorithm could someday be replaced with something better.
243+
// Basically - how many 1/Nths is this double?
244+
// And we choose to use 131072 for N
245+
let denominator : Int = 131072
246+
let numerator = Int(d / (1.0 / Double(denominator)))
247+
return (numerator, denominator)
248+
}
249+
250+
private static func _greatestCommonDivisor(_ inA : Int, _ inB : Int) -> Int {
251+
// This is Euclid's algorithm. There are faster ones, like Knuth, but this is the simplest one for now.
252+
var a = inA
253+
var b = inB
254+
repeat {
255+
let tmp = b
256+
b = a % b
257+
a = tmp
258+
} while (b != 0)
259+
return a
260+
}
261+
262+
private static func _leastCommonMultiple(_ a : Int, _ b : Int) -> Int? {
263+
// This division always results in an integer value because gcd(a,b) is a divisor of a.
264+
// lcm(a,b) == (|a|/gcd(a,b))*b == (|b|/gcd(a,b))*a
265+
let result = (a / _greatestCommonDivisor(a, b)).multipliedReportingOverflow(by: b)
266+
if result.overflow {
267+
return nil
268+
} else {
269+
return result.0
270+
}
271+
}
272+
273+
private static func _simplify(_ n : Int, _ d : Int) -> (Int, Int) {
274+
let gcd = _greatestCommonDivisor(n, d)
275+
return (n / gcd, d / gcd)
276+
}
277+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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+
@_spi(Progress)
13+
@available(FoundationPreview 6.2, *)
14+
extension ProgressReporter {
15+
//TODO: rdar://149092406 Manual Codable Conformance
16+
public struct FileFormatStyle: Sendable, Codable, Equatable, Hashable {
17+
18+
internal struct Option: Sendable, Codable, Equatable, Hashable {
19+
20+
internal static var file: Option { Option(.file) }
21+
22+
fileprivate enum RawOption: Codable, Equatable, Hashable {
23+
case file
24+
}
25+
26+
fileprivate var rawOption: RawOption
27+
28+
private init(
29+
_ rawOption: RawOption,
30+
) {
31+
self.rawOption = rawOption
32+
}
33+
}
34+
35+
public var locale: Locale
36+
let option: Option
37+
38+
internal init(_ option: Option, locale: Locale = .autoupdatingCurrent) {
39+
self.locale = locale
40+
self.option = option
41+
}
42+
}
43+
}
44+
45+
46+
@_spi(Progress)
47+
@available(FoundationPreview 6.2, *)
48+
extension ProgressReporter.FileFormatStyle: FormatStyle {
49+
50+
public func locale(_ locale: Locale) -> ProgressReporter.FileFormatStyle {
51+
.init(self.option, locale: locale)
52+
}
53+
54+
public func format(_ reporter: ProgressReporter) -> String {
55+
switch self.option.rawOption {
56+
57+
case .file:
58+
var fileCountLSR: LocalizedStringResource?
59+
var byteCountLSR: LocalizedStringResource?
60+
var throughputLSR: LocalizedStringResource?
61+
var timeRemainingLSR: LocalizedStringResource?
62+
63+
let properties = reporter.withProperties(\.self)
64+
65+
if let totalFileCount = properties.totalFileCount {
66+
let completedFileCount = properties.completedFileCount ?? 0
67+
fileCountLSR = LocalizedStringResource("\(completedFileCount, format: IntegerFormatStyle<Int>()) of \(totalFileCount, format: IntegerFormatStyle<Int>()) files", locale: self.locale, bundle: .forClass(ProgressReporter.self))
68+
}
69+
70+
if let totalByteCount = properties.totalByteCount {
71+
let completedByteCount = properties.completedByteCount ?? 0
72+
byteCountLSR = LocalizedStringResource("\(completedByteCount, format: ByteCountFormatStyle()) of \(totalByteCount, format: ByteCountFormatStyle())", locale: self.locale, bundle: .forClass(ProgressReporter.self))
73+
}
74+
75+
if let throughput = properties.throughput {
76+
throughputLSR = LocalizedStringResource("\(throughput, format: ByteCountFormatStyle())/s", locale: self.locale, bundle: .forClass(ProgressReporter.self))
77+
}
78+
79+
if let timeRemaining = properties.estimatedTimeRemaining {
80+
timeRemainingLSR = LocalizedStringResource("\(timeRemaining, format: Duration.UnitsFormatStyle(allowedUnits: [.hours, .minutes], width: .wide)) remaining", locale: self.locale, bundle: .forClass(ProgressReporter.self))
81+
}
82+
83+
return """
84+
\(String(localized: fileCountLSR ?? ""))
85+
\(String(localized: byteCountLSR ?? ""))
86+
\(String(localized: throughputLSR ?? ""))
87+
\(String(localized: timeRemainingLSR ?? ""))
88+
"""
89+
}
90+
}
91+
}
92+
93+
@_spi(Progress)
94+
@available(FoundationPreview 6.2, *)
95+
// Make access easier to format ProgressReporter
96+
extension ProgressReporter {
97+
public func formatted(_ style: ProgressReporter.FileFormatStyle) -> String {
98+
style.format(self)
99+
}
100+
}
101+
102+
@_spi(Progress)
103+
@available(FoundationPreview 6.2, *)
104+
extension FormatStyle where Self == ProgressReporter.FileFormatStyle {
105+
public static var file: Self {
106+
.init(.file)
107+
}
108+
}

0 commit comments

Comments
 (0)