Skip to content

Commit dfebd66

Browse files
Minor cleanup (#275)
- Warnings as errors in CI - Re-enable gdunit4 tests in CI - Better exception handling - SRP cleanup for packet gen scripts - Minor cleanup elsewhere
1 parent 510b855 commit dfebd66

40 files changed

Lines changed: 1008 additions & 653 deletions

.github/workflows/pr_ci.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,27 +116,27 @@ jobs:
116116
set -euo pipefail
117117
118118
if [[ "${{ steps.changes.outputs.build_godotutils }}" == "true" ]]; then
119-
dotnet build Template.GodotUtils/GodotUtils.csproj --no-restore -p:BuildExtras=true
119+
dotnet build Template.GodotUtils/GodotUtils.csproj --no-restore -p:BuildExtras=true -warnaserror
120120
fi
121121
122122
if [[ "${{ steps.changes.outputs.build_visualize }}" == "true" ]]; then
123-
dotnet build Template.Visualize/Visualize.csproj --no-restore -p:BuildExtras=true
123+
dotnet build Template.Visualize/Visualize.csproj --no-restore -p:BuildExtras=true -warnaserror
124124
fi
125125
126126
if [[ "${{ steps.changes.outputs.build_packetgen }}" == "true" ]]; then
127-
dotnet build Template.PacketGen/PacketGen/PacketGen.csproj -c Release --no-restore
127+
dotnet build Template.PacketGen/PacketGen/PacketGen.csproj -c Release --no-restore -warnaserror
128128
fi
129129
130130
if [[ "${{ steps.changes.outputs.build_optionsgen }}" == "true" ]]; then
131-
dotnet build Template.OptionsGen/OptionsGen/OptionsGen.csproj -c Release --no-restore
131+
dotnet build Template.OptionsGen/OptionsGen/OptionsGen.csproj -c Release --no-restore -warnaserror
132132
fi
133133
134134
if [[ "${{ steps.changes.outputs.build_template }}" == "true" ]]; then
135-
dotnet build Template/Template.csproj --no-restore
135+
dotnet build Template/Template.csproj --no-restore -warnaserror
136136
fi
137137
138138
- name: Full solution build
139-
run: dotnet build Template.sln --no-restore
139+
run: dotnet build Template.sln --no-restore -warnaserror
140140

141141
- name: Tests
142142
run: dotnet test Template.PacketGen/PacketGen.Tests/PacketGen.Tests.csproj --no-build
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
3+
namespace GodotUtils;
4+
5+
internal static class ExceptionGuard
6+
{
7+
public static bool IsNonFatal(Exception exception)
8+
{
9+
return exception is not OutOfMemoryException
10+
and not StackOverflowException
11+
and not AccessViolationException
12+
and not AppDomainUnloadedException
13+
and not BadImageFormatException
14+
and not CannotUnloadAppDomainException;
15+
}
16+
}

Template.GodotUtils/Utils/TaskUtils.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,19 @@ private static async Task FireAndForgetInternal(Func<Task> task)
2525
{
2626
await task();
2727
}
28-
catch (Exception e)
28+
catch (OperationCanceledException)
29+
{
30+
// Expected when the task is canceled by shutdown/dispose flow.
31+
}
32+
catch (ObjectDisposedException e)
33+
{
34+
GD.PrintErr($"Error: {e}");
35+
}
36+
catch (InvalidOperationException e)
37+
{
38+
GD.PrintErr($"Error: {e}");
39+
}
40+
catch (Exception e) when (ExceptionGuard.IsNonFatal(e))
2941
{
3042
GD.PrintErr($"Error: {e}");
3143
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using PacketGen.Generators.PacketGeneration;
2+
using System.Linq;
3+
4+
namespace PacketGen.Generators;
5+
6+
internal sealed class PacketConstructorBuilder
7+
{
8+
public string Build(PacketGenerationModel model)
9+
{
10+
const string indent4 = " ";
11+
const string indent8 = " ";
12+
13+
var refTypeProps = model.Properties.Where(p => !p.Type.IsValueType).ToList();
14+
string emptyConstructor;
15+
if (refTypeProps.Count == 0)
16+
{
17+
emptyConstructor = $"{indent4}public {model.ClassName}() {{ }}";
18+
}
19+
else
20+
{
21+
string nullInits = string.Join("\n", refTypeProps.Select(p => $"{indent8}{p.Name} = null!;"));
22+
emptyConstructor = $"{indent4}public {model.ClassName}()\n{indent4}{{\n{nullInits}\n{indent4}}}";
23+
}
24+
25+
string paramList = string.Join(", ", model.Properties.Select(p =>
26+
$"{p.Type.ToDisplayString()} {ToCamelCase(p.Name)}"));
27+
28+
string assignments = string.Join("\n", model.Properties.Select(p =>
29+
$"{indent8}{p.Name} = {ToCamelCase(p.Name)};"));
30+
31+
string paramsConstructor =
32+
$"{indent4}public {model.ClassName}({paramList})\n" +
33+
$"{indent4}{{\n" +
34+
$"{assignments}\n" +
35+
$"{indent4}}}";
36+
37+
return $"{emptyConstructor}\n\n{paramsConstructor}\n";
38+
}
39+
40+
private static string ToCamelCase(string name)
41+
{
42+
if (string.IsNullOrEmpty(name))
43+
return name;
44+
45+
return char.ToLowerInvariant(name[0]) + name.Substring(1);
46+
}
47+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
namespace PacketGen.Generators;
2+
3+
internal sealed class PacketDeepObjectHelperBuilder
4+
{
5+
public string BuildDeepEqualsHelper(bool include)
6+
{
7+
if (!include)
8+
return string.Empty;
9+
10+
return """
11+
12+
private static bool DeepEquals(object? left, object? right)
13+
{
14+
if (object.ReferenceEquals(left, right))
15+
return true;
16+
17+
if (left is null || right is null)
18+
return false;
19+
20+
if (left is string && right is string)
21+
return string.Equals((string)left, (string)right);
22+
23+
if (left is string || right is string)
24+
return false;
25+
26+
if (left is IDictionary leftDict && right is IDictionary rightDict)
27+
return DictionaryEquals(leftDict, rightDict);
28+
29+
if (left is Array leftArray && right is Array rightArray)
30+
return ArrayEquals(leftArray, rightArray);
31+
32+
if (left is IEnumerable leftEnumerable && right is IEnumerable rightEnumerable)
33+
return SequenceEquals(leftEnumerable, rightEnumerable);
34+
35+
return left.Equals(right);
36+
}
37+
38+
private static bool ArrayEquals(Array left, Array right)
39+
{
40+
if (left.Length != right.Length)
41+
return false;
42+
43+
Type? elementType = left.GetType().GetElementType();
44+
if (elementType != null && elementType != typeof(object) && !typeof(IEnumerable).IsAssignableFrom(elementType))
45+
return StructuralEqualityComparer.Equals(left, right);
46+
47+
return SequenceEquals(left, right);
48+
}
49+
50+
private static bool DictionaryEquals(IDictionary left, IDictionary right)
51+
{
52+
if (left.Count != right.Count)
53+
return false;
54+
55+
foreach (DictionaryEntry entry in left)
56+
{
57+
if (!right.Contains(entry.Key))
58+
return false;
59+
60+
if (!DeepEquals(entry.Value, right[entry.Key]))
61+
return false;
62+
}
63+
64+
return true;
65+
}
66+
67+
private static bool SequenceEquals(IEnumerable left, IEnumerable right)
68+
{
69+
if (left is ICollection leftCollection && right is ICollection rightCollection && leftCollection.Count != rightCollection.Count)
70+
return false;
71+
72+
IEnumerator leftEnumerator = left.GetEnumerator();
73+
IEnumerator rightEnumerator = right.GetEnumerator();
74+
75+
while (true)
76+
{
77+
bool leftNext = leftEnumerator.MoveNext();
78+
bool rightNext = rightEnumerator.MoveNext();
79+
80+
if (leftNext != rightNext)
81+
return false;
82+
83+
if (!leftNext)
84+
return true;
85+
86+
if (!DeepEquals(leftEnumerator.Current, rightEnumerator.Current))
87+
return false;
88+
}
89+
}
90+
""";
91+
}
92+
93+
public string BuildDeepHashHelper(bool include, int hashSeedValue, int hashMultiplierSecondary)
94+
{
95+
if (!include)
96+
return string.Empty;
97+
98+
return $$"""
99+
100+
private static int DeepHash(object? value)
101+
{
102+
if (value is null)
103+
return 0;
104+
105+
if (value is string)
106+
return value.GetHashCode();
107+
108+
if (value is IDictionary dict)
109+
return DictionaryHash(dict);
110+
111+
if (value is Array array)
112+
return ArrayHash(array);
113+
114+
if (value is IEnumerable enumerable)
115+
return SequenceHash(enumerable);
116+
117+
return value.GetHashCode();
118+
}
119+
120+
private static int ArrayHash(Array array)
121+
{
122+
Type? elementType = array.GetType().GetElementType();
123+
if (elementType != null && elementType != typeof(object) && !typeof(IEnumerable).IsAssignableFrom(elementType))
124+
return StructuralEqualityComparer.GetHashCode(array);
125+
126+
return SequenceHash(array);
127+
}
128+
129+
private static int DictionaryHash(IDictionary dict)
130+
{
131+
int hash = dict.Count;
132+
133+
foreach (DictionaryEntry entry in dict)
134+
{
135+
int entryHash = {{hashSeedValue}};
136+
entryHash = (entryHash * {{hashMultiplierSecondary}}) ^ DeepHash(entry.Key);
137+
entryHash = (entryHash * {{hashMultiplierSecondary}}) ^ DeepHash(entry.Value);
138+
hash ^= entryHash;
139+
}
140+
141+
return hash;
142+
}
143+
144+
private static int SequenceHash(IEnumerable sequence)
145+
{
146+
int hash = {{hashSeedValue}};
147+
148+
foreach (object item in sequence)
149+
{
150+
hash = (hash * {{hashMultiplierSecondary}}) ^ DeepHash(item);
151+
}
152+
153+
return hash;
154+
}
155+
""";
156+
}
157+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp.Syntax;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
6+
namespace PacketGen.Generators;
7+
8+
internal sealed class PacketFrameworkNamespaceResolver
9+
{
10+
public string? Resolve(INamedTypeSymbol symbol)
11+
{
12+
string? semanticNamespace = GetPacketFrameworkNamespaceFromSymbols(symbol);
13+
if (semanticNamespace is not null)
14+
return semanticNamespace;
15+
16+
return GetPacketFrameworkNamespaceFromSyntax(symbol);
17+
}
18+
19+
private static string? GetPacketFrameworkNamespaceFromSymbols(INamedTypeSymbol symbol)
20+
{
21+
INamedTypeSymbol? current = symbol;
22+
23+
while (current is not null)
24+
{
25+
if (current.Name is PacketGenConstants.ClientPacketTypeName or PacketGenConstants.ServerPacketTypeName)
26+
{
27+
string ns = current.ContainingNamespace?.ToDisplayString() ?? string.Empty;
28+
29+
if (string.IsNullOrWhiteSpace(ns) || ns == "<global namespace>")
30+
return null;
31+
32+
return ns;
33+
}
34+
35+
current = current.BaseType;
36+
}
37+
38+
return null;
39+
}
40+
41+
private static string? GetPacketFrameworkNamespaceFromSyntax(INamedTypeSymbol symbol)
42+
{
43+
foreach (SyntaxReference syntaxReference in symbol.DeclaringSyntaxReferences)
44+
{
45+
if (syntaxReference.GetSyntax() is not ClassDeclarationSyntax classDeclaration)
46+
continue;
47+
48+
if (classDeclaration.BaseList is null)
49+
continue;
50+
51+
string? namespaceFromBase = TryGetNamespaceFromBaseType(classDeclaration);
52+
if (namespaceFromBase is not null)
53+
return namespaceFromBase;
54+
55+
string? namespaceFromUsing = TryGetNamespaceFromUsings(classDeclaration);
56+
if (namespaceFromUsing is not null)
57+
return namespaceFromUsing;
58+
}
59+
60+
return null;
61+
}
62+
63+
private static string? TryGetNamespaceFromBaseType(ClassDeclarationSyntax classDeclaration)
64+
{
65+
foreach (BaseTypeSyntax baseType in classDeclaration.BaseList!.Types)
66+
{
67+
string baseTypeText = baseType.Type.ToString();
68+
69+
if (baseTypeText.EndsWith("." + PacketGenConstants.ClientPacketTypeName)
70+
|| baseTypeText.EndsWith("." + PacketGenConstants.ServerPacketTypeName))
71+
return baseTypeText.Substring(0, baseTypeText.LastIndexOf('.'));
72+
}
73+
74+
return null;
75+
}
76+
77+
private static string? TryGetNamespaceFromUsings(ClassDeclarationSyntax classDeclaration)
78+
{
79+
bool usesPacketBaseName = classDeclaration.BaseList!.Types.Any(baseType =>
80+
{
81+
string baseTypeText = baseType.Type.ToString();
82+
return baseTypeText is PacketGenConstants.ClientPacketTypeName or PacketGenConstants.ServerPacketTypeName;
83+
});
84+
85+
if (!usesPacketBaseName)
86+
return null;
87+
88+
if (classDeclaration.SyntaxTree.GetRoot() is not CompilationUnitSyntax compilationUnit)
89+
return null;
90+
91+
List<string> candidateUsings = compilationUnit.Usings
92+
.Where(usingDirective =>
93+
usingDirective.Alias is null
94+
&& usingDirective.StaticKeyword.RawKind != (int)Microsoft.CodeAnalysis.CSharp.SyntaxKind.StaticKeyword
95+
&& usingDirective.Name is not null)
96+
.Select(usingDirective => usingDirective.Name!.ToString())
97+
.Where(namespaceName => !namespaceName.StartsWith("System"))
98+
.ToList();
99+
100+
if (candidateUsings.Count == 1)
101+
return candidateUsings[0];
102+
103+
return candidateUsings.FirstOrDefault(namespaceName => namespaceName.EndsWith(PacketGenConstants.NetcodeNamespaceSuffix));
104+
}
105+
}

0 commit comments

Comments
 (0)