diff --git a/StarterNG/App.axaml.cs b/StarterNG/App.axaml.cs index 176313c..9c7f902 100644 --- a/StarterNG/App.axaml.cs +++ b/StarterNG/App.axaml.cs @@ -75,25 +75,35 @@ public partial class App : Application KeyboardConfig.Instance.Save(); }; - var splash = new SplashWindow(); - desktop.MainWindow = splash; - splash.Show(); - - // Progress marshals callbacks back to this (UI) thread. - var progress = new Progress(splash.Report); + SplashWindow? splash = null; + var sw = System.Diagnostics.Stopwatch.StartNew(); Task.Run(async () => { + var progress = new Progress(status => + { + Dispatcher.UIThread.Post(() => + { + // Show splash only if loading takes more than 300ms + if (sw.ElapsedMilliseconds > 300 && splash == null) + { + splash = new SplashWindow(); + desktop.MainWindow = splash; + splash.Show(); + } + splash?.Report(status); + }); + }); + GameData.Instance.Load(progress); - await Task.Delay(400); // let the completed bar be visible briefly }).ContinueWith(_ => { Dispatcher.UIThread.Post(() => { var main = new MainWindow(); - desktop.MainWindow = main; // keep a main window before closing the splash + desktop.MainWindow = main; main.Show(); - splash.Close(); + splash?.Close(); }); }); } diff --git a/StarterNG/Classes/GameData.cs b/StarterNG/Classes/GameData.cs index e14b37d..76522ec 100644 --- a/StarterNG/Classes/GameData.cs +++ b/StarterNG/Classes/GameData.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; namespace StarterNG.Classes; @@ -66,15 +67,26 @@ public sealed class GameData // (length / mass / couplings) resolve without a UI-thread directory scan. Physics.PreloadIndex(); - // Phase 2 - sceneries - foreach (string file in scnFiles) - { - progress?.Report(new LoadStatus((double)done / total, LoadPhase.Sceneries, - Path.GetFileName(file))); - try { Sceneries.Add(new Scenery(file)); } - catch { /* skip unreadable scenery */ } - done++; - } + // Phase 2 - sceneries (parallelized) + var sceneryLock = new object(); + var doneCount = done; + Parallel.ForEach(scnFiles, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, + file => + { + try + { + var scenery = new Scenery(file); + lock (sceneryLock) + { + Sceneries.Add(scenery); + doneCount++; + progress?.Report(new LoadStatus((double)doneCount / total, LoadPhase.Sceneries, + Path.GetFileName(file))); + } + } + catch { /* skip unreadable scenery */ } + }); + done = doneCount; progress?.Report(new LoadStatus(1.0, LoadPhase.Done, null)); Loaded = true; @@ -84,11 +96,19 @@ public sealed class GameData public void ReloadSceneries(string sceneryDir = "scenery/") { Sceneries.Clear(); - foreach (string file in EnumerateScenery(sceneryDir)) - { - try { Sceneries.Add(new Scenery(file)); } - catch { /* skip */ } - } + var scnFiles = EnumerateScenery(sceneryDir); + var sceneryLock = new object(); + Parallel.ForEach(scnFiles, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, + file => + { + try + { + var scenery = new Scenery(file); + lock (sceneryLock) + Sceneries.Add(scenery); + } + catch { /* skip */ } + }); } private static List EnumerateScenery(string sceneryDir) diff --git a/StarterNG/Infrastructure/MonoFont.cs b/StarterNG/Infrastructure/MonoFont.cs new file mode 100644 index 0000000..9672f51 --- /dev/null +++ b/StarterNG/Infrastructure/MonoFont.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Media; + +namespace StarterNG.Infrastructure; + +/// +/// A fixed-width font family that actually exists on this machine. +/// Avalonia treats the extra names in "Consolas, monospace" as glyph fallbacks, +/// not as family alternatives, and "monospace" is a fontconfig alias it never +/// resolves — so that stack silently renders in the proportional UI font +/// everywhere except Windows. The timetable and the fault/preview boxes align +/// their columns with spaces, so they need a real monospace family: probe the +/// installed ones once and keep the first match. +/// +public static class MonoFont +{ + private static readonly string[] Preferred = + { + "Consolas", // Windows + "Menlo", // macOS + "SF Mono", + "DejaVu Sans Mono", // most Linux distros + "Liberation Mono", + "Noto Sans Mono", + "Ubuntu Mono", + "Courier New" + }; + + public static FontFamily Family { get; } = Resolve(); + + private static FontFamily Resolve() + { + try + { + var installed = new HashSet( + FontManager.Current.SystemFonts.Select(f => f.Name), + StringComparer.OrdinalIgnoreCase); + + foreach (string name in Preferred) + if (installed.Contains(name)) + return new FontFamily(name); + + // None of the usual suspects: take any family that calls itself Mono. + string? anyMono = installed.FirstOrDefault( + n => n.Contains("Mono", StringComparison.OrdinalIgnoreCase)); + if (anyMono is not null) + return new FontFamily(anyMono); + } + catch { /* no font manager yet - fall back to the default family */ } + + return FontFamily.Default; + } +} diff --git a/StarterNG/MainWindow.axaml b/StarterNG/MainWindow.axaml index b4cbe6e..56eedb4 100644 --- a/StarterNG/MainWindow.axaml +++ b/StarterNG/MainWindow.axaml @@ -28,29 +28,13 @@ Background="{StaticResource CarbonPanelBg}" BorderBrush="{StaticResource CarbonBorderLight}" BorderThickness="0,0,0,1"> - + - - - - - - - - - + + + +