Compare commits

...

23 Commits

Author SHA1 Message Date
metalgearsloth
80f3aae30c Version: 150.0.0 2023-08-23 18:55:43 +10:00
Leon Friedrich
98b1862433 Add new spawn functions (#4280) 2023-08-23 18:54:55 +10:00
Leon Friedrich
d2311c193f Add AbstractDictionarySerializer (#4276) 2023-08-23 18:53:13 +10:00
metalgearsloth
f05ed96461 Remove FixtureId from fixtures (#4270) 2023-08-23 18:50:48 +10:00
DrSmugleaf
dc23dfaf4d Version: 149.0.1 2023-08-23 00:27:59 -07:00
DmitriyMX
62315f7c2e fix: crash client when window set maxsize (#4291) 2023-08-23 16:59:57 +10:00
DrSmugleaf
b2d121e780 Fix serialization sharing instances when copying data definitions and not assigning null when the source is null (#4295) 2023-08-22 23:59:03 -07:00
DrSmugleaf
fb4b029122 Version: 149.0.0 2023-08-22 18:07:35 -07:00
DrSmugleaf
66239d23ea Refactor serialization copying to use source generators (#4286) 2023-08-22 17:37:13 -07:00
DrSmugleaf
dbb45f1c13 Version: 148.4.0 2023-08-21 23:19:25 -07:00
Leon Friedrich
6284e16b64 Add recursive PVS overrides and remove IsOverride() (#4262) 2023-08-21 23:17:53 -07:00
DrSmugleaf
f6c55085fe Version: 148.3.0 2023-08-21 19:01:15 -07:00
DrSmugleaf
30eafd26e7 Fix test checking that Robust's and .NET's colors are equal (#4287) 2023-08-21 16:26:06 -07:00
Pieter-Jan Briers
63423d96b4 Fixes for new color PR (#4278)
Undo change to violet color

add to named color list
2023-08-21 23:06:20 +02:00
Morb
474334aff2 Make DiscordRichPresence icon CVars server-side with replication (#4272) 2023-08-21 10:44:40 +02:00
Leon Friedrich
be102f86bf Add IntegrationInstance fields for common dependencies (#4283) 2023-08-21 14:35:27 +10:00
Tom Leys
d7962c7190 Add implementation of Random.Pick(ValueList<T> ..) (#4257) 2023-08-21 13:56:18 +10:00
Leon Friedrich
7fe9385c3b Change default value of EntityLastModifiedTick from zero to one. (#4282)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2023-08-21 13:55:41 +10:00
Kara
a9d9d1348a Tile texture reload command (#4268) 2023-08-21 13:46:58 +10:00
Leon Friedrich
4eaf624555 Allow pre-startup components to be shut down. (#4281) 2023-08-21 13:45:57 +10:00
Leon Friedrich
e37c131fb4 Prevent invalid prototypes from being spawned (#4279) 2023-08-21 12:29:02 +10:00
PrPleGoo
9df4606492 Added colors (#4278) 2023-08-20 18:48:00 +02:00
Pieter-Jan Briers
3be8070274 Happy eyeballs delay can be configured. 2023-08-20 17:45:43 +02:00
194 changed files with 3636 additions and 666 deletions

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

@@ -25,4 +25,7 @@
<!-- analyzer -->
<Import Project="Robust.Analyzers.targets" Condition="'$(SkipRobustAnalyzer)' != 'true'" />
<!-- serialization generator -->
<Import Project="Robust.Serialization.Generator.targets" />
</Project>

View File

@@ -0,0 +1,5 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0">
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Serialization.Generator\Robust.Serialization.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>
</Project>

View File

@@ -54,6 +54,62 @@ END TEMPLATE-->
*None yet*
## 150.0.0
### Breaking changes
* Remove the Id field from Fixtures as the Id is already stored on FixturesComponent.
### New features
* Add AbstractDictionarySerializer for abstract classes.
* Add many new spawn functions for entities for common operations.
## 149.0.1
### Bugfixes
* Fix serialization sharing instances when copying data definitions and not assigning null when the source is null.
* Fixed resizing a window to be bigger than its set maxsize crashing the client.
## 149.0.0
### Breaking changes
* Data definitions must now be partial, their data fields must not be readonly and their data field properties must have a setter.
### Internal
* Copying data definitions through the serialization manager is now faster and consumes less memory.
## 148.4.0
### New features
* Add recursive PVS overrides and remove IsOverride()
## 148.3.0
### New features
* Happy eyeballs delay can be configured.
* Added more colors.
* Allow pre-startup components to be shut down.
* Added tile texture reload command.
* Add implementation of Random.Pick(ValueList<T> ..).
* Add IntegrationInstance fields for common dependencies.
### Bugfixes
* Prevent invalid prototypes from being spawned.
* Change default value of EntityLastModifiedTick from zero to one.
* Make DiscordRichPresence icon CVars server-side with replication.
## 148.2.0
### New features

View File

@@ -558,3 +558,6 @@ cmd-vfs_ls-help = Usage: vfs_list <path>
cmd-vfs_ls-err-args = Need exactly 1 argument.
cmd-vfs_ls-hint-path = <path>
cmd-reloadtiletextures-desc = Reloads the tile texture atlas to allow hot reloading tile sprites
cmd-reloadtiletextures-help = Usage: reloadtiletextures

View File

@@ -0,0 +1,293 @@
#nullable enable
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Robust.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
{
private const string DataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataDefinitionAttribute";
private const string ImplicitDataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.ImplicitDataDefinitionForInheritorsAttribute";
private const string DataFieldBaseNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataFieldBaseAttribute";
private static readonly DiagnosticDescriptor DataDefinitionPartialRule = new(
Diagnostics.IdDataDefinitionPartial,
"Type must be partial",
"Type {0} is a DataDefinition but is not partial.",
"Usage",
DiagnosticSeverity.Error,
true,
"Make sure to mark any type that is a data definition as partial."
);
private static readonly DiagnosticDescriptor NestedDataDefinitionPartialRule = new(
Diagnostics.IdNestedDataDefinitionPartial,
"Type must be partial",
"Type {0} contains nested data definition {1} but is not partial.",
"Usage",
DiagnosticSeverity.Error,
true,
"Make sure to mark any type containing a nested data definition as partial."
);
private static readonly DiagnosticDescriptor DataFieldWritableRule = new(
Diagnostics.IdDataFieldWritable,
"Data field must not be readonly",
"Data field {0} in data definition {1} is readonly.",
"Usage",
DiagnosticSeverity.Error,
true,
"Make sure to remove the readonly modifier."
);
private static readonly DiagnosticDescriptor DataFieldPropertyWritableRule = new(
Diagnostics.IdDataFieldPropertyWritable,
"Data field property must have a setter",
"Data field property {0} in data definition {1} does not have a setter.",
"Usage",
DiagnosticSeverity.Error,
true,
"Make sure to add a setter."
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule
);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.ClassDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.StructDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.RecordDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.RecordStructDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.InterfaceDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeDataField, SyntaxKind.FieldDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeDataFieldProperty, SyntaxKind.PropertyDeclaration);
}
private void AnalyzeDataDefinition(SyntaxNodeAnalysisContext context)
{
if (context.Node is not TypeDeclarationSyntax declaration)
return;
var type = context.SemanticModel.GetDeclaredSymbol(declaration)!;
if (!IsDataDefinition(type))
return;
if (!IsPartial(declaration))
{
context.ReportDiagnostic(Diagnostic.Create(DataDefinitionPartialRule, declaration.Keyword.GetLocation(), type.Name));
}
var containingType = type.ContainingType;
while (containingType != null)
{
var containingTypeDeclaration = (TypeDeclarationSyntax) containingType.DeclaringSyntaxReferences[0].GetSyntax();
if (!IsPartial(containingTypeDeclaration))
{
context.ReportDiagnostic(Diagnostic.Create(NestedDataDefinitionPartialRule, containingTypeDeclaration.Keyword.GetLocation(), containingType.Name, type.Name));
}
containingType = containingType.ContainingType;
}
}
private void AnalyzeDataField(SyntaxNodeAnalysisContext context)
{
if (context.Node is not FieldDeclarationSyntax field)
return;
var typeDeclaration = field.FirstAncestorOrSelf<TypeDeclarationSyntax>();
if (typeDeclaration == null)
return;
var type = context.SemanticModel.GetDeclaredSymbol(typeDeclaration)!;
if (!IsDataDefinition(type))
return;
foreach (var variable in field.Declaration.Variables)
{
var fieldSymbol = context.SemanticModel.GetDeclaredSymbol(variable);
if (fieldSymbol == null)
continue;
if (IsReadOnlyDataField(type, fieldSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
}
}
}
private void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context)
{
if (context.Node is not PropertyDeclarationSyntax property)
return;
var typeDeclaration = property.FirstAncestorOrSelf<TypeDeclarationSyntax>();
if (typeDeclaration == null)
return;
var type = context.SemanticModel.GetDeclaredSymbol(typeDeclaration)!;
if (!IsDataDefinition(type) || type.IsRecord || type.IsValueType)
return;
var propertySymbol = context.SemanticModel.GetDeclaredSymbol(property);
if (propertySymbol == null)
return;
if (IsReadOnlyDataField(type, propertySymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
}
}
private static bool IsReadOnlyDataField(ITypeSymbol type, ISymbol field)
{
if (!IsDataField(field, out _, out _))
return false;
return IsReadOnlyMember(type, field);
}
private static bool IsPartial(TypeDeclarationSyntax type)
{
return type.Modifiers.IndexOf(SyntaxKind.PartialKeyword) != -1;
}
private static bool IsDataDefinition(ITypeSymbol? type)
{
if (type == null)
return false;
return HasAttribute(type, DataDefinitionNamespace) ||
IsImplicitDataDefinition(type);
}
private static bool IsDataField(ISymbol member, out ITypeSymbol type, out AttributeData attribute)
{
// TODO data records and other attributes
if (member is IFieldSymbol field)
{
foreach (var attr in field.GetAttributes())
{
if (attr.AttributeClass != null && Inherits(attr.AttributeClass, DataFieldBaseNamespace))
{
type = field.Type;
attribute = attr;
return true;
}
}
}
else if (member is IPropertySymbol property)
{
foreach (var attr in property.GetAttributes())
{
if (attr.AttributeClass != null && Inherits(attr.AttributeClass, DataFieldBaseNamespace))
{
type = property.Type;
attribute = attr;
return true;
}
}
}
type = null!;
attribute = null!;
return false;
}
private static bool Inherits(ITypeSymbol type, string parent)
{
foreach (var baseType in GetBaseTypes(type))
{
if (baseType.ToDisplayString() == parent)
return true;
}
return false;
}
private static bool IsReadOnlyMember(ITypeSymbol type, ISymbol member)
{
if (member is IFieldSymbol field)
{
return field.IsReadOnly;
}
else if (member is IPropertySymbol property)
{
if (property.SetMethod == null)
return true;
if (property.SetMethod.IsInitOnly)
return type.IsReferenceType;
return false;
}
return false;
}
private static bool HasAttribute(ITypeSymbol type, string attributeName)
{
foreach (var attribute in type.GetAttributes())
{
if (attribute.AttributeClass?.ToDisplayString() == attributeName)
return true;
}
return false;
}
private static bool IsImplicitDataDefinition(ITypeSymbol type)
{
if (HasAttribute(type, ImplicitDataDefinitionNamespace))
return true;
foreach (var baseType in GetBaseTypes(type))
{
if (HasAttribute(baseType, ImplicitDataDefinitionNamespace))
return true;
}
foreach (var @interface in type.AllInterfaces)
{
if (IsImplicitDataDefinitionInterface(@interface))
return true;
}
return false;
}
private static bool IsImplicitDataDefinitionInterface(ITypeSymbol @interface)
{
if (HasAttribute(@interface, ImplicitDataDefinitionNamespace))
return true;
foreach (var subInterface in @interface.AllInterfaces)
{
if (HasAttribute(subInterface, ImplicitDataDefinitionNamespace))
return true;
}
return false;
}
private static IEnumerable<ITypeSymbol> GetBaseTypes(ITypeSymbol type)
{
var baseType = type.BaseType;
while (baseType != null)
{
yield return baseType;
baseType = baseType.BaseType;
}
}
}

View File

@@ -0,0 +1,168 @@
#nullable enable
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxKind;
using static Robust.Analyzers.Diagnostics;
namespace Robust.Analyzers;
[ExportCodeFixProvider(LanguageNames.CSharp)]
public sealed class DefinitionFixer : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable
);
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
foreach (var diagnostic in context.Diagnostics)
{
switch (diagnostic.Id)
{
case IdDataDefinitionPartial:
return RegisterPartialTypeFix(context, diagnostic);
case IdNestedDataDefinitionPartial:
return RegisterPartialTypeFix(context, diagnostic);
case IdDataFieldWritable:
return RegisterDataFieldFix(context, diagnostic);
case IdDataFieldPropertyWritable:
return RegisterDataFieldPropertyFix(context, diagnostic);
}
}
return Task.CompletedTask;
}
public override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}
private static async Task RegisterPartialTypeFix(CodeFixContext context, Diagnostic diagnostic)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
var span = diagnostic.Location.SourceSpan;
var token = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType<TypeDeclarationSyntax>().First();
if (token == null)
return;
context.RegisterCodeFix(CodeAction.Create(
"Make type partial",
c => MakeDataDefinitionPartial(context.Document, token, c),
"Make type partial"
), diagnostic);
}
private static async Task<Document> MakeDataDefinitionPartial(Document document, TypeDeclarationSyntax declaration, CancellationToken cancellation)
{
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
var token = SyntaxFactory.Token(PartialKeyword);
var newDeclaration = declaration.AddModifiers(token);
root = root!.ReplaceNode(declaration, newDeclaration);
return document.WithSyntaxRoot(root);
}
private static async Task RegisterDataFieldFix(CodeFixContext context, Diagnostic diagnostic)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
var span = diagnostic.Location.SourceSpan;
var field = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType<FieldDeclarationSyntax>().FirstOrDefault();
if (field == null)
return;
context.RegisterCodeFix(CodeAction.Create(
"Make data field writable",
c => MakeFieldWritable(context.Document, field, c),
"Make data field writable"
), diagnostic);
}
private static async Task RegisterDataFieldPropertyFix(CodeFixContext context, Diagnostic diagnostic)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
var span = diagnostic.Location.SourceSpan;
var property = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType<PropertyDeclarationSyntax>().FirstOrDefault();
if (property == null)
return;
context.RegisterCodeFix(CodeAction.Create(
"Make data field writable",
c => MakePropertyWritable(context.Document, property, c),
"Make data field writable"
), diagnostic);
}
private static async Task<Document> MakeFieldWritable(Document document, FieldDeclarationSyntax declaration, CancellationToken cancellation)
{
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
var token = declaration.Modifiers.First(t => t.IsKind(ReadOnlyKeyword));
var newDeclaration = declaration.WithModifiers(declaration.Modifiers.Remove(token));
root = root!.ReplaceNode(declaration, newDeclaration);
return document.WithSyntaxRoot(root);
}
private static async Task<Document> MakePropertyWritable(Document document, PropertyDeclarationSyntax declaration, CancellationToken cancellation)
{
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
var newDeclaration = declaration;
var privateSet = newDeclaration
.AccessorList?
.Accessors
.FirstOrDefault(s => s.IsKind(SetAccessorDeclaration) || s.IsKind(InitAccessorDeclaration));
if (newDeclaration.AccessorList != null && privateSet != null)
{
newDeclaration = newDeclaration.WithAccessorList(
newDeclaration.AccessorList.WithAccessors(
newDeclaration.AccessorList.Accessors.Remove(privateSet)
)
);
}
AccessorDeclarationSyntax setter;
if (declaration.Modifiers.Any(m => m.IsKind(PrivateKeyword)))
{
setter = SyntaxFactory.AccessorDeclaration(
SetAccessorDeclaration,
default,
default,
SyntaxFactory.Token(SetKeyword),
default,
default,
SyntaxFactory.Token(SemicolonToken)
);
}
else
{
setter = SyntaxFactory.AccessorDeclaration(
SetAccessorDeclaration,
default,
SyntaxFactory.TokenList(SyntaxFactory.Token(PrivateKeyword)),
SyntaxFactory.Token(SetKeyword),
default,
default,
SyntaxFactory.Token(SemicolonToken)
);
}
newDeclaration = newDeclaration.AddAccessorListAccessors(setter);
root = root!.ReplaceNode(declaration, newDeclaration);
return document.WithSyntaxRoot(root);
}
}

View File

@@ -21,6 +21,10 @@ public static class Diagnostics
public const string IdValueEventSubscribedByRef = "RA0014";
public const string IdByRefEventRaisedByValue = "RA0015";
public const string IdValueEventRaisedByRef = "RA0016";
public const string IdDataDefinitionPartial = "RA0017";
public const string IdNestedDataDefinitionPartial = "RA0018";
public const string IdDataFieldWritable = "RA0019";
public const string IdDataFieldPropertyWritable = "RA0020";
public static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");

View File

@@ -8,7 +8,7 @@ using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.EntityManager;
[Virtual]
public class AddRemoveComponentBenchmark
public partial class AddRemoveComponentBenchmark
{
private ISimulation _simulation = default!;
private IEntityManager _entityManager = default!;
@@ -48,7 +48,7 @@ public class AddRemoveComponentBenchmark
}
[ComponentProtoName("A")]
public sealed class A : Component
public sealed partial class A : Component
{
}
}

View File

@@ -6,7 +6,7 @@ using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.EntityManager;
public class ComponentIteratorBenchmark
public partial class ComponentIteratorBenchmark
{
private ISimulation _simulation = default!;
private IEntityManager _entityManager = default!;
@@ -69,7 +69,7 @@ public class ComponentIteratorBenchmark
}
[ComponentProtoName("A")]
public sealed class A : Component
public sealed partial class A : Component
{
}
}

View File

@@ -1,4 +1,3 @@
using System;
using BenchmarkDotNet.Attributes;
using JetBrains.Annotations;
using Robust.Shared.Analyzers;
@@ -9,7 +8,7 @@ using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.EntityManager;
[Virtual]
public class GetComponentBenchmark
public partial class GetComponentBenchmark
{
private ISimulation _simulation = default!;
private IEntityManager _entityManager = default!;
@@ -55,7 +54,7 @@ public class GetComponentBenchmark
}
[ComponentProtoName("A")]
public sealed class A : Component
public sealed partial class A : Component
{
}
}

View File

@@ -8,7 +8,7 @@ using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.EntityManager;
[Virtual]
public class SpawnDeleteEntityBenchmark
public partial class SpawnDeleteEntityBenchmark
{
private ISimulation _simulation = default!;
private IEntityManager _entityManager = default!;
@@ -56,7 +56,7 @@ public class SpawnDeleteEntityBenchmark
}
[ComponentProtoName("A")]
public sealed class A : Component
public sealed partial class A : Component
{
}
}

View File

@@ -5,9 +5,9 @@ namespace Robust.Benchmarks.Serialization.Definitions
{
[DataDefinition]
[Virtual]
public class DataDefinitionWithString
public partial class DataDefinitionWithString
{
[DataField("string")]
public string StringField { get; init; } = default!;
public string StringField { get; set; } = default!;
}
}

View File

@@ -3,9 +3,9 @@
namespace Robust.Benchmarks.Serialization.Definitions
{
[DataDefinition]
public sealed class SealedDataDefinitionWithString
public sealed partial class SealedDataDefinitionWithString
{
[DataField("string")]
public string StringField { get; init; } = default!;
public string StringField { get; private set; } = default!;
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
@@ -10,8 +11,7 @@ namespace Robust.Benchmarks.Serialization.Definitions
/// Arbitrarily large data definition for benchmarks.
/// Taken from content.
/// </summary>
[Prototype("seed")]
public sealed class SeedDataDefinition : IPrototype
public sealed partial class SeedDataDefinition : Component
{
public const string Prototype = @"
- type: seed
@@ -106,7 +106,7 @@ namespace Robust.Benchmarks.Serialization.Definitions
}
[DataDefinition]
public struct SeedChemQuantity
public partial struct SeedChemQuantity
{
[DataField("Min")]
public int Min;

View File

@@ -68,6 +68,7 @@ namespace Robust.Client
deps.Register<IComponentFactory, ComponentFactory>();
deps.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
deps.Register<IClydeTileDefinitionManager, ClydeTileDefinitionManager>();
deps.Register<ClydeTileDefinitionManager, ClydeTileDefinitionManager>();
deps.Register<GameController, GameController>();
deps.Register<IGameController, GameController>();
deps.Register<IGameControllerInternal, GameController>();

View File

@@ -6,7 +6,7 @@ using Robust.Shared.Physics;
namespace Robust.Client.ComponentTrees;
[RegisterComponent]
public sealed class LightTreeComponent: Component, IComponentTreeComponent<PointLightComponent>
public sealed partial class LightTreeComponent: Component, IComponentTreeComponent<PointLightComponent>
{
public DynamicTree<ComponentTreeEntry<PointLightComponent>> Tree { get; set; } = default!;
}

View File

@@ -6,7 +6,7 @@ using Robust.Shared.Physics;
namespace Robust.Client.ComponentTrees;
[RegisterComponent]
public sealed class SpriteTreeComponent: Component, IComponentTreeComponent<SpriteComponent>
public sealed partial class SpriteTreeComponent: Component, IComponentTreeComponent<SpriteComponent>
{
public DynamicTree<ComponentTreeEntry<SpriteComponent>> Tree { get; set; } = default!;
}

View File

@@ -11,7 +11,7 @@ namespace Robust.Client.GameObjects
/// Plays back <see cref="Animation"/>s on entities.
/// </summary>
[RegisterComponent]
public sealed class AnimationPlayerComponent : Component
public sealed partial class AnimationPlayerComponent : Component
{
// TODO: Give this component a friend someday. Way too much content shit to change atm ._.

View File

@@ -11,7 +11,7 @@ namespace Robust.Client.GameObjects;
/// </summary>
[RegisterComponent]
[Access(typeof(GenericVisualizerSystem))]
public sealed class GenericVisualizerComponent : Component
public sealed partial class GenericVisualizerComponent : Component
{
/// <summary>
/// This is a nested dictionary that maps appearance data keys -> sprite layer keys -> appearance data values -> layer data.

View File

@@ -10,7 +10,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[RegisterComponent, ComponentReference(typeof(SharedEyeComponent))]
public sealed class EyeComponent : SharedEyeComponent
public sealed partial class EyeComponent : SharedEyeComponent
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;

View File

@@ -16,8 +16,8 @@ namespace Robust.Client.GameObjects;
/// updated.
/// </remarks>
[RegisterComponent]
public sealed class IconComponent : Component
public sealed partial class IconComponent : Component
{
[IncludeDataField]
public readonly SpriteSpecifier.Rsi Icon = default!;
public SpriteSpecifier.Rsi Icon = default!;
}

View File

@@ -9,7 +9,7 @@ namespace Robust.Client.GameObjects
/// Defines data fields used in the <see cref="InputSystem"/>.
/// </summary>
[RegisterComponent]
public sealed class InputComponent : Component
public sealed partial class InputComponent : Component
{
/// <summary>
/// The context that will be made active for a client that attaches to this entity.

View File

@@ -12,7 +12,7 @@ namespace Robust.Client.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(SharedPointLightComponent))]
public sealed class PointLightComponent : SharedPointLightComponent, IComponentTreeEntry<PointLightComponent>
public sealed partial class PointLightComponent : SharedPointLightComponent, IComponentTreeEntry<PointLightComponent>
{
public EntityUid? TreeUid { get; set; }

View File

@@ -4,7 +4,7 @@ using Robust.Shared.Maths;
namespace Robust.Client.GameObjects
{
public interface IRenderableComponent : IComponent
public partial interface IRenderableComponent : IComponent
{
int DrawDepth { get; set; }
float Bottom { get; }

View File

@@ -32,7 +32,7 @@ using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteS
namespace Robust.Client.GameObjects
{
[RegisterComponent]
public sealed class SpriteComponent : Component, IComponentDebug, ISerializationHooks, IComponentTreeEntry<SpriteComponent>, IAnimationProperties
public sealed partial class SpriteComponent : Component, IComponentDebug, ISerializationHooks, IComponentTreeEntry<SpriteComponent>, IAnimationProperties
{
[Dependency] private readonly IResourceCache resourceCache = default!;
[Dependency] private readonly IPrototypeManager prototypes = default!;

View File

@@ -7,7 +7,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[RegisterComponent, ComponentReference(typeof(SharedUserInterfaceComponent))]
public sealed class ClientUserInterfaceComponent : SharedUserInterfaceComponent
public sealed partial class ClientUserInterfaceComponent : SharedUserInterfaceComponent
{
[ViewVariables]
internal readonly Dictionary<Enum, PrototypeData> _interfaces = new();

View File

@@ -81,7 +81,7 @@ namespace Robust.Client.GameObjects
while (chunkEnumerator.MoveNext(out var chunk))
{
foreach (var fixture in chunk.Fixtures)
foreach (var fixture in chunk.Fixtures.Values)
{
var poly = (PolygonShape) fixture.Shape;

View File

@@ -225,7 +225,7 @@ namespace Robust.Client.Graphics
}
[DataDefinition]
public struct StencilParameters
public partial struct StencilParameters
{
public StencilParameters()
{

View File

@@ -21,7 +21,7 @@ namespace Robust.Client.Graphics
{
[ViewVariables]
[IdDataField]
public string ID { get; } = default!;
public string ID { get; private set; } = default!;
[ViewVariables] private ShaderKind Kind;
[ViewVariables] private Dictionary<string, object>? _params;
@@ -95,19 +95,19 @@ namespace Robust.Client.Graphics
}
[DataField("kind", required: true)]
private readonly string _rawKind = default!;
private string _rawKind = default!;
[DataField("path")]
private readonly ResPath? _path;
private ResPath? _path;
[DataField("params")]
private readonly Dictionary<string, string>? _paramMapping;
private Dictionary<string, string>? _paramMapping;
[DataField("light_mode")]
private readonly string? _rawMode;
private string? _rawMode;
[DataField("blend_mode")]
private readonly string? _rawBlendMode;
private string? _rawBlendMode;
void ISerializationHooks.AfterDeserialization()
{

View File

@@ -4,7 +4,7 @@ using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Client.Input
{
[DataDefinition]
public sealed class KeyBindingRegistration
public sealed partial class KeyBindingRegistration
{
[DataField("function")]
public BoundKeyFunction Function;

View File

@@ -3,13 +3,16 @@ using System.Collections.Generic;
using System.Linq;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Map;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Toolshed;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -52,8 +55,11 @@ namespace Robust.Client.Map
_genTextureAtlas();
}
private void _genTextureAtlas()
internal void _genTextureAtlas()
{
_tileRegions.Clear();
_tileTextureAtlas = null;
var defList = TileDefs.Where(t => t.Sprite != null).ToList();
// If there are no tile definitions, we do nothing.
@@ -144,4 +150,17 @@ namespace Robust.Client.Map
_tileTextureAtlas = Texture.LoadFromImage(sheet, "Tile Atlas");
}
}
public sealed class ReloadTileTexturesCommand : LocalizedCommands
{
[Dependency] private readonly ClydeTileDefinitionManager _tile = default!;
public override string Command => "reloadtiletextures";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_tile._genTextureAtlas();
}
}
}

View File

@@ -6,6 +6,6 @@ namespace Robust.Client.Physics;
/// Simple component used to tag entities that have physics prediction enabled.
/// </summary>
[RegisterComponent]
public sealed class PredictedPhysicsComponent : Component
public sealed partial class PredictedPhysicsComponent : Component
{
}

View File

@@ -715,7 +715,7 @@ namespace Robust.Client.UserInterface
maxH = MathHelper.Clamp(maxConstraint, minH, maxH);
minConstraint = float.IsNaN(setH) ? 0 : setH;
minH = MathHelper.Clamp(maxH, minConstraint, minH);
minH = MathHelper.Clamp(minConstraint, minH, maxH);
return new Vector2(
Math.Clamp(avail.X, minW, maxW),

View File

@@ -8,8 +8,8 @@ namespace Robust.Client.UserInterface.RichText;
public sealed class FontPrototype : IPrototype
{
[IdDataField]
public string ID { get; } = default!;
public string ID { get; private set; } = default!;
[DataField("path", required: true)]
public ResPath Path { get; } = default!;
public ResPath Path { get; private set; } = default!;
}

View File

@@ -27,7 +27,7 @@ public sealed class UITheme : IPrototype
[ViewVariables]
[IdDataField]
public string ID { get; } = default!;
public string ID { get; private set; } = default!;
[DataField("path")]
private ResPath _path;
@@ -70,7 +70,7 @@ public sealed class UITheme : IPrototype
if (!texturePath.EndsWith(".png"))
texturePath = $"{texturePath}.png";
var resPath = new ResPath(texturePath);
if (resPath.IsRelative)
{

View File

@@ -0,0 +1,6 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
namespace Robust.Serialization.Generator;
public sealed record DataDefinition(ITypeSymbol Type, string GenericTypeName, List<DataField> Fields, bool HasHooks, bool InvalidFields);

View File

@@ -0,0 +1,17 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Robust.Serialization.Generator;
public sealed class DataDefinitionComparer : IEqualityComparer<TypeDeclarationSyntax>
{
public bool Equals(TypeDeclarationSyntax x, TypeDeclarationSyntax y)
{
return x.Equals(y);
}
public int GetHashCode(TypeDeclarationSyntax type)
{
return type.GetHashCode();
}
}

View File

@@ -0,0 +1,14 @@
using Microsoft.CodeAnalysis;
namespace Robust.Serialization.Generator;
public sealed record DataField(
ISymbol Symbol,
ITypeSymbol Type,
(INamedTypeSymbol Serializer, CustomSerializerType Type)? CustomSerializer);
public enum CustomSerializerType
{
Copier,
CopyCreator
}

View File

@@ -0,0 +1,564 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Robust.Serialization.Generator.CustomSerializerType;
using static Robust.Serialization.Generator.Types;
namespace Robust.Serialization.Generator;
[Generator]
public class Generator : IIncrementalGenerator
{
private const string TypeCopierInterfaceNamespace = "Robust.Shared.Serialization.TypeSerializers.Interfaces.ITypeCopier";
private const string TypeCopyCreatorInterfaceNamespace = "Robust.Shared.Serialization.TypeSerializers.Interfaces.ITypeCopyCreator";
private const string SerializationHooksNamespace = "Robust.Shared.Serialization.ISerializationHooks";
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)!;
var comparer = new DataDefinitionComparer();
initContext.RegisterSourceOutput(
initContext.CompilationProvider.Combine(dataDefinitions.WithComparer(comparer).Collect()),
static (sourceContext, source) =>
{
var (compilation, declarations) = source;
var builder = new StringBuilder();
var containingTypes = new Stack<INamedTypeSymbol>();
foreach (var declaration in declarations)
{
builder.Clear();
containingTypes.Clear();
var type = compilation.GetSemanticModel(declaration.SyntaxTree).GetDeclaredSymbol(declaration)!;
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
{{namespaceString}}
{{containingTypesStart}}
[RobustAutoGenerated]
{{GetPartialTypeDefinitionLine(type)}} : ISerializationGenerated<{{definition.GenericTypeName}}>
{
{{GetConstructor(definition)}}
{{GetCopyMethods(definition)}}
{{GetInstantiators(definition)}}
}
{{containingTypesEnd}}
""");
var symbolName = type
.ToDisplayString()
.Replace('<', '{')
.Replace('>', '}');
var sourceText = CSharpSyntaxTree
.ParseText(builder.ToString())
.GetRoot()
.NormalizeWhitespace()
.ToFullString();
sourceContext.AddSource($"{symbolName}.g.cs", sourceText);
}
}
);
}
private static DataDefinition GetDataDefinition(ITypeSymbol definition)
{
var fields = new List<DataField>();
var invalidFields = false;
foreach (var member in definition.GetMembers())
{
if (member is not IFieldSymbol && member is not IPropertySymbol)
continue;
if (member.IsStatic)
continue;
if (IsDataField(member, out var type, out var attribute))
{
if (attribute.ConstructorArguments.FirstOrDefault(arg => arg.Kind == TypedConstantKind.Type).Value is INamedTypeSymbol customSerializer)
{
if (ImplementsInterface(customSerializer, TypeCopierInterfaceNamespace))
{
fields.Add(new DataField(member, type, (customSerializer, Copier)));
continue;
}
else if (ImplementsInterface(customSerializer, TypeCopyCreatorInterfaceNamespace))
{
fields.Add(new DataField(member, type, (customSerializer, CopyCreator)));
continue;
}
}
fields.Add(new DataField(member, type, null));
if (IsReadOnlyMember(definition, type))
{
invalidFields = true;
}
}
}
var typeName = GetGenericTypeName(definition);
var hasHooks = ImplementsInterface(definition, SerializationHooksNamespace);
return new DataDefinition(definition, typeName, fields, hasHooks, invalidFields);
}
private static string GetConstructor(DataDefinition definition)
{
if (definition.Type.TypeKind == TypeKind.Interface)
return string.Empty;
var builder = new StringBuilder();
if (NeedsEmptyConstructor(definition.Type))
{
builder.AppendLine($$"""
// Implicit constructor
#pragma warning disable CS8618
public {{definition.Type.Name}}()
#pragma warning enable CS8618
{
}
""");
}
return builder.ToString();
}
private static string GetCopyMethods(DataDefinition definition)
{
var builder = new StringBuilder();
var modifiers = IsVirtualClass(definition.Type) ? "virtual " : string.Empty;
var baseCall = string.Empty;
string baseCopy;
var baseType = definition.Type.BaseType;
if (baseType != null && IsDataDefinition(definition.Type.BaseType))
{
var baseName = baseType.ToDisplayString();
baseCall = $"""
var definitionCast = ({baseName}) target;
base.InternalCopy(ref definitionCast, serialization, hookCtx, context);
target = ({definition.GenericTypeName}) definitionCast;
""";
baseCopy = $$"""
public override void Copy(ref {{baseName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
var cast = ({{definition.GenericTypeName}}) target;
Copy(ref cast, serialization, hookCtx, context);
target = cast!;
}
public override void Copy(ref object target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
var cast = ({{definition.GenericTypeName}}) target;
Copy(ref cast, serialization, hookCtx, context);
target = cast!;
}
""";
}
else
{
baseCopy = $$"""
public {{modifiers}} void Copy(ref object target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
var cast = ({{definition.GenericTypeName}}) target;
Copy(ref cast, serialization, hookCtx, context);
target = cast!;
}
""";
}
builder.AppendLine($$"""
public {{modifiers}} void InternalCopy(ref {{definition.GenericTypeName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
{{baseCall}}
{{CopyDataFields(definition)}}
}
public {{modifiers}} void Copy(ref {{definition.GenericTypeName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
InternalCopy(ref target, serialization, hookCtx, context);
}
{{baseCopy}}
""");
foreach (var @interface in GetImplicitDataDefinitionInterfaces(definition.Type, true))
{
var interfaceModifiers = baseType != null && baseType.AllInterfaces.Contains(@interface, SymbolEqualityComparer.Default)
? "override "
: modifiers;
var interfaceName = @interface.ToDisplayString();
builder.AppendLine($$"""
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;
}
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)
{
var builder = new StringBuilder();
var modifiers = string.Empty;
if (definition.Type.BaseType is { } baseType && IsDataDefinition(baseType))
modifiers = "override ";
else if (IsVirtualClass(definition.Type))
modifiers = "virtual ";
if (definition.Type.IsAbstract)
{
// TODO make abstract once data definitions are forced to be partial
builder.AppendLine($$"""
public {{modifiers}} {{definition.GenericTypeName}} Instantiate()
{
throw new NotImplementedException();
}
""");
}
else
{
builder.AppendLine($$"""
public {{modifiers}} {{definition.GenericTypeName}} Instantiate()
{
return new {{definition.GenericTypeName}}();
}
""");
}
foreach (var @interface in GetImplicitDataDefinitionInterfaces(definition.Type, false))
{
var interfaceName = @interface.ToDisplayString();
builder.AppendLine($$"""
{{interfaceName}} {{interfaceName}}.Instantiate()
{
return Instantiate();
}
{{interfaceName}} ISerializationGenerated<{{interfaceName}}>.Instantiate()
{
return Instantiate();
}
""");
}
return builder.ToString();
}
// TODO serveronly? do we care? who knows!!
private static StringBuilder CopyDataFields(DataDefinition definition)
{
var builder = new StringBuilder();
builder.AppendLine($"""
if (serialization.TryCustomCopy(this, ref target, hookCtx, {definition.HasHooks.ToString().ToLower()}, context))
return;
""");
var structCopier = new StringBuilder();
foreach (var field in definition.Fields)
{
var type = field.Type;
var typeName = type.ToDisplayString();
if (IsMultidimensionalArray(type))
{
typeName = typeName.Replace("*", "");
}
var isNullableValueType = IsNullableValueType(type);
var nonNullableTypeName = type.WithNullableAnnotation(NullableAnnotation.None).ToDisplayString();
if (isNullableValueType)
{
nonNullableTypeName = typeName.Substring(0, typeName.Length - 1);
}
var isClass = type.IsReferenceType || type.SpecialType == SpecialType.System_String;
var isNullable = type.NullableAnnotation == NullableAnnotation.Annotated;
var nullableOverride = isClass && !isNullable ? ", true" : string.Empty;
var name = field.Symbol.Name;
var tempVarName = $"{name}Temp";
var nullableValue = isNullableValueType ? ".Value" : string.Empty;
var nullNotAllowed = isClass && !isNullable;
if (field.CustomSerializer is { Serializer: var serializer, Type: var serializerType })
{
if (nullNotAllowed)
{
builder.AppendLine($$"""
if ({{name}} == null)
{
throw new NullNotAllowedException();
}
""");
}
builder.AppendLine($$"""
{{typeName}} {{tempVarName}} = default!;
""");
if (isNullable || isNullableValueType)
{
builder.AppendLine($$"""
if ({{name}} == null)
{
{{tempVarName}} = null!;
}
else
{
""");
}
var serializerName = serializer.ToDisplayString();
switch (serializerType)
{
case Copier:
CopyToCustom(
builder,
nonNullableTypeName,
serializerName,
tempVarName,
name,
isNullable,
isClass,
isNullableValueType
);
break;
case CopyCreator:
CreateCopyCustom(
builder,
name,
tempVarName,
nonNullableTypeName,
serializerName,
nullableValue,
nullableOverride
);
break;
}
if (isNullable || isNullableValueType)
{
builder.AppendLine("}");
}
if (definition.Type.IsValueType)
{
structCopier.AppendLine($"{name} = {tempVarName}!,");
}
else
{
builder.AppendLine($"target.{name} = {tempVarName}!;");
}
}
else
{
builder.AppendLine($$"""
{{typeName}} {{tempVarName}} = default!;
""");
if (nullNotAllowed)
{
builder.AppendLine($$"""
if ({{name}} == null)
{
throw new NullNotAllowedException();
}
""");
}
var instantiator = string.Empty;
if (IsDataDefinition(type))
{
instantiator = $"{tempVarName} = {name}.Instantiate();";
}
else if (!type.IsAbstract &&
HasEmptyPublicConstructor(type) &&
(type.IsReferenceType || IsNullableType(type)))
{
instantiator = $"{tempVarName} = new();";
}
var hasHooks = ImplementsInterface(type, SerializationHooksNamespace) || !type.IsSealed;
builder.AppendLine($$"""
if (!serialization.TryCustomCopy(this.{{name}}, ref {{tempVarName}}, hookCtx, {{hasHooks.ToString().ToLower()}}, context))
{
""");
if (CanBeCopiedByValue(field.Symbol, field.Type))
{
builder.AppendLine($"{tempVarName} = {name};");
}
else if (IsDataDefinition(type) && !type.IsAbstract &&
type is not INamedTypeSymbol { TypeKind: TypeKind.Interface })
{
var nullability = type.IsValueType ? string.Empty : "?";
var nullable = !type.IsValueType || IsNullableType(type);
if (nullable)
{
builder.AppendLine($$"""
if ({{name}} == null)
{
{{tempVarName}} = null!;
}
else
{
""");
}
builder.AppendLine($$"""
{{instantiator}}
{{name}}{{nullability}}.Copy(ref {{tempVarName}}, serialization, hookCtx, context);
""");
if (nullable)
{
builder.AppendLine("}");
}
}
else
{
builder.AppendLine($"{tempVarName} = serialization.CreateCopy({name}, hookCtx, context);");
}
builder.AppendLine("}");
if (definition.Type.IsValueType)
{
structCopier.AppendLine($"{name} = {tempVarName}!,");
}
else
{
builder.AppendLine($"target.{name} = {tempVarName}!;");
}
}
}
if (definition.Type.IsValueType)
{
builder.AppendLine($$"""
target = target with
{
{{structCopier}}
};
""");
}
return builder;
}
private static void CopyToCustom(
StringBuilder builder,
string typeName,
string serializerName,
string tempVarName,
string varName,
bool isNullable,
bool isClass,
bool isNullableValueType)
{
var newTemp = isNullable && isClass ? $"{tempVarName} ??= new();" : string.Empty;
var nullableOverride = isClass ? ", true" : string.Empty;
var nullableValue = isNullableValueType ? ".Value" : string.Empty;
var nonNullableTypeName = typeName.EndsWith("?") ? typeName.Substring(0, typeName.Length - 1) : typeName;
builder.AppendLine($$"""
{{nonNullableTypeName}} {{tempVarName}}CopyTo = default!;
{{newTemp}}
serialization.CopyTo<{{typeName}}, {{serializerName}}>(this.{{varName}}{{nullableValue}}, ref {{tempVarName}}CopyTo, hookCtx, context{{nullableOverride}});
{{tempVarName}} = {{tempVarName}}CopyTo;
""");
}
private static void CreateCopyCustom(
StringBuilder builder,
string varName,
string tempVarName,
string nonNullableTypeName,
string serializerName,
string nullableValue,
string nullableOverride)
{
builder.AppendLine($$"""
{{tempVarName}} = serialization.CreateCopy<{{nonNullableTypeName}}, {{serializerName}}>(this.{{varName}}{{nullableValue}}, hookCtx, context{{nullableOverride}});
""");
}
}

View File

@@ -0,0 +1,8 @@
// ReSharper disable once CheckNamespace
namespace System.Runtime.CompilerServices;
// Need this to be able to define records in the project
internal static class IsExternalInit
{
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>11</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.4.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.0.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,331 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Robust.Serialization.Generator;
internal static class Types
{
private const string DataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataDefinitionAttribute";
private const string ImplicitDataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.ImplicitDataDefinitionForInheritorsAttribute";
private const string DataFieldBaseNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataFieldBaseAttribute";
private const string CopyByRefNamespace = "Robust.Shared.Serialization.Manager.Attributes.CopyByRefAttribute";
internal static bool IsPartial(TypeDeclarationSyntax type)
{
return type.Modifiers.IndexOf(SyntaxKind.PartialKeyword) != -1;
}
internal static bool IsDataDefinition(ITypeSymbol? type)
{
if (type == null)
return false;
return HasAttribute(type, DataDefinitionNamespace) ||
IsImplicitDataDefinition(type);
}
internal static bool IsDataField(ISymbol member, out ITypeSymbol type, out AttributeData attribute)
{
// TODO data records and other attributes
if (member is IFieldSymbol field)
{
foreach (var attr in field.GetAttributes())
{
if (attr.AttributeClass != null && Inherits(attr.AttributeClass, DataFieldBaseNamespace))
{
type = field.Type;
attribute = attr;
return true;
}
}
}
else if (member is IPropertySymbol property)
{
foreach (var attr in property.GetAttributes())
{
if (attr.AttributeClass != null && Inherits(attr.AttributeClass, DataFieldBaseNamespace))
{
type = property.Type;
attribute = attr;
return true;
}
}
}
type = null!;
attribute = null!;
return false;
}
internal static bool IsImplicitDataDefinition(ITypeSymbol type)
{
if (HasAttribute(type, ImplicitDataDefinitionNamespace))
return true;
foreach (var baseType in GetBaseTypes(type))
{
if (HasAttribute(baseType, ImplicitDataDefinitionNamespace))
return true;
}
foreach (var @interface in type.AllInterfaces)
{
if (IsImplicitDataDefinitionInterface(@interface))
return true;
}
return false;
}
internal static bool IsImplicitDataDefinitionInterface(ITypeSymbol @interface)
{
if (HasAttribute(@interface, ImplicitDataDefinitionNamespace))
return true;
foreach (var subInterface in @interface.AllInterfaces)
{
if (HasAttribute(subInterface, ImplicitDataDefinitionNamespace))
return true;
}
return false;
}
internal static IEnumerable<ITypeSymbol> GetImplicitDataDefinitionInterfaces(ITypeSymbol type, bool all)
{
var interfaces = all ? type.AllInterfaces : type.Interfaces;
foreach (var @interface in interfaces)
{
if (IsImplicitDataDefinitionInterface(@interface))
yield return @interface;
}
}
internal static bool IsNullableType(ITypeSymbol type)
{
if (type.NullableAnnotation == NullableAnnotation.Annotated)
return true;
if (type.OriginalDefinition.ToDisplayString() == "System.Nullable<T>")
return true;
return false;
}
internal static bool IsNullableValueType(ITypeSymbol type)
{
return type.IsValueType && IsNullableType(type);
}
internal static bool IsMultidimensionalArray(ITypeSymbol type)
{
return type is IArrayTypeSymbol { Rank: > 1 };
}
internal static bool CanBeCopiedByValue(ISymbol member, ITypeSymbol type)
{
if (type.OriginalDefinition.ToDisplayString() == "System.Nullable<T>")
return CanBeCopiedByValue(member, ((INamedTypeSymbol) type).TypeArguments[0]);
if (type.TypeKind == TypeKind.Enum)
return true;
switch (type.SpecialType)
{
case SpecialType.System_Enum:
case SpecialType.System_Boolean:
case SpecialType.System_Char:
case SpecialType.System_SByte:
case SpecialType.System_Byte:
case SpecialType.System_Int16:
case SpecialType.System_UInt16:
case SpecialType.System_Int32:
case SpecialType.System_UInt32:
case SpecialType.System_Int64:
case SpecialType.System_UInt64:
case SpecialType.System_Decimal:
case SpecialType.System_Single:
case SpecialType.System_Double:
case SpecialType.System_String:
case SpecialType.System_DateTime:
return true;
}
if (HasAttribute(member, CopyByRefNamespace))
return true;
return false;
}
internal static string GetGenericTypeName(ITypeSymbol symbol)
{
var name = symbol.Name;
if (symbol is INamedTypeSymbol { TypeParameters: { Length: > 0 } parameters })
{
name += "<";
for (var i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
name += parameter.Name;
if (i < parameters.Length - 1)
{
name += ", ";
}
}
name += ">";
}
return name;
}
internal static string GetPartialTypeDefinitionLine(ITypeSymbol symbol)
{
var access = symbol.DeclaredAccessibility switch
{
Accessibility.Private => "private",
Accessibility.ProtectedAndInternal => "protected internal",
Accessibility.Protected => "protected",
Accessibility.Internal => "internal",
Accessibility.Public => "public",
_ => "public"
};
var typeKeyword = "partial ";
if (symbol.TypeKind == TypeKind.Interface)
{
typeKeyword += "interface";
}
else
{
if (symbol.IsRecord)
{
typeKeyword += symbol.IsValueType ? "record struct" : "record";
}
else
{
typeKeyword += symbol.IsValueType ? "struct" : "class";
}
if (symbol.IsAbstract)
{
typeKeyword = $"abstract {typeKeyword}";
}
}
var typeName = GetGenericTypeName(symbol);
return $"{access} {typeKeyword} {typeName}";
}
internal static bool Inherits(ITypeSymbol type, string parent)
{
foreach (var baseType in GetBaseTypes(type))
{
if (baseType.ToDisplayString() == parent)
return true;
}
return false;
}
internal static bool ImplementsInterface(ITypeSymbol type, string interfaceName)
{
foreach (var interfaceType in type.AllInterfaces)
{
if (interfaceType.ToDisplayString().Contains(interfaceName))
{
return true;
}
}
return false;
}
internal static bool IsReadOnlyMember(ITypeSymbol type, ISymbol member)
{
if (member is IFieldSymbol field)
{
return field.IsReadOnly;
}
else if (member is IPropertySymbol property)
{
if (property.SetMethod == null)
return true;
if (property.SetMethod.IsInitOnly)
return type.IsReferenceType;
return false;
}
return false;
}
internal static bool NeedsEmptyConstructor(ITypeSymbol type)
{
if (type is not INamedTypeSymbol named)
return false;
if (named.InstanceConstructors.Length == 0)
return true;
foreach (var constructor in named.InstanceConstructors)
{
if (constructor.Parameters.Length == 0 &&
!constructor.IsImplicitlyDeclared)
{
return false;
}
}
return true;
}
internal static bool HasEmptyPublicConstructor(ITypeSymbol type)
{
if (type is not INamedTypeSymbol named)
return false;
foreach (var constructor in named.InstanceConstructors)
{
if (constructor.DeclaredAccessibility == Accessibility.Public &&
constructor.Parameters.Length == 0)
{
return true;
}
}
return false;
}
internal static bool IsVirtualClass(ITypeSymbol type)
{
return type.IsReferenceType && !type.IsSealed && type.TypeKind != TypeKind.Interface;
}
internal static bool HasAttribute(ISymbol symbol, string attributeName)
{
foreach (var attribute in symbol.GetAttributes())
{
if (attribute.AttributeClass?.ToDisplayString() == attributeName)
return true;
}
return false;
}
internal static IEnumerable<ITypeSymbol> GetBaseTypes(ITypeSymbol type)
{
var baseType = type.BaseType;
while (baseType != null)
{
yield return baseType;
baseType = baseType.BaseType;
}
}
}

View File

@@ -59,19 +59,20 @@ public sealed class ScaleCommand : LocalizedCommands
if (_entityManager.TryGetComponent(uid, out FixturesComponent? manager))
{
foreach (var fixture in manager.Fixtures.Values)
foreach (var (id, fixture) in manager.Fixtures)
{
switch (fixture.Shape)
{
case EdgeShape edge:
physics.SetVertices(uid, fixture, edge,
physics.SetVertices(uid, id, fixture,
edge,
edge.Vertex0 * scale,
edge.Vertex1 * scale,
edge.Vertex2 * scale,
edge.Vertex3 * scale, manager);
break;
case PhysShapeCircle circle:
physics.SetPositionRadius(uid, fixture, circle, circle.Position * scale, circle.Radius * scale, manager);
physics.SetPositionRadius(uid, id, fixture, circle, circle.Position * scale, circle.Radius * scale, manager);
break;
case PolygonShape poly:
var verts = poly.Vertices;
@@ -81,7 +82,7 @@ public sealed class ScaleCommand : LocalizedCommands
verts[i] *= scale;
}
physics.SetVertices(uid, fixture, poly, verts, manager);
physics.SetVertices(uid, id, fixture, poly, verts, manager);
break;
default:
throw new NotImplementedException();

View File

@@ -137,10 +137,10 @@ namespace Robust.Server.Console.Commands
var ground = _ent.AddComponent<PhysicsComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
fixtures.CreateFixture(groundUid, new Fixture("fix1", horizontal, 2, 2, true), body: ground);
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
var vertical = new EdgeShape(new Vector2(10, 0), new Vector2(10, 10));
fixtures.CreateFixture(groundUid, new Fixture("fix2", vertical, 2, 2, true), body: ground);
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
var xs = new[]
{
@@ -166,7 +166,7 @@ namespace Robust.Server.Console.Commands
shape = new PolygonShape();
shape.SetAsBox(0.5f, 0.5f);
physics.SetFixedRotation(boxUid, false, body: box);
fixtures.CreateFixture(boxUid, new Fixture("fix1", shape, 2, 2, true), body: box);
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true), body: box);
physics.WakeBody(boxUid, body: box);
}
@@ -184,10 +184,10 @@ namespace Robust.Server.Console.Commands
var ground = _ent.AddComponent<PhysicsComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
fixtures.CreateFixture(groundUid, new Fixture("fix1", horizontal, 2, 2, true), body: ground);
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
var vertical = new EdgeShape(new Vector2(20, 0), new Vector2(20, 20));
fixtures.CreateFixture(groundUid, new Fixture("fix2", vertical, 2, 2, true), body: ground);
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
var xs = new[]
{
@@ -213,7 +213,7 @@ namespace Robust.Server.Console.Commands
physics.SetFixedRotation(boxUid, false, body: box);
// TODO: Need to detect shape and work out if we need to use fixedrotation
fixtures.CreateFixture(boxUid, new Fixture("fix1", shape, 2, 2, true, 5f));
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f));
physics.WakeBody(boxUid, body: box);
}
}
@@ -232,7 +232,7 @@ namespace Robust.Server.Console.Commands
var ground = _ent.AddComponent<PhysicsComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(40, 0), new Vector2(-40, 0));
fixtures.CreateFixture(groundUid, new Fixture("fix1", horizontal, 2, 2, true), body: ground);
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
physics.WakeBody(groundUid, body: ground);
// Setup boxes
@@ -255,7 +255,7 @@ namespace Robust.Server.Console.Commands
var box = _ent.AddComponent<PhysicsComponent>(boxUid);
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
fixtures.CreateFixture(boxUid, new Fixture("fix1", shape, 2, 2, true, 5f), body: box);
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f), body: box);
y += deltaY;
physics.WakeBody(boxUid, body: box);
@@ -275,7 +275,7 @@ namespace Robust.Server.Console.Commands
var ground = _ent.AddComponent<PhysicsComponent>(groundUid);
// Due to lookup changes fixtureless bodies are invalid, so
var cShape = new PhysShapeCircle(1f);
fixtures.CreateFixture(groundUid, new Fixture("fix1", cShape, 0, 0, false));
fixtures.CreateFixture(groundUid, "fix1", new Fixture(cShape, 0, 0, false));
var bodyUid = _ent.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
var body = _ent.AddComponent<PhysicsComponent>(bodyUid);
@@ -288,19 +288,19 @@ namespace Robust.Server.Console.Commands
// TODO: Box2D just deref, bleh shape structs someday
var shape1 = new PolygonShape();
shape1.SetAsBox(0.5f, 10.0f, new Vector2(10.0f, 0.0f), 0.0f);
fixtures.CreateFixture(bodyUid, new Fixture("fix1", shape1, 2, 0, true, 20f));
fixtures.CreateFixture(bodyUid, "fix1", new Fixture(shape1, 2, 0, true, 20f));
var shape2 = new PolygonShape();
shape2.SetAsBox(0.5f, 10.0f, new Vector2(-10.0f, 0.0f), 0f);
fixtures.CreateFixture(bodyUid, new Fixture("fix2", shape2, 2, 0, true, 20f));
fixtures.CreateFixture(bodyUid, "fix2", new Fixture(shape2, 2, 0, true, 20f));
var shape3 = new PolygonShape();
shape3.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, 10.0f), 0f);
fixtures.CreateFixture(bodyUid, new Fixture("fix3", shape3, 2, 0, true, 20f));
fixtures.CreateFixture(bodyUid, "fix3", new Fixture(shape3, 2, 0, true, 20f));
var shape4 = new PolygonShape();
shape4.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, -10.0f), 0f);
fixtures.CreateFixture(bodyUid, new Fixture("fix4", shape4, 2, 0, true, 20f));
fixtures.CreateFixture(bodyUid, "fix4", new Fixture(shape4, 2, 0, true, 20f));
physics.WakeBody(groundUid, body: ground);
physics.WakeBody(bodyUid, body: body);
@@ -328,7 +328,7 @@ namespace Robust.Server.Console.Commands
physics.SetFixedRotation(boxUid, false, body: box);
var shape = new PolygonShape();
shape.SetAsBox(0.125f, 0.125f);
fixtures.CreateFixture(boxUid, new Fixture("fix1", shape, 2, 2, true, 0.0625f), body: box);
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 0.0625f), body: box);
physics.WakeBody(boxUid, body: box);
});
}

View File

@@ -5,7 +5,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
{
[RegisterComponent]
public sealed class ActorComponent : Component
public sealed partial class ActorComponent : Component
{
[ViewVariables]
public IPlayerSession PlayerSession { get; internal set; } = default!;

View File

@@ -11,7 +11,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
{
[RegisterComponent, ComponentReference(typeof(SharedEyeComponent))]
public sealed class EyeComponent : SharedEyeComponent
public sealed partial class EyeComponent : SharedEyeComponent
{
public const int DefaultVisibilityMask = 1;

View File

@@ -5,7 +5,7 @@ using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
{
[RegisterComponent]
internal sealed class ViewSubscriberComponent : Component
internal sealed partial class ViewSubscriberComponent : Component
{
internal readonly HashSet<IPlayerSession> SubscribedSessions = new();
}

View File

@@ -4,5 +4,5 @@ namespace Robust.Server.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(SharedPointLightComponent))]
public sealed class PointLightComponent : SharedPointLightComponent {}
public sealed partial class PointLightComponent : SharedPointLightComponent {}
}

View File

@@ -7,6 +7,6 @@ namespace Robust.Server.GameObjects;
/// close the UI automatically.
/// </summary>
[RegisterComponent]
public sealed class IgnoreUIRangeComponent : Component
public sealed partial class IgnoreUIRangeComponent : Component
{
}

View File

@@ -15,14 +15,14 @@ namespace Robust.Server.GameObjects
/// <seealso cref="BoundUserInterface"/>
[PublicAPI]
[RegisterComponent, ComponentReference(typeof(SharedUserInterfaceComponent))]
public sealed class ServerUserInterfaceComponent : SharedUserInterfaceComponent
public sealed partial class ServerUserInterfaceComponent : SharedUserInterfaceComponent
{
[ViewVariables]
public readonly Dictionary<Enum, BoundUserInterface> Interfaces = new();
}
[RegisterComponent]
public sealed class ActiveUserInterfaceComponent : Component
public sealed partial class ActiveUserInterfaceComponent : Component
{
public HashSet<BoundUserInterface> Interfaces = new();
}

View File

@@ -8,7 +8,7 @@ namespace Robust.Server.GameObjects
{
[RegisterComponent]
[Access(typeof(VisibilitySystem))]
public sealed class VisibilityComponent : Component
public sealed partial class VisibilityComponent : Component
{
/// <summary>
/// The visibility layer for the entity.

View File

@@ -10,7 +10,7 @@ namespace Robust.Server.GameObjects
/// This can then be used to re-serialize the entity with the same UID for the merge driver to recognize.
/// </remarks>
[RegisterComponent]
public sealed class MapSaveIdComponent : Component
public sealed partial class MapSaveIdComponent : Component
{
public int Uid { get; set; }
}

View File

@@ -81,30 +81,35 @@ namespace Robust.Server.GameObjects
private protected override EntityUid CreateEntity(string? prototypeName, EntityUid uid = default, IEntityLoadContext? context = null)
{
var entity = base.CreateEntity(prototypeName, uid, context);
if (prototypeName == null)
return base.CreateEntity(prototypeName, uid, context);
if (!string.IsNullOrWhiteSpace(prototypeName))
{
var prototype = PrototypeManager.Index<EntityPrototype>(prototypeName);
if (!PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype))
throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}");
// At this point in time, all data configure on the entity *should* be purely from the prototype.
// As such, we can reset the modified ticks to Zero,
// which indicates "not different from client's own deserialization".
// So the initial data for the component or even the creation doesn't have to be sent over the wire.
foreach (var (netId, component) in GetNetComponents(entity))
{
// Make sure to ONLY get components that are defined in the prototype.
// Others could be instantiated directly by AddComponent (e.g. ContainerManager).
// And those aren't guaranteed to exist on the client, so don't clear them.
var compName = ComponentFactory.GetComponentName(component.GetType());
if (prototype.Components.ContainsKey(compName))
component.ClearTicks();
}
}
var entity = base.CreateEntity(prototype, uid, context);
// At this point in time, all data configure on the entity *should* be purely from the prototype.
// As such, we can reset the modified ticks to Zero,
// which indicates "not different from client's own deserialization".
// So the initial data for the component or even the creation doesn't have to be sent over the wire.
ClearTicks(entity, prototype);
return entity;
}
private void ClearTicks(EntityUid entity, EntityPrototype prototype)
{
foreach (var (netId, component) in GetNetComponents(entity))
{
// Make sure to ONLY get components that are defined in the prototype.
// Others could be instantiated directly by AddComponent (e.g. ContainerManager).
// And those aren't guaranteed to exist on the client, so don't clear them.
var compName = ComponentFactory.GetComponentName(netId);
if (prototype.Components.ContainsKey(compName))
component.ClearTicks();
}
}
public override EntityStringRepresentation ToPrettyString(EntityUid uid)
{
TryGetComponent(uid, out ActorComponent? actor);

View File

@@ -77,11 +77,21 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
/// </summary>
private readonly HashSet<TIndex> _globalOverrides = new();
/// <summary>
/// List of <see cref="TIndex"/> that should always get sent along with all of their children.
/// </summary>
private readonly HashSet<TIndex> _globalRecursiveOverrides = new();
/// <summary>
/// List of <see cref="TIndex"/> that should always get sent.
/// </summary>
public HashSet<TIndex>.Enumerator GlobalOverridesEnumerator => _globalOverrides.GetEnumerator();
/// <summary>
/// List of <see cref="TIndex"/> that should always get sent along with all of their children.
/// </summary>
public HashSet<TIndex>.Enumerator GlobalRecursiveOverridesEnumerator => _globalRecursiveOverrides.GetEnumerator();
/// <summary>
/// List of <see cref="TIndex"/> that should always get sent to a certain <see cref="ICommonSession"/>.
/// </summary>
@@ -203,8 +213,11 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
{
switch (location)
{
case GlobalOverride _:
_globalOverrides.Add(index);
case GlobalOverride global:
if (global.Recursive)
_globalRecursiveOverrides.Add(index);
else
_globalOverrides.Add(index);
break;
case GridChunkLocation gridChunkLocation:
// might be gone due to grid-deletions
@@ -239,8 +252,11 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
// since we can find the index, we can assume the dicts will be there too & dont need to do any checks. gaming.
switch (location)
{
case GlobalOverride _:
_globalOverrides.Remove(index);
case GlobalOverride global:
if (global.Recursive)
_globalRecursiveOverrides.Remove(index);
else
_globalOverrides.Remove(index);
break;
case GridChunkLocation gridChunkLocation:
_gridChunkContents[gridChunkLocation.GridId][gridChunkLocation.ChunkIndices].Remove(index);
@@ -376,15 +392,10 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
#region UpdateIndex
private bool IsOverride(TIndex index)
private bool TryGetLocation(TIndex index, out IIndexLocation? location)
{
if (_locationChangeBuffer.TryGetValue(index, out var change) &&
change is GlobalOverride or LocalOverride) return true;
if (_indexLocations.TryGetValue(index, out var indexLoc) &&
indexLoc is GlobalOverride or LocalOverride) return true;
return false;
return _locationChangeBuffer.TryGetValue(index, out location)
|| _indexLocations.TryGetValue(index, out location);
}
/// <summary>
@@ -392,15 +403,25 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
/// </summary>
/// <param name="index">The <see cref="TIndex"/> to update.</param>
/// <param name="removeFromOverride">An index at an override position will not be updated unless you set this flag.</param>
public void UpdateIndex(TIndex index, bool removeFromOverride = false)
/// <param name="recursive">If true, this will also recursively send any children of the given index.</param>
public void AddGlobalOverride(TIndex index, bool removeFromOverride, bool recursive)
{
if(!removeFromOverride && IsOverride(index))
if (!TryGetLocation(index, out var oldLocation))
{
RegisterUpdate(index, new GlobalOverride(recursive));
return;
}
if (!removeFromOverride && oldLocation is LocalOverride)
return;
if (_indexLocations.TryGetValue(index, out var oldLocation) &&
oldLocation is GlobalOverride) return;
if (oldLocation is GlobalOverride global &&
(!removeFromOverride || global.Recursive == recursive))
{
return;
}
RegisterUpdate(index, new GlobalOverride());
RegisterUpdate(index, new GlobalOverride(recursive));
}
/// <summary>
@@ -411,12 +432,20 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
/// <param name="removeFromOverride">An index at an override position will not be updated unless you set this flag.</param>
public void UpdateIndex(TIndex index, ICommonSession session, bool removeFromOverride = false)
{
if(!removeFromOverride && IsOverride(index))
if (!TryGetLocation(index, out var oldLocation))
{
RegisterUpdate(index, new LocalOverride(session));
return;
}
if (!removeFromOverride || oldLocation is GlobalOverride)
return;
if (_indexLocations.TryGetValue(index, out var oldLocation) &&
oldLocation is LocalOverride local &&
local.Session == session) return;
if (oldLocation is LocalOverride local &&
(!removeFromOverride || local.Session == session))
{
return;
}
RegisterUpdate(index, new LocalOverride(session));
}
@@ -429,34 +458,42 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
/// <param name="removeFromOverride">An index at an override position will not be updated unless you set this flag.</param>
public void UpdateIndex(TIndex index, EntityCoordinates coordinates, bool removeFromOverride = false)
{
if(!removeFromOverride && IsOverride(index))
if (!removeFromOverride
&& TryGetLocation(index, out var oldLocation)
&& oldLocation is GlobalOverride or LocalOverride)
{
return;
}
if (!_entityManager.TryGetComponent(coordinates.EntityId, out TransformComponent? xform))
return;
var gridIdOpt = coordinates.GetGridUid(_entityManager);
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
if (xform.GridUid is { } gridId && gridId.IsValid())
{
var gridIndices = GetChunkIndices(coordinates.Position);
UpdateIndex(index, gridId, gridIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
return;
}
var mapCoordinates = coordinates.ToMap(_entityManager, _transformSystem);
var mapIndices = GetChunkIndices(coordinates.Position);
UpdateIndex(index, mapCoordinates.MapId, mapIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
var worldPos = _transformSystem.GetWorldMatrix(xform).Transform(coordinates.Position);
var mapIndices = GetChunkIndices(worldPos);
UpdateIndex(index, xform.MapID, mapIndices, true); //skip overridecheck bc we already did it (saves some dict lookups)
}
public IChunkIndexLocation GetChunkIndex(EntityCoordinates coordinates)
{
var gridIdOpt = coordinates.GetGridUid(_entityManager);
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
if (!_entityManager.TryGetComponent(coordinates.EntityId, out TransformComponent? xform))
return new MapChunkLocation(default, default);
if (xform.GridUid is { } gridId && gridId.IsValid())
{
var gridIndices = GetChunkIndices(coordinates.Position);
return new GridChunkLocation(gridId, gridIndices);
}
var mapCoordinates = coordinates.ToMap(_entityManager, _transformSystem);
var mapIndices = GetChunkIndices(coordinates.Position);
return new MapChunkLocation(mapCoordinates.MapId, mapIndices);
var worldPos = _transformSystem.GetWorldMatrix(xform).Transform(coordinates.Position);
var mapIndices = GetChunkIndices(worldPos);
return new MapChunkLocation(xform.MapID, mapIndices);
}
/// <summary>
@@ -469,11 +506,14 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
/// <param name="forceDirty">If true, this will mark the previous chunk as dirty even if the entity did not move from that chunk.</param>
public void UpdateIndex(TIndex index, EntityUid gridId, Vector2i chunkIndices, bool removeFromOverride = false, bool forceDirty = false)
{
if(!removeFromOverride && IsOverride(index))
_locationChangeBuffer.TryGetValue(index, out var bufferedLocation);
_indexLocations.TryGetValue(index, out var oldLocation);
//removeFromOverride is false 99% of the time.
if ((bufferedLocation ?? oldLocation) is GlobalOverride or LocalOverride && !removeFromOverride)
return;
if (_indexLocations.TryGetValue(index, out var oldLocation) &&
oldLocation is GridChunkLocation oldGrid &&
if (oldLocation is GridChunkLocation oldGrid &&
oldGrid.ChunkIndices == chunkIndices &&
oldGrid.GridId == gridId)
{
@@ -497,22 +537,26 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
/// <param name="forceDirty">If true, this will mark the previous chunk as dirty even if the entity did not move from that chunk.</param>
public void UpdateIndex(TIndex index, MapId mapId, Vector2i chunkIndices, bool removeFromOverride = false, bool forceDirty = false)
{
if(!removeFromOverride && IsOverride(index))
_locationChangeBuffer.TryGetValue(index, out var bufferedLocation);
_indexLocations.TryGetValue(index, out var oldLocation);
//removeFromOverride is false 99% of the time.
if ((bufferedLocation ?? oldLocation) is GlobalOverride or LocalOverride && !removeFromOverride)
return;
if (_indexLocations.TryGetValue(index, out var oldLocation) &&
oldLocation is MapChunkLocation oldMap &&
// Is this entity just returning to its old location?
if (oldLocation is MapChunkLocation oldMap &&
oldMap.ChunkIndices == chunkIndices &&
oldMap.MapId == mapId)
{
_locationChangeBuffer.Remove(index);
if (bufferedLocation != null)
_locationChangeBuffer.Remove(index);
if (forceDirty)
_dirtyChunks.Add(oldMap);
return;
}
RegisterUpdate(index, new MapChunkLocation(mapId, chunkIndices));
}
@@ -584,7 +628,18 @@ public struct GridChunkLocation : IIndexLocation, IChunkIndexLocation, IEquatabl
}
}
public struct GlobalOverride : IIndexLocation { }
public struct GlobalOverride : IIndexLocation
{
/// <summary>
/// If true, this will also send all children of the override.
/// </summary>
public readonly bool Recursive;
public GlobalOverride(bool recursive)
{
Recursive = recursive;
}
}
public struct LocalOverride : IIndexLocation
{

View File

@@ -12,20 +12,23 @@ public sealed class PvsOverrideSystem : EntitySystem
[Shared.IoC.Dependency] private readonly PvsSystem _pvs = default!;
/// <summary>
/// Used to ensure that an entity is always sent to every client. Overrides any client-specific overrides.
/// Used to ensure that an entity is always sent to every client. By default this overrides any client-specific overrides.
/// </summary>
public void AddGlobalOverride(EntityUid uid)
/// <param name="removeExistingOverride">Whether or not to supersede existing overrides.</param>
/// <param name="recursive">If true, this will also recursively send any children of the given index.</param>
public void AddGlobalOverride(EntityUid uid, bool removeExistingOverride = true, bool recursive = false)
{
_pvs.EntityPVSCollection.UpdateIndex(uid, true);
_pvs.EntityPVSCollection.AddGlobalOverride(uid, removeExistingOverride, recursive);
}
/// <summary>
/// Used to ensure that an entity is always sent to a specific client. Overrides any global or pre-existing
/// client-specific overrides.
/// </summary>
public void AddSessionOverride(EntityUid uid, ICommonSession session)
/// <param name="removeExistingOverride">Whether or not to supersede existing overrides.</param>
public void AddSessionOverride(EntityUid uid, ICommonSession session,bool removeExistingOverride = true)
{
_pvs.EntityPVSCollection.UpdateIndex(uid, session, true);
_pvs.EntityPVSCollection.UpdateIndex(uid, session, removeExistingOverride);
}
/// <summary>

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.Extensions.ObjectPool;
using Robust.Server.Configuration;
@@ -13,11 +12,9 @@ using Robust.Shared.Collections;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Threading;
using Robust.Shared.Timing;
@@ -107,6 +104,7 @@ internal sealed partial class PvsSystem : EntitySystem
private EntityQuery<EyeComponent> _eyeQuery;
private EntityQuery<TransformComponent> _xformQuery;
private EntityQuery<MetaDataComponent> _metaQuery;
public override void Initialize()
{
@@ -114,6 +112,7 @@ internal sealed partial class PvsSystem : EntitySystem
_eyeQuery = GetEntityQuery<EyeComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_metaQuery = GetEntityQuery<MetaDataComponent>();
_entityPvsCollection = RegisterPVSCollection<EntityUid>();
@@ -387,7 +386,7 @@ internal sealed partial class PvsSystem : EntitySystem
pvsCollection.AddGrid(gridId);
}
_entityPvsCollection.UpdateIndex(gridId);
_entityPvsCollection.AddGlobalOverride(gridId, true, false);
}
private void OnMapDestroyed(MapChangedEvent e)
@@ -407,7 +406,7 @@ internal sealed partial class PvsSystem : EntitySystem
if(e.Map == MapId.Nullspace) return;
var uid = _mapManager.GetMapEntityId(e.Map);
_entityPvsCollection.UpdateIndex(uid);
_entityPvsCollection.AddGlobalOverride(uid, true, false);
}
#endregion
@@ -749,6 +748,16 @@ internal sealed partial class PvsSystem : EntitySystem
}
globalEnumerator.Dispose();
var globalRecursiveEnumerator = _entityPvsCollection.GlobalRecursiveOverridesEnumerator;
while (globalRecursiveEnumerator.MoveNext())
{
var uid = globalRecursiveEnumerator.Current;
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true);
}
globalRecursiveEnumerator.Dispose();
var localEnumerator = _entityPvsCollection.GetElementsForSession(session);
while (localEnumerator.MoveNext())
{
@@ -764,7 +773,7 @@ internal sealed partial class PvsSystem : EntitySystem
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
}
var expandEvent = new ExpandPvsEvent(session, new List<EntityUid>());
var expandEvent = new ExpandPvsEvent(session);
RaiseLocalEvent(ref expandEvent);
foreach (var entityUid in expandEvent.Entities)
{
@@ -772,6 +781,12 @@ internal sealed partial class PvsSystem : EntitySystem
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
}
foreach (var entityUid in expandEvent.RecursiveEntities)
{
RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen, in mQuery, in tQuery, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true);
}
var entityStates = new List<EntityState>(entStateCount);
foreach (var (uid, visiblity) in visibleEnts)
@@ -898,8 +913,7 @@ internal sealed partial class PvsSystem : EntitySystem
}
}
public bool RecursivelyAddOverride(
in EntityUid uid,
public bool RecursivelyAddOverride(in EntityUid uid,
Dictionary<EntityUid, PvsEntityVisibility>? lastAcked,
Dictionary<EntityUid, PvsEntityVisibility>? lastSent,
Dictionary<EntityUid, PvsEntityVisibility> toSend,
@@ -911,34 +925,74 @@ internal sealed partial class PvsSystem : EntitySystem
ref int enteredEntityCount,
ref int entStateCount,
in int newEntityBudget,
in int enteredEntityBudget)
in int enteredEntityBudget,
bool addChildren = false)
{
//are we valid?
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
if (!uid.IsValid()) return false;
if (!uid.IsValid())
return false;
var parent = transQuery.GetComponent(uid).ParentUid;
var xform = transQuery.GetComponent(uid);
var parent = xform.ParentUid;
if (parent.IsValid() && !RecursivelyAddOverride(in parent, lastAcked, lastSent, toSend, lastSeen, in metaQuery, in transQuery, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget))
return false;
//did we already get added?
if (toSend.ContainsKey(uid)) return true;
// Note that we check this AFTER adding parents. This is because while this entity may already have been added
// to the toSend set, it doesn't guarantee that its parents have been. E.g., if a player ghost just teleported
// to follow a far away entity, the player's own entity is still being sent, but we need to ensure that we also
// send the new parents, which may otherwise be delayed because of the PVS budget..
if (!toSend.ContainsKey(uid))
{
// TODO PERFORMANCE.
// ProcessEntry() unnecessarily checks lastSent.ContainsKey() and maybe lastSeen.Contains(). Given that at this
// point the budgets are just ignored, this should just bypass those checks. But then again 99% of the time this
// is just the player's own entity + maybe a singularity. So currently not all that performance intensive.
var (entered, _) = ProcessEntry(in uid, lastAcked, lastSent, lastSeen, ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
AddToSendSet(in uid, metaQuery.GetComponent(uid), toSend, fromTick, in entered, ref entStateCount);
}
// TODO PERFORMANCE.
// ProcessEntry() unnecessarily checks lastSent.ContainsKey() and maybe lastSeen.Contains(). Given that at this
// point the budgets are just ignored, this should just bypass those checks. But then again 99% of the time this
// is just the player's own entity + maybe a singularity. So currently not all that performance intensive.
var (entered, _) = ProcessEntry(in uid, lastAcked, lastSent, lastSeen, ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
if (addChildren)
{
RecursivelyAddChildren(xform, lastAcked, lastSent, toSend, lastSeen, fromTick, ref newEntityCount,
ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
}
AddToSendSet(in uid, metaQuery.GetComponent(uid), toSend, fromTick, in entered, ref entStateCount);
return true;
}
private void RecursivelyAddChildren(TransformComponent xform,
Dictionary<EntityUid, PvsEntityVisibility>? lastAcked,
Dictionary<EntityUid, PvsEntityVisibility>? lastSent,
Dictionary<EntityUid, PvsEntityVisibility> toSend,
Dictionary<EntityUid, GameTick> lastSeen,
in GameTick fromTick,
ref int newEntityCount,
ref int enteredEntityCount,
ref int entStateCount,
in int newEntityBudget,
in int enteredEntityBudget)
{
foreach (var child in xform.ChildEntities)
{
if (!_xformQuery.TryGetComponent(child, out var childXform))
continue;
if (!toSend.ContainsKey(child))
{
var (entered, _) = ProcessEntry(in child, lastAcked, lastSent, lastSeen, ref newEntityCount,
ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
AddToSendSet(in child, _metaQuery.GetComponent(child), toSend, fromTick, in entered, ref entStateCount);
}
RecursivelyAddChildren(childXform, lastAcked, lastSent, toSend, lastSeen, fromTick, ref newEntityCount,
ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
}
}
private (bool Entered, bool ShouldAdd) ProcessEntry(in EntityUid uid,
Dictionary<EntityUid, PvsEntityVisibility>? lastAcked,
Dictionary<EntityUid, PvsEntityVisibility>? lastSent,
@@ -1034,26 +1088,20 @@ internal sealed partial class PvsSystem : EntitySystem
while (query.MoveNext(out var uid, out var md))
{
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating);
if (md.EntityLastModifiedTick <= fromTick)
continue;
var state = GetEntityState(player, uid, fromTick, md);
// Temporary debugging code.
// TODO REMOVE TEMPORARY CODE
if (state.Empty)
{
var msg = $"{nameof(GetEntityState)} returned an empty state while enumerating all. Entity {ToPrettyString(uid)}. Net component Data:";
foreach (var (_, cmp) in EntityManager.GetNetComponents(uid))
{
msg += $"\nName: {_factory.GetComponentName(cmp.GetType())}" +
$"Enabled: {cmp.NetSyncEnabled}, " +
$"Lifestage: {cmp.LifeStage}, " +
$"OwnerOnly: {cmp.SendOnlyToOwner}, " +
$"SessionSpecific: {cmp.SessionSpecific}, " +
$"LastModified: {cmp.LastModifiedTick}";
}
Log.Error(msg);
Log.Error($@"{nameof(GetEntityState)} returned an empty state while enumerating entities.
Tick: {fromTick}--{_gameTiming.CurTick}
Entity: {ToPrettyString(uid)}
Last modified: {md.EntityLastModifiedTick}
Metadata last modified: {md.LastModifiedTick}
Transform last modified: {Transform(uid).LastModifiedTick}");
}
stateEntities.Add(state);
@@ -1077,26 +1125,21 @@ internal sealed partial class PvsSystem : EntitySystem
continue;
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick || md.EntityLastModifiedTick == GameTick.Zero);
DebugTools.Assert(md.EntityLastModifiedTick > fromTick || md.EntityLastModifiedTick == GameTick.Zero);
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating);
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick);
DebugTools.Assert(md.EntityLastModifiedTick > fromTick);
var state = GetEntityState(player, uid, fromTick, md);
// Temporary debugging code.
// TODO REMOVE TEMPORARY CODE
if (state.Empty)
{
var msg = $"{nameof(GetEntityState)} returned an empty state for new entity {ToPrettyString(uid)}. Net component Data:";
foreach (var (_, cmp) in EntityManager.GetNetComponents(uid))
{
msg += $"\nName: {_factory.GetComponentName(cmp.GetType())}" +
$"Enabled: {cmp.NetSyncEnabled}, " +
$"Lifestage: {cmp.LifeStage}, " +
$"OwnerOnly: {cmp.SendOnlyToOwner}, " +
$"SessionSpecific: {cmp.SessionSpecific}, " +
$"LastModified: {cmp.LastModifiedTick}";
}
Log.Error(msg);
Log.Error($@"{nameof(GetEntityState)} returned an empty state for a new entity.
Tick: {fromTick}--{_gameTiming.CurTick}
Entity: {ToPrettyString(uid)}
Last modified: {md.EntityLastModifiedTick}
Metadata last modified: {md.LastModifiedTick}
Transform last modified: {Transform(uid).LastModifiedTick}");
continue;
}
stateEntities.Add(state);
@@ -1109,8 +1152,9 @@ internal sealed partial class PvsSystem : EntitySystem
continue;
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick || md.EntityLastModifiedTick == GameTick.Zero);
DebugTools.Assert(md.EntityLastModifiedTick > fromTick || md.EntityLastModifiedTick == GameTick.Zero);
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating);
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick);
DebugTools.Assert(md.EntityLastModifiedTick > fromTick);
var state = GetEntityState(player, uid, fromTick, md);
if (!state.Empty)
@@ -1330,11 +1374,20 @@ internal sealed partial class PvsSystem : EntitySystem
public readonly struct ExpandPvsEvent
{
public readonly IPlayerSession Session;
public readonly List<EntityUid> Entities;
public ExpandPvsEvent(IPlayerSession session, List<EntityUid> entities)
/// <summary>
/// List of entities that will get added to this session's PVS set.
/// </summary>
public readonly List<EntityUid> Entities = new();
/// <summary>
/// List of entities that will get added to this session's PVS set. Unlike <see cref="Entities"/> this will also
/// recursively add all children of the given entity.
/// </summary>
public readonly List<EntityUid> RecursiveEntities = new();
public ExpandPvsEvent(IPlayerSession session)
{
Session = session;
Entities = entities;
}
}

View File

@@ -6,7 +6,7 @@ namespace Robust.Server.Maps;
/// Added to Maps that were loaded by MapLoaderSystem. If not present then this map was created externally.
/// </summary>
[RegisterComponent]
public sealed class LoadedMapComponent : Component
public sealed partial class LoadedMapComponent : Component
{
}

View File

@@ -29,7 +29,6 @@ using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Xml.Linq;
using JetBrains.Annotations;
using Robust.Shared.Utility;
using SysVector3 = System.Numerics.Vector3;
@@ -1638,6 +1637,11 @@ namespace Robust.Shared.Maths
/// </summary>
public static Color RoyalBlue => new(65, 105, 225, 255);
/// <summary>
/// Gets the system color with (R, G, B, A) = (204, 71, 120, 255).
/// </summary>
public static Color Ruber => new(204, 71, 120, 255);
/// <summary>
/// Gets the system color with (R, G, B, A) = (139, 69, 19, 255).
/// </summary>
@@ -1653,6 +1657,11 @@ namespace Robust.Shared.Maths
/// </summary>
public static Color SandyBrown => new(244, 164, 96, 255);
/// <summary>
/// Gets the system color with (R, G, B, A) = (0, 66, 153, 255).
/// </summary>
public static Color SeaBlue => new(0, 66, 153, 255);
/// <summary>
/// Gets the system color with (R, G, B, A) = (46, 139, 87, 255).
/// </summary>
@@ -1733,6 +1742,16 @@ namespace Robust.Shared.Maths
/// </summary>
public static Color Violet => new(238, 130, 238, 255);
/// <summary>
/// Gets the system color with (R, G, B, A) = (126, 3, 168, 255).
/// </summary>
public static Color BetterViolet => new(126, 3, 168, 255);
/// <summary>
/// Gets the system color with (R, G, B, A) = (255, 153, 0, 255).
/// </summary>
public static Color VividGamboge => new(255, 153, 0, 255);
/// <summary>
/// Gets the system color with (R, G, B, A) = (245, 222, 179, 255).
/// </summary>
@@ -1767,6 +1786,7 @@ namespace Robust.Shared.Maths
["aquamarine"] = Aquamarine,
["azure"] = Azure,
["beige"] = Beige,
["betterviolet"] = BetterViolet,
["bisque"] = Bisque,
["black"] = Black,
["blanchedalmond"] = BlanchedAlmond,
@@ -1877,9 +1897,11 @@ namespace Robust.Shared.Maths
["red"] = Red,
["rosybrown"] = RosyBrown,
["royalblue"] = RoyalBlue,
["ruber"] = Ruber,
["saddlebrown"] = SaddleBrown,
["salmon"] = Salmon,
["sandybrown"] = SandyBrown,
["seablue"] = SeaBlue,
["seagreen"] = SeaGreen,
["seashell"] = SeaShell,
["sienna"] = Sienna,
@@ -1896,6 +1918,7 @@ namespace Robust.Shared.Maths
["tomato"] = Tomato,
["turquoise"] = Turquoise,
["violet"] = Violet,
["vividgamboge"] = VividGamboge,
["wheat"] = Wheat,
["white"] = White,
["whitesmoke"] = WhiteSmoke,

View File

@@ -6,7 +6,7 @@ namespace Robust.Shared.Analyzers;
/// Placed on auto-generated classes to mark to certain robust analyzers that they are auto-generated
/// and may need to be ignored (e.g. the access analyzer)
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface)]
public sealed class RobustAutoGeneratedAttribute : Attribute
{
}

View File

@@ -29,7 +29,7 @@ namespace Robust.Shared.Audio
/// </summary>
[Serializable, NetSerializable]
[DataDefinition]
public struct AudioParams
public partial struct AudioParams
{
/// <summary>
/// The DistanceModel to use for this specific source.

View File

@@ -11,8 +11,8 @@ public sealed class SoundCollectionPrototype : IPrototype
{
[ViewVariables]
[IdDataField]
public string ID { get; } = default!;
public string ID { get; private set; } = default!;
[DataField("files")]
public List<ResPath> PickFiles { get; } = new();
public List<ResPath> PickFiles { get; private set; } = new();
}

View File

@@ -13,22 +13,22 @@ using System;
namespace Robust.Shared.Audio;
[ImplicitDataDefinitionForInheritors, Serializable, NetSerializable]
public abstract class SoundSpecifier
public abstract partial class SoundSpecifier
{
[DataField("params")]
public AudioParams Params { get; init; } = AudioParams.Default;
public AudioParams Params { get; set; } = AudioParams.Default;
[Obsolete("Use SharedAudioSystem.GetSound(), or just pass sound specifier directly into SharedAudioSystem.")]
public abstract string GetSound(IRobustRandom? rand = null, IPrototypeManager? proto = null);
}
[Serializable, NetSerializable]
public sealed class SoundPathSpecifier : SoundSpecifier
public sealed partial class SoundPathSpecifier : SoundSpecifier
{
public const string Node = "path";
[DataField(Node, customTypeSerializer: typeof(ResPathSerializer), required: true)]
public ResPath Path { get; }
public ResPath Path { get; private set; }
[UsedImplicitly]
private SoundPathSpecifier()
@@ -54,12 +54,12 @@ public sealed class SoundPathSpecifier : SoundSpecifier
}
[Serializable, NetSerializable]
public sealed class SoundCollectionSpecifier : SoundSpecifier
public sealed partial class SoundCollectionSpecifier : SoundSpecifier
{
public const string Node = "collection";
[DataField(Node, customTypeSerializer: typeof(PrototypeIdSerializer<SoundCollectionPrototype>), required: true)]
public string? Collection { get; }
public string? Collection { get; private set; }
[UsedImplicitly]
public SoundCollectionSpecifier() { }

View File

@@ -283,6 +283,12 @@ namespace Robust.Shared
/// </summary>
public static readonly CVarDef<float> NetFakeDuplicates = CVarDef.Create("net.fakeduplicates", 0f, CVar.CHEAT);
/// <summary>
/// When using Happy Eyeballs to try both IPv6 over IPv4, the delay that IPv4 gets to get less priority.
/// </summary>
public static readonly CVarDef<float> NetHappyEyeballsDelay =
CVarDef.Create("net.happy_eyeballs_delay", 0.025f, CVar.CLIENTONLY);
/**
* SUS
*/
@@ -1189,10 +1195,10 @@ namespace Robust.Shared
CVarDef.Create("discord.enabled", true, CVar.CLIENTONLY);
public static readonly CVarDef<string> DiscordRichPresenceMainIconId =
CVarDef.Create("discord.rich_main_icon_id", "devstation", CVar.CLIENTONLY);
CVarDef.Create("discord.rich_main_icon_id", "devstation", CVar.SERVER | CVar.REPLICATED);
public static readonly CVarDef<string> DiscordRichPresenceSecondIconId =
CVarDef.Create("discord.rich_second_icon_id", "logo", CVar.CLIENTONLY);
CVarDef.Create("discord.rich_second_icon_id", "logo", CVar.SERVER | CVar.REPLICATED);
/*
* RES

View File

@@ -19,7 +19,7 @@ namespace Robust.Shared.Containers
/// <summary>
/// Base container class that all container inherit from.
/// </summary>
public abstract class BaseContainer : IContainer
public abstract partial class BaseContainer : IContainer
{
/// <inheritdoc />
[ViewVariables]

View File

@@ -16,7 +16,7 @@ namespace Robust.Shared.Containers
/// </summary>
[UsedImplicitly]
[SerializedType(ClassName)]
public sealed class Container : BaseContainer
public sealed partial class Container : BaseContainer
{
private const string ClassName = "Container";
@@ -24,7 +24,7 @@ namespace Robust.Shared.Containers
/// The generic container class uses a list of entities
/// </summary>
[DataField("ents")]
private readonly List<EntityUid> _containerList = new();
private List<EntityUid> _containerList = new();
private readonly List<EntityUid> _expectedEntities = new();

View File

@@ -19,7 +19,7 @@ namespace Robust.Shared.Containers
[ComponentReference(typeof(IContainerManager))]
[NetworkedComponent]
[RegisterComponent, ComponentProtoName("ContainerContainer")]
public sealed class ContainerManagerComponent : Component, IContainerManager, ISerializationHooks
public sealed partial class ContainerManagerComponent : Component, IContainerManager, ISerializationHooks
{
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
@@ -184,7 +184,7 @@ namespace Robust.Shared.Containers
}
[DataDefinition]
private struct ContainerPrototypeData
private partial struct ContainerPrototypeData
{
[DataField("entities")] public List<EntityUid> Entities = new ();

View File

@@ -12,7 +12,7 @@ namespace Robust.Shared.Containers
{
[UsedImplicitly]
[SerializedType(ClassName)]
public sealed class ContainerSlot : BaseContainer
public sealed partial class ContainerSlot : BaseContainer
{
private const string ClassName = "ContainerSlot";

View File

@@ -30,7 +30,7 @@ namespace Robust.Shared.Containers
/// <seealso cref="IContainerManager" />
[PublicAPI]
[ImplicitDataDefinitionForInheritors]
public interface IContainer
public partial interface IContainer
{
/// <summary>
/// Readonly collection of all the entities contained within this specific container

View File

@@ -11,7 +11,7 @@ namespace Robust.Shared.Containers
/// Manages containers on an entity.
/// </summary>
/// <seealso cref="IContainer" />
public interface IContainerManager : IComponent
public partial interface IContainerManager : IComponent
{
/// <summary>
/// Makes a new container of the specified type.

View File

@@ -2,6 +2,8 @@ using System;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -12,7 +14,7 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
[Reflect(false)]
[ImplicitDataDefinitionForInheritors]
public abstract class Component : IComponent
public abstract partial class Component : IComponent
{
[DataField("netsync")]
[ViewVariables(VVAccess.ReadWrite)]
@@ -100,8 +102,14 @@ namespace Robust.Shared.GameObjects
/// </remarks>
internal void LifeShutdown(IEntityManager entManager)
{
// Starting allows a component to remove itself in it's own Startup function.
DebugTools.Assert(LifeStage == ComponentLifeStage.Starting || LifeStage == ComponentLifeStage.Running);
DebugTools.Assert(LifeStage is >= ComponentLifeStage.Initializing and < ComponentLifeStage.Stopping);
if (LifeStage <= ComponentLifeStage.Initialized)
{
// Component was never started, no shutdown logic necessary. Simply mark it as stopped.
LifeStage = ComponentLifeStage.Stopped;
return;
}
LifeStage = ComponentLifeStage.Stopping;
entManager.EventBus.RaiseComponentEvent(this, CompShutdownInstance);

View File

@@ -328,6 +328,12 @@ namespace Robust.Shared.GameObjects
return GetRegistration(componentType).Name;
}
[Pure]
public string GetComponentName(ushort netID)
{
return GetRegistration(netID).Name;
}
public ComponentRegistration GetRegistration(ushort netID)
{
if (_networkedComponents is null)

View File

@@ -16,7 +16,7 @@ namespace Robust.Shared.GameObjects;
/// Visualization works client side with derivatives of the <see cref="Robust.Client.GameObjects.VisualizerSystem">VisualizerSystem</see> class and corresponding components.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed class AppearanceComponent : Component
public sealed partial class AppearanceComponent : Component
{
/// <summary>
/// Whether or not the appearance needs to be updated.

View File

@@ -3,4 +3,4 @@ using Robust.Shared.GameStates;
namespace Robust.Shared.GameObjects;
[RegisterComponent, NetworkedComponent]
public sealed class ScaleVisualsComponent : Component {}
public sealed partial class ScaleVisualsComponent : Component {}

View File

@@ -7,7 +7,7 @@ namespace Robust.Shared.GameObjects
/// A component that toggles collision on an entity being toggled.
/// </summary>
[RegisterComponent]
public sealed class CollideOnAnchorComponent : Component
public sealed partial class CollideOnAnchorComponent : Component
{
/// <summary>
/// Whether we toggle collision on or off when anchoring (and vice versa when unanchoring).

View File

@@ -11,7 +11,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
[RegisterComponent, NetworkedComponent()]
[Access(typeof(CollisionWakeSystem))]
public sealed class CollisionWakeComponent : Component
public sealed partial class CollisionWakeComponent : Component
{
[DataField("enabled")]
public bool Enabled = true;

View File

@@ -10,17 +10,17 @@ namespace Robust.Shared.GameObjects
/// Throws an exception in <see cref="OnAdd" />.
/// </summary>
[RegisterComponent]
public sealed class DebugExceptionOnAddComponent : Component { }
public sealed partial class DebugExceptionOnAddComponent : Component { }
/// <summary>
/// Throws an exception in <see cref="Initialize" />.
/// </summary>
[RegisterComponent]
public sealed class DebugExceptionInitializeComponent : Component { }
public sealed partial class DebugExceptionInitializeComponent : Component { }
/// <summary>
/// Throws an exception in <see cref="Startup" />.
/// </summary>
[RegisterComponent]
public sealed class DebugExceptionStartupComponent : Component { }
public sealed partial class DebugExceptionStartupComponent : Component { }
}

View File

@@ -8,7 +8,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
[NetworkedComponent()]
public abstract class SharedEyeComponent : Component
public abstract partial class SharedEyeComponent : Component
{
[ViewVariables(VVAccess.ReadWrite)]
public virtual bool DrawFov { get; set; }

View File

@@ -12,7 +12,7 @@ namespace Robust.Shared.GameObjects;
[RegisterComponent]
[NetworkedComponent()]
[Access(typeof(OccluderSystem))]
public sealed class OccluderComponent : Component, IComponentTreeEntry<OccluderComponent>
public sealed partial class OccluderComponent : Component, IComponentTreeEntry<OccluderComponent>
{
[DataField("enabled")]
public bool Enabled = true;

View File

@@ -7,7 +7,7 @@ namespace Robust.Shared.GameObjects;
/// Stores the relevant occluder children of this entity.
/// </summary>
[RegisterComponent]
public sealed class OccluderTreeComponent : Component, IComponentTreeComponent<OccluderComponent>
public sealed partial class OccluderTreeComponent : Component, IComponentTreeComponent<OccluderComponent>
{
public DynamicTree<ComponentTreeEntry<OccluderComponent>> Tree { get; set; } = default!;
}

View File

@@ -10,7 +10,7 @@ using System.Numerics;
namespace Robust.Shared.GameObjects
{
[NetworkedComponent]
public abstract class SharedPointLightComponent : Component
public abstract partial class SharedPointLightComponent : Component
{
[Dependency] private readonly IEntitySystemManager _sysMan = default!;

View File

@@ -12,10 +12,10 @@ namespace Robust.Shared.GameObjects.Components.Localization
/// </summary>
[RegisterComponent]
[NetworkedComponent()]
public sealed class GrammarComponent : Component
public sealed partial class GrammarComponent : Component
{
[DataField("attributes")]
public Dictionary<string, string> Attributes { get; } = new();
public Dictionary<string, string> Attributes { get; private set; } = new();
[ViewVariables]
public Gender? Gender

View File

@@ -57,7 +57,7 @@ namespace Robust.Shared.GameObjects
/// Contains meta data about this entity that isn't component specific.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed class MetaDataComponent : Component
public sealed partial class MetaDataComponent : Component
{
[DataField("name")] internal string? _entityName;
[DataField("desc")] internal string? _entityDescription;
@@ -71,7 +71,7 @@ namespace Robust.Shared.GameObjects
// Every entity starts at tick 1, because they are conceptually created in the time between 0->1
[ViewVariables]
public GameTick EntityLastModifiedTick { get; internal set; } = GameTick.Zero;
public GameTick EntityLastModifiedTick { get; internal set; } = GameTick.First;
/// <summary>
/// This is the tick at which the client last applied state data received from the server.

View File

@@ -8,7 +8,7 @@ using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.GameObjects;
[Serializable, NetSerializable, DataDefinition]
public sealed class PrototypeLayerData
public sealed partial class PrototypeLayerData
{
/// <summary>
/// The shader prototype to use for this layer.

View File

@@ -7,7 +7,7 @@ namespace Robust.Shared.GameObjects;
/// this is useful if you require multiple entities to have synchronised animations.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed class SyncSpriteComponent : Component
public sealed partial class SyncSpriteComponent : Component
{
}

View File

@@ -10,7 +10,7 @@ using Timer = Robust.Shared.Timing.Timer;
namespace Robust.Shared.GameObjects
{
[RegisterComponent, Obsolete("Use a system update loop instead")]
public sealed class TimerComponent : Component
public sealed partial class TimerComponent : Component
{
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;

View File

@@ -20,7 +20,7 @@ namespace Robust.Shared.GameObjects
/// Stores the position and orientation of the entity.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed class TransformComponent : Component, IComponentDebug
public sealed partial class TransformComponent : Component, IComponentDebug
{
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;

View File

@@ -8,19 +8,19 @@ using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.GameObjects
{
[NetworkedComponent]
public abstract class SharedUserInterfaceComponent : Component
public abstract partial class SharedUserInterfaceComponent : Component
{
[DataField("interfaces")]
internal List<PrototypeData> _interfaceData = new();
[DataDefinition]
public sealed class PrototypeData
public sealed partial class PrototypeData
{
[DataField("key", required: true)]
public Enum UiKey { get; } = default!;
public Enum UiKey { get; private set; } = default!;
[DataField("type", required: true)]
public string ClientType { get; } = default!;
public string ClientType { get; private set; } = default!;
/// <summary>
/// Maximum range before a BUI auto-closes. A non-positive number means there is no limit.

View File

@@ -131,7 +131,7 @@ namespace Robust.Shared.GameObjects
foreach (var comp in comps)
{
if (comp is { LifeStage: < ComponentLifeStage.Initialized })
if (comp is { LifeStage: ComponentLifeStage.Added })
comp.LifeInitialize(this, CompIdx.Index(comp.GetType()));
}
@@ -490,7 +490,7 @@ namespace Robust.Shared.GameObjects
return;
}
if (component.Running)
if (component.LifeStage >= ComponentLifeStage.Initialized && component.LifeStage <= ComponentLifeStage.Running)
component.LifeShutdown(this);
#if EXCEPTION_TOLERANCE
}

View File

@@ -0,0 +1,191 @@
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Robust.Shared.Containers;
namespace Robust.Shared.GameObjects;
public partial class EntityManager
{
// This method will soon be marked as obsolete.
public EntityUid SpawnEntity(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
=> SpawnAttachedTo(protoName, coordinates, overrides);
// This method will soon be marked as obsolete.
public EntityUid SpawnEntity(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
=> Spawn(protoName, coordinates, overrides);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, params string?[] protoNames)
{
var ents = new EntityUid[protoNames.Length];
for (var i = 0; i < protoNames.Length; i++)
{
ents[i] = SpawnAttachedTo(protoNames[i], coordinates);
}
return ents;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityUid[] SpawnEntities(MapCoordinates coordinates, params string?[] protoNames)
{
var ents = new EntityUid[protoNames.Length];
for (var i = 0; i < protoNames.Length; i++)
{
ents[i] = Spawn(protoNames[i], coordinates);
}
return ents;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, List<string?> protoNames)
{
var ents = new EntityUid[protoNames.Count];
for (var i = 0; i < protoNames.Count; i++)
{
ents[i] = SpawnAttachedTo(protoNames[i], coordinates);
}
return ents;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityUid[] SpawnEntities(MapCoordinates coordinates, List<string?> protoNames)
{
var ents = new EntityUid[protoNames.Count];
for (var i = 0; i < protoNames.Count; i++)
{
ents[i] = Spawn(protoNames[i], coordinates);
}
return ents;
}
public virtual EntityUid SpawnAttachedTo(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
{
if (!coordinates.IsValid(this))
throw new InvalidOperationException($"Tried to spawn entity {protoName} on invalid coordinates {coordinates}.");
var entity = CreateEntityUninitialized(protoName, coordinates, overrides);
InitializeAndStartEntity(entity, coordinates.GetMapId(this));
return entity;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null)
=> Spawn(protoName, MapCoordinates.Nullspace, overrides);
public virtual EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
{
var entity = CreateEntityUninitialized(protoName, coordinates, overrides);
InitializeAndStartEntity(entity, coordinates.MapId);
return entity;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityUid SpawnAtPosition(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
=> Spawn(protoName, coordinates.ToMap(this, _xforms), overrides);
public bool TrySpawnNextTo(
string? protoName,
EntityUid target,
[NotNullWhen(true)] out EntityUid? uid,
TransformComponent? xform = null,
ComponentRegistry? overrides = null)
{
uid = null;
if (!_xformQuery.Resolve(target, ref xform))
return false;
if (!xform.ParentUid.IsValid())
return false;
if (!_metaQuery.TryGetComponent(target, out var meta))
return false;
if ((meta.Flags & MetaDataFlags.InContainer) == 0)
{
uid = SpawnAttachedTo(protoName, xform.Coordinates, overrides);
return true;
}
if (!TryGetComponent(xform.ParentUid, out ContainerManagerComponent? containerComp))
return false;
foreach (var container in containerComp.Containers.Values)
{
if (!container.Contains(target))
continue;
uid = Spawn(protoName, overrides);
if (container.Insert(uid.Value, this))
return true;
DeleteEntity(uid.Value);
uid = null;
return false;
}
return false;
}
public bool TrySpawnInContainer(
string? protoName,
EntityUid containerUid,
string containerId,
[NotNullWhen(true)] out EntityUid? uid,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null)
{
uid = null;
if (containerComp == null && !TryGetComponent(containerUid, out containerComp))
return false;
if (!containerComp.Containers.TryGetValue(containerId, out var container))
return false;
uid = Spawn(protoName, overrides);
if (container.Insert(uid.Value, this))
return true;
DeleteEntity(uid.Value);
uid = null;
return false;
}
public EntityUid SpawnNextToOrDrop(string? protoName, EntityUid target, TransformComponent? xform = null, ComponentRegistry? overrides = null)
{
xform ??= _xformQuery.GetComponent(target);
if (!xform.ParentUid.IsValid())
return Spawn(protoName);
var uid = Spawn(protoName, overrides);
_xforms.PlaceNextToOrDrop(uid, target);
return uid;
}
public EntityUid SpawnInContainerOrDrop(
string? protoName,
EntityUid containerUid,
string containerId,
TransformComponent? xform = null,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null)
{
var uid = Spawn(protoName, overrides);
if ((containerComp == null && !TryGetComponent(containerUid, out containerComp))
|| !containerComp.Containers.TryGetValue(containerId, out var container)
|| !container.Insert(uid, this))
{
xform ??= _xformQuery.GetComponent(containerUid);
if (xform.ParentUid.IsValid())
_xforms.PlaceNextToOrDrop(uid, containerUid, targetXform: xform);
}
return uid;
}
}

View File

@@ -318,12 +318,7 @@ namespace Robust.Shared.GameObjects
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
{
var newEntity = CreateEntity(prototypeName, default, overrides);
if (coordinates.IsValid(this))
{
_xforms.SetCoordinates(newEntity, _xformQuery.GetComponent(newEntity), coordinates, unanchor: false);
}
_xforms.SetCoordinates(newEntity, _xformQuery.GetComponent(newEntity), coordinates, unanchor: false);
return newEntity;
}
@@ -360,77 +355,6 @@ namespace Robust.Shared.GameObjects
return newEntity;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityUid[] SpawnEntities(EntityCoordinates coordinates, params string?[] protoNames)
{
var ents = new EntityUid[protoNames.Length];
for (var i = 0; i < protoNames.Length; i++)
{
ents[i] = SpawnEntity(protoNames[i], coordinates);
}
return ents;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityUid[] SpawnEntities(MapCoordinates coordinates, params string?[] protoNames)
{
var ents = new EntityUid[protoNames.Length];
for (var i = 0; i < protoNames.Length; i++)
{
ents[i] = SpawnEntity(protoNames[i], coordinates);
}
return ents;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityUid[] SpawnEntities(EntityCoordinates coordinates, List<string?> protoNames)
{
var ents = new EntityUid[protoNames.Count];
for (var i = 0; i < protoNames.Count; i++)
{
ents[i] = SpawnEntity(protoNames[i], coordinates);
}
return ents;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityUid[] SpawnEntities(MapCoordinates coordinates, List<string?> protoNames)
{
var ents = new EntityUid[protoNames.Count];
for (var i = 0; i < protoNames.Count; i++)
{
ents[i] = SpawnEntity(protoNames[i], coordinates);
}
return ents;
}
/// <inheritdoc />
public virtual EntityUid SpawnEntity(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
{
if (!coordinates.IsValid(this))
throw new InvalidOperationException($"Tried to spawn entity {protoName} on invalid coordinates {coordinates}.");
var entity = CreateEntityUninitialized(protoName, coordinates, overrides);
InitializeAndStartEntity(entity, coordinates.GetMapId(this));
return entity;
}
/// <inheritdoc />
public virtual EntityUid SpawnEntity(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
{
var entity = CreateEntityUninitialized(protoName, coordinates, overrides);
InitializeAndStartEntity(entity, coordinates.MapId);
return entity;
}
/// <inheritdoc />
public int EntityCount => Entities.Count;
@@ -751,8 +675,17 @@ namespace Robust.Shared.GameObjects
if (prototypeName == null)
return AllocEntity(out _, uid);
PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype);
if (!PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype))
throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}");
return CreateEntity(prototype, uid, context);
}
/// <summary>
/// Allocates an entity and loads components but does not do initialization.
/// </summary>
private protected EntityUid CreateEntity(EntityPrototype prototype, EntityUid uid = default, IEntityLoadContext? context = null)
{
var entity = AllocEntity(prototype, out var metadata, uid);
try
{
@@ -764,7 +697,7 @@ namespace Robust.Shared.GameObjects
// Exception during entity loading.
// Need to delete the entity to avoid corrupt state causing crashes later.
DeleteEntity(entity);
throw new EntityCreationException($"Exception inside CreateEntity with prototype {prototypeName}", e);
throw new EntityCreationException($"Exception inside CreateEntity with prototype {prototype.ID}", e);
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
@@ -641,18 +642,80 @@ public partial class EntitySystem
#region Entity Spawning
/// <inheritdoc cref="IEntityManager.SpawnEntity(string?, EntityCoordinates, ComponentRegistry?)" />
// This method will be obsoleted soon.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid Spawn(string? prototype, EntityCoordinates coordinates)
{
return EntityManager.SpawnEntity(prototype, coordinates);
return ((IEntityManager)EntityManager).SpawnEntity(prototype, coordinates);
}
/// <inheritdoc cref="IEntityManager.SpawnEntity(string?, MapCoordinates, ComponentRegistry?)" />
/// <inheritdoc cref="IEntityManager.Spawn(string?, MapCoordinates, ComponentRegistry?)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid Spawn(string? prototype, MapCoordinates coordinates)
=> EntityManager.Spawn(prototype, coordinates);
/// <inheritdoc cref="IEntityManager.Spawn(string?, ComponentRegistry?)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid Spawn(string? prototype)
=> EntityManager.Spawn(prototype);
/// <inheritdoc cref="IEntityManager.SpawnAttachedTo" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid SpawnAttachedTo(string? prototype, EntityCoordinates coordinates)
=> EntityManager.SpawnAttachedTo(prototype, coordinates);
/// <inheritdoc cref="IEntityManager.SpawnAtPosition" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid SpawnAtPosition(string? prototype, EntityCoordinates coordinates)
=> EntityManager.SpawnAtPosition(prototype, coordinates);
/// <inheritdoc cref="IEntityManager.TrySpawnInContainer" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool TrySpawnInContainer(
string? protoName,
EntityUid containerUid,
string containerId,
[NotNullWhen(true)] out EntityUid? uid,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null)
{
return EntityManager.SpawnEntity(prototype, coordinates);
return EntityManager.TrySpawnInContainer(protoName, containerUid, containerId, out uid, containerComp, overrides);
}
/// <inheritdoc cref="IEntityManager.TrySpawnNextTo" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool TrySpawnNextTo(
string? protoName,
EntityUid target,
[NotNullWhen(true)] out EntityUid? uid,
TransformComponent? xform = null,
ComponentRegistry? overrides = null)
{
return EntityManager.TrySpawnNextTo(protoName, target, out uid, xform, overrides);
}
/// <inheritdoc cref="IEntityManager.SpawnNextToOrDrop" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid SpawnNextToOrDrop(
string? protoName,
EntityUid target,
TransformComponent? xform = null,
ComponentRegistry? overrides = null)
{
return EntityManager.SpawnNextToOrDrop(protoName, target, xform, overrides);
}
/// <inheritdoc cref="IEntityManager.SpawnInContainerOrDrop" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid SpawnInContainerOrDrop(
string? protoName,
EntityUid containerUid,
string containerId,
TransformComponent? xform = null,
ContainerManagerComponent? container = null,
ComponentRegistry? overrides = null)
{
return EntityManager.SpawnInContainerOrDrop(protoName, containerUid, containerId, xform, container, overrides);
}
#endregion

View File

@@ -1,4 +1,6 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
namespace Robust.Shared.GameObjects
@@ -8,7 +10,8 @@ namespace Robust.Shared.GameObjects
/// All discoverable implementations of IComponent must override the <see cref="Name" />.
/// Instances are dynamically instantiated by a <c>ComponentFactory</c>, and will have their IoC Dependencies resolved.
/// </remarks>
public interface IComponent
[ImplicitDataDefinitionForInheritors]
public partial interface IComponent : ISerializationGenerated<IComponent>
{
/// <summary>
/// The current lifetime stage of this component. You can use this to check

View File

@@ -1,6 +1,6 @@
namespace Robust.Shared.GameObjects
{
public interface IComponentDebug : IComponent
public partial interface IComponentDebug : IComponent
{
string GetDebugString();
}

View File

@@ -156,6 +156,17 @@ namespace Robust.Shared.GameObjects
/// </exception>
[Pure]
string GetComponentName(Type componentType);
/// <summary>
/// Gets the name of a component, throwing an exception if it does not exist.
/// </summary>
/// <param name="netID">The network ID corresponding to the component.</param>
/// <returns>The registered name of the component</returns>
/// <exception cref="UnknownComponentException">
/// Thrown if no component with id <see cref="netID"/> exists.
/// </exception>
[Pure]
string GetComponentName(ushort netID);
/// <summary>
/// Gets the registration belonging to a component, throwing an exception if it does not exist.

View File

@@ -0,0 +1,91 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
namespace Robust.Shared.GameObjects;
public partial interface IEntityManager
{
// This method will soon be marked as obsolete.
EntityUid[] SpawnEntities(EntityCoordinates coordinates, List<string?> protoNames)
=> SpawnEntitiesAttachedTo(coordinates, protoNames);
// This method will soon be marked as obsolete.
EntityUid SpawnEntity(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null);
// This method will soon be marked as obsolete.
EntityUid SpawnEntity(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null);
EntityUid[] SpawnEntities(MapCoordinates coordinates, params string?[] protoNames);
EntityUid[] SpawnEntities(MapCoordinates coordinates, List<string?> protoNames);
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, List<string?> protoNames);
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, params string?[] protoNames);
/// <summary>
/// Spawns an entity in nullspace.
/// </summary>
EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null);
/// <summary>
/// Spawns an entity at a specific world position. The entity will either be parented to the map or a grid.
/// </summary>
EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null);
/// <summary>
/// Spawns an entity and then parents it to the entity that the given entity coordinates are relative to.
/// </summary>
EntityUid SpawnAttachedTo(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null);
/// <summary>
/// Resolves the given entity coordinates into world coordinates and spawns an entity at that location. The
/// entity will either be parented to the map or a grid.
/// </summary>
EntityUid SpawnAtPosition(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null);
/// <summary>
/// Attempts to spawn an entity inside of a container.
/// </summary>
bool TrySpawnInContainer(
string? protoName,
EntityUid containerUid,
string containerId,
[NotNullWhen(true)] out EntityUid? uid,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null);
/// <summary>
/// Attempts to spawn an entity inside of a container. If it fails to insert into the container, it will
/// instead attempt to spawn the entity next to the target.
/// </summary>
public EntityUid SpawnInContainerOrDrop(
string? protoName,
EntityUid containerUid,
string containerId,
TransformComponent? xform = null,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null);
/// <summary>
/// Attempts to spawn an entity adjacent to some other entity. If the other entity is in a container, this will
/// attempt to insert the new entity into the same container.
/// </summary>
bool TrySpawnNextTo(
string? protoName,
EntityUid target,
[NotNullWhen(true)] out EntityUid? uid,
TransformComponent? xform = null,
ComponentRegistry? overrides = null);
/// <summary>
/// Attempts to spawn an entity adjacent to some other entity. If the other entity is in a container, this will
/// attempt to insert the new entity into the same container. If it fails to insert into the container, it will
/// instead attempt to spawn the entity next to the target's parent.
/// </summary>
EntityUid SpawnNextToOrDrop(
string? protoName,
EntityUid target,
TransformComponent? xform = null,
ComponentRegistry? overrides = null);
}

View File

@@ -71,32 +71,6 @@ namespace Robust.Shared.GameObjects
void StartEntity(EntityUid entity);
EntityUid[] SpawnEntities(EntityCoordinates coordinates, params string?[] protoNames);
EntityUid[] SpawnEntities(MapCoordinates coordinates, params string?[] protoNames);
EntityUid[] SpawnEntities(EntityCoordinates coordinates, List<string?> protoNames);
EntityUid[] SpawnEntities(MapCoordinates coordinates, List<string?> protoNames);
/// <summary>
/// Spawns an initialized entity and sets its local coordinates to the given entity coordinates. Note that this
/// means that if you specify coordinates relative to some entity, the newly spawned entity will be a child of
/// that entity.
/// </summary>
/// <param name="protoName">The prototype to clone. If this is null, the entity won't have a prototype.</param>
/// <param name="coordinates"></param>
/// <returns>Newly created entity.</returns>
EntityUid SpawnEntity(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null);
/// <summary>
/// Spawns an entity at a specific world position.
/// </summary>
/// <param name="protoName"></param>
/// <param name="coordinates"></param>
/// <returns></returns>
EntityUid SpawnEntity(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null);
/// <summary>
/// How many entities are currently active.
/// </summary>

View File

@@ -7,7 +7,7 @@ namespace Robust.Shared.GameObjects;
/// If an entity with this component is placed on top of another anchored entity with this component and the same key it will replace it.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed class PlacementReplacementComponent : Component
public sealed partial class PlacementReplacementComponent : Component
{
[DataField("key")]
public string Key = "";

View File

@@ -221,7 +221,8 @@ public sealed partial class EntityLookupSystem : EntitySystem
return _transform.GetInvWorldMatrix(treeXform).TransformBox(GetWorldAABB(entity, xform));
}
internal void CreateProxies(EntityUid uid, TransformComponent xform, Fixture fixture, PhysicsComponent body)
internal void CreateProxies(EntityUid uid, string fixtureId, Fixture fixture, TransformComponent xform,
PhysicsComponent body)
{
if (!TryGetCurrentBroadphase(xform, out var broadphase))
return;
@@ -237,10 +238,10 @@ public sealed partial class EntityLookupSystem : EntitySystem
var tree = body.BodyType == BodyType.Static ? broadphase.StaticTree : broadphase.DynamicTree;
DebugTools.Assert(fixture.ProxyCount == 0);
AddOrMoveProxies(uid, fixture, body, tree, broadphaseTransform, mapTransform, physMap.MoveBuffer);
AddOrMoveProxies(uid, fixtureId, fixture, body, tree, broadphaseTransform, mapTransform, physMap.MoveBuffer);
}
internal void DestroyProxies(EntityUid uid, Fixture fixture, TransformComponent xform, BroadphaseComponent broadphase, PhysicsMapComponent? physicsMap)
internal void DestroyProxies(EntityUid uid, string fixtureId, Fixture fixture, TransformComponent xform, BroadphaseComponent broadphase, PhysicsMapComponent? physicsMap)
{
DebugTools.AssertNotNull(xform.Broadphase);
DebugTools.Assert(xform.Broadphase!.Value.Uid == broadphase.Owner);
@@ -250,7 +251,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (fixture.ProxyCount == 0)
{
Log.Warning($"Tried to destroy fixture {fixture.ID} on {ToPrettyString(uid)} that already has no proxies?");
Log.Warning($"Tried to destroy fixture {fixtureId} on {ToPrettyString(uid)} that already has no proxies?");
return;
}
@@ -381,14 +382,15 @@ public sealed partial class EntityLookupSystem : EntitySystem
// TODO BROADPHASE PARENTING this just assumes local = world
var broadphaseTransform = new Transform(broadphaseXform.InvLocalMatrix.Transform(mapTransform.Position), mapTransform.Quaternion2D.Angle - broadphaseXform.LocalRotation);
foreach (var fixture in manager.Fixtures.Values)
foreach (var (id, fixture) in manager.Fixtures)
{
AddOrMoveProxies(uid, fixture, body, tree, broadphaseTransform, mapTransform, physicsMap.MoveBuffer);
AddOrMoveProxies(uid, id, fixture, body, tree, broadphaseTransform, mapTransform, physicsMap.MoveBuffer);
}
}
private void AddOrMoveProxies(
EntityUid uid,
string fixtureId,
Fixture fixture,
PhysicsComponent body,
IBroadPhase tree,
@@ -417,7 +419,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
for (var i = 0; i < count; i++)
{
var bounds = fixture.Shape.ComputeAABB(broadphaseTransform, i);
var proxy = new FixtureProxy(uid, body, bounds, fixture, i);
var proxy = new FixtureProxy(uid, body, bounds, fixtureId, fixture, i);
proxy.ProxyId = tree.AddProxy(ref proxy);
proxy.AABB = bounds;
proxies[i] = proxy;

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Robust.Shared.Collections;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -95,12 +96,16 @@ namespace Robust.Shared.GameObjects
return;
}
var fixtures = new List<Fixture>(mapChunks.Count);
var fixtures = new Dictionary<string, Fixture>(mapChunks.Count);
foreach (var (chunk, rectangles) in mapChunks)
{
UpdateFixture(uid, chunk, rectangles, body, manager, xform);
fixtures.AddRange(chunk.Fixtures);
foreach (var (id, fixture) in chunk.Fixtures)
{
fixtures[id] = fixture;
}
}
EntityManager.EventBus.RaiseLocalEvent(uid,new GridFixtureChangeEvent {NewFixtures = fixtures}, true);
@@ -126,7 +131,7 @@ namespace Robust.Shared.GameObjects
// Additionally, we need to handle map deserialization where content may have stored its own data
// on the grid (e.g. mass) which we want to preserve.
var newFixtures = new List<Fixture>();
var newFixtures = new ValueList<(string Id, Fixture Fixture)>();
Span<Vector2> vertices = stackalloc Vector2[4];
@@ -144,21 +149,20 @@ namespace Robust.Shared.GameObjects
#pragma warning disable CS0618
var newFixture = new Fixture(
$"grid_chunk-{bounds.Left}-{bounds.Bottom}",
poly,
MapGridHelpers.CollisionGroup,
MapGridHelpers.CollisionGroup,
true) { Body = body};
#pragma warning restore CS0618
newFixtures.Add(newFixture);
newFixtures.Add(($"grid_chunk-{bounds.Left}-{bounds.Bottom}", newFixture));
}
var toRemove = new RemQueue<Fixture>();
var toRemove = new ValueList<(string Id, Fixture Fixture)>();
// Check if we even need to issue an eventbus event
var updated = false;
foreach (var oldFixture in chunk.Fixtures)
foreach (var (oldId, oldFixture) in chunk.Fixtures)
{
var existing = false;
@@ -166,8 +170,10 @@ namespace Robust.Shared.GameObjects
// (TODO: Check IDs and cross-reference for updates?)
for (var i = newFixtures.Count - 1; i >= 0; i--)
{
var fixture = newFixtures[i];
if (!oldFixture.Equals(fixture)) continue;
var fixture = newFixtures[i].Fixture;
if (!oldFixture.Equals(fixture))
continue;
existing = true;
newFixtures.RemoveSwap(i);
break;
@@ -176,36 +182,36 @@ namespace Robust.Shared.GameObjects
// Doesn't align with any new fixtures so delete
if (existing) continue;
toRemove.Add(oldFixture);
toRemove.Add((oldId, oldFixture));
}
foreach (var fixture in toRemove)
foreach (var (id, fixture) in toRemove)
{
// TODO add a DestroyFixture() override that takes in a list.
// reduced broadphase lookups
chunk.Fixtures.Remove(fixture);
_fixtures.DestroyFixture(uid, fixture, false, body: body, manager: manager, xform: xform);
chunk.Fixtures.Remove(id);
_fixtures.DestroyFixture(uid, id, fixture, false, body: body, manager: manager, xform: xform);
}
if (newFixtures.Count > 0 || toRemove.List?.Count > 0)
if (newFixtures.Count > 0 || toRemove.Count > 0)
{
updated = true;
}
// Anything remaining is a new fixture (or at least, may have not serialized onto the chunk yet).
foreach (var fixture in newFixtures)
foreach (var (id, fixture) in newFixtures)
{
var existingFixture = _fixtures.GetFixtureOrNull(uid, fixture.ID, manager: manager);
var existingFixture = _fixtures.GetFixtureOrNull(uid, id, manager: manager);
// Check if it's the same (otherwise remove anyway).
if (existingFixture?.Shape is PolygonShape poly &&
poly.EqualsApprox((PolygonShape) fixture.Shape))
{
chunk.Fixtures.Add(existingFixture);
chunk.Fixtures.Add(id, existingFixture);
continue;
}
chunk.Fixtures.Add(fixture);
_fixtures.CreateFixture(uid, fixture, false, manager, body, xform);
chunk.Fixtures.Add(id, fixture);
_fixtures.CreateFixture(uid, id, fixture, false, manager, body, xform);
}
return updated;
@@ -218,7 +224,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
public sealed class GridFixtureChangeEvent : EntityEventArgs
{
public List<Fixture> NewFixtures { get; init; } = default!;
public Dictionary<string, Fixture> NewFixtures { get; init; } = default!;
}
[Serializable, NetSerializable]

Some files were not shown because too many files have changed in this diff Show More