Skip to content

Commit 7086f13

Browse files
committed
Batch audio plugin loading
1 parent 7170290 commit 7086f13

1 file changed

Lines changed: 68 additions & 47 deletions

File tree

Pearcleaner/Views/PluginsView.swift

Lines changed: 68 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ struct PluginsView: View {
6666
@EnvironmentObject var appState: AppState
6767
@EnvironmentObject var locations: Locations
6868
@Environment(\.colorScheme) var colorScheme
69-
@State private var plugins: [String: [PluginInfo]] = [:]
7069
@State private var allPlugins: [PluginInfo] = []
7170
@State private var isLoading: Bool = false
7271
@State private var lastRefreshDate: Date?
@@ -343,7 +342,6 @@ struct PluginsView: View {
343342
private func refreshPluginsAsync() async {
344343
await MainActor.run {
345344
isLoading = true
346-
plugins = [:]
347345
allPlugins = []
348346
selectedPlugins = []
349347
selectedPluginPaths = []
@@ -363,30 +361,24 @@ struct PluginsView: View {
363361
let fileManager = FileManager.default
364362
let pluginCategories = locations.plugins.subcategories
365363

366-
// Process categories concurrently but update UI incrementally
367-
await withTaskGroup(of: (String, [PluginInfo]).self) { group in
364+
// Process categories concurrently with incremental UI updates
365+
await withTaskGroup(of: Void.self) { group in
368366

369367
// Add tasks for each category
370368
for (category, paths) in pluginCategories {
371369
group.addTask {
372-
return await self.processCategory(category: category, paths: paths, fileManager: fileManager)
370+
await self.processCategory(category: category, paths: paths, fileManager: fileManager)
373371
}
374372
}
375373

376-
// Collect results and update UI incrementally
377-
for await (category, categoryPlugins) in group {
378-
if !categoryPlugins.isEmpty {
379-
await MainActor.run {
380-
self.plugins[category] = categoryPlugins
381-
self.allPlugins.append(contentsOf: categoryPlugins)
382-
}
383-
}
384-
}
374+
// Wait for all categories to complete (no results to collect - processCategory updates UI directly)
375+
await group.waitForAll()
385376
}
386377
}
387378

388-
private func processCategory(category: String, paths: [String], fileManager: FileManager) async -> (String, [PluginInfo]) {
389-
var categoryPlugins: [PluginInfo] = []
379+
private func processCategory(category: String, paths: [String], fileManager: FileManager) async {
380+
var batchBuffer: [PluginInfo] = []
381+
let batchSize = 20 // Update UI every 20 plugins for smooth incremental loading
390382

391383
for path in paths {
392384
// Check if path exists before trying to read it
@@ -415,13 +407,8 @@ struct PluginsView: View {
415407
if !name.hasPrefix(".") && !name.hasPrefix("~") {
416408
// Check if file matches the expected types for this category
417409
if shouldIncludeFile(name: name, isDirectory: isDirectory, category: category) {
418-
// Extract bundle info for Audio .driver bundles
419-
let bundleId = await extractBundleId(from: itemURL.path, category: category, isDirectory: isDirectory)
420-
421-
// Skip Apple system plugins for Audio category
422-
if category == "Audio", let bundleId = bundleId, bundleId.contains("com.apple") {
423-
continue
424-
}
410+
// Bundle ID will be loaded lazily when needed for search
411+
let bundleId: String? = nil
425412

426413
let plugin = PluginInfo(
427414
name: name,
@@ -433,7 +420,17 @@ struct PluginsView: View {
433420
bundleId: bundleId,
434421
customIcon: nil
435422
)
436-
categoryPlugins.append(plugin)
423+
batchBuffer.append(plugin)
424+
425+
// Update UI every batchSize plugins for incremental loading
426+
if batchBuffer.count >= batchSize {
427+
let pluginsToAdd = batchBuffer
428+
batchBuffer = []
429+
430+
await MainActor.run {
431+
self.allPlugins.append(contentsOf: pluginsToAdd)
432+
}
433+
}
437434
}
438435
}
439436
} catch {
@@ -447,26 +444,40 @@ struct PluginsView: View {
447444
}
448445
}
449446

450-
// #if DEBUG
451-
// // Duplicate ZoomAudioDevice.driver 599 more times for performance testing
452-
// if let zoomPlugin = categoryPlugins.first(where: { $0.name == "ZoomAudioDevice.driver" }) {
453-
// for i in 1...599 {
454-
// let duplicatedPlugin = PluginInfo(
455-
// name: "\(zoomPlugin.name) (Copy \(i))",
456-
// path: "\(zoomPlugin.path)_copy\(i)", // Fake path
457-
// category: zoomPlugin.category,
458-
// isDirectory: zoomPlugin.isDirectory,
459-
// size: zoomPlugin.size,
460-
// dateModified: zoomPlugin.dateModified,
461-
// bundleId: zoomPlugin.bundleId.map { "\($0).copy\(i)" },
462-
// customIcon: zoomPlugin.customIcon
463-
// )
464-
// categoryPlugins.append(duplicatedPlugin)
465-
// }
466-
// }
467-
// #endif
468-
469-
return (category, categoryPlugins)
447+
// Flush any remaining plugins in the batch buffer
448+
if !batchBuffer.isEmpty {
449+
await MainActor.run {
450+
self.allPlugins.append(contentsOf: batchBuffer)
451+
}
452+
}
453+
454+
#if DEBUG
455+
// Duplicate ZoomAudioDevice.driver 599 more times for performance testing
456+
if let zoomPlugin = self.allPlugins.first(where: { $0.name == "ZoomAudioDevice.driver" && $0.category == category }) {
457+
var debugPlugins: [PluginInfo] = []
458+
for i in 1...599 {
459+
let duplicatedPlugin = PluginInfo(
460+
name: "\(zoomPlugin.name) (Copy \(i))",
461+
path: "\(zoomPlugin.path)_copy\(i)", // Fake path
462+
category: zoomPlugin.category,
463+
isDirectory: zoomPlugin.isDirectory,
464+
size: zoomPlugin.size,
465+
dateModified: zoomPlugin.dateModified,
466+
bundleId: zoomPlugin.bundleId.map { "\($0).copy\(i)" },
467+
customIcon: zoomPlugin.customIcon
468+
)
469+
debugPlugins.append(duplicatedPlugin)
470+
}
471+
472+
// Add debug plugins in batches for smooth UI
473+
let debugBatches = debugPlugins.chunked(into: batchSize)
474+
for batch in debugBatches {
475+
await MainActor.run {
476+
self.allPlugins.append(contentsOf: batch)
477+
}
478+
}
479+
}
480+
#endif
470481
}
471482

472483

@@ -759,7 +770,8 @@ struct PluginCategorySection: View {
759770
}
760771
}
761772
}
762-
.transition(.opacity.combined(with: .slide))
773+
.transition(plugins.count > 50 ? .identity : .opacity)
774+
763775
}
764776
}
765777
}
@@ -1246,7 +1258,7 @@ struct AudioPluginRowView: View {
12461258
.padding(.vertical, 4)
12471259
}
12481260
}
1249-
.transition(pluginsCount > 50 ? .identity : .opacity.combined(with: .slide))
1261+
.transition(pluginsCount > 50 ? .identity : .opacity)
12501262
}
12511263
}
12521264
.background(
@@ -1303,8 +1315,17 @@ struct AudioPluginRowView: View {
13031315
// Build search filters - search for plugin name
13041316
let filters: [FilterType] = [.name(.contains, pluginName)]
13051317

1318+
// Lazily extract bundle ID only when search is clicked (if not already loaded)
1319+
var bundleIdToSearch: String? = plugin.bundleId
1320+
if bundleIdToSearch == nil && plugin.isDirectory && plugin.path.hasSuffix(".driver") {
1321+
let infoPlistPath = plugin.path + "/Contents/Info.plist"
1322+
if let plistData = NSDictionary(contentsOfFile: infoPlistPath) {
1323+
bundleIdToSearch = plistData["CFBundleIdentifier"] as? String
1324+
}
1325+
}
1326+
13061327
// If bundle ID exists, also search for that
1307-
if let bundleId = plugin.bundleId, !bundleId.isEmpty {
1328+
if let bundleId = bundleIdToSearch, !bundleId.isEmpty {
13081329
// Don't add bundle ID as a filter since we can only search for name
13091330
// Instead, we'll do two searches - one for plugin name, one for bundle ID
13101331
let bundleIdEngine = FileSearchEngine()

0 commit comments

Comments
 (0)