Compare commits

..

2 Commits

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

View File

@@ -27,8 +27,7 @@ jobs:
run: dotnet restore
- name: Build
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
- name: Robust.UnitTesting
- name: Test Engine
run: dotnet test --no-build Robust.UnitTesting/Robust.UnitTesting.csproj -- NUnit.ConsoleOut=0
- name: Robust.Analyzers.Tests
run: dotnet test --no-build Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj -- NUnit.ConsoleOut=0

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
# Release notes for RobustToolbox.
# Release notes for RobustToolbox.
<!--
NOTE: automatically updated sometimes by version.py.
@@ -54,385 +54,7 @@ END TEMPLATE-->
*None yet*
## 218.2.2
## 218.2.1
## 218.2.0
### New features
* Control layout properties such as `Margin` can now be set via style sheets.
* Expose worldposition in SpriteComponent.Render
* Network audio entity Play/Pause/Stop states and playback position.
* Add `Disabled` functionality to `Slider` control.
## 218.1.0
### New features
* Add IEquatable.Equals to the sandbox.
* Enable roslyn extensions tests in CI.
* Add a VerticalTabContainer control to match the horizontal one.
### Bugfixes
* Fix divison remainder issue for Colors, fixing purples.
### Other
* Default hub address (`hub.hub_urls`) has been changed to `https://hub.spacestation14.com/`.
## 218.0.0
### Breaking changes
* `Robust.Shared.Configuration.EnvironmentVariables` is now internal and no longer usable by content.
### New features
* Add TryGetRandom to EntityManager to get a random entity with the specified component and TryGetRandom to IPrototypeManager to return a random prototype of the specified type.
* Add CopyData to AppearanceSystem.
* Update UI themes on prototype reloads.
* Allow scaling the line height of a RichTextLabel.
* You can now specify CVar overrides via environment variable with the `ROBUST_CVAR_*` prefix. For example `ROBUST_CVAR_game__hostname=foobar` would set the appropriate CVar. Double underscores in the environment variable name are replaced with ".".
* Added non-generic variant of `GetCVar` to `IConfigurationManager`.
* Add type tracking to FieldNotFoundErrorNode for serialization.
* Distance between lines of a `RichTextLabel` can now be modified with `LineHeightScale`.
* UI theme prototypes are now updated when reloaded.
* New `RA0025` analyzer diagnostic warns for manual assignment to `[Dependency]` fields.
### Bugfixes
* Request headers in `IStatusHandlerContext` are now case-insensitive.
* SetWorldPosition rotation now more closely aligns with prior behavior.
* Fix exception when inspecting elements in some cases.
* Fix HTTP errors on watchdog ping not being reported.
### Other
* Add an analyzer for redundantly assigning to dependency fields.
### Internal
* Remove redundant Exists checks in ContainerSystem.
* Improve logging on watchdog pings.
## 217.2.1
### Bugfixes
* Fix LineEdit tests on engine.
### Internal
* Make various ValueList enumerators access the span directly for performance.
## 217.2.0
### New features
* Added `AddComponents` and `RemoveComponents` methods to EntityManager that handle EntityPrototype / ComponentRegistry bulk component changes.
* Add double-clicking to LineEdit.
### Bugfixes
* Properly ignore non-hard fixtures for IntersectRayWithPredicate.
* Fix nullable TimeSpan addition on some platforms.
## 217.1.0
### New features
* Added `IRobustRandom.GetItems` extension methods for randomly picking multiple items from a collections.
* Added `SharedPhysicsSystem.EffectiveCurTime`. This is effectively a variation of `IGameTiming.CurTime` that takes into account the current physics sub-step.
### Bugfixes
* Fix `MapComponent.LightingEnabled` not leaving FOV rendering in a broken state.
### Internal
* `Shuffle<T>(Span<T>, System.Random)` has been removed, just use the builtin method.
## 217.0.0
### Breaking changes
* TransformSystem.SetWorldPosition and SetWorldPositionRotation will now also perform parent updates as necessary. Previously it would just set the entity's LocalPosition which may break if they were inside of a container. Now they will be removed from their container and TryFindGridAt will run to correctly parent them to the new position. If the old functionality is desired then you can use GetInvWorldMatrix to update the LocalPosition (bearing in mind containers may prevent this).
### New features
* Implement VV for AudioParams on SoundSpecifiers.
* Add AddUi to the shared UI system.
### Bugfixes
* Fix the first measure of ScrollContainer bars.
## 216.0.0
### Breaking changes
* The `net.low_lod_distance` cvar has been replaced with a new `net.pvs_priority_range`. Instead of limiting the range at which all entities are sent to a player, it now extends the range at which high priorities can be sent. The default value of this new cvar is 32.5, which is larger than the default `net.pvs_range` value of 25.
### New features
* You can now specify a component to not be saved to map files with `[UnsavedComponent]`.
* Added `ITileDefinitionManager.TryGetDefinition`.
* The map loader now tries to preserve the `tilemap` contents of map files, which should reduce diffs when re-saving a map after the game's internal tile IDs have changed.
### Bugfixes
* Fix buffered audio sources not being disposed.
## 215.3.1
### Bugfixes
* Revert zstd update.
## 215.3.0
### New features
* `EntityQuery<T>` now has `HasComp` and `TryComp` methods that are shorter than its existing ones.
* Added `PlacementInformation.UseEditorContext`.
* Added `Vector2Helpers` functions for comparing ranges between vectors.
### Bugfixes
* `Texture.GetPixel()`: fixed off-by-one with Y coordinate.
* `Texture.GetPixel()`: fix stack overflow when reading large images.
* `Texture.GetPixel()`: use more widely compatible OpenGL calls.
### Other
* Disabled `net.mtu_expand` again by default, as it was causing issues.
* Updated `SharpZstd` dependency.
## 215.2.0
### New features
* Implement basic VV for SoundSpecifiers.
### Bugfixes
* Fix QueueDel during EndCollideEvents from throwing while removing contacts.
## 215.1.0
### New features
* Add a CompletionHelper for audio filepaths that handles server packaging.
* Add Random.NextAngle(min, max) method and Pick for `ValueList<T>`.
* Added an `ICommonSession` parser for toolshed commands.
### Bugfixes
## 215.0.0
### Breaking changes
* Update Lidgren to 0.3.0
### New features
* Made a new `IMetricsManager` interface with an `UpdateMetrics` event that can be used to update Prometheus metrics whenever they are scraped.
* Also added a `metrics.update_interval` CVar to go along with this, when metrics are scraped without usage of Prometheus directly.
* IoC now contains an `IMeterFactory` implementation that you can use to instantiate metric meters.
* `net.mtu_ipv6` CVar allows specifying a different MTU value for IPv6.
* Allows `player:entity` to take a parameter representing the player name.
* Add collection parsing to the dev window for UI.
* Add a debug assert to Dirty(uid, comp) to catch mismatches being passed in.
### Bugfixes
* Support transform states with unknown parents.
* Fix serialization error logging.
* Fix naming of ResizableMemoryRegion metrics.
* Fix uncaught overflow exception when parsing NetEntities.
### Other
* The replay system now allows loading a replay with a mismatching serializer type hash. This means replays should be more robust against future version updates (engine security patches or .NET updates).
* `CheckBox`'s interior texture is now vertically centered.
* Lidgren.Network has been updated to [`v0.3.0`](https://github.com/space-wizards/SpaceWizards.Lidgren.Network/blob/v0.3.0/RELEASE-NOTES.md).
* Lowered default IPv4 MTU to 900 (from 1000).
* Automatic MTU expansion (`net.mtu_expand`) is now enabled by default.
### Internal
* Cleanup some Dirty component calls internally.
## 214.2.0
### New features
* Added a `Undetachable` entity metadata flag, which stops the client from moving an entity to nullspace when it moves out of PVS range.
### Bugfixes
* Fix tooltips not clamping to the left side of the viewport.
* Fix global audio property not being properly set.
### Internal
* The server game state / PVS code has been rewritten. It should be somewhat faster now, albeit at the cost of using more memory. The current engine version may be unstable.
## 214.1.1
### Bugfixes
* Fixed connection denial always causing redial.
## 214.1.0
### New features
* Added the `pvs_override_info` command for debugging PVS overrides.
### Bugfixes
* Fix VV for prototype structs.
* Fix audio limits for clientside audio.
## 214.0.0
### Breaking changes
* `NetStructuredDisconnectMessages` has received a complete overhaul and has been moved to `NetDisconnectMessage`. The API is no longer designed such that consumers must pass around JSON nodes, as they are not in sandbox (and clunky).
### New features
* Add a basic default concurrent audio limit of 16 for a single filepath to avoid overflowing audio sources.
* `NetConnectingArgs.Deny()` can now pass along structured data that will be received by the client.
### Bugfixes
* Fixed cursor position bugs when an empty `TextEdit` has a multi-line place holder.
* Fixed empty `TextEdit` throwing exception if cursor is moved left.
## 213.0.0
### Breaking changes
* Remove obsoleted BaseContainer methods.
### New features
* Add EntityManager.RaiseSharedEvent where the event won't go to the attached client but will be predicted locally on their end.
* Add GetEntitiesInRange override that takes in EntityCoordinates and an EntityUid hashset.
### Bugfixes
* Check if a sprite entity is deleted before drawing in SpriteView.
## 212.2.0
### New features
* Add IsHardCollidable to SharedPhysicsSystem to determine if 2 entities would collide.
### Other
* Double the default maximum replay size.
## 212.1.0
### New features
* Add nullable methods for TryIndex / HasIndex on IPrototypeManager.
### Bugfixes
* Fix TextureRect alignment where the strech mode is KeepCentered.
## 212.0.1
### Bugfixes
* Fix passing array by `this` instead of by `ref`.
## 212.0.0
### Breaking changes
* Change Collapsible controls default orientations to Vertical.
### New features
* Expose the Label control for Collapsible controls.
* Add GetGridPosition that considers physics center-of-mass.
* Add TileToVector methods to get the LocalPosition of tile-coords (taking into account tile size).
* Add some more helper methods to PVS filters around EntityUids.
* Add support for Dictionary AutoNetworkedFields.
* Add EnsureLength method for arrays.
* Add PushMarkup to FormattedMessage.
* Add DrawPrimitives overload for `List<Vector2>`
* Add more ValueList ctors that are faster.
* Add ToMapCoordinates method for NetCoordinates.
### Other
* Remove ISerializationHooks obsoletion as they are useful in some rare cases.
### Internal
* Bump max pool size for robust jobs.
## 211.0.2
### Bugfixes
* Fix TextureRect scaling not handling UIScale correctly.
## 211.0.1
### Bugfixes
* Fix GridChunkEnumerator on maps.
## 211.0.0
### Breaking changes
* Moved ChunkIndicesEnumerator to engine and to a re-useable namespace at Robust.Shared/Maps.
### New features
* Added an Enlarged method for Box2Rotated.
### Internal
* Significantly optimise ChunkEnumerator / FindGridsIntersecting in certain use cases by intersecting the grid's AABB with the local AABB to avoid iterating dummy chunks.
## 210.1.2
## 210.1.1

View File

@@ -11,7 +11,6 @@ cmd-parse-failure-uid = {$arg} is not a valid entity UID.
cmd-parse-failure-mapid = {$arg} is not a valid MapId.
cmd-parse-failure-grid = {$arg} is not a valid grid.
cmd-parse-failure-entity-exist = UID {$arg} does not correspond to an existing entity.
cmd-parse-failure-session = There is no session with username: {$username}
cmd-error-file-not-found = Could not find file: {$file}.
cmd-error-dir-not-found = Could not find directory: {$dir}.
@@ -491,7 +490,7 @@ cmd-net_entityreport-help = Usage: net_entityreport
cmd-net_refresh-desc = Requests a full server state.
cmd-net_refresh-help = Usage: net_refresh
cmd-net_graph-desc = Toggles the net statistics panel.
cmd-net_graph-desc = Toggles the net statistics pannel.
cmd-net_graph-help = Usage: net_graph
cmd-net_watchent-desc = Dumps all network updates for an EntityId to the console.
@@ -567,9 +566,3 @@ cmd-reloadtiletextures-help = Usage: reloadtiletextures
cmd-audio_length-desc = Shows the length of an audio file
cmd-audio_length-help = Usage: audio_length { cmd-audio_length-arg-file-name }
cmd-audio_length-arg-file-name = <file name>
## PVS
cmd-pvs-override-info-desc = Prints information about any PVS overrides associated with an entity.
cmd-pvs-override-info-empty = Entity {$nuid} has no PVS overrides.
cmd-pvs-override-info-global = Entity {$nuid} has a global override.
cmd-pvs-override-info-clients = Entity {$nuid} has a session override for {$clients}.

View File

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

View File

@@ -10,18 +10,3 @@ view-variable-instance-entity-client-components-search-bar-placeholder = Search
view-variable-instance-entity-server-components-search-bar-placeholder = Search
view-variable-instance-entity-add-window-server-components = Add Component [S]
view-variable-instance-entity-add-window-client-components = Add Component [C]
## SoundSpecifier
vv-sound-none = None
vv-sound-path = Path
vv-sound-collection = Collection
vv-sound-volume = volume
vv-sound-pitch = Pitch
vv-sound-max-distance = Max Distance
vv-sound-rolloff-factor = Rolloff Factor
vv-sound-reference-distance = Reference Distance
vv-sound-loop = Loop
vv-sound-play-offset = Play Offset (s)
vv-sound-variation = Pitch variation

View File

@@ -20,16 +20,11 @@ public sealed class AccessAnalyzer_Test
{
TestState =
{
AdditionalReferences = { typeof(AccessAnalyzer).Assembly },
Sources = { code }
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.Analyzers.AccessAttribute.cs",
"Robust.Shared.Analyzers.AccessPermissions.cs"
);
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
test.TestState.ExpectedDiagnostics.AddRange(expected);

View File

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

View File

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

View File

@@ -1,58 +0,0 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.DependencyAssignAnalyzer>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
public sealed class DependencyAssignAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<DependencyAssignAnalyzer, NUnitVerifier>()
{
TestState =
{
Sources = { code }
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.IoC.DependencyAttribute.cs"
);
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
[Test]
public async Task Test()
{
const string code = """
using Robust.Shared.IoC;
public sealed class Foo
{
[Dependency]
private object? Field;
public Foo()
{
Field = "A";
}
}
""";
await Verifier(code,
// /0/Test0.cs(10,9): warning RA0025: Tried to assign to [Dependency] field 'Field'. Remove [Dependency] or inject it via field injection instead.
VerifyCS.Diagnostic().WithSpan(10, 9, 10, 20).WithArguments("Field"));
}
}

View File

@@ -1,18 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<SkipRobustAnalyzer>true</SkipRobustAnalyzer>
</PropertyGroup>
<Import Project="..\MSBuild\Robust.Properties.targets"/>
<Import Project="..\MSBuild\Robust.Engine.props"/>
<!-- Engine source files needed to make the tests work -->
<ItemGroup>
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" LogicalName="Robust.Shared.Analyzers.AccessAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" LogicalName="Robust.Shared.Analyzers.AccessPermissions.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
<PropertyGroup>
<IsPackable>false</IsPackable>
</PropertyGroup>
@@ -34,6 +23,5 @@
<ItemGroup>
<ProjectReference Include="..\Robust.Analyzers\Robust.Analyzers.csproj"/>
<ProjectReference Include="..\Robust.Serialization.Generator\Robust.Serialization.Generator.csproj" Aliases="SerializationGenerator" />
</ItemGroup>
</Project>

View File

@@ -1,22 +0,0 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Text;
namespace Robust.Analyzers.Tests;
public static class TestHelper
{
public static void AddEmbeddedSources(SolutionState state, params string[] embeddedFiles)
{
AddEmbeddedSources(state, (IEnumerable<string>) embeddedFiles);
}
public static void AddEmbeddedSources(SolutionState state, IEnumerable<string> embeddedFiles)
{
foreach (var fileName in embeddedFiles)
{
using var stream = typeof(AccessAnalyzer_Test).Assembly.GetManifestResourceStream(fileName)!;
state.Sources.Add((fileName, SourceText.From(stream)));
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,61 +0,0 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class DependencyAssignAnalyzer : DiagnosticAnalyzer
{
private const string DependencyAttributeType = "Robust.Shared.IoC.DependencyAttribute";
private static readonly DiagnosticDescriptor Rule = new (
Diagnostics.IdDependencyFieldAssigned,
"Assignment to dependency field",
"Tried to assign to [Dependency] field '{0}'. Remove [Dependency] or inject it via field injection instead.",
"Usage",
DiagnosticSeverity.Warning,
true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterOperationAction(CheckAssignment, OperationKind.SimpleAssignment);
}
private static void CheckAssignment(OperationAnalysisContext context)
{
if (context.Operation is not ISimpleAssignmentOperation assignment)
return;
if (assignment.Target is not IFieldReferenceOperation fieldRef)
return;
var field = fieldRef.Field;
var attributes = field.GetAttributes();
if (attributes.Length == 0)
return;
var depAttribute = context.Compilation.GetTypeByMetadataName(DependencyAttributeType);
if (!HasAttribute(attributes, depAttribute))
return;
context.ReportDiagnostic(Diagnostic.Create(Rule, assignment.Syntax.GetLocation(), field.Name));
}
private static bool HasAttribute(ImmutableArray<AttributeData> attributes, ISymbol symbol)
{
foreach (var attribute in attributes)
{
if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, symbol))
return true;
}
return false;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,30 +1,31 @@
<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" LinkBase="Implementations" />
<Compile Include="..\Robust.Shared\Analyzers\NotNullableFlagAttribute.cs" />
</ItemGroup>
<ItemGroup>
<!-- Needed for FriendAnalyzer. -->
<Compile Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" LinkBase="Implementations" />
<Compile Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" LinkBase="Implementations" />
<Compile Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" />
<Compile Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" />
</ItemGroup>
<ItemGroup>
<!-- Needed for PreferGenericVariantAnalyzer. -->
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" LinkBase="Implementations" />
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" />
</ItemGroup>
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
<PropertyGroup>
<Nullable>disable</Nullable>
<!--
Rider seems to get really confused with hot reload if we directly compile in the above-linked classes.
As such, they have an #if to change their namespace in this project.
-->
<DefineConstants>$(DefineConstants);ROBUST_ANALYZERS_IMPL</DefineConstants>
</PropertyGroup>
</Project>

View File

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

View File

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

View File

@@ -1,83 +0,0 @@
using System;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.Collections;
namespace Robust.Benchmarks.Collections;
[Virtual]
public class ValueListEnumerationBenchmarks
{
[Params(4, 16, 64)]
public int N { get; set; }
private sealed class Data(int i)
{
public readonly int I = i;
}
private ValueList<Data> _valueList;
private Data[] _array = default!;
[GlobalSetup]
public void Setup()
{
var list = new List<Data>(N);
for (var i = 0; i < N; i++)
{
list.Add(new(i));
}
_array = list.ToArray();
_valueList = new(list.ToArray());
}
[Benchmark]
public int ValueList()
{
var total = 0;
foreach (var ev in _valueList)
{
total += ev.I;
}
return total;
}
[Benchmark]
public int ValueListSpan()
{
var total = 0;
foreach (var ev in _valueList.Span)
{
total += ev.I;
}
return total;
}
[Benchmark]
public int Array()
{
var total = 0;
foreach (var ev in _array)
{
total += ev.I;
}
return total;
}
[Benchmark]
public int Span()
{
var total = 0;
foreach (var ev in _array.AsSpan())
{
total += ev.I;
}
return total;
}
}

View File

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

View File

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

View File

@@ -41,7 +41,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
[Dependency] private readonly IParallelManager _parMan = default!;
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
[Dependency] private readonly IAudioInternal _audio = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!;
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
@@ -52,7 +51,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
private EntityUid? _listenerGrid;
private UpdateAudioJob _updateAudioJob;
private EntityQuery<PhysicsComponent> _physicsQuery;
private float _maxRayLength;
@@ -110,7 +108,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
Subs.CVar(CfgManager, CVars.AudioAttenuation, OnAudioAttenuation, true);
Subs.CVar(CfgManager, CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
InitializeLimit();
}
private void OnAudioState(EntityUid uid, AudioComponent component, ref AfterAutoHandleStateEvent args)
@@ -126,33 +123,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
{
component.Source.SetAuxiliary(null);
}
switch (component.State)
{
case AudioState.Playing:
component.StartPlaying();
break;
case AudioState.Paused:
component.Pause();
break;
case AudioState.Stopped:
component.StopPlaying();
component.PlaybackPosition = 0f;
break;
}
// If playback position changed then update it.
if (!string.IsNullOrEmpty(component.FileName))
{
var position = (float) ((component.PauseTime ?? Timing.CurTime) - component.AudioStart).TotalSeconds;
var currentPosition = component.Source.PlaybackPosition;
var diff = Math.Abs(position - currentPosition);
if (diff > 0.1f)
{
component.PlaybackPosition = position;
}
}
}
/// <summary>
@@ -193,37 +163,26 @@ public sealed partial class AudioSystem : SharedAudioSystem
return;
}
SetupSource((uid, component), audioResource);
SetupSource(component, audioResource);
component.Loaded = true;
}
private void SetupSource(Entity<AudioComponent> entity, AudioResource audioResource, TimeSpan? length = null)
private void SetupSource(AudioComponent component, AudioResource audioResource, TimeSpan? length = null)
{
var component = entity.Comp;
var source = _audio.CreateAudioSource(audioResource);
if (TryAudioLimit(component.FileName))
if (source == null)
{
var newSource = _audio.CreateAudioSource(audioResource);
if (newSource == null)
{
Log.Error($"Error creating audio source for {audioResource}");
DebugTools.Assert(false);
}
else
{
component.Source = newSource;
}
Log.Error($"Error creating audio source for {audioResource}");
DebugTools.Assert(false);
source = component.Source;
}
if ((component.Flags & AudioFlags.GridAudio) != 0x0)
{
_metadata.SetFlag(entity.Owner, MetaDataFlags.Undetachable, true);
}
component.Source = source;
// Need to set all initial data for first frame.
ApplyAudioParams(component.Params, component);
component.Source.Global = component.Global;
source.Global = component.Global;
// Don't play until first frame so occlusion etc. are correct.
component.Gain = 0f;
@@ -243,8 +202,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
{
// Breaks with prediction?
component.Source.Dispose();
RemoveAudioLimit(component.FileName);
}
private void OnAudioAttenuation(int obj)
@@ -454,13 +411,13 @@ public sealed partial class AudioSystem : SharedAudioSystem
return false;
}
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityCoordinates coordinates,
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates,
AudioParams? audioParams = null)
{
return PlayStatic(filename, Filter.Local(), coordinates, true, audioParams);
}
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityUid uid, AudioParams? audioParams = null)
{
return PlayEntity(filename, Filter.Local(), uid, true, audioParams);
}
@@ -487,11 +444,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="audioParams"></param>
private (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, AudioParams? audioParams = null, bool recordReplay = true)
private (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, AudioParams? audioParams = null, bool recordReplay = true)
{
if (string.IsNullOrEmpty(filename))
return null;
if (recordReplay && _replayRecording.IsRecording)
{
_replayRecording.RecordReplayMessage(new PlayAudioGlobalMessage
@@ -523,11 +477,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
private (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true)
private (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true)
{
if (string.IsNullOrEmpty(filename))
return null;
if (recordReplay && _replayRecording.IsRecording)
{
_replayRecording.RecordReplayMessage(new PlayAudioEntityMessage
@@ -567,11 +518,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
private (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true)
private (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true)
{
if (string.IsNullOrEmpty(filename))
return null;
if (recordReplay && _replayRecording.IsRecording)
{
_replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage
@@ -605,65 +553,65 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
{
return PlayGlobal(filename, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null)
{
return PlayEntity(filename, entity, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
{
return PlayStatic(filename, coordinates, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null)
{
return PlayGlobal(filename, audioParams);
}
public override void LoadStream<T>(Entity<AudioComponent> entity, T stream)
public override void LoadStream<T>(AudioComponent component, T stream)
{
if (stream is AudioStream audioStream)
{
TryGetAudio(audioStream, out var audio);
SetupSource(entity, audio!, audioStream.Length);
entity.Comp.Loaded = true;
SetupSource(component, audio!, audioStream.Length);
component.Loaded = true;
}
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null)
{
return PlayGlobal(filename, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
{
return PlayEntity(filename, uid, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
{
return PlayEntity(filename, uid, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
return PlayStatic(filename, coordinates, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
return PlayStatic(filename, coordinates, audioParams);
}
@@ -673,7 +621,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
var audioP = audioParams ?? AudioParams.Default;
var entity = EntityManager.CreateEntityUninitialized("Audio", MapCoordinates.Nullspace);
var comp = SetupAudio(entity, null, audioP, stream.Length);
LoadStream((entity, comp), stream);
LoadStream(comp, stream);
EntityManager.InitializeAndStartEntity(entity);
var source = comp.Source;

View File

@@ -314,8 +314,6 @@ public abstract class BaseAudioSource : IAudioSource
set
{
_checkDisposed();
value = MathF.Max(value, 0f);
AL.Source(SourceHandle, ALSourcef.SecOffset, value);
Master._checkAlError($"Tried to set invalid playback position of {value:0.00}");
}

View File

@@ -294,6 +294,7 @@ namespace Robust.Client.Console.Commands
internal sealed class SnapGridGetCell : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _map = default!;
public override string Command => "sggcell";
@@ -319,7 +320,7 @@ namespace Robust.Client.Console.Commands
return;
}
if (_entManager.TryGetComponent<MapGridComponent>(_entManager.GetEntity(gridNet), out var grid))
if (_map.TryGetGrid(_entManager.GetEntity(gridNet), out var grid))
{
foreach (var entity in grid.GetAnchoredEntities(new Vector2i(
int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture),
@@ -428,6 +429,7 @@ namespace Robust.Client.Console.Commands
internal sealed class GridTileCount : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _map = default!;
public override string Command => "gridtc";
@@ -446,7 +448,7 @@ namespace Robust.Client.Console.Commands
return;
}
if (_entManager.TryGetComponent<MapGridComponent>(gridUid, out var grid))
if (_map.TryGetGrid(gridUid, out var grid))
{
shell.WriteLine(grid.GetAllTiles().Count().ToString());
}
@@ -553,7 +555,7 @@ namespace Robust.Client.Console.Commands
if (type != typeof(Control))
cname = $"Control > {cname}";
returnVal.GetOrNew(cname).Add((member.Name, GetMemberValue(member, control, ", ")));
returnVal.GetOrNew(cname).Add((member.Name, member.GetValue(control)?.ToString() ?? "null"));
}
foreach (var (attachedProperty, value) in control.AllAttachedProperties)
@@ -568,28 +570,6 @@ namespace Robust.Client.Console.Commands
}
return returnVal;
}
internal static string PropertyValuesString(Control control, string key)
{
var member = GetAllMembers(control).Find(m => m.Name == key);
return GetMemberValue(member, control, "\n", "\"{0}\"");
}
private static string GetMemberValue(MemberInfo? member, Control control, string separator, string
wrap = "{0}")
{
var value = member?.GetValue(control);
var o = value switch
{
ICollection<Control> controls => string.Join(separator,
controls.Select(ctrl => $"{ctrl.Name}({ctrl.GetType()})")),
ICollection<string> list => string.Join(separator, list),
null => null,
_ => value.ToString()
};
// Convert to quote surrounded string or null with no quotes
return o is not null ? string.Format(wrap, o) : "null";
}
}
internal sealed class SetClipboardCommand : LocalizedCommands

View File

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

View File

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

View File

@@ -612,8 +612,6 @@ namespace Robust.Client
{
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePostEngine, frameEventArgs);
}
_audio.FlushALDisposeQueues();
}
internal static void SetupLogging(

View File

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

View File

@@ -8,9 +8,11 @@ using System.Text;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared;
using Robust.Shared.Animations;
using Robust.Shared.ComponentTrees;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -1235,9 +1237,9 @@ namespace Robust.Client.GameObjects
public IEnumerable<ISpriteLayer> AllLayers => Layers;
// Lobby SpriteView rendering path
public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null, Vector2 position = default)
public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null)
{
RenderInternal(drawingHandle, eyeRotation, worldRotation, position, overrideDirection);
RenderInternal(drawingHandle, eyeRotation, worldRotation, Vector2.Zero, overrideDirection);
}
[DataField("noRot")] private bool _screenLock = false;

View File

@@ -107,7 +107,7 @@ namespace Robust.Client.GameObjects
toDelete.Add(id);
}
foreach (var dead in toDelete.Span)
foreach (var dead in toDelete)
{
component.Containers.Remove(dead);
}
@@ -142,7 +142,7 @@ namespace Robust.Client.GameObjects
toRemove.Add(entity);
}
foreach (var entity in toRemove.Span)
foreach (var entity in toRemove)
{
Remove(
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)),
@@ -162,7 +162,7 @@ namespace Robust.Client.GameObjects
removedExpected.Add(netEntity);
}
foreach (var entityUid in removedExpected.Span)
foreach (var entityUid in removedExpected)
{
RemoveExpectedEntity(entityUid, out _);
}

View File

@@ -26,7 +26,6 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IConsoleHost _conHost = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private ISawmill _sawmillInputContext = default!;
@@ -152,7 +151,7 @@ namespace Robust.Client.GameObjects
var pxform = Transform(pent);
var wPos = pxform.WorldPosition + new Vector2(float.Parse(args[2]), float.Parse(args[3]));
var coords = EntityCoordinates.FromMap(pent, new MapCoordinates(wPos, pxform.MapID), _transform, EntityManager);
var coords = EntityCoordinates.FromMap(EntityManager, pent, new MapCoordinates(wPos, pxform.MapID));
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);

View File

@@ -700,7 +700,7 @@ namespace Robust.Client.GameStates
{
using var _ = _timing.StartStateApplicationArea();
// TODO replays optimize this.
// TODO repays optimize this.
// This currently just saves game states as they are applied.
// However this is inefficient and may have redundant data.
// E.g., we may record states: [10 to 15] [11 to 16] *error* [0 to 18] [18 to 19] [18 to 20] ...
@@ -1120,7 +1120,7 @@ namespace Robust.Client.GameStates
continue;
}
if ((meta.Flags & (MetaDataFlags.Detached | MetaDataFlags.Undetachable)) != 0)
if ((meta.Flags & MetaDataFlags.Detached) != 0)
continue;
if (lastStateApplied.HasValue)
@@ -1138,7 +1138,7 @@ namespace Robust.Client.GameStates
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
metas.TryGetComponent(xform.ParentUid, out var containerMeta) &&
(containerMeta.Flags & MetaDataFlags.Detached) == 0 &&
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container))
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container, null, true))
{
containerSys.Remove((ent.Value, xform, meta), container, false, true);
}
@@ -1414,7 +1414,7 @@ namespace Robust.Client.GameStates
_entities.TryGetComponent(xform.ParentUid, out MetaDataComponent? containerMeta) &&
(containerMeta.Flags & MetaDataFlags.Detached) == 0)
{
containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container);
containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container, null, true);
}
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachParentToNull(uid, xform);

View File

@@ -11,7 +11,6 @@ using Robust.Shared;
using Robust.Shared.Enums;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Profiling;
using Robust.Shared.Utility;
@@ -515,9 +514,7 @@ namespace Robust.Client.Graphics.Clyde
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawLight && eye.DrawFov)
{
var mapUid = _mapManager.GetMapEntityId(eye.Position.MapId);
if (_entityManager.GetComponent<MapComponent>(mapUid).LightingEnabled)
ApplyFovToBuffer(viewport, eye);
ApplyFovToBuffer(viewport, eye);
}
}

View File

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

View File

@@ -649,30 +649,24 @@ namespace Robust.Client.Graphics.Clyde
return $"ClydeTexture: ({TextureId})";
}
public override unsafe Color GetPixel(int x, int y)
public override Color GetPixel(int x, int y)
{
if (!_clyde._loadedTextures.TryGetValue(TextureId, out var loaded))
{
throw new DataException("Texture not found");
}
var curTexture2D = GL.GetInteger(GetPName.TextureBinding2D);
var bufSize = 4 * loaded.Size.X * loaded.Size.Y;
var buffer = ArrayPool<byte>.Shared.Rent(bufSize);
GL.BindTexture(TextureTarget.Texture2D, loaded.OpenGLObject.Handle);
fixed (byte* p = buffer)
Span<byte> rgba = stackalloc byte[4*this.Size.X*this.Size.Y];
unsafe
{
GL.GetnTexImage(TextureTarget.Texture2D, 0, PF.Rgba, PT.UnsignedByte, bufSize, (IntPtr) p);
fixed (byte* p = rgba)
{
GL.GetTextureImage(loaded.OpenGLObject.Handle, 0, PF.Rgba, PT.UnsignedByte, 4*this.Size.X*this.Size.Y, (IntPtr) p);
}
}
GL.BindTexture(TextureTarget.Texture2D, curTexture2D);
var pixelPos = (loaded.Size.X * (loaded.Size.Y - y - 1) + x) * 4;
var color = new Color(buffer[pixelPos+0], buffer[pixelPos+1], buffer[pixelPos+2], buffer[pixelPos+3]);
ArrayPool<byte>.Shared.Return(buffer);
return color;
int pixelPos = (this.Size.X*(this.Size.Y-y) + x)*4;
return new Color(rgba[pixelPos+0], rgba[pixelPos+1], rgba[pixelPos+2], rgba[pixelPos+3]);
}
}

View File

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

View File

@@ -34,7 +34,7 @@ namespace Robust.Client.Placement.Modes
var snapToEntities = EntitySystem.Get<EntityLookupSystem>().GetEntitiesInRange(MouseCoords, SnapToRange)
.Where(entity => pManager.EntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype == pManager.CurrentPrototype && pManager.EntityManager.GetComponent<TransformComponent>(entity).MapID == mapId)
.OrderBy(entity => (pManager.EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - MouseCoords.ToMapPos(pManager.EntityManager, pManager.EntityManager.System<SharedTransformSystem>())).LengthSquared())
.OrderBy(entity => (pManager.EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - MouseCoords.ToMapPos(pManager.EntityManager)).LengthSquared())
.ToList();
if (snapToEntities.Count == 0)

View File

@@ -2,7 +2,6 @@ using System;
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
namespace Robust.Client.Placement.Modes
@@ -25,7 +24,7 @@ namespace Robust.Client.Placement.Modes
SnapSize = 1f;
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{
Grid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
Grid = pManager.MapManager.GetGrid(gridId);
SnapSize = Grid.TileSize; //Find snap size for the grid.
}
else

View File

@@ -56,7 +56,7 @@ namespace Robust.Client.Placement.Modes
SnapSize = 1f;
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{
Grid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
Grid = pManager.MapManager.GetGrid(gridId);
SnapSize = Grid.TileSize; //Find snap size for the grid.
}
else

View File

@@ -1,6 +1,5 @@
using System.Numerics;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
namespace Robust.Client.Placement.Modes
{
@@ -20,7 +19,7 @@ namespace Robust.Client.Placement.Modes
var gridId = MouseCoords.GetGridUid(pManager.EntityManager);
if (!pManager.EntityManager.TryGetComponent<MapGridComponent>(gridId, out var mapGrid))
if (!pManager.MapManager.TryGetGrid(gridId, out var mapGrid))
return;
CurrentTile = mapGrid.GetTileRef(MouseCoords);

View File

@@ -1,7 +1,6 @@
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
namespace Robust.Client.Placement.Modes
{
@@ -21,7 +20,7 @@ namespace Robust.Client.Placement.Modes
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{
var mapGrid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
var mapGrid = pManager.MapManager.GetGrid(gridId);
tileSize = mapGrid.TileSize; //convert from ushort to float
}

View File

@@ -2,7 +2,6 @@
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
namespace Robust.Client.Placement.Modes
@@ -23,7 +22,7 @@ namespace Robust.Client.Placement.Modes
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{
var mapGrid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
var mapGrid = pManager.MapManager.GetGrid(gridId);
CurrentTile = mapGrid.GetTileRef(MouseCoords);
tileSize = mapGrid.TileSize; //convert from ushort to float
}

View File

@@ -1,7 +1,6 @@
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
namespace Robust.Client.Placement.Modes
{
@@ -21,7 +20,7 @@ namespace Robust.Client.Placement.Modes
var gridIdOpt = MouseCoords.GetGridUid(pManager.EntityManager);
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{
var mapGrid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
var mapGrid = pManager.MapManager.GetGrid(gridId);
tileSize = mapGrid.TileSize; //convert from ushort to float
}

View File

@@ -21,7 +21,6 @@ using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.Log;
using Direction = Robust.Shared.Maths.Direction;
using Robust.Shared.Map.Components;
namespace Robust.Client.Placement
{
@@ -80,10 +79,6 @@ namespace Robust.Client.Placement
private set
{
_isActive = value;
if (CurrentPermission?.UseEditorContext is false)
return;
SwitchEditorContext(value);
}
}
@@ -337,7 +332,7 @@ namespace Robust.Client.Placement
private void HandleTileChanged(ref TileChangedEvent args)
{
var coords = EntityManager.GetComponent<MapGridComponent>(args.NewTile.GridUid).GridTileToLocal(args.NewTile.GridIndices);
var coords = MapManager.GetGrid(args.NewTile.GridUid).GridTileToLocal(args.NewTile.GridIndices);
_pendingTileChanges.RemoveAll(c => c.Item1 == coords);
}
@@ -758,7 +753,7 @@ namespace Robust.Client.Placement
// If we have actually placed something on a valid grid...
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{
var grid = EntityManager.GetComponent<MapGridComponent>(gridId);
var grid = MapManager.GetGrid(gridId);
// no point changing the tile to the same thing.
if (grid.GetTileRef(coordinates).Tile.TypeId == CurrentPermission.TileType)

View File

@@ -11,7 +11,6 @@ using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Utility;
@@ -116,12 +115,11 @@ namespace Robust.Client.Placement
var dirAng = pManager.Direction.ToAngle();
var spriteSys = pManager.EntityManager.System<SpriteSystem>();
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
foreach (var coordinate in locationcollection)
{
if (!coordinate.IsValid(pManager.EntityManager))
return; // Just some paranoia just in case
var worldPos = coordinate.ToMapPos(pManager.EntityManager, transformSys);
var worldPos = coordinate.ToMapPos(pManager.EntityManager);
var worldRot = pManager.EntityManager.GetComponent<TransformComponent>(coordinate.EntityId).WorldRotation + dirAng;
sprite.Color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor;
@@ -138,12 +136,11 @@ namespace Robust.Client.Placement
{
var mouseScreen = pManager.InputManager.MouseScreenPosition;
var mousePos = pManager.EyeManager.PixelToMap(mouseScreen);
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
if (mousePos.MapId == MapId.Nullspace)
yield break;
var (_, (x, y)) = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, transformSys, pManager.EntityManager) - pManager.StartPoint;
var (_, (x, y)) = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, pManager.EntityManager) - pManager.StartPoint;
float iterations;
Vector2 distance;
if (Math.Abs(x) > Math.Abs(y))
@@ -170,12 +167,11 @@ namespace Robust.Client.Placement
{
var mouseScreen = pManager.InputManager.MouseScreenPosition;
var mousePos = pManager.EyeManager.PixelToMap(mouseScreen);
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
if (mousePos.MapId == MapId.Nullspace)
yield break;
var placementdiff = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, transformSys, pManager.EntityManager) - pManager.StartPoint;
var placementdiff = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, pManager.EntityManager) - pManager.StartPoint;
var xSign = Math.Sign(placementdiff.X);
var ySign = Math.Sign(placementdiff.Y);
@@ -197,9 +193,9 @@ namespace Robust.Client.Placement
public TileRef GetTileRef(EntityCoordinates coordinates)
{
var gridUidOpt = coordinates.GetGridUid(pManager.EntityManager);
return gridUidOpt is EntityUid gridUid && gridUid.IsValid() ? pManager.EntityManager.GetComponent<MapGridComponent>(gridUid).GetTileRef(MouseCoords)
return gridUidOpt is EntityUid gridUid && gridUid.IsValid() ? pManager.MapManager.GetGrid(gridUid).GetTileRef(MouseCoords)
: new TileRef(gridUidOpt ?? EntityUid.Invalid,
MouseCoords.ToVector2i(pManager.EntityManager, pManager.MapManager, pManager.EntityManager.System<SharedTransformSystem>()), Tile.Empty);
MouseCoords.ToVector2i(pManager.EntityManager, pManager.MapManager), Tile.Empty);
}
public TextureResource GetSprite(string key)
@@ -227,8 +223,7 @@ namespace Robust.Client.Placement
}
var range = pManager.CurrentPermission!.Range;
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
if (range > 0 && !pManager.EntityManager.GetComponent<TransformComponent>(controlled).Coordinates.InRange(pManager.EntityManager, transformSys, coordinates, range))
if (range > 0 && !pManager.EntityManager.GetComponent<TransformComponent>(controlled).Coordinates.InRange(pManager.EntityManager, coordinates, range))
return false;
return true;
}
@@ -236,8 +231,7 @@ namespace Robust.Client.Placement
public bool IsColliding(EntityCoordinates coordinates)
{
var bounds = pManager.ColliderAABB;
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
var mapCoords = coordinates.ToMap(pManager.EntityManager, transformSys);
var mapCoords = coordinates.ToMap(pManager.EntityManager);
var (x, y) = mapCoords.Position;
var collisionBox = Box2.FromDimensions(
@@ -267,8 +261,7 @@ namespace Robust.Client.Placement
return EntityCoordinates.FromMap(pManager.MapManager, mapCoords);
}
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
return EntityCoordinates.FromMap(gridUid, mapCoords, transformSys, pManager.EntityManager);
return EntityCoordinates.FromMap(pManager.EntityManager, gridUid, mapCoords);
}
}
}

View File

@@ -129,7 +129,7 @@ public sealed partial class ReplayLoadManager
return parsed.FirstOrDefault()?.Root as MappingDataNode;
}
private (MappingDataNode YamlData, HashSet<string> CVars, TimeSpan? Duration, TimeSpan StartTime, bool ClientSide)
private (MappingDataNode YamlData, HashSet<string> CVars, TimeSpan Duration, TimeSpan StartTime, bool ClientSide)
LoadMetadata(IReplayFileReader fileReader)
{
_sawmill.Info($"Reading replay metadata");
@@ -137,16 +137,23 @@ public sealed partial class ReplayLoadManager
if (data == null)
throw new Exception("Failed to load yaml metadata");
TimeSpan duration;
var finalData = LoadYamlFinalMetadata(fileReader);
TimeSpan? duration = finalData == null
? null
: TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value);
if (finalData == null)
_sawmill.Warning("Failed to load final yaml metadata. Partial/incomplete replay?");
{
var msg = "Failed to load final yaml metadata";
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
throw new Exception(msg);
var typeHashString = ((ValueDataNode) data[MetaKeyTypeHash]).Value;
var typeHash = Convert.FromHexString(typeHashString);
_sawmill.Error(msg);
duration = TimeSpan.FromDays(1);
}
else
{
duration = TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value);
}
var typeHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyTypeHash]).Value);
var stringHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyStringHash]).Value);
var startTick = ((ValueDataNode) data[MetaKeyStartTick]).Value;
var timeBaseTick = ((ValueDataNode) data[MetaKeyBaseTick]).Value;
@@ -154,12 +161,7 @@ public sealed partial class ReplayLoadManager
var clientSide = bool.Parse(((ValueDataNode) data[MetaKeyIsClientRecording]).Value);
if (!typeHash.SequenceEqual(_serializer.GetSerializableTypesHash()))
{
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
throw new Exception($"RobustSerializer hash mismatch. do not match. Client hash: {_serializer.GetSerializableTypesHashString()}, replay hash: {typeHashString}.");
_sawmill.Warning($"RobustSerializer hash mismatch. Replay may fail to load!");
}
throw new Exception($"{nameof(IRobustSerializer)} hashes do not match. Loading replays using a bad replay-client version?");
using var stringFile = fileReader.Open(FileStrings);
var stringData = new byte[stringFile.Length];

View File

@@ -90,7 +90,6 @@ public sealed partial class ReplayControlWidget : UIWidget // AKA Tardis - The f
var maxIndex = Math.Max(1, replay.States.Count - 1);
var state = replay.States[index];
var replayTime = TimeSpan.FromSeconds(TickSlider.Value);
var end = replay.Duration == null ? "N/A" : replay.Duration.Value.ToString(TimeFormat);
IndexLabel.Text = Loc.GetString("replay-time-box-index-label",
("current", index), ("total", maxIndex), ("percentage", percentage));
@@ -99,10 +98,10 @@ public sealed partial class ReplayControlWidget : UIWidget // AKA Tardis - The f
("current", state.ToSequence), ("total", replay.States[^1].ToSequence), ("percentage", percentage));
TimeLabel.Text = Loc.GetString("replay-time-box-replay-time-label",
("current", replayTime.ToString(TimeFormat)), ("end", end), ("percentage", percentage));
("current", replayTime.ToString(TimeFormat)), ("end", replay.Duration.ToString(TimeFormat)), ("percentage", percentage));
var serverTime = (replayTime + replay.StartTime).ToString(TimeFormat);
string duration = replay.Duration == null ? "N/A" : (replay.Duration + replay.StartTime).Value.ToString(TimeFormat);
var duration = (replay.Duration + replay.StartTime).ToString(TimeFormat);
ServerTimeLabel.Text = Loc.GetString("replay-time-box-server-time-label",
("current", serverTime), ("end", duration), ("percentage", percentage));

View File

@@ -23,7 +23,7 @@
<PackageReference Include="Robust.Natives" />
<PackageReference Include="System.Numerics.Vectors" />
<PackageReference Include="TerraFX.Interop.Windows" PrivateAssets="compile" />
<PackageReference Condition="'$(RobustToolsBuild)' == 'True'" Include="JetBrains.Profiler.Api" PrivateAssets="compile" />
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" PrivateAssets="compile" />
<PackageReference Include="SpaceWizards.Sodium" PrivateAssets="compile" />
<PackageReference Include="Microsoft.NET.ILLink.Tasks" />
<PackageReference Include="TerraFX.Interop.Xlib" />

View File

@@ -57,7 +57,7 @@ namespace Robust.Client.UserInterface
toRemove.Add(key);
}
foreach (var key in toRemove.Span)
foreach (var key in toRemove)
{
_playingAnimations.Remove(key);
AnimationCompleted?.Invoke(key);

View File

@@ -1,132 +0,0 @@
using System;
using System.Runtime.CompilerServices;
namespace Robust.Client.UserInterface;
public partial class Control
{
private LayoutStyleProperties _layoutStyleOverride;
private LayoutStyleProperties _layoutStyleSheet;
private void UpdateLayoutStyleProperties()
{
var propertiesSet = LayoutStyleProperties.None;
// Assumed most controls will have little or no style properties,
// so iterating once is less expensive overall then checking 10+ properties.
// C# switch statements are compiled efficiently anyways.
foreach (var (key, value) in _styleProperties)
{
switch (key)
{
case nameof(SizeFlagsStretchRatio):
UpdateField(ref _sizeFlagsStretchRatio, value, LayoutStyleProperties.StretchRatio);
break;
case nameof(MinWidth):
UpdateField(ref _minWidth, value, LayoutStyleProperties.MinWidth);
break;
case nameof(MinHeight):
UpdateField(ref _minHeight, value, LayoutStyleProperties.MinHeight);
break;
case nameof(SetWidth):
UpdateField(ref _setWidth, value, LayoutStyleProperties.SetWidth);
break;
case nameof(SetHeight):
UpdateField(ref _setHeight, value, LayoutStyleProperties.SetHeight);
break;
case nameof(MaxWidth):
UpdateField(ref _maxWidth, value, LayoutStyleProperties.MaxWidth);
break;
case nameof(MaxHeight):
UpdateField(ref _maxHeight, value, LayoutStyleProperties.MaxHeight);
break;
case nameof(HorizontalExpand):
UpdateField(ref _horizontalExpand, value, LayoutStyleProperties.HorizontalExpand);
break;
case nameof(VerticalExpand):
UpdateField(ref _verticalExpand, value, LayoutStyleProperties.VerticalExpand);
break;
case nameof(HorizontalAlignment):
UpdateField(ref _horizontalAlignment, value, LayoutStyleProperties.HorizontalAlignment);
break;
case nameof(VerticalAlignment):
UpdateField(ref _verticalAlignment, value, LayoutStyleProperties.VerticalAlignment);
break;
case nameof(Margin):
UpdateField(ref _margin, value, LayoutStyleProperties.Margin);
break;
}
}
// Reset cleared properties back to defaults.
var toClear = _layoutStyleSheet & ~propertiesSet;
if (toClear != 0)
{
ClearField(ref _sizeFlagsStretchRatio, DefaultStretchRatio, LayoutStyleProperties.StretchRatio);
ClearField(ref _minWidth, 0, LayoutStyleProperties.MinWidth);
ClearField(ref _minHeight, 0, LayoutStyleProperties.MinHeight);
ClearField(ref _setWidth, DefaultSetSize, LayoutStyleProperties.SetWidth);
ClearField(ref _setHeight, DefaultSetSize, LayoutStyleProperties.SetHeight);
ClearField(ref _maxWidth, DefaultMaxSize, LayoutStyleProperties.MaxWidth);
ClearField(ref _maxHeight, DefaultMaxSize, LayoutStyleProperties.MaxHeight);
ClearField(ref _horizontalExpand, false, LayoutStyleProperties.HorizontalExpand);
ClearField(ref _verticalExpand, false, LayoutStyleProperties.VerticalExpand);
ClearField(ref _horizontalAlignment, DefaultHAlignment, LayoutStyleProperties.HorizontalAlignment);
ClearField(ref _verticalAlignment, DefaultVAlignment, LayoutStyleProperties.VerticalAlignment);
ClearField(ref _margin, default, LayoutStyleProperties.Margin);
}
_layoutStyleSheet = propertiesSet;
return;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void UpdateField<T>(ref T field, object value, LayoutStyleProperties flag)
{
if ((_layoutStyleOverride & flag) != 0)
return;
// TODO: Probably need better error handling...
if (value is not T valueCast)
return;
field = valueCast;
propertiesSet |= flag;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void ClearField<T>(ref T field, T defaultValue, LayoutStyleProperties flag)
{
if ((toClear & flag) == 0)
return;
field = defaultValue;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetLayoutStyleProp(LayoutStyleProperties flag)
{
_layoutStyleOverride |= flag;
}
[Flags]
private enum LayoutStyleProperties : short
{
// @formatter:off
None = 0,
Margin = 1 << 0,
MinWidth = 1 << 1,
MinHeight = 1 << 2,
SetWidth = 1 << 3,
SetHeight = 1 << 4,
MaxWidth = 1 << 5,
MaxHeight = 1 << 6,
StretchRatio = 1 << 7,
HorizontalExpand = 1 << 8,
VerticalExpand = 1 << 9,
HorizontalAlignment = 1 << 10,
VerticalAlignment = 1 << 11,
// @formatter:on
}
}

View File

@@ -12,30 +12,24 @@ namespace Robust.Client.UserInterface
public partial class Control
{
private const float DefaultStretchRatio = 1;
private const float DefaultSetSize = float.NaN;
private const float DefaultMaxSize = float.PositiveInfinity;
private const HAlignment DefaultHAlignment = HAlignment.Stretch;
private const VAlignment DefaultVAlignment = VAlignment.Stretch;
private Vector2 _size;
[ViewVariables] internal Vector2? PreviousMeasure;
[ViewVariables] internal UIBox2? PreviousArrange;
private float _sizeFlagsStretchRatio = DefaultStretchRatio;
private float _sizeFlagsStretchRatio = 1;
private float _minWidth;
private float _minHeight;
private float _setWidth = DefaultSetSize;
private float _setHeight = DefaultSetSize;
private float _maxWidth = DefaultMaxSize;
private float _maxHeight = DefaultMaxSize;
private float _setWidth = float.NaN;
private float _setHeight = float.NaN;
private float _maxWidth = float.PositiveInfinity;
private float _maxHeight = float.PositiveInfinity;
private bool _horizontalExpand;
private bool _verticalExpand;
private HAlignment _horizontalAlignment = DefaultHAlignment;
private VAlignment _verticalAlignment = DefaultVAlignment;
private HAlignment _horizontalAlignment = HAlignment.Stretch;
private VAlignment _verticalAlignment = VAlignment.Stretch;
private Thickness _margin;
private bool _measuring;
private bool _arranging;
@@ -59,7 +53,6 @@ namespace Robust.Client.UserInterface
set
{
_margin = value;
SetLayoutStyleProp(LayoutStyleProperties.Margin);
InvalidateMeasure();
}
}
@@ -249,7 +242,6 @@ namespace Robust.Client.UserInterface
set
{
_horizontalAlignment = value;
SetLayoutStyleProp(LayoutStyleProperties.HorizontalAlignment);
InvalidateArrange();
}
}
@@ -266,7 +258,6 @@ namespace Robust.Client.UserInterface
set
{
_verticalAlignment = value;
SetLayoutStyleProp(LayoutStyleProperties.VerticalAlignment);
InvalidateArrange();
}
}
@@ -285,7 +276,6 @@ namespace Robust.Client.UserInterface
set
{
_horizontalExpand = value;
SetLayoutStyleProp(LayoutStyleProperties.HorizontalExpand);
Parent?.InvalidateMeasure();
}
}
@@ -304,7 +294,6 @@ namespace Robust.Client.UserInterface
set
{
_verticalExpand = value;
SetLayoutStyleProp(LayoutStyleProperties.VerticalExpand);
Parent?.InvalidateArrange();
}
}
@@ -329,7 +318,6 @@ namespace Robust.Client.UserInterface
_sizeFlagsStretchRatio = value;
SetLayoutStyleProp(LayoutStyleProperties.StretchRatio);
Parent?.InvalidateArrange();
}
}
@@ -406,7 +394,6 @@ namespace Robust.Client.UserInterface
set
{
_minWidth = value;
SetLayoutStyleProp(LayoutStyleProperties.MinWidth);
InvalidateMeasure();
}
}
@@ -421,7 +408,6 @@ namespace Robust.Client.UserInterface
set
{
_minHeight = value;
SetLayoutStyleProp(LayoutStyleProperties.MinHeight);
InvalidateMeasure();
}
}
@@ -436,7 +422,6 @@ namespace Robust.Client.UserInterface
set
{
_setWidth = value;
SetLayoutStyleProp(LayoutStyleProperties.SetWidth);
InvalidateMeasure();
}
}
@@ -451,7 +436,6 @@ namespace Robust.Client.UserInterface
set
{
_setHeight = value;
SetLayoutStyleProp(LayoutStyleProperties.SetHeight);
InvalidateMeasure();
}
}
@@ -466,7 +450,6 @@ namespace Robust.Client.UserInterface
set
{
_maxWidth = value;
SetLayoutStyleProp(LayoutStyleProperties.MaxWidth);
InvalidateMeasure();
}
}
@@ -481,7 +464,6 @@ namespace Robust.Client.UserInterface
set
{
_maxHeight = value;
SetLayoutStyleProp(LayoutStyleProperties.MaxHeight);
InvalidateMeasure();
}
}

View File

@@ -239,7 +239,6 @@ namespace Robust.Client.UserInterface
protected virtual void StylePropertiesChanged()
{
UpdateLayoutStyleProperties();
InvalidateMeasure();
}

View File

@@ -641,11 +641,7 @@ namespace Robust.Client.UserInterface
foreach (var child in Children.ToArray())
{
// This checks fails in some obscure cases like using the element inspector in the dev window.
// Why? Well I could probably spend 15 minutes in a debugger to find out,
// but I'd probably still end up with this fix.
if (child.Parent == this)
RemoveChild(child);
RemoveChild(child);
}
}

View File

@@ -29,7 +29,6 @@ namespace Robust.Client.UserInterface.Controls
TextureRect = new TextureRect
{
StyleClasses = { StyleClassCheckBox },
VerticalAlignment = VAlignment.Center,
};
hBox.AddChild(TextureRect);

View File

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

View File

@@ -4,8 +4,6 @@ using System.Numerics;
using System.Text;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -22,8 +20,6 @@ namespace Robust.Client.UserInterface.Controls
public class LineEdit : Control
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private const float MouseScrollDelay = 0.001f;
@@ -50,9 +46,6 @@ namespace Robust.Client.UserInterface.Controls
private bool _mouseSelectingText;
private float _lastMousePosition;
private TimeSpan? _lastClickTime;
private Vector2? _lastClickPosition;
private bool IsPlaceHolderVisible => string.IsNullOrEmpty(_text) && _placeHolder != null;
public event Action<LineEditEventArgs>? OnTextChanged;
@@ -692,26 +685,8 @@ namespace Robust.Client.UserInterface.Controls
args.Handle();
}
}
// Double-clicking. Clicks delay should be <= 250ms and the distance < 10 pixels.
else if (args.Function == EngineKeyFunctions.UIClick && _lastClickPosition != null && _lastClickTime != null
&& _timing.RealTime - _lastClickTime <= TimeSpan.FromMilliseconds(_cfgManager.GetCVar(CVars.DoubleClickDelay))
&& (_lastClickPosition.Value - args.PointerLocation.Position).IsShorterThan(_cfgManager.GetCVar(CVars.DoubleClickRange)))
{
_lastClickTime = _timing.RealTime;
_lastClickPosition = args.PointerLocation.Position;
_lastMousePosition = args.RelativePosition.X;
_selectionStart = TextEditShared.PrevWordPosition(_text, GetIndexAtPos(args.RelativePosition.X));
_cursorPosition = TextEditShared.EndWordPosition(_text, GetIndexAtPos(args.RelativePosition.X));
args.Handle();
}
else
{
_lastClickTime = _timing.RealTime;
_lastClickPosition = args.PointerLocation.Position;
_mouseSelectingText = true;
_lastMousePosition = args.RelativePosition.X;

View File

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

View File

@@ -6,7 +6,6 @@ using Robust.Client.UserInterface.RichText;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface.Controls
{
@@ -17,26 +16,6 @@ namespace Robust.Client.UserInterface.Controls
private FormattedMessage? _message;
private RichTextEntry _entry;
private float _lineHeightScale = 1;
private bool _lineHeightOverride;
[ViewVariables(VVAccess.ReadWrite)]
public float LineHeightScale
{
get
{
if (!_lineHeightOverride && TryGetStyleProperty(nameof(LineHeightScale), out float value))
return value;
return _lineHeightScale;
}
set
{
_lineHeightScale = value;
_lineHeightOverride = true;
InvalidateMeasure();
}
}
public RichTextLabel()
{
@@ -68,7 +47,7 @@ namespace Robust.Client.UserInterface.Controls
}
var font = _getFont();
_entry.Update(font, availableSize.X * UIScale, UIScale, LineHeightScale);
_entry.Update(font, availableSize.X * UIScale, UIScale);
return new Vector2(_entry.Width / UIScale, _entry.Height / UIScale);
}
@@ -82,7 +61,7 @@ namespace Robust.Client.UserInterface.Controls
return;
}
_entry.Draw(handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale, LineHeightScale);
_entry.Draw(handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale);
}
[Pure]

View File

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

View File

@@ -2,7 +2,6 @@
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.LayoutContainer;
@@ -33,11 +32,6 @@ namespace Robust.Client.UserInterface.Controls
public bool Grabbed => _grabbed;
/// <summary>
/// Whether the slider can be adjusted.
/// </summary>
public bool Disabled { get; set; }
public StyleBox? ForegroundStyleBoxOverride
{
get => _foregroundStyleBoxOverride;
@@ -138,7 +132,7 @@ namespace Robust.Client.UserInterface.Controls
{
base.KeyBindDown(args);
if (args.Function != EngineKeyFunctions.UIClick || Disabled)
if (args.Function != EngineKeyFunctions.UIClick)
{
return;
}
@@ -152,7 +146,7 @@ namespace Robust.Client.UserInterface.Controls
{
base.KeyBindUp(args);
if (args.Function != EngineKeyFunctions.UIClick || !_grabbed) return;
if (args.Function != EngineKeyFunctions.UIClick) return;
_grabbed = false;
OnReleased?.Invoke(this);

View File

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

View File

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

View File

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

View File

@@ -1,34 +0,0 @@
<!-- Organised with tabs in a vertical list to the left and the contents to the right -->
<BoxContainer Name="Container" xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:graphics="clr-namespace:Robust.Client.Graphics"
Orientation="Horizontal"
MouseFilter="Pass">
<ScrollContainer VerticalExpand="True"
HScrollEnabled="False"
ReturnMeasure="True"
Margin="5 5 0 5">
<PanelContainer Margin="5" VerticalExpand="False"
VerticalAlignment="Top">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<BoxContainer Name="TabContainer" Margin="5" Orientation="Vertical"/>
</PanelContainer>
</ScrollContainer>
<ScrollContainer VerticalExpand="True"
HScrollEnabled="False"
HorizontalAlignment="Stretch"
HorizontalExpand="True"
ReturnMeasure="True"
Margin="0 5 5 5">
<PanelContainer Margin="5" HorizontalExpand="True" HorizontalAlignment="Stretch">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<BoxContainer Margin="5"
Orientation="Vertical"
Name="ContentsContainer"/>
</PanelContainer>
</ScrollContainer>
</BoxContainer>

View File

@@ -1,97 +0,0 @@
using System.Collections.Generic;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Maths;
namespace Robust.Client.UserInterface.Controls;
[GenerateTypedNameReferences]
public sealed partial class VerticalTabContainer : BoxContainer
{
private readonly Dictionary<Control, BaseButton> _tabs = new();
// Just used to order controls in case one gets removed.
private readonly List<Control> _controls = new();
private readonly ButtonGroup _tabGroup = new(false);
private Control? _currentControl;
public VerticalTabContainer()
{
RobustXamlLoader.Load(this);
}
public int AddTab(Control control, string title)
{
var button = new Button()
{
Text = title,
Group = _tabGroup,
};
TabContainer.AddChild(button);
ContentsContainer.AddChild(control);
var index = ChildCount - 1;
button.OnPressed += args =>
{
SelectTab(control);
};
_controls.Add(control);
_tabs.Add(control, button);
// Existing tabs
if (ContentsContainer.ChildCount > 1)
{
control.Visible = false;
}
// First tab
else
{
SelectTab(control);
}
return index;
}
protected override void ChildRemoved(Control child)
{
if (_tabs.Remove(child, out var button))
{
button.Dispose();
}
// Set the current tab to a different control
if (_currentControl == child)
{
var previous = _controls.IndexOf(child) - 1;
if (previous > -1)
{
var setControl = _controls[previous];
SelectTab(setControl);
}
else
{
_currentControl = null;
}
}
_controls.Remove(child);
base.ChildRemoved(child);
}
private void SelectTab(Control control)
{
if (_currentControl != null)
{
_currentControl.Visible = false;
}
var button = _tabs[control];
button.Pressed = true;
control.Visible = true;
_currentControl = control;
}
}

View File

@@ -90,7 +90,7 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
contents.TextMemory = FormatHelpers.FormatIntoMem(_textBuffer,
$@"UP: {sentBytes / ONE_KIBIBYTE:N} KiB/s, {sentPackets} pckt/s, {LastSentBytes / ONE_KIBIBYTE:N} KiB, {LastSentPackets} pckt
DOWN: {receivedBytes / ONE_KIBIBYTE:N} KiB/s, {receivedPackets} pckt/s, {LastReceivedBytes / ONE_KIBIBYTE:N} KiB, {LastReceivedPackets} pckt
PING: {NetManager.ServerChannel?.Ping ?? -1} ms, MTU: {NetManager.ServerChannel?.CurrentMtu} B");
PING: {NetManager.ServerChannel?.Ping ?? -1} ms");
}
}
}

View File

@@ -20,14 +20,10 @@
<BoxContainer Name="ControlTreeRoot" Orientation="Vertical" MouseFilter="Stop" />
</PanelContainer>
</ScrollContainer>
<ScrollContainer>
<ScrollContainer HScrollEnabled="False">
<BoxContainer Name="ControlProperties" Orientation="Vertical" MouseFilter="Stop" />
</ScrollContainer>
</SplitContainer>
</BoxContainer>
</PanelContainer>
<!-- TODO Remove and replace with a popup container on WindowRoot -->
<PopupContainer Name="PopupContainer" Access="Public">
<DevWindowTabUIPopup Name="UIPopup" />
</PopupContainer>
</Control>

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Robust.Client.AutoGenerated;
using Robust.Client.Console.Commands;
using Robust.Client.Graphics;
@@ -209,40 +208,25 @@ public sealed partial class DevWindowTabUI : Control
});
foreach (var (prop, value) in values)
{
var button = new ContainerButton
ControlProperties.AddChild(new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
SeparationOverride = 3,
Margin = new Thickness(3, 1),
Children =
{
new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
SeparationOverride = 3,
Margin = new Thickness(3, 1),
Children =
{
new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label { Text = $"{prop}", FontColorOverride = Color.GreenYellow },
new Label { Text = ":" }, // this is for the non colored ":", intentional
}
},
new Label { Text = $"{value}" },
new Label { Text = $"{prop}", FontColorOverride = Color.GreenYellow },
new Label { Text = ":" }, // this is for the non colored ":", intentional
}
}
},
new Label { Text = $"{value}" },
}
};
button.OnPressed += _ =>
{
// TODO replace with parenting to popup container on WindowRoot
UIPopup.Text = GuiDumpCommand.PropertyValuesString(SelectedControl, prop);
var box = UIBox2.FromDimensions(UserInterfaceManager.MousePositionScaled.Position
- GlobalPosition, Vector2.One);
UIPopup.Open(box);
};
ControlProperties.AddChild(button);
});
}
}
}

View File

@@ -1,11 +0,0 @@
<DevWindowTabUIPopup xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Robust.Client.UserInterface.DevWindowTabUIPopup"
xmlns:gfx="clr-namespace:Robust.Client.Graphics">
<PanelContainer>
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#40404C" />
</PanelContainer.PanelOverride>
</PanelContainer>
<Label Name="TextLabel" />
</DevWindowTabUIPopup>

View File

@@ -1,20 +0,0 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Robust.Client.UserInterface;
[GenerateTypedNameReferences]
internal sealed partial class DevWindowTabUIPopup : Popup
{
public string? Text
{
get => TextLabel.Text;
set => TextLabel.Text = value;
}
public DevWindowTabUIPopup()
{
RobustXamlLoader.Load(this);
}
}

View File

@@ -71,8 +71,7 @@ namespace Robust.Client.UserInterface
/// <param name="defaultFont">The font being used for display.</param>
/// <param name="maxSizeX">The maximum horizontal size of the container of this entry.</param>
/// <param name="uiScale"></param>
/// <param name="lineHeightScale"></param>
public void Update(Font defaultFont, float maxSizeX, float uiScale, float lineHeightScale = 1)
public void Update(Font defaultFont, float maxSizeX, float uiScale)
{
// This method is gonna suck due to complexity.
// Bear with me here.
@@ -160,7 +159,7 @@ namespace Robust.Client.UserInterface
if (!context.Font.TryPeek(out var font))
font = defaultFont;
src.Height += GetLineHeight(font, uiScale, lineHeightScale);
src.Height += font.GetLineHeight(uiScale);
}
}
}
@@ -171,8 +170,7 @@ namespace Robust.Client.UserInterface
UIBox2 drawBox,
float verticalOffset,
MarkupDrawingContext context,
float uiScale,
float lineHeightScale = 1)
float uiScale)
{
context.Clear();
context.Color.Push(_defaultColor);
@@ -199,7 +197,7 @@ namespace Robust.Client.UserInterface
if (lineBreakIndex < LineBreaks.Count &&
LineBreaks[lineBreakIndex] == globalBreakCounter)
{
baseLine = new Vector2(drawBox.Left, baseLine.Y + GetLineHeight(font, uiScale, lineHeightScale) + controlYAdvance);
baseLine = new Vector2(drawBox.Left, baseLine.Y + font.GetLineHeight(uiScale) + controlYAdvance);
controlYAdvance = 0;
lineBreakIndex += 1;
}
@@ -218,7 +216,7 @@ namespace Robust.Client.UserInterface
control.Position = new Vector2(baseLine.X * invertedScale, (baseLine.Y - defaultFont.GetAscent(uiScale)) * invertedScale);
control.Measure(new Vector2(Width, Height));
var advanceX = control.DesiredPixelSize.X;
controlYAdvance = Math.Max(0f, (control.DesiredPixelSize.Y - GetLineHeight(font, uiScale, lineHeightScale)) * invertedScale);
controlYAdvance = Math.Max(0f, (control.DesiredPixelSize.Y - font.GetLineHeight(uiScale)) * invertedScale);
baseLine += new Vector2(advanceX, 0);
}
}
@@ -244,11 +242,5 @@ namespace Robust.Client.UserInterface
tag.PopDrawContext(node, context);
return tag.TextAfter(node);
}
private static int GetLineHeight(Font font, float uiScale, float lineHeightScale)
{
var height = font.GetLineHeight(uiScale);
return (int)(height * lineHeightScale);
}
}
}

View File

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

View File

@@ -2,7 +2,6 @@
using Robust.Client.UserInterface.Themes;
using Robust.Shared;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
namespace Robust.Client.UserInterface;
@@ -19,29 +18,11 @@ internal partial class UserInterfaceManager
{
DefaultTheme = _protoManager.Index<UITheme>(UITheme.DefaultName);
CurrentTheme = DefaultTheme;
ReloadThemes();
_configurationManager.OnValueChanged(CVars.InterfaceTheme, SetThemeOrPrevious, true);
_protoManager.PrototypesReloaded += OnPrototypesReloaded;
}
private void OnPrototypesReloaded(PrototypesReloadedEventArgs eventArgs)
{
if (eventArgs.WasModified<UITheme>())
{
_sawmillUI.Debug("Reloading UI themes due to prototype reload");
ReloadThemes();
}
}
private void ReloadThemes()
{
_themes.Clear();
foreach (var proto in _protoManager.EnumeratePrototypes<UITheme>())
{
_themes.Add(proto.ID, proto);
}
SetThemeOrPrevious(CurrentTheme.ID);
_configurationManager.OnValueChanged(CVars.InterfaceTheme, SetThemeOrPrevious, true);
}
//Try to set the current theme, if the theme is not found do nothing

View File

@@ -8,8 +8,6 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.ViewVariables.Editors;
using Robust.Client.ViewVariables.Instances;
using Robust.Shared.Audio;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
@@ -31,8 +29,6 @@ namespace Robust.Client.ViewVariables
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IRobustSerializer _robustSerializer = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IResourceManager _resManager = default!;
private uint _nextReqId = 1;
private readonly Vector2i _defaultWindowSize = (640, 420);
@@ -130,26 +126,6 @@ namespace Robust.Client.ViewVariables
return new VVPropEditorString();
}
if (type == typeof(EntProtoId?))
{
return new VVPropEditorNullableEntProtoId();
}
if (type == typeof(EntProtoId))
{
return new VVPropEditorEntProtoId();
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ProtoId<>))
{
var editor =
(VVPropEditor)Activator.CreateInstance(
typeof(VVPropEditorProtoId<>).MakeGenericType(type.GenericTypeArguments[0]))!;
IoCManager.InjectDependencies(editor);
return editor;
}
if (typeof(IPrototype).IsAssignableFrom(type) || typeof(ViewVariablesBlobMembers.PrototypeReferenceToken).IsAssignableFrom(type))
{
return (VVPropEditor)Activator.CreateInstance(typeof(VVPropEditorIPrototype<>).MakeGenericType(type))!;
@@ -230,12 +206,6 @@ namespace Robust.Client.ViewVariables
return new VVPropEditorTimeSpan();
}
if (typeof(SoundSpecifier).IsAssignableFrom(type))
{
var control = new VVPropEditorSoundSpecifier(_protoManager, _resManager);
return control;
}
if (type == typeof(ViewVariablesBlobMembers.ServerKeyValuePairToken) ||
type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
{

View File

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

View File

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

View File

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

View File

@@ -1,393 +0,0 @@
using System.Globalization;
using System.Numerics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Audio;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client.ViewVariables.Editors;
public sealed class VVPropEditorSoundSpecifier : VVPropEditor
{
private readonly IPrototypeManager _protoManager;
private readonly IResourceManager _resManager;
// Need to cache to some level just to make sure each edit doesn't reset the specifier to the default.
private SoundSpecifier? _specifier;
public VVPropEditorSoundSpecifier(IPrototypeManager protoManager, IResourceManager resManager)
{
_protoManager = protoManager;
_resManager = resManager;
}
protected override Control MakeUI(object? value)
{
var typeButton = new OptionButton()
{
Disabled = ReadOnly,
};
typeButton.AddItem(Loc.GetString("vv-sound-none"));
typeButton.AddItem(Loc.GetString("vv-sound-collection"), 1);
typeButton.AddItem(Loc.GetString("vv-sound-path"), 2);
var editBox = new LineEdit()
{
HorizontalExpand = true,
Editable = !ReadOnly,
};
var pathControls = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
typeButton,
editBox
},
SetSize = new Vector2(384f, 32f)
};
if (value != null)
{
switch (value)
{
case SoundCollectionSpecifier collection:
typeButton.SelectId(1);
editBox.Text = collection.Collection ?? string.Empty;
_specifier = collection;
break;
case SoundPathSpecifier path:
typeButton.SelectId(2);
editBox.Text = path.Path.ToString();
_specifier = path;
break;
default:
_specifier = null;
break;
}
}
typeButton.OnItemSelected += args =>
{
typeButton.SelectId(args.Id);
editBox.Text = string.Empty;
editBox.Editable = !ReadOnly && typeButton.SelectedId > 0;
if (typeButton.SelectedId == 0)
{
// Dummy value
ValueChanged(new SoundPathSpecifier(""));
}
};
editBox.OnTextEntered += args =>
{
if (string.IsNullOrEmpty(args.Text))
return;
switch (typeButton.SelectedId)
{
case 1:
if (!_protoManager.HasIndex<SoundCollectionPrototype>(args.Text))
return;
_specifier = new SoundCollectionSpecifier(args.Text)
{
Params = _specifier?.Params ?? AudioParams.Default,
};
ValueChanged(_specifier);
break;
case 2:
var path = new ResPath(args.Text);
if (!_resManager.ContentFileExists(path))
return;
_specifier = new SoundPathSpecifier(args.Text)
{
Params = _specifier?.Params ?? AudioParams.Default,
};
ValueChanged(_specifier);
break;
default:
return;
}
};
// Audio params
/* Volume */
var volumeEdit = new LineEdit()
{
Text = _specifier?.Params.Volume.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
volumeEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithVolume(floatValue);
ValueChanged(_specifier);
};
var volumeContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-volume"),
},
volumeEdit,
}
};
/* Pitch */
var pitchEdit = new LineEdit()
{
Text = _specifier?.Params.Pitch.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
pitchEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithPitchScale(floatValue);
ValueChanged(_specifier);
};
var pitchContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-pitch"),
},
pitchEdit,
}
};
/* MaxDistance */
var maxDistanceEdit = new LineEdit()
{
Text = _specifier?.Params.MaxDistance.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
maxDistanceEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithMaxDistance(floatValue);
ValueChanged(_specifier);
};
var maxDistanceContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-max-distance"),
},
maxDistanceEdit,
}
};
/* RolloffFactor */
var rolloffFactorEdit = new LineEdit()
{
Text = _specifier?.Params.RolloffFactor.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
rolloffFactorEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithRolloffFactor(floatValue);
ValueChanged(_specifier);
};
var rolloffFactorContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-rolloff-factor"),
},
rolloffFactorEdit,
}
};
/* ReferenceDistance */
var referenceDistanceEdit = new LineEdit()
{
Text = _specifier?.Params.ReferenceDistance.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
referenceDistanceEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithReferenceDistance(floatValue);
ValueChanged(_specifier);
};
var referenceDistanceContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-reference-distance"),
},
referenceDistanceEdit,
}
};
/* Loop */
var loopButton = new Button()
{
Text = Loc.GetString("vv-sound-loop"),
Pressed = _specifier?.Params.Loop ?? false,
ToggleMode = true,
Disabled = ReadOnly || _specifier == null,
};
loopButton.OnPressed += args =>
{
if (_specifier == null)
return;
_specifier.Params = _specifier.Params.WithLoop(args.Button.Pressed);
ValueChanged(_specifier);
};
/* PlayOffsetSeconds */
var playOffsetEdit = new LineEdit()
{
Text = _specifier?.Params.PlayOffsetSeconds.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
playOffsetEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithPlayOffset(floatValue);
ValueChanged(_specifier);
};
var playOffsetContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-play-offset"),
},
playOffsetEdit,
}
};
/* Variation */
var variationEdit = new LineEdit()
{
Text = _specifier?.Params.Variation.ToString() ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
variationEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithVariation(floatValue);
ValueChanged(_specifier);
};
var variationContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-variation"),
},
variationEdit,
}
};
var audioParamsControls = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
Children =
{
volumeContainer,
pitchContainer,
maxDistanceContainer,
rolloffFactorContainer,
referenceDistanceContainer,
loopButton,
playOffsetContainer,
variationContainer,
}
};
var controls = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
Children =
{
pathControls,
audioParamsControls,
}
};
return controls;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -68,7 +68,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
{
var entity = Spawn("Audio", MapCoordinates.Nullspace);
var audio = SetupAudio(entity, filename, audioParams);
@@ -78,11 +78,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null)
{
if (string.IsNullOrEmpty(filename))
return null;
if (TerminatingOrDeleted(uid))
{
Log.Error($"Tried to play audio on a terminating / deleted entity {ToPrettyString(uid)}. Trace: {Environment.StackTrace}");
@@ -97,11 +94,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityUid uid, AudioParams? audioParams = null)
{
if (string.IsNullOrEmpty(filename))
return null;
if (TerminatingOrDeleted(uid))
{
Log.Error($"Tried to play audio on a terminating / deleted entity {ToPrettyString(uid)}. Trace: {Environment.StackTrace}");
@@ -115,11 +109,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
{
if (string.IsNullOrEmpty(filename))
return null;
if (TerminatingOrDeleted(coordinates.EntityId))
{
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}. Trace: {Environment.StackTrace}");
@@ -137,12 +128,9 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityCoordinates coordinates,
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates,
AudioParams? audioParams = null)
{
if (string.IsNullOrEmpty(filename))
return null;
if (TerminatingOrDeleted(coordinates.EntityId))
{
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}. Trace: {Environment.StackTrace}");
@@ -188,12 +176,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
return audio;
}
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null)
{
return PlayGlobal(filename, Filter.SinglePlayer(recipient), false, audioParams);
}
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null)
{
if (TryComp(recipient, out ActorComponent? actor))
return PlayGlobal(filename, actor.PlayerSession, audioParams);
@@ -201,12 +189,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
return null;
}
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
{
return PlayEntity(filename, Filter.SinglePlayer(recipient), uid, false, audioParams);
}
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
{
if (TryComp(recipient, out ActorComponent? actor))
return PlayEntity(filename, actor.PlayerSession, uid, audioParams);
@@ -214,12 +202,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
return null;
}
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
return PlayStatic(filename, Filter.SinglePlayer(recipient), coordinates, false, audioParams);
}
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
if (TryComp(recipient, out ActorComponent? actor))
return PlayStatic(filename, actor.PlayerSession, coordinates, audioParams);
@@ -250,8 +238,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
}
public override void LoadStream<T>(Entity<AudioComponent> entity, T stream)
public override void LoadStream<T>(AudioComponent component, T stream)
{
// TODO: Yeah remove this...
}
}

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