Compare commits

...

8 Commits

Author SHA1 Message Date
PJB3005
bcc4cd77cf Version: 239.0.1 2025-01-15 20:16:05 +01:00
PJB3005
941cb4c1d6 Release notes 2025-01-15 20:15:21 +01:00
Myra
7b58760331 Downgrade VorbisPizza back to 1.3.0 (#5607)
Somewhere between 1.3.0 and 1.4.0 (I tested all versions) exists a bug that for some reason messes with the audio on some devices.

A proper fix would require us trying to make contact with the developer of VorbisPizza, find the buggy commit and waiting them to update their repo. This repo has been inactive for 8 months. I doubt we can get support unless we fork it and find the bug ourselves.

Closes & fixes #5605
2025-01-15 17:18:23 +01:00
PJB3005
f0306b593a Optimize Robust.Serialization.Generator
Slightly less bad use of incremental generators.

Remove format of generated code, it was taking most of the CPU time.

This seems to significantly cut compile time cost of this generator. Nice. I'm seeing 20 -> 5 seconds on my system.
2025-01-15 02:41:57 +01:00
PJB3005
5e1935c310 Optimize ByRefEventAnalyzer
According to a log, like 20 seconds of the build is spent in this analyzer. It now takes ~200ms. Hooray!
2025-01-13 05:52:31 +01:00
PJB3005
67c44a5fc5 Add unit test for ByRefEventAnalyzer 2025-01-13 05:44:35 +01:00
PJB3005
3ea0a0244b Fix net.packet RECV logging 2025-01-10 18:09:27 +01:00
PJB3005
325a39ee4b Wow way to expose that my lazy ass didn't actually try running package_webview.py on this build. 2025-01-09 04:22:25 +01:00
10 changed files with 301 additions and 164 deletions

View File

@@ -62,7 +62,7 @@
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />
<PackageVersion Include="TerraFX.Interop.Xlib" Version="6.4.0" />
<PackageVersion Include="VorbisPizza" Version="1.4.2" />
<PackageVersion Include="VorbisPizza" Version="1.3.0" />
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
<PackageVersion Include="prometheus-net" Version="8.2.1" />
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />

View File

@@ -1,4 +1,4 @@
<Project>
<!-- This file automatically reset by Tools/version.py -->
<!-- This file automatically reset by Tools/version.py -->

View File

@@ -54,6 +54,18 @@ END TEMPLATE-->
*None yet*
## 239.0.1
### Bugfixes
* Fix logging of received packets with `net.packet` logging level.
* Downgrade `VorbisPizza` to fix audio playback for systems without AVX2 support.
### Other
* Improved performance of some Roslyn analyzers and source generators, which should significantly improve compile times and IDE performance.
## 239.0.0
### Breaking changes

View File

@@ -0,0 +1,114 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.ByRefEventAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture, TestOf(typeof(ByRefEventAnalyzer))]
public sealed class ByRefEventAnalyzerTest
{
private const string EventBusDef = """
namespace Robust.Shared.GameObjects;
public readonly struct EntityUid;
public sealed class EntitySystem
{
public void RaiseLocalEvent<TEvent>(EntityUid uid, ref TEvent args, bool broadcast = false)
where TEvent : notnull { }
public void RaiseLocalEvent<TEvent>(EntityUid uid, TEvent args, bool broadcast = false)
where TEvent : notnull { }
}
public sealed class EntityEventBus
{
public void RaiseLocalEvent<TEvent>(EntityUid uid, ref TEvent args, bool broadcast = false)
where TEvent : notnull { }
public void RaiseLocalEvent<TEvent>(EntityUid uid, TEvent args, bool broadcast = false)
where TEvent : notnull { }
}
""";
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<ByRefEventAnalyzer, DefaultVerifier>()
{
TestState =
{
Sources = { code }
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.GameObjects.EventBusAttributes.cs"
);
test.TestState.Sources.Add(("EntityEventBus.cs", EventBusDef));
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
[Test]
public async Task TestSuccess()
{
const string code = """
using Robust.Shared.GameObjects;
[ByRefEvent]
public readonly struct RefEvent;
public readonly struct ValueEvent;
public static class Foo
{
public static void Bar(EntityEventBus bus)
{
bus.RaiseLocalEvent(default(EntityUid), new ValueEvent());
var refEv = new RefEvent();
bus.RaiseLocalEvent(default(EntityUid), ref refEv);
}
}
""";
await Verifier(code);
}
[Test]
public async Task TestWrong()
{
const string code = """
using Robust.Shared.GameObjects;
[ByRefEvent]
public readonly struct RefEvent;
public readonly struct ValueEvent;
public static class Foo
{
public static void Bar(EntityEventBus bus)
{
bus.RaiseLocalEvent(default(EntityUid), new RefEvent());
var valueEv = new ValueEvent();
bus.RaiseLocalEvent(default(EntityUid), ref valueEv);
}
}
""";
await Verifier(
code,
// /0/Test0.cs(11,49): error RA0015: Tried to raise a by-ref event 'RefEvent' by value
VerifyCS.Diagnostic(ByRefEventAnalyzer.ByRefEventRaisedByValueRule).WithSpan(11, 49, 11, 63).WithArguments("RefEvent"),
// /0/Test0.cs(13,49): error RA0016: Tried to raise a value event 'ValueEvent' by-ref
VerifyCS.Diagnostic(ByRefEventAnalyzer.ByValueEventRaisedByRefRule).WithSpan(13, 49, 13, 60).WithArguments("ValueEvent")
);
}
}

View File

@@ -14,6 +14,7 @@
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferNonGenericVariantForAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferNonGenericVariantForAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferOtherTypeAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferOtherTypeAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\GameObjects\EventBusAttributes.cs" LogicalName="Robust.Shared.GameObjects.EventBusAttributes.cs" LinkBase="Implementations" />
</ItemGroup>
<PropertyGroup>

View File

@@ -1,4 +1,4 @@
#nullable enable
#nullable enable
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
@@ -24,7 +24,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
"Make sure that methods subscribing to a ref event have the ref keyword for the event argument."
);
private static readonly DiagnosticDescriptor ByRefEventRaisedByValueRule = new(
public static readonly DiagnosticDescriptor ByRefEventRaisedByValueRule = new(
Diagnostics.IdByRefEventRaisedByValue,
"By-ref event raised by value",
"Tried to raise a by-ref event '{0}' by value",
@@ -34,7 +34,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
"Make sure to use the ref keyword when raising ref events."
);
private static readonly DiagnosticDescriptor ByValueEventRaisedByRefRule = new(
public static readonly DiagnosticDescriptor ByValueEventRaisedByRefRule = new(
Diagnostics.IdValueEventRaisedByRef,
"Value event raised by-ref",
"Tried to raise a value event '{0}' by-ref",
@@ -54,32 +54,44 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.EnableConcurrentExecution();
context.RegisterOperationAction(CheckEventRaise, OperationKind.Invocation);
context.RegisterCompilationStartAction(compilationContext =>
{
var raiseMethods = compilationContext.Compilation
.GetTypeByMetadataName("Robust.Shared.GameObjects.EntitySystem")?
.GetMembers()
.Where(m => m.Name.Contains("RaiseLocalEvent") && m.Kind == SymbolKind.Method)
.Cast<IMethodSymbol>();
var busRaiseMethods = compilationContext.Compilation
.GetTypeByMetadataName("Robust.Shared.GameObjects.EntityEventBus")?
.GetMembers()
.Where(m => m.Name.Contains("RaiseLocalEvent") && m.Kind == SymbolKind.Method)
.Cast<IMethodSymbol>();
if (raiseMethods == null)
return;
if (busRaiseMethods != null)
raiseMethods = raiseMethods.Concat(busRaiseMethods);
var raiseMethodsArray = raiseMethods.ToArray();
compilationContext.RegisterOperationAction(
ctx => CheckEventRaise(ctx, raiseMethodsArray),
OperationKind.Invocation);
});
}
private void CheckEventRaise(OperationAnalysisContext context)
private static void CheckEventRaise(
OperationAnalysisContext context,
IReadOnlyCollection<IMethodSymbol> raiseMethods)
{
if (context.Operation is not IInvocationOperation operation)
return;
var raiseMethods = context.Compilation
.GetTypeByMetadataName("Robust.Shared.GameObjects.EntitySystem")?
.GetMembers()
.Where(m => m.Name.Contains("RaiseLocalEvent") && m.Kind == SymbolKind.Method)
.Cast<IMethodSymbol>();
var busRaiseMethods = context.Compilation
.GetTypeByMetadataName("Robust.Shared.GameObjects.EntityEventBus")?
.GetMembers()
.Where(m => m.Name.Contains("RaiseLocalEvent") && m.Kind == SymbolKind.Method)
.Cast<IMethodSymbol>();
if (raiseMethods == null)
if (!operation.TargetMethod.Name.Contains("RaiseLocalEvent"))
return;
if (busRaiseMethods != null)
raiseMethods = raiseMethods.Concat(busRaiseMethods);
if (!raiseMethods.Any(m => m.Equals(operation.TargetMethod.OriginalDefinition, Default)))
{
// If you try to do this normally by concatenating like busRaiseMethods above

View File

@@ -19,117 +19,112 @@ public class Generator : IIncrementalGenerator
public void Initialize(IncrementalGeneratorInitializationContext initContext)
{
IncrementalValuesProvider<TypeDeclarationSyntax> dataDefinitions = initContext.SyntaxProvider.CreateSyntaxProvider(
static (node, _) => node is TypeDeclarationSyntax,
static (context, _) =>
{
var type = (TypeDeclarationSyntax) context.Node;
var symbol = (ITypeSymbol) context.SemanticModel.GetDeclaredSymbol(type)!;
return IsDataDefinition(symbol) ? type : null;
}
).Where(static type => type != null)!;
IncrementalValuesProvider<(string name, string code)?> dataDefinitions = initContext.SyntaxProvider
.CreateSyntaxProvider(
static (node, _) => node is TypeDeclarationSyntax,
static (context, _) =>
{
var type = (TypeDeclarationSyntax)context.Node;
var symbol = (ITypeSymbol)context.SemanticModel.GetDeclaredSymbol(type)!;
if (!IsDataDefinition(symbol))
return null;
return GenerateForDataDefinition(type, symbol);
}
)
.Where(static type => type != null);
var comparer = new DataDefinitionComparer();
initContext.RegisterSourceOutput(
initContext.CompilationProvider.Combine(dataDefinitions.WithComparer(comparer).Collect()),
dataDefinitions,
static (sourceContext, source) =>
{
var (compilation, declarations) = source;
var builder = new StringBuilder();
var containingTypes = new Stack<INamedTypeSymbol>();
var declarationsGenerated = new HashSet<string>();
var deltaType = compilation.GetTypeByMetadataName(ComponentDeltaInterfaceName)!;
// TODO: deduplicate based on name?
var (name, code) = source!.Value;
foreach (var declaration in declarations)
{
builder.Clear();
containingTypes.Clear();
var type = compilation.GetSemanticModel(declaration.SyntaxTree).GetDeclaredSymbol(declaration)!;
var symbolName = type
.ToDisplayString()
.Replace('<', '{')
.Replace('>', '}');
if (!declarationsGenerated.Add(symbolName))
continue;
var nonPartial = !IsPartial(declaration);
var namespaceString = type.ContainingNamespace.IsGlobalNamespace
? string.Empty
: $"namespace {type.ContainingNamespace.ToDisplayString()};";
var containingType = type.ContainingType;
while (containingType != null)
{
containingTypes.Push(containingType);
containingType = containingType.ContainingType;
}
var containingTypesStart = new StringBuilder();
var containingTypesEnd = new StringBuilder();
foreach (var parent in containingTypes)
{
var syntax = (ClassDeclarationSyntax) parent.DeclaringSyntaxReferences[0].GetSyntax();
if (!IsPartial(syntax))
{
nonPartial = true;
continue;
}
containingTypesStart.AppendLine($"{GetPartialTypeDefinitionLine(parent)}\n{{");
containingTypesEnd.AppendLine("}");
}
var definition = GetDataDefinition(type);
if (nonPartial || definition.InvalidFields)
continue;
builder.AppendLine($$"""
#nullable enable
using System;
using Robust.Shared.Analyzers;
using Robust.Shared.IoC;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Exceptions;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
#pragma warning disable CS0618 // Type or member is obsolete
#pragma warning disable CS0612 // Type or member is obsolete
#pragma warning disable CS0108 // Member hides inherited member; missing new keyword
#pragma warning disable RA0002 // Robust access analyzer
{{namespaceString}}
{{containingTypesStart}}
{{GetPartialTypeDefinitionLine(type)}} : ISerializationGenerated<{{definition.GenericTypeName}}>
{
{{GetConstructor(definition)}}
{{GetCopyMethods(definition, deltaType)}}
{{GetInstantiators(definition, deltaType)}}
}
{{containingTypesEnd}}
""");
var sourceText = CSharpSyntaxTree
.ParseText(builder.ToString())
.GetRoot()
.NormalizeWhitespace()
.ToFullString();
sourceContext.AddSource($"{symbolName}.g.cs", sourceText);
}
sourceContext.AddSource(name, code);
}
);
}
private static (string, string)? GenerateForDataDefinition(
TypeDeclarationSyntax declaration,
ITypeSymbol type)
{
var builder = new StringBuilder();
var containingTypes = new Stack<INamedTypeSymbol>();
containingTypes.Clear();
var symbolName = type
.ToDisplayString()
.Replace('<', '{')
.Replace('>', '}');
var nonPartial = !IsPartial(declaration);
var namespaceString = type.ContainingNamespace.IsGlobalNamespace
? string.Empty
: $"namespace {type.ContainingNamespace.ToDisplayString()};";
var containingType = type.ContainingType;
while (containingType != null)
{
containingTypes.Push(containingType);
containingType = containingType.ContainingType;
}
var containingTypesStart = new StringBuilder();
var containingTypesEnd = new StringBuilder();
foreach (var parent in containingTypes)
{
var syntax = (ClassDeclarationSyntax)parent.DeclaringSyntaxReferences[0].GetSyntax();
if (!IsPartial(syntax))
{
nonPartial = true;
continue;
}
containingTypesStart.AppendLine($"{GetPartialTypeDefinitionLine(parent)}\n{{");
containingTypesEnd.AppendLine("}");
}
var definition = GetDataDefinition(type);
if (nonPartial || definition.InvalidFields)
return null;
builder.AppendLine($$"""
#nullable enable
using System;
using Robust.Shared.Analyzers;
using Robust.Shared.IoC;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Exceptions;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
#pragma warning disable CS0618 // Type or member is obsolete
#pragma warning disable CS0612 // Type or member is obsolete
#pragma warning disable CS0108 // Member hides inherited member; missing new keyword
#pragma warning disable RA0002 // Robust access analyzer
{{namespaceString}}
{{containingTypesStart}}
{{GetPartialTypeDefinitionLine(type)}} : ISerializationGenerated<{{definition.GenericTypeName}}>
{
{{GetConstructor(definition)}}
{{GetCopyMethods(definition)}}
{{GetInstantiators(definition)}}
}
{{containingTypesEnd}}
""");
return ($"{symbolName}.g.cs", builder.ToString());
}
private static DataDefinition GetDataDefinition(ITypeSymbol definition)
{
var fields = new List<DataField>();
@@ -196,7 +191,7 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
return builder.ToString();
}
private static string GetCopyMethods(DataDefinition definition, ITypeSymbol deltaType)
private static string GetCopyMethods(DataDefinition definition)
{
var builder = new StringBuilder();
@@ -267,36 +262,36 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
{{baseCopy}}
""");
foreach (var @interface in InternalGetImplicitDataDefinitionInterfaces(definition.Type, true, deltaType))
foreach (var interfaceName in InternalGetImplicitDataDefinitionInterfaces(definition.Type, true))
{
var interfaceModifiers = baseType != null && baseType.AllInterfaces.Contains(@interface, SymbolEqualityComparer.Default)
var interfaceModifiers = baseType != null &&
baseType.AllInterfaces.Any(i => i.ToDisplayString() == interfaceName)
? "override "
: modifiers;
var interfaceName = @interface.ToDisplayString();
builder.AppendLine($$"""
/// <seealso cref="ISerializationManager.CopyTo"/>
[Obsolete("Use ISerializationManager.CopyTo instead")]
public {{interfaceModifiers}} void InternalCopy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
var def = ({{definition.GenericTypeName}}) target;
Copy(ref def, serialization, hookCtx, context);
target = def;
}
/// <seealso cref="ISerializationManager.CopyTo"/>
[Obsolete("Use ISerializationManager.CopyTo instead")]
public {{interfaceModifiers}} void InternalCopy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
var def = ({{definition.GenericTypeName}}) target;
Copy(ref def, serialization, hookCtx, context);
target = def;
}
/// <seealso cref="ISerializationManager.CopyTo"/>
[Obsolete("Use ISerializationManager.CopyTo instead")]
public {{interfaceModifiers}} void Copy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
InternalCopy(ref target, serialization, hookCtx, context);
}
""");
/// <seealso cref="ISerializationManager.CopyTo"/>
[Obsolete("Use ISerializationManager.CopyTo instead")]
public {{interfaceModifiers}} void Copy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
InternalCopy(ref target, serialization, hookCtx, context);
}
""");
}
return builder.ToString();
}
private static string GetInstantiators(DataDefinition definition, ITypeSymbol deltaType)
private static string GetInstantiators(DataDefinition definition)
{
var builder = new StringBuilder();
var modifiers = string.Empty;
@@ -330,27 +325,28 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
""");
}
foreach (var @interface in InternalGetImplicitDataDefinitionInterfaces(definition.Type, false, deltaType))
foreach (var interfaceName in InternalGetImplicitDataDefinitionInterfaces(definition.Type, false))
{
var interfaceName = @interface.ToDisplayString();
builder.AppendLine($$"""
{{interfaceName}} {{interfaceName}}.Instantiate()
{
return Instantiate();
}
{{interfaceName}} {{interfaceName}}.Instantiate()
{
return Instantiate();
}
{{interfaceName}} ISerializationGenerated<{{interfaceName}}>.Instantiate()
{
return Instantiate();
}
""");
{{interfaceName}} ISerializationGenerated<{{interfaceName}}>.Instantiate()
{
return Instantiate();
}
""");
}
return builder.ToString();
}
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
private static IEnumerable<ITypeSymbol> InternalGetImplicitDataDefinitionInterfaces(ITypeSymbol type, bool all, ITypeSymbol deltaType)
private static IEnumerable<string> InternalGetImplicitDataDefinitionInterfaces(
ITypeSymbol type,
bool all)
{
var symbols = GetImplicitDataDefinitionInterfaces(type, all);
@@ -368,10 +364,10 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
return symbols;
}
if (symbols.Any(x => x.ToDisplayString() == deltaType.ToDisplayString()))
if (symbols.Any(x => x == ComponentDeltaInterfaceName))
return symbols;
return symbols.Append(deltaType);
return symbols.Append(ComponentDeltaInterfaceName);
}
// TODO serveronly? do we care? who knows!!

View File

@@ -94,13 +94,13 @@ internal static class Types
return false;
}
internal static IEnumerable<ITypeSymbol> GetImplicitDataDefinitionInterfaces(ITypeSymbol type, bool all)
internal static IEnumerable<string> GetImplicitDataDefinitionInterfaces(ITypeSymbol type, bool all)
{
var interfaces = all ? type.AllInterfaces : type.Interfaces;
foreach (var @interface in interfaces)
{
if (IsImplicitDataDefinitionInterface(@interface))
yield return @interface;
yield return @interface.ToDisplayString();
}
}

View File

@@ -957,7 +957,7 @@ namespace Robust.Shared.Network
}
if (_loggerPacket.IsLogLevelEnabled(LogLevel.Verbose))
_loggerPacket.Verbose("net", $"RECV: {instance.GetType().Name} {msg.LengthBytes}");
_loggerPacket.Verbose($"RECV: {instance.GetType().Name} {msg.LengthBytes}");
try
{

View File

@@ -33,6 +33,8 @@ PLATFORM_LINUX = "linux-x64"
PLATFORM_LINUX_ARM64 = "linux-arm64"
PLATFORM_MACOS = "osx-x64"
TARGET_FRAMEWORK = "net9.0"
def main() -> None:
parser = argparse.ArgumentParser(
description="Packages the Robust.Client.WebView module for release on all platforms.")
@@ -85,7 +87,7 @@ def build_windows(skip_build: bool) -> None:
# Run a full build.
print(Fore.GREEN + "Building project for Windows x64..." + Style.RESET_ALL)
base_bin = p("Robust.Client.WebView", "bin", "Release", "net8.0")
base_bin = p("Robust.Client.WebView", "bin", "Release", TARGET_FRAMEWORK)
if not skip_build:
build_client_rid("Windows", "win-x64")
@@ -144,7 +146,7 @@ def build_linux(skip_build: bool) -> None:
# Run a full build.
print(Fore.GREEN + "Building project for Linux x64..." + Style.RESET_ALL)
base_bin = p("Robust.Client.WebView", "bin", "Release", "net8.0")
base_bin = p("Robust.Client.WebView", "bin", "Release", TARGET_FRAMEWORK)
if not skip_build:
build_client_rid("Linux", "linux-x64")