From 5560056645fd897ff3b473279f54f05432332ab2 Mon Sep 17 00:00:00 2001 From: Nayiem Willems Date: Fri, 10 Jul 2026 03:15:01 -0700 Subject: [PATCH] Fix client startup hangs on macOS The UniversalGL client never reaches its main menu on macOS - it deadlocks during startup. There are two distinct hangs, both rooted in .NET's FileSystemWatcher (FSEvents-backed) stalling during setup on macOS: 1. GameClass.BuildServiceProvider built a full .NET Generic Host via Host.CreateDefaultBuilder().Build(), whose configuration provider sets up a reloadOnChange FileSystemWatcher. That setup hangs, so Build() never returns and the main thread blocks on it forever. The client only ever used the host as a DI container, so replace it with a plain ServiceCollection (identical service registrations), dropping the Generic Host machinery entirely. 2. MapLoader.Initialize subscribed StartMapFileWatcher to MapLoadingComplete, which is invoked synchronously at the end of the map-loading task. That watcher is another FileSystemWatcher; its setup hangs and blocks the map loading task from ever completing, leaving the client stuck on the loading screen ("Waiting for loading maps..."). Skip the live map-file watcher on macOS - maps still load, they just are not hot-reloaded at runtime. With both changes the UniversalGL client boots to the main menu on macOS. The MapLoader change is guarded to macOS only. The ServiceCollection change applies to all platforms; the client used no Generic Host feature beyond DI, so behavior should be unchanged, but a Windows/Linux build+run check is worthwhile. --- DXMainClient/DXGUI/GameClass.cs | 14 +++++++------- DXMainClient/Domain/Multiplayer/MapLoader.cs | 7 ++++++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/DXMainClient/DXGUI/GameClass.cs b/DXMainClient/DXGUI/GameClass.cs index 89494a925..37f2f3db5 100644 --- a/DXMainClient/DXGUI/GameClass.cs +++ b/DXMainClient/DXGUI/GameClass.cs @@ -22,7 +22,6 @@ using DTAClient.Online; using ClientGUI.Settings; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Rampastring.XNAUI.XNAControls; using MainMenu = DTAClient.DXGUI.Generic.MainMenu; using System.Threading.Tasks; @@ -259,9 +258,12 @@ private static Random GetRandom() private IServiceProvider BuildServiceProvider(WindowManager windowManager) { - // Create host - this allows for things like DependencyInjection - IHost host = Host.CreateDefaultBuilder() - .ConfigureServices((_, services) => + // Dependency-injection container. This previously built a full .NET Generic Host + // (Host.CreateDefaultBuilder().Build()), but the client only ever used it as a DI + // container, and that Build() hangs on macOS: the host's configuration provider sets + // up a FileSystemWatcher (reloadOnChange), whose FSEvents-backed setup stalls there. + // A plain ServiceCollection provides the same DI with none of that machinery. + var services = new ServiceCollection(); { // services (or service-like) services @@ -336,10 +338,8 @@ private IServiceProvider BuildServiceProvider(WindowManager windowManager) .AddTransientXnaControl() .AddTransientXnaControl(); } - ) - .Build(); - return host.Services.GetService(); + return services.BuildServiceProvider(); } private void InitializeUISettings() diff --git a/DXMainClient/Domain/Multiplayer/MapLoader.cs b/DXMainClient/Domain/Multiplayer/MapLoader.cs index 81fb18c83..031d8a95e 100644 --- a/DXMainClient/Domain/Multiplayer/MapLoader.cs +++ b/DXMainClient/Domain/Multiplayer/MapLoader.cs @@ -114,7 +114,12 @@ public MapLoader() { } public void Initialize() { - MapLoadingComplete += (sender, args) => StartMapFileWatcher(); + // .NET's FileSystemWatcher (FSEvents-backed) stalls during setup on macOS. + // Since MapLoadingComplete is invoked synchronously, that would block the map + // loading task from ever completing and hang the client on the loading screen. + // Skip the live map-file watcher on macOS; maps still load, just no hot-reload. + if (!OperatingSystem.IsMacOS()) + MapLoadingComplete += (sender, args) => StartMapFileWatcher(); } ///