Skip to content

Commit

Permalink
Merge pull request #58 from yutailang0119/swift-concurrency
Browse files Browse the repository at this point in the history
Swift concurrency
  • Loading branch information
yutailang0119 authored Jan 1, 2025
2 parents 34f045e + 85c6c88 commit 610fa6f
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 211 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PackageDescription
let package = Package(
name: "ProgressSpinnerKit",
platforms: [
.macOS(.v10_15)
.macOS(.v13)
],
products: [
.library(name: "ProgressSpinnerKit", targets: ["ProgressSpinnerKit"])
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@ https://github.com/apple/swift-package-manager
import TSCBasic
import ProgressSpinnerKit

let spinner = progressSpinner(for: TSCBasic.stdoutStream, header: " Loading:")
spinner.start()
let task = Task {
let spinner = progressSpinner(for: TSCBasic.stdoutStream, header: " Loading:")
await spinner.start()
}
// Something on the main thread.
spinner.stop()
task.cancel()
```

## Author
Expand Down
138 changes: 41 additions & 97 deletions Sources/ProgressSpinnerKit/ProgressSpinner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,163 +9,107 @@ import Foundation
import TSCBasic
import TSCUtility

private var fps: useconds_t {
let fps: Double = 1 / 60
return useconds_t(fps * pow(1000, 2))
private var fps: Double {
1 / 60
}

public protocol ProgressSpinnable {
func start()
func stop()
func start() async
}

/// A single line progress bar.
final class SingleLineProgressSpinner: ProgressSpinnable {
private let stream: OutputByteStream
private let header: String
private var spinner: Spinner
private var isProgressing: Bool
private var isClear: Bool // true if haven't drawn anything yet.
private var displayed: Set<Int> = []

private let queue: DispatchQueue
private let sleepInterval: useconds_t

init(stream: OutputByteStream, header: String, spinner: Spinner) {
self.stream = stream
self.header = header
self.spinner = spinner
self.isProgressing = false
self.isClear = true
self.queue = DispatchQueue(label: "progressSpinnerQueue", qos: .background)
self.sleepInterval = fps * 100
}

func start() {
isProgressing = true
func start() async {
stream.send(header)
stream.send("\n")
stream.flush()

if isClear {
stream.send(header)
stream.send("\n")
stream.flush()
isClear = false
let timer = AsyncStream {
try? await Task.sleep(for: .seconds(fps))
}

queue.async { [weak self] in
guard let self = self else {
return
}
while self.isProgressing {
self.stream.send(self.spinner.frame)
self.stream.send("\n")
self.stream.flush()

usleep(self.sleepInterval)
for await _ in timer {
if !Task.isCancelled {
stream.send(spinner.frame)
stream.send("\n")
stream.flush()
}
}
}

func stop() {
isProgressing = false
}
}

final class SimpleProgressSpinner: ProgressSpinnable {
private let stream: OutputByteStream
private let header: String
private var spinner: Spinner
private var isProgressing: Bool
private var isClear: Bool // true if haven't drawn anything yet.

private let queue: DispatchQueue
private let sleepInterval: useconds_t

init(stream: OutputByteStream, header: String, spinner: Spinner) {
self.stream = stream
self.header = header
self.spinner = spinner
self.isProgressing = false
self.isClear = true
self.queue = DispatchQueue(label: "progressSpinnerQueue", qos: .background)
self.sleepInterval = fps * 100
}

func start() {
isProgressing = true
func start() async {
stream.send(header)
stream.send("\n")
stream.flush()

if isClear {
stream.send(header)
stream.send("\n")
stream.flush()
isClear = false
let timer = AsyncStream {
try? await Task.sleep(for: .seconds(fps))
}

queue.async { [weak self] in
guard let self = self else {
return
}
while self.isProgressing {
self.stream.send(self.spinner.frame)
self.stream.send("\n")
self.stream.flush()

usleep(self.sleepInterval)
for await _ in timer {
if !Task.isCancelled {
stream.send(spinner.frame)
stream.send("\n")
stream.flush()
}
}

}

func stop() {
isProgressing = false
}

}

final class ProgressSpinner: ProgressSpinnable {
private let term: TerminalController
private let header: String
private var spinner: Spinner
private var isProgressing: Bool

private let queue: DispatchQueue
private let sleepInterval: useconds_t

init(term: TerminalController, header: String, spinner: Spinner) {
self.term = term
self.header = header
self.spinner = spinner
self.isProgressing = false
self.queue = DispatchQueue(label: "progressSpinnerQueue", qos: .background)
self.sleepInterval = fps
}

func start() {
isProgressing = true

queue.async { [weak self] in
guard let self = self else {
return
}
while self.isProgressing {
self.term.clearLine()
self.term.write(self.header, inColor: .green, bold: true)
self.term.write(self.spinner.frame, inColor: .green)
self.term.endLine()
func start() async {
let timer = AsyncThrowingStream {
try await Task.sleep(for: .seconds(fps))
}

self.term.moveCursor(up: 1)
do {
for try await _ in timer {
if !Task.isCancelled {
term.clearLine()
term.write(header, inColor: .green, bold: true)
term.write(spinner.frame, inColor: .green)
term.endLine()

usleep(self.sleepInterval)
term.moveCursor(up: 1)
}
}
} catch {
term.clearLine()
term.endLine()
}

}

func stop() {
isProgressing = false
term.clearLine()
term.endLine()
}

}

/// Creates colored or simple progress spinner based on the provided output stream.
Expand Down
Loading

0 comments on commit 610fa6f

Please sign in to comment.