From 7d157957e221440d845a2077a9af4f72766fbb23 Mon Sep 17 00:00:00 2001 From: Hajin Jang Date: Sun, 2 Sep 2018 00:04:10 +0900 Subject: [PATCH] Convert BackgroundWorker into Task --- .gitignore | 1 + PEBakery.Tests/TestSetup.cs | 2 +- PEBakery/Core/Commands/CommandInterface.cs | 12 +- PEBakery/Core/Commands/CommandSystem.cs | 14 +- PEBakery/Core/Engine.cs | 6 +- PEBakery/Core/Logger.cs | 36 +- PEBakery/Core/Project.cs | 44 +- PEBakery/Core/ScriptCache.cs | 50 +- PEBakery/WPF/MainWindow.xaml.cs | 766 ++++++++++----------- PEBakery/WPF/UIRenderer.cs | 10 +- PEBakery/WPF/UtilityWindow.xaml.cs | 2 +- 11 files changed, 473 insertions(+), 470 deletions(-) diff --git a/.gitignore b/.gitignore index 3bb12235..d7eea187 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ ## files generated by popular Visual Studio add-ons. # Joveler-specific Resoures +BinaryRelease/ res/ PEBakery/Core/Parser/.antlr PEBakery.Tests/Samples/WorkBench/TestSuite/Dest/ diff --git a/PEBakery.Tests/TestSetup.cs b/PEBakery.Tests/TestSetup.cs index 7bc114e4..a2a96b1c 100644 --- a/PEBakery.Tests/TestSetup.cs +++ b/PEBakery.Tests/TestSetup.cs @@ -20,7 +20,7 @@ public static void PrepareTests(TestContext ctx) projects.Load(null); // Should be only one project named TestSuite - EngineTests.Project = projects.Projects[0]; + EngineTests.Project = projects.ProjectList[0]; // Init NativeAssembly NativeGlobalInit(); diff --git a/PEBakery/Core/Commands/CommandInterface.cs b/PEBakery/Core/Commands/CommandInterface.cs index 57e39e21..529573ce 100644 --- a/PEBakery/Core/Commands/CommandInterface.cs +++ b/PEBakery/Core/Commands/CommandInterface.cs @@ -125,10 +125,16 @@ public static List VisibleOp(EngineState s, CodeCommand cmd) CodeInfo_Visible info = subCmd.Info.Cast(); string visibilityStr = StringEscaper.Preprocess(s, info.Visibility); - bool visibility = false; - if (visibilityStr.Equals("True", StringComparison.OrdinalIgnoreCase)) + Debug.Assert(visibilityStr != null, $"{nameof(visibilityStr)} != null"); + + bool visibility; + if (visibilityStr.Equals("1", StringComparison.Ordinal) || + visibilityStr.Equals("True", StringComparison.OrdinalIgnoreCase)) visibility = true; - else if (!visibilityStr.Equals("False", StringComparison.OrdinalIgnoreCase)) + else if (visibilityStr.Equals("0", StringComparison.Ordinal) || + visibilityStr.Equals("False", StringComparison.OrdinalIgnoreCase)) + visibility = false; + else return LogInfo.LogErrorMessage(logs, $"Invalid boolean value [{visibilityStr}]"); prepArgs.Add((info.UIControlKey, visibility, subCmd)); diff --git a/PEBakery/Core/Commands/CommandSystem.cs b/PEBakery/Core/Commands/CommandSystem.cs index ada44cea..d1294942 100644 --- a/PEBakery/Core/Commands/CommandSystem.cs +++ b/PEBakery/Core/Commands/CommandSystem.cs @@ -35,6 +35,7 @@ using System.Security.Principal; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Threading.Tasks; using PEBakery.WPF; using PEBakery.Helper; @@ -214,13 +215,11 @@ public static List SystemCmd(EngineState s, CodeCommand cmd) { Debug.Assert(info.SubInfo.GetType() == typeof(SystemInfo), "Invalid CodeInfo"); - AutoResetEvent resetEvent = null; Application.Current?.Dispatcher.Invoke(() => { MainWindow w = Application.Current.MainWindow as MainWindow; - resetEvent = w?.StartRefreshScriptWorker(); + w?.StartRefreshScript().Wait(); }); - resetEvent?.WaitOne(); logs.Add(new LogInfo(LogState.Success, $"Rerendered script [{cmd.Addr.Script.Title}]")); } @@ -231,13 +230,14 @@ public static List SystemCmd(EngineState s, CodeCommand cmd) Debug.Assert(info.SubInfo.GetType() == typeof(SystemInfo), "Invalid CodeInfo"); // Refresh Project - AutoResetEvent resetEvent = null; + Task scriptLoadTask = Task.CompletedTask; Application.Current?.Dispatcher.Invoke(() => { - MainWindow w = Application.Current.MainWindow as MainWindow; - resetEvent = w?.StartLoadWorker(true); + if (!(Application.Current.MainWindow is MainWindow w)) + return; + scriptLoadTask = w.StartLoadingProjects(true); }); - resetEvent?.WaitOne(); + scriptLoadTask.Wait(); logs.Add(new LogInfo(LogState.Success, $"Reload project [{cmd.Addr.Script.Project.ProjectName}]")); } diff --git a/PEBakery/Core/Engine.cs b/PEBakery/Core/Engine.cs index 1a9406b6..949953fa 100644 --- a/PEBakery/Core/Engine.cs +++ b/PEBakery/Core/Engine.cs @@ -1034,14 +1034,14 @@ public enum LogMode /// - Medium performance impact /// - Write to database when script is finised /// - PartDelay, + PartDefer, /// /// For interface button /// - Minimize performance impact /// - Disable trivial LogWindow event /// - Write to database after bulid is finished /// - FullDelay, + FullDefer, } #endregion @@ -1177,7 +1177,7 @@ public void SetOptions(SettingViewModel m) { CustomUserAgent = m.General_UseCustomUserAgent ? m.General_CustomUserAgent : null; - LogMode = m.Log_DeferredLogging ? LogMode.PartDelay : LogMode.NoDelay; + LogMode = m.Log_DeferredLogging ? LogMode.PartDefer : LogMode.NoDelay; CompatDirCopyBug = m.Compat_AsteriskBugDirCopy; CompatFileRenameCanMoveDir = m.Compat_FileRenameCanMoveDir; diff --git a/PEBakery/Core/Logger.cs b/PEBakery/Core/Logger.cs index 226deb20..a4d3069e 100644 --- a/PEBakery/Core/Logger.cs +++ b/PEBakery/Core/Logger.cs @@ -334,7 +334,7 @@ public DeferredLogging(bool fullDelayed) /// Real BuildId after written to database public int FlushFullDeferred(EngineState s) { - if (s.LogMode != LogMode.FullDelay) + if (s.LogMode != LogMode.FullDefer) return s.BuildId; Debug.Assert(BuildInfo != null, "Internal Logic Error at DelayedLogging"); @@ -472,11 +472,11 @@ public int Flush(EngineState s) { switch (s.LogMode) { - case LogMode.PartDelay: + case LogMode.PartDefer: Db.InsertAll(_deferred.BuildLogPool); _deferred.BuildLogPool.Clear(); break; - case LogMode.FullDelay: + case LogMode.FullDefer: return _deferred.FlushFullDeferred(s); } return s.BuildId; @@ -498,10 +498,10 @@ public int BuildInit(EngineState s, string name) switch (s.LogMode) { - case LogMode.PartDelay: + case LogMode.PartDefer: _deferred = new DeferredLogging(false); break; - case LogMode.FullDelay: + case LogMode.FullDefer: _deferred = new DeferredLogging(true); dbBuild.Id = -1; @@ -509,7 +509,7 @@ public int BuildInit(EngineState s, string name) break; } - if (s.LogMode != LogMode.FullDelay) + if (s.LogMode != LogMode.FullDefer) { Db.Insert(dbBuild); @@ -537,12 +537,12 @@ public int BuildInit(EngineState s, string name) varLogs.Add(dbVar); // Fire Event - if (s.LogMode != LogMode.FullDelay) + if (s.LogMode != LogMode.FullDefer) VariableUpdated?.Invoke(this, new VariableUpdateEventArgs(dbVar)); } } - if (s.LogMode == LogMode.FullDelay) + if (s.LogMode == LogMode.FullDefer) _deferred.VariablePool.AddRange(varLogs); else Db.InsertAll(varLogs); @@ -566,7 +566,7 @@ public void BuildFinish(EngineState s) switch (s.LogMode) { - case LogMode.PartDelay: + case LogMode.PartDefer: Flush(s); Db.Update(dbBuild); break; @@ -601,7 +601,7 @@ public int BuildScriptInit(EngineState s, Script sc, int order, bool prepareBuil dbScript.Version = "0"; } - if (s.LogMode == LogMode.FullDelay) + if (s.LogMode == LogMode.FullDefer) { _deferred.CurrentScriptId -= 1; dbScript.Id = _deferred.CurrentScriptId; @@ -626,7 +626,7 @@ public void BuildScriptFinish(EngineState s, Dictionary localVar if (s.DisableLogger) return; - if (s.LogMode == LogMode.PartDelay) + if (s.LogMode == LogMode.PartDefer) { Db.InsertAll(_deferred.BuildLogPool); _deferred.BuildLogPool.Clear(); @@ -661,21 +661,21 @@ public void BuildScriptFinish(EngineState s, Dictionary localVar varLogs.Add(dbVar); // Fire Event - if (s.LogMode != LogMode.FullDelay) + if (s.LogMode != LogMode.FullDefer) VariableUpdated?.Invoke(this, new VariableUpdateEventArgs(dbVar)); } - if (s.LogMode == LogMode.FullDelay) + if (s.LogMode == LogMode.FullDefer) _deferred.VariablePool.AddRange(varLogs); else Db.InsertAll(varLogs); } - if (s.LogMode != LogMode.FullDelay) + if (s.LogMode != LogMode.FullDefer) Db.Update(dbScript); // Fire Event - if (s.LogMode == LogMode.PartDelay) + if (s.LogMode == LogMode.PartDefer) ScriptUpdated?.Invoke(this, new ScriptUpdateEventArgs(dbScript)); } @@ -699,7 +699,7 @@ public int BuildRefScriptWrite(EngineState s, Script sc) ElapsedMilliSec = 0, }; - if (s.LogMode == LogMode.FullDelay) + if (s.LogMode == LogMode.FullDefer) { _deferred.CurrentScriptId -= 1; dbScript.Id = _deferred.CurrentScriptId; @@ -729,8 +729,8 @@ private void InternalBuildWrite(EngineState s, DB_BuildLog dbCode) { switch (s.LogMode) { - case LogMode.FullDelay: - case LogMode.PartDelay: + case LogMode.FullDefer: + case LogMode.PartDefer: _deferred.BuildLogPool.Add(dbCode); break; case LogMode.NoDelay: diff --git a/PEBakery/Core/Project.cs b/PEBakery/Core/Project.cs index b72f6aec..07816f68 100644 --- a/PEBakery/Core/Project.cs +++ b/PEBakery/Core/Project.cs @@ -65,9 +65,9 @@ public class ProjectCollection : IReadOnlyCollection #region Properties public string ProjectRoot { get; } - public List Projects => _projectDict.Values.OrderBy(x => x.ProjectName).ToList(); + public List ProjectList => _projectDict.Values.OrderBy(x => x.ProjectName).ToList(); public List ProjectNames => _projectDict.Keys.OrderBy(x => x).ToList(); - public Project this[int i] => Projects[i]; + public Project this[int i] => ProjectList[i]; public int Count => _projectDict.Count; #endregion @@ -271,7 +271,17 @@ private List<(string RealPath, string TreePath, bool IsDir)> GetDirLinks(string #endregion #region Load, LoadLinks - public List Load(BackgroundWorker worker) + public enum LoadReport + { + None, + LoadingCache, + Stage1, + Stage1Cached, + Stage2, + Stage2Cached, + } + + public List Load(IProgress<(LoadReport Type, string Path)> progress) { List logs = new List(32); try @@ -281,7 +291,7 @@ public List Load(BackgroundWorker worker) Project project = new Project(_baseDir, key); // Load scripts - List projLogs = project.Load(_scriptPathDict[key], _dirLinkPathDict[key], _scriptCache, worker); + List projLogs = project.Load(_scriptPathDict[key], _dirLinkPathDict[key], _scriptCache, progress); logs.AddRange(projLogs); // Add Project.Scripts to ProjectCollections.Scripts @@ -291,7 +301,7 @@ public List Load(BackgroundWorker worker) } // Populate *.link scripts - List linkLogs = LoadLinks(worker); + List linkLogs = LoadLinks(progress); logs.AddRange(linkLogs); // PostLoad scripts @@ -310,7 +320,7 @@ public List Load(BackgroundWorker worker) return logs; } - private List LoadLinks(BackgroundWorker worker) + private List LoadLinks(IProgress<(LoadReport Type, string Path)> progress) { List logs = new List(32); List removeIdxs = new List(); @@ -319,7 +329,7 @@ private List LoadLinks(BackgroundWorker worker) DB_ScriptCache[] cacheDb = null; if (_scriptCache != null) { - worker?.ReportProgress(-1); + progress?.Report((LoadReport.LoadingCache, null)); cacheDb = _scriptCache.Table().ToArray(); } @@ -330,7 +340,7 @@ private List LoadLinks(BackgroundWorker worker) { Script link = null; bool valid = false; - int cached = 2; + LoadReport cached = LoadReport.Stage2; try { do @@ -366,7 +376,7 @@ private List LoadLinks(BackgroundWorker worker) { link.Project = sc.Project; link.IsDirLink = false; - cached = 3; + cached = LoadReport.Stage2Cached; } } catch { link = null; } @@ -405,13 +415,13 @@ private List LoadLinks(BackgroundWorker worker) { sc.LinkLoaded = true; sc.Link = link; - worker?.ReportProgress(cached, Path.GetDirectoryName(sc.TreePath)); + progress?.Report((cached, Path.GetDirectoryName(sc.TreePath))); } else // Error { int idx = _allProjectScripts.IndexOf(sc); removeIdxs.Add(idx); - worker?.ReportProgress(cached); + progress?.Report((cached, null)); } }); @@ -488,7 +498,7 @@ public ScriptParseInfo(string realPath, string treePath, bool isDir, bool isDirL List<(string Path, bool IsDir)> allScriptPathList, List<(string RealPath, string TreePath, bool IsDir)> allDirLinkPathList, ScriptCache scriptCache, - BackgroundWorker worker) + IProgress<(ProjectCollection.LoadReport Type, string Path)> progress) { List logs = new List(32); @@ -499,7 +509,7 @@ public ScriptParseInfo(string realPath, string treePath, bool isDir, bool isDirL DB_ScriptCache[] cacheDb = null; if (scriptCache != null) { - worker?.ReportProgress(-1); + progress?.Report((ProjectCollection.LoadReport.LoadingCache, null)); cacheDb = scriptCache.Table().ToArray(); } @@ -515,7 +525,7 @@ public ScriptParseInfo(string realPath, string treePath, bool isDir, bool isDirL Debug.Assert(spi.RealPath != null, "Internal Logic Error at Project.Load"); Debug.Assert(spi.TreePath != null, "Internal Logic Error at Project.Load"); - int cached = 0; + ProjectCollection.LoadReport cached = ProjectCollection.LoadReport.Stage1; Script sc = null; try { @@ -545,7 +555,7 @@ public ScriptParseInfo(string realPath, string treePath, bool isDir, bool isDirL { sc.Project = this; sc.IsDirLink = spi.IsDirLink; - cached = 1; + cached = ProjectCollection.LoadReport.Stage1Cached; } } catch { sc = null; } // Cache Error @@ -579,12 +589,12 @@ public ScriptParseInfo(string realPath, string treePath, bool isDir, bool isDirL listLock.ExitWriteLock(); } - worker?.ReportProgress(cached, Path.GetDirectoryName(sc.TreePath)); + progress?.Report((cached, Path.GetDirectoryName(sc.TreePath))); } catch (Exception e) { logs.Add(new LogInfo(LogState.Error, Logger.LogExceptionMessage(e))); - worker?.ReportProgress(cached); + progress?.Report((cached, null)); } }); diff --git a/PEBakery/Core/ScriptCache.cs b/PEBakery/Core/ScriptCache.cs index 41962abd..d5cbfdc7 100644 --- a/PEBakery/Core/ScriptCache.cs +++ b/PEBakery/Core/ScriptCache.cs @@ -45,7 +45,6 @@ namespace PEBakery.Core public class ScriptCache : SQLiteConnection { public static int DbLock = 0; - private ReaderWriterLockSlim _listLock; public ScriptCache(string path) : base(path, SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.FullMutex) { @@ -67,7 +66,7 @@ public async void WaitClose() } #region Cache Script or Scripts - public void CacheScripts(ProjectCollection projects, string baseDir, BackgroundWorker worker) + public (int cached, int updated) CacheScripts(ProjectCollection projects, string baseDir) { try { @@ -79,28 +78,33 @@ public void CacheScripts(ProjectCollection projects, string baseDir, BackgroundW }; InsertOrReplaceAll(infos); - foreach (Project project in projects.Projects) + int cachedCount = 0; + int updatedCount = 0; + foreach (Project project in projects.ProjectList) { // Remove duplicate // Exclude directory links because treePath is inconsistent - var scUniqueList = project.AllScripts + // TODO: Include directory link + Script[] uniqueScripts = project.AllScripts .Where(x => x.Type != ScriptType.Directory && !x.IsDirLink) .GroupBy(x => x.DirectRealPath) - .Select(x => x.First()); - - _listLock = new ReaderWriterLockSlim(); + .Select(x => x.First()) + .ToArray(); DB_ScriptCache[] memDb = Table().ToArray(); List updateDb = new List(); - - Parallel.ForEach(scUniqueList, sc => + Parallel.ForEach(uniqueScripts, sc => { bool updated = CacheScript(sc, memDb, updateDb); - worker.ReportProgress(updated ? 1 : 0); // 1 - updated, 0 - not updated + if (updated) + Interlocked.Increment(ref updatedCount); }); + cachedCount += uniqueScripts.Length; InsertOrReplaceAll(updateDb); } + + return (cachedCount, updatedCount); } catch (SQLiteException e) { // Update failure @@ -108,13 +112,17 @@ public void CacheScripts(ProjectCollection projects, string baseDir, BackgroundW MessageBox.Show(msg, "SQLite Error!", MessageBoxButton.OK, MessageBoxImage.Error); Application.Current.Shutdown(1); } + + return (0, 0); } /// Return true if cache is updated private bool CacheScript(Script sc, DB_ScriptCache[] memDb, List updateDb) { - if (memDb == null) throw new ArgumentNullException(nameof(memDb)); - if (updateDb == null) throw new ArgumentNullException(nameof(updateDb)); + if (memDb == null) + throw new ArgumentNullException(nameof(memDb)); + if (updateDb == null) + throw new ArgumentNullException(nameof(updateDb)); Debug.Assert(sc.Type != ScriptType.Directory); // Does cache exist? @@ -143,19 +151,14 @@ private bool CacheScript(Script sc, DB_ScriptCache[] memDb, List scCache.Serialized = ms.ToArray(); } - _listLock.EnterWriteLock(); - try + lock (updateDb) { updateDb.Add(scCache); updated = true; } - finally - { - _listLock.ExitWriteLock(); - } } - else if (scCache.Path.Equals(sPath, StringComparison.Ordinal) && - (!DateTime.Equals(scCache.LastWriteTimeUtc, f.LastWriteTime) || scCache.FileSize != f.Length)) + else if (scCache.Path.Equals(sPath, StringComparison.Ordinal) && + (!DateTime.Equals(scCache.LastWriteTimeUtc, f.LastWriteTime) || scCache.FileSize != f.Length)) { // Cache is outdated BinaryFormatter formatter = new BinaryFormatter(); using (MemoryStream ms = new MemoryStream()) @@ -166,16 +169,11 @@ private bool CacheScript(Script sc, DB_ScriptCache[] memDb, List scCache.FileSize = f.Length; } - _listLock.EnterWriteLock(); - try + lock (updateDb) { updateDb.Add(scCache); updated = true; } - finally - { - _listLock.ExitWriteLock(); - } } if (sc.Type == ScriptType.Link && sc.LinkLoaded) diff --git a/PEBakery/WPF/MainWindow.xaml.cs b/PEBakery/WPF/MainWindow.xaml.cs index 699ad3b8..a5f1cd79 100644 --- a/PEBakery/WPF/MainWindow.xaml.cs +++ b/PEBakery/WPF/MainWindow.xaml.cs @@ -64,10 +64,10 @@ public partial class MainWindow : Window public ProjectCollection Projects { get; private set; } public string BaseDir { get; } - private BackgroundWorker _loadWorker = new BackgroundWorker(); - private BackgroundWorker _refreshWorker = new BackgroundWorker(); - private BackgroundWorker _cacheWorker = new BackgroundWorker(); - private BackgroundWorker _syntaxCheckWorker = new BackgroundWorker(); + // private BackgroundWorker _loadWorker = new BackgroundWorker(); + private int _projectsLoading = 0; + private int _scriptRefreshing = 0; + private int _syntaxChecking = 0; public TreeViewModel CurMainTree { get; private set; } public TreeViewModel CurBuildTree { get; set; } @@ -182,401 +182,368 @@ public MainWindow() Logger.SystemWrite(new LogInfo(LogState.Info, "ScriptCache disabled")); } - StartLoadWorker(); + // Load Projects + StartLoadingProjects(); } #endregion - #region Background Workers - public AutoResetEvent StartLoadWorker(bool quiet = false) + #region Background Workers and Tasks + public Task StartLoadingProjects(bool quiet = false) { - AutoResetEvent resetEvent = new AutoResetEvent(false); - Stopwatch watch = Stopwatch.StartNew(); - - // Load CommentProcessing Icon - ScriptLogo.Content = ImageHelper.GetMaterialIcon(PackIconMaterialKind.CommentProcessing, 10); - - // Prepare PEBakery Loading Information - if (!quiet) - { - Model.ScriptTitleText = "PEBakery loading..."; - Model.ScriptDescriptionText = string.Empty; - } - Logger.SystemWrite(new LogInfo(LogState.Info, $@"Loading from [{BaseDir}]")); - MainCanvas.Children.Clear(); - (MainTreeView.DataContext as TreeViewModel)?.Children.Clear(); - - int stage2LinksCount = 0; - int loadedScriptCount = 0; - int stage1CachedCount = 0; - int stage2LoadedCount = 0; - int stage2CachedCount = 0; - - Model.BottomProgressBarMinimum = 0; - Model.BottomProgressBarMaximum = 100; - Model.BottomProgressBarValue = 0; - if (!quiet) - Model.WorkInProgress = true; - Model.SwitchStatusProgressBar = false; // Show Progress Bar + if (_projectsLoading != 0) + return Task.CompletedTask; - _loadWorker = new BackgroundWorker(); - _loadWorker.DoWork += (object sender, DoWorkEventArgs e) => + return Task.Run(() => { - string baseDir = (string)e.Argument; - BackgroundWorker worker = sender as BackgroundWorker; - - // Init ProjectCollection - if (Setting.Script_EnableCache && _scriptCache != null) // Use ScriptCache - { - if (_scriptCache.IsGlobalCacheValid(baseDir)) - Projects = new ProjectCollection(baseDir, _scriptCache); - else // Cache is invalid - Projects = new ProjectCollection(baseDir, null); - } - else // Do not use ScriptCache + Interlocked.Increment(ref _projectsLoading); + if (!quiet) + Model.WorkInProgress = true; + Model.SwitchStatusProgressBar = false; // Show Progress Bar + try { - Projects = new ProjectCollection(baseDir, null); - } - - _allScriptCount = Projects.PrepareLoad(out stage2LinksCount); - Dispatcher.Invoke(() => { Model.BottomProgressBarMaximum = _allScriptCount + stage2LinksCount; }); + Stopwatch watch = Stopwatch.StartNew(); - // Load scripts in parallel - List errorLogs = Projects.Load(worker); - Logger.SystemWrite(errorLogs); - Setting.UpdateProjectList(); + // Prepare PEBakery Loading Information + if (!quiet) + { + Model.ScriptTitleText = "PEBakery loading..."; + Model.ScriptDescriptionText = string.Empty; + } + Logger.SystemWrite(new LogInfo(LogState.Info, $"Loading from [{BaseDir}]")); - if (0 < Projects.ProjectNames.Count) - { // Load Success - // Populate TreeView - Dispatcher.Invoke(() => + Application.Current.Dispatcher.Invoke(() => { - foreach (Project project in Projects.Projects) - ScriptListToTreeViewModel(project, project.VisibleScripts, true, Model.MainTree); - - int pIdx = Setting.Project_DefaultIndex; - CurMainTree = Model.MainTree.Children[pIdx]; - CurMainTree.IsExpanded = true; - if (Projects[pIdx] != null) - DrawScript(Projects[pIdx].MainScript); + // Load CommentProcessing Icon + ScriptLogo.Content = ImageHelper.GetMaterialIcon(PackIconMaterialKind.CommentProcessing, 10); + MainCanvas.Children.Clear(); + (MainTreeView.DataContext as TreeViewModel)?.Children.Clear(); }); - e.Result = true; - } - else - { - e.Result = false; - } - }; - _loadWorker.WorkerReportsProgress = true; - _loadWorker.ProgressChanged += (object sender, ProgressChangedEventArgs e) => - { - Interlocked.Increment(ref loadedScriptCount); - Model.BottomProgressBarValue = loadedScriptCount; - string msg = string.Empty; - switch (e.ProgressPercentage) - { - case -1: // Loading Cache - msg = "Loading script cache"; - break; - case 0: // Stage 1 - msg = e.UserState == null ? "Error" : $"{e.UserState}"; - break; - case 1: // Stage 1, Cached - Interlocked.Increment(ref stage1CachedCount); - msg = e.UserState == null ? "Cached - Error" : $"Cached - {e.UserState}"; - break; - case 2: // Stage 2 - Interlocked.Increment(ref stage2LoadedCount); - msg = e.UserState == null ? "Error" : $"{e.UserState}"; - break; - case 3: // Stage 2, Cached - Interlocked.Increment(ref stage2LoadedCount); - Interlocked.Increment(ref stage2CachedCount); - msg = e.UserState == null ? "Cached - Error" : $"Cached - {e.UserState}"; - break; - } - - if (0 <= e.ProgressPercentage) - { - int stage = e.ProgressPercentage / 2 + 1; - if (stage == 1) - msg = $"Stage {stage} ({loadedScriptCount} / {_allScriptCount}) \r\n{msg}"; - else - msg = $"Stage {stage} ({stage2LoadedCount} / {stage2LinksCount}) \r\n{msg}"; - } + Model.BottomProgressBarMinimum = 0; + Model.BottomProgressBarMaximum = 100; + Model.BottomProgressBarValue = 0; - Model.ScriptDescriptionText = msg; - }; - _loadWorker.RunWorkerCompleted += (object sender, RunWorkerCompletedEventArgs e) => - { - if ((bool)e.Result) - { // Load Success - StringBuilder b = new StringBuilder(); - b.Append("Projects ["); - List projList = Projects.Projects; - for (int i = 0; i < projList.Count; i++) + // Init ProjectCollection + if (Setting.Script_EnableCache && _scriptCache != null) // Use ScriptCache { - b.Append(projList[i].ProjectName); - if (i + 1 < projList.Count) - b.Append(", "); + if (_scriptCache.IsGlobalCacheValid(BaseDir)) + Projects = new ProjectCollection(BaseDir, _scriptCache); + else // Cache is invalid + Projects = new ProjectCollection(BaseDir, null); } - b.Append("] loaded"); - Logger.SystemWrite(new LogInfo(LogState.Info, b.ToString())); - - watch.Stop(); - double t = watch.Elapsed.TotalMilliseconds / 1000.0; - string msg; - if (Setting.Script_EnableCache) + else // Do not use ScriptCache { - double cachePercent = (double)(stage1CachedCount + stage2CachedCount) * 100 / (_allScriptCount + stage2LinksCount); - msg = $"{_allScriptCount} scripts loaded ({t:0.#}s) - {cachePercent:0.#}% cached"; - Model.StatusBarText = msg; + Projects = new ProjectCollection(BaseDir, null); } - else + + // Prepare by getting script paths + _allScriptCount = Projects.PrepareLoad(out int stage2LinkCount); + Dispatcher.Invoke(() => { Model.BottomProgressBarMaximum = _allScriptCount + stage2LinkCount; }); + + // Progress handler + int loadedScriptCount = 0; + int stage1CachedCount = 0; + int stage2LoadedCount = 0; + int stage2CachedCount = 0; + var progress = new Progress<(ProjectCollection.LoadReport Type, string Path)>(x => { - msg = $"{_allScriptCount} scripts loaded ({t:0.#}s)"; - Model.StatusBarText = msg; - } - if (!quiet) - Model.WorkInProgress = false; - Model.SwitchStatusProgressBar = true; // Show Status Bar + Interlocked.Increment(ref loadedScriptCount); + Model.BottomProgressBarValue = loadedScriptCount; - Logger.SystemWrite(new LogInfo(LogState.Info, msg)); - Logger.SystemWrite(Logger.LogSeperator); + int stage = 0; + string msg = string.Empty; + switch (x.Type) + { + case ProjectCollection.LoadReport.LoadingCache: + msg = "Loading script cache"; + break; + case ProjectCollection.LoadReport.Stage1: + stage = 1; + msg = x.Path == null ? "Error" : $"{x.Path}"; + break; + case ProjectCollection.LoadReport.Stage1Cached: + stage = 1; + Interlocked.Increment(ref stage1CachedCount); + msg = x.Path == null ? "Cached - Error" : $"Cached - {x.Path}"; + break; + case ProjectCollection.LoadReport.Stage2: + stage = 2; + Interlocked.Increment(ref stage2LoadedCount); + msg = x.Path == null ? "Error" : $"{x.Path}"; + break; + case ProjectCollection.LoadReport.Stage2Cached: + stage = 2; + Interlocked.Increment(ref stage2LoadedCount); + Interlocked.Increment(ref stage2CachedCount); + msg = x.Path == null ? "Cached - Error" : $"Cached - {x.Path}"; + break; + } - // If script cache is enabled, generate cache. - if (Setting.Script_EnableCache) - StartCacheWorker(); - } - else - { - Model.ScriptTitleText = "Unable to find project."; - Model.ScriptDescriptionText = $"Please provide project in [{Projects.ProjectRoot}]"; + if (0 < stage) + { + if (stage == 1) + msg = $"Stage {stage} ({loadedScriptCount} / {_allScriptCount}) \r\n{msg}"; + else + msg = $"Stage {stage} ({stage2LoadedCount} / {stage2LinkCount}) \r\n{msg}"; + } - if (!quiet) - Model.WorkInProgress = false; - Model.SwitchStatusProgressBar = true; // Show Status Bar - Model.StatusBarText = "Unable to find project."; - } + Model.ScriptDescriptionText = msg; + }); - resetEvent.Set(); - }; + // Load scripts in parallel + List errorLogs = Projects.Load(progress); + Logger.SystemWrite(errorLogs); + Setting.UpdateProjectList(); + + if (0 < Projects.ProjectNames.Count) + { // Load success + // Populate TreeView + Dispatcher.Invoke(() => + { + foreach (Project project in Projects.ProjectList) + ScriptListToTreeViewModel(project, project.VisibleScripts, true, Model.MainTree); + + int pIdx = Setting.Project_DefaultIndex; + CurMainTree = Model.MainTree.Children[pIdx]; + CurMainTree.IsExpanded = true; + if (Projects[pIdx] != null) + DrawScript(Projects[pIdx].MainScript); + }); + + Logger.SystemWrite(new LogInfo(LogState.Info, $"Projects [{string.Join(", ", Projects.ProjectList.Select(x => x.ProjectName))}] loaded")); + + watch.Stop(); + double t = watch.Elapsed.TotalMilliseconds / 1000.0; + string msg; + if (Setting.Script_EnableCache) + { + double cachePercent = (double)(stage1CachedCount + stage2CachedCount) * 100 / (_allScriptCount + stage2LinkCount); + msg = $"{_allScriptCount} scripts loaded ({t:0.#}s) - {cachePercent:0.#}% cached"; + Model.StatusBarText = msg; + } + else + { + msg = $"{_allScriptCount} scripts loaded ({t:0.#}s)"; + Model.StatusBarText = msg; + } + if (!quiet) + Model.WorkInProgress = false; + Model.SwitchStatusProgressBar = true; // Show Status Bar - _loadWorker.RunWorkerAsync(BaseDir); + Logger.SystemWrite(new LogInfo(LogState.Info, msg)); + Logger.SystemWrite(Logger.LogSeperator); - return resetEvent; + // If script cache is enabled, update cache. + if (Setting.Script_EnableCache) + StartScriptCaching(); + } + else + { // Load failure + Model.ScriptTitleText = "Unable to find project."; + Model.ScriptDescriptionText = $"Please provide project in [{Projects.ProjectRoot}]"; + + if (!quiet) + Model.WorkInProgress = false; + Model.SwitchStatusProgressBar = true; // Show Status Bar + Model.StatusBarText = "Unable to find project."; + } + } + finally + { + Interlocked.Decrement(ref _projectsLoading); + } + }); } - private void StartCacheWorker() + private Task StartScriptCaching() { - if (ScriptCache.DbLock == 0) + if (ScriptCache.DbLock != 0) + return Task.CompletedTask; + + return Task.Run(() => { Interlocked.Increment(ref ScriptCache.DbLock); + Model.WorkInProgress = true; try { - Stopwatch watch = new Stopwatch(); - _cacheWorker = new BackgroundWorker(); - - Model.WorkInProgress = true; - int updatedCount = 0; - int cachedCount = 0; - _cacheWorker.DoWork += (object sender, DoWorkEventArgs e) => - { - BackgroundWorker worker = sender as BackgroundWorker; - - watch = Stopwatch.StartNew(); - _scriptCache.CacheScripts(Projects, BaseDir, worker); - }; - - _cacheWorker.WorkerReportsProgress = true; - _cacheWorker.ProgressChanged += (object sender, ProgressChangedEventArgs e) => - { - Interlocked.Increment(ref cachedCount); - if (e.ProgressPercentage == 1) - Interlocked.Increment(ref updatedCount); - }; - _cacheWorker.RunWorkerCompleted += (object sender, RunWorkerCompletedEventArgs e) => - { - watch.Stop(); - - double cachePercent = (double)updatedCount * 100 / _allScriptCount; + Stopwatch watch = Stopwatch.StartNew(); + (int updatedCount, _) = _scriptCache.CacheScripts(Projects, BaseDir); + watch.Stop(); - double t = watch.Elapsed.TotalMilliseconds / 1000.0; - string msg = $"{_allScriptCount} scripts cached ({t:0.###}s), {cachePercent:0.#}% updated"; - Logger.SystemWrite(new LogInfo(LogState.Info, msg)); - Logger.SystemWrite(Logger.LogSeperator); + double cachedPercent = (double)updatedCount * 100 / _allScriptCount; + double t = watch.Elapsed.TotalMilliseconds / 1000.0; + string msg = $"{_allScriptCount} scripts cached ({t:0.###}s), {cachedPercent:0.#}% updated"; + Logger.SystemWrite(new LogInfo(LogState.Info, msg)); + Logger.SystemWrite(Logger.LogSeperator); - Model.WorkInProgress = false; - }; - _cacheWorker.RunWorkerAsync(); } finally { + Model.WorkInProgress = false; Interlocked.Decrement(ref ScriptCache.DbLock); } - } + }); } - public AutoResetEvent StartRefreshScriptWorker() + public Task StartRefreshScript() { if (CurMainTree?.Script == null) - return null; - if (_refreshWorker.IsBusy) - return null; + return Task.CompletedTask; if (CurMainTree.Script.Type == ScriptType.Directory) - return null; - - AutoResetEvent resetEvent = new AutoResetEvent(false); + return Task.CompletedTask; + if (_scriptRefreshing != 0) + return Task.CompletedTask; - Stopwatch watch = new Stopwatch(); - - Model.WorkInProgress = true; - _refreshWorker = new BackgroundWorker(); - _refreshWorker.DoWork += (object sender, DoWorkEventArgs e) => + TreeViewModel node = CurMainTree; + return Task.Run(() => { - watch.Start(); - Script sc = CurMainTree.Script; - if (sc.Type != ScriptType.Directory) - e.Result = sc.Project.RefreshScript(CurMainTree.Script); - }; - _refreshWorker.RunWorkerCompleted += (object sender, RunWorkerCompletedEventArgs e) => - { - if (e.Result is Script sc) - PostRefreshScript(sc); + Interlocked.Increment(ref _scriptRefreshing); + Model.WorkInProgress = true; + try + { + Stopwatch watch = Stopwatch.StartNew(); - Model.WorkInProgress = false; - watch.Stop(); - double sec = watch.Elapsed.TotalSeconds; - if (e.Result is Script) - Model.StatusBarText = $"{Path.GetFileName(CurMainTree.Script.TreePath)} reloaded. ({sec:0.000}s)"; - else - Model.StatusBarText = $"{Path.GetFileName(CurMainTree.Script.TreePath)} reload failed. ({sec:0.000}s)"; + Script sc = node.Script; + if (sc.Type != ScriptType.Directory) + sc = sc.Project.RefreshScript(node.Script); - resetEvent.Set(); - }; - _refreshWorker.RunWorkerAsync(); + watch.Stop(); + double t = watch.Elapsed.TotalSeconds; - return resetEvent; + if (sc == null) + { + PostRefreshScript(node, sc); + Model.StatusBarText = $"{Path.GetFileName(node.Script.TreePath)} reload failed. ({t:0.000}s)"; + } + else + { + Model.StatusBarText = $"{Path.GetFileName(node.Script.TreePath)} reloaded. ({t:0.000}s)"; + } + } + finally + { + Model.WorkInProgress = false; + Interlocked.Decrement(ref _scriptRefreshing); + } + }); } - private void PostRefreshScript(Script sc) + private void PostRefreshScript(TreeViewModel node, Script sc) { - CurMainTree.Script = sc; - CurMainTree.ParentCheckedPropagation(); - UpdateTreeViewIcon(CurMainTree); - DrawScript(CurMainTree.Script); + node.Script = sc; + node.ParentCheckedPropagation(); + UpdateTreeViewIcon(node); + DrawScript(node.Script); } - private void StartSyntaxCheckWorker(bool quiet) + private Task StartSyntaxCheck(bool quiet) { if (CurMainTree?.Script == null) - return; - - if (_syntaxCheckWorker.IsBusy) - return; + return Task.CompletedTask; + if (_syntaxChecking != 0) + return Task.CompletedTask; Script sc = CurMainTree.Script; if (sc.Type == ScriptType.Directory) - return; + return Task.CompletedTask; if (!quiet) Model.WorkInProgress = true; - _syntaxCheckWorker = new BackgroundWorker(); - _syntaxCheckWorker.DoWork += (object sender, DoWorkEventArgs e) => + return Task.Run(() => { - CodeValidator v = new CodeValidator(sc); - v.Validate(); - - e.Result = v; - }; - _syntaxCheckWorker.RunWorkerCompleted += (object sender, RunWorkerCompletedEventArgs e) => - { - if (!(e.Result is CodeValidator v)) - return; - - LogInfo[] logs = v.LogInfos; - LogInfo[] errorLogs = logs.Where(x => x.State == LogState.Error).ToArray(); - LogInfo[] warnLogs = logs.Where(x => x.State == LogState.Warning).ToArray(); - - int errorWarns = 0; - StringBuilder b = new StringBuilder(); - if (0 < errorLogs.Length) + Interlocked.Increment(ref _syntaxChecking); + try { - errorWarns += errorLogs.Length; + CodeValidator v = new CodeValidator(sc); + v.Validate(); - if (!quiet) + LogInfo[] logs = v.LogInfos; + LogInfo[] errorLogs = logs.Where(x => x.State == LogState.Error).ToArray(); + LogInfo[] warnLogs = logs.Where(x => x.State == LogState.Warning).ToArray(); + + int errorWarns = 0; + StringBuilder b = new StringBuilder(); + if (0 < errorLogs.Length) { - b.AppendLine($"{errorLogs.Length} syntax error detected at [{sc.TreePath}]"); - b.AppendLine(); - for (int i = 0; i < errorLogs.Length; i++) + errorWarns += errorLogs.Length; + + if (!quiet) { - LogInfo log = errorLogs[i]; - if (log.Command != null) - b.AppendLine($"[{i + 1}/{errorLogs.Length}] {log.Message} ({log.Command}) (Line {log.Command.LineIdx})"); - else - b.AppendLine($"[{i + 1}/{errorLogs.Length}] {log.Message}"); + b.AppendLine($"{errorLogs.Length} syntax error detected at [{sc.TreePath}]"); + b.AppendLine(); + for (int i = 0; i < errorLogs.Length; i++) + { + LogInfo log = errorLogs[i]; + if (log.Command != null) + b.AppendLine($"[{i + 1}/{errorLogs.Length}] {log.Message} ({log.Command}) (Line {log.Command.LineIdx})"); + else + b.AppendLine($"[{i + 1}/{errorLogs.Length}] {log.Message}"); + } + b.AppendLine(); } - b.AppendLine(); } - } - - if (0 < warnLogs.Length) - { - errorWarns += warnLogs.Length; - if (!quiet) + if (0 < warnLogs.Length) { - b.AppendLine($"{errorLogs.Length} syntax warning detected"); - b.AppendLine(); - for (int i = 0; i < warnLogs.Length; i++) + errorWarns += warnLogs.Length; + + if (!quiet) { - LogInfo log = warnLogs[i]; - b.AppendLine($"[{i + 1}/{warnLogs.Length}] {log.Message} ({log.Command})"); + b.AppendLine($"{errorLogs.Length} syntax warning detected"); + b.AppendLine(); + for (int i = 0; i < warnLogs.Length; i++) + { + LogInfo log = warnLogs[i]; + b.AppendLine($"[{i + 1}/{warnLogs.Length}] {log.Message} ({log.Command})"); + } + b.AppendLine(); } - b.AppendLine(); } - } - - if (errorWarns == 0) - { - Model.ScriptCheckResult = true; - if (!quiet) + if (errorWarns == 0) { - b.AppendLine("No syntax error detected"); - b.AppendLine(); - b.AppendLine($"Section Coverage : {v.Coverage * 100:0.#}% ({v.VisitedSectionCount}/{v.CodeSectionCount})"); + Model.ScriptCheckResult = true; - MessageBox.Show(b.ToString(), "Syntax Check", MessageBoxButton.OK, MessageBoxImage.Information); - } - } - else - { - Model.ScriptCheckResult = false; + if (!quiet) + { + b.AppendLine("No syntax error detected"); + b.AppendLine(); + b.AppendLine($"Section Coverage : {v.Coverage * 100:0.#}% ({v.VisitedSectionCount}/{v.CodeSectionCount})"); - if (!quiet) + MessageBox.Show(b.ToString(), "Syntax Check", MessageBoxButton.OK, MessageBoxImage.Information); + } + } + else { - MessageBoxResult result = MessageBox.Show($"{errorWarns} syntax error detected!\r\n\r\nOpen logs?", "Syntax Check", MessageBoxButton.OKCancel, MessageBoxImage.Error); - if (result == MessageBoxResult.OK) + Model.ScriptCheckResult = false; + + if (!quiet) { - b.AppendLine($"Section Coverage : {v.Coverage * 100:0.#}% ({v.VisitedSectionCount}/{v.CodeSectionCount})"); + MessageBoxResult result = MessageBox.Show($"{errorWarns} syntax error detected!\r\n\r\nOpen logs?", "Syntax Check", MessageBoxButton.OKCancel, MessageBoxImage.Error); + if (result == MessageBoxResult.OK) + { + b.AppendLine($"Section Coverage : {v.Coverage * 100:0.#}% ({v.VisitedSectionCount}/{v.CodeSectionCount})"); - string tempFile = Path.GetTempFileName(); - File.Delete(tempFile); - tempFile = Path.GetTempFileName().Replace(".tmp", ".txt"); - using (StreamWriter sw = new StreamWriter(tempFile, false, Encoding.UTF8)) - sw.Write(b.ToString()); + string tempFile = Path.GetTempFileName(); + File.Delete(tempFile); + tempFile = Path.GetTempFileName().Replace(".tmp", ".txt"); + using (StreamWriter sw = new StreamWriter(tempFile, false, Encoding.UTF8)) + sw.Write(b.ToString()); - OpenTextFile(tempFile); + OpenTextFile(tempFile); + } } } - } - if (!quiet) - Model.WorkInProgress = false; - }; - _syntaxCheckWorker.RunWorkerAsync(); + if (!quiet) + Model.WorkInProgress = false; + } + finally + { + Interlocked.Decrement(ref _syntaxChecking); + } + }); } #endregion @@ -627,8 +594,9 @@ public void DrawScript(Script sc) MainCanvas.LayoutTransform = scale; render.Render(); + // Do not use await, let it run in background if (Setting.Script_AutoSyntaxCheck) - StartSyntaxCheckWorker(true); + StartSyntaxCheck(true); } Model.IsTreeEntryFile = sc.Type != ScriptType.Directory; @@ -748,18 +716,18 @@ private async void BuildButton_Click(object sender, RoutedEventArgs e) private void RefreshButton_Click(object sender, RoutedEventArgs e) { - if (_loadWorker.IsBusy) + if (_projectsLoading != 0) return; (MainTreeView.DataContext as TreeViewModel)?.Children.Clear(); - StartLoadWorker(); + StartLoadingProjects(); } [SuppressMessage("ReSharper", "InconsistentNaming")] private void SettingButton_Click(object sender, RoutedEventArgs e) { - if (_loadWorker.IsBusy) + if (_projectsLoading != 0) return; double old_Interface_ScaleFactor = Setting.Interface_ScaleFactor; @@ -777,7 +745,7 @@ private void SettingButton_Click(object sender, RoutedEventArgs e) // Refresh Project if (old_Compat_AsteriskBugDirLink != Setting.Compat_AsteriskBugDirLink) { - StartLoadWorker(); + StartLoadingProjects(); } else { @@ -795,16 +763,15 @@ private void SettingButton_Click(object sender, RoutedEventArgs e) // Script if (!old_Script_EnableCache && Setting.Script_EnableCache) - StartCacheWorker(); + StartScriptCaching(); } } } private void UtilityButton_Click(object sender, RoutedEventArgs e) { - if (_loadWorker.IsBusy) + if (_projectsLoading != 0) return; - if (0 < UtilityWindow.Count) return; @@ -918,7 +885,7 @@ private void ScriptRefreshButton_Click(object sender, RoutedEventArgs e) if (Model.WorkInProgress) return; - StartRefreshScriptWorker(); + StartRefreshScript(); } private void ScriptEditButton_Click(object sender, RoutedEventArgs e) @@ -1032,7 +999,7 @@ private void ScriptUpdateButton_Click(object sender, RoutedEventArgs e) } else { - PostRefreshScript(newScript); + PostRefreshScript(CurMainTree, newScript); MessageBox.Show(msg, "Update Success", MessageBoxButton.OK, MessageBoxImage.Information); App.Logger.SystemWrite(new LogInfo(LogState.Success, msg)); @@ -1059,7 +1026,8 @@ private void ScriptCheckButton_Click(object sender, RoutedEventArgs e) if (Model.WorkInProgress) return; - StartSyntaxCheckWorker(false); + // Do not use await, let it run in background + StartSyntaxCheck(false); } private void ScriptOpenFolderButton_Click(object sender, RoutedEventArgs e) @@ -1391,11 +1359,11 @@ private async void Window_Closing(object sender, CancelEventArgs e) } // TODO: Do this in more cleaner way - while (_refreshWorker.IsBusy) + while (_scriptRefreshing != 0) await Task.Delay(500); - while (_cacheWorker.IsBusy) + while (ScriptCache.DbLock != 0) await Task.Delay(500); - while (_loadWorker.IsBusy) + if (_projectsLoading != 0) await Task.Delay(500); _scriptCache?.WaitClose(); @@ -1512,6 +1480,9 @@ public bool IsTreeEntryFile public string ScriptUpdateButtonToolTip => IsTreeEntryFile ? "Update Script" : "Update Scripts"; private bool? _scriptCheckResult = null; + /// + /// true -> has issue, false -> no issue, null -> not checked + /// public bool? ScriptCheckResult { get => _scriptCheckResult; @@ -1990,6 +1961,7 @@ public void OnPropertyUpdate(string propertyName) #region TreeViewModel public class TreeViewModel : INotifyPropertyChanged { + #region Basic Property and Constructor public TreeViewModel Root { get; } public TreeViewModel Parent { get; } @@ -1998,7 +1970,9 @@ public TreeViewModel(TreeViewModel root, TreeViewModel parent) Root = root ?? this; Parent = parent; } + #endregion + #region Shared Property private bool _isExpanded = false; public bool IsExpanded { @@ -2010,6 +1984,46 @@ public bool IsExpanded } } + public string Text => _script.Title; + + private Script _script; + public Script Script + { + get => _script; + set + { + _script = value; + OnPropertyUpdate(nameof(Script)); + OnPropertyUpdate(nameof(Checked)); + OnPropertyUpdate(nameof(CheckBoxVisible)); + OnPropertyUpdate(nameof(Text)); + OnPropertyUpdate(nameof(MainViewModel.MainCanvas)); + } + } + + private Control _icon; + public Control Icon + { + get => _icon; + set + { + _icon = value; + OnPropertyUpdate(nameof(Icon)); + } + } + + public ObservableCollection Children { get; private set; } = new ObservableCollection(); + + public void SortChildren() + { + IOrderedEnumerable sorted = Children + .OrderBy(x => x.Script.Level) + .ThenBy(x => x.Script.Type) + .ThenBy(x => x.Script.RealPath); + Children = new ObservableCollection(sorted); + } + #endregion + #region Build Mode Property private bool _buildFocus = false; public bool BuildFocus @@ -2026,6 +2040,7 @@ public bool BuildFocus public FontWeight BuildFontWeight => _buildFocus ? FontWeights.SemiBold : FontWeights.Normal; #endregion + #region Enabled CheckBox public bool Checked { get @@ -2128,51 +2143,35 @@ public Visibility CheckBoxVisible } } - public string Text => _script.Title; - - private Script _script; - public Script Script + private List DisableScripts(TreeViewModel root, Script sc) { - get => _script; - set - { - _script = value; - OnPropertyUpdate(nameof(Script)); - OnPropertyUpdate(nameof(Checked)); - OnPropertyUpdate(nameof(CheckBoxVisible)); - OnPropertyUpdate(nameof(Text)); - OnPropertyUpdate(nameof(MainViewModel.MainCanvas)); - } - } + if (root == null || sc == null) + return new List(); - private Control _icon; - public Control Icon - { - get => _icon; - set + string[] paths = Script.GetDisableScriptPaths(sc, out List errorLogs); + if (paths == null) + return new List(); + + foreach (string path in paths) { - _icon = value; - OnPropertyUpdate(nameof(Icon)); + int exist = sc.Project.AllScripts.Count(x => x.RealPath.Equals(path, StringComparison.OrdinalIgnoreCase)); + if (exist == 1) + { + Ini.WriteKey(path, "Main", "Selected", "False"); + TreeViewModel found = FindScriptByFullPath(path); + if (found != null) + { + if (sc.Type != ScriptType.Directory && sc.Mandatory == false && sc.Selected != SelectedState.None) + found.Checked = false; + } + } } - } - - public ObservableCollection Children { get; private set; } = new ObservableCollection(); - - public void SortChildren() - { - IOrderedEnumerable sorted = Children - .OrderBy(x => x.Script.Level) - .ThenBy(x => x.Script.Type) - .ThenBy(x => x.Script.RealPath); - Children = new ObservableCollection(sorted); - } - public event PropertyChangedEventHandler PropertyChanged; - public void OnPropertyUpdate(string propertyName) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + return errorLogs; } + #endregion + #region Find Script public TreeViewModel FindScriptByFullPath(string fullPath) { return RecursiveFindScriptByFullPath(Root, fullPath); @@ -2199,33 +2198,22 @@ private static TreeViewModel RecursiveFindScriptByFullPath(TreeViewModel cur, st // Not found in this path return null; } + #endregion - private List DisableScripts(TreeViewModel root, Script sc) + #region OnProperetyUpdate + public event PropertyChangedEventHandler PropertyChanged; + public void OnPropertyUpdate(string propertyName) { - if (root == null || sc == null) - return new List(); - - string[] paths = Script.GetDisableScriptPaths(sc, out List errorLogs); - if (paths == null) - return new List(); - - foreach (string path in paths) - { - int exist = sc.Project.AllScripts.Count(x => x.RealPath.Equals(path, StringComparison.OrdinalIgnoreCase)); - if (exist == 1) - { - Ini.WriteKey(path, "Main", "Selected", "False"); - TreeViewModel found = FindScriptByFullPath(path); - if (found != null) - { - if (sc.Type != ScriptType.Directory && sc.Mandatory == false && sc.Selected != SelectedState.None) - found.Checked = false; - } - } - } + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + #endregion - return errorLogs; + #region ToString + public override string ToString() + { + return Text; } + #endregion } #endregion diff --git a/PEBakery/WPF/UIRenderer.cs b/PEBakery/WPF/UIRenderer.cs index ba92c92b..60a532d2 100644 --- a/PEBakery/WPF/UIRenderer.cs +++ b/PEBakery/WPF/UIRenderer.cs @@ -1175,8 +1175,8 @@ private static async void RunOneSection(SectionAddress addr, string logMsg, bool EngineState s = new EngineState(addr.Script.Project, logger, mainModel, EngineMode.RunMainAndOne, addr.Script, addr.Section.Name); s.SetOptions(setting); - if (s.LogMode == LogMode.PartDelay) - s.LogMode = LogMode.FullDelay; + if (s.LogMode == LogMode.PartDefer) // Use FullDefer in UIRenderer + s.LogMode = LogMode.FullDefer; Engine.WorkingEngine = new Engine(s); @@ -1192,10 +1192,10 @@ private static async void RunOneSection(SectionAddress addr, string logMsg, bool mainModel.SwitchNormalBuildInterface = true; // Flush FullDelayedLogs - if (s.LogMode == LogMode.FullDelay) + if (s.LogMode == LogMode.FullDefer) { - DeferredLogging delayed = logger.Deferred; - delayed.FlushFullDeferred(s); + DeferredLogging deferred = logger.Deferred; + deferred.FlushFullDeferred(s); } // Turn off ProgressRing diff --git a/PEBakery/WPF/UtilityWindow.xaml.cs b/PEBakery/WPF/UtilityWindow.xaml.cs index 04021e9b..48aea1a9 100644 --- a/PEBakery/WPF/UtilityWindow.xaml.cs +++ b/PEBakery/WPF/UtilityWindow.xaml.cs @@ -69,7 +69,7 @@ public UtilityWindow(FontHelper.WPFFont monoFont) MainWindow w = Application.Current.MainWindow as MainWindow; Debug.Assert(w != null, "MainWindow != null"); - List projList = w.Projects.Projects; + List projList = w.Projects.ProjectList; for (int i = 0; i < projList.Count; i++) { Project proj = projList[i];