mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4b6af09f1 | ||
|
|
1ef29ae781 | ||
|
|
5686950421 | ||
|
|
2b54aa8984 | ||
|
|
859f150404 | ||
|
|
558f4b5b16 | ||
|
|
108366152b | ||
|
|
c55327e1d1 | ||
|
|
370e0fa0d0 | ||
|
|
4f9f82c20c | ||
|
|
43670a8ddd | ||
|
|
250313e1ed | ||
|
|
18d511d4b6 | ||
|
|
da9e5fb370 | ||
|
|
e3bac382ce | ||
|
|
179c6790b6 | ||
|
|
a7db5634df | ||
|
|
2daa86ff59 | ||
|
|
d6803f5294 | ||
|
|
bdcc0f7b9d | ||
|
|
ce49aa47cf | ||
|
|
c7d48b2526 | ||
|
|
6bb7f5b4ef | ||
|
|
2974310450 | ||
|
|
2694dce076 | ||
|
|
8960d1d995 | ||
|
|
0a4683d33e | ||
|
|
379bcfabe0 | ||
|
|
1d91838166 | ||
|
|
a5d4b8096f | ||
|
|
a77eee5658 | ||
|
|
156187a0dd | ||
|
|
852f002f59 | ||
|
|
9dc49c1904 | ||
|
|
1995b13e5d | ||
|
|
f985d10ed9 | ||
|
|
ae6cebbfbb | ||
|
|
ef0bc1a2e4 | ||
|
|
72ba484f5b | ||
|
|
a70e511fcb | ||
|
|
e7f9e95525 | ||
|
|
bd908f9db6 | ||
|
|
f8cb1729a3 | ||
|
|
fd9d5c8aa8 | ||
|
|
4677296934 | ||
|
|
708f5dd376 | ||
|
|
4a06acda32 | ||
|
|
e7beb2032b | ||
|
|
c7bd75f800 | ||
|
|
b4165e8661 | ||
|
|
ad339b5bfd | ||
|
|
e1197af8ce | ||
|
|
102cadf3a6 | ||
|
|
e7723b61bc | ||
|
|
a9d17337a3 | ||
|
|
74622bac83 | ||
|
|
a3047b1687 | ||
|
|
3a55118143 | ||
|
|
3c5fbc648a | ||
|
|
f9c39bce0b | ||
|
|
0e8c803c0f | ||
|
|
6bb7b88c69 | ||
|
|
9e0fc7017c | ||
|
|
76317b7ab3 | ||
|
|
d5f4d4bf2f | ||
|
|
156d1a6b14 | ||
|
|
3fa456fd44 | ||
|
|
bf9bb46154 | ||
|
|
fb3da0b53c | ||
|
|
5c635c09b4 | ||
|
|
fad539212d | ||
|
|
7275302639 | ||
|
|
5057ff97a3 | ||
|
|
754d5a1fbb | ||
|
|
7cee5b67a7 | ||
|
|
21729e7e48 | ||
|
|
394d1e6cc2 | ||
|
|
f73d7f7285 | ||
|
|
e505cfffd8 | ||
|
|
d0fe3591ef | ||
|
|
1868f32457 |
@@ -45,17 +45,20 @@
|
||||
<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.2" />
|
||||
<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>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -24,12 +24,16 @@
|
||||
<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="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\bin\$(RobustInjectorsConfiguration)\netstandard2.0\Robust.Client.Injectors.dll"/>
|
||||
AssemblyFile="$(CompileRobustXamlTaskAssemblyFile)"/>
|
||||
<Target
|
||||
Name="CompileRobustXaml"
|
||||
Condition="Exists('@(IntermediateAssembly)')"
|
||||
|
||||
186
RELEASE-NOTES.md
186
RELEASE-NOTES.md
@@ -54,6 +54,192 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 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.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed multiple recent bugs with key binding storage.
|
||||
|
||||
### Other
|
||||
|
||||
* Change default of `ButtonGroup.IsNoneSetAllowed` to `true`. This makes it default again to the previous (unintentional) behavior.
|
||||
|
||||
|
||||
## 210.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* `NetUserId` implements `ISelfSerialize` so can be used in data fields.
|
||||
* `ButtonGroup.IsNoneSetAllowed` to allow a button group to have no buttons pressed by default.
|
||||
|
||||
|
||||
## 210.0.3
|
||||
|
||||
|
||||
## 210.0.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Revert changes to `TextureRect` too.
|
||||
|
||||
|
||||
## 210.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Revert changes to `TextureButton` that broke style property handling.
|
||||
|
||||
|
||||
## 210.0.0
|
||||
|
||||
### New features
|
||||
|
||||
* Controls can now hook before, after, and during rendering of their children.
|
||||
* IRenderHandle is now a public API, with the caveat that it's properties and methods are unstable.
|
||||
* ButtonGroup now exposes what buttons it contains, alongside which is currently pressed.
|
||||
* OptionButton has additional styleclasses, and has a hook for modifying it's internal buttons.
|
||||
* PanelContainer.GetStyleBox() is now protected rather than private.
|
||||
* TextureButton now uses a TextureRect instead of custom drawing code.
|
||||
* TextureRect has additional style properties exposed.
|
||||
* A new property, TextureSizeTarget, was added, which allows specifying a size in virtual pixels that the control should attempt to draw at.
|
||||
* Stretch mode is now a style property.
|
||||
* Scale is now a style property.
|
||||
* Avalonia.Metadata.XmlnsDefinitionAttribute is now permitted by the sandbox.
|
||||
* Add MaxDimension property to Box2 to return the higher of the Width or Height.
|
||||
* Add GetLocalPosition to convert ScreenCoordinates to coordinates relative to the control. Ignores window.
|
||||
* Add GlobalRect and GlobalPixelRect for controls to get their UIBox2i in screen terms.
|
||||
* Add dotted line drawing to DrawingHandleScreen.
|
||||
* You can use `Subs.CVar()` from an entity systems to subscribe to CVar changes. This is more convenient than `IConfigurationManager.OnValueChanged` as it automatically unsubscribes on system shutdown.
|
||||
* There is now a built-in type serializer for `DateTime`, so you can put `DateTime`s in your data fields.
|
||||
* `System.Text.Unicode.UnicodeRange` and `UnicodeRanges` are now available in the sandbox.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* UI drawing now properly accounts for a control's draw routine potentially mangling the current matrix.
|
||||
* UI roots now properly update when the global stylesheet is changed. They previously only did so if they had a dedicated stylesheet (which is the one case where they would be unaffected by a global sheet update.
|
||||
|
||||
|
||||
## 209.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix missed import from 209.0.0.
|
||||
|
||||
|
||||
## 209.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `replay.max_compressed_size` and `replay.max_uncompressed_size` CVars are now `long`.
|
||||
* Remove obsolete CoordinatesExtension for ToEntityCoordinates from GridUid / Vector2i.
|
||||
|
||||
### New features
|
||||
|
||||
* Add GetEntitiesOnMap / GetChildEntities to EntityLookupSystem to return components on the specified map and components with the specified parent respectively.
|
||||
* Add MaxDimension property to Box2 to return the higher of the Width or Height.
|
||||
* Add GetLocalPosition to convert ScreenCoordinates to coordinates relative to the control. Ignores window.
|
||||
* Add GlobalRect and GlobalPixelRect for controls to get their UIBox2i in screen terms.
|
||||
* Add dotted line drawing to DrawingHandleScreen.
|
||||
* `IConfigurationManager.LoadDefaultsFromTomlStream` properly does type conversions. This fixes scenarios like loading of `long` CVars.
|
||||
* Add helper methods for TileRef / Vector2i to SharedMapSystem for ToCenterCoordinates (tile center EntityCoordinates) and ToCoordinates (tile origin to EntityCoordinates).
|
||||
* Copy some of the coordinates extensions to SharedTransformSystem.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed integer overflows in replay max size calculation.
|
||||
* Explicitly capped `replay.replay_tick_batchSize` internally to avoid high values causing allocation failures.
|
||||
|
||||
### Other
|
||||
|
||||
* Important MIDI performance improvements.
|
||||
|
||||
|
||||
## 208.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
@@ -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 pannel.
|
||||
cmd-net_graph-desc = Toggles the net statistics panel.
|
||||
cmd-net_graph-help = Usage: net_graph
|
||||
|
||||
cmd-net_watchent-desc = Dumps all network updates for an EntityId to the console.
|
||||
|
||||
@@ -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 overriden on the input type.
|
||||
Returns all methods overridden on the input type.
|
||||
command-description-methods-overridesfrom =
|
||||
Returns all methods overriden from the given type on the input type.
|
||||
Returns all methods overridden 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 it's own, this means it'll print the comamnd's help message.
|
||||
On its own, this means it'll print the command's help message.
|
||||
command-description-comp-rm =
|
||||
Removes the given component from the entity.
|
||||
|
||||
8
Robust.Analyzers.Tests/Aliases.cs
Normal file
8
Robust.Analyzers.Tests/Aliases.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
// 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;
|
||||
340
Robust.Analyzers.Tests/ComponentPauseGeneratorTest.cs
Normal file
340
Robust.Analyzers.Tests/ComponentPauseGeneratorTest.cs
Normal file
@@ -0,0 +1,340 @@
|
||||
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];
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<SkipRobustAnalyzer>true</SkipRobustAnalyzer>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets"/>
|
||||
<Import Project="..\MSBuild\Robust.Engine.props"/>
|
||||
|
||||
@@ -23,5 +27,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Robust.Analyzers\Robust.Analyzers.csproj"/>
|
||||
<ProjectReference Include="..\Robust.Serialization.Generator\Robust.Serialization.Generator.csproj" Aliases="SerializationGenerator" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -5,6 +5,7 @@ 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
|
||||
|
||||
@@ -4,6 +4,7 @@ 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;
|
||||
@@ -16,7 +17,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,
|
||||
@@ -26,7 +27,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,
|
||||
@@ -36,7 +37,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,
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
@@ -18,7 +19,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,
|
||||
@@ -28,7 +29,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,
|
||||
@@ -38,7 +39,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,
|
||||
@@ -48,7 +49,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,
|
||||
|
||||
@@ -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.Analyzers.Diagnostics;
|
||||
using static Robust.Roslyn.Shared.Diagnostics;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
@@ -31,7 +32,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,
|
||||
@@ -41,7 +42,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,
|
||||
@@ -49,7 +50,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,
|
||||
|
||||
@@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
<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" />
|
||||
@@ -28,4 +16,10 @@
|
||||
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CodeFixes;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
<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>
|
||||
|
||||
@@ -39,7 +39,7 @@ public sealed class AudioOverlay : Overlay
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var localPlayer = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
var localPlayer = _playerManager.LocalEntity;
|
||||
|
||||
if (args.ViewportControl == null || localPlayer == null)
|
||||
return;
|
||||
|
||||
@@ -106,8 +106,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
SubscribeNetworkEvent<PlayAudioEntityMessage>(OnEntityAudio);
|
||||
SubscribeNetworkEvent<PlayAudioPositionalMessage>(OnEntityCoordinates);
|
||||
|
||||
CfgManager.OnValueChanged(CVars.AudioAttenuation, OnAudioAttenuation, true);
|
||||
CfgManager.OnValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
|
||||
Subs.CVar(CfgManager, CVars.AudioAttenuation, OnAudioAttenuation, true);
|
||||
Subs.CVar(CfgManager, CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
|
||||
}
|
||||
|
||||
private void OnAudioState(EntityUid uid, AudioComponent component, ref AfterAutoHandleStateEvent args)
|
||||
@@ -133,13 +133,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
_audio.SetMasterGain(value);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
CfgManager.UnsubValueChanged(CVars.AudioAttenuation, OnAudioAttenuation);
|
||||
CfgManager.UnsubValueChanged(CVars.AudioRaycastLength, OnRaycastLengthChanged);
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
private void OnAudioPaused(EntityUid uid, AudioComponent component, ref EntityPausedEvent args)
|
||||
{
|
||||
component.Pause();
|
||||
|
||||
@@ -114,7 +114,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
"/usr/share/sounds/sf2/TimGM6mb.sf2",
|
||||
};
|
||||
|
||||
private const string WindowsSoundfont = @"C:\WINDOWS\system32\drivers\gm.dls";
|
||||
private static readonly string WindowsSoundfont = $@"{Environment.GetEnvironmentVariable("SystemRoot")}\system32\drivers\gm.dls";
|
||||
|
||||
private const string OsxSoundfont =
|
||||
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
|
||||
@@ -192,7 +192,12 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
_settings["synth.midi-bank-select"].StringValue = "gm";
|
||||
//_settings["synth.verbose"].IntValue = 1; // Useful for debugging.
|
||||
|
||||
_parallel.AddAndInvokeParallelCountChanged(UpdateParallelCount);
|
||||
var midiParallel = _cfgMan.GetCVar(CVars.MidiParallelism);
|
||||
_settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int)(Math.Log2(midiParallel) * 2048), 1, 65535);
|
||||
_settings["synth.cpu-cores"].IntValue = Math.Clamp(midiParallel, 1, 256);
|
||||
|
||||
_midiSawmill.Debug($"Synth Cores: {_settings["synth.cpu-cores"].IntValue}");
|
||||
_midiSawmill.Debug($"Synth Polyphony: {_settings["synth.polyphony"].IntValue}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -201,7 +206,10 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
return;
|
||||
}
|
||||
|
||||
_midiThread = new Thread(ThreadUpdate);
|
||||
_midiThread = new Thread(ThreadUpdate)
|
||||
{
|
||||
Name = "RobustToolbox MIDI Thread"
|
||||
};
|
||||
_midiThread.Start();
|
||||
|
||||
_updateJob = new MidiUpdateJob()
|
||||
@@ -219,18 +227,6 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
FluidsynthInitialized = true;
|
||||
}
|
||||
|
||||
private void UpdateParallelCount()
|
||||
{
|
||||
if (_settings == null)
|
||||
return;
|
||||
|
||||
_settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int)(Math.Log2(_parallel.ParallelProcessCount) * 2048), 1, 65535);
|
||||
_settings["synth.cpu-cores"].IntValue = Math.Clamp(_parallel.ParallelProcessCount, 1, 256);
|
||||
|
||||
_midiSawmill.Debug($"Synth Cores: {_settings["synth.cpu-cores"].IntValue}");
|
||||
_midiSawmill.Debug($"Synth Polyphony: {_settings["synth.polyphony"].IntValue}");
|
||||
}
|
||||
|
||||
private void LoggerDelegate(NFluidsynth.Logger.LogLevel level, string message, IntPtr data)
|
||||
{
|
||||
var rLevel = level switch
|
||||
|
||||
@@ -38,6 +38,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
private readonly Synth _synth;
|
||||
private readonly Sequencer _sequencer;
|
||||
private NFluidsynth.Player? _player;
|
||||
private int _playerTotalTicks;
|
||||
private MidiDriver? _driver;
|
||||
private byte _midiProgram = 1;
|
||||
private byte _midiBank = 1;
|
||||
@@ -144,7 +145,21 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
public bool DisableProgramChangeEvent { get; set; } = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int PlayerTotalTick => _player?.GetTotalTicks ?? 0;
|
||||
public int PlayerTotalTick
|
||||
{
|
||||
get
|
||||
{
|
||||
// GetTotalTicks is really expensive (has to iterate the entire file, not cached).
|
||||
// Slight problem with caching it ourselves: the value only becomes available when the player loads the MIDI file.
|
||||
// And that only happens after playback really starts, with the timer and synth and all that stuff.
|
||||
// So we cache it "as soon as it's available", i.e. not 0.
|
||||
// We don't care about playlists and such, so it shouldn't change anymore after.
|
||||
if (_playerTotalTicks != 0)
|
||||
return _playerTotalTicks;
|
||||
|
||||
return _playerTotalTicks = _player?.GetTotalTicks ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int PlayerTick
|
||||
@@ -339,6 +354,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
return false;
|
||||
}
|
||||
|
||||
_playerTotalTicks = 0;
|
||||
_player?.Dispose();
|
||||
_player = new NFluidsynth.Player(_synth);
|
||||
_player.SetPlaybackCallback(MidiPlayerEventHandler);
|
||||
@@ -377,6 +393,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
_player?.Join();
|
||||
_player?.Dispose();
|
||||
_player = null;
|
||||
_playerTotalTicks = 0;
|
||||
}
|
||||
|
||||
StopAllNotes();
|
||||
|
||||
@@ -229,7 +229,7 @@ namespace Robust.Client
|
||||
|
||||
// Don't invoke PlayerLeaveServer if PlayerJoinedServer & GameStartedSetup hasn't been called yet.
|
||||
if (RunLevel > ClientRunLevel.Connecting)
|
||||
PlayerLeaveServer?.Invoke(this, new PlayerEventArgs(_playMan.LocalPlayer?.Session));
|
||||
PlayerLeaveServer?.Invoke(this, new PlayerEventArgs(_playMan.LocalSession));
|
||||
|
||||
LastDisconnectReason = args.Reason;
|
||||
GameStoppedReset();
|
||||
|
||||
@@ -188,7 +188,7 @@ namespace Robust.Client.Console
|
||||
}
|
||||
|
||||
args.RemoveAt(0);
|
||||
var shell = new ConsoleShell(this, session ?? _player.LocalPlayer?.Session, session == null);
|
||||
var shell = new ConsoleShell(this, session ?? _player.LocalSession, session == null);
|
||||
var cmdArgs = args.ToArray();
|
||||
|
||||
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
|
||||
@@ -200,8 +200,7 @@ namespace Robust.Client.Console
|
||||
// When not connected to a server, you can run all local commands.
|
||||
// When connected to a server, you can only run commands according to the con group controller.
|
||||
|
||||
return _player.LocalPlayer == null
|
||||
|| _player.LocalPlayer.Session.Status <= SessionStatus.Connecting
|
||||
return _player.LocalSession is not { Status: > SessionStatus.Connecting }
|
||||
|| _conGroup.CanCommand(cmdName);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var controlled = _playerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
|
||||
if (controlled == EntityUid.Invalid)
|
||||
if (_playerManager.LocalEntity is not { } controlled)
|
||||
{
|
||||
shell.WriteLine("You don't have an attached entity.");
|
||||
return;
|
||||
|
||||
@@ -420,7 +420,7 @@ namespace Robust.Client.Debugging
|
||||
if (mapPos.MapId != args.MapId)
|
||||
return;
|
||||
|
||||
var player = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
var player = _playerManager.LocalEntity;
|
||||
|
||||
if (!_entityManager.TryGetComponent<TransformComponent>(player, out var playerXform) ||
|
||||
playerXform.MapID != args.MapId)
|
||||
|
||||
@@ -42,6 +42,8 @@ public sealed partial class ClientEntityManager
|
||||
var pending = PendingNetEntityStates.GetOrNew(nEntity);
|
||||
pending.Add((typeof(T), callerEntity));
|
||||
|
||||
|
||||
|
||||
return entity.Item1;
|
||||
}
|
||||
|
||||
|
||||
@@ -221,7 +221,7 @@ namespace Robust.Client.GameObjects
|
||||
public void DispatchReceivedNetworkMsg(EntityEventArgs msg)
|
||||
{
|
||||
var sessionType = typeof(EntitySessionMessage<>).MakeGenericType(msg.GetType());
|
||||
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(_playerManager.LocalPlayer!.Session), msg)!;
|
||||
var sessionMsg = Activator.CreateInstance(sessionType, new EntitySessionEventArgs(_playerManager.LocalSession!), msg)!;
|
||||
ReceivedSystemMessage?.Invoke(this, msg);
|
||||
ReceivedSystemMessage?.Invoke(this, sessionMsg);
|
||||
}
|
||||
|
||||
@@ -105,12 +105,10 @@ namespace Robust.Client.GameObjects
|
||||
/// <param name="inputCmd">Input command to handle as predicted.</param>
|
||||
public void PredictInputCommand(IFullInputCmdMessage inputCmd)
|
||||
{
|
||||
DebugTools.AssertNotNull(_playerManager.LocalPlayer);
|
||||
|
||||
var keyFunc = _inputManager.NetworkBindMap.KeyFunctionName(inputCmd.InputFunctionId);
|
||||
|
||||
Predicted = true;
|
||||
var session = _playerManager.LocalPlayer!.Session;
|
||||
var session = _playerManager.LocalSession;
|
||||
foreach (var handler in BindRegistry.GetHandlers(keyFunc))
|
||||
{
|
||||
if (handler.HandleCmdMessage(EntityManager, session, inputCmd))
|
||||
@@ -145,27 +143,22 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void GenerateInputCommand(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
var localPlayer = _playerManager.LocalPlayer;
|
||||
if(localPlayer is null)
|
||||
return;
|
||||
|
||||
var pent = localPlayer.ControlledEntity;
|
||||
if(pent is null)
|
||||
if (_playerManager.LocalEntity is not { } pent)
|
||||
return;
|
||||
|
||||
BoundKeyFunction keyFunction = new BoundKeyFunction(args[0]);
|
||||
BoundKeyState state = args[1] == "u" ? BoundKeyState.Up: BoundKeyState.Down;
|
||||
|
||||
var pxform = Transform(pent.Value);
|
||||
var pxform = Transform(pent);
|
||||
var wPos = pxform.WorldPosition + new Vector2(float.Parse(args[2]), float.Parse(args[3]));
|
||||
var coords = EntityCoordinates.FromMap(EntityManager, pent.Value, new MapCoordinates(wPos, pxform.MapID));
|
||||
var coords = EntityCoordinates.FromMap(EntityManager, pent, new MapCoordinates(wPos, pxform.MapID));
|
||||
|
||||
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
|
||||
|
||||
var message = new FullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId, state,
|
||||
GetNetCoordinates(coords), new ScreenCoordinates(0, 0, default), NetEntity.Invalid);
|
||||
|
||||
HandleInputCommand(localPlayer.Session, keyFunction, message);
|
||||
HandleInputCommand(_playerManager.LocalSession, keyFunction, message);
|
||||
}
|
||||
|
||||
private void OnAttachedEntityChanged(LocalPlayerAttachedEvent message)
|
||||
@@ -208,11 +201,8 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
public void SetEntityContextActive()
|
||||
{
|
||||
var controlled = _playerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
|
||||
if (controlled == EntityUid.Invalid)
|
||||
{
|
||||
if (_playerManager.LocalEntity is not { } controlled)
|
||||
return;
|
||||
}
|
||||
|
||||
SetEntityContextActive(_inputManager, controlled);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Robust.Client.GameObjects
|
||||
SubscribeLocalEvent<SpriteComponent, SpriteUpdateInertEvent>(QueueUpdateInert);
|
||||
SubscribeLocalEvent<SpriteComponent, ComponentInit>(OnInit);
|
||||
|
||||
_cfg.OnValueChanged(CVars.RenderSpriteDirectionBias, OnBiasChanged, true);
|
||||
Subs.CVar(_cfg, CVars.RenderSpriteDirectionBias, OnBiasChanged, true);
|
||||
_sawmill = _logManager.GetSawmill("sprite");
|
||||
}
|
||||
|
||||
@@ -72,12 +72,6 @@ namespace Robust.Client.GameObjects
|
||||
QueueUpdateInert(uid, component);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_cfg.UnsubValueChanged(CVars.RenderSpriteDirectionBias, OnBiasChanged);
|
||||
}
|
||||
|
||||
private void OnBiasChanged(double value)
|
||||
{
|
||||
SpriteComponent.DirectionBias = value;
|
||||
|
||||
@@ -29,10 +29,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
var uiKey = ev.UiKey;
|
||||
var message = ev.Message;
|
||||
// This should probably not happen at this point, but better make extra sure!
|
||||
if (_playerManager.LocalPlayer != null)
|
||||
message.Session = _playerManager.LocalPlayer.Session;
|
||||
|
||||
message.Session = _playerManager.LocalSession!;
|
||||
message.Entity = GetNetEntity(uid);
|
||||
message.UiKey = uiKey;
|
||||
|
||||
@@ -75,8 +72,7 @@ namespace Robust.Client.GameObjects
|
||||
boundInterface.Open();
|
||||
uiComp.OpenInterfaces[uiKey] = boundInterface;
|
||||
|
||||
var playerSession = _playerManager.LocalPlayer?.Session;
|
||||
if (playerSession != null)
|
||||
if (_playerManager.LocalSession is { } playerSession)
|
||||
{
|
||||
uiComp.Interfaces[uiKey]._subscribedSessions.Add(playerSession);
|
||||
RaiseLocalEvent(uid, new BoundUIOpenedEvent(uiKey, uid, playerSession), true);
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
var player = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
var player = _playerManager.LocalEntity;
|
||||
|
||||
if (player == null || !EntityManager.TryGetComponent(player.Value, out PhysicsComponent? body))
|
||||
{
|
||||
|
||||
@@ -232,9 +232,9 @@ namespace Robust.Client.GameStates
|
||||
return default;
|
||||
}
|
||||
|
||||
DebugTools.AssertNotNull(_players.LocalPlayer);
|
||||
DebugTools.Assert(_players.LocalSession != null);
|
||||
|
||||
var evArgs = new EntitySessionEventArgs(_players.LocalPlayer!.Session);
|
||||
var evArgs = new EntitySessionEventArgs(_players.LocalSession);
|
||||
_pendingSystemMessages.Enqueue((_nextInputCmdSeq, _timing.CurTick, message,
|
||||
new EntitySessionMessage<T>(evArgs, message)));
|
||||
|
||||
|
||||
@@ -313,7 +313,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
entity = _playerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
|
||||
entity = _playerManager.LocalEntity ?? EntityUid.Invalid;
|
||||
}
|
||||
else if (!NetEntity.TryParse(args[0], out var netEntity) || !_entManager.TryGetEntity(netEntity, out entity))
|
||||
{
|
||||
|
||||
@@ -1201,7 +1201,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void LightResolutionScaleChanged(float newValue)
|
||||
{
|
||||
_lightResolutionScale = newValue;
|
||||
_lightResolutionScale = newValue > 0.05f ? newValue : 0.05f;
|
||||
RegenAllLightRts();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Graphics;
|
||||
using System.Runtime.Intrinsics;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
@@ -63,6 +64,19 @@ 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>
|
||||
@@ -100,12 +114,43 @@ namespace Robust.Client.Graphics
|
||||
DrawPrimitives(primitiveTopology, White, indices, drawVertices);
|
||||
}
|
||||
|
||||
private void PadVerticesV2(ReadOnlySpan<Vector2> input, Span<DrawVertexUV2DColor> output, Color color)
|
||||
private static void PadVerticesV2(ReadOnlySpan<Vector2> input, Span<DrawVertexUV2DColor> output, Color color)
|
||||
{
|
||||
Color colorLinear = Color.FromSrgb(color);
|
||||
for (var i = 0; i < output.Length; i++)
|
||||
if (input.Length == 0)
|
||||
return;
|
||||
|
||||
if (input.Length != output.Length)
|
||||
{
|
||||
output[i] = new DrawVertexUV2DColor(input[i], new Vector2(0.5f, 0.5f), colorLinear);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -14,6 +15,79 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simialr to DrawLine but has dashes interspersed.
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset from the start of the line.</param>
|
||||
/// <param name="dashSize">How long a dash is.</param>
|
||||
/// <param name="gapSize">How long the gap between dashes is.</param>
|
||||
public void DrawDottedLine(Vector2 from, Vector2 to, Color color, float offset = 0f, float dashSize = 8f, float gapSize = 2f)
|
||||
{
|
||||
var lineVector = to - from;
|
||||
|
||||
// No drawing for you.
|
||||
if (lineVector.LengthSquared() < 10f * float.Epsilon)
|
||||
return;
|
||||
|
||||
var lineAndGap = gapSize + dashSize;
|
||||
var lines = new ValueList<Vector2>();
|
||||
|
||||
// Minimum distance.
|
||||
if (lineVector.Length() < lineAndGap)
|
||||
{
|
||||
lines.Add(from);
|
||||
lines.Add(to);
|
||||
}
|
||||
else
|
||||
{
|
||||
var maxLength = lineVector.Length();
|
||||
var normalizedLine = lineVector.Normalized();
|
||||
var dashVector = normalizedLine * dashSize;
|
||||
var gapVector = normalizedLine * gapSize;
|
||||
|
||||
var position = from;
|
||||
offset %= (dashSize + gapSize);
|
||||
var length = offset;
|
||||
var dashLength = dashSize;
|
||||
|
||||
// If offset is less than gap size then start with a gap
|
||||
// otherwise start with a partial line
|
||||
if (offset > 0f)
|
||||
{
|
||||
if (offset < gapSize)
|
||||
{
|
||||
position += normalizedLine * offset;
|
||||
length += offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
dashLength = (offset - gapSize);
|
||||
}
|
||||
}
|
||||
|
||||
while (length < maxLength)
|
||||
{
|
||||
lines.Add(position);
|
||||
|
||||
position += normalizedLine * dashLength;
|
||||
var lengthFromStart = (position - from).Length();
|
||||
|
||||
// if over length then cap the thing.
|
||||
if (lengthFromStart > maxLength)
|
||||
{
|
||||
position = to;
|
||||
}
|
||||
|
||||
lines.Add(position);
|
||||
dashLength = dashVector.Length();
|
||||
position += gapVector;
|
||||
length = (position - from).Length();
|
||||
}
|
||||
}
|
||||
|
||||
DrawPrimitives(DrawPrimitiveTopology.LineList, lines.Span, color);
|
||||
}
|
||||
|
||||
public abstract void DrawRect(UIBox2 rect, Color color, bool filled = true);
|
||||
|
||||
public abstract void DrawTextureRectRegion(Texture texture, UIBox2 rect, UIBox2? subRegion = null, Color? modulate = null);
|
||||
|
||||
@@ -6,7 +6,10 @@ using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
internal interface IRenderHandle
|
||||
/// <remarks>
|
||||
/// Unstable API. Likely to break hard during renderer rewrite if you rely on it.
|
||||
/// </remarks>
|
||||
public interface IRenderHandle
|
||||
{
|
||||
DrawingHandleScreen DrawingHandleScreen { get; }
|
||||
DrawingHandleWorld DrawingHandleWorld { get; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
@@ -69,7 +69,7 @@ namespace Robust.Client.Graphics
|
||||
ShaderBlendMode? blend = null;
|
||||
if (_rawBlendMode != null)
|
||||
{
|
||||
if (!Enum.TryParse<ShaderBlendMode>(_rawBlendMode.ToUpper(), out var parsed))
|
||||
if (!Enum.TryParse<ShaderBlendMode>(_rawBlendMode, true, out var parsed))
|
||||
Logger.Error($"invalid mode: {_rawBlendMode}");
|
||||
else
|
||||
blend = parsed;
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Robust.Client.Input
|
||||
void KeyDown(KeyEventArgs e);
|
||||
void KeyUp(KeyEventArgs e);
|
||||
|
||||
IKeyBinding RegisterBinding(in KeyBindingRegistration reg, bool markModified=true);
|
||||
IKeyBinding RegisterBinding(in KeyBindingRegistration reg, bool markModified=true, bool invalid=false);
|
||||
|
||||
void RemoveBinding(IKeyBinding binding, bool markModified=true);
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace Robust.Client.Input
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadKeyFile(path, true);
|
||||
LoadKeyFile(path, false, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -136,7 +136,7 @@ namespace Robust.Client.Input
|
||||
|
||||
if (_resourceMan.ContentFileExists(path))
|
||||
{
|
||||
LoadKeyFile(path, false);
|
||||
LoadKeyFile(path, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,7 +496,13 @@ namespace Robust.Client.Input
|
||||
return true;
|
||||
}
|
||||
|
||||
private void LoadKeyFile(ResPath file, bool userData)
|
||||
/// <summary>
|
||||
/// Loads a keybind file, configuring keybinds.
|
||||
/// </summary>
|
||||
/// <param name="file">File to load from the content package</param>
|
||||
/// <param name="defaultRegistration">Whether or not this is a "default" keybind set. If it is, then it won't override the current configuration, only the defaults.</param>
|
||||
/// <param name="userData">Whether or not to load from the user data directory instead of the content package.</param>
|
||||
public void LoadKeyFile(ResPath file, bool defaultRegistration, bool userData = false)
|
||||
{
|
||||
TextReader reader;
|
||||
if (userData)
|
||||
@@ -517,16 +523,19 @@ namespace Robust.Client.Input
|
||||
{
|
||||
var baseKeyRegs = _serialization.Read<KeyBindingRegistration[]>(BaseKeyRegsNode, notNullableOverride: true);
|
||||
|
||||
|
||||
foreach (var reg in baseKeyRegs)
|
||||
{
|
||||
var invalid = false;
|
||||
|
||||
if (reg.Type != KeyBindingType.Command && !NetworkBindMap.FunctionExists(reg.Function.FunctionName))
|
||||
{
|
||||
Logger.ErrorS("input", "Key function in {0} does not exist: '{1}'", file,
|
||||
Logger.DebugS("input", "Key function in {0} does not exist: '{1}'.", file,
|
||||
reg.Function);
|
||||
continue;
|
||||
invalid = true;
|
||||
}
|
||||
|
||||
if (!userData)
|
||||
if (defaultRegistration)
|
||||
{
|
||||
_defaultRegistrations.Add(reg);
|
||||
|
||||
@@ -538,19 +547,24 @@ namespace Robust.Client.Input
|
||||
}
|
||||
}
|
||||
|
||||
RegisterBinding(reg, markModified: userData);
|
||||
RegisterBinding(reg, markModified: !defaultRegistration, invalid);
|
||||
}
|
||||
}
|
||||
|
||||
if (userData && mapping.TryGet("leaveEmpty", out var node))
|
||||
if (!defaultRegistration && mapping.TryGet("leaveEmpty", out var node))
|
||||
{
|
||||
var leaveEmpty = _serialization.Read<BoundKeyFunction[]>(node, notNullableOverride: true);
|
||||
|
||||
if (leaveEmpty.Length > 0)
|
||||
foreach (var bind in leaveEmpty)
|
||||
{
|
||||
// Adding to _modifiedKeyFunctions means that these keybinds won't be loaded from the base file.
|
||||
// Because they've been explicitly cleared.
|
||||
_modifiedKeyFunctions.UnionWith(leaveEmpty);
|
||||
_modifiedKeyFunctions.Add(bind);
|
||||
|
||||
// Adding to bindingsByFunction because if the keybind is not valid(For example if it's from another
|
||||
// server then we will have problems saving the file)
|
||||
_bindingsByFunction.GetOrNew(bind);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -578,7 +592,7 @@ namespace Robust.Client.Input
|
||||
return binding;
|
||||
}
|
||||
|
||||
public IKeyBinding RegisterBinding(in KeyBindingRegistration reg, bool markModified = true)
|
||||
public IKeyBinding RegisterBinding(in KeyBindingRegistration reg, bool markModified = true, bool invalid = false)
|
||||
{
|
||||
var binding = new KeyBinding(this, reg.Function.FunctionName, reg.Type, reg.BaseKey, reg.CanFocus, reg.CanRepeat,
|
||||
reg.AllowSubCombs, reg.Priority, reg.Mod1, reg.Mod2, reg.Mod3);
|
||||
@@ -609,7 +623,7 @@ namespace Robust.Client.Input
|
||||
|
||||
public void InputModeChanged() => OnInputModeChanged?.Invoke();
|
||||
|
||||
private void RegisterBinding(KeyBinding binding, bool markModified = true)
|
||||
private void RegisterBinding(KeyBinding binding, bool markModified = true, bool invalid = false)
|
||||
{
|
||||
// we sort larger combos first so they take priority over smaller (single key) combos,
|
||||
// so they get processed first in KeyDown and such.
|
||||
@@ -624,7 +638,8 @@ namespace Robust.Client.Input
|
||||
_modifiedKeyFunctions.Add(binding.Function);
|
||||
}
|
||||
|
||||
_bindings.Insert(pos, binding);
|
||||
if (!invalid)
|
||||
_bindings.Insert(pos, binding);
|
||||
_bindingsByFunction.GetOrNew(binding.Function).Add(binding);
|
||||
OnKeyBindingAdded?.Invoke(binding);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,9 @@ public sealed class TileEdgeOverlay : GridOverlay
|
||||
var tileDimensions = new Vector2(tileSize, tileSize);
|
||||
var (_, _, worldMatrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(Grid.Owner);
|
||||
args.WorldHandle.SetTransform(worldMatrix);
|
||||
var localAABB = invMatrix.TransformBox(args.WorldBounds);
|
||||
var bounds = args.WorldBounds;
|
||||
bounds = new Box2Rotated(bounds.Box.Enlarged(1), bounds.Rotation, bounds.Origin);
|
||||
var localAABB = invMatrix.TransformBox(bounds);
|
||||
|
||||
var enumerator = mapSystem.GetLocalTilesEnumerator(Grid.Owner, Grid, localAABB, false);
|
||||
|
||||
|
||||
@@ -495,7 +495,7 @@ namespace Robust.Client.Placement
|
||||
{
|
||||
// Try to get current map.
|
||||
var map = MapId.Nullspace;
|
||||
if (EntityManager.TryGetComponent(PlayerManager.LocalPlayer?.ControlledEntity, out TransformComponent? xform))
|
||||
if (EntityManager.TryGetComponent(PlayerManager.LocalEntity, out TransformComponent? xform))
|
||||
{
|
||||
map = xform.MapID;
|
||||
}
|
||||
@@ -512,7 +512,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
private bool CurrentEraserMouseCoordinates(out EntityCoordinates coordinates)
|
||||
{
|
||||
var ent = PlayerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
|
||||
var ent = PlayerManager.LocalEntity ?? EntityUid.Invalid;
|
||||
if (ent == EntityUid.Invalid)
|
||||
{
|
||||
coordinates = new EntityCoordinates();
|
||||
@@ -640,7 +640,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
if (CurrentPermission is not {Range: > 0} ||
|
||||
!CurrentMode.RangeRequired ||
|
||||
PlayerManager.LocalPlayer?.ControlledEntity is not {Valid: true} controlled)
|
||||
PlayerManager.LocalEntity is not {Valid: true} controlled)
|
||||
return;
|
||||
|
||||
var worldPos = EntityManager.GetComponent<TransformComponent>(controlled).WorldPosition;
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace Robust.Client.Placement
|
||||
{
|
||||
if (!RangeRequired)
|
||||
return true;
|
||||
var controlled = pManager.PlayerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
|
||||
var controlled = pManager.PlayerManager.LocalEntity ?? EntityUid.Invalid;
|
||||
if (controlled == EntityUid.Invalid)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -10,14 +10,13 @@ namespace Robust.Client.Player
|
||||
|
||||
public override Filter FromEntities(Filter filter, params EntityUid[] entities)
|
||||
{
|
||||
if (_playerManager.LocalPlayer is not { } localPlayer
|
||||
|| localPlayer.Session.AttachedEntity is not {Valid: true} attachedUid)
|
||||
if (_playerManager.LocalEntity is not {Valid: true} attachedUid)
|
||||
return filter;
|
||||
|
||||
foreach (var uid in entities)
|
||||
{
|
||||
if (uid == attachedUid)
|
||||
filter.AddPlayer(localPlayer.Session);
|
||||
filter.AddPlayer(_playerManager.LocalSession!);
|
||||
}
|
||||
|
||||
return filter;
|
||||
|
||||
@@ -36,12 +36,12 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager
|
||||
|
||||
private void OnRecordingStarted(MappingDataNode metadata, List<object> messages)
|
||||
{
|
||||
if (_player.LocalPlayer == null)
|
||||
if (_player.LocalSession == null)
|
||||
return;
|
||||
|
||||
// Add information about the user doing the recording. This is used to set the default replay observer position
|
||||
// when playing back the replay.
|
||||
var guid = _player.LocalPlayer.UserId.UserId.ToString();
|
||||
var guid = _player.LocalUser.ToString();
|
||||
metadata[ReplayConstants.MetaKeyRecordedBy] = new ValueDataNode(guid);
|
||||
}
|
||||
|
||||
|
||||
@@ -226,6 +226,10 @@ namespace Robust.Client.UserInterface
|
||||
/// <seealso cref="Rect"/>
|
||||
public UIBox2i PixelRect => UIBox2i.FromDimensions(PixelPosition, PixelSize);
|
||||
|
||||
public UIBox2 GlobalRect => UIBox2.FromDimensions(GlobalPosition, _size);
|
||||
|
||||
public UIBox2i GlobalPixelRect => UIBox2i.FromDimensions(GlobalPixelPosition, PixelSize);
|
||||
|
||||
/// <summary>
|
||||
/// Horizontal alignment mode.
|
||||
/// This determines how the control should be laid out horizontally
|
||||
@@ -464,6 +468,14 @@ namespace Robust.Client.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the screen coordinates position relative to the control.
|
||||
/// </summary>
|
||||
public Vector2 GetLocalPosition(ScreenCoordinates coordinates)
|
||||
{
|
||||
return coordinates.Position - GlobalPixelPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify the layout system that this control's <see cref="Measure"/> result may have changed
|
||||
/// and must be recalculated.
|
||||
|
||||
@@ -545,6 +545,36 @@ namespace Robust.Client.UserInterface
|
||||
Draw(renderHandle.DrawingHandleScreen);
|
||||
}
|
||||
|
||||
protected internal virtual void PreRenderChildren(ref ControlRenderArguments args)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected internal virtual void PostRenderChildren(ref ControlRenderArguments args)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected internal virtual void RenderChildOverride(ref ControlRenderArguments args, int childIndex, Vector2i position)
|
||||
{
|
||||
RenderControl(ref args, childIndex, position);
|
||||
}
|
||||
|
||||
public ref struct ControlRenderArguments
|
||||
{
|
||||
public IRenderHandle Handle;
|
||||
public ref int Total;
|
||||
public Vector2i Position;
|
||||
public Color Modulate;
|
||||
public UIBox2i? ScissorBox;
|
||||
public ref Matrix3 CoordinateTransform;
|
||||
}
|
||||
|
||||
protected void RenderControl(ref ControlRenderArguments args, int childIndex, Vector2i position)
|
||||
{
|
||||
UserInterfaceManagerInternal.RenderControl(args.Handle, ref args.Total, GetChild(childIndex), position, args.Modulate, args.ScissorBox, args.CoordinateTransform);
|
||||
}
|
||||
|
||||
public void UpdateDraw()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.ContentPack;
|
||||
@@ -37,8 +38,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
get => _group;
|
||||
set
|
||||
{
|
||||
if (value?.InternalButtons.Contains(this) ?? false)
|
||||
return; // No work to do.
|
||||
// Remove from old group.
|
||||
_group?.Buttons.Remove(this);
|
||||
_group?.InternalButtons.Remove(this);
|
||||
|
||||
_group = value;
|
||||
|
||||
@@ -47,11 +50,21 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return;
|
||||
}
|
||||
|
||||
value.Buttons.Add(this);
|
||||
value.InternalButtons.Add(this);
|
||||
ToggleMode = true;
|
||||
|
||||
// Set us to pressed if we're the first button.
|
||||
Pressed = value.Buttons.Count == 0;
|
||||
if (value.IsNoneSetAllowed)
|
||||
{
|
||||
// Still UNPRESS if there's another pressed button, but don't PRESS it otherwise.
|
||||
if (value.Pressed != this)
|
||||
_pressed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set us to pressed if we're the first button. Doesn't go through the setter to avoid setting off our own error check.
|
||||
_pressed = value.InternalButtons.Count == 1;
|
||||
}
|
||||
DrawModeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +108,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value && Group != null)
|
||||
if (!value && Group is { IsNoneSetAllowed: false })
|
||||
{
|
||||
throw new InvalidOperationException("Cannot directly unset a grouped button. Set another button in the group instead.");
|
||||
}
|
||||
@@ -326,7 +339,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var button in _group.Buttons)
|
||||
foreach (var button in _group.InternalButtons)
|
||||
{
|
||||
if (button != this && button.Pressed)
|
||||
{
|
||||
@@ -440,6 +453,29 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </remarks>
|
||||
public sealed class ButtonGroup
|
||||
{
|
||||
internal readonly List<BaseButton> Buttons = new();
|
||||
/// <summary>
|
||||
/// Whether it is legal for this button group to have no selected button.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If true, it's legal for no button in the group to be active.
|
||||
/// This is then the initial state of a new group of buttons (no button is automatically selected),
|
||||
/// and it becomes legal to manually clear the active button through code.
|
||||
/// The user cannot manually unselect the active button regardless, only by selecting a difference button.
|
||||
/// </remarks>
|
||||
public bool IsNoneSetAllowed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ButtonGroup"/>
|
||||
/// </summary>
|
||||
/// <param name="isNoneSetAllowed">The value of <see cref="IsNoneSetAllowed"/> on the new button group.</param>
|
||||
public ButtonGroup(bool isNoneSetAllowed = true)
|
||||
{
|
||||
IsNoneSetAllowed = isNoneSetAllowed;
|
||||
}
|
||||
|
||||
internal readonly List<BaseButton> InternalButtons = new();
|
||||
public IReadOnlyList<BaseButton> Buttons => InternalButtons;
|
||||
|
||||
public BaseButton? Pressed => InternalButtons.FirstOrDefault(x => x.Pressed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,9 +29,11 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
|
||||
public Collapsible()
|
||||
{}
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical;
|
||||
}
|
||||
|
||||
public Collapsible(CollapsibleHeading header, CollapsibleBody body)
|
||||
public Collapsible(CollapsibleHeading header, CollapsibleBody body) : this()
|
||||
{
|
||||
AddChild(header);
|
||||
AddChild(body);
|
||||
@@ -39,12 +41,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public Collapsible(string title, CollapsibleBody body)
|
||||
public Collapsible(string title, CollapsibleBody body) : this(new CollapsibleHeading(title), body)
|
||||
{
|
||||
AddChild(new CollapsibleHeading(title));
|
||||
AddChild(body);
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
protected internal override void Draw(DrawingHandleScreen handle)
|
||||
@@ -105,11 +104,15 @@ namespace Robust.Client.UserInterface.Controls
|
||||
set => _chevron.Margin = value;
|
||||
}
|
||||
|
||||
private Label _title = new();
|
||||
/// <summary>
|
||||
/// Exposes the label for this heading.
|
||||
/// </summary>
|
||||
public Label Label { get; }
|
||||
|
||||
public string? Title
|
||||
{
|
||||
get => _title.Text;
|
||||
set => _title.Text = value;
|
||||
get => Label.Text;
|
||||
set => Label.Text = value;
|
||||
}
|
||||
|
||||
public CollapsibleHeading()
|
||||
@@ -118,8 +121,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
var box = new BoxContainer();
|
||||
AddChild(box);
|
||||
box.AddChild(_chevron);
|
||||
_title = new Label();
|
||||
box.AddChild(_title);
|
||||
Label = new Label();
|
||||
box.AddChild(Label);
|
||||
}
|
||||
|
||||
public CollapsibleHeading(string title) : this()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
@@ -9,6 +9,9 @@ using Robust.Shared.Input;
|
||||
using Robust.Shared.Maths;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a scrollable list of items in a user interface.
|
||||
/// </summary>
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
[Virtual]
|
||||
@@ -29,6 +32,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public const string StylePropertySelectedItemBackground = "selected-item-background";
|
||||
public const string StylePropertyDisabledItemBackground = "disabled-item-background";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ItemSeparation of individual list items
|
||||
/// </summary>
|
||||
public int ItemSeparation { get; set; } = 0; // Default value is 0px
|
||||
public int Count => _itemList.Count;
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
@@ -68,8 +75,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
itemHeight += ActualItemBackground.MinimumSize.Y * UIScale;
|
||||
|
||||
_totalContentHeight += (int)Math.Ceiling(itemHeight);
|
||||
_totalContentHeight += ItemSeparation;
|
||||
}
|
||||
|
||||
//Remove unneeded ItemSeparation on last item.
|
||||
_totalContentHeight -= ItemSeparation;
|
||||
_scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
|
||||
_updateScrollbarVisibility();
|
||||
}
|
||||
@@ -390,6 +399,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
|
||||
offset += itemHeight;
|
||||
|
||||
// Add a ItemSeparation at the bottom of each item.
|
||||
offset += ItemSeparation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,7 +411,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
var color = ActualFontColor;
|
||||
var offsetY = (int) (box.Height - font.GetHeight(UIScale)) / 2;
|
||||
var baseLine = new Vector2i(0, offsetY + font.GetAscent(UIScale)) + box.TopLeft;
|
||||
var baseLine = new Vector2i(5, offsetY + font.GetAscent(UIScale)) + box.TopLeft;
|
||||
|
||||
foreach (var rune in text.EnumerateRunes())
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public class OptionButton : ContainerButton
|
||||
{
|
||||
public const string StyleClassOptionButton = "optionButton";
|
||||
public const string StyleClassPopup = "optionButtonPopup";
|
||||
public const string StyleClassOptionTriangle = "optionTriangle";
|
||||
public readonly ScrollContainer OptionsScroll;
|
||||
|
||||
@@ -74,7 +75,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
_popup = new Popup()
|
||||
{
|
||||
Children = { OptionsScroll }
|
||||
Children = { new PanelContainer(), OptionsScroll },
|
||||
StyleClasses = { StyleClassPopup }
|
||||
};
|
||||
_popup.OnPopupHide += OnPopupHide;
|
||||
|
||||
@@ -99,6 +101,11 @@ namespace Robust.Client.UserInterface.Controls
|
||||
AddItem(label, id);
|
||||
}
|
||||
|
||||
public virtual void ButtonOverride(Button button)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void AddItem(string label, int? id = null)
|
||||
{
|
||||
if (id == null)
|
||||
@@ -132,6 +139,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
Select(0);
|
||||
}
|
||||
|
||||
ButtonOverride(button);
|
||||
}
|
||||
|
||||
private void TogglePopup(bool show)
|
||||
@@ -139,6 +148,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
if (show)
|
||||
{
|
||||
var globalPos = GlobalPosition;
|
||||
globalPos.Y += Size.Y + 1; // Place it below us, with a safety margin.
|
||||
globalPos.Y -= Margin.SumVertical;
|
||||
OptionsScroll.Measure(Window?.Size ?? Vector2Helpers.Infinity);
|
||||
var (minX, minY) = OptionsScroll.DesiredSize;
|
||||
var box = UIBox2.FromDimensions(globalPos, new Vector2(Math.Max(minX, Width), minY));
|
||||
|
||||
@@ -15,13 +15,13 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
var style = _getStyleBox();
|
||||
var style = GetStyleBox();
|
||||
style?.Draw(handle, PixelSizeBox, UIScale);
|
||||
}
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
var styleSize = _getStyleBox()?.MinimumSize ?? Vector2.Zero;
|
||||
var styleSize = GetStyleBox()?.MinimumSize ?? Vector2.Zero;
|
||||
var measureSize = Vector2.Max(availableSize - styleSize, Vector2.Zero);
|
||||
var childSize = Vector2.Zero;
|
||||
foreach (var child in Children)
|
||||
@@ -36,7 +36,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
var ourSize = UIBox2.FromDimensions(Vector2.Zero, finalSize);
|
||||
var contentBox = _getStyleBox()?.GetContentBox(ourSize, 1) ?? ourSize;
|
||||
var contentBox = GetStyleBox()?.GetContentBox(ourSize, 1) ?? ourSize;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
@@ -47,7 +47,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
|
||||
[System.Diagnostics.Contracts.Pure]
|
||||
private StyleBox? _getStyleBox()
|
||||
protected StyleBox? GetStyleBox()
|
||||
{
|
||||
if (PanelOverride != null)
|
||||
{
|
||||
|
||||
@@ -20,6 +20,11 @@ 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;
|
||||
|
||||
@@ -246,9 +251,19 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
if (_hScrollEnabled)
|
||||
{
|
||||
_hScrollBar.ValueTarget += args.Delta.X * ScrollSpeedX;
|
||||
var delta =
|
||||
args.Delta.X == 0f &&
|
||||
!_vScrollEnabled &&
|
||||
FallbackDeltaScroll ?
|
||||
-args.Delta.Y :
|
||||
args.Delta.X;
|
||||
|
||||
_hScrollBar.ValueTarget += delta * ScrollSpeedX;
|
||||
}
|
||||
|
||||
if (!_vScrollVisible && !_hScrollVisible)
|
||||
return;
|
||||
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ 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;
|
||||
@@ -29,7 +32,18 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </summary>
|
||||
public Texture? Texture
|
||||
{
|
||||
get => _texture;
|
||||
get
|
||||
{
|
||||
if (_texture is null)
|
||||
{
|
||||
if (TryGetStyleProperty(StylePropertyTexture, out Texture? texture))
|
||||
{
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
|
||||
return _texture;
|
||||
}
|
||||
set
|
||||
{
|
||||
var oldSize = _texture?.Size;
|
||||
@@ -43,6 +57,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
|
||||
private string? _texturePath;
|
||||
private StretchMode _stretch = StretchMode.Keep;
|
||||
|
||||
public string TexturePath
|
||||
{
|
||||
@@ -54,21 +69,45 @@ 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 => _textureScale;
|
||||
get
|
||||
{
|
||||
if (!TryGetStyleProperty(StylePropertyTextureScale, out Vector2 scale))
|
||||
scale = _textureScale;
|
||||
|
||||
return scale;
|
||||
}
|
||||
set
|
||||
{
|
||||
_textureScale = value;
|
||||
@@ -96,23 +135,27 @@ 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; set; } = StretchMode.Keep;
|
||||
public StretchMode Stretch
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!TryGetStyleProperty(StylePropertyTextureStretch, out StretchMode stretch))
|
||||
stretch = _stretch;
|
||||
return stretch;
|
||||
}
|
||||
set => _stretch = value;
|
||||
}
|
||||
|
||||
protected internal override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
var texture = _texture;
|
||||
ShaderInstance? shader = null;
|
||||
var texture = Texture;
|
||||
|
||||
if (texture == null)
|
||||
{
|
||||
TryGetStyleProperty(StylePropertyTexture, out texture);
|
||||
if (texture == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (texture is null)
|
||||
return;
|
||||
|
||||
ShaderInstance? shader = null;
|
||||
|
||||
if (ShaderOverride != null)
|
||||
{
|
||||
@@ -167,17 +210,17 @@ namespace Robust.Client.UserInterface.Controls
|
||||
case StretchMode.Tile:
|
||||
// TODO: Implement Tile.
|
||||
case StretchMode.Keep:
|
||||
return UIBox2.FromDimensions(Vector2.Zero, texture.Size * _textureScale * UIScale);
|
||||
return UIBox2.FromDimensions(Vector2.Zero, TextureSizeTarget * UIScale);
|
||||
case StretchMode.KeepCentered:
|
||||
{
|
||||
var position = (PixelSize - texture.Size * _textureScale * UIScale) / 2;
|
||||
return UIBox2.FromDimensions(position, texture.Size * _textureScale * UIScale);
|
||||
var position = (Size - TextureSizeTarget) / 2;
|
||||
return UIBox2.FromDimensions(position, TextureSizeTarget * UIScale);
|
||||
}
|
||||
|
||||
case StretchMode.KeepAspect:
|
||||
case StretchMode.KeepAspectCentered:
|
||||
{
|
||||
var (texWidth, texHeight) = texture.Size * _textureScale;
|
||||
var (texWidth, texHeight) = TextureSizeTarget;
|
||||
var width = texWidth * (PixelSize.Y / texHeight);
|
||||
var height = (float)PixelSize.Y;
|
||||
if (width > PixelSize.X)
|
||||
@@ -197,7 +240,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
|
||||
case StretchMode.KeepAspectCovered:
|
||||
var texSize = texture.Size * _textureScale;
|
||||
var texSize = TextureSizeTarget;
|
||||
// Calculate the scale necessary to fit width and height to control size.
|
||||
var (scaleX, scaleY) = PixelSize / texSize;
|
||||
// Use whichever scale is greater.
|
||||
@@ -259,19 +302,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
var texture = _texture;
|
||||
|
||||
if (texture == null)
|
||||
{
|
||||
TryGetStyleProperty(StylePropertyTexture, out texture);
|
||||
}
|
||||
|
||||
if (texture == null || CanShrink)
|
||||
{
|
||||
if (CanShrink || Texture == null)
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
return texture.Size * TextureScale;
|
||||
return TextureSizeTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,9 @@ namespace Robust.Client.UserInterface
|
||||
Vector2? CalcRelativeMousePositionFor(Control control, ScreenCoordinates mousePos);
|
||||
|
||||
Color GetMainClearColor();
|
||||
|
||||
void RenderControl(IRenderHandle renderHandle, ref int total, Control control, Vector2i position, Color modulate,
|
||||
UIBox2i? scissorBox, Matrix3 coordinateTransform);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,9 +132,10 @@ internal sealed partial class UserInterfaceManager
|
||||
try
|
||||
{
|
||||
var total = 0;
|
||||
_render(renderHandle, ref total, root, Vector2i.Zero, Color.White, null);
|
||||
var drawingHandle = renderHandle.DrawingHandleScreen;
|
||||
drawingHandle.SetTransform(Vector2.Zero, Angle.Zero, Vector2.One);
|
||||
drawingHandle.SetTransform(Matrix3.Identity);
|
||||
RenderControl(renderHandle, ref total, root, Vector2i.Zero, Color.White, null, Matrix3.Identity);
|
||||
drawingHandle.SetTransform(Matrix3.Identity);
|
||||
OnPostDrawUIRoot?.Invoke(new PostDrawUIRootEventArgs(root, drawingHandle));
|
||||
|
||||
_prof.WriteValue("Controls rendered", ProfData.Int32(total));
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
foreach (var root in _roots)
|
||||
{
|
||||
if (root.Stylesheet != null)
|
||||
if (root.Stylesheet == null)
|
||||
{
|
||||
root.StylesheetUpdateRecursive();
|
||||
}
|
||||
@@ -329,8 +329,8 @@ namespace Robust.Client.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
private void _render(IRenderHandle renderHandle, ref int total, Control control, Vector2i position, Color modulate,
|
||||
UIBox2i? scissorBox)
|
||||
public void RenderControl(IRenderHandle renderHandle, ref int total, Control control, Vector2i position, Color modulate,
|
||||
UIBox2i? scissorBox, Matrix3 coordinateTransform)
|
||||
{
|
||||
if (!control.Visible)
|
||||
{
|
||||
@@ -377,7 +377,10 @@ namespace Robust.Client.UserInterface
|
||||
total += 1;
|
||||
|
||||
var handle = renderHandle.DrawingHandleScreen;
|
||||
handle.SetTransform(position, Angle.Zero, Vector2.One);
|
||||
var oldXform = handle.GetTransform();
|
||||
var xform = oldXform;
|
||||
xform.Multiply(Matrix3.CreateTransform(position, Angle.Zero, Vector2.One));
|
||||
handle.SetTransform(xform);
|
||||
modulate *= control.Modulate;
|
||||
|
||||
if (_rendering || control.AlwaysRender)
|
||||
@@ -389,16 +392,32 @@ namespace Robust.Client.UserInterface
|
||||
handle.Modulate = oldMod;
|
||||
handle.UseShader(null);
|
||||
}
|
||||
handle.SetTransform(oldXform);
|
||||
var args = new Control.ControlRenderArguments()
|
||||
{
|
||||
Handle = renderHandle,
|
||||
Total = ref total,
|
||||
Modulate = modulate,
|
||||
ScissorBox = scissorRegion,
|
||||
CoordinateTransform = ref coordinateTransform
|
||||
};
|
||||
|
||||
control.PreRenderChildren(ref args);
|
||||
|
||||
foreach (var child in control.Children)
|
||||
{
|
||||
_render(renderHandle, ref total, child, position + child.PixelPosition, modulate, scissorRegion);
|
||||
var pos = position + (Vector2i) coordinateTransform.Transform(child.PixelPosition);
|
||||
control.RenderChildOverride(ref args, child.GetPositionInParent(), pos);
|
||||
}
|
||||
|
||||
control.PostRenderChildren(ref args);
|
||||
|
||||
if (clip)
|
||||
{
|
||||
renderHandle.SetScissor(scissorBox);
|
||||
}
|
||||
|
||||
handle.SetTransform(oldXform);
|
||||
}
|
||||
|
||||
public Color GetMainClearColor() => RootControl.ActualBgColor;
|
||||
|
||||
46
Robust.Roslyn.Shared/AttributeHelper.cs
Normal file
46
Robust.Roslyn.Shared/AttributeHelper.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
namespace Robust.Roslyn.Shared;
|
||||
|
||||
public static class Diagnostics
|
||||
{
|
||||
@@ -24,6 +24,10 @@ 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.");
|
||||
201
Robust.Roslyn.Shared/Helpers/EquatableArray{T}.cs
Normal file
201
Robust.Roslyn.Shared/Helpers/EquatableArray{T}.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
190
Robust.Roslyn.Shared/Helpers/HashCode.cs
Normal file
190
Robust.Roslyn.Shared/Helpers/HashCode.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
121
Robust.Roslyn.Shared/PartialTypeHelper.cs
Normal file
121
Robust.Roslyn.Shared/PartialTypeHelper.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
38
Robust.Roslyn.Shared/Robust.Roslyn.Shared.props
Normal file
38
Robust.Roslyn.Shared/Robust.Roslyn.Shared.props
Normal file
@@ -0,0 +1,38 @@
|
||||
<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>
|
||||
28
Robust.Roslyn.Shared/TypeSymbolHelper.cs
Normal file
28
Robust.Roslyn.Shared/TypeSymbolHelper.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
252
Robust.Serialization.Generator/ComponentPauseGenerator.cs
Normal file
252
Robust.Serialization.Generator/ComponentPauseGenerator.cs
Normal file
@@ -0,0 +1,252 @@
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"Generators": {
|
||||
"commandName": "DebugRoslynComponent",
|
||||
"targetProject": "../../Content.Shared/Content.Shared.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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>
|
||||
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,6 +2,7 @@ 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;
|
||||
@@ -283,25 +284,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 ValueTask<CompletionResult?> CalcCompletions(IConsoleShell shell, string[] args, string argStr)
|
||||
private async 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 ValueTask.FromResult<CompletionResult?>(CompletionResult.FromOptions(
|
||||
AvailableCommands.Values.Where(c => ShellCanExecute(shell, c.Command)).Select(c => new CompletionOption(c.Command, c.Description))));
|
||||
return 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 ValueTask.FromResult<CompletionResult?>(null);
|
||||
return null;
|
||||
|
||||
if (!ShellCanExecute(shell, cmdName))
|
||||
return ValueTask.FromResult<CompletionResult?>(CompletionResult.Empty);
|
||||
return CompletionResult.Empty;
|
||||
|
||||
return ValueTask.FromResult<CompletionResult?>(cmd.GetCompletion(shell, args[1..]));
|
||||
return await cmd.GetCompletionAsync(shell, args[1..], argStr, CancellationToken.None);
|
||||
}
|
||||
|
||||
private sealed class SudoShell : IConsoleShell
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Robust.Server.GameObjects
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<MapGridComponent, EmptyGridEvent>(HandleGridEmpty);
|
||||
|
||||
_cfg.OnValueChanged(CVars.GameDeleteEmptyGrids, SetGridDeletion, true);
|
||||
Subs.CVar(_cfg, CVars.GameDeleteEmptyGrids, SetGridDeletion, true);
|
||||
}
|
||||
|
||||
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
|
||||
@@ -64,13 +64,6 @@ namespace Robust.Server.GameObjects
|
||||
return !(grid.GetAllTiles().Any());
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_cfg.UnsubValueChanged(CVars.GameDeleteEmptyGrids, SetGridDeletion);
|
||||
}
|
||||
|
||||
private void HandleGridEmpty(EntityUid uid, MapGridComponent component, EmptyGridEvent args)
|
||||
{
|
||||
if (!_deleteEmptyGrids || TerminatingOrDeleted(uid) || HasComp<MapComponent>(uid))
|
||||
|
||||
@@ -20,7 +20,8 @@ namespace Robust.Server.GameObjects
|
||||
{
|
||||
base.Initialize();
|
||||
LoadMetricCVar();
|
||||
_configurationManager.OnValueChanged(CVars.MetricsEnabled, _ => LoadMetricCVar());
|
||||
|
||||
Subs.CVar(_configurationManager, CVars.MetricsEnabled, _ => LoadMetricCVar());
|
||||
}
|
||||
|
||||
private void LoadMetricCVar()
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ 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;
|
||||
|
||||
@@ -126,11 +126,12 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
_transform.OnGlobalMoveEvent += OnEntityMove;
|
||||
|
||||
_configManager.OnValueChanged(CVars.NetPVS, SetPvs, true);
|
||||
_configManager.OnValueChanged(CVars.NetMaxUpdateRange, OnViewsizeChanged, true);
|
||||
_configManager.OnValueChanged(CVars.NetLowLodRange, OnLodChanged, true);
|
||||
_configManager.OnValueChanged(CVars.NetForceAckThreshold, OnForceAckChanged, true);
|
||||
_configManager.OnValueChanged(CVars.NetPvsAsync, OnAsyncChanged, true);
|
||||
Subs.CVar(_configManager, CVars.NetPVS, SetPvs, true);
|
||||
Subs.CVar(_configManager, CVars.NetMaxUpdateRange, OnViewsizeChanged, true);
|
||||
Subs.CVar(_configManager, CVars.NetLowLodRange, OnLodChanged, true);
|
||||
Subs.CVar(_configManager, CVars.NetForceAckThreshold, OnForceAckChanged, true);
|
||||
Subs.CVar(_configManager, CVars.NetPvsAsync, OnAsyncChanged, true);
|
||||
Subs.CVar(_configManager, CVars.NetPvsCompressLevel, ResetParallelism, true);
|
||||
|
||||
_serverGameStateManager.ClientAck += OnClientAck;
|
||||
_serverGameStateManager.ClientRequestFull += OnClientRequestFull;
|
||||
@@ -138,7 +139,6 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
InitializeDirty();
|
||||
|
||||
_parallelMgr.ParallelCountChanged += ResetParallelism;
|
||||
_configManager.OnValueChanged(CVars.NetPvsCompressLevel, ResetParallelism, true);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -148,10 +148,6 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
_transform.OnGlobalMoveEvent -= OnEntityMove;
|
||||
|
||||
_configManager.UnsubValueChanged(CVars.NetPVS, SetPvs);
|
||||
_configManager.UnsubValueChanged(CVars.NetMaxUpdateRange, OnViewsizeChanged);
|
||||
_configManager.UnsubValueChanged(CVars.NetForceAckThreshold, OnForceAckChanged);
|
||||
_configManager.UnsubValueChanged(CVars.NetPvsCompressLevel, ResetParallelism);
|
||||
_parallelMgr.ParallelCountChanged -= ResetParallelism;
|
||||
|
||||
_serverGameStateManager.ClientAck -= OnClientAck;
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Robust.Server.Physics
|
||||
SubscribeNetworkEvent<RequestGridNodesMessage>(OnDebugRequest);
|
||||
SubscribeNetworkEvent<StopGridNodesMessage>(OnDebugStopRequest);
|
||||
|
||||
_cfg.OnValueChanged(CVars.GridSplitting, SetSplitAllowed, true);
|
||||
Subs.CVar(_cfg, CVars.GridSplitting, SetSplitAllowed, true);
|
||||
}
|
||||
|
||||
private void SetSplitAllowed(bool value) => SplitAllowed = value;
|
||||
@@ -73,7 +73,6 @@ namespace Robust.Server.Physics
|
||||
{
|
||||
base.Shutdown();
|
||||
_subscribedSessions.Clear();
|
||||
_cfg.UnsubValueChanged(CVars.GridSplitting, SetSplitAllowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
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
|
||||
{
|
||||
@@ -19,20 +17,26 @@ 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<global::Robust.Shared.GameObjects.NetEntity>";
|
||||
private const string GlobalNetEntityUidSetName = $"global::System.Collections.Generic.HashSet<{GlobalNetEntityName}>";
|
||||
|
||||
private const string GlobalEntityUidListName = "global::System.Collections.Generic.List<global::Robust.Shared.GameObjects.EntityUid>";
|
||||
private const string GlobalNetEntityUidListName = "global::System.Collections.Generic.List<global::Robust.Shared.GameObjects.NetEntity>";
|
||||
private const string GlobalNetEntityUidListName = $"global::System.Collections.Generic.List<{GlobalNetEntityName}>";
|
||||
|
||||
private const string GlobalDictionaryName = "global::System.Collections.Generic.Dictionary<TKey, TValue>";
|
||||
private const string GlobalHashSetName = "global::System.Collections.Generic.HashSet<T>";
|
||||
private const string GlobalListName = "global::System.Collections.Generic.List<T>";
|
||||
|
||||
private static string GenerateSource(in GeneratorExecutionContext context, INamedTypeSymbol classSymbol, CSharpCompilation comp, bool raiseAfterAutoHandle)
|
||||
private static readonly SymbolDisplayFormat FullNullableFormat =
|
||||
FullyQualifiedFormat.WithMiscellaneousOptions(IncludeNullableReferenceTypeModifier);
|
||||
|
||||
private static string? GenerateSource(in GeneratorExecutionContext context, INamedTypeSymbol classSymbol, CSharpCompilation comp, bool raiseAfterAutoHandle)
|
||||
{
|
||||
var nameSpace = classSymbol.ContainingNamespace.ToDisplayString();
|
||||
var componentName = classSymbol.Name;
|
||||
@@ -132,7 +136,7 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
|
||||
foreach (var (type, name) in fields)
|
||||
{
|
||||
var typeDisplayStr = type.ToDisplayString(FullyQualifiedFormat);
|
||||
var typeDisplayStr = type.ToDisplayString(FullNullableFormat);
|
||||
var nullable = type.NullableAnnotation == NullableAnnotation.Annotated;
|
||||
var nullableAnnotation = nullable ? "?" : string.Empty;
|
||||
|
||||
@@ -181,6 +185,62 @@ 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!;");
|
||||
|
||||
@@ -193,7 +253,7 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
|
||||
handleStateSetters.Append($@"
|
||||
if (state.{name} == null)
|
||||
component.{name} = null;
|
||||
component.{name} = null!;
|
||||
else
|
||||
component.{name} = new(state.{name});");
|
||||
}
|
||||
@@ -219,6 +279,7 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
}
|
||||
|
||||
return $@"// <auto-generated />
|
||||
#nullable enable
|
||||
using System;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -280,10 +341,10 @@ public partial class {componentName}
|
||||
{
|
||||
var attr = type.Attribute;
|
||||
var raiseEv = false;
|
||||
if (attr.ConstructorArguments.Length == 1 && attr.ConstructorArguments[0].Value != null)
|
||||
if (attr.ConstructorArguments is [{Value: bool raise}])
|
||||
{
|
||||
// Get the afterautohandle bool, which is first constructor arg
|
||||
raiseEv = (bool) attr.ConstructorArguments[0].Value;
|
||||
raiseEv = raise;
|
||||
}
|
||||
|
||||
var source = GenerateSource(context, type.Type, comp, raiseEv);
|
||||
@@ -325,11 +386,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 =>
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>9</LangVersion>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -75,6 +75,15 @@ namespace Robust.Shared.Maths
|
||||
get => new(Width, Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the highest of width or height.
|
||||
/// </summary>
|
||||
public readonly float MaxDimension
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => MathF.Max(Height, Width);
|
||||
}
|
||||
|
||||
public readonly Vector2 Center
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -177,13 +186,13 @@ namespace Robust.Shared.Maths
|
||||
[Pure]
|
||||
public readonly Box2 Intersect(in Box2 other)
|
||||
{
|
||||
var ourLeftBottom = new System.Numerics.Vector2(Left, Bottom);
|
||||
var ourRightTop = new System.Numerics.Vector2(Right, Top);
|
||||
var otherLeftBottom = new System.Numerics.Vector2(other.Left, other.Bottom);
|
||||
var otherRightTop = new System.Numerics.Vector2(other.Right, other.Top);
|
||||
var ourLeftBottom = BottomLeft;
|
||||
var ourRightTop = TopRight;
|
||||
var otherLeftBottom = other.BottomLeft;
|
||||
var otherRightTop = other.TopRight;
|
||||
|
||||
var max = System.Numerics.Vector2.Max(ourLeftBottom, otherLeftBottom);
|
||||
var min = System.Numerics.Vector2.Min(ourRightTop, otherRightTop);
|
||||
var max = Vector2.Max(ourLeftBottom, otherLeftBottom);
|
||||
var min = Vector2.Min(ourRightTop, otherRightTop);
|
||||
|
||||
if (max.X <= min.X && max.Y <= min.Y)
|
||||
return new Box2(max.X, max.Y, min.X, min.Y);
|
||||
@@ -210,13 +219,13 @@ namespace Robust.Shared.Maths
|
||||
[Pure]
|
||||
public readonly Box2 Union(in Box2 other)
|
||||
{
|
||||
var ourLeftBottom = new System.Numerics.Vector2(Left, Bottom);
|
||||
var otherLeftBottom = new System.Numerics.Vector2(other.Left, other.Bottom);
|
||||
var ourRightTop = new System.Numerics.Vector2(Right, Top);
|
||||
var otherRightTop = new System.Numerics.Vector2(other.Right, other.Top);
|
||||
var ourLeftBottom = BottomLeft;
|
||||
var otherLeftBottom = other.BottomLeft;
|
||||
var ourRightTop = TopRight;
|
||||
var otherRightTop = other.TopRight;
|
||||
|
||||
var leftBottom = System.Numerics.Vector2.Min(ourLeftBottom, otherLeftBottom);
|
||||
var rightTop = System.Numerics.Vector2.Max(ourRightTop, otherRightTop);
|
||||
var leftBottom = Vector2.Min(ourLeftBottom, otherLeftBottom);
|
||||
var rightTop = Vector2.Max(ourRightTop, otherRightTop);
|
||||
|
||||
if (leftBottom.X <= rightTop.X && leftBottom.Y <= rightTop.Y)
|
||||
return new Box2(leftBottom.X, leftBottom.Y, rightTop.X, rightTop.Y);
|
||||
@@ -396,11 +405,11 @@ namespace Robust.Shared.Maths
|
||||
[Pure]
|
||||
public static Box2 Union(in Vector2 a, in Vector2 b)
|
||||
{
|
||||
var vecA = new System.Numerics.Vector2(a.X, a.Y);
|
||||
var vecB = new System.Numerics.Vector2(b.X, b.Y);
|
||||
var vecA = new Vector2(a.X, a.Y);
|
||||
var vecB = new Vector2(b.X, b.Y);
|
||||
|
||||
var min = System.Numerics.Vector2.Min(vecA, vecB);
|
||||
var max = System.Numerics.Vector2.Max(vecA, vecB);
|
||||
var min = Vector2.Min(vecA, vecB);
|
||||
var max = Vector2.Max(vecA, vecB);
|
||||
|
||||
return new Box2(min.X, min.Y, max.X, max.Y);
|
||||
}
|
||||
@@ -412,12 +421,12 @@ namespace Robust.Shared.Maths
|
||||
[Pure]
|
||||
public readonly Box2 ExtendToContain(Vector2 vec)
|
||||
{
|
||||
var leftBottom = new System.Numerics.Vector2(Left, Bottom);
|
||||
var rightTop = new System.Numerics.Vector2(Right, Top);
|
||||
var vector = new System.Numerics.Vector2(vec.X, vec.Y);
|
||||
var leftBottom = new Vector2(Left, Bottom);
|
||||
var rightTop = new Vector2(Right, Top);
|
||||
var vector = new Vector2(vec.X, vec.Y);
|
||||
|
||||
var min = System.Numerics.Vector2.Min(vector, leftBottom);
|
||||
var max = System.Numerics.Vector2.Max(vector, rightTop);
|
||||
var min = Vector2.Min(vector, leftBottom);
|
||||
var max = Vector2.Max(vector, rightTop);
|
||||
|
||||
return new Box2(min.X, min.Y, max.X, max.Y);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ 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
|
||||
@@ -57,7 +58,17 @@ namespace Robust.Shared.Maths
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// calculates the smallest AABB that will encompass the rotated box. The AABB is in local space.
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public readonly Box2 CalcBoundingBox()
|
||||
{
|
||||
|
||||
@@ -1,13 +1,229 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Robust.Shared.Maths;
|
||||
|
||||
// Reference: https://easings.net/
|
||||
|
||||
internal static class Easings
|
||||
/// <summary>
|
||||
/// A static class for computing easings for animations.
|
||||
/// The parameter "p" is the absolute progress of the animation between 0 and 1.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public static class Easings
|
||||
{
|
||||
#region Trig
|
||||
|
||||
public static float InSine(float p)
|
||||
{
|
||||
return 1.0f - MathF.Cos(p * MathF.PI / 2.0f);
|
||||
}
|
||||
|
||||
public static float OutSine(float p)
|
||||
{
|
||||
return MathF.Sin(p * MathF.PI / 2);
|
||||
}
|
||||
|
||||
public static float InOutSine(float p)
|
||||
{
|
||||
return -(MathF.Cos(MathF.PI * p) - 1.0f) / 2.0f;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Polynomial
|
||||
|
||||
public static float InQuad(float p)
|
||||
{
|
||||
return p * p;
|
||||
}
|
||||
|
||||
public static float OutQuad(float p)
|
||||
{
|
||||
return 1 - (1 - p) * (1 - p);
|
||||
}
|
||||
|
||||
public static float InOutQuad(float p)
|
||||
{
|
||||
return p < 0.5 ? 2 * p * p : 1 - MathF.Pow(-2 * p + 2, 2) / 2;
|
||||
}
|
||||
|
||||
public static float InCubic(float p)
|
||||
{
|
||||
return p * p * p;
|
||||
}
|
||||
|
||||
public static float OutCubic(float p)
|
||||
{
|
||||
return 1 - MathF.Pow(1 - p, 3);
|
||||
}
|
||||
|
||||
public static float InOutCubic(float p)
|
||||
{
|
||||
return p < 0.5 ? 4 * p * p * p : 1 - MathF.Pow(-2 * p + 2, 3) / 2;
|
||||
}
|
||||
|
||||
public static float InQuart(float p)
|
||||
{
|
||||
return p * p * p * p;
|
||||
}
|
||||
|
||||
public static float OutQuart(float p)
|
||||
{
|
||||
return 1 - MathF.Pow(1 - p, 4);
|
||||
}
|
||||
|
||||
public static float InOutQuart(float p)
|
||||
{
|
||||
return p < 0.5 ? 8 * p * p * p * p : 1 - MathF.Pow(-2 * p + 2, 4) / 2;
|
||||
}
|
||||
|
||||
public static float InQuint(float p)
|
||||
{
|
||||
return p * p * p * p * p;
|
||||
}
|
||||
|
||||
public static float OutQuint(float p)
|
||||
{
|
||||
return 1 - MathF.Pow(1 - p, 5);
|
||||
}
|
||||
|
||||
public static float InOutQuint(float p)
|
||||
{
|
||||
return p < 0.5f ? (16 * p * p * p * p * p) : 1 - MathF.Pow(-2 * p + 2, 5) / 2;
|
||||
return p < 0.5f ? 16 * p * p * p * p * p : 1 - MathF.Pow(-2 * p + 2, 5) / 2;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Other
|
||||
|
||||
public static float InExpo(float p)
|
||||
{
|
||||
return p == 0 ? 0 : MathF.Pow(2, 10 * p - 10);
|
||||
}
|
||||
|
||||
public static float OutExpo(float p)
|
||||
{
|
||||
return Math.Abs(p - 1) < 0.0001f ? 1 : 1 - MathF.Pow(2, -10 * p);
|
||||
}
|
||||
|
||||
public static float InOutExpo(float p)
|
||||
{
|
||||
return p == 0.0f
|
||||
? 0
|
||||
: Math.Abs(p - 1) < 0.0001f
|
||||
? 1
|
||||
: p < 0.5f
|
||||
? MathF.Pow(2, 20 * p - 10) / 2
|
||||
: (2 - MathF.Pow(2, -20 * p + 10)) / 2;
|
||||
}
|
||||
|
||||
public static float InCirc(float p)
|
||||
{
|
||||
return 1 - MathF.Sqrt(1 - MathF.Pow(p, 2));
|
||||
}
|
||||
|
||||
public static float OutCirc(float p)
|
||||
{
|
||||
return MathF.Sqrt(1 - MathF.Pow(p - 1, 2));
|
||||
}
|
||||
|
||||
public static float InOutCirc(float p)
|
||||
{
|
||||
return p < 0.5
|
||||
? (1 - MathF.Sqrt(1 - MathF.Pow(2 * p, 2))) / 2
|
||||
: (MathF.Sqrt(1 - MathF.Pow(-2 * p + 2, 2)) + 1) / 2;
|
||||
}
|
||||
|
||||
public static float InBack(float p)
|
||||
{
|
||||
var c1 = 1.70158f;
|
||||
var c3 = c1 + 1;
|
||||
|
||||
return c3 * p * p * p - c1 * p * p;
|
||||
}
|
||||
|
||||
public static float OutBack(float p)
|
||||
{
|
||||
const float c1 = 1.70158f;
|
||||
const float c3 = c1 + 1;
|
||||
|
||||
return 1 + c3 * MathF.Pow(p - 1, 3) + c1 * MathF.Pow(p - 1, 2);
|
||||
}
|
||||
|
||||
public static float InOutBack(float p)
|
||||
{
|
||||
const float c1 = 1.70158f;
|
||||
const float c2 = c1 * 1.525f;
|
||||
|
||||
return p < 0.5
|
||||
? MathF.Pow(2 * p, 2) * ((c2 + 1) * 2 * p - c2) / 2
|
||||
: (MathF.Pow(2 * p - 2, 2) * ((c2 + 1) * (p * 2 - 2) + c2) + 2) / 2;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// elastic in, not "inelastic"
|
||||
/// </remarks>
|
||||
public static float InElastic(float p)
|
||||
{
|
||||
const float c4 = 2 * MathF.PI / 3;
|
||||
|
||||
return p == 0
|
||||
? 0
|
||||
: Math.Abs(p - 1) < 0.0001f
|
||||
? 1
|
||||
: -MathF.Pow(2, 10 * p - 10) * MathF.Sin((p * 10 - 10.75f) * c4);
|
||||
}
|
||||
|
||||
public static float OutElastic(float p)
|
||||
{
|
||||
const float c4 = 2.0f * MathF.PI / 3.0f;
|
||||
|
||||
return p == 0
|
||||
? 0
|
||||
: Math.Abs(p - 1) < 0.0001f
|
||||
? 1
|
||||
: MathF.Pow(2, -10 * p) * MathF.Sin((p * 10.0f - 0.75f) * c4) + 1.0f;
|
||||
}
|
||||
|
||||
public static float InOutElastic(float p)
|
||||
{
|
||||
const float c5 = 2.0f * MathF.PI / 4.5f;
|
||||
|
||||
return p == 0
|
||||
? 0
|
||||
: Math.Abs(p - 1) < 0.0001f
|
||||
? 1
|
||||
: p < 0.5
|
||||
? -(MathF.Pow(2, 20 * p - 10) * MathF.Sin((20.0f * p - 11.125f) * c5)) / 2.0f
|
||||
: MathF.Pow(2, -20.0f * p + 10.0f) * MathF.Sin((20.0f * p - 11.125f) * c5) / 2.0f + 1.0f;
|
||||
}
|
||||
|
||||
public static float InBounce(float p)
|
||||
{
|
||||
return 1 - OutBounce(1 - p);
|
||||
}
|
||||
|
||||
public static float OutBounce(float p)
|
||||
{
|
||||
const float n1 = 7.5625f;
|
||||
const float d1 = 2.75f;
|
||||
|
||||
if (p < 1 / d1) return n1 * p * p;
|
||||
|
||||
if (p < 2 / d1) return n1 * (p -= 1.5f / d1) * p + 0.75f;
|
||||
|
||||
if (p < 2.5 / d1) return n1 * (p -= 2.25f / d1) * p + 0.9375f;
|
||||
|
||||
return n1 * (p -= 2.625f / d1) * p + 0.984375f;
|
||||
}
|
||||
|
||||
public static float InOutBounce(float p)
|
||||
{
|
||||
return p < 0.5
|
||||
? (1 - OutBounce(1 - 2 * p)) / 2
|
||||
: (1 + OutBounce(2 * p - 1)) / 2;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
29
Robust.Shared/Analyzers/ComponentPauseGeneratorAttributes.cs
Normal file
29
Robust.Shared/Analyzers/ComponentPauseGeneratorAttributes.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
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;
|
||||
@@ -50,17 +50,11 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
base.Initialize();
|
||||
InitializeEffect();
|
||||
ZOffset = CfgManager.GetCVar(CVars.AudioZOffset);
|
||||
CfgManager.OnValueChanged(CVars.AudioZOffset, SetZOffset);
|
||||
Subs.CVar(CfgManager, CVars.AudioZOffset, SetZOffset);
|
||||
SubscribeLocalEvent<AudioComponent, ComponentGetStateAttemptEvent>(OnAudioGetStateAttempt);
|
||||
SubscribeLocalEvent<AudioComponent, EntityUnpausedEvent>(OnAudioUnpaused);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
CfgManager.UnsubValueChanged(CVars.AudioZOffset, SetZOffset);
|
||||
}
|
||||
|
||||
protected void SetZOffset(float value)
|
||||
{
|
||||
ZOffset = value;
|
||||
|
||||
@@ -1239,7 +1239,7 @@ namespace Robust.Shared
|
||||
/// Enable Discord rich presence integration.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> DiscordEnabled =
|
||||
CVarDef.Create("discord.enabled", true, CVar.CLIENTONLY);
|
||||
CVarDef.Create("discord.enabled", true, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
public static readonly CVarDef<string> DiscordRichPresenceMainIconId =
|
||||
CVarDef.Create("discord.rich_main_icon_id", "devstation", CVar.SERVER | CVar.REPLICATED);
|
||||
@@ -1323,6 +1323,16 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<float> MidiVolume =
|
||||
CVarDef.Create("midi.volume", 0.50f, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// Controls amount of CPU cores and (by extension) polyphony for Fluidsynth.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You probably don't want to set this to be multithreaded, the way Fluidsynth's multithreading works is
|
||||
/// probably worse-than-nothing for Robust's usage.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<int> MidiParallelism =
|
||||
CVarDef.Create("midi.parallelism", 1, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/*
|
||||
* HUB
|
||||
* CVars related to public master server hub
|
||||
@@ -1487,14 +1497,14 @@ namespace Robust.Shared
|
||||
/// <summary>
|
||||
/// Maximum compressed size of a replay recording (in kilobytes) before recording automatically stops.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> ReplayMaxCompressedSize = CVarDef.Create("replay.max_compressed_size",
|
||||
1024 * 256, CVar.ARCHIVE);
|
||||
public static readonly CVarDef<long> ReplayMaxCompressedSize = CVarDef.Create("replay.max_compressed_size",
|
||||
1024L * 512, CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum uncompressed size of a replay recording (in kilobytes) before recording automatically stops.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> ReplayMaxUncompressedSize = CVarDef.Create("replay.max_uncompressed_size",
|
||||
1024 * 1024, CVar.ARCHIVE);
|
||||
public static readonly CVarDef<long> ReplayMaxUncompressedSize = CVarDef.Create("replay.max_uncompressed_size",
|
||||
1024L * 1024, CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// Uncompressed size of individual files created by the replay (in kilobytes), where each file contains data
|
||||
|
||||
@@ -6,7 +6,9 @@ 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;
|
||||
|
||||
@@ -50,6 +52,41 @@ 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>
|
||||
@@ -157,12 +194,10 @@ 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)
|
||||
{
|
||||
Count = size + 1;
|
||||
array![size] = item;
|
||||
AddNoResize(item, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -170,6 +205,14 @@ 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()
|
||||
{
|
||||
@@ -522,4 +565,46 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,10 +113,46 @@ namespace Robust.Shared.Configuration
|
||||
{
|
||||
var loaded = new HashSet<string>();
|
||||
|
||||
foreach (var (cvar, value) in ParseCVarValuesFromToml(stream))
|
||||
var callbackEvents = new ValueList<ValueChangedInvoke>();
|
||||
|
||||
// Ensure callbacks are raised OUTSIDE the write lock.
|
||||
using (Lock.WriteGuard())
|
||||
{
|
||||
loaded.Add(cvar);
|
||||
OverrideDefault(cvar, value);
|
||||
foreach (var (cVarName, value) in ParseCVarValuesFromToml(stream))
|
||||
{
|
||||
if (!_configVars.TryGetValue(cVarName, out var cVar) || !cVar.Registered)
|
||||
{
|
||||
_sawmill.Error($"Trying to set unregistered variable '{cVarName}'");
|
||||
continue;
|
||||
}
|
||||
|
||||
var convertedValue = value;
|
||||
if (!cVar.DefaultValue.GetType().IsEnum && cVar.DefaultValue.GetType() != value.GetType())
|
||||
{
|
||||
try
|
||||
{
|
||||
convertedValue = ConvertToCVarType(value, cVar.DefaultValue.GetType());
|
||||
}
|
||||
catch
|
||||
{
|
||||
_sawmill.Error($"Override TOML parsed cvar does not match registered cvar type. Name: {cVarName}. Code Type: {cVar.DefaultValue.GetType()}. Toml type: {value.GetType()}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
cVar.DefaultValue = value;
|
||||
|
||||
if (cVar.OverrideValue == null && cVar.Value == null)
|
||||
{
|
||||
if (SetupInvokeValueChanged(cVar, convertedValue) is { } invoke)
|
||||
callbackEvents.Add(invoke);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var callback in callbackEvents)
|
||||
{
|
||||
InvokeValueChanged(callback);
|
||||
}
|
||||
|
||||
return loaded;
|
||||
@@ -303,8 +339,7 @@ namespace Robust.Shared.Configuration
|
||||
{
|
||||
try
|
||||
{
|
||||
// try convert thing like int to float.
|
||||
cVar.Value = Convert.ChangeType(cVar.Value, type);
|
||||
cVar.Value = ConvertToCVarType(cVar.Value, type);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -711,6 +746,26 @@ namespace Robust.Shared.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to convert a compatible value to the actual registration type of a CVar.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When CVars are parsed from TOML, their in-code type is not known.
|
||||
/// This function does the necessary conversions from e.g. int to long.
|
||||
/// </remarks>
|
||||
/// <param name="value">
|
||||
/// The value to convert.
|
||||
/// This must be a simple type like strings or integers.
|
||||
/// </param>
|
||||
/// <param name="cVar">
|
||||
/// The registration type of the CVar.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
private static object ConvertToCVarType(object value, Type cVar)
|
||||
{
|
||||
return Convert.ChangeType(value, cVar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the data for a single configuration variable.
|
||||
/// </summary>
|
||||
|
||||
@@ -73,6 +73,9 @@ WhitelistedNamespaces:
|
||||
# * The API is not *relevant* to content. e.g. System.Type.IsAnsiClass.
|
||||
# * I am lazy these API lists are huge dude.
|
||||
Types:
|
||||
Avalonia.Metadata:
|
||||
XmlnsDefinitionAttribute:
|
||||
All: True
|
||||
NetSerializer:
|
||||
NetListAsArray`1:
|
||||
Fields:
|
||||
@@ -626,6 +629,7 @@ Types:
|
||||
- "System.Text.Encoding get_UTF7()"
|
||||
- "System.Text.Encoding get_UTF8()"
|
||||
- "System.Text.Encoding get_UTF32()"
|
||||
- "System.Text.Encoding GetEncoding(string)"
|
||||
NormalizationForm: { } # Enum
|
||||
Rune: { All: True }
|
||||
StringBuilder:
|
||||
@@ -716,6 +720,9 @@ Types:
|
||||
ChunkEnumerator: { All: True }
|
||||
AppendInterpolatedStringHandler: { All: True }
|
||||
StringRuneEnumerator: { All: True }
|
||||
System.Text.Unicode:
|
||||
UnicodeRange: { All: True }
|
||||
UnicodeRanges: { All: True }
|
||||
System.Threading.Tasks:
|
||||
Task: { All: True }
|
||||
Task`1: { All: True }
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -279,6 +278,66 @@ 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);
|
||||
@@ -324,6 +383,72 @@ 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)
|
||||
{
|
||||
@@ -467,6 +592,71 @@ 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)
|
||||
{
|
||||
|
||||
@@ -1049,6 +1049,42 @@ 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>
|
||||
@@ -1184,6 +1220,96 @@ 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)
|
||||
{
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Collections;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public abstract partial class EntitySystem
|
||||
{
|
||||
private List<SubBase>? _subscriptions;
|
||||
private ValueList<SubBase> _subscriptions;
|
||||
|
||||
/// <summary>
|
||||
/// A handle to allow subscription on this entity system's behalf.
|
||||
@@ -84,7 +84,6 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
EntityManager.EventBus.SubscribeEvent(src, this, handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubBroadcast<T>(src));
|
||||
}
|
||||
|
||||
@@ -96,7 +95,6 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
EntityManager.EventBus.SubscribeEvent(src, this, handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubBroadcast<T>(src));
|
||||
}
|
||||
|
||||
@@ -108,7 +106,6 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
EntityManager.EventBus.SubscribeSessionEvent(src, this, handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubBroadcast<EntitySessionMessage<T>>(src));
|
||||
}
|
||||
|
||||
@@ -122,7 +119,6 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
EntityManager.EventBus.SubscribeLocalEvent(handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubLocal<TComp, TEvent>());
|
||||
}
|
||||
|
||||
@@ -134,7 +130,6 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
EntityManager.EventBus.SubscribeLocalEvent(handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubLocal<TComp, TEvent>());
|
||||
}
|
||||
|
||||
@@ -146,21 +141,17 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
EntityManager.EventBus.SubscribeLocalEvent(handler, GetType(), before, after);
|
||||
|
||||
_subscriptions ??= new();
|
||||
_subscriptions.Add(new SubLocal<TComp, TEvent>());
|
||||
}
|
||||
|
||||
private void ShutdownSubscriptions()
|
||||
{
|
||||
if (_subscriptions == null)
|
||||
return;
|
||||
|
||||
foreach (var sub in _subscriptions)
|
||||
{
|
||||
sub.Unsubscribe(this, EntityManager.EventBus);
|
||||
}
|
||||
|
||||
_subscriptions = null;
|
||||
_subscriptions = default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -232,6 +223,19 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
System.SubscribeLocalEvent(handler, before, after);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an action to be ran when this entity system is shut down.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used by extension methods for <see cref="Subscriptions"/>
|
||||
/// to unsubscribe from from external sources such as CVars.
|
||||
/// </remarks>
|
||||
/// <param name="action">An action to be ran when the entity system is shut down.</param>
|
||||
public void RegisterUnsubscription(Action action)
|
||||
{
|
||||
System._subscriptions.Add(new SubAction(action));
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class SubBase
|
||||
@@ -261,5 +265,13 @@ namespace Robust.Shared.GameObjects
|
||||
bus.UnsubscribeLocalEvent<TComp, TBase>();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SubAction(Action action) : SubBase
|
||||
{
|
||||
public override void Unsubscribe(EntitySystem sys, IEventBus bus)
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -41,8 +42,16 @@ namespace Robust.Shared.GameObjects
|
||||
name = name.Substring(0, name.Length - "System".Length);
|
||||
|
||||
// Convert CamelCase to snake_case
|
||||
name = string.Concat(name.Select(x => char.IsUpper(x) ? $"_{char.ToLower(x)}" : x.ToString()));
|
||||
name = name.Trim('_');
|
||||
// Ignore if all uppercase, assume acronym (e.g. NPC or HTN)
|
||||
if (name.All(char.IsUpper))
|
||||
{
|
||||
name = name.ToLower(CultureInfo.InvariantCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
name = string.Concat(name.Select(x => char.IsUpper(x) ? $"_{char.ToLower(x)}" : x.ToString()));
|
||||
name = name.Trim('_');
|
||||
}
|
||||
|
||||
return $"system.{name}";
|
||||
}
|
||||
|
||||
130
Robust.Shared/GameObjects/EntitySystemSubscriptionExt.cs
Normal file
130
Robust.Shared/GameObjects/EntitySystemSubscriptionExt.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Extra subscription helpers for <see cref="EntitySystem"/> that are not part of the core entity system behavior.
|
||||
/// </summary>
|
||||
public static class EntitySystemSubscriptionExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Listen for an event for if the config value changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is an O(n) operation.
|
||||
/// </remarks>
|
||||
/// <param name="subs">
|
||||
/// The entity system subscriptions.
|
||||
/// Call this with <see cref="EntitySystem.Subscriptions"/>.
|
||||
/// </param>
|
||||
/// <param name="cfg">The configuration manager.</param>
|
||||
/// <param name="name">The name of the CVar to listen for.</param>
|
||||
/// <param name="onValueChanged">The delegate to run when the value was changed.</param>
|
||||
/// <param name="invokeImmediately">
|
||||
/// Whether to run the callback immediately inw this method. Can help reduce boilerplate
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
|
||||
public static void CVar<T>(
|
||||
this EntitySystem.Subscriptions subs,
|
||||
IConfigurationManager cfg,
|
||||
string name,
|
||||
Action<T> onValueChanged,
|
||||
bool invokeImmediately = false)
|
||||
where T : notnull
|
||||
{
|
||||
cfg.OnValueChanged(name, onValueChanged, invokeImmediately);
|
||||
|
||||
subs.RegisterUnsubscription(() => cfg.UnsubValueChanged(name, onValueChanged));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Listen for an event for if the config value changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is an O(n) operation.
|
||||
/// </remarks>
|
||||
/// <param name="subs">
|
||||
/// The entity system subscriptions.
|
||||
/// Call this with <see cref="EntitySystem.Subscriptions"/>.
|
||||
/// </param>
|
||||
/// <param name="cfg">The configuration manager.</param>
|
||||
/// <param name="cVar">The CVar to listen for.</param>
|
||||
/// <param name="onValueChanged">The delegate to run when the value was changed.</param>
|
||||
/// <param name="invokeImmediately">
|
||||
/// Whether to run the callback immediately in this method. Can help reduce boilerplate
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
|
||||
public static void CVar<T>(
|
||||
this EntitySystem.Subscriptions subs,
|
||||
IConfigurationManager cfg,
|
||||
CVarDef<T> cVar,
|
||||
Action<T> onValueChanged,
|
||||
bool invokeImmediately = false)
|
||||
where T : notnull
|
||||
{
|
||||
cfg.OnValueChanged(cVar, onValueChanged, invokeImmediately);
|
||||
|
||||
subs.RegisterUnsubscription(() => cfg.UnsubValueChanged(cVar, onValueChanged));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Listen for an event for if the config value changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is an O(n) operation.
|
||||
/// </remarks>
|
||||
/// <param name="subs">
|
||||
/// The entity system subscriptions.
|
||||
/// Call this with <see cref="EntitySystem.Subscriptions"/>.
|
||||
/// </param>
|
||||
/// <param name="cfg">The configuration manager.</param>
|
||||
/// <param name="name">The name of the CVar to listen for.</param>
|
||||
/// <param name="onValueChanged">The delegate to run when the value was changed.</param>
|
||||
/// <param name="invokeImmediately">
|
||||
/// Whether to run the callback immediately in this method. Can help reduce boilerplate
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
|
||||
public static void CVar<T>(
|
||||
this EntitySystem.Subscriptions subs,
|
||||
IConfigurationManager cfg,
|
||||
string name,
|
||||
CVarChanged<T> onValueChanged,
|
||||
bool invokeImmediately = false)
|
||||
where T : notnull
|
||||
{
|
||||
cfg.OnValueChanged(name, onValueChanged, invokeImmediately);
|
||||
|
||||
subs.RegisterUnsubscription(() => cfg.UnsubValueChanged(name, onValueChanged));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Listen for an event for if the config value changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is an O(n) operation.
|
||||
/// </remarks>
|
||||
/// <param name="subs">
|
||||
/// The entity system subscriptions.
|
||||
/// Call this with <see cref="EntitySystem.Subscriptions"/>.
|
||||
/// </param>
|
||||
/// <param name="cfg">The configuration manager.</param>
|
||||
/// <param name="cVar">The CVar to listen for.</param>
|
||||
/// <param name="onValueChanged">The delegate to run when the value was changed.</param>
|
||||
/// <param name="invokeImmediately">
|
||||
/// Whether to run the callback immediately in this method. Can help reduce boilerplate
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
|
||||
public static void CVar<T>(
|
||||
this EntitySystem.Subscriptions subs,
|
||||
IConfigurationManager cfg,
|
||||
CVarDef<T> cVar,
|
||||
CVarChanged<T> onValueChanged,
|
||||
bool invokeImmediately = false)
|
||||
where T : notnull
|
||||
{
|
||||
cfg.OnValueChanged(cVar, onValueChanged, invokeImmediately);
|
||||
|
||||
subs.RegisterUnsubscription(() => cfg.UnsubValueChanged(cVar, onValueChanged));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.Map;
|
||||
@@ -107,6 +106,16 @@ 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>
|
||||
@@ -117,6 +126,11 @@ 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>
|
||||
@@ -128,15 +142,40 @@ public partial interface IEntityManager
|
||||
public List<NetEntity?> GetNetEntityList(List<EntityUid?> entities);
|
||||
|
||||
/// <summary>
|
||||
/// List version of <see cref="GetNetEntity"/>
|
||||
/// Array version of <see cref="GetNetEntity"/>
|
||||
/// </summary>
|
||||
NetEntity[] GetNetEntityArray(EntityUid[] entities);
|
||||
|
||||
/// <summary>
|
||||
/// List version of <see cref="GetNetEntity"/>
|
||||
/// Array 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>
|
||||
@@ -178,6 +217,27 @@ 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);
|
||||
|
||||
@@ -703,16 +703,75 @@ public sealed partial class EntityLookupSystem
|
||||
GetEntitiesIntersecting(mapId, shape, transform, entities, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets entities with the specified component with the specified map.
|
||||
/// </summary>
|
||||
public void GetEntitiesOnMap<TComp1>(MapId mapId, HashSet<Entity<TComp1>> entities) where TComp1 : IComponent
|
||||
{
|
||||
var query = AllEntityQuery<TComp1, TransformComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp, out var xform))
|
||||
{
|
||||
if (xform.MapID != mapId)
|
||||
continue;
|
||||
|
||||
entities.Add((uid, comp));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets entities with the specified component with the specified parent.
|
||||
/// </summary>
|
||||
public void GetEntitiesOnMap<TComp1, TComp2>(MapId mapId, HashSet<Entity<TComp1, TComp2>> entities)
|
||||
where TComp1 : IComponent
|
||||
where TComp2 : IComponent
|
||||
{
|
||||
var query = AllEntityQuery<TComp1, TComp2, TransformComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp, out var comp2, out var xform))
|
||||
{
|
||||
if (xform.MapID != mapId)
|
||||
continue;
|
||||
|
||||
entities.Add((uid, comp, comp2));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private readonly record struct GridQueryCompState<T>(
|
||||
HashSet<Entity<IComponent>> Intersecting,
|
||||
IPhysShape Shape,
|
||||
Transform Transform,
|
||||
EntityLookupSystem Lookup,
|
||||
LookupFlags Flags,
|
||||
EntityQuery<T> Query
|
||||
) where T : IComponent;
|
||||
/// <summary>
|
||||
/// Gets entities with the specified component with the specified parent.
|
||||
/// </summary>
|
||||
public void GetChildEntities<TComp1>(EntityUid parentUid, HashSet<Entity<TComp1>> entities) where TComp1 : IComponent
|
||||
{
|
||||
var query = AllEntityQuery<TComp1, TransformComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp, out var xform))
|
||||
{
|
||||
if (xform.ParentUid != parentUid)
|
||||
continue;
|
||||
|
||||
entities.Add((uid, comp));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets entities with the specified component with the specified parent.
|
||||
/// </summary>
|
||||
public void GetChildEntities<TComp1, TComp2>(EntityUid parentUid, HashSet<Entity<TComp1, TComp2>> entities)
|
||||
where TComp1 : IComponent
|
||||
where TComp2 : IComponent
|
||||
{
|
||||
var query = AllEntityQuery<TComp1, TComp2, TransformComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var comp, out var comp2, out var xform))
|
||||
{
|
||||
if (xform.ParentUid != parentUid)
|
||||
continue;
|
||||
|
||||
entities.Add((uid, comp, comp2));
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct GridQueryState<T>(
|
||||
HashSet<Entity<T>> Intersecting,
|
||||
|
||||
@@ -36,8 +36,8 @@ namespace Robust.Shared.GameObjects
|
||||
base.Initialize();
|
||||
UpdatesBefore.Add(typeof(SharedBroadphaseSystem));
|
||||
|
||||
_cfg.OnValueChanged(CVars.GenerateGridFixtures, SetEnabled, true);
|
||||
_cfg.OnValueChanged(CVars.GridFixtureEnlargement, SetEnlargement, true);
|
||||
Subs.CVar(_cfg, CVars.GenerateGridFixtures, SetEnabled, true);
|
||||
Subs.CVar(_cfg, CVars.GridFixtureEnlargement, SetEnlargement, true);
|
||||
|
||||
SubscribeLocalEvent<GridInitializeEvent>(OnGridInit);
|
||||
SubscribeLocalEvent<RegenerateGridBoundsEvent>(OnGridBoundsRegenerate);
|
||||
@@ -58,14 +58,6 @@ namespace Robust.Shared.GameObjects
|
||||
_map.RegenerateCollision(ev.EntityUid, grid, _map.GetMapChunks(ev.EntityUid, grid).Values.ToHashSet());
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_cfg.UnsubValueChanged(CVars.GenerateGridFixtures, SetEnabled);
|
||||
_cfg.UnsubValueChanged(CVars.GridFixtureEnlargement, SetEnlargement);
|
||||
}
|
||||
|
||||
private void SetEnabled(bool value) => _enabled = value;
|
||||
|
||||
private void SetEnlargement(float value) => _fixtureEnlargement = value;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public abstract partial class SharedMapSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// If the supplied coordinates intersects a grid will align with the tile center, otherwise returns the coordinates.
|
||||
/// </summary>
|
||||
/// <param name="coordinates"></param>
|
||||
/// <returns></returns>
|
||||
[Pure]
|
||||
public EntityCoordinates AlignToGrid(EntityCoordinates coordinates)
|
||||
{
|
||||
// Check if the parent is already a grid.
|
||||
if (_gridQuery.TryGetComponent(coordinates.EntityId, out var gridComponent))
|
||||
{
|
||||
var tile = CoordinatesToTile(coordinates.EntityId, gridComponent, coordinates);
|
||||
return ToCenterCoordinates(coordinates.EntityId, tile, gridComponent);
|
||||
}
|
||||
|
||||
// Check if mappos intersects a grid.
|
||||
var mapPos = coordinates.ToMap(EntityManager, _transform);
|
||||
|
||||
if (_mapInternal.TryFindGridAt(mapPos, out var gridUid, out gridComponent))
|
||||
{
|
||||
var tile = CoordinatesToTile(gridUid, gridComponent, coordinates);
|
||||
return ToCenterCoordinates(gridUid, tile, gridComponent);
|
||||
}
|
||||
|
||||
// No grid so just return it.
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a tileRef to EntityCoordinates.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public EntityCoordinates ToCoordinates(TileRef tileRef, MapGridComponent? gridComponent = null)
|
||||
{
|
||||
return ToCoordinates(tileRef.GridUid, tileRef.GridIndices, gridComponent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a tileRef to EntityCoordinates.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public EntityCoordinates ToCoordinates(EntityUid gridUid, Vector2i tile, MapGridComponent? gridComponent = null)
|
||||
{
|
||||
if (!_gridQuery.Resolve(gridUid, ref gridComponent))
|
||||
{
|
||||
return EntityCoordinates.Invalid;
|
||||
}
|
||||
|
||||
return new EntityCoordinates(gridUid, tile * gridComponent.TileSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a tileRef to EntityCoordinates for the center of the tile.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public EntityCoordinates ToCenterCoordinates(TileRef tileRef, MapGridComponent? gridComponent = null)
|
||||
{
|
||||
return ToCenterCoordinates(tileRef.GridUid, tileRef.GridIndices, gridComponent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a tileRef to EntityCoordinates for the center of the tile.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public EntityCoordinates ToCenterCoordinates(EntityUid gridUid, Vector2i tile, MapGridComponent? gridComponent = null)
|
||||
{
|
||||
if (!_gridQuery.Resolve(gridUid, ref gridComponent))
|
||||
{
|
||||
return EntityCoordinates.Invalid;
|
||||
}
|
||||
|
||||
return new EntityCoordinates(gridUid, tile * gridComponent.TileSize + gridComponent.TileSizeHalfVector);
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,31 @@ public abstract partial class SharedMapSystem
|
||||
SubscribeLocalEvent<MapGridComponent, MoveEvent>(OnGridMove);
|
||||
}
|
||||
|
||||
public void OnGridBoundsChange(EntityUid uid, MapGridComponent component)
|
||||
/// <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)
|
||||
{
|
||||
// Just MapLoader things.
|
||||
if (component.MapProxy == DynamicTree.Proxy.Free) return;
|
||||
@@ -189,11 +213,11 @@ public abstract partial class SharedMapSystem
|
||||
if (xform.MapUid == null && meta.EntityLifeStage < EntityLifeStage.Terminating && _netManager.IsServer)
|
||||
Log.Error($"Grid {ToPrettyString(uid, meta)} was moved to nullspace! AAAAAAAAAAAAAAAAAAAAAAAAA! {Environment.StackTrace}");
|
||||
|
||||
DebugTools.Assert(!HasComp<MapComponent>(uid));
|
||||
DebugTools.Assert(!_mapQuery.HasComponent(uid));
|
||||
|
||||
if (xform.ParentUid != xform.MapUid && meta.EntityLifeStage < EntityLifeStage.Terminating && _netManager.IsServer)
|
||||
{
|
||||
Log.Error($"Grid {ToPrettyString(uid, meta)} it not parented to a map. y'all need jesus. {Environment.StackTrace}");
|
||||
Log.Error($"Grid {ToPrettyString(uid, meta)} is not parented to {ToPrettyString(xform._parent)} which is not a map. y'all need jesus. {Environment.StackTrace}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -454,7 +478,7 @@ public abstract partial class SharedMapSystem
|
||||
if (xform.MapUid != null && xform.MapUid != uid)
|
||||
_transform.SetParent(uid, xform, xform.MapUid.Value);
|
||||
|
||||
if (!HasComp<MapComponent>(uid))
|
||||
if (!_mapQuery.HasComponent(uid))
|
||||
{
|
||||
var aabb = GetWorldAABB(uid, component);
|
||||
|
||||
@@ -506,7 +530,7 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
private void AddGrid(EntityUid uid, MapGridComponent grid)
|
||||
{
|
||||
DebugTools.Assert(!EntityManager.HasComponent<MapComponent>(uid));
|
||||
DebugTools.Assert(!_mapQuery.HasComponent(uid));
|
||||
var aabb = GetWorldAABB(uid, grid);
|
||||
|
||||
if (!_xformQuery.TryGetComponent(uid, out var xform))
|
||||
@@ -974,19 +998,34 @@ public abstract partial class SharedMapSystem
|
||||
internal ChunkEnumerator GetMapChunks(EntityUid uid, MapGridComponent grid, Box2 worldAABB)
|
||||
{
|
||||
var localAABB = _transform.GetInvWorldMatrix(uid).TransformBox(worldAABB);
|
||||
return new ChunkEnumerator(grid.Chunks, localAABB, grid.ChunkSize);
|
||||
return GetLocalMapChunks(uid, grid, localAABB);
|
||||
}
|
||||
|
||||
internal ChunkEnumerator GetMapChunks(EntityUid uid, MapGridComponent grid, Box2Rotated worldArea)
|
||||
{
|
||||
var matrix = _transform.GetInvWorldMatrix(uid);
|
||||
var localArea = matrix.TransformBox(worldArea);
|
||||
return new ChunkEnumerator(grid.Chunks, localArea, grid.ChunkSize);
|
||||
return GetLocalMapChunks(uid, grid, localArea);
|
||||
}
|
||||
|
||||
internal ChunkEnumerator GetLocalMapChunks(EntityUid uid, MapGridComponent grid, Box2 localAABB)
|
||||
{
|
||||
return new ChunkEnumerator(grid.Chunks, localAABB, grid.ChunkSize);
|
||||
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);
|
||||
}
|
||||
|
||||
#endregion ChunkAccess
|
||||
@@ -1254,7 +1293,7 @@ public abstract partial class SharedMapSystem
|
||||
$"Grid {uid} is on map {mapId}, but coords are on map {posWorld.MapId}.",
|
||||
nameof(posWorld));
|
||||
|
||||
if (!TryComp<MapGridComponent>(uid, out var grid))
|
||||
if (!_gridQuery.TryGetComponent(uid, out var grid))
|
||||
{
|
||||
return new EntityCoordinates(MapManager.GetMapEntityId(posWorld.MapId), new Vector2(posWorld.X, posWorld.Y));
|
||||
}
|
||||
@@ -1348,8 +1387,36 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
public EntityCoordinates GridTileToLocal(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
|
||||
{
|
||||
return new(uid,
|
||||
new Vector2(gridTile.X * grid.TileSize + (grid.TileSize / 2f), gridTile.Y * grid.TileSize + (grid.TileSize / 2f)));
|
||||
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;
|
||||
}
|
||||
|
||||
public Vector2 GridTileToWorldPos(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user