Skip to content

Commit 510b855

Browse files
Further netcode cleanup
1 parent 42613b8 commit 510b855

24 files changed

Lines changed: 1178 additions & 129 deletions

Template/Framework/Autoloads/AutoloadsFramework.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public abstract partial class AutoloadsFramework : Node
3838
public FocusOutlineManager FocusOutline { get; private set; } = null!;
3939
public Logger Logger { get; private set; } = null!;
4040
public IApplicationLifetime ApplicationLifetime { get; private set; } = null!;
41+
public IBackgroundTaskTracker BackgroundTasks { get; private set; } = null!;
4142
public GameServices RuntimeServices { get; private set; } = null!;
4243

4344
#if DEBUG
@@ -64,6 +65,7 @@ public sealed override void _EnterTree()
6465
GameConsole = GetNode<GameConsole>("%Console");
6566
FocusOutline = new FocusOutlineManager(this);
6667
Logger = new Logger(GameConsole);
68+
BackgroundTasks = new BackgroundTaskTracker(Logger);
6769

6870
OptionsManager = new OptionsManager(this);
6971
AudioManager = new AudioManager(this, OptionsManager);
@@ -83,7 +85,8 @@ public sealed override void _EnterTree()
8385
Services,
8486
FocusOutline,
8587
Logger,
86-
ApplicationLifetime);
88+
ApplicationLifetime,
89+
BackgroundTasks);
8790
Game.Initialize(RuntimeServices);
8891

8992
SceneComposition.ConfigureNodeTree(this, RuntimeServices);
@@ -126,7 +129,7 @@ public sealed override void _Notification(int what)
126129
{
127130
if (what == NotificationWMCloseRequest)
128131
{
129-
TaskUtils.FireAndForget(ExitGame);
132+
BackgroundTasks.Run(_ => ExitGame(), "Autoloads.ExitGame");
130133
}
131134

132135
Notification(what);
@@ -143,6 +146,7 @@ public sealed override void _ExitTree()
143146
#endif
144147

145148
Logger.Dispose();
149+
BackgroundTasks.Dispose();
146150
Profiler.Dispose();
147151

148152
Game.Reset();
@@ -172,6 +176,10 @@ public async Task ExitGame()
172176
{
173177
await subscriber();
174178
}
179+
catch (OperationCanceledException ex)
180+
{
181+
GD.Print($"PreQuit subscriber canceled: {ex.Message}");
182+
}
175183
catch (Exception ex)
176184
{
177185
GD.PrintErr($"PreQuit subscriber failed: {ex}");

Template/Framework/Debugging/GameFramework.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public partial class Game
2121
public static FocusOutlineManager FocusOutline => RuntimeServices.FocusOutline;
2222

2323
public static ILoggerService Logger => RuntimeServices.Logger;
24+
public static IBackgroundTaskTracker BackgroundTasks => RuntimeServices.BackgroundTasks;
2425

2526
public static IApplicationLifetime Application => RuntimeServices.ApplicationLifetime;
2627

Template/Framework/ModLoader/ModLoaderUi.cs

Lines changed: 86 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -60,73 +60,86 @@ public void LoadMods(Node node)
6060
{
6161
if (!dir.CurrentIsDir())
6262
{
63-
goto Next;
63+
filename = dir.GetNext();
64+
continue;
6465
}
6566

66-
string modRoot = $@"{modsPath}/{filename}";
67-
string modJson = $@"{modRoot}/mod.json";
67+
TryLoadModDirectory(node, modsPath, filename, options);
68+
filename = dir.GetNext();
69+
}
6870

69-
if (!File.Exists(modJson))
70-
{
71-
_logger.LogWarning($"The mod folder '{filename}' does not have a mod.json so it will not be loaded");
72-
goto Next;
73-
}
71+
dir.ListDirEnd();
72+
dir.Dispose();
73+
}
7474

75-
string jsonFileContents = File.ReadAllText(modJson);
75+
private void TryLoadModDirectory(Node hostNode, string modsPath, string folderName, JsonSerializerOptions options)
76+
{
77+
string modRoot = $@"{modsPath}/{folderName}";
78+
string modJson = $@"{modRoot}/mod.json";
7679

77-
jsonFileContents = jsonFileContents.Replace("*", "Any");
80+
if (!File.Exists(modJson))
81+
{
82+
_logger.LogWarning($"The mod folder '{folderName}' does not have a mod.json so it will not be loaded");
83+
return;
84+
}
7885

79-
if (!TryDeserializeModInfo(modJson, jsonFileContents, options, out ModInfo? modInfo))
80-
{
81-
goto Next;
82-
}
86+
string jsonFileContents;
87+
try
88+
{
89+
jsonFileContents = File.ReadAllText(modJson);
90+
}
91+
catch (IOException exception)
92+
{
93+
_logger.LogWarning($"Failed to read '{modJson}': {exception.Message}");
94+
return;
95+
}
96+
catch (UnauthorizedAccessException exception)
97+
{
98+
_logger.LogWarning($"Access denied while reading '{modJson}': {exception.Message}");
99+
return;
100+
}
83101

84-
modInfo!.Normalize();
102+
jsonFileContents = jsonFileContents.Replace("*", "Any");
85103

86-
if (string.IsNullOrWhiteSpace(modInfo.Id))
87-
{
88-
_logger.LogWarning($"The mod folder '{filename}' has an invalid or empty id and will be skipped");
89-
goto Next;
90-
}
104+
if (!TryDeserializeModInfo(modJson, jsonFileContents, options, out ModInfo? modInfo))
105+
return;
91106

92-
if (_mods.ContainsKey(modInfo.Id))
93-
{
94-
_logger.LogWarning($"Duplicate mod id '{modInfo.Id}' was skipped");
95-
goto Next;
96-
}
107+
modInfo!.Normalize();
108+
109+
if (string.IsNullOrWhiteSpace(modInfo.Id))
110+
{
111+
_logger.LogWarning($"The mod folder '{folderName}' has an invalid or empty id and will be skipped");
112+
return;
113+
}
97114

98-
_mods.Add(modInfo.Id, modInfo);
115+
if (_mods.ContainsKey(modInfo.Id))
116+
{
117+
_logger.LogWarning($"Duplicate mod id '{modInfo.Id}' was skipped");
118+
return;
119+
}
99120

100-
// Load pck
101-
string pckPath = $@"{modRoot}/mod.pck";
121+
_mods.Add(modInfo.Id, modInfo);
102122

103-
if (File.Exists(pckPath))
104-
{
105-
bool success = ProjectSettings.LoadResourcePack(pckPath, replaceFiles: false);
123+
string pckPath = $@"{modRoot}/mod.pck";
124+
if (File.Exists(pckPath))
125+
{
126+
bool success = ProjectSettings.LoadResourcePack(pckPath, replaceFiles: false);
106127

107-
if (!success)
108-
{
109-
_logger.LogWarning($"Failed to load pck file for mod '{modInfo.Name}'");
110-
}
111-
else
112-
{
113-
TryInstantiateModScene(node, modInfo);
114-
}
128+
if (!success)
129+
{
130+
_logger.LogWarning($"Failed to load pck file for mod '{modInfo.Name}'");
115131
}
116-
117-
// Load dll
118-
string dllPath = $@"{modRoot}/Mod.dll";
119-
if (File.Exists(dllPath))
132+
else
120133
{
121-
TryLoadManagedMod(node, modInfo, dllPath);
134+
TryInstantiateModScene(hostNode, modInfo);
122135
}
123-
124-
Next:
125-
filename = dir.GetNext();
126136
}
127137

128-
dir.ListDirEnd();
129-
dir.Dispose();
138+
string dllPath = $@"{modRoot}/Mod.dll";
139+
if (File.Exists(dllPath))
140+
{
141+
TryLoadManagedMod(hostNode, modInfo, dllPath);
142+
}
130143
}
131144

132145
private bool TryDeserializeModInfo(
@@ -188,6 +201,18 @@ private void TryLoadManagedMod(Node hostNode, ModInfo modInfo, string dllPath)
188201
ManagedModRuntime runtime = new(loadContext, assembly, entrypoints);
189202
_managedMods.Add(modInfo.Id, runtime);
190203
}
204+
catch (FileNotFoundException exception)
205+
{
206+
_logger.LogErr(exception, $"Managed mod '{modInfo.Id}' assembly was not found");
207+
}
208+
catch (FileLoadException exception)
209+
{
210+
_logger.LogErr(exception, $"Managed mod '{modInfo.Id}' assembly could not be loaded");
211+
}
212+
catch (BadImageFormatException exception)
213+
{
214+
_logger.LogErr(exception, $"Managed mod '{modInfo.Id}' assembly is not a valid .NET assembly");
215+
}
191216
catch (Exception exception)
192217
{
193218
_logger.LogErr(exception, $"Failed to load managed mod '{modInfo.Id}'");
@@ -213,6 +238,18 @@ private List<IModEntrypoint> ActivateEntrypoints(Node hostNode, ModInfo modInfo,
213238
entrypoints.Add(entrypoint);
214239
}
215240
}
241+
catch (MissingMethodException exception)
242+
{
243+
_logger.LogErr(exception, $"Entrypoint '{type.FullName}' for mod '{modInfo.Id}' requires a public parameterless constructor");
244+
}
245+
catch (MemberAccessException exception)
246+
{
247+
_logger.LogErr(exception, $"Entrypoint '{type.FullName}' for mod '{modInfo.Id}' is not accessible");
248+
}
249+
catch (TargetInvocationException exception)
250+
{
251+
_logger.LogErr(exception, $"Entrypoint '{type.FullName}' for mod '{modInfo.Id}' threw during activation");
252+
}
216253
catch (Exception exception)
217254
{
218255
_logger.LogErr(exception, $"Failed to initialize entrypoint '{type.FullName}' for mod '{modInfo.Id}'");
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
3+
namespace __TEMPLATE__.Netcode;
4+
5+
/// <summary>
6+
/// Optional explicit order used by legacy reflection-based serialization fallback.
7+
/// Members without this attribute retain metadata-token ordering.
8+
/// </summary>
9+
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
10+
public sealed class NetOrderAttribute(int order) : Attribute
11+
{
12+
public int Order { get; } = order;
13+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://ctojg4qlwxp8j

0 commit comments

Comments
 (0)