From be0e6bead121400e1ea80ac7af320d31ec1adeeb Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Fri, 19 Jul 2019 15:24:51 -0700 Subject: [PATCH] Minor reshuffling This was an attempt to add a "loading..." dialog during the initial open of the project. This can have some lag because we create a sandbox (which is currently taking about 300ms) and do a full project refresh (which can take more than a second on a large 65816 project). The trick is that we need to do these things on a background thread while the main thread manages the UI. We can't manipulate the UI from the background thread. For the most part this works, as the project refresh stuff isn't tied to the UI, but we run into trouble when generating the line list. As currently implemented, the line generator interacts directly with DisplayList, which is acting as an ItemsSource for the main ListView. To make this work correctly we'd need to dissociate DisplayList from LineListGen, e.g. by having DisplayList record but defer changes until a "go" method is called on the main thread. The speed is only an issue for large programs, which aren't really supported yet -- the UI is awkward to use with large files -- so I'm not going to pursue this further for now. Also, an unrelated fix: there was an issue where the current ListView scroll position would be retained if you opened a project while one was already open. Harmless but weird. We now scroll to the top. --- SourceGenWPF/MainController.cs | 138 ++++++++++++++--------- SourceGenWPF/WpfGui/MainWindow.xaml.cs | 16 +++ SourceGenWPF/WpfGui/WorkProgress.xaml.cs | 2 +- 3 files changed, 102 insertions(+), 54 deletions(-) diff --git a/SourceGenWPF/MainController.cs b/SourceGenWPF/MainController.cs index 14b8380..80e909f 100644 --- a/SourceGenWPF/MainController.cs +++ b/SourceGenWPF/MainController.cs @@ -606,18 +606,41 @@ namespace SourceGenWPF { return true; } +#if false + private class FinishPrepProgress : WorkProgress.IWorker { + public string ExtMessages { get; private set; } + private MainController mMainCtrl; + + public FinishPrepProgress(MainController mainCtrl) { + mMainCtrl = mainCtrl; + } + public object DoWork(BackgroundWorker worker) { + string messages = mMainCtrl.mProject.LoadExternalFiles(); + mMainCtrl.DoRefreshProject(UndoableChange.ReanalysisScope.CodeAndData); + return messages; + } + + public void RunWorkerCompleted(object results) { + ExtMessages = (string)results; + } + } +#endif + private void FinishPrep() { + CodeLineList = new LineListGen(mProject, mMainWin.CodeDisplayList, + mOutputFormatter, mPseudoOpNames); + string messages = mProject.LoadExternalFiles(); if (messages.Length != 0) { - // ProjectLoadIssues isn't quite the right dialog, but it'll do. + // ProjectLoadIssues isn't quite the right dialog, but it'll do. This is + // purely informative; no decision needs to be made. ProjectLoadIssues dlg = new ProjectLoadIssues(mMainWin, messages, ProjectLoadIssues.Buttons.Continue); dlg.ShowDialog(); } - CodeLineList = new LineListGen(mProject, mMainWin.CodeDisplayList, - mOutputFormatter, mPseudoOpNames); - + // Ideally we'd call DoRefreshProject (and LoadExternalFiles) from a progress + // dialog, but we're not allowed to update the DisplayList from a different thread. RefreshProject(UndoableChange.ReanalysisScope.CodeAndData); // Populate the Symbols list. @@ -753,6 +776,20 @@ namespace SourceGenWPF { UpdateSelectionHighlight(); } + /// + /// Updates all of the specified ListView entries. This is called after minor changes, + /// such as editing a comment or renaming a label, that can be handled by regenerating + /// selected parts of the DisplayList. + /// + /// + private void RefreshCodeListViewEntries(RangeSet offsetSet) { + IEnumerator iter = offsetSet.RangeListIterator; + while (iter.MoveNext()) { + RangeSet.Range range = iter.Current; + CodeLineList.GenerateRange(range.Low, range.High); + } + } + /// /// Refreshes the project after something of substance has changed. Some /// re-analysis will be done, followed by a complete rebuild of the DisplayList. @@ -768,6 +805,47 @@ namespace SourceGenWPF { // reanalysis after many common operations, the program becomes unpleasant to // use if we miss this goal, and progress bars won't make it less so. + if (mProject.FileDataLength > 65536) { + try { + Mouse.OverrideCursor = Cursors.Wait; + DoRefreshProject(reanalysisRequired); + } finally { + Mouse.OverrideCursor = null; + } + } else { + DoRefreshProject(reanalysisRequired); + } + + if (mGenerationLog != null) { + //mReanalysisTimer.StartTask("Save _log"); + //mGenerationLog.WriteToFile(@"C:\Src\WorkBench\SourceGen\TestData\_log.txt"); + //mReanalysisTimer.EndTask("Save _log"); + + if (mShowAnalyzerOutputDialog != null) { + mShowAnalyzerOutputDialog.DisplayText = mGenerationLog.WriteToString(); + } + } + + if (FormatDescriptor.DebugCreateCount != 0) { + Debug.WriteLine("FormatDescriptor total=" + FormatDescriptor.DebugCreateCount + + " prefab=" + FormatDescriptor.DebugPrefabCount + " (" + + (FormatDescriptor.DebugPrefabCount * 100) / FormatDescriptor.DebugCreateCount + + "%)"); + } + } + + /// + /// Refreshes the project after something of substance has changed. + /// + /// + /// Ideally from this point on we can run on a background thread. The tricky part + /// is the close relationship between LineListGen and DisplayList -- we can't update + /// DisplayList from a background thread. Until that's fixed, putting up a "working..." + /// dialog or other UI will be awkward. + /// + /// Indicates whether reanalysis is required, and + /// what level. + private void DoRefreshProject(UndoableChange.ReanalysisScope reanalysisRequired) { // Changing the CPU type or whether undocumented instructions are supported // invalidates the Formatter's mnemonic cache. We can change these values // through undo/redo, so we need to check it here. @@ -780,40 +858,6 @@ namespace SourceGenWPF { mOutputFormatterCpuDef = mProject.CpuDef; } - if (CodeLineList.Count > 40000) { - try { - Mouse.OverrideCursor = Cursors.Wait; - DoRefreshProject(reanalysisRequired); - } finally { - Mouse.OverrideCursor = null; - } - } else { - DoRefreshProject(reanalysisRequired); - } - - if (FormatDescriptor.DebugCreateCount != 0) { - Debug.WriteLine("FormatDescriptor total=" + FormatDescriptor.DebugCreateCount + - " prefab=" + FormatDescriptor.DebugPrefabCount + " (" + - (FormatDescriptor.DebugPrefabCount * 100) / FormatDescriptor.DebugCreateCount + - "%)"); - } - } - - /// - /// Updates all of the specified ListView entries. This is called after minor changes, - /// such as editing a comment or renaming a label, that can be handled by regenerating - /// selected parts of the DisplayList. - /// - /// - private void RefreshCodeListViewEntries(RangeSet offsetSet) { - IEnumerator iter = offsetSet.RangeListIterator; - while (iter.MoveNext()) { - RangeSet.Range range = iter.Current; - CodeLineList.GenerateRange(range.Low, range.High); - } - } - - private void DoRefreshProject(UndoableChange.ReanalysisScope reanalysisRequired) { if (reanalysisRequired != UndoableChange.ReanalysisScope.DisplayOnly) { mGenerationLog = new CommonUtil.DebugLog(); mGenerationLog.SetMinPriority(CommonUtil.DebugLog.Priority.Debug); @@ -824,16 +868,6 @@ namespace SourceGenWPF { mReanalysisTimer.EndTask("Call DisasmProject.Analyze()"); } - if (mGenerationLog != null) { - //mReanalysisTimer.StartTask("Save _log"); - //mGenerationLog.WriteToFile(@"C:\Src\WorkBench\SourceGen\TestData\_log.txt"); - //mReanalysisTimer.EndTask("Save _log"); - - if (mShowAnalyzerOutputDialog != null) { - mShowAnalyzerOutputDialog.DisplayText = mGenerationLog.WriteToString(); - } - } - mReanalysisTimer.StartTask("Generate DisplayList"); CodeLineList.GenerateAll(); mReanalysisTimer.EndTask("Generate DisplayList"); @@ -903,13 +937,14 @@ namespace SourceGenWPF { } /// - /// Handles opening an existing project, given a pathname to the project file. + /// Handles opening an existing project, given a full pathname to the project file. /// private void DoOpenFile(string projPathName) { Debug.WriteLine("DoOpenFile: " + projPathName); Debug.Assert(mProject == null); if (!File.Exists(projPathName)) { + // Should only happen for projects in "recents". string msg = string.Format(Res.Strings.ERR_FILE_NOT_FOUND_FMT, projPathName); MessageBox.Show(msg, Res.Strings.ERR_FILE_GENERIC_CAPTION, MessageBoxButton.OK, MessageBoxImage.Error); @@ -1188,11 +1223,8 @@ namespace SourceGenWPF { mProjectPathName = null; mTargetHighlightIndex = -1; - // Clear this to release the memory. - mMainWin.CodeDisplayList.Clear(); - - mMainWin.InfoPanelContents = string.Empty; mMainWin.ShowCodeListView = false; + mMainWin.ProjectClosing(); mGenerationLog = null; diff --git a/SourceGenWPF/WpfGui/MainWindow.xaml.cs b/SourceGenWPF/WpfGui/MainWindow.xaml.cs index 0b987b1..18cf433 100644 --- a/SourceGenWPF/WpfGui/MainWindow.xaml.cs +++ b/SourceGenWPF/WpfGui/MainWindow.xaml.cs @@ -388,6 +388,22 @@ namespace SourceGenWPF.WpfGui { } } + /// + /// Cleans up state when MainController decides to close the project. + /// + public void ProjectClosing() { + // Clear this to release the memory. + CodeDisplayList.Clear(); + + InfoPanelContents = string.Empty; + + // If you open a new project while one is already open, the ListView apparently + // doesn't reset certain state, possibly because it's never asked to draw after + // the list is cleared. This results in the new project being open at the same + // line as the previous project. This is a little weird, so we reset it here. + CodeListView_SetTopIndex(0); + } + /// /// Catch mouse-down events so we can treat the fourth mouse button as "back". /// diff --git a/SourceGenWPF/WpfGui/WorkProgress.xaml.cs b/SourceGenWPF/WpfGui/WorkProgress.xaml.cs index f744405..bbe91fa 100644 --- a/SourceGenWPF/WpfGui/WorkProgress.xaml.cs +++ b/SourceGenWPF/WpfGui/WorkProgress.xaml.cs @@ -126,7 +126,7 @@ namespace SourceGenWPF.WpfGui { DialogResult = false; } } else if (e.Error != null) { - // Unexpected -- shell command execution shouldn't throw exceptions. + // Unexpected; success/failure should be passed through e.Result. MessageBox.Show(e.Error.ToString(), Res.Strings.OPERATION_FAILED, MessageBoxButton.OK, MessageBoxImage.Error); DialogResult = false;