Skip to content

Commit

Permalink
Use combine to throttle kvo events
Browse files Browse the repository at this point in the history
  • Loading branch information
* committed Jul 3, 2022
1 parent 166acfd commit 6c260d5
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 17 deletions.
35 changes: 25 additions & 10 deletions virtualOS/Model/MainViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ final class MainViewModel: NSObject, ObservableObject {
@Published var statusLabel = ""
@Published var buttonLabel = ""
@Published var buttonDisabled = false
@Published var installProgress: Progress?
@Published var progress: Progress?
@Published var showLicenseInformationModal = false
@Published var showConfirmationAlert = false
@Published var licenseInformationTitleString = ""
Expand Down Expand Up @@ -101,8 +101,9 @@ final class MainViewModel: NSObject, ObservableObject {
func loadLicenseInformationFromBundle() {
if let filepath = Bundle.main.path(forResource: "LICENSE", ofType: "") {
do {
licenseInformationString = "Restore image and virtual machine are stored at:\n\(NSHomeDirectory())\n\n\n\n"
let contents = try String(contentsOfFile: filepath)
licenseInformationString = contents
licenseInformationString += contents
} catch {
licenseInformationString = "Failed to load license information"
}
Expand Down Expand Up @@ -152,7 +153,7 @@ final class MainViewModel: NSObject, ObservableObject {

virtualMac.downloadRestoreImage { (progress: Progress) in
debugLog("Download progress: \(progress.fractionCompleted * 100)%")
self.installProgress = progress
self.progress = progress
self.updateLabels(for: self.state)
} completionHandler: { (errorString: String?) in
if let errorString = errorString {
Expand All @@ -168,11 +169,11 @@ final class MainViewModel: NSObject, ObservableObject {
state = .Installing
virtualMac.install(delegate: self) { (progress: Progress) in
debugLog("Install progress: \(progress.completedUnitCount)%")
self.installProgress = progress
self.progress = progress
self.updateLabels(for: self.state)
} completionHandler: { (errorString: String?, virtualMachine: VZVirtualMachine?) in
DispatchQueue.main.async {
self.installProgress = nil
self.progress = nil
}
if let errorString = errorString {
self.display(errorString: errorString)
Expand Down Expand Up @@ -249,17 +250,17 @@ final class MainViewModel: NSObject, ObservableObject {
statusLabel = state.rawValue
buttonLabel = "Start"
case .Downloading:
if let installProgress = installProgress {
statusLabel = String(format: "Downloading restore image: %2.2f%%", installProgress.fractionCompleted * 100)
if let progress = progress {
updateDownloadProgress(progress)
}
buttonLabel = "Stop"
case .Installing:
if let installProgress = installProgress {
if let progress = progress {
statusLabel = "Installing macOS \(virtualMac.versionString): "
if installProgress.completedUnitCount == 0 {
if progress.completedUnitCount == 0 {
statusLabel = statusLabel + "Waiting for begin …"
} else {
statusLabel = statusLabel + "\(installProgress.completedUnitCount)%"
statusLabel = statusLabel + "\(progress.completedUnitCount)%"
}
}
buttonLabel = "Stop"
Expand All @@ -274,6 +275,20 @@ final class MainViewModel: NSObject, ObservableObject {
buttonDisabled = false
}
}

fileprivate func updateDownloadProgress(_ progress: Progress) {
var statusText = String(format: "Downloading restore image: %2.2f%%", progress.fractionCompleted * 100)

if let byteCompletedCount = progress.userInfo[ProgressUserInfoKey("NSProgressByteCompletedCountKey")] as? Int,
let byteTotalCount = progress.userInfo[ProgressUserInfoKey("NSProgressByteTotalCountKey")] as? Int
{
let mbCompleted = byteCompletedCount / (1024 * 1024)
let mbTotal = byteTotalCount / (1024 * 1024)
statusText += " (\(mbCompleted) of \(mbTotal) MB)"
}

statusLabel = statusText
}
}

extension MainViewModel: VZVirtualMachineDelegate {
Expand Down
18 changes: 11 additions & 7 deletions virtualOS/Model/VirtualMac.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#if arch(arm64)

import Virtualization
import Combine

final class VirtualMac: ObservableObject {
struct Parameters: Codable {
Expand All @@ -29,7 +30,7 @@ final class VirtualMac: ObservableObject {
var parameters = Parameters()
var versionString = "(unknown)"
var virtualMachineConfiguration: VirtualMacConfiguration?
fileprivate var progressObserver: NSKeyValueObservation?
var progressObserverCancellable: Cancellable?
fileprivate var downloadTask: URLSessionDownloadTask?

func readFromDisk(delegate: VZVirtualMachineDelegate) -> String? {
Expand Down Expand Up @@ -213,11 +214,12 @@ final class VirtualMac: ObservableObject {
}

self.downloadTask = downloadTask
progressObserver = downloadTask.progress.observe(\.fractionCompleted, options: [.initial, .new]) { (progress, change) in
DispatchQueue.main.async {

progressObserverCancellable = downloadTask.progress.publisher(for: \.fractionCompleted)
.throttle(for: 1.0, scheduler: RunLoop.main, latest: true)
.sink() { (progress) in
progressHandler(downloadTask.progress)
}
}
downloadTask.resume()
}

Expand Down Expand Up @@ -301,9 +303,11 @@ final class VirtualMac: ObservableObject {
}
}

self.progressObserver = installer.progress.observe(\.fractionCompleted, options: [.initial, .new]) { (progress, change) in
progressHandler(installer.progress)
}
self.progressObserverCancellable = installer.progress.publisher(for: \.fractionCompleted)
.throttle(for: 1.0, scheduler: RunLoop.main, latest: true)
.sink() { (progress) in
progressHandler(installer.progress)
}
}
}

Expand Down
107 changes: 107 additions & 0 deletions virtualOS/View/ConfigurationView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//
// ConfigurationView.swift
// virtualOS
//
// Created by Jahn Bertsch on 03.04.22.
//

#if arch(arm64)

import SwiftUI

struct ConfigurationView: View {
@ObservedObject var viewModel: MainViewModel
fileprivate let sliderTextWidth = CGFloat(150)
@State fileprivate var cpuCountSliderValue: Float = 0 {
didSet {
viewModel.virtualMac.parameters.cpuCount = Int(cpuCountSliderValue)
}
}
@State fileprivate var memorySliderValue: Float = 0 {
didSet {
viewModel.virtualMac.parameters.memorySizeInGB = UInt64(memorySliderValue)
}
}
@State fileprivate var screenWidthValue: Float = 0 {
didSet {
viewModel.virtualMac.parameters.screenWidth = Int(screenWidthValue)
}
}
@State fileprivate var screenHeightValue: Float = 0 {
didSet {
viewModel.virtualMac.parameters.screenHeight = Int(screenHeightValue)
}
}

var body: some View {
VStack {
Spacer()
VStack {
let parameters = viewModel.virtualMac.parameters
Text("Virtual Machine Configuration").font(.title)

Slider(value: Binding(get: {
cpuCountSliderValue
}, set: { (newValue) in
cpuCountSliderValue = newValue
}), in: Float(parameters.cpuCountMin) ... Float(parameters.cpuCountMax), step: 1) {
Text("CPU Count: \(viewModel.virtualMac.parameters.cpuCount)")
.frame(minWidth: sliderTextWidth, alignment: .leading)
}

Slider(value: Binding(get: {
memorySliderValue
}, set: { (newValue) in
memorySliderValue = newValue
}), in: Float(parameters.memorySizeInGBMin) ... Float(parameters.memorySizeInGBMax), step: 1) {
Text("RAM: \(viewModel.virtualMac.parameters.memorySizeInGB) GB")
.frame(minWidth: sliderTextWidth, alignment: .leading)
}

Slider(value: Binding(get: {
screenWidthValue
}, set: { (newValue) in
screenWidthValue = newValue
}), in: 800 ... Float(NSScreen.main?.frame.width ?? CGFloat(parameters.screenWidth)), step: 100) {
Text("Screen Width: \(viewModel.virtualMac.parameters.screenWidth) px")
.frame(minWidth: sliderTextWidth, alignment: .leading)
}

Slider(value: Binding(get: {
screenHeightValue
}, set: { (newValue) in
screenHeightValue = newValue
}), in: 600 ... Float(NSScreen.main?.frame.height ?? CGFloat(parameters.screenHeight)), step: 50) {
Text("Screen Height: \(viewModel.virtualMac.parameters.screenHeight) px")
.frame(minWidth: sliderTextWidth, alignment: .leading)
}
}
.padding()
.overlay {
RoundedRectangle(cornerRadius: 10)
.stroke(.tertiary, lineWidth: 1)
}

Spacer()
}
.padding()
.frame(maxWidth: 400)
.onAppear {
let parameters = viewModel.virtualMac.parameters
cpuCountSliderValue = Float(parameters.cpuCount)
memorySliderValue = Float(parameters.memorySizeInGB)
screenWidthValue = Float(parameters.screenWidth)
screenHeightValue = Float(parameters.screenHeight)
}
}
}

struct ConfigurationViewProvider_Previews: PreviewProvider {
static var previews: some View {
VStack {
ConfigurationView(viewModel: MainViewModel())
}
}
}

#endif

0 comments on commit 6c260d5

Please sign in to comment.