123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 |
- //
- // Copyright © 2020 osy. All rights reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //
- import SwiftUI
- private let bytesInMib: UInt64 = 1024 * 1024
- private let minMemoryMib = 32
- private let baseUsageMib = 128
- private let warningThreshold = 0.9
- struct VMConfigSystemView: View {
- @Binding var config: UTMQemuConfigurationSystem
- @Binding var isResetConfig: Bool
- @State private var warningMessage: WarningMessage? = nil
-
- @State private var architecture: QEMUArchitecture = .x86_64
- @State private var target: any QEMUTarget = QEMUTarget_x86_64.pc
-
- var body: some View {
- VStack {
- Form {
- HardwareOptions(config: $config, architecture: $architecture, target: $target, warningMessage: $warningMessage)
- RAMSlider(systemMemory: $config.memorySize, onValidate: validateMemorySize)
- Section(header: Text("CPU")) {
- VMConfigConstantPicker(selection: $config.cpu, type: config.architecture.cpuType)
- }
- CPUFlagsOptions(title: "Force Enable CPU Flags", config: $config, flags: $config.cpuFlagsAdd)
- .help("If checked, the CPU flag will be enabled. Otherwise, the default value will be used.")
- CPUFlagsOptions(title: "Force Disable CPU Flags", config: $config, flags: $config.cpuFlagsRemove)
- .help("If checked, the CPU flag will be disabled. Otherwise, the default value will be used.")
- DetailedSection("CPU Cores", description: "Force multicore may improve speed of emulation but also might result in unstable and incorrect emulation.") {
- HStack {
- NumberTextField("", number: $config.cpuCount, prompt: "Default", onEditingChanged: validateCpuCount)
- .multilineTextAlignment(.trailing)
- Text("Cores")
- }
- Toggle(isOn: $config.isForceMulticore, label: {
- Text("Force Multicore")
- })
- }
- DetailedSection("JIT Cache", description: "Default is 1/4 of the RAM size (above). The JIT cache size is additive to the RAM size in the total memory usage!") {
- HStack {
- NumberTextField("", number: $config.jitCacheSize, prompt: "Default", onEditingChanged: validateMemorySize)
- .multilineTextAlignment(.trailing)
- Text("MiB")
- }
- }
- }
- }.alert(item: $warningMessage) { warning in
- switch warning {
- case .overallocatedRam(_, _):
- return Alert(title: Text(warning.localizedWarningTitle), message: Text(warning.localizedWarningMessage))
- case .resetSystem:
- return Alert(title: Text(warning.localizedWarningTitle), message: Text(warning.localizedWarningMessage), primaryButton: .destructive(Text("Reset"), action: {
- config.architecture = architecture
- if !architecture.targetType.allRawValues.contains(target.rawValue) {
- target = architecture.targetType.default
- }
- config.target = target
- isResetConfig = true
- }), secondaryButton: .cancel(Text("Cancel"), action: {
- architecture = config.architecture
- target = config.target
- }))
- }
- }.disableAutocorrection(true)
- }
-
- func validateMemorySize(editing: Bool) {
- guard !editing else {
- return
- }
- let memorySizeMib = config.memorySize
- guard memorySizeMib >= minMemoryMib else {
- config.memorySize = 0
- return
- }
- let jitSizeMib = config.jitCacheSize
- guard jitSizeMib >= 0 else {
- config.jitCacheSize = 0
- return
- }
- var totalDeviceMemory = ProcessInfo.processInfo.physicalMemory
- #if os(iOS) || os(visionOS)
- let availableMemory = UInt64(os_proc_available_memory())
- if availableMemory > 0 {
- totalDeviceMemory = availableMemory
- }
- #endif
- let actualJitSizeMib = jitSizeMib == 0 ? memorySizeMib / 4 : jitSizeMib
- let jitMirrorMultiplier = UTMCapabilities.current.contains(.hasJitEntitlements) ? 1 : 2;
- let estMemoryUsage = UInt64(memorySizeMib + jitMirrorMultiplier*actualJitSizeMib + baseUsageMib) * bytesInMib
- if Double(estMemoryUsage) > Double(totalDeviceMemory) * warningThreshold {
- warningMessage = WarningMessage.overallocatedRam(totalMib: totalDeviceMemory / bytesInMib, estimatedMib: estMemoryUsage / bytesInMib)
- }
- }
- func validateCpuCount(editing: Bool) {
- guard !editing else {
- return
- }
- guard config.cpuCount >= 0 else {
- config.cpuCount = 0
- return
- }
- }
- }
- private enum WarningMessage: Identifiable {
- case overallocatedRam(totalMib: UInt64, estimatedMib: UInt64)
- case resetSystem
-
- var id: Int {
- switch self {
- case .overallocatedRam(_, _):
- return 1
- case .resetSystem:
- return 2
- }
- }
-
- var localizedWarningTitle: String {
- switch self {
- case .overallocatedRam(_, _):
- return NSLocalizedString("Allocating too much memory will crash the VM.", comment: "VMConfigSystemView")
- case .resetSystem:
- return NSLocalizedString("This change will reset all settings", comment: "VMConfigSystemView")
- }
- }
-
- var localizedWarningMessage: String {
- switch self {
- case .overallocatedRam(let totalMib, let estimatedMib):
- let format = NSLocalizedString("Your device has %llu MB of memory and the estimated usage is %llu MB.", comment: "VMConfigSystemView")
- return String.localizedStringWithFormat(format, totalMib, estimatedMib)
- case .resetSystem:
- return NSLocalizedString("Any unsaved changes will be lost.", comment: "VMConfigSystemView")
- }
- }
- }
- private struct HardwareOptions: View {
- @Binding var config: UTMQemuConfigurationSystem
- @Binding var architecture: QEMUArchitecture
- @Binding var target: any QEMUTarget
- @Binding var warningMessage: WarningMessage?
- @EnvironmentObject private var data: UTMData
- @State private var isArchitectureFirstAppear: Bool = true
- @State private var isArchitectureSupported: Bool = true
- @State private var isTargetFirstAppear: Bool = true
-
- var body: some View {
- Section(header: Text("Hardware")) {
- VMConfigConstantPicker("Architecture", selection: $architecture)
- .onAppear {
- if isArchitectureFirstAppear {
- architecture = config.architecture
- }
- isArchitectureFirstAppear = false
- }
- .onChange(of: architecture) { newValue in
- if newValue != config.architecture {
- warningMessage = .resetSystem
- }
- }
- .onChange(of: config.architecture) { newValue in
- isArchitectureSupported = ConcreteVirtualMachine.isSupported(systemArchitecture: newValue)
- if newValue != architecture {
- architecture = newValue
- }
- }
- if !isArchitectureSupported {
- Text("The selected architecture is unsupported in this version of UTM.")
- .foregroundColor(.red)
- }
- VMConfigConstantPicker("System", selection: $target, type: config.architecture.targetType)
- .onAppear {
- if isTargetFirstAppear {
- target = config.target
- }
- isTargetFirstAppear = false
- }
- .onChange(of: target.rawValue) { newValue in
- if newValue != config.target.rawValue {
- warningMessage = .resetSystem
- }
- }
- .onChange(of: config.target.rawValue) { newValue in
- if newValue != target.rawValue {
- target = AnyQEMUConstant(rawValue: newValue)!
- }
- }
- }
- }
- }
- struct CPUFlagsOptions: View {
- let title: LocalizedStringKey
- @Binding var config: UTMQemuConfigurationSystem
- @Binding var flags: [any QEMUCPUFlag]
- @State private var showAllFlags: Bool = false
-
- var body: some View {
- let allFlags = config.architecture.cpuFlagType.allRawValues
- if config.cpu.rawValue != "default" && allFlags.count > 0 {
- Section(header: Text(title)) {
- if showAllFlags || flags.count > 0 {
- OptionsList {
- ForEach(allFlags) { flagStr in
- let flag = config.architecture.cpuFlagType.init(rawValue: flagStr)!
- let isFlagOn = Binding<Bool> { () -> Bool in
- flags.contains(where: { $0.rawValue == flag.rawValue })
- } set: { isOn in
- if isOn {
- flags.append(flag)
- } else {
- flags.removeAll(where: { $0.rawValue == flag.rawValue })
- }
- }
- if showAllFlags || isFlagOn.wrappedValue {
- Toggle(isOn: isFlagOn, label: {
- Text(flag.prettyValue)
- })
- }
- }
- }
- }
- Button {
- showAllFlags.toggle()
- } label: {
- if (showAllFlags) {
- Text("Hide Unused…")
- } else {
- Text("Show All…")
- }
- }
- }
- }
- }
- }
- struct OptionsList<Content>: View where Content: View {
- private var columns: [GridItem] = [
- GridItem(.fixed(150), spacing: 16),
- GridItem(.fixed(150), spacing: 16),
- GridItem(.fixed(150), spacing: 16),
- GridItem(.fixed(150), spacing: 16)
- ]
-
- var content: () -> Content
-
- init(content: @escaping () -> Content) {
- self.content = content
- }
-
- var body: some View {
- #if os(macOS)
- LazyVGrid(columns: columns, alignment: .leading) {
- content()
- }
- #else
- LazyVStack {
- content()
- }
- #endif
- }
- }
- struct VMConfigSystemView_Previews: PreviewProvider {
- @State static private var config = UTMQemuConfigurationSystem()
-
- static var previews: some View {
- VMConfigSystemView(config: $config, isResetConfig: .constant(false))
- #if os(macOS)
- .scrollable()
- #endif
- }
- }
|