From 52ecce6f020250e4f281957bf92404d457cf0ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=BCleyman=20Yasir=20KULA?= Date: Mon, 2 Nov 2020 18:40:11 +0300 Subject: [PATCH] Fixed performance issues that occur when selecting lots of files --- .../SimpleFileBrowser/Scripts/FileBrowser.cs | 162 ++++++++++++------ .../FileBrowserDeleteConfirmationPanel.cs | 17 +- package.json | 2 +- 3 files changed, 117 insertions(+), 64 deletions(-) diff --git a/Plugins/SimpleFileBrowser/Scripts/FileBrowser.cs b/Plugins/SimpleFileBrowser/Scripts/FileBrowser.cs index 5092653..e90003a 100644 --- a/Plugins/SimpleFileBrowser/Scripts/FileBrowser.cs +++ b/Plugins/SimpleFileBrowser/Scripts/FileBrowser.cs @@ -103,6 +103,8 @@ public override string ToString() private const string FOLDERS_FILTER_TEXT = "Folders"; private string DEFAULT_PATH; + private const int FILENAME_INPUT_FIELD_MAX_FILE_COUNT = 7; + #if !UNITY_EDITOR && UNITY_ANDROID private const string SAF_PICK_FOLDER_QUICK_LINK_TEXT = "Pick Folder"; private const string SAF_PICK_FOLDER_QUICK_LINK_PATH = "SAF_PICK_FOLDER"; @@ -365,13 +367,19 @@ private string CurrentPath if( value != null ) value = GetPathWithoutTrailingDirectorySeparator( value.Trim() ); - if( value == null ) + if( string.IsNullOrEmpty( value ) ) + { + pathInputField.text = m_currentPath; return; + } if( m_currentPath != value ) { if( !FileBrowserHelpers.DirectoryExists( value ) ) + { + pathInputField.text = m_currentPath; return; + } m_currentPath = value; pathInputField.text = m_currentPath; @@ -404,7 +412,10 @@ private string CurrentPath filenameImage.color = Color.white; if( m_folderSelectMode ) + { filenameInputField.text = string.Empty; + filenameInputField.interactable = true; + } } m_multiSelectionToggleSelectionMode = false; @@ -465,8 +476,8 @@ private bool FolderSelectMode } Text placeholder = filenameInputField.placeholder as Text; - if( placeholder != null ) - placeholder.text = m_folderSelectMode ? string.Empty : "Filename"; + if( placeholder ) + placeholder.gameObject.SetActive( !m_folderSelectMode ); } } } @@ -873,64 +884,85 @@ public void OnSubmitButtonClicked() return; } - // In the first iteration, verify that all filenames entered to the input field are valid - // ExtractFilenameFromInput doesn't use Substring, so this iteration is GC-free - int startIndex = 0, nextStartIndex; - int fileCount = 0; - int indexOfDirectoryEntryToOpen = -1; - while( startIndex < filenameInput.Length ) + if( m_allowMultiSelection && selectedFileEntries.Count > 1 ) { - int filenameLength = ExtractFilenameFromInput( filenameInput, ref startIndex, out nextStartIndex ); - if( filenameLength == 0 ) - continue; + // When multiple files are selected via file browser UI, filenameInputField is not interactable and will show + // only the first FILENAME_INPUT_FIELD_MAX_FILE_COUNT entries for performance reasons. We should iterate over + // selectedFileEntries instead of filenameInputField - if( m_acceptNonExistingFilename ) - fileCount++; - else + // Beforehand, check if a folder is selected in file selection mode. If so, open that directory + if( !m_folderSelectMode ) { - int fileEntryIndex = FilenameInputToFileEntryIndex( filenameInput, startIndex, filenameLength ); - if( fileEntryIndex < 0 ) - { - // File doesn't exist - filenameImage.color = wrongFilenameColor; - return; - } - - if( validFileEntries[fileEntryIndex].IsDirectory ) + for( int i = 0; i < selectedFileEntries.Count; i++ ) { - if( m_folderSelectMode ) - fileCount++; - else + if( validFileEntries[selectedFileEntries[i]].IsDirectory ) { - // Selected a directory in file selection mode, we'll open that directory if no files are selected - indexOfDirectoryEntryToOpen = fileEntryIndex; + CurrentPath = validFileEntries[selectedFileEntries[i]].Path; + return; } } + } + + string[] result = new string[selectedFileEntries.Count]; + for( int i = 0; i < selectedFileEntries.Count; i++ ) + result[i] = validFileEntries[selectedFileEntries[i]].Path; + + OnOperationSuccessful( result ); + } + else + { + // When multiple files aren't selected via file browser UI, we must consider the rare case where user manually enters + // multiple filenames to filenameInputField in format "file1" "file2" and so on. So, we must parse filenameInputField + + // In the first iteration, verify that all filenames entered to the input field are valid + // ExtractFilenameFromInput doesn't use Substring, so this iteration is GC-free + int fileCount = 0; + int startIndex = 0, nextStartIndex; + while( startIndex < filenameInput.Length ) + { + int filenameLength = ExtractFilenameFromInput( filenameInput, ref startIndex, out nextStartIndex ); + if( filenameLength == 0 ) + continue; + + if( m_acceptNonExistingFilename ) + fileCount++; else { - if( !m_folderSelectMode ) - fileCount++; - else + int fileEntryIndex = FilenameInputToFileEntryIndex( filenameInput, startIndex, filenameLength ); + if( fileEntryIndex < 0 ) { - // Can't select a file in folder selection mode + // File doesn't exist filenameImage.color = wrongFilenameColor; return; } + + if( !validFileEntries[fileEntryIndex].IsDirectory ) + fileCount++; + else + { + if( m_folderSelectMode ) + fileCount++; + else + { + // Selected a directory in file selection mode, open that directory + CurrentPath = validFileEntries[fileEntryIndex].Path; + return; + } + } } + + startIndex = nextStartIndex; } - startIndex = nextStartIndex; - } + if( fileCount == 0 ) + { + filenameImage.color = wrongFilenameColor; + return; + } - if( indexOfDirectoryEntryToOpen >= 0 ) - CurrentPath = validFileEntries[indexOfDirectoryEntryToOpen].Path; - else if( fileCount == 0 ) - filenameImage.color = wrongFilenameColor; - else - { + // In the second iteration, extract filenames from the input field string[] result = new string[fileCount]; - // In the second iteration, extract filenames from the input field startIndex = 0; fileCount = 0; while( startIndex < filenameInput.Length ) @@ -1271,6 +1303,7 @@ public void Show( string initialPath ) filesScrollRect.verticalNormalizedPosition = 1; filenameInputField.text = string.Empty; + filenameInputField.interactable = true; filenameImage.color = Color.white; IsOpen = true; @@ -1376,6 +1409,14 @@ public void RefreshFiles( bool pathChanged ) pendingFileEntrySelection.Clear(); } + if( !filenameInputField.interactable && selectedFileEntries.Count <= 1 ) + { + filenameInputField.interactable = true; + + if( selectedFileEntries.Count == 0 ) + filenameInputField.text = string.Empty; + } + listView.UpdateList(); // Prevent the case where all the content stays offscreen after changing the search string @@ -1412,6 +1453,8 @@ public void DeselectAllFiles() MultiSelectionToggleSelectionMode = false; filenameInputField.text = string.Empty; + filenameInputField.interactable = true; + listView.UpdateList(); } @@ -1431,6 +1474,9 @@ private IEnumerator CreateNewFolderCoroutine() selectedFileEntries.Clear(); MultiSelectionToggleSelectionMode = false; + filenameInputField.text = string.Empty; + filenameInputField.interactable = true; + listView.UpdateList(); } @@ -1546,16 +1592,7 @@ public void DeleteSelectedFiles() selectedFileEntries.Sort(); - Sprite[] icons = new Sprite[selectedFileEntries.Count]; - string[] filenames = new string[selectedFileEntries.Count]; - for( int i = 0; i < selectedFileEntries.Count; i++ ) - { - FileSystemEntry fileInfo = validFileEntries[selectedFileEntries[i]]; - icons[i] = GetIconForFileEntry( fileInfo ); - filenames[i] = fileInfo.Name; - } - - deleteConfirmationPanel.Show( icons, filenames, () => + deleteConfirmationPanel.Show( this, validFileEntries, selectedFileEntries, () => { for( int i = selectedFileEntries.Count - 1; i >= 0; i-- ) { @@ -1702,7 +1739,7 @@ internal void OnWindowDimensionsChanged( Vector2 size ) } } - private Sprite GetIconForFileEntry( FileSystemEntry fileInfo ) + internal Sprite GetIconForFileEntry( FileSystemEntry fileInfo ) { Sprite icon; if( fileInfo.IsDirectory ) @@ -1741,7 +1778,7 @@ private void UpdateFilenameInputFieldWithSelection() // Refresh filenameInputField as follows: // 0 files selected: *blank* // 1 file selected: file.Name - // 2+ files selected: "file1.Name" "file2.Name" ... + // 2+ files selected: "file1.Name" "file2.Name" ... (up to FILENAME_INPUT_FIELD_MAX_FILE_COUNT filenames are displayed for performance reasons) int filenameContributingFileCount = 0; if( FolderSelectMode ) filenameContributingFileCount = selectedFileEntries.Count; @@ -1750,10 +1787,17 @@ private void UpdateFilenameInputFieldWithSelection() for( int i = 0; i < selectedFileEntries.Count; i++ ) { if( !validFileEntries[selectedFileEntries[i]].IsDirectory ) + { filenameContributingFileCount++; + + if( filenameContributingFileCount >= FILENAME_INPUT_FIELD_MAX_FILE_COUNT ) + break; + } } } + filenameInputField.interactable = selectedFileEntries.Count <= 1; + if( filenameContributingFileCount == 0 ) filenameInputField.text = string.Empty; else @@ -1766,7 +1810,7 @@ private void UpdateFilenameInputFieldWithSelection() multiSelectionFilenameBuilder.Length = 0; } - for( int i = 0; i < selectedFileEntries.Count; i++ ) + for( int i = 0, fileCount = 0; i < selectedFileEntries.Count; i++ ) { FileSystemEntry selectedFile = validFileEntries[selectedFileEntries[i]]; if( FolderSelectMode || !selectedFile.IsDirectory ) @@ -1777,7 +1821,15 @@ private void UpdateFilenameInputFieldWithSelection() break; } else + { multiSelectionFilenameBuilder.Append( "\"" ).Append( selectedFile.Name ).Append( "\" " ); + + if( ++fileCount >= FILENAME_INPUT_FIELD_MAX_FILE_COUNT ) + { + multiSelectionFilenameBuilder.Append( "..." ); + break; + } + } } } diff --git a/Plugins/SimpleFileBrowser/Scripts/FileBrowserDeleteConfirmationPanel.cs b/Plugins/SimpleFileBrowser/Scripts/FileBrowserDeleteConfirmationPanel.cs index a8f01de..df4f387 100644 --- a/Plugins/SimpleFileBrowser/Scripts/FileBrowserDeleteConfirmationPanel.cs +++ b/Plugins/SimpleFileBrowser/Scripts/FileBrowserDeleteConfirmationPanel.cs @@ -1,4 +1,5 @@ -using UnityEngine; +using System.Collections.Generic; +using UnityEngine; using UnityEngine.UI; namespace SimpleFileBrowser @@ -35,22 +36,22 @@ public class FileBrowserDeleteConfirmationPanel : MonoBehaviour private OnDeletionConfirmed onDeletionConfirmed; - internal void Show( Sprite[] icons, string[] filenames, OnDeletionConfirmed onDeletionConfirmed ) + internal void Show( FileBrowser fileBrowser, List items, List selectedItemIndices, OnDeletionConfirmed onDeletionConfirmed ) { this.onDeletionConfirmed = onDeletionConfirmed; for( int i = 0; i < deletedItems.Length; i++ ) - deletedItems[i].SetActive( i < icons.Length ); + deletedItems[i].SetActive( i < selectedItemIndices.Count ); - for( int i = 0; i < deletedItems.Length && i < icons.Length; i++ ) + for( int i = 0; i < deletedItems.Length && i < selectedItemIndices.Count; i++ ) { - deletedItemIcons[i].sprite = icons[i]; - deletedItemNames[i].text = filenames[i]; + deletedItemIcons[i].sprite = fileBrowser.GetIconForFileEntry( items[selectedItemIndices[i]] ); + deletedItemNames[i].text = items[selectedItemIndices[i]].Name; } - if( icons.Length > deletedItems.Length ) + if( selectedItemIndices.Count > deletedItems.Length ) { - deletedItemsRestLabel.text = string.Concat( "...and ", ( icons.Length - deletedItems.Length ).ToString(), " other" ); + deletedItemsRestLabel.text = string.Concat( "...and ", ( selectedItemIndices.Count - deletedItems.Length ).ToString(), " other" ); deletedItemsRest.SetActive( true ); } else diff --git a/package.json b/package.json index 67eded2..8f863ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.yasirkula.simplefilebrowser", "displayName": "Simple File Browser", - "version": "1.3.5", + "version": "1.3.6", "description": "This plugin helps you show save/load dialogs during gameplay with its uGUI based file browser." } \ No newline at end of file