Compare commits

..

2 Commits

Author SHA1 Message Date
Pieter-Jan Briers
420f690a77 Version: 210.1.2 2024-03-10 21:09:05 +01:00
Pieter-Jan Briers
612320b947 YIPPEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE IMAGESHARP VULNERABILITY
(cherry picked from commit 859f150404)
2024-03-10 21:09:05 +01:00
115 changed files with 599 additions and 4649 deletions

View File

@@ -33,10 +33,10 @@ jobs:
mkdir "release/${{ steps.parse_version.outputs.version }}"
mv release/*.zip "release/${{ steps.parse_version.outputs.version }}"
- name: Upload files to Suns
- name: Upload files to centcomm
uses: appleboy/scp-action@master
with:
host: suns.spacestation14.com
host: centcomm.spacestation14.io
username: robust-build-push
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
source: "release/${{ steps.parse_version.outputs.version }}"
@@ -46,7 +46,7 @@ jobs:
- name: Update manifest JSON
uses: appleboy/ssh-action@master
with:
host: suns.spacestation14.com
host: centcomm.spacestation14.io
username: robust-build-push
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
script: /home/robust-build-push/push.ps1 ${{ steps.parse_version.outputs.version }}

View File

@@ -1,14 +1,4 @@
<Project>
<PropertyGroup>
<!--
We actually set ManagePackageVersionsCentrally manually in another import file.
Since .NET SDK 8.0.300, ManagePackageVersionsCentrally is automatically set if Directory.Packages.props exists.
https://github.com/NuGet/NuGet.Client/pull/5572
We actively negate this here, as we have some packages in tree we don't want such automatic behavior for.
We use Directory.Build.props to get copy the state *after* our MSBuild config but before Nuget's config.
-->
<ManagePackageVersionsCentrally />
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
@@ -55,20 +45,17 @@
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.3" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.0" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageVersion Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageVersion Include="System.Memory" Version="4.5.5" />
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.22621.5" />
<PackageVersion Include="TerraFX.Interop.Xlib" Version="6.4.0" />
<PackageVersion Include="VorbisPizza" Version="1.3.0" />
<PackageVersion Include="YamlDotNet" Version="13.7.1" />
<PackageVersion Include="prometheus-net" Version="8.2.1" />
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
<PackageVersion Include="PolySharp" Version="1.14.1" />
</ItemGroup>
</Project>

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

@@ -24,16 +24,12 @@
<RobustInjectorsConfiguration>$(Configuration)</RobustInjectorsConfiguration>
<RobustInjectorsConfiguration Condition="'$(Configuration)' == 'DebugOpt'">Debug</RobustInjectorsConfiguration>
<RobustInjectorsConfiguration Condition="'$(Configuration)' == 'Tools'">Release</RobustInjectorsConfiguration>
<RobustInjectorsConfiguration Condition="'$(UseArtifactsOutput)' == 'true' And '$(RuntimeIdentifier)' != ''">$(RobustInjectorsConfiguration)_$(RuntimeIdentifier)</RobustInjectorsConfiguration>
<RobustInjectorsConfiguration Condition="'$(UseArtifactsOutput)' == 'true'">$(RobustInjectorsConfiguration.ToLower())</RobustInjectorsConfiguration>
<CompileRobustXamlTaskAssemblyFile Condition="'$(UseArtifactsOutput)' != 'true'">$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\bin\$(RobustInjectorsConfiguration)\netstandard2.0\Robust.Client.Injectors.dll</CompileRobustXamlTaskAssemblyFile>
<CompileRobustXamlTaskAssemblyFile Condition="'$(UseArtifactsOutput)' == 'true'">$(MSBuildThisFileDirectory)\..\..\artifacts\bin\Robust.Client.Injectors\$(RobustInjectorsConfiguration)\Robust.Client.Injectors.dll</CompileRobustXamlTaskAssemblyFile>
</PropertyGroup>
<UsingTask
Condition="'$(_RobustUseExternalMSBuild)' != 'true' And $(DesignTimeBuild) != true"
TaskName="CompileRobustXamlTask"
AssemblyFile="$(CompileRobustXamlTaskAssemblyFile)"/>
AssemblyFile="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\bin\$(RobustInjectorsConfiguration)\netstandard2.0\Robust.Client.Injectors.dll"/>
<Target
Name="CompileRobustXaml"
Condition="Exists('@(IntermediateAssembly)')"

View File

@@ -54,164 +54,7 @@ END TEMPLATE-->
*None yet*
## 214.2.2
## 214.2.1
## 214.2.0
### New features
* Added a `Undetachable` entity metadata flag, which stops the client from moving an entity to nullspace when it moves out of PVS range.
### Bugfixes
* Fix tooltips not clamping to the left side of the viewport.
* Fix global audio property not being properly set.
### Internal
* The server game state / PVS code has been rewritten. It should be somewhat faster now, albeit at the cost of using more memory. The current engine version may be unstable.
## 214.1.1
### Bugfixes
* Fixed connection denial always causing redial.
## 214.1.0
### New features
* Added the `pvs_override_info` command for debugging PVS overrides.
### Bugfixes
* Fix VV for prototype structs.
* Fix audio limits for clientside audio.
## 214.0.0
### Breaking changes
* `NetStructuredDisconnectMessages` has received a complete overhaul and has been moved to `NetDisconnectMessage`. The API is no longer designed such that consumers must pass around JSON nodes, as they are not in sandbox (and clunky).
### New features
* Add a basic default concurrent audio limit of 16 for a single filepath to avoid overflowing audio sources.
* `NetConnectingArgs.Deny()` can now pass along structured data that will be received by the client.
### Bugfixes
* Fixed cursor position bugs when an empty `TextEdit` has a multi-line place holder.
* Fixed empty `TextEdit` throwing exception if cursor is moved left.
## 213.0.0
### Breaking changes
* Remove obsoleted BaseContainer methods.
### New features
* Add EntityManager.RaiseSharedEvent where the event won't go to the attached client but will be predicted locally on their end.
* Add GetEntitiesInRange override that takes in EntityCoordinates and an EntityUid hashset.
### Bugfixes
* Check if a sprite entity is deleted before drawing in SpriteView.
## 212.2.0
### New features
* Add IsHardCollidable to SharedPhysicsSystem to determine if 2 entities would collide.
### Other
* Double the default maximum replay size.
## 212.1.0
### New features
* Add nullable methods for TryIndex / HasIndex on IPrototypeManager.
### Bugfixes
* Fix TextureRect alignment where the strech mode is KeepCentered.
## 212.0.1
### Bugfixes
* Fix passing array by `this` instead of by `ref`.
## 212.0.0
### Breaking changes
* Change Collapsible controls default orientations to Vertical.
### New features
* Expose the Label control for Collapsible controls.
* Add GetGridPosition that considers physics center-of-mass.
* Add TileToVector methods to get the LocalPosition of tile-coords (taking into account tile size).
* Add some more helper methods to PVS filters around EntityUids.
* Add support for Dictionary AutoNetworkedFields.
* Add EnsureLength method for arrays.
* Add PushMarkup to FormattedMessage.
* Add DrawPrimitives overload for `List<Vector2>`
* Add more ValueList ctors that are faster.
* Add ToMapCoordinates method for NetCoordinates.
### Other
* Remove ISerializationHooks obsoletion as they are useful in some rare cases.
### Internal
* Bump max pool size for robust jobs.
## 211.0.2
### Bugfixes
* Fix TextureRect scaling not handling UIScale correctly.
## 211.0.1
### Bugfixes
* Fix GridChunkEnumerator on maps.
## 211.0.0
### Breaking changes
* Moved ChunkIndicesEnumerator to engine and to a re-useable namespace at Robust.Shared/Maps.
### New features
* Added an Enlarged method for Box2Rotated.
### Internal
* Significantly optimise ChunkEnumerator / FindGridsIntersecting in certain use cases by intersecting the grid's AABB with the local AABB to avoid iterating dummy chunks.
## 210.1.2
## 210.1.1

View File

@@ -490,7 +490,7 @@ cmd-net_entityreport-help = Usage: net_entityreport
cmd-net_refresh-desc = Requests a full server state.
cmd-net_refresh-help = Usage: net_refresh
cmd-net_graph-desc = Toggles the net statistics panel.
cmd-net_graph-desc = Toggles the net statistics pannel.
cmd-net_graph-help = Usage: net_graph
cmd-net_watchent-desc = Dumps all network updates for an EntityId to the console.
@@ -566,9 +566,3 @@ cmd-reloadtiletextures-help = Usage: reloadtiletextures
cmd-audio_length-desc = Shows the length of an audio file
cmd-audio_length-help = Usage: audio_length { cmd-audio_length-arg-file-name }
cmd-audio_length-arg-file-name = <file name>
## PVS
cmd-pvs-override-info-desc = Prints information about any PVS overrides associated with an entity.
cmd-pvs-override-info-empty = Entity {$nuid} has no PVS overrides.
cmd-pvs-override-info-global = Entity {$nuid} has a global override.
cmd-pvs-override-info-clients = Entity {$nuid} has a session override for {$clients}.

View File

@@ -70,9 +70,9 @@ command-description-ls-in =
command-description-methods-get =
Returns all methods associated with the input type.
command-description-methods-overrides =
Returns all methods overridden on the input type.
Returns all methods overriden on the input type.
command-description-methods-overridesfrom =
Returns all methods overridden from the given type on the input type.
Returns all methods overriden from the given type on the input type.
command-description-cmd-moo =
Asks the important questions.
command-description-cmd-descloc =
@@ -418,6 +418,6 @@ command-description-tee =
This essentially lets you have a branch in your code to do multiple operations on one value.
command-description-cmd-info =
Returns a CommandSpec for the given command.
On its own, this means it'll print the command's help message.
On it's own, this means it'll print the comamnd's help message.
command-description-comp-rm =
Removes the given component from the entity.

View File

@@ -1,8 +0,0 @@
// OH BOY. TURNS OUT IT GETS EVEN MORE CURSED.
//
// So because we're compiling a copy of Robust.Roslyn.Shared into every analyzer project,
// the test project sees multiple copies of it. This would make it impossible to use.
// UNLESS you use this obscure C# feature called "extern alias"
// that I guarantee you you've never heard of before, and are now concerned about.
extern alias SerializationGenerator;

View File

@@ -1,340 +0,0 @@
extern alias SerializationGenerator;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using NUnit.Framework;
using SerializationGenerator::Robust.Roslyn.Shared;
using SerializationGenerator::Robust.Serialization.Generator;
namespace Robust.Analyzers.Tests;
[TestFixture]
[TestOf(typeof(ComponentPauseGenerator))]
[Parallelizable(ParallelScope.All)]
public sealed class ComponentPauseGeneratorTest
{
private const string TypesCode = """
global using System;
global using Robust.Shared.Analyzers;
global using Robust.Shared.GameObjects;
namespace Robust.Shared.Analyzers
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class AutoGenerateComponentPauseAttribute : Attribute
{
public bool Dirty = false;
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class AutoPausedFieldAttribute : Attribute;
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class AutoNetworkedFieldAttribute : Attribute
{
}
}
namespace Robust.Shared.GameObjects
{
public interface IComponent;
}
""";
[Test]
public void TestBasic()
{
var result = RunGenerator("""
[AutoGenerateComponentPause]
public sealed partial class FooComponent : IComponent
{
[AutoPausedField]
public TimeSpan Foo;
}
""");
ExpectNoDiagnostics(result);
ExpectSource(
result,
"""
// <auto-generated />
using Robust.Shared.GameObjects;
public partial class FooComponent
{
[RobustAutoGenerated]
public sealed class FooComponent_AutoPauseSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
}
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
{
component.Foo += args.PausedTime;
}
}
}
""");
}
[Test]
public void TestNullable()
{
var result = RunGenerator("""
[AutoGenerateComponentPause]
public sealed partial class FooComponent : IComponent
{
[AutoPausedField]
public TimeSpan? Foo;
}
""");
ExpectNoDiagnostics(result);
ExpectSource(
result,
"""
// <auto-generated />
using Robust.Shared.GameObjects;
public partial class FooComponent
{
[RobustAutoGenerated]
public sealed class FooComponent_AutoPauseSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
}
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
{
if (component.Foo.HasValue)
component.Foo = component.Foo.Value + args.PausedTime;
}
}
}
""");
}
[Test]
public void TestAutoState()
{
var result = RunGenerator("""
[AutoGenerateComponentPause]
public sealed partial class FooComponent : IComponent
{
[AutoPausedField, AutoNetworkedField]
public TimeSpan Foo;
}
""");
ExpectNoDiagnostics(result);
ExpectSource(
result,
"""
// <auto-generated />
using Robust.Shared.GameObjects;
public partial class FooComponent
{
[RobustAutoGenerated]
public sealed class FooComponent_AutoPauseSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
}
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
{
component.Foo += args.PausedTime;
Dirty(uid, component);
}
}
}
""");
}
[Test]
public void TestExplicitDirty()
{
var result = RunGenerator("""
[AutoGenerateComponentPause(Dirty = true)]
public sealed partial class FooComponent : IComponent
{
[AutoPausedField]
public TimeSpan Foo;
}
""");
ExpectNoDiagnostics(result);
ExpectSource(
result,
"""
// <auto-generated />
using Robust.Shared.GameObjects;
public partial class FooComponent
{
[RobustAutoGenerated]
public sealed class FooComponent_AutoPauseSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
}
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
{
component.Foo += args.PausedTime;
Dirty(uid, component);
}
}
}
""");
}
[Test]
public void TestDiagnosticNotIComponent()
{
var result = RunGenerator("""
[AutoGenerateComponentPause]
public sealed partial class FooComponent
{
[AutoPausedField]
public TimeSpan Foo;
}
""");
ExpectNoSource(result);
ExpectDiagnostics(result, [
(Diagnostics.IdComponentPauseNotComponent, new LinePositionSpan(new LinePosition(1, 28), new LinePosition(1, 40)))
]);
}
[Test]
public void TestDiagnosticNoFields()
{
var result = RunGenerator("""
[AutoGenerateComponentPause]
public sealed partial class FooComponent : IComponent
{
public TimeSpan Foo;
}
""");
ExpectNoSource(result);
ExpectDiagnostics(result, [
(Diagnostics.IdComponentPauseNoFields, new LinePositionSpan(new LinePosition(1, 28), new LinePosition(1, 40)))
]);
}
[Test]
public void TestDiagnosticNoParentAttribute()
{
var result = RunGenerator("""
public sealed partial class FooComponent : IComponent
{
[AutoPausedField]
public TimeSpan Foo, Fooz;
[AutoPausedField]
public TimeSpan Bar { get; set; }
}
""");
ExpectNoSource(result);
ExpectDiagnostics(result, [
(Diagnostics.IdComponentPauseNoParentAttribute, new LinePositionSpan(new LinePosition(3, 20), new LinePosition(3, 23))),
(Diagnostics.IdComponentPauseNoParentAttribute, new LinePositionSpan(new LinePosition(3, 25), new LinePosition(3, 29))),
(Diagnostics.IdComponentPauseNoParentAttribute, new LinePositionSpan(new LinePosition(6, 20), new LinePosition(6, 23)))
]);
}
[Test]
public void TestDiagnosticWrongType()
{
var result = RunGenerator("""
[AutoGenerateComponentPause]
public sealed partial class FooComponent : IComponent
{
[AutoPausedField]
public int Foo, Fooz;
[AutoPausedField]
public int Bar { get; set; }
}
""");
ExpectNoSource(result);
ExpectDiagnostics(result, [
(Diagnostics.IdComponentPauseWrongTypeAttribute, new LinePositionSpan(new LinePosition(4, 15), new LinePosition(4, 18))),
(Diagnostics.IdComponentPauseWrongTypeAttribute, new LinePositionSpan(new LinePosition(4, 20), new LinePosition(4, 24))),
(Diagnostics.IdComponentPauseWrongTypeAttribute, new LinePositionSpan(new LinePosition(7, 15), new LinePosition(7, 18)))
]);
}
private static void ExpectSource(GeneratorRunResult result, string expected)
{
Assert.That(result.GeneratedSources, Has.Length.EqualTo(1));
var source = result.GeneratedSources[0];
Assert.That(source.SourceText.ToString(), Is.EqualTo(expected));
}
private static void ExpectNoSource(GeneratorRunResult result)
{
Assert.That(result.GeneratedSources, Is.Empty);
}
private static void ExpectNoDiagnostics(GeneratorRunResult result)
{
Assert.That(result.Diagnostics, Is.Empty);
}
private static void ExpectDiagnostics(GeneratorRunResult result, (string code, LinePositionSpan span)[] diagnostics)
{
Assert.Multiple(() =>
{
Assert.That(result.Diagnostics, Has.Length.EqualTo(diagnostics.Length));
foreach (var (code, span) in diagnostics)
{
Assert.That(
result.Diagnostics.Any(x => x.Id == code && x.Location.GetLineSpan().Span == span),
$"Expected diagnostic with code {code} and location {span}");
}
});
}
private static GeneratorRunResult RunGenerator(string source)
{
var compilation = (Compilation)CSharpCompilation.Create("compilation",
new[]
{
CSharpSyntaxTree.ParseText(source, path: "Source.cs"),
CSharpSyntaxTree.ParseText(TypesCode, path: "Types.cs")
},
new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) },
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
var generator = new ComponentPauseGenerator();
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out _);
var result = driver.GetRunResult();
return result.Results[0];
}
}

View File

@@ -1,8 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<SkipRobustAnalyzer>true</SkipRobustAnalyzer>
</PropertyGroup>
<Import Project="..\MSBuild\Robust.Properties.targets"/>
<Import Project="..\MSBuild\Robust.Engine.props"/>
@@ -27,6 +23,5 @@
<ItemGroup>
<ProjectReference Include="..\Robust.Analyzers\Robust.Analyzers.csproj"/>
<ProjectReference Include="..\Robust.Serialization.Generator\Robust.Serialization.Generator.csproj" Aliases="SerializationGenerator" />
</ItemGroup>
</Project>

View File

@@ -5,7 +5,6 @@ using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
using Robust.Shared.Analyzers.Implementation;
namespace Robust.Analyzers

View File

@@ -4,7 +4,6 @@ using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
using static Microsoft.CodeAnalysis.SymbolEqualityComparer;
namespace Robust.Analyzers;
@@ -17,7 +16,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor ByRefEventSubscribedByValueRule = new(
Diagnostics.IdByRefEventSubscribedByValue,
"By-ref event subscribed to by value",
"Tried to subscribe to a by-ref event '{0}' by value",
"Tried to subscribe to a by-ref event '{0}' by value.",
"Usage",
DiagnosticSeverity.Error,
true,
@@ -27,7 +26,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor ByRefEventRaisedByValueRule = new(
Diagnostics.IdByRefEventRaisedByValue,
"By-ref event raised by value",
"Tried to raise a by-ref event '{0}' by value",
"Tried to raise a by-ref event '{0}' by value.",
"Usage",
DiagnosticSeverity.Error,
true,
@@ -37,7 +36,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor ByValueEventRaisedByRefRule = new(
Diagnostics.IdValueEventRaisedByRef,
"Value event raised by-ref",
"Tried to raise a value event '{0}' by-ref",
"Tried to raise a value event '{0}' by-ref.",
"Usage",
DiagnosticSeverity.Error,
true,

View File

@@ -5,7 +5,6 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
@@ -19,7 +18,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor DataDefinitionPartialRule = new(
Diagnostics.IdDataDefinitionPartial,
"Type must be partial",
"Type {0} is a DataDefinition but is not partial",
"Type {0} is a DataDefinition but is not partial.",
"Usage",
DiagnosticSeverity.Error,
true,
@@ -29,7 +28,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor NestedDataDefinitionPartialRule = new(
Diagnostics.IdNestedDataDefinitionPartial,
"Type must be partial",
"Type {0} contains nested data definition {1} but is not partial",
"Type {0} contains nested data definition {1} but is not partial.",
"Usage",
DiagnosticSeverity.Error,
true,
@@ -39,7 +38,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor DataFieldWritableRule = new(
Diagnostics.IdDataFieldWritable,
"Data field must not be readonly",
"Data field {0} in data definition {1} is readonly",
"Data field {0} in data definition {1} is readonly.",
"Usage",
DiagnosticSeverity.Error,
true,
@@ -49,7 +48,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
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",
"Data field property {0} in data definition {1} does not have a setter.",
"Usage",
DiagnosticSeverity.Error,
true,

View File

@@ -9,7 +9,7 @@ using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxKind;
using static Robust.Roslyn.Shared.Diagnostics;
using static Robust.Analyzers.Diagnostics;
namespace Robust.Analyzers;

View File

@@ -1,6 +1,6 @@
using Microsoft.CodeAnalysis;
namespace Robust.Roslyn.Shared;
namespace Robust.Analyzers;
public static class Diagnostics
{
@@ -24,10 +24,6 @@ public static class Diagnostics
public const string IdNestedDataDefinitionPartial = "RA0018";
public const string IdDataFieldWritable = "RA0019";
public const string IdDataFieldPropertyWritable = "RA0020";
public const string IdComponentPauseNotComponent = "RA0021";
public const string IdComponentPauseNoFields = "RA0022";
public const string IdComponentPauseNoParentAttribute = "RA0023";
public const string IdComponentPauseWrongTypeAttribute = "RA0024";
public static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");

View File

@@ -10,7 +10,6 @@ using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
using Document = Microsoft.CodeAnalysis.Document;
namespace Robust.Analyzers

View File

@@ -5,7 +5,6 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;

View File

@@ -2,7 +2,6 @@ using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers
{

View File

@@ -3,7 +3,6 @@ using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
@@ -32,7 +31,7 @@ public sealed class NotNullableFlagAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor InvalidNotNullableImplementationRule = new (
Diagnostics.IdInvalidNotNullableFlagImplementation,
"Invalid NotNullable flag implementation",
"Invalid NotNullable flag implementation.",
"NotNullable flag is either not typed as bool, or does not have a default value equaling false",
"Usage",
DiagnosticSeverity.Error,
@@ -42,7 +41,7 @@ public sealed class NotNullableFlagAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor InvalidNotNullableTypeRule = new (
Diagnostics.IdInvalidNotNullableFlagType,
"Failed to resolve type parameter",
"Failed to resolve type parameter \"{0}\"",
"Failed to resolve type parameter \"{0}\".",
"Usage",
DiagnosticSeverity.Error,
true,
@@ -50,7 +49,7 @@ public sealed class NotNullableFlagAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor NotNullableFlagValueTypeRule = new (
Diagnostics.IdNotNullableFlagValueType,
"NotNullable flag not supported for value types",
"NotNullable flag not supported for value types.",
"Value types as generic arguments are not supported for NotNullable flags",
"Usage",
DiagnosticSeverity.Error,

View File

@@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;

View File

@@ -1,5 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>10</LangVersion>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" />
</ItemGroup>
<ItemGroup>
<!-- Needed for NotNullableFlagAnalyzer. -->
<Compile Include="..\Robust.Shared\Analyzers\NotNullableFlagAttribute.cs" />
@@ -16,10 +28,4 @@
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" />
</ItemGroup>
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
<PropertyGroup>
<Nullable>disable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -10,7 +10,6 @@ using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers
{

View File

@@ -3,7 +3,6 @@ using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;

View File

@@ -1,17 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<Compile Link="XamlX\filename" Include="../XamlX/src/XamlX/**/*.cs" />
<Compile Remove="../XamlX/src/XamlX/**/SreTypeSystem.cs" />
<Compile Remove="../XamlX/src/XamlX/obj/**" />
<Compile Include="..\Robust.Client\UserInterface\ControlPropertyAccess.cs" />
</ItemGroup>
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
<PropertyGroup>
<!-- XamlX doesn't do NRTs. -->
<Nullable>disable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -1,57 +0,0 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Robust.Shared;
using Robust.Shared.GameObjects;
namespace Robust.Client.Audio;
public sealed partial class AudioSystem
{
/*
* Handles limiting concurrent sounds for audio to avoid blowing the source budget on one sound getting spammed.
*/
private readonly Dictionary<string, int> _playingCount = new();
private int _maxConcurrent;
private void InitializeLimit()
{
Subs.CVar(CfgManager, CVars.AudioDefaultConcurrent, SetConcurrentLimit, true);
}
private void SetConcurrentLimit(int obj)
{
_maxConcurrent = obj;
}
private bool TryAudioLimit(string sound)
{
if (string.IsNullOrEmpty(sound))
return true;
ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(_playingCount, sound, out _);
if (count >= _maxConcurrent)
return false;
count++;
return true;
}
private void RemoveAudioLimit(string sound)
{
if (!_playingCount.TryGetValue(sound, out var count))
return;
count--;
if (count <= 0)
{
_playingCount.Remove(sound);
return;
}
_playingCount[sound] = count;
}
}

View File

@@ -41,7 +41,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
[Dependency] private readonly IParallelManager _parMan = default!;
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
[Dependency] private readonly IAudioInternal _audio = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!;
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
@@ -52,7 +51,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
private EntityUid? _listenerGrid;
private UpdateAudioJob _updateAudioJob;
private EntityQuery<PhysicsComponent> _physicsQuery;
private float _maxRayLength;
@@ -110,7 +108,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
Subs.CVar(CfgManager, CVars.AudioAttenuation, OnAudioAttenuation, true);
Subs.CVar(CfgManager, CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
InitializeLimit();
}
private void OnAudioState(EntityUid uid, AudioComponent component, ref AfterAutoHandleStateEvent args)
@@ -166,37 +163,26 @@ public sealed partial class AudioSystem : SharedAudioSystem
return;
}
SetupSource((uid, component), audioResource);
SetupSource(component, audioResource);
component.Loaded = true;
}
private void SetupSource(Entity<AudioComponent> entity, AudioResource audioResource, TimeSpan? length = null)
private void SetupSource(AudioComponent component, AudioResource audioResource, TimeSpan? length = null)
{
var component = entity.Comp;
if (TryAudioLimit(component.FileName))
{
var newSource = _audio.CreateAudioSource(audioResource);
var source = _audio.CreateAudioSource(audioResource);
if (newSource == null)
{
Log.Error($"Error creating audio source for {audioResource}");
DebugTools.Assert(false);
}
else
{
component.Source = newSource;
}
if (source == null)
{
Log.Error($"Error creating audio source for {audioResource}");
DebugTools.Assert(false);
source = component.Source;
}
if ((component.Flags & AudioFlags.GridAudio) != 0x0)
{
_metadata.SetFlag(entity.Owner, MetaDataFlags.Undetachable, true);
}
component.Source = source;
// Need to set all initial data for first frame.
ApplyAudioParams(component.Params, component);
component.Source.Global = component.Global;
source.Global = component.Global;
// Don't play until first frame so occlusion etc. are correct.
component.Gain = 0f;
@@ -216,8 +202,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
{
// Breaks with prediction?
component.Source.Dispose();
RemoveAudioLimit(component.FileName);
}
private void OnAudioAttenuation(int obj)
@@ -592,13 +576,13 @@ public sealed partial class AudioSystem : SharedAudioSystem
return PlayGlobal(filename, audioParams);
}
public override void LoadStream<T>(Entity<AudioComponent> entity, T stream)
public override void LoadStream<T>(AudioComponent component, T stream)
{
if (stream is AudioStream audioStream)
{
TryGetAudio(audioStream, out var audio);
SetupSource(entity, audio!, audioStream.Length);
entity.Comp.Loaded = true;
SetupSource(component, audio!, audioStream.Length);
component.Loaded = true;
}
}
@@ -637,7 +621,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
var audioP = audioParams ?? AudioParams.Default;
var entity = EntityManager.CreateEntityUninitialized("Audio", MapCoordinates.Nullspace);
var comp = SetupAudio(entity, null, audioP, stream.Length);
LoadStream((entity, comp), stream);
LoadStream(comp, stream);
EntityManager.InitializeAndStartEntity(entity);
var source = comp.Source;

View File

@@ -11,7 +11,7 @@ namespace Robust.Client.Console.Commands
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var type = GetType(args[0]);
var type = Type.GetType(args[0]);
if (type == null)
{
@@ -25,17 +25,6 @@ namespace Robust.Client.Console.Commands
shell.WriteLine(sig);
}
}
private Type? GetType(string name)
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.GetType(name) is { } type)
return type;
}
return null;
}
}
#endif
}

View File

@@ -165,7 +165,7 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
{
var textEdit = new TextEdit
{
Placeholder = new Rope.Leaf("You deleted the lipsum\nOwO")
Placeholder = new Rope.Leaf("You deleted the lipsum OwO")
};
TabContainer.SetTabTitle(textEdit, "TextEdit");

View File

@@ -8,7 +8,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Player;
using Robust.Shared.Replays;
using Robust.Shared.Utility;
@@ -135,24 +134,6 @@ namespace Robust.Client.GameObjects
EventBus.RaiseEvent(EventSource.Local, new EntitySessionMessage<T>(eventArgs, msg));
}
/// <inheritdoc />
public override void RaiseSharedEvent<T>(T message, EntityUid? user = null)
{
if (user == null || user != _playerManager.LocalEntity || !_gameTiming.IsFirstTimePredicted)
return;
EventBus.RaiseEvent(EventSource.Local, ref message);
}
/// <inheritdoc />
public override void RaiseSharedEvent<T>(T message, ICommonSession? user = null)
{
if (user == null || user != _playerManager.LocalSession || !_gameTiming.IsFirstTimePredicted)
return;
EventBus.RaiseEvent(EventSource.Local, ref message);
}
#region IEntityNetworkManager impl
public override IEntityNetworkManager EntityNetManager => this;

View File

@@ -1120,7 +1120,7 @@ namespace Robust.Client.GameStates
continue;
}
if ((meta.Flags & (MetaDataFlags.Detached | MetaDataFlags.Undetachable)) != 0)
if ((meta.Flags & MetaDataFlags.Detached) != 0)
continue;
if (lastStateApplied.HasValue)

View File

@@ -1201,7 +1201,7 @@ namespace Robust.Client.Graphics.Clyde
private void LightResolutionScaleChanged(float newValue)
{
_lightResolutionScale = newValue > 0.05f ? newValue : 0.05f;
_lightResolutionScale = newValue;
RegenAllLightRts();
}

View File

@@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics
@@ -64,19 +63,6 @@ namespace Robust.Client.Graphics
// ---- DrawPrimitives: Vector2 API ----
/// <summary>
/// Draws arbitrary geometry primitives with a flat color.
/// </summary>
/// <param name="primitiveTopology">The topology of the primitives to draw.</param>
/// <param name="vertices">The list of vertices to render.</param>
/// <param name="color">The color to draw with.</param>
public void DrawPrimitives(DrawPrimitiveTopology primitiveTopology, List<Vector2> vertices,
Color color)
{
var span = CollectionsMarshal.AsSpan(vertices);
DrawPrimitives(primitiveTopology, span, color);
}
/// <summary>
/// Draws arbitrary geometry primitives with a flat color.
/// </summary>
@@ -114,43 +100,12 @@ namespace Robust.Client.Graphics
DrawPrimitives(primitiveTopology, White, indices, drawVertices);
}
private static void PadVerticesV2(ReadOnlySpan<Vector2> input, Span<DrawVertexUV2DColor> output, Color color)
private void PadVerticesV2(ReadOnlySpan<Vector2> input, Span<DrawVertexUV2DColor> output, Color color)
{
if (input.Length == 0)
return;
if (input.Length != output.Length)
Color colorLinear = Color.FromSrgb(color);
for (var i = 0; i < output.Length; i++)
{
throw new InvalidOperationException("Invalid lengths!");
}
var colorLinear = Color.FromSrgb(color);
var colorVec = Unsafe.As<Color, Vector128<float>>(ref colorLinear);
var uvVec = Vector128.Create(0, 0, 0.5f, 0.5f);
var maskVec = Vector128.Create(0xFFFFFFFF, 0xFFFFFFFF, 0, 0).AsSingle();
var simdVectors = (nuint)(input.Length / 2);
ref readonly var srcBase = ref Unsafe.As<Vector2, float>(ref Unsafe.AsRef(in input[0]));
ref var dstBase = ref Unsafe.As<DrawVertexUV2DColor, float>(ref output[0]);
for (nuint i = 0; i < simdVectors; i++)
{
var positions = Vector128.LoadUnsafe(in srcBase, i * 4);
var posColorLower = (positions & maskVec) | uvVec;
var posColorUpper = (Vector128.Shuffle(positions, Vector128.Create(2, 3, 0, 0)) & maskVec) | uvVec;
posColorLower.StoreUnsafe(ref dstBase, i * 16);
colorVec.StoreUnsafe(ref dstBase, i * 16 + 4);
posColorUpper.StoreUnsafe(ref dstBase, i * 16 + 8);
colorVec.StoreUnsafe(ref dstBase, i * 16 + 12);
}
var lastPos = (int)simdVectors * 2;
if (lastPos != output.Length)
{
// Odd number of vertices. Handle the last manually.
output[lastPos] = new DrawVertexUV2DColor(input[lastPos], new Vector2(0.5f, 0.5f), colorLinear);
output[i] = new DrawVertexUV2DColor(input[i], new Vector2(0.5f, 0.5f), colorLinear);
}
}

View File

@@ -29,11 +29,9 @@ namespace Robust.Client.UserInterface.Controls
}
public Collapsible()
{
Orientation = LayoutOrientation.Vertical;
}
{}
public Collapsible(CollapsibleHeading header, CollapsibleBody body) : this()
public Collapsible(CollapsibleHeading header, CollapsibleBody body)
{
AddChild(header);
AddChild(body);
@@ -41,9 +39,12 @@ namespace Robust.Client.UserInterface.Controls
Initialize();
}
public Collapsible(string title, CollapsibleBody body) : this(new CollapsibleHeading(title), body)
public Collapsible(string title, CollapsibleBody body)
{
AddChild(new CollapsibleHeading(title));
AddChild(body);
Initialize();
}
protected internal override void Draw(DrawingHandleScreen handle)
@@ -104,15 +105,11 @@ namespace Robust.Client.UserInterface.Controls
set => _chevron.Margin = value;
}
/// <summary>
/// Exposes the label for this heading.
/// </summary>
public Label Label { get; }
private Label _title = new();
public string? Title
{
get => Label.Text;
set => Label.Text = value;
get => _title.Text;
set => _title.Text = value;
}
public CollapsibleHeading()
@@ -121,8 +118,8 @@ namespace Robust.Client.UserInterface.Controls
var box = new BoxContainer();
AddChild(box);
box.AddChild(_chevron);
Label = new Label();
box.AddChild(Label);
_title = new Label();
box.AddChild(_title);
}
public CollapsibleHeading(string title) : this()

View File

@@ -13,7 +13,6 @@ namespace Robust.Client.UserInterface.Controls
private float _value;
private float _page;
private bool _rounded;
private int _roundingDecimals = 0;
public event Action<Range>? OnValueChanged;
@@ -87,17 +86,6 @@ namespace Robust.Client.UserInterface.Controls
}
}
[ViewVariables]
public int RoundingDecimals
{
get => _roundingDecimals;
set
{
_roundingDecimals = value;
_ensureValueClamped();
}
}
public virtual void SetValueWithoutEvent(float newValue)
{
newValue = ClampValue(newValue);
@@ -119,7 +107,7 @@ namespace Robust.Client.UserInterface.Controls
{
if (_rounded)
{
value = MathF.Round(value, _roundingDecimals);
value = MathF.Round(value);
}
return MathHelper.Clamp(value, _minValue, _maxValue-_page);
}

View File

@@ -20,11 +20,6 @@ namespace Robust.Client.UserInterface.Controls
private bool _suppressScrollValueChanged;
/// <summary>
/// If true then if we have a y-axis scroll it will convert it to an x-axis scroll.
/// </summary>
public bool FallbackDeltaScroll { get; set; } = true;
public int ScrollSpeedX { get; set; } = 50;
public int ScrollSpeedY { get; set; } = 50;
@@ -251,19 +246,9 @@ namespace Robust.Client.UserInterface.Controls
if (_hScrollEnabled)
{
var delta =
args.Delta.X == 0f &&
!_vScrollEnabled &&
FallbackDeltaScroll ?
-args.Delta.Y :
args.Delta.X;
_hScrollBar.ValueTarget += delta * ScrollSpeedX;
_hScrollBar.ValueTarget += args.Delta.X * ScrollSpeedX;
}
if (!_vScrollVisible && !_hScrollVisible)
return;
args.Handle();
}

View File

@@ -16,7 +16,7 @@ namespace Robust.Client.UserInterface.Controls
{
private SpriteSystem? _sprite;
private SharedTransformSystem? _transform;
private readonly IEntityManager _entMan;
IEntityManager _entMan;
[ViewVariables]
public SpriteComponent? Sprite => Entity?.Comp1;
@@ -143,8 +143,6 @@ namespace Robust.Client.UserInterface.Controls
if (netEnt == NetEnt)
return;
// The Entity is getting set later in the ResolveEntity method
// because the client may not have received it yet.
Entity = null;
NetEnt = netEnt;
}
@@ -258,19 +256,28 @@ namespace Robust.Client.UserInterface.Controls
[NotNullWhen(true)] out SpriteComponent? sprite,
[NotNullWhen(true)] out TransformComponent? xform)
{
if (NetEnt != null && Entity == null && _entMan.TryGetEntity(NetEnt, out var ent))
SetEntity(ent);
if (Entity != null)
{
(uid, sprite, xform) = Entity.Value;
return !_entMan.Deleted(uid);
return true;
}
sprite = null;
xform = null;
uid = default;
return false;
if (NetEnt == null)
return false;
if (!_entMan.TryGetEntity(NetEnt, out var ent))
return false;
SetEntity(ent);
if (Entity == null)
return false;
(uid, sprite, xform) = Entity.Value;
return true;
}
}
}

View File

@@ -576,7 +576,7 @@ public sealed class TextEdit : Control
var newPos = CursorShiftedLeft();
// Explicit newlines work kinda funny with bias, so keep it at top there.
var bias = _cursorPosition.Index == TextLength || Rope.Index(TextRope, newPos) == '\n'
var bias = Rope.Index(TextRope, newPos) == '\n'
? LineBreakBias.Top
: LineBreakBias.Bottom;
@@ -940,13 +940,6 @@ public sealed class TextEdit : Control
private CursorPos GetIndexAtHorizontalPos(int line, float horizontalPos)
{
// If the placeholder is visible, this function does not return correct results because it looks at TextRope,
// but _lineBreaks is configured for the display rope.
// Bail out early in this case, the function is not currently used in any situation in any location
// where something else is desired if the placeholder is visible.
if (IsPlaceholderVisible)
return default;
var contentBox = PixelSizeBox;
var font = GetFont();
var uiScale = UIScale;

View File

@@ -17,9 +17,6 @@ namespace Robust.Client.UserInterface.Controls
{
public const string StylePropertyTexture = "texture";
public const string StylePropertyShader = "shader";
public const string StylePropertyTextureStretch = "texture-stretch";
public const string StylePropertyTextureScale = "texture-scale";
public const string StylePropertyTextureSizeTarget = "texture-size-target";
private bool _canShrink;
private Texture? _texture;
@@ -32,18 +29,7 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
public Texture? Texture
{
get
{
if (_texture is null)
{
if (TryGetStyleProperty(StylePropertyTexture, out Texture? texture))
{
return texture;
}
}
return _texture;
}
get => _texture;
set
{
var oldSize = _texture?.Size;
@@ -57,7 +43,6 @@ namespace Robust.Client.UserInterface.Controls
}
private string? _texturePath;
private StretchMode _stretch = StretchMode.Keep;
public string TexturePath
{
@@ -69,45 +54,21 @@ namespace Robust.Client.UserInterface.Controls
}
protected override void StylePropertiesChanged()
{
base.StylePropertiesChanged();
InvalidateMeasure();
}
protected override void OnThemeUpdated()
{
if (_texturePath != null) Texture = Theme.ResolveTexture(_texturePath);
base.OnThemeUpdated();
}
public Vector2 TextureSizeTarget
{
get
{
if (!TryGetStyleProperty(StylePropertyTextureSizeTarget, out Vector2 target))
target = _textureScale * Texture?.Size ?? Vector2.Zero;
return target;
}
}
/// <summary>
/// Scales the texture displayed.
/// </summary>
/// <remarks>
/// This does not apply to the following stretch modes: <see cref="StretchMode.Scale"/>.
/// This additionally does not apply if a size target is set.
/// </remarks>
public Vector2 TextureScale
{
get
{
if (!TryGetStyleProperty(StylePropertyTextureScale, out Vector2 scale))
scale = _textureScale;
return scale;
}
get => _textureScale;
set
{
_textureScale = value;
@@ -135,28 +96,24 @@ namespace Robust.Client.UserInterface.Controls
/// <summary>
/// Controls how the texture should be drawn if the control is larger than the size of the texture.
/// </summary>
public StretchMode Stretch
{
get
{
if (!TryGetStyleProperty(StylePropertyTextureStretch, out StretchMode stretch))
stretch = _stretch;
return stretch;
}
set => _stretch = value;
}
public StretchMode Stretch { get; set; } = StretchMode.Keep;
protected internal override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
var texture = Texture;
if (texture is null)
return;
var texture = _texture;
ShaderInstance? shader = null;
if (texture == null)
{
TryGetStyleProperty(StylePropertyTexture, out texture);
if (texture == null)
{
return;
}
}
if (ShaderOverride != null)
{
shader = ShaderOverride;
@@ -210,17 +167,17 @@ namespace Robust.Client.UserInterface.Controls
case StretchMode.Tile:
// TODO: Implement Tile.
case StretchMode.Keep:
return UIBox2.FromDimensions(Vector2.Zero, TextureSizeTarget * UIScale);
return UIBox2.FromDimensions(Vector2.Zero, texture.Size * _textureScale * UIScale);
case StretchMode.KeepCentered:
{
var position = (Size - TextureSizeTarget) / 2;
return UIBox2.FromDimensions(position, TextureSizeTarget * UIScale);
var position = (PixelSize - texture.Size * _textureScale * UIScale) / 2;
return UIBox2.FromDimensions(position, texture.Size * _textureScale * UIScale);
}
case StretchMode.KeepAspect:
case StretchMode.KeepAspectCentered:
{
var (texWidth, texHeight) = TextureSizeTarget;
var (texWidth, texHeight) = texture.Size * _textureScale;
var width = texWidth * (PixelSize.Y / texHeight);
var height = (float)PixelSize.Y;
if (width > PixelSize.X)
@@ -240,7 +197,7 @@ namespace Robust.Client.UserInterface.Controls
}
case StretchMode.KeepAspectCovered:
var texSize = TextureSizeTarget;
var texSize = texture.Size * _textureScale;
// Calculate the scale necessary to fit width and height to control size.
var (scaleX, scaleY) = PixelSize / texSize;
// Use whichever scale is greater.
@@ -302,10 +259,19 @@ namespace Robust.Client.UserInterface.Controls
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
if (CanShrink || Texture == null)
return Vector2.Zero;
var texture = _texture;
return TextureSizeTarget;
if (texture == null)
{
TryGetStyleProperty(StylePropertyTexture, out texture);
}
if (texture == null || CanShrink)
{
return Vector2.Zero;
}
return texture.Size * TextureScale;
}
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Numerics;
using System.Numerics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
@@ -42,16 +41,20 @@ namespace Robust.Client.UserInterface
tooltip.Measure(Vector2Helpers.Infinity);
var combinedMinSize = tooltip.DesiredSize;
// If it overflows right bounds then just place left on the edge.
var right = MathF.Min(screenPosition.X + combinedMinSize.X, screenBounds.X);
LayoutContainer.SetPosition(tooltip, new Vector2(screenPosition.X, screenPosition.Y - combinedMinSize.Y));
// However, better to clamp the end of the tooltip instead of the start.
var left = MathF.Max(0f, right - combinedMinSize.X);
var right = tooltip.Position.X + combinedMinSize.X;
var top = tooltip.Position.Y;
var bottom = MathF.Min(screenPosition.Y, screenBounds.Y);
var top = MathF.Max(0f, bottom - combinedMinSize.Y);
if (right > screenBounds.X)
{
LayoutContainer.SetPosition(tooltip, new(screenPosition.X - combinedMinSize.X, tooltip.Position.Y));
}
LayoutContainer.SetPosition(tooltip, new Vector2(left, top));
if (top < 0f)
{
LayoutContainer.SetPosition(tooltip, new(tooltip.Position.X, 0f));
}
}
}
}

View File

@@ -126,22 +126,6 @@ namespace Robust.Client.ViewVariables
return new VVPropEditorString();
}
if (type == typeof(EntProtoId) ||
type == typeof(EntProtoId?))
{
return new VVPropEditorEntProtoId();
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ProtoId<>))
{
var editor =
(VVPropEditor)Activator.CreateInstance(
typeof(VVPropEditorProtoId<>).MakeGenericType(type.GenericTypeArguments[0]))!;
IoCManager.InjectDependencies(editor);
return editor;
}
if (typeof(IPrototype).IsAssignableFrom(type) || typeof(ViewVariablesBlobMembers.PrototypeReferenceToken).IsAssignableFrom(type))
{
return (VVPropEditor)Activator.CreateInstance(typeof(VVPropEditorIPrototype<>).MakeGenericType(type))!;

View File

@@ -1,28 +0,0 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Prototypes;
namespace Robust.Client.ViewVariables.Editors;
internal sealed class VVPropEditorEntProtoId : VVPropEditor
{
protected override Control MakeUI(object? value)
{
var lineEdit = new LineEdit
{
Text = (EntProtoId) (value ?? ""),
Editable = !ReadOnly,
HorizontalExpand = true,
};
if (!ReadOnly)
{
lineEdit.OnTextEntered += e =>
{
ValueChanged((EntProtoId) e.Text);
};
}
return lineEdit;
}
}

View File

@@ -1,38 +0,0 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
namespace Robust.Client.ViewVariables.Editors;
internal sealed class VVPropEditorProtoId<T> : VVPropEditor where T : class, IPrototype
{
[Dependency] private readonly IPrototypeManager _protoManager = default!;
protected override Control MakeUI(object? value)
{
var lineEdit = new LineEdit
{
Text = (ProtoId<T>) (value ?? ""),
Editable = !ReadOnly,
HorizontalExpand = true,
};
if (!ReadOnly)
{
lineEdit.OnTextEntered += e =>
{
var id = (ProtoId<T>)e.Text;
if (!_protoManager.HasIndex(id))
{
return;
}
ValueChanged(id);
};
}
return lineEdit;
}
}

View File

@@ -1,46 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
namespace Robust.Roslyn.Shared;
#nullable enable
public static class AttributeHelper
{
public static bool HasAttribute(ISymbol symbol, string attributeMetadataName, [NotNullWhen(true)] out AttributeData? matchedAttribute)
{
foreach (var attribute in symbol.GetAttributes())
{
if (attribute.AttributeClass == null)
continue;
if (TypeSymbolHelper.ShittyTypeMatch(attribute.AttributeClass, attributeMetadataName))
{
matchedAttribute = attribute;
return true;
}
}
matchedAttribute = null;
return false;
}
public static bool GetNamedArgumentBool(AttributeData data, string name, bool defaultValue)
{
foreach (var kv in data.NamedArguments)
{
if (kv.Key != name)
continue;
if (kv.Value.Kind != TypedConstantKind.Primitive)
continue;
if (kv.Value.Value is not bool value)
continue;
return value;
}
return defaultValue;
}
}

View File

@@ -1,201 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// Taken from https://github.com/CommunityToolkit/dotnet/blob/ecd1711b740f4f88d2bb943ce292ae4fc90df1bc/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/EquatableArray%7BT%7D.cs
using System.Collections;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace Robust.Roslyn.Shared.Helpers;
#nullable enable
/// <summary>
/// Extensions for <see cref="EquatableArray{T}"/>.
/// </summary>
public static class EquatableArray
{
/// <summary>
/// Creates an <see cref="EquatableArray{T}"/> instance from a given <see cref="ImmutableArray{T}"/>.
/// </summary>
/// <typeparam name="T">The type of items in the input array.</typeparam>
/// <param name="array">The input <see cref="ImmutableArray{T}"/> instance.</param>
/// <returns>An <see cref="EquatableArray{T}"/> instance from a given <see cref="ImmutableArray{T}"/>.</returns>
public static EquatableArray<T> AsEquatableArray<T>(this ImmutableArray<T> array)
where T : IEquatable<T>
{
return new(array);
}
}
/// <summary>
/// An immutable, equatable array. This is equivalent to <see cref="ImmutableArray{T}"/> but with value equality support.
/// </summary>
/// <typeparam name="T">The type of values in the array.</typeparam>
public readonly struct EquatableArray<T> : IEquatable<EquatableArray<T>>, IEnumerable<T>
where T : IEquatable<T>
{
/// <summary>
/// The underlying <typeparamref name="T"/> array.
/// </summary>
private readonly T[]? array;
/// <summary>
/// Creates a new <see cref="EquatableArray{T}"/> instance.
/// </summary>
/// <param name="array">The input <see cref="ImmutableArray{T}"/> to wrap.</param>
public EquatableArray(ImmutableArray<T> array)
{
this.array = Unsafe.As<ImmutableArray<T>, T[]?>(ref array);
}
/// <summary>
/// Gets a reference to an item at a specified position within the array.
/// </summary>
/// <param name="index">The index of the item to retrieve a reference to.</param>
/// <returns>A reference to an item at a specified position within the array.</returns>
public ref readonly T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref AsImmutableArray().ItemRef(index);
}
/// <summary>
/// Gets a value indicating whether the current array is empty.
/// </summary>
public bool IsEmpty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => AsImmutableArray().IsEmpty;
}
/// <sinheritdoc/>
public bool Equals(EquatableArray<T> array)
{
return AsSpan().SequenceEqual(array.AsSpan());
}
/// <sinheritdoc/>
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is EquatableArray<T> array && Equals(this, array);
}
/// <sinheritdoc/>
public override int GetHashCode()
{
if (this.array is not T[] array)
{
return 0;
}
HashCode hashCode = default;
foreach (T item in array)
{
hashCode.Add(item);
}
return hashCode.ToHashCode();
}
/// <summary>
/// Gets an <see cref="ImmutableArray{T}"/> instance from the current <see cref="EquatableArray{T}"/>.
/// </summary>
/// <returns>The <see cref="ImmutableArray{T}"/> from the current <see cref="EquatableArray{T}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ImmutableArray<T> AsImmutableArray()
{
return Unsafe.As<T[]?, ImmutableArray<T>>(ref Unsafe.AsRef(in this.array));
}
/// <summary>
/// Creates an <see cref="EquatableArray{T}"/> instance from a given <see cref="ImmutableArray{T}"/>.
/// </summary>
/// <param name="array">The input <see cref="ImmutableArray{T}"/> instance.</param>
/// <returns>An <see cref="EquatableArray{T}"/> instance from a given <see cref="ImmutableArray{T}"/>.</returns>
public static EquatableArray<T> FromImmutableArray(ImmutableArray<T> array)
{
return new(array);
}
/// <summary>
/// Returns a <see cref="ReadOnlySpan{T}"/> wrapping the current items.
/// </summary>
/// <returns>A <see cref="ReadOnlySpan{T}"/> wrapping the current items.</returns>
public ReadOnlySpan<T> AsSpan()
{
return AsImmutableArray().AsSpan();
}
/// <summary>
/// Copies the contents of this <see cref="EquatableArray{T}"/> instance to a mutable array.
/// </summary>
/// <returns>The newly instantiated array.</returns>
public T[] ToArray()
{
return AsImmutableArray().ToArray();
}
/// <summary>
/// Gets an <see cref="ImmutableArray{T}.Enumerator"/> value to traverse items in the current array.
/// </summary>
/// <returns>An <see cref="ImmutableArray{T}.Enumerator"/> value to traverse items in the current array.</returns>
public ImmutableArray<T>.Enumerator GetEnumerator()
{
return AsImmutableArray().GetEnumerator();
}
/// <sinheritdoc/>
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return ((IEnumerable<T>)AsImmutableArray()).GetEnumerator();
}
/// <sinheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)AsImmutableArray()).GetEnumerator();
}
/// <summary>
/// Implicitly converts an <see cref="ImmutableArray{T}"/> to <see cref="EquatableArray{T}"/>.
/// </summary>
/// <returns>An <see cref="EquatableArray{T}"/> instance from a given <see cref="ImmutableArray{T}"/>.</returns>
public static implicit operator EquatableArray<T>(ImmutableArray<T> array)
{
return FromImmutableArray(array);
}
/// <summary>
/// Implicitly converts an <see cref="EquatableArray{T}"/> to <see cref="ImmutableArray{T}"/>.
/// </summary>
/// <returns>An <see cref="ImmutableArray{T}"/> instance from a given <see cref="EquatableArray{T}"/>.</returns>
public static implicit operator ImmutableArray<T>(EquatableArray<T> array)
{
return array.AsImmutableArray();
}
/// <summary>
/// Checks whether two <see cref="EquatableArray{T}"/> values are the same.
/// </summary>
/// <param name="left">The first <see cref="EquatableArray{T}"/> value.</param>
/// <param name="right">The second <see cref="EquatableArray{T}"/> value.</param>
/// <returns>Whether <paramref name="left"/> and <paramref name="right"/> are equal.</returns>
public static bool operator ==(EquatableArray<T> left, EquatableArray<T> right)
{
return left.Equals(right);
}
/// <summary>
/// Checks whether two <see cref="EquatableArray{T}"/> values are not the same.
/// </summary>
/// <param name="left">The first <see cref="EquatableArray{T}"/> value.</param>
/// <param name="right">The second <see cref="EquatableArray{T}"/> value.</param>
/// <returns>Whether <paramref name="left"/> and <paramref name="right"/> are not equal.</returns>
public static bool operator !=(EquatableArray<T> left, EquatableArray<T> right)
{
return !left.Equals(right);
}
}

View File

@@ -1,190 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// Taken from https://raw.githubusercontent.com/CommunityToolkit/dotnet/ecd1711b740f4f88d2bb943ce292ae4fc90df1bc/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/HashCode.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
#pragma warning disable CS0809
namespace System;
#nullable enable
/// <summary>
/// A polyfill type that mirrors some methods from <see cref="HashCode"/> on .NET 6.
/// </summary>
public struct HashCode
{
private const uint Prime1 = 2654435761U;
private const uint Prime2 = 2246822519U;
private const uint Prime3 = 3266489917U;
private const uint Prime4 = 668265263U;
private const uint Prime5 = 374761393U;
private static readonly uint seed = GenerateGlobalSeed();
private uint v1, v2, v3, v4;
private uint queue1, queue2, queue3;
private uint length;
/// <summary>
/// Initializes the default seed.
/// </summary>
/// <returns>A random seed.</returns>
private static unsafe uint GenerateGlobalSeed()
{
byte[] bytes = new byte[4];
using (RandomNumberGenerator generator = RandomNumberGenerator.Create())
{
generator.GetBytes(bytes);
}
return BitConverter.ToUInt32(bytes, 0);
}
/// <summary>
/// Adds a single value to the current hash.
/// </summary>
/// <typeparam name="T">The type of the value to add into the hash code.</typeparam>
/// <param name="value">The value to add into the hash code.</param>
public void Add<T>(T value)
{
Add(value?.GetHashCode() ?? 0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4)
{
v1 = seed + Prime1 + Prime2;
v2 = seed + Prime2;
v3 = seed;
v4 = seed - Prime1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint Round(uint hash, uint input)
{
return RotateLeft(hash + input * Prime2, 13) * Prime1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint QueueRound(uint hash, uint queuedValue)
{
return RotateLeft(hash + queuedValue * Prime3, 17) * Prime4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint MixState(uint v1, uint v2, uint v3, uint v4)
{
return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint MixEmptyState()
{
return seed + Prime5;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint MixFinal(uint hash)
{
hash ^= hash >> 15;
hash *= Prime2;
hash ^= hash >> 13;
hash *= Prime3;
hash ^= hash >> 16;
return hash;
}
private void Add(int value)
{
uint val = (uint)value;
uint previousLength = this.length++;
uint position = previousLength % 4;
if (position == 0)
{
this.queue1 = val;
}
else if (position == 1)
{
this.queue2 = val;
}
else if (position == 2)
{
this.queue3 = val;
}
else
{
if (previousLength == 3)
{
Initialize(out this.v1, out this.v2, out this.v3, out this.v4);
}
this.v1 = Round(this.v1, this.queue1);
this.v2 = Round(this.v2, this.queue2);
this.v3 = Round(this.v3, this.queue3);
this.v4 = Round(this.v4, val);
}
}
/// <summary>
/// Gets the resulting hashcode from the current instance.
/// </summary>
/// <returns>The resulting hashcode from the current instance.</returns>
public int ToHashCode()
{
uint length = this.length;
uint position = length % 4;
uint hash = length < 4 ? MixEmptyState() : MixState(this.v1, this.v2, this.v3, this.v4);
hash += length * 4;
if (position > 0)
{
hash = QueueRound(hash, this.queue1);
if (position > 1)
{
hash = QueueRound(hash, this.queue2);
if (position > 2)
{
hash = QueueRound(hash, this.queue3);
}
}
}
hash = MixFinal(hash);
return (int)hash;
}
/// <inheritdoc/>
[Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode() => throw new NotSupportedException();
/// <inheritdoc/>
[Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj) => throw new NotSupportedException();
/// <summary>
/// Rotates the specified value left by the specified number of bits.
/// Similar in behavior to the x86 instruction ROL.
/// </summary>
/// <param name="value">The value to rotate.</param>
/// <param name="offset">The number of bits to rotate by.
/// Any value outside the range [0..31] is treated as congruent mod 32.</param>
/// <returns>The rotated value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint RotateLeft(uint value, int offset)
{
return (value << offset) | (value >> (32 - offset));
}
}

View File

@@ -1,121 +0,0 @@
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Robust.Roslyn.Shared.Helpers;
namespace Robust.Roslyn.Shared;
#nullable enable
/// <summary>
/// All the information to make a partial type alternative for a type.
/// </summary>
public sealed record PartialTypeInfo(
string? Namespace,
string Name,
string DisplayName,
EquatableArray<string> TypeParameterNames,
bool IsValid,
Location SyntaxLocation,
Accessibility Accessibility,
TypeKind Kind,
bool IsRecord,
bool IsAbstract)
{
public static PartialTypeInfo FromSymbol(INamedTypeSymbol symbol, TypeDeclarationSyntax syntax)
{
var typeParameters = ImmutableArray<string>.Empty;
if (symbol.TypeParameters.Length > 0)
{
var builder = ImmutableArray.CreateBuilder<string>(symbol.TypeParameters.Length);
foreach (var typeParameter in symbol.TypeParameters)
{
builder.Add(typeParameter.Name);
}
typeParameters = builder.MoveToImmutable();
}
return new PartialTypeInfo(
symbol.ContainingNamespace.IsGlobalNamespace ? null : symbol.ContainingNamespace.ToDisplayString(),
symbol.Name,
symbol.ToDisplayString(),
typeParameters,
syntax.Modifiers.Any(x => x.IsKind(SyntaxKind.PartialKeyword)),
syntax.Keyword.GetLocation(),
symbol.DeclaredAccessibility,
symbol.TypeKind,
symbol.IsRecord,
symbol.IsAbstract);
}
public bool CheckPartialDiagnostic(SourceProductionContext context, DiagnosticDescriptor diagnostic)
{
if (!IsValid)
{
context.ReportDiagnostic(Diagnostic.Create(diagnostic, SyntaxLocation, DisplayName));
return true;
}
return false;
}
public string GetGeneratedFileName()
{
var name = Namespace == null ? Name : $"{Namespace}.{Name}";
if (TypeParameterNames.AsImmutableArray().Length > 0)
name += $"`{TypeParameterNames.AsImmutableArray().Length}";
name += ".g.cs";
return name;
}
public void WriteHeader(StringBuilder builder)
{
if (Namespace != null)
builder.AppendLine($"namespace {Namespace};\n");
// TODO: Nested classes
var access = Accessibility switch
{
Accessibility.Private => "private",
Accessibility.ProtectedAndInternal => "private protected",
Accessibility.ProtectedOrInternal => "protected internal",
Accessibility.Protected => "protected",
Accessibility.Internal => "internal",
_ => "public"
};
string keyword;
if (Kind == TypeKind.Interface)
{
keyword = "interface";
}
else
{
if (IsRecord)
{
keyword = Kind == TypeKind.Struct ? "record struct" : "record";
}
else
{
keyword = Kind == TypeKind.Struct ? "struct" : "class";
}
}
builder.Append($"{access} {(IsAbstract ? "abstract " : "")}partial {keyword} {Name}");
if (TypeParameterNames.AsSpan().Length > 0)
{
builder.Append($"<{string.Join(", ", TypeParameterNames.AsImmutableArray())}>");
}
}
public void WriteFooter(StringBuilder builder)
{
// TODO: Nested classes
}
}

View File

@@ -1,38 +0,0 @@
<Project>
<!--
I wanted to make a Robust.Roslyn.Shared library project,
but doing that causes various random library load failures in practice.
Instead, you'll get this vomit. Enjoy.
-->
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ImplicitUsings>enable</ImplicitUsings>
<PolySharpIncludeGeneratedTypes>System.Index;System.Diagnostics.CodeAnalysis.NotNullWhenAttribute;System.Runtime.CompilerServices.IsExternalInit;System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute</PolySharpIncludeGeneratedTypes>
<NoWarn>RS2008</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Common" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" />
<PackageReference Include="PolySharp">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\Robust.Roslyn.Shared\**\*.cs">
<Link>Robust.Roslyn.Shared\%(RecursiveDir)%(Filename)%(Extension)</Link>
</Compile>
<Compile Remove="..\Robust.Roslyn.Shared\obj\**\*.cs" />
</ItemGroup>
</Project>

View File

@@ -1,28 +0,0 @@
using Microsoft.CodeAnalysis;
namespace Robust.Roslyn.Shared;
#nullable enable
public static class TypeSymbolHelper
{
public static bool ShittyTypeMatch(INamedTypeSymbol type, string attributeMetadataName)
{
// Doing it like this only allocates when the type actually matches, which is good enough for me right now.
if (!attributeMetadataName.EndsWith(type.Name))
return false;
return type.ToDisplayString() == attributeMetadataName;
}
public static bool ImplementsInterface(INamedTypeSymbol type, string interfaceTypeName)
{
foreach (var interfaceType in type.AllInterfaces)
{
if (ShittyTypeMatch(interfaceType, interfaceTypeName))
return true;
}
return false;
}
}

View File

@@ -1,252 +0,0 @@
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Robust.Roslyn.Shared;
using Robust.Roslyn.Shared.Helpers;
namespace Robust.Serialization.Generator;
/// <summary>
/// Automatically generates implementations for handling timer unpausing.
/// </summary>
[Generator(LanguageNames.CSharp)]
public sealed class ComponentPauseGenerator : IIncrementalGenerator
{
private const string AutoGenerateComponentPauseAttributeName = "Robust.Shared.Analyzers.AutoGenerateComponentPauseAttribute";
private const string AutoPausedFieldAttributeName = "Robust.Shared.Analyzers.AutoPausedFieldAttribute";
private const string AutoNetworkFieldAttributeName = "Robust.Shared.Analyzers.AutoNetworkedFieldAttribute";
// ReSharper disable once InconsistentNaming
private const string IComponentTypeName = "Robust.Shared.GameObjects.IComponent";
private static readonly DiagnosticDescriptor NotComponentDiagnostic = new(
Diagnostics.IdComponentPauseNotComponent,
"Class must be an IComponent to use AutoGenerateComponentPause",
"Class '{0}' must implement IComponent to be used with [AutoGenerateComponentPause]",
"Usage",
DiagnosticSeverity.Error,
true);
private static readonly DiagnosticDescriptor NoFieldsDiagnostic = new(
Diagnostics.IdComponentPauseNoFields,
"AutoGenerateComponentPause has no fields",
"Class '{0}' has [AutoGenerateComponentPause] but has no fields or properties with [AutoPausedField]",
"Usage",
DiagnosticSeverity.Warning,
true);
private static readonly DiagnosticDescriptor NoParentAttributeDiagnostic = new(
Diagnostics.IdComponentPauseNoParentAttribute,
"AutoPausedField on type of field without AutoGenerateComponentPause",
"Field '{0}' has [AutoPausedField] but its containing type does not have [AutoGenerateComponentPause]",
"Usage",
DiagnosticSeverity.Error,
true);
private static readonly DiagnosticDescriptor WrongTypeAttributeDiagnostic = new(
Diagnostics.IdComponentPauseWrongTypeAttribute,
"AutoPausedField has wrong type",
"Field '{0}' has [AutoPausedField] but is not of type TimeSpan",
"Usage",
DiagnosticSeverity.Error,
true);
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var componentInfos = context.SyntaxProvider.ForAttributeWithMetadataName(
AutoGenerateComponentPauseAttributeName,
(syntaxNode, _) => syntaxNode is TypeDeclarationSyntax,
(syntaxContext, _) =>
{
var symbol = (INamedTypeSymbol)syntaxContext.TargetSymbol;
var typeDeclarationSyntax = (TypeDeclarationSyntax) syntaxContext.TargetNode;
var partialTypeInfo = PartialTypeInfo.FromSymbol(
symbol,
typeDeclarationSyntax);
var dirty = AttributeHelper.GetNamedArgumentBool(syntaxContext.Attributes[0], "Dirty", false);
var fieldBuilder = ImmutableArray.CreateBuilder<FieldInfo>();
foreach (var member in symbol.GetMembers())
{
if (!AttributeHelper.HasAttribute(member, AutoPausedFieldAttributeName, out var _))
continue;
var type = member switch
{
IPropertySymbol property => property.Type,
IFieldSymbol field => field.Type,
_ => null
};
if (type is not INamedTypeSymbol namedType)
continue;
var invalid = false;
var nullable = false;
if (namedType.Name != "TimeSpan")
{
if (namedType is { Name: "Nullable", TypeArguments: [{Name: "TimeSpan"}] })
{
nullable = true;
}
else
{
invalid = true;
}
}
// If any pause field has [AutoNetworkedField], automatically mark it to dirty on unpause.
if (AttributeHelper.HasAttribute(member, AutoNetworkFieldAttributeName, out var _))
dirty = true;
fieldBuilder.Add(new FieldInfo(member.Name, nullable, invalid, member.Locations[0]));
}
return new ComponentInfo(
partialTypeInfo,
EquatableArray<FieldInfo>.FromImmutableArray(fieldBuilder.ToImmutable()),
dirty,
!TypeSymbolHelper.ImplementsInterface(symbol, IComponentTypeName),
typeDeclarationSyntax.Identifier.GetLocation());
});
context.RegisterImplementationSourceOutput(componentInfos, static (productionContext, info) =>
{
if (info.NotComponent)
{
productionContext.ReportDiagnostic(Diagnostic.Create(
NotComponentDiagnostic,
info.Location,
info.PartialTypeInfo.Name));
return;
}
// Component always have to be partial anyways due to the serialization generator.
// So I can't be arsed to define a diagnostic for this.
if (!info.PartialTypeInfo.IsValid)
return;
if (info.Fields.AsImmutableArray().Length == 0)
{
productionContext.ReportDiagnostic(Diagnostic.Create(
NoFieldsDiagnostic,
info.Location,
info.PartialTypeInfo.Name));
return;
}
var builder = new StringBuilder();
builder.AppendLine("""
// <auto-generated />
using Robust.Shared.GameObjects;
""");
info.PartialTypeInfo.WriteHeader(builder);
builder.AppendLine();
builder.AppendLine("{");
builder.AppendLine($$"""
[RobustAutoGenerated]
public sealed class {{info.PartialTypeInfo.Name}}_AutoPauseSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<{{info.PartialTypeInfo.Name}}, EntityUnpausedEvent>(OnEntityUnpaused);
}
private void OnEntityUnpaused(EntityUid uid, {{info.PartialTypeInfo.Name}} component, ref EntityUnpausedEvent args)
{
""");
var anyValidField = false;
foreach (var field in info.Fields)
{
if (field.Invalid)
{
productionContext.ReportDiagnostic(Diagnostic.Create(WrongTypeAttributeDiagnostic, field.Location));
continue;
}
if (field.Nullable)
{
builder.AppendLine($"""
if (component.{field.Name}.HasValue)
component.{field.Name} = component.{field.Name}.Value + args.PausedTime;
""");
}
else
{
builder.AppendLine($" component.{field.Name} += args.PausedTime;");
}
anyValidField = true;
}
if (!anyValidField)
return;
if (info.Dirty)
builder.AppendLine(" Dirty(uid, component);");
builder.AppendLine("""
}
}
""");
builder.AppendLine("}");
info.PartialTypeInfo.WriteFooter(builder);
productionContext.AddSource(info.PartialTypeInfo.GetGeneratedFileName(), builder.ToString());
});
// Code to report diagnostic for fields that have it but don't have the attribute on the parent.
var allFields = context.SyntaxProvider.ForAttributeWithMetadataName(
AutoPausedFieldAttributeName,
(syntaxNode, _) => syntaxNode is VariableDeclaratorSyntax or PropertyDeclarationSyntax,
(syntaxContext, _) =>
{
var errorTarget = syntaxContext.TargetNode is PropertyDeclarationSyntax prop
? prop.Identifier.GetLocation()
: syntaxContext.TargetNode.GetLocation();
return new AllFieldInfo(
syntaxContext.TargetSymbol.Name,
syntaxContext.TargetSymbol.ContainingType.ToDisplayString(),
errorTarget);
});
var allComponentsTogether = componentInfos.Collect();
var allFieldsTogether = allFields.Collect();
var componentFieldJoin = allFieldsTogether.Combine(allComponentsTogether);
context.RegisterImplementationSourceOutput(componentFieldJoin, (productionContext, info) =>
{
var componentsByName = new HashSet<string>(info.Right.Select(x => x.PartialTypeInfo.DisplayName));
foreach (var field in info.Left)
{
if (!componentsByName.Contains(field.ParentDisplayName))
{
productionContext.ReportDiagnostic(
Diagnostic.Create(NoParentAttributeDiagnostic, field.Location, field.Name));
}
}
});
}
public sealed record ComponentInfo(
PartialTypeInfo PartialTypeInfo,
EquatableArray<FieldInfo> Fields,
bool Dirty,
bool NotComponent,
Location Location);
public sealed record FieldInfo(string Name, bool Nullable, bool Invalid, Location Location);
public sealed record AllFieldInfo(string Name, string ParentDisplayName, Location Location);
}

View File

@@ -1,9 +0,0 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"Generators": {
"commandName": "DebugRoslynComponent",
"targetProject": "../../Content.Shared/Content.Shared.csproj"
}
}
}

View File

@@ -1,5 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>11</LangVersion>
<Nullable>enable</Nullable>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Common" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" />
</ItemGroup>
</Project>

View File

@@ -238,8 +238,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
}
public override void LoadStream<T>(Entity<AudioComponent> entity, T stream)
public override void LoadStream<T>(AudioComponent component, T stream)
{
// TODO: Yeah remove this...
}
}

View File

@@ -653,8 +653,8 @@ namespace Robust.Server
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
// shut down networking, kicking all players.
var shutdownReasonWithRedial = new NetDisconnectMessage($"Server shutting down: {_shutdownReason}", true);
_network.Shutdown(shutdownReasonWithRedial.Encode());
var shutdownReasonWithRedial = NetStructuredDisconnectMessages.Encode($"Server shutting down: {_shutdownReason}", true);
_network.Shutdown(shutdownReasonWithRedial);
// shutdown entities
_entityManager.Cleanup();

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Robust.Server.Player;
using Robust.Shared.Console;
@@ -284,25 +283,25 @@ namespace Robust.Server.Console
/// Get completions. Non-null results imply that the command was handled. If it is empty, it implies that
/// there are no completions for this command.
/// </summary>
private async ValueTask<CompletionResult?> CalcCompletions(IConsoleShell shell, string[] args, string argStr)
private ValueTask<CompletionResult?> CalcCompletions(IConsoleShell shell, string[] args, string argStr)
{
// Logger.Debug(string.Join(", ", args));
if (args.Length <= 1)
{
// Typing out command name, handle this ourselves.
return CompletionResult.FromOptions(
AvailableCommands.Values.Where(c => ShellCanExecute(shell, c.Command)).Select(c => new CompletionOption(c.Command, c.Description)));
return ValueTask.FromResult<CompletionResult?>(CompletionResult.FromOptions(
AvailableCommands.Values.Where(c => ShellCanExecute(shell, c.Command)).Select(c => new CompletionOption(c.Command, c.Description))));
}
var cmdName = args[0];
if (!RegisteredCommands.TryGetValue(cmdName, out var cmd))
return null;
return ValueTask.FromResult<CompletionResult?>(null);
if (!ShellCanExecute(shell, cmdName))
return CompletionResult.Empty;
return ValueTask.FromResult<CompletionResult?>(CompletionResult.Empty);
return await cmd.GetCompletionAsync(shell, args[1..], argStr, CancellationToken.None);
return ValueTask.FromResult<CompletionResult?>(cmd.GetCompletion(shell, args[1..]));
}
private sealed class SudoShell : IConsoleShell

View File

@@ -1,5 +1,4 @@
using System;
using Robust.Server.GameStates;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
@@ -8,7 +7,6 @@ namespace Robust.Server.GameObjects
{
public sealed class VisibilitySystem : EntitySystem
{
[Dependency] private readonly PvsSystem _pvs = default!;
[Dependency] private readonly IViewVariablesManager _vvManager = default!;
private EntityQuery<TransformComponent> _xformQuery;
@@ -135,7 +133,6 @@ namespace Robust.Server.GameObjects
var xform = _xformQuery.GetComponent(uid);
meta.VisibilityMask = mask;
_pvs.SyncMetadata(meta);
foreach (var child in xform._children)
{

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Prometheus;
using Robust.Server.GameStates;
using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
@@ -43,7 +42,7 @@ namespace Robust.Server.GameObjects
#endif
private ISawmill _netEntSawmill = default!;
private PvsSystem _pvs = default!;
private EntityQuery<ActorComponent> _actorQuery;
public override void Initialize()
{
@@ -58,7 +57,7 @@ namespace Robust.Server.GameObjects
public override void Startup()
{
base.Startup();
_pvs = System<PvsSystem>();
_actorQuery = GetEntityQuery<ActorComponent>();
}
EntityUid IServerEntityManagerInternal.AllocEntity(EntityPrototype? prototype)
@@ -104,40 +103,6 @@ namespace Robust.Server.GameObjects
return entity;
}
/// <inheritdoc />
public override void RaiseSharedEvent<T>(T message, EntityUid? user = null)
{
if (user != null)
{
var filter = Filter.Broadcast().RemoveWhereAttachedEntity(e => e == user.Value);
foreach (var session in filter.Recipients)
{
EntityNetManager.SendSystemNetworkMessage(message, session.Channel);
}
}
else
{
EntityNetManager.SendSystemNetworkMessage(message);
}
}
/// <inheritdoc />
public override void RaiseSharedEvent<T>(T message, ICommonSession? user = null)
{
if (user != null)
{
var filter = Filter.Broadcast().RemovePlayer(user);
foreach (var session in filter.Recipients)
{
EntityNetManager.SendSystemNetworkMessage(message, session.Channel);
}
}
else
{
EntityNetManager.SendSystemNetworkMessage(message);
}
}
private void ClearTicks(EntityUid entity, EntityPrototype prototype)
{
foreach (var (netId, component) in GetNetComponents(entity))
@@ -151,12 +116,6 @@ namespace Robust.Server.GameObjects
}
}
internal override void SetLifeStage(MetaDataComponent meta, EntityLifeStage stage)
{
base.SetLifeStage(meta, stage);
_pvs.SyncMetadata(meta);
}
#region IEntityNetworkManager impl
public override IEntityNetworkManager EntityNetManager => this;

View File

@@ -0,0 +1,46 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Robust.Shared.Maths;
namespace Robust.Server.GameStates;
public struct ChunkIndicesEnumerator
{
private Vector2i _bottomLeft;
private Vector2i _topRight;
private int _x;
private int _y;
public ChunkIndicesEnumerator(Vector2 viewPos, float range, float chunkSize)
{
var rangeVec = new Vector2(range, range);
_bottomLeft = ((viewPos - rangeVec) / chunkSize).Floored();
// Also floor this as we get the whole chunk anyway.
_topRight = ((viewPos + rangeVec) / chunkSize).Floored();
_x = _bottomLeft.X;
_y = _bottomLeft.Y;
}
public bool MoveNext([NotNullWhen(true)] out Vector2i? chunkIndices)
{
if (_y > _topRight.Y)
{
_x++;
_y = _bottomLeft.Y;
}
if (_x > _topRight.X)
{
chunkIndices = null;
return false;
}
chunkIndices = new Vector2i(_x, _y);
_y++;
return true;
}
}

View File

@@ -42,7 +42,7 @@ internal sealed class PvsChunk
/// <remarks>
/// This already includes <see cref="Map"/>, <see cref="Root"/>, and <see cref="Children"/>
/// </remarks>
public readonly List<ChunkEntity> Contents = new();
public readonly List<Entity<MetaDataComponent>> Contents = new();
/// <summary>
/// The unique location identifier for this chunk.
@@ -68,8 +68,8 @@ internal sealed class PvsChunk
// the same chunk can be repopulated more than once.
private List<HashSet<EntityUid>> _childSets = new();
private List<HashSet<EntityUid>> _nextChildSets = new();
private List<ChunkEntity> _lowPriorityChildren = new();
private List<ChunkEntity> _anchoredChildren = new();
private List<Entity<MetaDataComponent>> _lowPriorityChildren = new();
private List<Entity<MetaDataComponent>> _anchoredChildren = new();
/// <summary>
/// Effective "counts" of <see cref="Contents"/> that should be used to limit the number of entities in a chunk that
@@ -151,11 +151,11 @@ internal sealed class PvsChunk
childMeta.LastPvsLocation = Location;
if ((childMeta.Flags & MetaDataFlags.PvsPriority) == MetaDataFlags.PvsPriority)
Contents.Add(new ChunkEntity(child, childMeta));
Contents.Add((child, childMeta));
else if (childXform.Anchored)
_anchoredChildren.Add(new(child, childMeta));
_anchoredChildren.Add((child, childMeta));
else
_lowPriorityChildren.Add(new(child, childMeta));
_lowPriorityChildren.Add((child, childMeta));
var subCount = childXform._children.Count;
if (subCount == 0)
@@ -196,7 +196,7 @@ internal sealed class PvsChunk
}
childMeta.LastPvsLocation = Location;
Contents.Add(new(child, childMeta));
Contents.Add((child, childMeta));
var subCount = childXform._children.Count;
if (subCount == 0)
@@ -241,10 +241,10 @@ internal sealed class PvsChunk
set.Add(Map.Owner);
foreach (var child in Contents)
{
var parent = query.GetComponent(child.Uid).ParentUid;
var parent = query.GetComponent(child).ParentUid;
DebugTools.Assert(set.Contains(parent),
"A child's parent is not in the chunk, or is not listed first.");
DebugTools.Assert(set.Add(child.Uid), "Child appears more than once in the chunk.");
DebugTools.Assert(set.Add(child), "Child appears more than once in the chunk.");
}
}
@@ -274,11 +274,4 @@ internal sealed class PvsChunk
? $"map-{Root.Owner}-{Location.Indices}"
: $"grid-{Root.Owner}-{Location.Indices}";
}
public readonly struct ChunkEntity(EntityUid uid, MetaDataComponent meta)
{
public readonly EntityUid Uid = uid;
public readonly PvsIndex Ptr = meta.PvsData;
public readonly MetaDataComponent Meta = meta;
}
}

View File

@@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.Collections;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Timing;
@@ -15,24 +14,26 @@ namespace Robust.Server.GameStates;
/// <summary>
/// Class for storing session specific PVS data.
/// </summary>
internal sealed class PvsSession(ICommonSession session, ResizableMemoryRegion<PvsData> memoryRegion)
internal sealed class PvsSession(ICommonSession session)
{
public readonly ICommonSession Session = session;
public readonly ResizableMemoryRegion<PvsData> DataMemory = memoryRegion;
public INetChannel Channel => Session.Channel;
/// <summary>
/// All entities that this session saw during the last <see cref="PvsSystem.DirtyBufferSize"/> ticks.
/// All <see cref="EntityUid"/>s that this session saw during the last <see cref="PvsSystem.DirtyBufferSize"/> ticks.
/// </summary>
public readonly OverflowDictionary<GameTick, List<PvsIndex>> PreviouslySent = new(PvsSystem.DirtyBufferSize);
public readonly OverflowDictionary<GameTick, List<PvsData>> PreviouslySent = new(PvsSystem.DirtyBufferSize);
/// <summary>
/// Dictionary containing data about all entities that this client has ever seen.
/// </summary>
public readonly Dictionary<NetEntity, PvsData> Entities = new();
/// <summary>
/// <see cref="PreviouslySent"/> overflow in case a player's last ack is more than
/// <see cref="PvsSystem.DirtyBufferSize"/> ticks behind the current tick.
/// </summary>
public (GameTick Tick, List<PvsIndex> SentEnts)? Overflow;
public (GameTick Tick, List<PvsData> SentEnts)? Overflow;
/// <summary>
/// The client's current visibility mask.
@@ -42,13 +43,13 @@ internal sealed class PvsSession(ICommonSession session, ResizableMemoryRegion<P
/// <summary>
/// The list that is currently being prepared for sending.
/// </summary>
public List<PvsIndex>? ToSend;
public List<PvsData>? ToSend;
/// <summary>
/// The <see cref="ToSend"/> list from the previous tick. Also caches the current tick that the PVS leave message
/// should belong to, in case the processing is ever run asynchronously with normal system/game ticking.
/// </summary>
public (GameTick ToTick, List<PvsIndex> PreviouslySent)? LastSent;
public (GameTick ToTick, List<PvsData> PreviouslySent)? LastSent;
/// <summary>
/// Visible chunks, sorted by proximity to the clients's viewers;
@@ -125,12 +126,10 @@ internal sealed class PvsSession(ICommonSession session, ResizableMemoryRegion<P
/// <summary>
/// Class for storing session-specific information about when an entity was last sent to a player.
/// </summary>
/// <remarks>
/// Size is padded to 16 bytes so
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 16)]
internal struct PvsData
internal sealed class PvsData(NetEntity entity) : IEquatable<PvsData>
{
public readonly NetEntity NetEntity = entity;
/// <summary>
/// Tick at which this entity was last sent to a player.
/// </summary>
@@ -147,55 +146,22 @@ internal struct PvsData
/// present in that state.
/// </summary>
public GameTick EntityLastAcked;
}
/// <summary>
/// Specialized struct with the same size as <see cref="PvsData"/> that is used to store metadata in the pinned PVsData array
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 16)]
internal struct PvsMetadata
{
/// <summary>
/// Tick at which this entity was last sent to a player.
/// </summary>
public NetEntity NetEntity;
public GameTick LastModifiedTick;
public ushort VisMask;
public EntityLifeStage LifeStage;
#if DEBUG
// This struct is padded to a size of 16 so it's aligned to cache boundaries nicely.
// We have this extra space that isn't being used,
// so I'm opting to use them to make debugging the free list easier.
// "Marker" overlaps with the field used by the free list (which occupies the unused memory of PvsMetadata).
// So we set it to a bogus value and BAM! Errors are obvious!
private byte Pad0;
public uint Marker;
#endif
}
[StructLayout(LayoutKind.Sequential, Size = 16)]
internal struct PvsMetadataFreeLink
{
#if DEBUG
static unsafe PvsMetadataFreeLink()
public bool Equals(PvsData? other)
{
DebugTools.Assert(sizeof(PvsMetadataFreeLink) == sizeof(PvsMetadata));
DebugTools.Assert((NetEntity != other?.NetEntity) || ReferenceEquals(this, other));
return NetEntity == other?.NetEntity;
}
#endif
public int Pad0;
public int Pad1;
public int Pad2;
// We offset the NextFree to be at the end of the struct.
// This is so that it overlaps with the debug Marker field of PvsMetadata instead of real data.
public PvsIndex NextFree;
public override int GetHashCode()
{
return NetEntity.GetHashCode();
}
}
/// <summary>
/// Struct for storing information about the current number of entities that are being sent to the player this tick.
/// Used to enforce pvs budgets.
/// </summary>
internal struct PvsBudget
{
public int NewLimit;

View File

@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -14,7 +12,6 @@ namespace Robust.Server.GameStates;
public sealed class PvsOverrideSystem : EntitySystem
{
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IConsoleHost _console = default!;
private readonly HashSet<EntityUid> _hasOverride = new();
@@ -31,64 +28,8 @@ public sealed class PvsOverrideSystem : EntitySystem
SubscribeLocalEvent<MapChangedEvent>(OnMapChanged);
SubscribeLocalEvent<GridInitializeEvent>(OnGridCreated);
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
// TODO console commands for adding/removing overrides?
_console.RegisterCommand(
"pvs_override_info",
Loc.GetString("cmd-pvs-override-info-desc"),
"pvs_override_info",
GetPvsInfo,
GetCompletion);
}
#region Console Commands
/// <summary>
/// Debug command for displaying PVS override information.
/// </summary>
private void GetPvsInfo(IConsoleShell shell, string argstr, string[] args)
{
if (args.Length != 1)
{
shell.WriteError(Loc.GetString("cmd-invalid-arg-number-error"));
return;
}
if (!NetEntity.TryParse(args[0], out var nuid) || !TryGetEntity(nuid, out var uid))
{
shell.WriteError(Loc.GetString("cmd-parse-failure-uid"));
return;
}
if (!_hasOverride.Contains(uid.Value))
{
shell.WriteLine(Loc.GetString("cmd-pvs-override-info-empty", ("nuid", args[0])));
return;
}
if (GlobalOverride.Contains(uid.Value) || ForceSend.Contains(uid.Value))
shell.WriteLine(Loc.GetString("cmd-pvs-override-info-global", ("nuid", args[0])));
HashSet<ICommonSession> sessions = new();
sessions.UnionWith(SessionOverrides.Where(x => x.Value.Contains(uid.Value)).Select(x => x.Key));
sessions.UnionWith(SessionForceSend.Where(x => x.Value.Contains(uid.Value)).Select(x => x.Key));
if (sessions.Count == 0)
return;
var clients = string.Join(", ", sessions.Select(x => x.ToString()));
shell.WriteLine(Loc.GetString("cmd-pvs-override-info-clients", ("nuid", args[0]), ("clients", clients)));
}
private CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length != 1)
return CompletionResult.Empty;
return CompletionResult.FromHintOptions(CompletionHelper.NetEntities(args[0], EntityManager), "NetEntity");
}
#endregion
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs ev)
{
if (ev.NewStatus != SessionStatus.Disconnected)

View File

@@ -4,7 +4,6 @@ using System.Runtime.InteropServices;
using System.Threading;
using Prometheus;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Player;
using Robust.Shared.Threading;
@@ -106,7 +105,7 @@ internal sealed partial class PvsSystem
private void ProcessQueuedAck(PvsSession session)
{
var ackedTick = session.LastReceivedAck;
List<PvsIndex>? ackedEnts;
List<PvsData>? ackedEnts;
if (session.Overflow != null && session.Overflow.Value.Tick <= ackedTick)
{
@@ -126,12 +125,12 @@ internal sealed partial class PvsSystem
else if (!session.PreviouslySent.TryGetValue(ackedTick, out ackedEnts))
return;
foreach (ref var intPtr in CollectionsMarshal.AsSpan(ackedEnts))
foreach (var data in CollectionsMarshal.AsSpan(ackedEnts))
{
ref var data = ref session.DataMemory.GetRef(intPtr.Index);
DebugTools.AssertNotEqual(data.LastSeen, GameTick.Zero);
DebugTools.Assert(data.LastSeen >= ackedTick); // LastSent may equal ackedTick if the packet was sent reliably.
data.EntityLastAcked = ackedTick;
DebugTools.Assert(data.LastSeen >= ackedTick); // LastSent may equal ackedTick if the packet was sent reliably.
DebugTools.Assert(!session.Entities.TryGetValue(data.NetEntity, out var old)
|| ReferenceEquals(data, old));
}
// The client acked a tick. If they requested a full state, this ack happened some time after that, so we can safely set this to false

View File

@@ -8,7 +8,6 @@ using Prometheus;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Map.Enumerators;
using Robust.Shared.Maths;
using Robust.Shared.Player;
using Robust.Shared.Utility;

View File

@@ -1,384 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using Robust.Shared;
using Robust.Shared.GameObjects;
using Robust.Shared.Threading;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Server.GameStates;
// This partial class handles the PvsData memory. This array stores information about when each entity was last sent to
// each player. This is somewhat faster than using a per-player Dictionary<EntityUid, PvsData>, though it can be less
// memory efficient.
internal sealed partial class PvsSystem
{
// This is used for asserts.
private HashSet<PvsIndex> _assignedEnts = new();
/// <summary>
/// Recently returned indexes from deleted entities. These get moved to <see cref="_pendingReturns"/> before
/// moving back into the free list.
/// </summary>
private List<PvsIndex> _incomingReturns = new();
/// <summary>
/// Recently returned pointers from deleted entities. These will get returned to the free list
/// after a minimum amount of time has passed, to ensure that processing late game-state ack messages doesn't
/// write data to deleted entities.
/// </summary>
private List<PvsIndex> _pendingReturns = new();
/// <summary>
/// Tick at which the <see cref="_pendingReturns"/> were last processed.
/// </summary>
private GameTick _lastReturn = GameTick.Zero;
/// <summary>
/// Memory region to store <see cref="PvsMetadata"/> instances and the free list.
/// </summary>
/// <remarks>
/// Unused elements form a linked list out of <see cref="PvsMetadataFreeLink"/> elements.
/// </remarks>
private ResizableMemoryRegion<PvsMetadata> _metadataMemory = default!;
/// <summary>
/// The head of the PVS data free list. This is the first element that will be used if a new one is needed.
/// </summary>
/// <remarks>
/// If the value is <see cref="PvsIndex.Invalid"/>,
/// there are no more free elements and the next allocation must expand the memory.
/// </remarks>
private PvsIndex _dataFreeListHead;
private WaitHandle? _deletionTask;
/// <summary>
/// Expand the size of <see cref="_metadataMemory"/> (and all session data stores) one iteration.
/// </summary>
/// <remarks>
/// This ensures that we have at least one free list slot.
/// </remarks>
private void ExpandEntityCapacity()
{
var initial = _metadataMemory.CurrentSize;
var entityGrowth = _configManager.GetCVar(CVars.NetPvsEntityGrowth);
var newSize = initial + (entityGrowth <= 0 ? initial : entityGrowth);
newSize = Math.Min(newSize, _metadataMemory.MaxSize);
if (newSize == initial)
throw new InvalidOperationException("Out of PVS entity capacity! Increase net.pvs_entity_max!");
Log.Debug($"Growing PvsData memory from {initial} -> {newSize} entities");
_metadataMemory.Expand(newSize);
foreach (var playerSession in PlayerData.Values)
{
playerSession.DataMemory.Expand(newSize);
}
var newSlots = _metadataMemory.GetSpan<PvsMetadataFreeLink>()[initial..];
InitializeFreeList(newSlots, initial, ref _dataFreeListHead);
}
/// <summary>
/// Initialize <see cref="_metadataMemory"/> and the free list.
/// </summary>
private void InitializePvsArray()
{
var initialCount = _configManager.GetCVar(CVars.NetPvsEntityInitial);
var maxCount = _configManager.GetCVar(CVars.NetPvsEntityMax);
if (initialCount <= 0 || maxCount <= 0)
throw new InvalidOperationException("net.pvs_entity_initial and net.pvs_entity_max must be positive");
_metadataMemory = new ResizableMemoryRegion<PvsMetadata>(maxCount, initialCount);
ResetDataMemory();
}
/// <summary>
/// Initialize a section of the free list.
/// </summary>
/// <param name="memory">The section of the free list to initialize.</param>
/// <param name="baseOffset">What offset in the total PVS data this section starts at.</param>
/// <param name="head">The current head storage of the free list to update.</param>
private static void InitializeFreeList(Span<PvsMetadataFreeLink> memory, int baseOffset, ref PvsIndex head)
{
for (var i = 0; i < memory.Length; i++)
{
memory[i].NextFree = new PvsIndex(baseOffset + i + 1);
}
memory[^1].NextFree = head;
head = new PvsIndex(baseOffset);
}
/// <summary>
/// Clear all PVS data. After this function is called,
/// <see cref="ResetDataMemory"/> must be called if the system isn't being shut down.
/// </summary>
private void ClearPvsData()
{
_leaveTask?.WaitOne();
_leaveTask = null;
_deletionTask?.WaitOne();
_deletionTask = null;
_incomingReturns.Clear();
_pendingReturns.Clear();
_deletionJob.ToClear.Clear();
_assignedEnts.Clear();
// Remove all pointers stored in any player's PVS send-histories. Required to avoid accidentally writing to
// invalid bits of memory while processing late game-state acks. This also forces all players to receive a full
// game state, in lieu of sending the required PVS leave messages.
foreach (var session in PlayerData.Values)
{
session.DataMemory.Clear();
ForceFullState(session);
}
_metadataMemory.Clear();
}
/// <summary>
/// Re-initialize the memory in <see cref="_metadataMemory"/> after it was fully cleared on reset.
/// </summary>
private void ResetDataMemory()
{
_dataFreeListHead = PvsIndex.Invalid;
InitializeFreeList(_metadataMemory.GetSpan<PvsMetadataFreeLink>(), 0, ref _dataFreeListHead);
}
/// <summary>
/// Shrink <see cref="_metadataMemory"/> (and all sessions) back down to initial entity size after clear.
/// </summary>
private void ShrinkDataMemory()
{
DebugTools.Assert(EntityManager.EntityCount == 0);
var initialCount = _configManager.GetCVar(CVars.NetPvsEntityInitial);
if (initialCount != _metadataMemory.CurrentSize)
{
Log.Debug($"Shrinking PVS data from {_metadataMemory.CurrentSize} -> {initialCount} entities");
_metadataMemory.Shrink(initialCount);
foreach (var player in PlayerData.Values)
{
player.DataMemory.Shrink(initialCount);
}
}
}
/// <summary>
/// This method shuffles the entity free list. This is used to avoid accidental / unrealistic cache locality
/// in benchmarks.
/// </summary>
internal void ShufflePointers(int seed)
{
throw new NotImplementedException();
/*List<IntPtr> ptrs = new(_pointerPool);
_pointerPool.Clear();
var rng = new Random(seed);
var n = ptrs.Count;
while (n > 0)
{
var k = rng.Next(n);
_pointerPool.Push(ptrs[k]);
ptrs[k] = ptrs[^1];
ptrs.RemoveAt(--n);
}*/
}
/// <summary>
/// Clear all of this sessions' PvsData for all entities. This effectively means that PVS will act as if the player
/// had never been sent information about any entity. Used when returning the player's index offset to the pool.
/// </summary>
private void ClearPlayerPvsData(PvsSession session)
{
session.DataMemory.Clear();
}
/// <summary>
/// Clear all of this entity' PvsData entries. This effectively means that PVS will act as if no player
/// had never been sent information about this entity. Used when returning the entity's index back to the free list.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ClearEntityPvsData(PvsIndex index)
{
foreach (var playerData in PlayerData.Values)
{
ref var entry = ref playerData.DataMemory.GetRef(index.Index);
entry = default;
}
}
/// <summary>
/// Get the NetEntity associated with a given <see cref="PvsIndex"/>.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private NetEntity IndexToNetEntity(PvsIndex index)
{
DebugTools.Assert(_assignedEnts.Contains(index));
return _metadataMemory.GetRef(index.Index).NetEntity;
}
/// <summary>
/// Create a new <see cref="ResizableMemoryRegion{T}"/> suitable for assigning to a new <see cref="PvsSession"/>.
/// </summary>
private ResizableMemoryRegion<PvsData> CreateSessionDataMemory()
{
return new ResizableMemoryRegion<PvsData>(_metadataMemory.MaxSize, _metadataMemory.CurrentSize);
}
private static void FreeSessionDataMemory(PvsSession session)
{
session.DataMemory.Dispose();
}
private void OnEntityAdded(Entity<MetaDataComponent> entity)
{
DebugTools.Assert(entity.Comp.PvsData.Index == default);
AssignEntityPointer(entity.Comp);
}
/// <summary>
/// Retrieve a free entity index and assign it to an entity.
/// </summary>
private void AssignEntityPointer(MetaDataComponent meta)
{
if (_dataFreeListHead == PvsIndex.Invalid)
{
ExpandEntityCapacity();
DebugTools.Assert(_dataFreeListHead != PvsIndex.Invalid);
}
var index = _dataFreeListHead;
DebugTools.Assert(_assignedEnts.Add(index));
ref var metadata = ref _metadataMemory.GetRef(index.Index);
ref var freeLink = ref Unsafe.As<PvsMetadata, PvsMetadataFreeLink>(ref metadata);
_dataFreeListHead = freeLink.NextFree;
// TODO: re-introduce this assert.
// DebugTools.AssertEqual(((PvsMetadata*) ptr)->NetEntity, NetEntity.Invalid);
DebugTools.AssertNotEqual(meta.NetEntity, NetEntity.Invalid);
meta.PvsData = index;
metadata.NetEntity = meta.NetEntity;
metadata.LastModifiedTick = meta.LastModifiedTick;
metadata.VisMask = meta.VisibilityMask;
metadata.LifeStage = meta.EntityLifeStage;
#if DEBUG
metadata.Marker = uint.MaxValue;
#endif
}
/// <summary>
/// Return an entity's index in the data array back to the free list of available indices.
/// </summary>
private void OnEntityDeleted(Entity<MetaDataComponent> entity)
{
var ptr = entity.Comp.PvsData;
entity.Comp.PvsData = default;
if (ptr == default)
return;
_incomingReturns.Add(ptr);
}
/// <summary>
/// Immediately return all data indexes back to the pool after flushing all entities.
/// </summary>
private void AfterEntityFlush()
{
DebugTools.Assert(EntityManager.EntityCount == 0);
ClearPvsData();
ShrinkDataMemory();
ResetDataMemory();
}
/// <summary>
/// This update method periodically returns entity indices back to the pool, once we are sure no old
/// game state acks will use indices to that entity.
/// </summary>
private void ProcessDeletions()
{
var curTick = _gameTiming.CurTick;
if (curTick < _lastReturn + (uint)ForceAckThreshold + 1)
return;
if (curTick < _lastReturn)
throw new InvalidOperationException($"Time travel is not supported");
_leaveTask?.WaitOne();
_leaveTask = null;
_deletionTask?.WaitOne();
_deletionTask = null;
_lastReturn = curTick;
foreach (var index in CollectionsMarshal.AsSpan(_deletionJob.ToClear))
{
ReturnEntity(index);
}
_deletionJob.ToClear.Clear();
// Cycle lists.
(_deletionJob.ToClear, _pendingReturns, _incomingReturns) = (_pendingReturns, _incomingReturns, _deletionJob.ToClear);
if (_deletionJob.ToClear.Count == 0)
return;
#if DEBUG
foreach (var index in CollectionsMarshal.AsSpan(_deletionJob.ToClear))
{
DebugTools.Assert(_assignedEnts.Remove(index));
}
#endif
if (_deletionJob.ToClear.Count > 16)
{
_deletionTask = _parallelManager.Process(_deletionJob, _deletionJob.Count);
return;
}
foreach (var index in CollectionsMarshal.AsSpan(_deletionJob.ToClear))
{
ClearEntityPvsData(index);
}
}
private void ReturnEntity(PvsIndex index)
{
DebugTools.Assert(!_assignedEnts.Contains(index));
ref var freeLink = ref _metadataMemory.GetRef<PvsMetadataFreeLink>(index.Index);
freeLink.NextFree = _dataFreeListHead;
_dataFreeListHead = index;
}
private record struct PvsDeletionsJob(PvsSystem _pvs) : IParallelRobustJob
{
public int BatchSize => 8;
private PvsSystem _pvs = _pvs;
public List<PvsIndex> ToClear = new();
public int Count => ToClear.Count;
public void Execute(int index)
{
_pvs.ClearEntityPvsData(ToClear[index]);
}
}
}

View File

@@ -49,12 +49,6 @@ namespace Robust.Server.GameStates
private void OnEntityDirty(Entity<MetaDataComponent> uid)
{
if (uid.Comp.PvsData != default)
{
ref var meta = ref _metadataMemory.GetRef(uid.Comp.PvsData.Index);
meta.LastModifiedTick = uid.Comp.EntityLastModifiedTick;
}
if (!_addEntities[_currentIndex].Contains(uid))
_dirtyEntities[_currentIndex].Add(uid);
}

View File

@@ -1,4 +1,3 @@
using System;
using System.Diagnostics;
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
@@ -26,6 +25,11 @@ internal sealed partial class PvsSystem
{
var meta = ev.Entity.Comp;
foreach (var sessionData in PlayerData.Values)
{
sessionData.Entities.Remove(meta.NetEntity);
}
_deletedEntities.Add(meta.NetEntity);
_deletedTick.Add(_gameTiming.CurTick);
RemoveEntityFromChunk(ev.Entity.Owner, meta);
@@ -107,14 +111,4 @@ internal sealed partial class PvsSystem
DebugTools.AssertNull(xform.MapUid);
AssertNullspace(xform.ParentUid);
}
internal void SyncMetadata(MetaDataComponent meta)
{
if (meta.PvsData == default)
return;
ref var ptr = ref _metadataMemory.GetRef(meta.PvsData.Index);
ptr.VisMask = meta.VisibilityMask;
ptr.LifeStage = meta.EntityLifeStage;
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using Prometheus;
@@ -48,14 +47,12 @@ internal sealed partial class PvsSystem
return;
var (toTick, lastSent) = session.LastSent.Value;
foreach (var intPtr in CollectionsMarshal.AsSpan(lastSent))
foreach (var data in CollectionsMarshal.AsSpan(lastSent))
{
ref var data = ref session.DataMemory.GetRef(intPtr.Index);
if (data.LastSeen == toTick)
continue;
session.LeftView.Add(IndexToNetEntity(intPtr));
session.LeftView.Add(data.NetEntity);
data.LastLeftView = toTick;
}

View File

@@ -1,6 +1,4 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.GameObjects;
using Robust.Shared.Timing;
@@ -10,23 +8,21 @@ namespace Robust.Server.GameStates;
// range/chunk restrictions.
internal sealed partial class PvsSystem
{
private readonly List<PvsChunk.ChunkEntity> _cachedForceOverride = new();
private readonly List<PvsChunk.ChunkEntity> _cachedGlobalOverride = new();
private readonly List<Entity<MetaDataComponent>> _cachedForceOverride = new();
private readonly List<Entity<MetaDataComponent>> _cachedGlobalOverride = new();
private readonly HashSet<EntityUid> _forceOverrideSet = new();
private readonly HashSet<EntityUid> _globalOverrideSet = new();
private void AddAllOverrides(PvsSession session)
{
var mask = session.VisMask;
var fromTick = session.FromTick;
RaiseExpandEvent(session, fromTick);
foreach (ref var ent in CollectionsMarshal.AsSpan(_cachedGlobalOverride))
foreach (var entity in _cachedGlobalOverride)
{
ref var meta = ref _metadataMemory.GetRef(ent.Ptr.Index);
if ((mask & meta.VisMask) == meta.VisMask)
AddEntity(session, ref ent, ref meta, fromTick);
if (!AddEntity(session, entity, fromTick))
break;
}
if (!_pvsOverride.SessionOverrides.TryGetValue(session.Session, out var sessionOverrides))
@@ -46,13 +42,10 @@ internal sealed partial class PvsSystem
// Ignore PVS budgets
session.Budget = new() {NewLimit = int.MaxValue, EnterLimit = int.MaxValue};
var mask = session.VisMask;
var fromTick = session.FromTick;
foreach (ref var ent in CollectionsMarshal.AsSpan(_cachedForceOverride))
foreach (var entity in _cachedForceOverride)
{
ref var meta = ref _metadataMemory.GetRef(ent.Ptr.Index);
if ((mask & meta.VisMask) == meta.VisMask)
AddEntity(session, ref ent, ref meta, fromTick);
AddEntity(session, entity, fromTick);
}
foreach (var uid in session.Viewers)
@@ -163,7 +156,7 @@ internal sealed partial class PvsSystem
private bool CacheOverrideParents(
EntityUid uid,
List<PvsChunk.ChunkEntity> list,
List<Entity<MetaDataComponent>> list,
HashSet<EntityUid> set,
out TransformComponent xform)
{
@@ -182,11 +175,11 @@ internal sealed partial class PvsSystem
return false;
}
list.Add(new(uid, meta));
list.Add((uid, meta));
return true;
}
private void CacheOverrideChildren(TransformComponent xform, List<PvsChunk.ChunkEntity> list, HashSet<EntityUid> set)
private void CacheOverrideChildren(TransformComponent xform, List<Entity<MetaDataComponent>> list, HashSet<EntityUid> set)
{
foreach (var child in xform._children)
{
@@ -197,7 +190,7 @@ internal sealed partial class PvsSystem
}
if (set.Add(child))
list.Add(new(child, _metaQuery.GetComponent(child)));
list.Add((child, _metaQuery.GetComponent(child)));
CacheOverrideChildren(childXform, list, set);
}

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.ObjectPool;
using Robust.Shared.GameObjects;
@@ -16,8 +15,8 @@ internal sealed partial class PvsSystem
/// </summary>
private const int MaxVisPoolSize = 1024;
private readonly ObjectPool<List<PvsIndex>> _entDataListPool
= new DefaultObjectPool<List<PvsIndex>>(new ListPolicy<PvsIndex>(), MaxVisPoolSize);
private readonly ObjectPool<List<PvsData>> _entDataListPool
= new DefaultObjectPool<List<PvsData>>(new ListPolicy<PvsData>(), MaxVisPoolSize);
private readonly ObjectPool<HashSet<EntityUid>> _uidSetPool
= new DefaultObjectPool<HashSet<EntityUid>>(new SetPolicy<EntityUid>(), MaxVisPoolSize);

View File

@@ -72,10 +72,7 @@ internal sealed partial class PvsSystem
private PvsSession GetOrNewPvsSession(ICommonSession session)
{
if (!PlayerData.TryGetValue(session, out var pvsSession))
{
var memoryRegion = CreateSessionDataMemory();
PlayerData[session] = pvsSession = new(session, memoryRegion);
}
PlayerData[session] = pvsSession = new(session);
return pvsSession;
}
@@ -196,5 +193,6 @@ internal sealed partial class PvsSystem
session.PreviouslySent.Clear();
session.LastSent = null;
session.Entities.Clear();
}
}

View File

@@ -61,91 +61,71 @@ internal sealed partial class PvsSystem
for (var i = 0; i < limit; i++)
{
var ent = span[i];
ref var meta = ref _metadataMemory.GetRef(ent.Ptr.Index);
if ((mask & meta.VisMask) != meta.VisMask)
if ((mask & ent.Comp.VisibilityMask) != ent.Comp.VisibilityMask)
continue;
// TODO PVS improve this somehow
// Having entities "leave" pvs view just because the pvs entry budget was exceeded sucks.
// This probably requires changing client game state manager to support receiving entities with unknown parents.
// Probably needs to do something similar to pending net entity states, but for entity spawning.
if (!AddEntity(session, ref ent, ref meta, fromTick))
if (!AddEntity(session, ent, fromTick))
limit = directChildren;
}
}
/// <summary>
/// Attempt to add an entity to the to-send lists, while respecting pvs budgets.
/// </summary>
/// <returns>Returns false if the entity would exceed the client's PVS budget.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool AddEntity(PvsSession session, ref PvsChunk.ChunkEntity ent, ref PvsMetadata meta,
GameTick fromTick)
{
DebugTools.Assert(fromTick < _gameTiming.CurTick);
ref var data = ref session.DataMemory.GetRef(ent.Ptr.Index);
if (data.LastSeen == _gameTiming.CurTick)
return true;
if (meta.LifeStage >= EntityLifeStage.Terminating)
{
Log.Error($"Attempted to send deleted entity: {ToPrettyString(ent.Uid)}");
EntityManager.QueueDeleteEntity(ent.Uid);
return true;
}
var (entered,budgetExceeded) = IsEnteringPvsRange(ref data, fromTick, ref session.Budget);
if (budgetExceeded)
return false;
data.LastSeen = _gameTiming.CurTick;
session.ToSend!.Add(ent.Ptr);
if (session.RequestedFull)
{
var state = GetFullEntityState(session.Session, ent.Uid, ent.Meta);
session.States.Add(state);
return true;
}
if (entered)
{
var state = GetEntityState(session.Session, ent.Uid, data.EntityLastAcked, ent.Meta);
session.States.Add(state);
return true;
}
if (meta.LastModifiedTick <= fromTick)
return true;
var entState = GetEntityState(session.Session, ent.Uid, fromTick , ent.Meta);
if (!entState.Empty)
session.States.Add(entState);
return true;
}
/// <summary>
/// Attempt to add an entity to the to-send lists, while respecting pvs budgets.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool AddEntity(PvsSession session, Entity<MetaDataComponent> entity, GameTick fromTick)
{
DebugTools.Assert(fromTick < _gameTiming.CurTick);
ref var data = ref session.DataMemory.GetRef(entity.Comp.PvsData.Index);
var nuid = entity.Comp.NetEntity;
ref var data = ref CollectionsMarshal.GetValueRefOrAddDefault(session.Entities, nuid, out var exists);
if (!exists)
data = new(nuid);
if (entity.Comp.Deleted)
{
Log.Error($"Attempted to send deleted entity: {ToPrettyString(entity, entity)}");
session.Entities.Remove(entity.Comp.NetEntity);
return false;
}
DebugTools.AssertEqual(data!.NetEntity, entity.Comp.NetEntity);
if (data.LastSeen == _gameTiming.CurTick)
return true;
var (entered,budgetExceeded) = IsEnteringPvsRange(ref data, fromTick, ref session.Budget);
var (entered,budgetExceeded) = IsEnteringPvsRange(data, fromTick, ref session.Budget);
if (budgetExceeded)
return false;
if (!AddToSendList(session, data, entity, fromTick, entered))
return false;
DebugTools.AssertNotEqual(data.LastSeen, GameTick.Zero);
return true;
}
/// <summary>
/// This method adds an entity to the list of visible entities, updates the last-seen tick, and computes any
/// required game states.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool AddToSendList(PvsSession session, PvsData data, Entity<MetaDataComponent> entity, GameTick fromTick,
bool entered)
{
DebugTools.Assert(fromTick < _gameTiming.CurTick);
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (data == null)
{
Log.Error($"Encountered null EntityData.");
return false;
}
DebugTools.AssertNotEqual(data.LastSeen, _gameTiming.CurTick);
DebugTools.Assert(data.EntityLastAcked <= fromTick || fromTick == GameTick.Zero);
var (uid, meta) = entity;
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
@@ -167,18 +147,19 @@ internal sealed partial class PvsSystem
}
data.LastSeen = _gameTiming.CurTick;
session.ToSend!.Add(entity.Comp.PvsData);
session.ToSend!.Add(data);
EntityState state;
if (session.RequestedFull)
{
var state = GetFullEntityState(session.Session, uid, meta);
state = GetFullEntityState(session.Session, uid, meta);
session.States.Add(state);
return true;
}
if (entered)
{
var state = GetEntityState(session.Session, uid, data.EntityLastAcked, meta);
state = GetEntityState(session.Session, uid, data.EntityLastAcked, meta);
session.States.Add(state);
return true;
}
@@ -186,10 +167,10 @@ internal sealed partial class PvsSystem
if (meta.EntityLastModifiedTick <= fromTick)
return true;
var entState = GetEntityState(session.Session, uid, fromTick , meta);
state = GetEntityState(session.Session, uid, fromTick , meta);
if (!entState.Empty)
session.States.Add(entState);
if (!state.Empty)
session.States.Add(state);
return true;
}
@@ -200,7 +181,7 @@ internal sealed partial class PvsSystem
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private (bool Entering, bool BudgetExceeded) IsEnteringPvsRange(
ref PvsData data,
PvsData data,
GameTick fromTick,
ref PvsBudget budget)
{

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
@@ -16,8 +15,6 @@ using Robust.Server.Replays;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
@@ -78,7 +75,6 @@ internal sealed partial class PvsSystem : EntitySystem
private PvsAckJob _ackJob;
private PvsChunkJob _chunkJob;
private PvsLeaveJob _leaveJob;
private PvsDeletionsJob _deletionJob;
private EntityQuery<EyeComponent> _eyeQuery;
private EntityQuery<MetaDataComponent> _metaQuery;
@@ -114,10 +110,6 @@ internal sealed partial class PvsSystem : EntitySystem
{
base.Initialize();
if (Marshal.SizeOf<PvsMetadata>() != Marshal.SizeOf<PvsData>())
throw new Exception($"Pvs struct sizes must match");
_deletionJob = new PvsDeletionsJob(this);
_leaveJob = new PvsLeaveJob(this);
_chunkJob = new PvsChunkJob(this);
_ackJob = new PvsAckJob(this);
@@ -133,9 +125,6 @@ internal sealed partial class PvsSystem : EntitySystem
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
_transform.OnGlobalMoveEvent += OnEntityMove;
EntityManager.EntityAdded += OnEntityAdded;
EntityManager.EntityDeleted += OnEntityDeleted;
EntityManager.AfterEntityFlush += AfterEntityFlush;
Subs.CVar(_configManager, CVars.NetPVS, SetPvs, true);
Subs.CVar(_configManager, CVars.NetMaxUpdateRange, OnViewsizeChanged, true);
@@ -146,10 +135,10 @@ internal sealed partial class PvsSystem : EntitySystem
_serverGameStateManager.ClientAck += OnClientAck;
_serverGameStateManager.ClientRequestFull += OnClientRequestFull;
_parallelMgr.ParallelCountChanged += ResetParallelism;
InitializeDirty();
InitializePvsArray();
_parallelMgr.ParallelCountChanged += ResetParallelism;
}
public override void Shutdown()
@@ -158,22 +147,15 @@ internal sealed partial class PvsSystem : EntitySystem
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
_transform.OnGlobalMoveEvent -= OnEntityMove;
EntityManager.EntityAdded -= OnEntityAdded;
EntityManager.EntityDeleted -= OnEntityDeleted;
EntityManager.AfterEntityFlush -= AfterEntityFlush;
_parallelMgr.ParallelCountChanged -= ResetParallelism;
_serverGameStateManager.ClientAck -= OnClientAck;
_serverGameStateManager.ClientRequestFull -= OnClientRequestFull;
ClearPvsData();
ShutdownDirty();
}
public override void Update(float frameTime)
{
ProcessDeletions();
_leaveTask?.WaitOne();
_leaveTask = null;
}
/// <summary>
@@ -271,7 +253,6 @@ internal sealed partial class PvsSystem : EntitySystem
session.LastReceivedAck = _gameTiming.CurTick;
session.RequestedFull = true;
ClearSendHistory(session);
ClearPlayerPvsData(session);
}
private void OnViewsizeChanged(float value)
@@ -361,20 +342,19 @@ internal sealed partial class PvsSystem : EntitySystem
{
var toSend = pvsSession.ToSend;
var toSendSet = new HashSet<NetEntity>(toSend!.Count);
foreach (var intPtr in toSend)
foreach (var data in toSend)
{
toSendSet.Add(IndexToNetEntity(intPtr));
toSendSet.Add(data.NetEntity);
}
DebugTools.AssertEqual(toSend.Count, toSendSet.Count);
foreach (var intPtr in CollectionsMarshal.AsSpan(toSend))
foreach (var data in CollectionsMarshal.AsSpan(toSend))
{
ref var data = ref pvsSession.DataMemory.GetRef(intPtr.Index);
DebugTools.AssertEqual(data.LastSeen, _gameTiming.CurTick);
DebugTools.Assert(ReferenceEquals(data, pvsSession.Entities[data.NetEntity]));
// if an entity is visible, its parents should always be visible.
if (_xformQuery.GetComponent(GetEntity(IndexToNetEntity(intPtr))).ParentUid is not {Valid: true} pUid)
if (_xformQuery.GetComponent(GetEntity(data.NetEntity)).ParentUid is not {Valid: true} pUid)
continue;
DebugTools.Assert(toSendSet.Contains(GetNetEntity(pUid)),
@@ -382,11 +362,11 @@ internal sealed partial class PvsSystem : EntitySystem
}
pvsSession.PreviouslySent.TryGetValue(_gameTiming.CurTick - 1, out var lastSent);
foreach (var intPtr in CollectionsMarshal.AsSpan(lastSent))
foreach (var data in CollectionsMarshal.AsSpan(lastSent))
{
ref var data = ref pvsSession.DataMemory.GetRef(intPtr.Index);
DebugTools.Assert(!pvsSession.Entities.TryGetValue(data.NetEntity, out var old) || ReferenceEquals(data, old));
DebugTools.Assert(data.LastSeen != GameTick.Zero);
DebugTools.AssertEqual(toSendSet.Contains(IndexToNetEntity(intPtr)), data.LastSeen == _gameTiming.CurTick);
DebugTools.AssertEqual(toSendSet.Contains(data.NetEntity), data.LastSeen == _gameTiming.CurTick);
DebugTools.Assert(data.LastSeen == _gameTiming.CurTick
|| data.LastSeen == _gameTiming.CurTick - 1);
}
@@ -429,10 +409,7 @@ internal sealed partial class PvsSystem : EntitySystem
foreach (var session in _disconnected)
{
if (PlayerData.Remove(session, out var pvsSession))
{
ClearSendHistory(pvsSession);
FreeSessionDataMemory(pvsSession);
}
}
var ackJob = ProcessQueuedAcks();

View File

@@ -5,7 +5,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Replays;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Server.Replays;
@@ -14,7 +13,7 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager, ISe
[Dependency] private readonly IEntitySystemManager _sysMan = default!;
private PvsSystem _pvs = default!;
private PvsSession _pvsSession = new(default!, new ResizableMemoryRegion<PvsData>(1)) { DisableCulling = true };
private PvsSession _pvsSession = new(default!) { DisableCulling = true };
public override void Initialize()
{

View File

@@ -1,10 +1,12 @@
using System.Diagnostics;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using static Microsoft.CodeAnalysis.SymbolDisplayFormat;
using static Microsoft.CodeAnalysis.SymbolDisplayMiscellaneousOptions;
namespace Robust.Shared.CompNetworkGenerator
{
@@ -17,26 +19,20 @@ namespace Robust.Shared.CompNetworkGenerator
private const string GlobalEntityUidName = "global::Robust.Shared.GameObjects.EntityUid";
private const string GlobalNullableEntityUidName = "global::Robust.Shared.GameObjects.EntityUid?";
private const string GlobalNetEntityName = "global::Robust.Shared.GameObjects.NetEntity";
private const string GlobalNetEntityNullableName = "global::Robust.Shared.GameObjects.NetEntity?";
private const string GlobalEntityCoordinatesName = "global::Robust.Shared.Map.EntityCoordinates";
private const string GlobalNullableEntityCoordinatesName = "global::Robust.Shared.Map.EntityCoordinates?";
private const string GlobalEntityUidSetName = "global::System.Collections.Generic.HashSet<global::Robust.Shared.GameObjects.EntityUid>";
private const string GlobalNetEntityUidSetName = $"global::System.Collections.Generic.HashSet<{GlobalNetEntityName}>";
private const string GlobalNetEntityUidSetName = "global::System.Collections.Generic.HashSet<global::Robust.Shared.GameObjects.NetEntity>";
private const string GlobalEntityUidListName = "global::System.Collections.Generic.List<global::Robust.Shared.GameObjects.EntityUid>";
private const string GlobalNetEntityUidListName = $"global::System.Collections.Generic.List<{GlobalNetEntityName}>";
private const string GlobalNetEntityUidListName = "global::System.Collections.Generic.List<global::Robust.Shared.GameObjects.NetEntity>";
private const string GlobalDictionaryName = "global::System.Collections.Generic.Dictionary<TKey, TValue>";
private const string GlobalHashSetName = "global::System.Collections.Generic.HashSet<T>";
private const string GlobalListName = "global::System.Collections.Generic.List<T>";
private static readonly SymbolDisplayFormat FullNullableFormat =
FullyQualifiedFormat.WithMiscellaneousOptions(IncludeNullableReferenceTypeModifier);
private static string? GenerateSource(in GeneratorExecutionContext context, INamedTypeSymbol classSymbol, CSharpCompilation comp, bool raiseAfterAutoHandle)
private static string GenerateSource(in GeneratorExecutionContext context, INamedTypeSymbol classSymbol, CSharpCompilation comp, bool raiseAfterAutoHandle)
{
var nameSpace = classSymbol.ContainingNamespace.ToDisplayString();
var componentName = classSymbol.Name;
@@ -136,7 +132,7 @@ namespace Robust.Shared.CompNetworkGenerator
foreach (var (type, name) in fields)
{
var typeDisplayStr = type.ToDisplayString(FullNullableFormat);
var typeDisplayStr = type.ToDisplayString(FullyQualifiedFormat);
var nullable = type.NullableAnnotation == NullableAnnotation.Annotated;
var nullableAnnotation = nullable ? "?" : string.Empty;
@@ -185,62 +181,6 @@ namespace Robust.Shared.CompNetworkGenerator
break;
default:
if (type is INamedTypeSymbol { TypeArguments.Length: 2 } named &&
named.ConstructedFrom.ToDisplayString(FullyQualifiedFormat) == GlobalDictionaryName)
{
var key = named.TypeArguments[0].ToDisplayString(FullNullableFormat);
var keyNullable = key.EndsWith("?");
var value = named.TypeArguments[1].ToDisplayString(FullNullableFormat);
var valueNullable = value.EndsWith("?");
if (key is GlobalEntityUidName or GlobalNullableEntityUidName)
{
key = keyNullable ? GlobalNetEntityNullableName : GlobalNetEntityName;
var ensureGeneric = $"{componentName}, {value}";
if (value is GlobalEntityUidName or GlobalNullableEntityUidName)
{
value = valueNullable ? GlobalNetEntityNullableName : GlobalNetEntityName;
ensureGeneric = componentName;
}
stateFields.Append($@"
public Dictionary<{key}, {value}> {name} = default!;");
getStateInit.Append($@"
{name} = GetNetEntityDictionary(component.{name}),");
if (valueNullable && value is not GlobalNetEntityName and not GlobalNetEntityNullableName)
{
handleStateSetters.Append($@"
EnsureEntityDictionaryNullableValue<{componentName}, {value}>(state.{name}, uid, component.{name});");
}
else
{
handleStateSetters.Append($@"
EnsureEntityDictionary<{ensureGeneric}>(state.{name}, uid, component.{name});");
}
break;
}
if (value is GlobalEntityUidName or GlobalNullableEntityUidName)
{
value = valueNullable ? GlobalNetEntityNullableName : GlobalNetEntityName;
stateFields.Append($@"
public Dictionary<{key}, {value}> {name} = default!;");
getStateInit.Append($@"
{name} = GetNetEntityDictionary(component.{name}),");
handleStateSetters.Append($@"
EnsureEntityDictionary<{componentName}, {key}>(state.{name}, uid, component.{name});");
break;
}
}
stateFields.Append($@"
public {typeDisplayStr} {name} = default!;");
@@ -253,7 +193,7 @@ namespace Robust.Shared.CompNetworkGenerator
handleStateSetters.Append($@"
if (state.{name} == null)
component.{name} = null!;
component.{name} = null;
else
component.{name} = new(state.{name});");
}
@@ -279,7 +219,6 @@ namespace Robust.Shared.CompNetworkGenerator
}
return $@"// <auto-generated />
#nullable enable
using System;
using Robust.Shared.GameStates;
using Robust.Shared.GameObjects;
@@ -341,10 +280,10 @@ public partial class {componentName}
{
var attr = type.Attribute;
var raiseEv = false;
if (attr.ConstructorArguments is [{Value: bool raise}])
if (attr.ConstructorArguments.Length == 1 && attr.ConstructorArguments[0].Value != null)
{
// Get the afterautohandle bool, which is first constructor arg
raiseEv = raise;
raiseEv = (bool) attr.ConstructorArguments[0].Value;
}
var source = GenerateSource(context, type.Type, comp, raiseEv);
@@ -386,11 +325,11 @@ public partial class {componentName}
attr.AttributeClass != null &&
attr.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default));
if (typeSymbol == null)
continue;
if (relevantAttribute == null)
{
if (typeSymbol == null)
continue;
foreach (var mem in typeSymbol.GetMembers())
{
var attribute = mem.GetAttributes().FirstOrDefault(a =>

View File

@@ -1,5 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>9</LangVersion>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
</ItemGroup>
</Project>

View File

@@ -186,10 +186,10 @@ namespace Robust.Shared.Maths
[Pure]
public readonly Box2 Intersect(in Box2 other)
{
var ourLeftBottom = BottomLeft;
var ourRightTop = TopRight;
var otherLeftBottom = other.BottomLeft;
var otherRightTop = other.TopRight;
var ourLeftBottom = new Vector2(Left, Bottom);
var ourRightTop = new Vector2(Right, Top);
var otherLeftBottom = new Vector2(other.Left, other.Bottom);
var otherRightTop = new Vector2(other.Right, other.Top);
var max = Vector2.Max(ourLeftBottom, otherLeftBottom);
var min = Vector2.Min(ourRightTop, otherRightTop);
@@ -219,10 +219,10 @@ namespace Robust.Shared.Maths
[Pure]
public readonly Box2 Union(in Box2 other)
{
var ourLeftBottom = BottomLeft;
var otherLeftBottom = other.BottomLeft;
var ourRightTop = TopRight;
var otherRightTop = other.TopRight;
var ourLeftBottom = new Vector2(Left, Bottom);
var otherLeftBottom = new Vector2(other.Left, other.Bottom);
var ourRightTop = new Vector2(Right, Top);
var otherRightTop = new Vector2(other.Right, other.Top);
var leftBottom = Vector2.Min(ourLeftBottom, otherLeftBottom);
var rightTop = Vector2.Max(ourRightTop, otherRightTop);

View File

@@ -3,7 +3,6 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using JetBrains.Annotations;
using Robust.Shared.Utility;
namespace Robust.Shared.Maths
@@ -58,17 +57,7 @@ namespace Robust.Shared.Maths
}
/// <summary>
/// Enlarges the box by the specified value.
/// </summary>
[Pure]
public readonly Box2Rotated Enlarged(float value)
{
var box = Box.Enlarged(value);
return new Box2Rotated(box, Rotation, Origin);
}
/// <summary>
/// Calculates the smallest AABB that will encompass the rotated box. The AABB is in local space.
/// calculates the smallest AABB that will encompass the rotated box. The AABB is in local space.
/// </summary>
public readonly Box2 CalcBoundingBox()
{

View File

@@ -697,32 +697,6 @@ namespace Robust.Shared.Maths
#endregion
/// <summary>
/// Round up (ceiling) a value to a multiple of a known power of two.
/// </summary>
/// <param name="value">The value to round up.</param>
/// <param name="powerOfTwo">
/// The power of two to round up to a multiple of. The result is undefined if this is not a power of two.
/// </param>
/// <remarks>
/// The result is undefined if either value is negative.
/// </remarks>
/// <typeparam name="T">The type of integer to operate on.</typeparam>
/// <example>
/// <code>
/// MathHelper.CeilMultiplyPowerOfTwo(5, 4) // 8
/// MathHelper.CeilMultiplyPowerOfTwo(4, 4) // 4
/// MathHelper.CeilMultiplyPowerOfTwo(8, 4) // 8
/// </code>
/// </example>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T CeilMultipleOfPowerOfTwo<T>(T value, T powerOfTwo) where T : IBinaryInteger<T>
{
var mask = powerOfTwo - T.One;
var remainder = value & mask;
return remainder == T.Zero ? value : (value | mask) + T.One;
}
#endregion Public Members
}
}

View File

@@ -1,29 +0,0 @@
using System;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Shared.Analyzers;
/// <summary>
/// Indicate that a <see cref="Component"/> should automatically handle unpausing of timer fields.
/// </summary>
/// <remarks>
/// When this attribute is set on a <see cref="Component"/>, an <see cref="EntitySystem"/> will automatically be
/// generated that increments any fields tagged with <see cref="AutoPausedFieldAttribute"/> when the entity is unpaused
/// (<see cref="EntityUnpausedEvent"/>).
/// </remarks>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
[BaseTypeRequired(typeof(IComponent))]
public sealed class AutoGenerateComponentPauseAttribute : Attribute
{
public bool Dirty = false;
}
/// <summary>
/// Mark a field or property to automatically handle unpausing with <see cref="AutoGenerateComponentPauseAttribute"/>.
/// </summary>
/// <remarks>
/// The type of the field or prototype must be <see cref="TimeSpan"/> (potentially nullable).
/// </remarks>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class AutoPausedFieldAttribute : Attribute;

View File

@@ -266,7 +266,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
return sound == null ? null : PlayGlobal(GetSound(sound), recipient, sound.Params);
}
public abstract void LoadStream<T>(Entity<AudioComponent> entity, T stream);
public abstract void LoadStream<T>(AudioComponent component, T stream);
/// <summary>
/// Play an audio file globally, without position.

View File

@@ -183,30 +183,6 @@ namespace Robust.Shared
public static readonly CVarDef<bool> NetPVS =
CVarDef.Create("net.pvs", true, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// Size increments for the automatic growth of Pvs' entity data storage. 0 will increase it by factors of 2
/// </summary>
public static readonly CVarDef<int> NetPvsEntityGrowth =
CVarDef.Create("net.pvs_entity_growth", 1 << 16, CVar.ARCHIVE | CVar.SERVERONLY);
/// <summary>
/// Initial size of PVS' entity data storage.
/// </summary>
public static readonly CVarDef<int> NetPvsEntityInitial =
CVarDef.Create("net.pvs_entity_initial", 1 << 16, CVar.ARCHIVE | CVar.SERVERONLY);
/// <summary>
/// Maximum ever size of PVS' entity data storage.
/// </summary>
/// <remarks>
/// <para>
/// Arbitrarily set to a default of 16 million entities.
/// Increasing this parameter does not increase real memory usage, only virtual.
/// </para>
/// </remarks>
public static readonly CVarDef<int> NetPvsEntityMax =
CVarDef.Create("net.pvs_entity_max", 1 << 24, CVar.ARCHIVE | CVar.SERVERONLY);
/// <summary>
/// If false, this will run more parts of PVS synchronously. This will generally slow it down, can be useful
/// for collecting tick timing metrics.
@@ -1055,12 +1031,6 @@ namespace Robust.Shared
* AUDIO
*/
/// <summary>
/// Default limit for concurrently playing an audio file.
/// </summary>
public static readonly CVarDef<int> AudioDefaultConcurrent =
CVarDef.Create("audio.default_concurrent", 16, CVar.CLIENTONLY | CVar.ARCHIVE);
public static readonly CVarDef<int> AudioAttenuation =
CVarDef.Create("audio.attenuation", (int) Attenuation.LinearDistanceClamped, CVar.REPLICATED | CVar.ARCHIVE);
@@ -1528,7 +1498,7 @@ namespace Robust.Shared
/// Maximum compressed size of a replay recording (in kilobytes) before recording automatically stops.
/// </summary>
public static readonly CVarDef<long> ReplayMaxCompressedSize = CVarDef.Create("replay.max_compressed_size",
1024L * 512, CVar.ARCHIVE);
1024L * 256, CVar.ARCHIVE);
/// <summary>
/// Maximum uncompressed size of a replay recording (in kilobytes) before recording automatically stops.

View File

@@ -6,9 +6,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Robust.Shared.Collections;
@@ -52,41 +50,6 @@ public struct ValueList<T> : IEnumerable<T>
Count = 0;
}
/// <summary>
/// Creates a list by copying the contents of the source list.
/// </summary>
public ValueList(List<T> list) : this(list, 0, list.Count)
{
}
/// <summary>
/// Creates a list by copying the contents of the source list.
/// </summary>
public ValueList(List<T> list, int start, int count)
{
_items = new T[count];
var liSpan = CollectionsMarshal.AsSpan(list)[start..(start + count)];
liSpan.CopyTo(_items);
Count = count;
}
/// <summary>
/// Creates a list by copying the contents of the source list.
/// </summary>
public ValueList(IReadOnlyCollection<T> list)
{
var count = list.Count;
_items = new T[count];
foreach (var entry in list)
{
var size = Count;
AddNoResize(entry, size);
}
}
/// <summary>
/// Create a list by copying the contents from another enumerable.
/// </summary>
@@ -194,10 +157,12 @@ public struct ValueList<T> : IEnumerable<T>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(T item)
{
var array = _items;
var size = Count;
if ((uint)size < (uint)Capacity)
{
AddNoResize(item, size);
Count = size + 1;
array![size] = item;
}
else
{
@@ -205,14 +170,6 @@ public struct ValueList<T> : IEnumerable<T>
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AddNoResize(T item, int size)
{
var array = _items;
Count = size + 1;
array![size] = item;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T AddRef()
{
@@ -565,46 +522,4 @@ public struct ValueList<T> : IEnumerable<T>
RemoveAt(Count - 1);
return old;
}
/// <summary>
/// Adds a range of values from the source list.
/// </summary>
public void AddRange(ValueList<T> list)
{
var liSpan = list.Span;
AddRange(liSpan);
}
/// <summary>
/// Adds a range of values from the source list.
/// </summary>
public void AddRange(List<T> list)
{
var liSpan = CollectionsMarshal.AsSpan(list);
AddRange(liSpan);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddRange(Span<T> span)
{
var spanCount = span.Length;
EnsureCapacity(Count + spanCount);
var target = new Span<T>(_items, Count, spanCount);
span.CopyTo(target);
Count += spanCount;
}
/// <summary>
/// Fills this with default data up to the specified count.
/// </summary>
public void EnsureLength(int newCount)
{
if (Count > newCount)
return;
EnsureCapacity(newCount);
var region = new Span<T>(_items, Count, (newCount - Count));
region.Clear();
Count = newCount;
}
}

View File

@@ -71,6 +71,46 @@ namespace Robust.Shared.Containers
[DataField("showEnts")]
public bool ShowContents { get; set; }
[Obsolete("Use container system method")]
public bool Insert(
EntityUid toinsert,
IEntityManager? entMan = null,
TransformComponent? transform = null,
TransformComponent? ownerTransform = null,
MetaDataComponent? meta = null,
PhysicsComponent? physics = null,
bool force = false)
{
IoCManager.Resolve(ref entMan);
return entMan.System<SharedContainerSystem>().Insert((toinsert, transform, meta, physics), this, ownerTransform, force);
}
/// <summary>
/// Whether the given entity can be inserted into this container.
/// </summary>
/// <param name="assumeEmpty">Whether to assume that the container is currently empty.</param>
protected internal virtual bool CanInsert(EntityUid toInsert, bool assumeEmpty, IEntityManager entMan) => true;
[Obsolete("Use container system method")]
public bool Remove(
EntityUid toRemove,
IEntityManager? entMan = null,
TransformComponent? xform = null,
MetaDataComponent? meta = null,
bool reparent = true,
bool force = false,
EntityCoordinates? destination = null,
Angle? localRotation = null
)
{
IoCManager.Resolve(ref entMan);
return entMan.System<SharedContainerSystem>().Remove((toRemove, xform, meta), this, reparent, force, destination, localRotation);
}
[Obsolete("Use container system method")]
public void ForceRemove(EntityUid toRemove, IEntityManager? entMan = null, MetaDataComponent? meta = null)
=> Remove(toRemove, entMan, meta: meta, reparent: false, force: true);
/// <summary>
/// Checks if the entity is contained in this container.
/// This is not recursive, so containers of children are not checked.
@@ -80,10 +120,18 @@ namespace Robust.Shared.Containers
public abstract bool Contains(EntityUid contained);
/// <summary>
/// Whether the given entity can be inserted into this container.
/// Clears the container and marks it as deleted.
/// </summary>
/// <param name="assumeEmpty">Whether to assume that the container is currently empty.</param>
protected internal virtual bool CanInsert(EntityUid toInsert, bool assumeEmpty, IEntityManager entMan) => true;
[Obsolete("use system method")]
public void Shutdown(IEntityManager? entMan = null, INetManager? _ = null)
{
IoCManager.Resolve(ref entMan);
entMan.System<SharedContainerSystem>().ShutdownContainer(this);
}
/// <inheritdoc />
[Access(typeof(SharedContainerSystem))]
protected internal abstract void InternalShutdown(IEntityManager entMan, SharedContainerSystem system, bool isClient);
/// <summary>
/// Implement to store the reference in whatever form you want
@@ -100,14 +148,5 @@ namespace Robust.Shared.Containers
/// <param name="entMan"></param>
[Access(typeof(SharedContainerSystem))]
protected internal abstract void InternalRemove(EntityUid toRemove, IEntityManager entMan);
/// <summary>
/// Implement to clear the container and mark it as deleted.
/// </summary>
/// <param name="entMan"></param>
/// <param name="system"></param>
/// <param name=isClient"></param>
[Access(typeof(SharedContainerSystem))]
protected internal abstract void InternalShutdown(IEntityManager entMan, SharedContainerSystem system, bool isClient);
}
}

View File

@@ -32,7 +32,7 @@ namespace Robust.Shared.ContentPack
String("short").ThenReturn(PrimitiveTypeCode.Int16);
private static readonly Parser<char, PrimitiveTypeCode> UInt16TypeParser =
String("ushort").ThenReturn(PrimitiveTypeCode.UInt16);
String("ushort").ThenReturn(PrimitiveTypeCode.UInt32);
private static readonly Parser<char, PrimitiveTypeCode> Int32TypeParser =
String("int").ThenReturn(PrimitiveTypeCode.Int32);

View File

@@ -84,146 +84,12 @@ Types:
- "bool get_HasContents()"
Lidgren.Network:
NetBuffer:
Methods:
- "byte[] get_Data()"
- "void set_Data(byte[])"
- "int get_LengthBytes()"
- "void set_LengthBytes(int)"
- "int get_LengthBits()"
- "void set_LengthBits(int)"
- "long get_Position()"
- "void set_Position(long)"
- "int get_PositionInBytes()"
- "byte[] PeekDataBuffer()"
- "bool PeekBoolean()"
- "byte PeekByte()"
- "sbyte PeekSByte()"
- "byte PeekByte(int)"
- "System.Span`1<byte> PeekBytes(System.Span`1<byte>)"
- "byte[] PeekBytes(int)"
- "void PeekBytes(byte[], int, int)"
- "short PeekInt16()"
- "ushort PeekUInt16()"
- "int PeekInt32()"
- "int PeekInt32(int)"
- "uint PeekUInt32()"
- "uint PeekUInt32(int)"
- "ulong PeekUInt64()"
- "long PeekInt64()"
- "ulong PeekUInt64(int)"
- "long PeekInt64(int)"
- "float PeekFloat()"
- "System.Half PeekHalf()"
- "float PeekSingle()"
- "double PeekDouble()"
- "string PeekString()"
- "int PeekStringSize()"
- "bool ReadBoolean()"
- "byte ReadByte()"
- "bool ReadByte(ref byte)"
- "sbyte ReadSByte()"
- "byte ReadByte(int)"
- "System.Span`1<byte> ReadBytes(System.Span`1<byte>)"
- "byte[] ReadBytes(int)"
- "bool ReadBytes(int, ref byte[])"
- "bool TryReadBytes(System.Span`1<byte>)"
- "void ReadBytes(byte[], int, int)"
- "void ReadBits(System.Span`1<byte>, int)"
- "void ReadBits(byte[], int, int)"
- "short ReadInt16()"
- "ushort ReadUInt16()"
- "int ReadInt32()"
- "bool ReadInt32(ref int)"
- "int ReadInt32(int)"
- "uint ReadUInt32()"
- "bool ReadUInt32(ref uint)"
- "uint ReadUInt32(int)"
- "ulong ReadUInt64()"
- "long ReadInt64()"
- "ulong ReadUInt64(int)"
- "long ReadInt64(int)"
- "float ReadFloat()"
- "System.Half ReadHalf()"
- "float ReadSingle()"
- "bool ReadSingle(ref float)"
- "double ReadDouble()"
- "uint ReadVariableUInt32()"
- "bool ReadVariableUInt32(ref uint)"
- "int ReadVariableInt32()"
- "long ReadVariableInt64()"
- "ulong ReadVariableUInt64()"
- "float ReadSignedSingle(int)"
- "float ReadUnitSingle(int)"
- "float ReadRangedSingle(float, float, int)"
- "int ReadRangedInteger(int, int)"
- "long ReadRangedInteger(long, long)"
- "string ReadString()"
- "bool ReadString(ref string)"
- "double ReadTime(Lidgren.Network.NetConnection, bool)"
- "System.Net.IPEndPoint ReadIPEndPoint()"
- "void SkipPadBits()"
- "void ReadPadBits()"
- "void SkipPadBits(int)"
- "void EnsureBufferSize(int)"
- "void Write(bool)"
- "void Write(byte)"
- "void WriteAt(int, byte)"
- "void Write(sbyte)"
- "void Write(byte, int)"
- "void Write(byte[])"
- "void Write(System.ReadOnlySpan`1<byte>)"
- "void Write(byte[], int, int)"
- "void Write(ushort)"
- "void WriteAt(int, ushort)"
- "void Write(ushort, int)"
- "void Write(short)"
- "void WriteAt(int, short)"
- "void Write(int)"
- "void WriteAt(int, int)"
- "void Write(uint)"
- "void WriteAt(int, uint)"
- "void Write(uint, int)"
- "void Write(int, int)"
- "void Write(ulong)"
- "void WriteAt(int, ulong)"
- "void Write(ulong, int)"
- "void Write(long)"
- "void Write(long, int)"
- "void Write(System.Half)"
- "void Write(float)"
- "void Write(double)"
- "int WriteVariableUInt32(uint)"
- "int WriteVariableInt32(int)"
- "int WriteVariableInt64(long)"
- "int WriteVariableUInt64(ulong)"
- "void WriteSignedSingle(float, int)"
- "void WriteUnitSingle(float, int)"
- "void WriteRangedSingle(float, float, float, int)"
- "int WriteRangedInteger(int, int, int)"
- "int WriteRangedInteger(long, long, long)"
- "void Write(string)"
- "void Write(System.Net.IPEndPoint)"
- "void WriteTime(bool)"
- "void WriteTime(double, bool)"
- "void WritePadBits()"
- "void WritePadBits(int)"
- "void Write(Lidgren.Network.NetBuffer)"
- "void Zero(int)"
- "void .ctor()"
All: True
NetDeliveryMethod: { }
NetIncomingMessage:
Methods:
- "Lidgren.Network.NetIncomingMessageType get_MessageType()"
- "Lidgren.Network.NetDeliveryMethod get_DeliveryMethod()"
- "int get_SequenceChannel()"
- "System.Net.IPEndPoint get_SenderEndPoint()"
- "Lidgren.Network.NetConnection get_SenderConnection()"
- "double get_ReceiveTime()"
- "double ReadTime(bool)"
- "string ToString()"
All: True
NetOutgoingMessage:
Methods:
- "string ToString()"
All: True
Nett:
CommentLocation: { } # Enum
Toml:

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using Robust.Shared.Utility;
namespace Robust.Shared.ContentPack
@@ -136,37 +135,11 @@ namespace Robust.Shared.ContentPack
path = path.Directory;
var fullPath = GetFullPath(path);
if (OperatingSystem.IsWindows())
Process.Start(new ProcessStartInfo
{
Process.Start(new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = ".",
WorkingDirectory = fullPath,
});
}
else if (OperatingSystem.IsMacOS())
{
Process.Start(new ProcessStartInfo
{
FileName = "open",
Arguments = ".",
WorkingDirectory = fullPath,
});
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD())
{
Process.Start(new ProcessStartInfo
{
FileName = "xdg-open",
Arguments = ".",
WorkingDirectory = fullPath,
});
}
else
{
throw new NotSupportedException("Opening OS windows not supported on this OS");
}
UseShellExecute = true,
FileName = fullPath,
});
}
#endregion

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Robust.Shared.GameStates;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
@@ -200,11 +199,6 @@ namespace Robust.Shared.GameObjects
// (Creation can still be cleared though)
ClearCreationTick();
}
/// <summary>
/// Offset into internal PVS data.
/// </summary>
internal PvsIndex PvsData;
}
[Flags]
@@ -227,32 +221,16 @@ namespace Robust.Shared.GameObjects
/// </summary>
Detached = 1 << 2,
/// <summary>
/// Indicates this entity can never be handled by the client as PVS detached.
/// </summary>
Undetachable = 1 << 3,
/// <summary>
/// If true, then this entity is considered a "high priority" entity and will be sent to players from further
/// away. Useful for things like light sources and occluders. Only works if the entity is directly parented to
/// a grid or map.
/// </summary>
PvsPriority = 1 << 4,
PvsPriority = 1 << 3,
}
/// <summary>
/// Key struct for uniquely identifying a PVS chunk.
/// </summary>
internal readonly record struct PvsChunkLocation(EntityUid Uid, Vector2i Indices);
/// <summary>
/// An opaque index into the PVS data arrays on the server.
/// </summary>
internal readonly record struct PvsIndex(int Index)
{
/// <summary>
/// An invalid index. This is also used as a marker value in the free list.
/// </summary>
public static readonly PvsIndex Invalid = new PvsIndex(-1);
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
@@ -32,7 +33,6 @@ public partial class EntityManager
/// </summary>
internal void SetNetEntity(EntityUid uid, NetEntity netEntity, MetaDataComponent component)
{
DebugTools.Assert(component.NetEntity == NetEntity.Invalid || _netMan.IsClient);
DebugTools.Assert(!NetEntityLookup.ContainsKey(netEntity));
NetEntityLookup[netEntity] = (uid, component);
component.NetEntity = netEntity;
@@ -279,66 +279,6 @@ public partial class EntityManager
return entities;
}
public Dictionary<EntityUid, T> GetEntityDictionary<T>(Dictionary<NetEntity, T> netEntities)
{
var entities = new Dictionary<EntityUid, T>(netEntities.Count);
foreach (var pair in netEntities)
{
entities.Add(GetEntity(pair.Key), pair.Value);
}
return entities;
}
public Dictionary<T, EntityUid> GetEntityDictionary<T>(Dictionary<T, NetEntity> netEntities) where T : notnull
{
var entities = new Dictionary<T, EntityUid>(netEntities.Count);
foreach (var pair in netEntities)
{
entities.Add(pair.Key, GetEntity(pair.Value));
}
return entities;
}
public Dictionary<T, EntityUid?> GetEntityDictionary<T>(Dictionary<T, NetEntity?> netEntities) where T : notnull
{
var entities = new Dictionary<T, EntityUid?>(netEntities.Count);
foreach (var pair in netEntities)
{
entities.Add(pair.Key, GetEntity(pair.Value));
}
return entities;
}
public Dictionary<EntityUid, EntityUid> GetEntityDictionary(Dictionary<NetEntity, NetEntity> netEntities)
{
var entities = new Dictionary<EntityUid, EntityUid>(netEntities.Count);
foreach (var pair in netEntities)
{
entities.Add(GetEntity(pair.Key), GetEntity(pair.Value));
}
return entities;
}
public Dictionary<EntityUid, EntityUid?> GetEntityDictionary(Dictionary<NetEntity, NetEntity?> netEntities)
{
var entities = new Dictionary<EntityUid, EntityUid?>(netEntities.Count);
foreach (var pair in netEntities)
{
entities.Add(GetEntity(pair.Key), GetEntity(pair.Value));
}
return entities;
}
public HashSet<EntityUid> EnsureEntitySet<T>(HashSet<NetEntity> netEntities, EntityUid callerEntity)
{
var entities = new HashSet<EntityUid>(netEntities.Count);
@@ -384,72 +324,6 @@ public partial class EntityManager
}
}
public void EnsureEntityDictionary<TComp, TValue>(Dictionary<NetEntity, TValue> netEntities, EntityUid callerEntity,
Dictionary<EntityUid, TValue> entities)
{
entities.Clear();
entities.EnsureCapacity(netEntities.Count);
foreach (var pair in netEntities)
{
entities.Add(EnsureEntity<TComp>(pair.Key, callerEntity), pair.Value);
}
}
public void EnsureEntityDictionaryNullableValue<TComp, TValue>(Dictionary<NetEntity, TValue?> netEntities, EntityUid callerEntity,
Dictionary<EntityUid, TValue?> entities)
{
entities.Clear();
entities.EnsureCapacity(netEntities.Count);
foreach (var pair in netEntities)
{
entities.Add(EnsureEntity<TComp>(pair.Key, callerEntity), pair.Value);
}
}
public void EnsureEntityDictionary<TComp, TKey>(Dictionary<TKey, NetEntity> netEntities, EntityUid callerEntity,
Dictionary<TKey, EntityUid> entities) where TKey : notnull
{
entities.Clear();
entities.EnsureCapacity(netEntities.Count);
foreach (var pair in netEntities)
{
entities.Add(pair.Key, EnsureEntity<TComp>(pair.Value, callerEntity));
}
}
public void EnsureEntityDictionary<TComp, TKey>(Dictionary<TKey, NetEntity?> netEntities, EntityUid callerEntity,
Dictionary<TKey, EntityUid?> entities) where TKey : notnull
{
entities.Clear();
entities.EnsureCapacity(netEntities.Count);
foreach (var pair in netEntities)
{
entities.Add(pair.Key, EnsureEntity<TComp>(pair.Value, callerEntity));
}
}
public void EnsureEntityDictionary<TComp>(Dictionary<NetEntity, NetEntity> netEntities, EntityUid callerEntity,
Dictionary<EntityUid, EntityUid> entities)
{
entities.Clear();
entities.EnsureCapacity(netEntities.Count);
foreach (var pair in netEntities)
{
entities.Add(EnsureEntity<TComp>(pair.Key, callerEntity), EnsureEntity<TComp>(pair.Value, callerEntity));
}
}
public void EnsureEntityDictionary<TComp>(Dictionary<NetEntity, NetEntity?> netEntities, EntityUid callerEntity,
Dictionary<EntityUid, EntityUid?> entities)
{
entities.Clear();
entities.EnsureCapacity(netEntities.Count);
foreach (var pair in netEntities)
{
entities.Add(EnsureEntity<TComp>(pair.Key, callerEntity), EnsureEntity<TComp>(pair.Value, callerEntity));
}
}
/// <inheritdoc />
public List<EntityUid> GetEntityList(ICollection<NetEntity> netEntities)
{
@@ -593,71 +467,6 @@ public partial class EntityManager
return netEntities;
}
/// <inheritdoc />
public Dictionary<NetEntity, T> GetNetEntityDictionary<T>(Dictionary<EntityUid, T> entities)
{
var netEntities = new Dictionary<NetEntity, T>(entities.Count);
foreach (var pair in entities)
{
netEntities.Add(GetNetEntity(pair.Key), pair.Value);
}
return netEntities;
}
/// <inheritdoc />
public Dictionary<T, NetEntity> GetNetEntityDictionary<T>(Dictionary<T, EntityUid> entities) where T : notnull
{
var netEntities = new Dictionary<T, NetEntity>(entities.Count);
foreach (var pair in entities)
{
netEntities.Add(pair.Key, GetNetEntity(pair.Value));
}
return netEntities;
}
/// <inheritdoc />
public Dictionary<T, NetEntity?> GetNetEntityDictionary<T>(Dictionary<T, EntityUid?> entities) where T : notnull
{
var netEntities = new Dictionary<T, NetEntity?>(entities.Count);
foreach (var pair in entities)
{
netEntities.Add(pair.Key, GetNetEntity(pair.Value));
}
return netEntities;
}
/// <inheritdoc />
public Dictionary<NetEntity, NetEntity> GetNetEntityDictionary(Dictionary<EntityUid, EntityUid> entities)
{
var netEntities = new Dictionary<NetEntity, NetEntity>(entities.Count);
foreach (var pair in entities)
{
netEntities.Add(GetNetEntity(pair.Key), GetNetEntity(pair.Value));
}
return netEntities;
}
/// <inheritdoc />
public Dictionary<NetEntity, NetEntity?> GetNetEntityDictionary(Dictionary<EntityUid, EntityUid?> entities)
{
var netEntities = new Dictionary<NetEntity, NetEntity?>(entities.Count);
foreach (var pair in entities)
{
netEntities.Add(GetNetEntity(pair.Key), GetNetEntity(pair.Value));
}
return netEntities;
}
/// <inheritdoc />
public HashSet<EntityCoordinates> GetEntitySet(HashSet<NetCoordinates> netEntities)
{

View File

@@ -906,16 +906,6 @@ namespace Robust.Shared.GameObjects
DebugTools.Assert("Why are you raising predictive events on the server?");
}
/// <summary>
/// Raises an event locally on client or networked on server.
/// </summary>
public abstract void RaiseSharedEvent<T>(T message, EntityUid? user = null) where T : EntityEventArgs;
/// <summary>
/// Raises an event locally on client or networked on server.
/// </summary>
public abstract void RaiseSharedEvent<T>(T message, ICommonSession? user = null) where T : EntityEventArgs;
/// <summary>
/// Factory for generating a new EntityUid for an entity currently being created.
/// </summary>

View File

@@ -1049,42 +1049,6 @@ public partial class EntitySystem
EntityManager.EnsureEntityList<T>(netEntities, callerEntity, entities);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void EnsureEntityDictionary<TComp, TValue>(Dictionary<NetEntity, TValue> netEntities, EntityUid callerEntity, Dictionary<EntityUid, TValue> entities)
{
EntityManager.EnsureEntityDictionary<TComp, TValue>(netEntities, callerEntity, entities);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void EnsureEntityDictionaryNullableValue<TComp, TValue>(Dictionary<NetEntity, TValue?> netEntities, EntityUid callerEntity, Dictionary<EntityUid, TValue?> entities)
{
EntityManager.EnsureEntityDictionaryNullableValue<TComp, TValue>(netEntities, callerEntity, entities);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void EnsureEntityDictionary<TComp, TKey>(Dictionary<TKey, NetEntity> netEntities, EntityUid callerEntity, Dictionary<TKey, EntityUid> entities) where TKey : notnull
{
EntityManager.EnsureEntityDictionary<TComp, TKey>(netEntities, callerEntity, entities);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void EnsureEntityDictionary<TComp, TKey>(Dictionary<TKey, NetEntity?> netEntities, EntityUid callerEntity, Dictionary<TKey, EntityUid?> entities) where TKey : notnull
{
EntityManager.EnsureEntityDictionary<TComp, TKey>(netEntities, callerEntity, entities);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void EnsureEntityDictionary<TComp>(Dictionary<NetEntity, NetEntity> netEntities, EntityUid callerEntity, Dictionary<EntityUid, EntityUid> entities)
{
EntityManager.EnsureEntityDictionary<TComp>(netEntities, callerEntity, entities);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void EnsureEntityDictionary<TComp>(Dictionary<NetEntity, NetEntity?> netEntities, EntityUid callerEntity, Dictionary<EntityUid, EntityUid?> entities)
{
EntityManager.EnsureEntityDictionary<TComp>(netEntities, callerEntity, entities);
}
/// <summary>
/// Returns the <see cref="EntityUid"/> of a <see cref="NetEntity"/>. Returns <see cref="EntityUid.Invalid"/> if it doesn't exist.
/// </summary>
@@ -1220,96 +1184,6 @@ public partial class EntitySystem
return EntityManager.GetEntityArray(netEntities);
}
/// <summary>
/// Returns the <see cref="NetEntity"/> versions of the supplied entities. Logs an error if the entities do not exist.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Dictionary<NetEntity, T> GetNetEntityDictionary<T>(Dictionary<EntityUid, T> uids)
{
return EntityManager.GetNetEntityDictionary(uids);
}
/// <summary>
/// Returns the <see cref="NetEntity"/> versions of the supplied entities. Logs an error if the entities do not exist.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Dictionary<T, NetEntity> GetNetEntityDictionary<T>(Dictionary<T, EntityUid> uids) where T : notnull
{
return EntityManager.GetNetEntityDictionary(uids);
}
/// <summary>
/// Returns the <see cref="NetEntity"/> versions of the supplied entities. Logs an error if the entities do not exist.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Dictionary<T, NetEntity?> GetNetEntityDictionary<T>(Dictionary<T, EntityUid?> uids) where T : notnull
{
return EntityManager.GetNetEntityDictionary(uids);
}
/// <summary>
/// Returns the <see cref="NetEntity"/> versions of the supplied entities. Logs an error if the entities do not exist.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Dictionary<NetEntity, NetEntity> GetNetEntityDictionary(Dictionary<EntityUid, EntityUid> uids)
{
return EntityManager.GetNetEntityDictionary(uids);
}
/// <summary>
/// Returns the <see cref="NetEntity"/> versions of the supplied entities. Logs an error if the entities do not exist.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Dictionary<NetEntity, NetEntity?> GetNetEntityDictionary(Dictionary<EntityUid, EntityUid?> uids)
{
return EntityManager.GetNetEntityDictionary(uids);
}
/// <summary>
/// Returns the <see cref="EntityUid"/> versions of the supplied <see cref="NetEntity"/>. Returns <see cref="EntityUid.Invalid"/> if it doesn't exist.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Dictionary<EntityUid, T> GetEntityDictionary<T>(Dictionary<NetEntity, T> uids)
{
return EntityManager.GetEntityDictionary(uids);
}
/// <summary>
/// Returns the <see cref="EntityUid"/> versions of the supplied <see cref="NetEntity"/>. Returns <see cref="EntityUid.Invalid"/> if it doesn't exist.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Dictionary<T, EntityUid> GetEntityDictionary<T>(Dictionary<T, NetEntity> uids) where T : notnull
{
return EntityManager.GetEntityDictionary(uids);
}
/// <summary>
/// Returns the <see cref="EntityUid"/> versions of the supplied <see cref="NetEntity"/>. Returns <see cref="EntityUid.Invalid"/> if it doesn't exist.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Dictionary<T, EntityUid?> GetEntityDictionary<T>(Dictionary<T, NetEntity?> uids) where T : notnull
{
return EntityManager.GetEntityDictionary(uids);
}
/// <summary>
/// Returns the <see cref="EntityUid"/> versions of the supplied <see cref="NetEntity"/>. Returns <see cref="EntityUid.Invalid"/> if it doesn't exist.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Dictionary<EntityUid, EntityUid> GetEntityDictionary(Dictionary<NetEntity, NetEntity> uids)
{
return EntityManager.GetEntityDictionary(uids);
}
/// <summary>
/// Returns the <see cref="EntityUid"/> versions of the supplied <see cref="NetEntity"/>. Returns <see cref="EntityUid.Invalid"/> if it doesn't exist.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Dictionary<EntityUid, EntityUid?> GetEntityDictionary(Dictionary<NetEntity, NetEntity?> uids)
{
return EntityManager.GetEntityDictionary(uids);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected NetCoordinates GetNetCoordinates(EntityCoordinates coordinates, MetaDataComponent? metadata = null)
{

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Map;
@@ -106,16 +107,6 @@ public partial interface IEntityManager
/// </summary>
EntityUid?[] GetEntityArray(NetEntity?[] netEntities);
/// <summary>
/// Dictionary version of <see cref="GetEntity"/>
/// </summary>
Dictionary<EntityUid, T> GetEntityDictionary<T>(Dictionary<NetEntity, T> netEntities);
/// <summary>
/// Dictionary version of <see cref="GetEntity"/>
/// </summary>
Dictionary<T, EntityUid> GetEntityDictionary<T>(Dictionary<T, NetEntity> netEntities) where T : notnull;
/// <summary>
/// HashSet version of <see cref="GetNetEntity"/>
/// </summary>
@@ -126,11 +117,6 @@ public partial interface IEntityManager
/// </summary>
public List<NetEntity> GetNetEntityList(List<EntityUid> entities);
/// <summary>
/// List version of <see cref="GetNetEntity"/>
/// </summary>
List<NetEntity> GetNetEntityList(IReadOnlyList<EntityUid> entities);
/// <summary>
/// List version of <see cref="GetNetEntity"/>
/// </summary>
@@ -142,40 +128,15 @@ public partial interface IEntityManager
public List<NetEntity?> GetNetEntityList(List<EntityUid?> entities);
/// <summary>
/// Array version of <see cref="GetNetEntity"/>
/// List version of <see cref="GetNetEntity"/>
/// </summary>
NetEntity[] GetNetEntityArray(EntityUid[] entities);
/// <summary>
/// Array version of <see cref="GetNetEntity"/>
/// List version of <see cref="GetNetEntity"/>
/// </summary>
NetEntity?[] GetNetEntityArray(EntityUid?[] entities);
/// <summary>
/// Dictionary version of <see cref="GetNetEntity"/>
/// </summary>
Dictionary<NetEntity, T> GetNetEntityDictionary<T>(Dictionary<EntityUid, T> entities);
/// <summary>
/// Dictionary version of <see cref="GetNetEntity"/>
/// </summary>
Dictionary<T, NetEntity> GetNetEntityDictionary<T>(Dictionary<T, EntityUid> entities) where T : notnull;
/// <summary>
/// Dictionary version of <see cref="GetNetEntity"/>
/// </summary>
Dictionary<T, NetEntity?> GetNetEntityDictionary<T>(Dictionary<T, EntityUid?> entities) where T : notnull;
/// <summary>
/// Dictionary version of <see cref="GetNetEntity"/>
/// </summary>
Dictionary<NetEntity, NetEntity> GetNetEntityDictionary(Dictionary<EntityUid, EntityUid> entities);
/// <summary>
/// Dictionary version of <see cref="GetNetEntity"/>
/// </summary>
Dictionary<NetEntity, NetEntity?> GetNetEntityDictionary(Dictionary<EntityUid, EntityUid?> entities);
/// <summary>
/// Returns the corresponding <see cref="NetCoordinates"/> for the specified local coordinates.
/// </summary>
@@ -217,27 +178,6 @@ public partial interface IEntityManager
public List<EntityUid> EnsureEntityList<T>(List<NetEntity> netEntities, EntityUid callerEntity);
void EnsureEntityList<T>(List<NetEntity> netEntities, EntityUid callerEntity, List<EntityUid> entities);
void EnsureEntityDictionary<TComp, TValue>(Dictionary<NetEntity, TValue> netEntities, EntityUid callerEntity,
Dictionary<EntityUid, TValue> entities);
void EnsureEntityDictionaryNullableValue<TComp, TValue>(Dictionary<NetEntity, TValue?> netEntities,
EntityUid callerEntity,
Dictionary<EntityUid, TValue?> entities);
void EnsureEntityDictionary<TComp, TKey>(Dictionary<TKey, NetEntity> netEntities, EntityUid callerEntity,
Dictionary<TKey, EntityUid> entities) where TKey : notnull;
void EnsureEntityDictionary<TComp, TKey>(Dictionary<TKey, NetEntity?> netEntities, EntityUid callerEntity,
Dictionary<TKey, EntityUid?> entities) where TKey : notnull;
void EnsureEntityDictionary<TComp>(Dictionary<NetEntity, NetEntity> netEntities, EntityUid callerEntity,
Dictionary<EntityUid, EntityUid> entities);
void EnsureEntityDictionary<TComp>(Dictionary<NetEntity, NetEntity?> netEntities, EntityUid callerEntity,
Dictionary<EntityUid, EntityUid?> entities);
public List<EntityCoordinates> GetEntityList(ICollection<NetCoordinates> netEntities);
public List<EntityCoordinates?> GetEntityList(List<NetCoordinates?> netEntities);

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using Robust.Shared.Collections;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
@@ -745,20 +744,9 @@ public sealed partial class EntityLookupSystem
}
public HashSet<EntityUid> GetEntitiesInRange(EntityCoordinates coordinates, float range, LookupFlags flags = DefaultFlags)
{
var ents = new HashSet<EntityUid>();
GetEntitiesInRange(coordinates, range, ents, flags);
return ents;
}
public void GetEntitiesInRange(EntityCoordinates coordinates, float range, HashSet<EntityUid> entities, LookupFlags flags = DefaultFlags)
{
var mapPos = coordinates.ToMap(EntityManager, _transform);
if (mapPos.MapId == MapId.Nullspace)
return;
GetEntitiesInRange(mapPos.MapId, mapPos.Position, range, entities, flags);
return GetEntitiesInRange(mapPos, range, flags);
}
#endregion

View File

@@ -127,31 +127,7 @@ public abstract partial class SharedMapSystem
SubscribeLocalEvent<MapGridComponent, MoveEvent>(OnGridMove);
}
/// <summary>
/// <see cref="GetGridPosition(Robust.Shared.GameObjects.Entity{Robust.Shared.Physics.Components.PhysicsComponent?},System.Numerics.Vector2,Robust.Shared.Maths.Angle)"/>
/// </summary>
public Vector2 GetGridPosition(Entity<PhysicsComponent?> grid, Vector2 worldPos, Angle worldRot)
{
if (!Resolve(grid.Owner, ref grid.Comp))
return Vector2.Zero;
return worldPos + worldRot.RotateVec(grid.Comp.LocalCenter);
}
/// <summary>
/// Gets the mapgrid's position considering its local physics center.
/// </summary>
public Vector2 GetGridPosition(Entity<PhysicsComponent?, TransformComponent?> grid)
{
if (!Resolve(grid.Owner, ref grid.Comp1, ref grid.Comp2))
return Vector2.Zero;
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(grid.Comp2);
return GetGridPosition((grid.Owner, grid.Comp1), worldPos, worldRot);
}
private void OnGridBoundsChange(EntityUid uid, MapGridComponent component)
public void OnGridBoundsChange(EntityUid uid, MapGridComponent component)
{
// Just MapLoader things.
if (component.MapProxy == DynamicTree.Proxy.Free) return;
@@ -217,7 +193,7 @@ public abstract partial class SharedMapSystem
if (xform.ParentUid != xform.MapUid && meta.EntityLifeStage < EntityLifeStage.Terminating && _netManager.IsServer)
{
Log.Error($"Grid {ToPrettyString(uid, meta)} is not parented to {ToPrettyString(xform._parent)} which is not a map. y'all need jesus. {Environment.StackTrace}");
Log.Error($"Grid {ToPrettyString(uid, meta)} it not parented to a map. y'all need jesus. {Environment.StackTrace}");
return;
}
@@ -998,34 +974,19 @@ public abstract partial class SharedMapSystem
internal ChunkEnumerator GetMapChunks(EntityUid uid, MapGridComponent grid, Box2 worldAABB)
{
var localAABB = _transform.GetInvWorldMatrix(uid).TransformBox(worldAABB);
return GetLocalMapChunks(uid, grid, localAABB);
return new ChunkEnumerator(grid.Chunks, localAABB, grid.ChunkSize);
}
internal ChunkEnumerator GetMapChunks(EntityUid uid, MapGridComponent grid, Box2Rotated worldArea)
{
var matrix = _transform.GetInvWorldMatrix(uid);
var localArea = matrix.TransformBox(worldArea);
return GetLocalMapChunks(uid, grid, localArea);
return new ChunkEnumerator(grid.Chunks, localArea, grid.ChunkSize);
}
internal ChunkEnumerator GetLocalMapChunks(EntityUid uid, MapGridComponent grid, Box2 localAABB)
{
Box2 compAABB;
// The entire area intersects.
if (_mapQuery.HasComponent(uid))
{
compAABB = localAABB;
}
else
{
compAABB = grid.LocalAABB.Intersect(localAABB);
}
if (compAABB.IsEmpty())
return ChunkEnumerator.Empty;
return new ChunkEnumerator(grid.Chunks, compAABB, grid.ChunkSize);
return new ChunkEnumerator(grid.Chunks, localAABB, grid.ChunkSize);
}
#endregion ChunkAccess
@@ -1387,36 +1348,8 @@ public abstract partial class SharedMapSystem
public EntityCoordinates GridTileToLocal(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
{
var position = TileCenterToVector(uid, grid, gridTile);
return new(uid, position);
}
/// <summary>
/// Turns a gridtile origin into a Vector2, accounting for tile size.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector2 TileToVector(Entity<MapGridComponent> grid, Vector2i gridTile)
{
return new Vector2(gridTile.X * grid.Comp.TileSize, gridTile.Y * grid.Comp.TileSize);
}
/// <summary>
/// Turns a gridtile center into a Vector2, accounting for tile size.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector2 TileCenterToVector(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
{
return TileCenterToVector((uid, grid), gridTile);
}
/// <summary>
/// Turns a gridtile center into a Vector2, accounting for tile size.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector2 TileCenterToVector(Entity<MapGridComponent> grid, Vector2i gridTile)
{
return new Vector2(gridTile.X * grid.Comp.TileSize, gridTile.Y * grid.Comp.TileSize) + grid.Comp.TileSizeHalfVector;
return new(uid,
new Vector2(gridTile.X * grid.TileSize + (grid.TileSize / 2f), gridTile.Y * grid.TileSize + (grid.TileSize / 2f)));
}
public Vector2 GridTileToWorldPos(EntityUid uid, MapGridComponent grid, Vector2i gridTile)

View File

@@ -616,7 +616,7 @@ public abstract partial class SharedTransformSystem
TransformComponent? newParent = null,
TransformComponent? oldParent = null)
{
SetCoordinates((uid, xform, _metaQuery.GetComponent(uid)), value, rotation, unanchor, newParent, oldParent);
SetCoordinates((uid, xform, MetaData(uid)), value, rotation, unanchor, newParent, oldParent);
}
#endregion

View File

@@ -1,6 +1,5 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using Robust.Shared.Map;
namespace Robust.Shared.GameObjects;
@@ -68,16 +67,6 @@ public abstract partial class SharedTransformSystem
return new MapCoordinates(worldPos, xform.MapID);
}
/// <summary>
/// Converts entity-local coordinates into map terms.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MapCoordinates ToMapCoordinates(NetCoordinates coordinates)
{
var eCoords = GetCoordinates(coordinates);
return ToMapCoordinates(eCoords);
}
/// <summary>
/// Creates EntityCoordinates given an entity and some MapCoordinates.
/// </summary>

View File

@@ -1,19 +1,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
namespace Robust.Shared.Map.Enumerators;
internal struct ChunkEnumerator
{
/// <summary>
/// An empty enumerator that will return nothing.
/// </summary>
public static ChunkEnumerator Empty => new(new Dictionary<Vector2i, MapChunk>(), Box2.Empty, 1);
private Dictionary<Vector2i, MapChunk> _chunks;
private Vector2i _chunkLB;
private Vector2i _chunkRT;

View File

@@ -1,59 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Robust.Shared.Maths;
namespace Robust.Shared.Map.Enumerators;
/// <summary>
/// Generic iterator for chunk indices for the specified bounds with the specified chunk size.
/// </summary>
public struct ChunkIndicesEnumerator
{
private readonly Vector2i _chunkLB;
private readonly Vector2i _chunkRT;
private int _xIndex;
private int _yIndex;
public ChunkIndicesEnumerator(Vector2 viewPos, float range, float chunkSize)
{
var rangeVec = new Vector2(range, range);
_chunkLB = ((viewPos - rangeVec) / chunkSize).Floored();
// Also floor this as we get the whole chunk anyway.
_chunkRT = ((viewPos + rangeVec) / chunkSize).Floored();
_xIndex = _chunkLB.X;
_yIndex = _chunkLB.Y;
}
public ChunkIndicesEnumerator(Box2 localAABB, int chunkSize)
{
_chunkLB = (localAABB.BottomLeft / chunkSize).Floored();
_chunkRT = (localAABB.TopRight / chunkSize).Floored();
_xIndex = _chunkLB.X;
_yIndex = _chunkLB.Y;
}
public bool MoveNext([NotNullWhen(true)] out Vector2i? indices)
{
if (_yIndex > _chunkRT.Y)
{
_yIndex = _chunkLB.Y;
_xIndex++;
}
if (_xIndex > _chunkRT.X)
{
indices = null;
return false;
}
indices = new Vector2i(_xIndex, _yIndex);
_yIndex++;
return true;
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace Robust.Shared.Network
{
@@ -30,10 +31,9 @@ namespace Robust.Shared.Network
/// </summary>
public sealed class NetConnectingArgs : EventArgs
{
public bool IsDenied => DenyReasonData != null;
public bool IsDenied => DenyReason != null;
public string? DenyReason => DenyReasonData?.Text;
public NetDenyReason? DenyReasonData { get; private set; }
public string? DenyReason { get; private set; }
public NetUserData UserData { get; }
@@ -48,12 +48,7 @@ namespace Robust.Shared.Network
public void Deny(string reason)
{
Deny(new NetDenyReason(reason));
}
public void Deny(NetDenyReason reason)
{
DenyReasonData = reason;
DenyReason = reason;
}
/// <summary>
@@ -70,29 +65,12 @@ namespace Robust.Shared.Network
}
}
/// <summary>
/// Contains a reason for denying a client connection to the game server.
/// </summary>
/// <param name="Text">The textual reason, presented to the user.</param>
/// <param name="AdditionalProperties">
/// Additional JSON properties that will be included in the <see cref="NetDisconnectMessage"/>.
/// Valid value types are: string, int, float, bool.
/// </param>
/// <seealso cref="NetDisconnectMessage"/>
/// <seealso cref="NetConnectingArgs"/>
public record NetDenyReason(string Text, Dictionary<string, object> AdditionalProperties)
{
public NetDenyReason(string Text) : this(Text, new Dictionary<string, object>())
{
}
}
/// <summary>
/// Structured reason common interface.
/// </summary>
public interface INetStructuredReason
{
NetDisconnectMessage Message { get; }
JsonObject StructuredReason { get; }
string Reason { get; }
bool RedialFlag { get; }
}
@@ -102,33 +80,33 @@ namespace Robust.Shared.Network
/// </summary>
public sealed class NetConnectFailArgs : EventArgs, INetStructuredReason
{
public NetConnectFailArgs(string reason) : this(NetDisconnectMessage.Decode(reason))
public NetConnectFailArgs(string reason) : this(NetStructuredDisconnectMessages.Decode(reason))
{
}
internal NetConnectFailArgs(NetDisconnectMessage reason)
public NetConnectFailArgs(JsonObject reason)
{
Message = reason;
StructuredReason = reason;
}
public NetDisconnectMessage Message { get; }
public string Reason => Message.Reason;
public bool RedialFlag => Message.RedialFlag;
public JsonObject StructuredReason { get; }
public string Reason => NetStructuredDisconnectMessages.ReasonOf(StructuredReason);
public bool RedialFlag => NetStructuredDisconnectMessages.RedialFlagOf(StructuredReason);
}
public sealed class NetDisconnectedArgs : NetChannelArgs, INetStructuredReason
{
public NetDisconnectedArgs(INetChannel channel, string reason) : this(channel, NetDisconnectMessage.Decode(reason))
public NetDisconnectedArgs(INetChannel channel, string reason) : this(channel, NetStructuredDisconnectMessages.Decode(reason))
{
}
internal NetDisconnectedArgs(INetChannel channel, NetDisconnectMessage reason) : base(channel)
public NetDisconnectedArgs(INetChannel channel, JsonObject reason) : base(channel)
{
Message = reason;
StructuredReason = reason;
}
public NetDisconnectMessage Message { get; }
public string Reason => Message.Reason;
public bool RedialFlag => Message.RedialFlag;
public JsonObject StructuredReason { get; }
public string Reason => NetStructuredDisconnectMessages.ReasonOf(StructuredReason);
public bool RedialFlag => NetStructuredDisconnectMessages.RedialFlagOf(StructuredReason);
}
}

View File

@@ -1,259 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Robust.Shared.Utility;
namespace Robust.Shared.Network;
// Why did this dinky class grow to this LOC...
/// <summary>
/// Stores structured information about why a connection was denied or disconnected.
/// </summary>
/// <remarks>
/// <para>
/// The core networking layer (Lidgren) allows passing plain strings for disconnect reasons.
/// We can beam a structured format (like JSON) over this,
/// but Lidgren also generates messages internally (such as on timeout).
/// This class is responsible for bridging the two to produce consistent results.
/// </para>
/// <para>
/// Disconnect messages are just a simple key/value format.
/// Valid value types are <see cref="int"/>, <see cref="float"/>, <see cref="bool"/>, and <see cref="string"/>.
/// </para>
/// </remarks>
public sealed class NetDisconnectMessage
{
private const string LidgrenDisconnectedPrefix = "Disconnected: ";
/// <summary>
/// The reason given if none was included in the structured message.
/// </summary>
internal const string DefaultReason = "unknown reason";
/// <summary>
/// The default redial flag given if none was included in the structured message.
/// </summary>
internal const bool DefaultRedialFlag = false;
/// <summary>
/// The key of the <see cref="Reason"/> value.
/// </summary>
public const string ReasonKey = "reason";
/// <summary>
/// The key of the <see cref="RedialFlag"/> value.
/// </summary>
public const string RedialKey = "redial";
internal readonly Dictionary<string, object> Values;
internal NetDisconnectMessage(Dictionary<string, object> values)
{
Values = values;
}
internal NetDisconnectMessage(
string reason = DefaultReason,
bool redialFlag = DefaultRedialFlag)
{
Values = new Dictionary<string, object>
{
{ ReasonKey, reason },
{ RedialKey, redialFlag }
};
}
/// <summary>
/// The human-readable reason for why the disconnection happened.
/// </summary>
/// <seealso cref="ReasonKey"/>
public string Reason => StringOf(ReasonKey, DefaultReason);
/// <summary>
/// Whether the client should "redial" to reconnect to the server.
/// </summary>
/// <remarks>
/// Redial means the client gets restarted by the launcher, to enable an update to occur.
/// This is generally set if the disconnection reason is some sort of version mismatch.
/// </remarks>
/// <seealso cref="RedialKey"/>
public bool RedialFlag => BoolOf(RedialKey, DefaultRedialFlag);
/// <summary>
/// Decode from a disconnect message.
/// </summary>
/// <remarks>
/// <para>
/// If structured JSON can be extracted, it is used.
/// Otherwise, or if the format is invalid, the entire input is returned as disconnect reason.
/// </para>
/// <para>Invalid JSON values (e.g. arrays) are discarded.</para>
/// </remarks>
/// <param name="text">The disconnect reason from Lidgren's disconnect message.</param>
internal static NetDisconnectMessage Decode(string text)
{
var start = text.AsMemory().TrimStart();
// Lidgren generates this prefix internally.
if (start.Span.StartsWith(LidgrenDisconnectedPrefix))
start = start[LidgrenDisconnectedPrefix.Length..];
// If it starts with { it's probably a JSON object.
if (start.Span.StartsWith("{"))
{
try
{
using var node = JsonDocument.Parse(start);
DebugTools.Assert(node.RootElement.ValueKind == JsonValueKind.Object);
return JsonToReason(node.RootElement);
}
catch (Exception)
{
// Discard the exception
}
}
// Something went wrong. That probably means it's not a structured reason.
// Or worst case scenario, some poor end-user has to look at half-broken JSON.
return new NetDisconnectMessage(new Dictionary<string, object>
{
{ ReasonKey, text }
});
}
/// <summary>
/// Encode to a textual string, that can be embedded into a disconnect message.
/// </summary>
internal string Encode()
{
return JsonSerializer.Serialize(Values);
}
private static NetDisconnectMessage JsonToReason(JsonElement obj)
{
DebugTools.Assert(obj.ValueKind == JsonValueKind.Object);
var dict = new Dictionary<string, object>();
foreach (var property in obj.EnumerateObject())
{
object value;
switch (property.Value.ValueKind)
{
case JsonValueKind.String:
value = property.Value.GetString()!;
break;
case JsonValueKind.Number:
if (property.Value.TryGetInt32(out var valueInt))
value = valueInt;
else
value = property.Value.GetSingle();
break;
case JsonValueKind.True:
case JsonValueKind.False:
value = property.Value.GetBoolean();
break;
default:
// Discard invalid values intentionally.
continue;
}
dict[property.Name] = value;
}
return new NetDisconnectMessage(dict);
}
/// <summary>
/// Get a value by its key.
/// </summary>
/// <param name="key">The key of the value to look up.</param>
/// <returns>
/// Null if no such value exists, otherwise an object of one of the valid types (int, float, string, bool).
/// </returns>
public object? ValueOf(string key)
{
return Values.GetValueOrDefault(key);
}
/// <summary>
/// Get a <see cref="string"/> value by its key.
/// </summary>
/// <param name="key">The key of the value to look up.</param>
/// <param name="defaultValue">Default value to return if the value does not exist or is the wrong type.</param>
/// <returns>
/// The <see cref="string"/> value with the given key,
/// or <paramref name="defaultValue"/> if no such value exists or it's a different type.
/// </returns>
[return: NotNullIfNotNull(nameof(defaultValue))]
public string? StringOf(string key, string? defaultValue = null)
{
if (ValueOf(key) is not string valueString)
return defaultValue;
return valueString;
}
/// <summary>
/// Get a <see cref="bool"/> value by its key.
/// </summary>
/// <param name="key">The key of the value to look up.</param>
/// <returns>
/// The <see cref="bool"/> value with the given key, or <see langword="null" /> if no such value exists or it's a different type.
/// </returns>
public bool? BoolOf(string key) => ValueOf(key) as bool?;
/// <summary>
/// Get a <see cref="bool"/> value by its key.
/// </summary>
/// <param name="key">The key of the value to look up.</param>
/// <param name="defaultValue">Default value to return if the value does not exist or is the wrong type.</param>
/// <returns>
/// The <see cref="bool"/> value with the given key,
/// or <paramref name="defaultValue"/> if no such value exists or it's a different type.
/// </returns>
public bool BoolOf(string key, bool defaultValue) => BoolOf(key) ?? defaultValue;
/// <summary>
/// Get a <see cref="int"/> value by its key.
/// </summary>
/// <param name="key">The key of the value to look up.</param>
/// <returns>
/// The <see cref="int"/> value with the given key, or <see langword="null" /> if no such value exists or it's a different type.
/// </returns>
public int? Int32Of(string key) => ValueOf(key) as int?;
/// <summary>
/// Get an <see cref="Int32"/> value by its key.
/// </summary>
/// <param name="key">The key of the value to look up.</param>
/// <param name="defaultValue">Default value to return if the value does not exist or is the wrong type.</param>
/// <returns>
/// The <see cref="Int32"/> value with the given key,
/// or <paramref name="defaultValue"/> if no such value exists or it's a different type.
/// </returns>
public int Int32Of(string key, int defaultValue) => Int32Of(key) ?? defaultValue;
/// <summary>
/// Get a <see cref="float"/> value by its key.
/// </summary>
/// <param name="key">The key of the value to look up.</param>
/// <returns>
/// The <see cref="float"/> value with the given key, or <see langword="null" /> if no such value exists or it's a different type.
/// </returns>
public float? SingleOf(string key)
{
var value = ValueOf(key);
return value as float? ?? value as int?;
}
/// <summary>
/// Get a <see cref="Single"/> value by its key.
/// </summary>
/// <param name="key">The key of the value to look up.</param>
/// <param name="defaultValue">Default value to return if the value does not exist or is the wrong type.</param>
/// <returns>
/// The <see cref="Single"/> value with the given key,
/// or <paramref name="defaultValue"/> if no such value exists or it's a different type.
/// </returns>
public float SingleOf(string key, float defaultValue) => SingleOf(key) ?? defaultValue;
}

View File

@@ -16,7 +16,7 @@ namespace Robust.Shared.Network
{
partial class NetManager
{
private static readonly string DisconnectReasonWrongKey = new NetDisconnectMessage("Token decryption failed.\nPlease reconnect to this server from the launcher.", true).Encode();
private readonly static string DisconnectReasonWrongKey = NetStructuredDisconnectMessages.Encode("Token decryption failed.\nPlease reconnect to this server from the launcher.", true);
private readonly byte[] _cryptoPrivateKey = new byte[CryptoBox.SecretKeyBytes];
@@ -211,15 +211,9 @@ namespace Robust.Shared.Network
var endPoint = connection.RemoteEndPoint;
var connect = await OnConnecting(endPoint, userData, type);
if (connect.DenyReasonData is { } deny)
if (connect.IsDenied)
{
var denyMsg = $"Connect denied: {deny.Text}";
var structured = new NetDisconnectMessage(denyMsg);
foreach (var (k, v) in deny.AdditionalProperties)
{
structured.Values[k] = v;
}
connection.Disconnect(structured.Encode());
connection.Disconnect($"Connection denied: {connect.DenyReason}");
return;
}

View File

@@ -0,0 +1,119 @@
using System;
using System.Net;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace Robust.Shared.Network;
/// <summary>
/// Structured disconnection utilities.
/// These use JsonNode so that Content may add it's own data.
/// Note that to prevent encoding a NetStructuredDisco value within a NetStructuredDisco value,
/// these should be encoded at the "highest level".
/// Whatever generates the final "reason" value is responsible for performing NetStructuredDisco.Encode.
/// </summary>
public static class NetStructuredDisconnectMessages
{
public const string ReasonKey = "reason";
public const string RedialKey = "redial";
/// <summary>
/// Encodes a structured disconnect message into a JsonObject.
/// That can then be extended with additional properties.
/// </summary>
public static JsonObject EncodeObject(string text, bool redialFlag = false)
{
JsonObject obj = new();
obj[ReasonKey] = text;
obj[RedialKey] = redialFlag;
return obj;
}
/// <summary>
/// Encodes a structured disconnect message.
/// Note that using this kind of gets in the way of adding content properties.
/// </summary>
public static string Encode(string text, bool redialFlag = false) => Encode(EncodeObject(text, redialFlag));
/// <summary>
/// Encodes a structured disconnect message from a JsonObject.
/// </summary>
public static string Encode(JsonObject obj) => obj.ToJsonString();
/// <summary>
/// Decodes a structured disconnect message.
/// This is designed assuming the input isn't necessarily a structured disconnect message.
/// As such this will always produce output that can be passed to ReasonOf.
/// </summary>
public static JsonObject Decode(string text)
{
var start = text.AsSpan().TrimStart();
// Lidgren generates this prefix internally.
var lidgrenDisconnectedPrefix = "Disconnected: ";
if (start.StartsWith(lidgrenDisconnectedPrefix))
start = start.Slice(lidgrenDisconnectedPrefix.Length);
// If it starts with { it's probably a JSON object.
if (start.StartsWith("{"))
{
try
{
var node = JsonNode.Parse(new string(start));
if (node != null)
return (JsonObject)node;
}
catch (Exception)
{
// Discard the exception
}
}
// Something went wrong, so...
JsonObject fallback = new();
fallback[ReasonKey] = text;
return fallback;
}
/// <summary>
/// Get a property as a JsonValue.
/// </summary>
public static JsonValue? ValueOf(JsonObject obj, string key)
{
if (obj.TryGetPropertyValue(key, out var val))
if (val is JsonValue)
return ((JsonValue) val);
return null;
}
/// <summary>
/// Decode a string property.
/// </summary>
public static string StringOf(JsonObject obj, string key, string def)
{
var val = ValueOf(obj, key);
if (val != null && val.TryGetValue(out string? res))
return res;
return def;
}
/// <summary>
/// Grab the redial flag.
/// </summary>
public static bool BoolOf(JsonObject obj, string key, bool def)
{
var val = ValueOf(obj, key);
if (val != null && val.TryGetValue(out bool res))
return res;
return def;
}
/// <summary>
/// Decode a reason.
/// </summary>
public static string ReasonOf(JsonObject obj) => StringOf(obj, ReasonKey, "unknown reason");
/// <summary>
/// Grab the redial flag.
/// </summary>
public static bool RedialFlagOf(JsonObject obj) => BoolOf(obj, RedialKey, false);
}

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