From 6307ee17400dd8847cf1c3ec8c4ab893ec520a6e Mon Sep 17 00:00:00 2001 From: Tae Won Ha Date: Sat, 26 Nov 2016 22:12:41 +0100 Subject: [PATCH] GH-351 Use concurrent map for column width computation --- VimR/FileItem.swift | 6 +++++- VimR/FileItemService.swift | 25 ++++++++++++++++++++++--- VimR/FileOutlineView.swift | 37 +++++++++++++------------------------ 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/VimR/FileItem.swift b/VimR/FileItem.swift index 15aeced4..adef7bcc 100644 --- a/VimR/FileItem.swift +++ b/VimR/FileItem.swift @@ -12,7 +12,7 @@ class Token: Equatable { } } -class FileItem : CustomStringConvertible, Hashable, Copyable { +class FileItem : CustomStringConvertible, Hashable, Comparable, Copyable { typealias InstanceType = FileItem @@ -20,6 +20,10 @@ class FileItem : CustomStringConvertible, Hashable, Copyable { return left.url == right.url } + static func <(left: FileItem, right: FileItem) -> Bool { + return left.url.lastPathComponent < right.url.lastPathComponent + } + let url: URL let isDir: Bool let isHidden: Bool diff --git a/VimR/FileItemService.swift b/VimR/FileItemService.swift index 439fc592..45ef087c 100644 --- a/VimR/FileItemService.swift +++ b/VimR/FileItemService.swift @@ -217,6 +217,19 @@ class FileItemService: StandardFlow { return fileItem } + func sortedChildren(for url: URL) -> [FileItem] { + guard let fileItem = self.fileItem(for: url) else { + return [] + } + + if !fileItem.childrenScanned || fileItem.needsScanChildren { + self.scanChildren(fileItem, sorted: true) + return fileItem.children + } + + return fileItem.children.sorted() + } + // FIXME: what if root? fileprivate func parentFileItem(of url: URL) -> FileItem { return self.fileItem(for: Array(url.pathComponents.dropLast()))! @@ -226,7 +239,7 @@ class FileItemService: StandardFlow { /// instantiates the intermediate `FileItem`s. The children of the result may be empty. /// /// - returns: `FileItem` corresponding to `pathComponents`. `nil` if the file does not exist. - fileprivate func fileItem(for url: URL) -> FileItem? { + func fileItem(for url: URL) -> FileItem? { let pathComponents = url.pathComponents return self.fileItem(for: pathComponents) } @@ -275,9 +288,15 @@ class FileItemService: StandardFlow { return filteredChildren.first } - fileprivate func scanChildren(_ item: FileItem) { + fileprivate func scanChildren(_ item: FileItem, sorted: Bool = false) { let children = FileUtils.directDescendants(of: item.url).map(FileItem.init) - self.syncAddChildren { item.children = children } + self.syncAddChildren { + if sorted { + item.children = children.sorted() + } else { + item.children = children + } + } item.childrenScanned = true item.needsScanChildren = false diff --git a/VimR/FileOutlineView.swift b/VimR/FileOutlineView.swift index 23a03871..da39495b 100644 --- a/VimR/FileOutlineView.swift +++ b/VimR/FileOutlineView.swift @@ -60,8 +60,6 @@ class FileOutlineView: NSOutlineView, Flow, NSOutlineViewDataSource, NSOutlineVi fileprivate let fileItemService: FileItemService - fileprivate let cellWidthCache = NSCache() - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -77,8 +75,7 @@ class FileOutlineView: NSOutlineView, Flow, NSOutlineViewDataSource, NSOutlineVi self.flow = EmbeddableComponent(source: source) self.fileItemService = fileItemService - let rootFileItem = fileItemService.fileItemWithChildren(for: self.cwd) - ?? fileItemService.fileItemWithChildren(for: FileUtils.userHomeUrl)! + let rootFileItem = fileItemService.fileItem(for: self.cwd) ?? fileItemService.fileItem(for: FileUtils.userHomeUrl)! self.root = FileBrowserItem(fileItem: rootFileItem) super.init(frame: CGRect.zero) @@ -108,7 +105,7 @@ class FileOutlineView: NSOutlineView, Flow, NSOutlineViewDataSource, NSOutlineVi } fileprivate func handleRemovals(for fileBrowserItem: FileBrowserItem, new newChildren: [FileBrowserItem]) { - let curChildren = fileBrowserItem.children.sorted() + let curChildren = fileBrowserItem.children let curPreparedChildren = self.prepare(curChildren) let newPreparedChildren = self.prepare(newChildren) @@ -125,7 +122,7 @@ class FileOutlineView: NSOutlineView, Flow, NSOutlineViewDataSource, NSOutlineVi } fileprivate func handleAdditions(for fileBrowserItem: FileBrowserItem, new newChildren: [FileBrowserItem]) { - let curChildren = fileBrowserItem.children.sorted() + let curChildren = fileBrowserItem.children // We don't just take newChildren because NSOutlineView look at the pointer equality for preserving the expanded // states... @@ -144,7 +141,7 @@ class FileOutlineView: NSOutlineView, Flow, NSOutlineViewDataSource, NSOutlineVi } fileprivate func handleChildren(for fileBrowserItem: FileBrowserItem, new newChildren: [FileBrowserItem]) { - let curChildren = fileBrowserItem.children.sorted() + let curChildren = fileBrowserItem.children let curPreparedChildren = self.prepare(curChildren) let newPreparedChildren = self.prepare(newChildren) @@ -159,9 +156,7 @@ class FileOutlineView: NSOutlineView, Flow, NSOutlineViewDataSource, NSOutlineVi let url = fileBrowserItem.fileItem.url // Sort the array to keep the order. - let newChildren = (self.fileItemService.fileItemWithChildren(for: url)?.children ?? []) - .map(FileBrowserItem.init) - .sorted() + let newChildren = self.fileItemService.sortedChildren(for: url).map(FileBrowserItem.init) self.handleRemovals(for: fileBrowserItem, new: newChildren) self.handleAdditions(for: fileBrowserItem, new: newChildren) @@ -195,15 +190,15 @@ class FileOutlineView: NSOutlineView, Flow, NSOutlineViewDataSource, NSOutlineVi extension FileOutlineView { fileprivate func prepare(_ children: [FileBrowserItem]) -> [FileBrowserItem] { - return children.filter { !$0.fileItem.isHidden }.sorted() + return children.filter { !$0.fileItem.isHidden } } func outlineView(_: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { if item == nil { - let rootFileItem = fileItemService.fileItemWithChildren(for: self.cwd) - ?? fileItemService.fileItemWithChildren(for: FileUtils.userHomeUrl)! + let rootFileItem = fileItemService.fileItem(for: self.cwd) + ?? fileItemService.fileItem(for: FileUtils.userHomeUrl)! self.root = FileBrowserItem(fileItem: rootFileItem) - self.root.children = rootFileItem.children.map(FileBrowserItem.init) + self.root.children = fileItemService.sortedChildren(for: self.cwd).map(FileBrowserItem.init) return self.prepare(self.root.children).count } @@ -214,7 +209,7 @@ extension FileOutlineView { let fileItem = fileBrowserItem.fileItem if fileItem.isDir { - let fileItemChildren = self.fileItemService.fileItemWithChildren(for: fileItem.url)?.children ?? [] + let fileItemChildren = self.fileItemService.sortedChildren(for: fileItem.url) fileBrowserItem.fileItem.children = fileItemChildren fileBrowserItem.children = fileItemChildren.map(FileBrowserItem.init) return self.prepare(fileBrowserItem.children).count @@ -259,18 +254,12 @@ extension FileOutlineView { fileprivate func adjustColumnWidth(for items: [FileBrowserItem], outlineViewLevel level: Int) { let column = self.outlineTableColumn! + // It seems like that caching the widths is slower due to thread-safeness of NSCache... let cellWidth = items.concurrentChunkMap(20) { - let name = $0.fileItem.url.lastPathComponent - - guard let cached = self.cellWidthCache.object(forKey: name as NSString) else { - let result = ImageAndTextTableCell.width(with: name) - self.cellWidthCache.setObject(NSNumber(value: Float(result)), forKey: name as NSString) + let result = ImageAndTextTableCell.width(with: $0.fileItem.url.lastPathComponent) return result } - - return CGFloat(cached.floatValue) - } - .max() ?? column.maxWidth + .max() ?? column.maxWidth let width = cellWidth + (CGFloat(level + 2) * (self.indentationPerLevel + 2)) // + 2 just to have a buffer... -_-