Skip to content

Commit

Permalink
Make auto comp states infer when data should be cloned (#4461)
Browse files Browse the repository at this point in the history
  • Loading branch information
DrSmugleaf authored Sep 30, 2023
1 parent 3b6adeb commit 4818c3a
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 42 deletions.
2 changes: 1 addition & 1 deletion RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Don't change the format without looking at the script!
### Breaking changes
*None yet*
* Removed cloneData from AutoNetworkedFieldAttribute. This is now automatically inferred.
### New features
Expand Down
80 changes: 53 additions & 27 deletions Robust.Shared.CompNetworkGenerator/ComponentNetworkGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using static Microsoft.CodeAnalysis.SymbolDisplayFormat;

namespace Robust.Shared.CompNetworkGenerator
{
Expand All @@ -14,23 +15,31 @@ public class ComponentNetworkGenerator : ISourceGenerator
{
private const string ClassAttributeName = "Robust.Shared.Analyzers.AutoGenerateComponentStateAttribute";
private const string MemberAttributeName = "Robust.Shared.Analyzers.AutoNetworkedFieldAttribute";

private const string GlobalEntityUidName = "global::Robust.Shared.GameObjects.EntityUid";
private const string GlobalNullableEntityUidName = "global::Robust.Shared.GameObjects.EntityUid?";

private const string GlobalEntityCoordinatesName = "global::Robust.Shared.Map.EntityCoordinates";
private const string GlobalNullableEntityCoordinatesName = "global::Robust.Shared.Map.EntityCoordinates?";

private const string GlobalEntityUidSetName = "global::System.Collections.Generic.HashSet<global::Robust.Shared.GameObjects.EntityUid>";
private const string GlobalNetEntityUidSetName = "global::System.Collections.Generic.HashSet<global::Robust.Shared.GameObjects.NetEntity>";

private const string GlobalEntityUidListName = "global::System.Collections.Generic.List<global::Robust.Shared.GameObjects.EntityUid>";
private const string GlobalNetEntityUidListName = "global::System.Collections.Generic.List<global::Robust.Shared.GameObjects.NetEntity>";

private const string GlobalDictionaryName = "global::System.Collections.Generic.Dictionary<TKey, TValue>";
private const string GlobalHashSetName = "global::System.Collections.Generic.HashSet<T>";
private const string GlobalListName = "global::System.Collections.Generic.List<T>";

private static string GenerateSource(in GeneratorExecutionContext context, INamedTypeSymbol classSymbol, CSharpCompilation comp, bool raiseAfterAutoHandle)
{
var nameSpace = classSymbol.ContainingNamespace.ToDisplayString();
var componentName = classSymbol.Name;
var stateName = $"{componentName}_AutoState";

var members = classSymbol.GetMembers();
var fields = new List<(ITypeSymbol Type, string FieldName, AttributeData Attribute)>();
var fields = new List<(ITypeSymbol Type, string FieldName)>();
var fieldAttr = comp.GetTypeByMetadataName(MemberAttributeName);

foreach (var mem in members)
Expand All @@ -47,7 +56,7 @@ private static string GenerateSource(in GeneratorExecutionContext context, IName
switch (mem)
{
case IFieldSymbol field:
fields.Add((field.Type, field.Name, attribute));
fields.Add((field.Type, field.Name));
break;
case IPropertySymbol prop:
{
Expand Down Expand Up @@ -83,7 +92,7 @@ private static string GenerateSource(in GeneratorExecutionContext context, IName
continue;
}

fields.Add((prop.Type, prop.Name, attribute));
fields.Add((prop.Type, prop.Name));
break;
}
}
Expand Down Expand Up @@ -121,9 +130,9 @@ private static string GenerateSource(in GeneratorExecutionContext context, IName
// component.Count = state.Count;
var handleStateSetters = new StringBuilder();

foreach (var (type, name, attribute) in fields)
foreach (var (type, name) in fields)
{
var typeDisplayStr = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var typeDisplayStr = type.ToDisplayString(FullyQualifiedFormat);
var nullable = type.NullableAnnotation == NullableAnnotation.Annotated;
var nullableAnnotation = nullable ? "?" : string.Empty;

Expand All @@ -150,20 +159,42 @@ private static string GenerateSource(in GeneratorExecutionContext context, IName
handleStateSetters.Append($@"
component.{name} = EnsureCoordinates<{componentName}>(state.{name}, uid);");

break;
case GlobalEntityUidSetName:
stateFields.Append($@"
public {GlobalNetEntityUidSetName} {name} = default!;");

getStateInit.Append($@"
{name} = GetNetEntitySet(component.{name}),");
handleStateSetters.Append($@"
component.{name} = EnsureEntitySet<{componentName}>(state.{name}, uid);");

break;
case GlobalEntityUidListName:
stateFields.Append($@"
public {GlobalNetEntityUidListName} {name} = default!;");

getStateInit.Append($@"
{name} = GetNetEntityList(component.{name}),");
handleStateSetters.Append($@"
component.{name} = EnsureEntityList<{componentName}>(state.{name}, uid);");

break;
default:
stateFields.Append($@"
public {typeDisplayStr} {name} = default!;");

if (attribute.ConstructorArguments[0].Value is bool val && val)
if (IsCloneType(type))
{
// get first ctor arg of the field attribute, which determines whether the field should be cloned
// (like if its a dict or list)
getStateInit.Append($@"
{name} = component.{name},");

handleStateSetters.Append($@"
if (state.{name} != null)
if (state.{name} == null)
component.{name} = null;
else
component.{name} = new(state.{name});");
}
else
Expand All @@ -175,26 +206,6 @@ private static string GenerateSource(in GeneratorExecutionContext context, IName
component.{name} = state.{name};");
}

break;
case GlobalEntityUidSetName:
stateFields.Append($@"
public {GlobalNetEntityUidSetName} {name} = default!;");

getStateInit.Append($@"
{name} = GetNetEntitySet(component.{name}),");
handleStateSetters.Append($@"
component.{name} = EnsureEntitySet<{componentName}>(state.{name}, uid);");

break;
case GlobalEntityUidListName:
stateFields.Append($@"
public {GlobalNetEntityUidListName} {name} = default!;");

getStateInit.Append($@"
{name} = GetNetEntityList(component.{name}),");
handleStateSetters.Append($@"
component.{name} = EnsureEntityList<{componentName}>(state.{name}, uid);");

break;
}
}
Expand Down Expand Up @@ -353,5 +364,20 @@ public void Initialize(GeneratorInitializationContext context)
}
context.RegisterForSyntaxNotifications(() => new NameReferenceSyntaxReceiver());
}

private static bool IsCloneType(ITypeSymbol type)
{
if (type is not INamedTypeSymbol named || !named.IsGenericType)
{
return false;
}

var constructed = named.ConstructedFrom.ToDisplayString(FullyQualifiedFormat);
return constructed switch
{
GlobalDictionaryName or GlobalHashSetName or GlobalListName => true,
_ => false
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>9</LangVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
14 changes: 0 additions & 14 deletions Robust.Shared/Analyzers/ComponentNetworkGeneratorAuxiliary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,6 @@ public AutoGenerateComponentStateAttribute(bool raiseAfterAutoHandleState = fals
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class AutoNetworkedFieldAttribute : Attribute
{
/// <summary>
/// Determines whether the data should be wrapped in a new() when setting in get/handlestate
/// e.g. for cloning collections like dictionaries or hashsets which is sometimes necessary.
/// </summary>
/// <remarks>
/// This should only be true if the type actually has a constructor that takes in itself.
/// </remarks>
[UsedImplicitly]
public bool CloneData;

public AutoNetworkedFieldAttribute(bool cloneData=false)
{
CloneData = cloneData;
}
}

/// <summary>
Expand Down

0 comments on commit 4818c3a

Please sign in to comment.