Compare commits

...

42 Commits

Author SHA1 Message Date
Pieter-Jan Briers
ec794ce4e4 Version: 222.4.0 2024-05-17 02:51:44 +02:00
metalgearsloth
6b13475842 Revert "Add physics delta states (#5116)" (#5144)
This reverts commit 1189613908.
2024-05-17 02:21:05 +02:00
Pieter-Jan Briers
b48ee22800 Add more System.Numerics types to sandbox. 2024-05-16 22:25:13 +02:00
Pieter-Jan Briers
0b95a4edeb Version: 222.3.0 2024-05-16 20:14:37 +02:00
Pieter-Jan Briers
ed359481b4 We can't expect god to do the release notes. 2024-05-16 20:14:18 +02:00
metalgearsloth
1189613908 Add physics delta states (#5116)
* Add physics delta states

Significantly cuts down on data being sent + should make client state handling faster.

* Update Robust.Shared/Physics/Components/PhysicsComponentState.cs
2024-05-16 20:09:25 +02:00
DrSmugleaf
30907d8415 Fix ordered subscriptions not working when targeting a parent system type (#5135)
* Fix ordered subscriptions not working when targeting a parent system type

* Fix missing usages of expand ordering

* Extract method
2024-05-16 20:00:15 +02:00
Leon Friedrich
7f2da4d4f3 Fix paused entities not updating on prototype reload (#5128) 2024-05-16 19:06:11 +02:00
Ed
e30e963623 Hidden tiles (#5102)
* hidden tiles

* Update TileSpawningUIController.cs

* Update TileSpawningUIController.cs

* Update ITileDefinition.cs

* Update TileSpawningUIController.cs

* Move EditorHidden where clause out

* Make EditorHidden a DIM

So there's no breaking change

* Release notes.

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-05-16 19:05:39 +02:00
metalgearsloth
b056caeed7 Fix cross-map BUIs (#5115)
Even the ignore range bit is going to break with pvs but uhh not sure on that one unless we do overrides or something.
2024-05-16 19:00:11 +02:00
Leon Friedrich
fbc8086335 Don't iterate over component events when removing components (#5138)
* Don't iterate over component events when removing components

* Welp nevermind, forgot about tests

* A

* AAAAAA

* AAAA
2024-05-16 18:53:33 +02:00
Pieter-Jan Briers
799702b814 Work against .NET SDK update ManagePackageVersionsCentrally change
.NET SDK 8.0.300 changed ManagePackageVersionsCentrally to be implicitly set if Directory.Packages.props exists. We do not want this, as we intentionally have some projects that have it disabled.

We now explicitly unset the value in the Directory.Packages.props file to get the old behavior back.

See https://github.com/dotnet/core/issues/9309
2024-05-15 21:02:35 +02:00
ElectroJr
63df90f86f Version: 222.2.0 2024-05-14 23:01:41 +12:00
Vasilis
51f0c60bd3 Do not log wrong and correct watchdog token into info logs (#5133)
* Lets not do this?

* Webedit 2
2024-05-14 02:12:24 +02:00
metalgearsloth
a9ed53f47b Run BUI range checks in parallel (#5118)
These still take almost half-ms in server tick time on live as it has to do a raycast for everyone that has storage or their PDA open or whatever. Haven't benchmarked with a lot of clients but easiest way to tell is just check grafanaTM and I'm not sure how to check this locally.
2024-05-14 09:03:26 +10:00
Amy
41c40f1a94 Fix checking wrong property in TryGetVariableType() (#5120)
Co-authored-by: amylizzle <amylizzle@users.noreply.github.com>
2024-05-14 08:44:19 +10:00
deltanedas
6e61c35d35 add missing Comp inline to EntityQuery (#5123)
Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-05-14 08:43:03 +10:00
ShadowCommander
aae0a8bc51 Add doc comments to CreateEntityUninitialized (#5131) 2024-05-14 08:42:34 +10:00
DrSmugleaf
cb543240c6 Fix clients mispredicting a character's gender (#5119)
* Fix clients mispredicting a character's gender

* Allow nullable value in set
2024-05-12 22:20:21 -07:00
metalgearsloth
1654ab06f5 Revert "Add audio stream name to entity name" (#5127)
This reverts commit d2a2afe82e.
2024-05-13 10:53:30 +10:00
Brandon Li
211245215e remove XAMLIL after generating populate method (#5126) 2024-05-13 01:34:32 +02:00
ShadowCommander
10aaaa65c5 Add editorconfig wrapping settings (#5125) 2024-05-12 17:50:19 +02:00
ShadowCommander
d2a2afe82e Add audio stream name to entity name (#5121) 2024-05-10 17:58:38 +02:00
Pieter-Jan Briers
025d90d281 Change build upload server to Suns 2024-05-09 09:48:49 +02:00
metalgearsloth
c229f2e312 Fix valuelist ensurecapacity(0) throwing (#5113)
_items is still null so.
2024-05-08 20:46:45 +02:00
metalgearsloth
fe051a3577 Minor bui thing (#5114) 2024-05-08 19:59:13 +10:00
DrSmugleaf
51a0ef1e60 Fix error when JointRelayTargetComponent shuts down while applying state (#5110) 2024-05-08 19:21:55 +10:00
DrSmugleaf
702dfef5fc Version: 222.1.1 2024-05-07 22:28:07 -07:00
DrSmugleaf
a0c1ad246f Fix never setting BoundUserInterface.State (#5111)
* Fix never setting BoundUserInterface.State

* Make setter internal
2024-05-07 20:51:26 -07:00
metalgearsloth
1153888bd1 Add truncate for filesaving (#5098)
* Add truncate for filesaving

If I expose it to content I pretty much always want truncate to be honest.

* Update Robust.Client/UserInterface/FileDialogManager.cs

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2024-05-07 00:58:57 +02:00
Amy
ccbb6ddec7 Add method for getting type of var (#5070)
Co-authored-by: amylizzle <amylizzle@users.noreply.github.com>
2024-05-06 20:39:32 +02:00
metalgearsloth
970da5f717 Version: 222.1.0 2024-05-06 13:43:11 +10:00
Pieter-Jan Briers
4d528dd577 Analyzer to ban uncached regexes (#5107)
Using static Regex functions that take in a pattern is bad, because they constantly have to be re-parsed. Cache the Regex instance.
2024-05-06 10:30:31 +10:00
Pieter-Jan Briers
c83720b163 Update Lidgren to v0.3.1 2024-05-06 01:55:05 +02:00
Pieter-Jan Briers
bd87a805d4 Add CVars to turn Lidgren's error/warning logs off.
Combined with upcoming Lidgren changes, this should make DDoS-induced warning log spam not cause huge server perf issues anymore.
2024-05-06 01:47:27 +02:00
Leon Friedrich
fff42fb2b4 Partially fix UI assert (#5100)
* Partially fix UI assert

* Avoid breaking change in BoundKeyEventArgs

This is a public constructor, as much as it maybe shouldn't be. Adding this parameter is a breaking change.

* Replace .Disposed checks with ! .VisibleInTree

Control disposal should not be used anymore.

* Release notes

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-05-06 01:29:34 +02:00
metalgearsloth
4500669f65 Version: 222.0.0 2024-05-06 08:54:19 +10:00
Pieter-Jan Briers
7d19ea9338 Fix compiler error from merges
EntProtoId PR was incompatible with the PR to change EntityPrototype methods to require IComponentFactory passed in
2024-05-06 00:32:54 +02:00
Leon Friedrich
2dc610907d Make IComponentFactory argument in EntityPrototype mandatory (#5101) 2024-05-05 23:00:10 +02:00
DrSmugleaf
beb1c4b1fb Add EntProtoId<T> (#5097)
* Add EntProtoId<T>

* Fix error messages

* Shorten error messages

* Make services non-optional
2024-05-05 22:59:45 +02:00
metalgearsloth
7e331eaa75 Defer clientside BUI opens (#5073)
* Defer clientside BUI opens

Needs content fix first as storage UI breaks.

* tweaks

* Re-revert this because it seems needed
2024-05-03 12:58:19 +10:00
Leon Friedrich
caf9e45ad9 Fix PVS iterating over duplicate chunks when a a client has multiple viewers/eyes (#5094) 2024-05-03 05:52:56 +10:00
50 changed files with 854 additions and 195 deletions

View File

@@ -7,6 +7,18 @@ indent_size = 4
trim_trailing_whitespace = true
charset = utf-8
max_line_length = 120
# ReSharper properties
resharper_csharp_max_line_length = 120
resharper_csharp_wrap_after_declaration_lpar = true
resharper_csharp_wrap_arguments_style = chop_if_long
resharper_csharp_wrap_parameters_style = chop_if_long
resharper_keep_existing_attribute_arrangement = true
resharper_place_field_attribute_on_same_line = if_owner_is_single_line
resharper_wrap_chained_binary_patterns = chop_if_long
resharper_wrap_chained_method_calls = chop_if_long
[*.{csproj,xml,yml,dll.config,targets,props}]
indent_size = 2

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 centcomm
- name: Upload files to Suns
uses: appleboy/scp-action@master
with:
host: centcomm.spacestation14.io
host: suns.spacestation14.com
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: centcomm.spacestation14.io
host: suns.spacestation14.com
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,4 +1,14 @@
<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" />

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

@@ -54,6 +54,94 @@ END TEMPLATE-->
*None yet*
## 222.4.0
### New features
* Added the following types from `System.Numerics` to the sandbox: `Complex`, `Matrix3x2`, `Matrix4x4`, `Plane`, `Quaternion`, `Vector3`, `Vector4`.
## 222.3.0
### New features
* `ITileDefinition.EditorHidden` allows hiding a tile from the tile spawn panel.
* Ordered event subscriptions now take child types into account, so ordering based on a shared type will work.
### Bugfixes
* Cross-map BUI range checks now work.
* Paused entities update on prototype reload.
### Other
* Fixed build compatibility with .NET 8.0.300 SDK, due to changes in how Central Package Management behaves.
* Physics component has delta states to reduce network usage.
## 222.2.0
### New features
* Added `EntityQuery.Comp()` (abbreviation of `GetComponent()`)
### Bugfixes
* Fix `SerializationManager.TryGetVariableType` checking the wrong property.
* Fixed GrammarSystem mispredicting a character's gender
### Other
* User interface system now performs range checks in parallel
## 222.1.1
### Bugfixes
* Fixed never setting BoundUserInterface.State.
### Other
* Add truncate for filesaving.
* Add method for getting the type of a data field by name from ISerializationManager.
## 222.1.0
### New features
* Added `BoundKeyEventArgs.IsRepeat`.
* Added `net.lidgren_log_warning` and `net.lidgren_log_error` CVars.
### Bugfixes
* Fix assert trip when holding repeatable keybinds.
### Other
* Updated Lidgren to v0.3.1. This should provide performance improvements if warning/error logs are disabled.
## 222.0.0
### Breaking changes
* Mark IComponentFactory argument in EntityPrototype as mandatory.
### New features
* Add `EntProtoId<T>` to check for components on the attached entity as well.
### Bugfixes
* Fix PVS iterating duplicate chunks for multiple viewsubscriptions.
### Other
* Defer clientside BUI opens if it's the first state that comes in.
## 221.2.0
### New features

View File

@@ -0,0 +1,57 @@
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.NoUncachedRegexAnalyzer>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
public sealed class NoUncachedRegexAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<NoUncachedRegexAnalyzer, NUnitVerifier>()
{
TestState =
{
Sources = { code }
},
};
// 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 System.Text.RegularExpressions;
public static class Foo
{
public static void Bad()
{
Regex.Replace("foo", "bar", "baz");
}
public static void Good()
{
var r = new Regex("bar");
r.Replace("foo", "baz");
}
}
""";
await Verifier(code,
// /0/Test0.cs(7,9): warning RA0026: Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern.
VerifyCS.Diagnostic().WithSpan(7, 9, 7, 43)
);
}
}

View File

@@ -0,0 +1,66 @@
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 NoUncachedRegexAnalyzer : DiagnosticAnalyzer
{
private const string RegexTypeName = "Regex";
private const string RegexType = $"System.Text.RegularExpressions.{RegexTypeName}";
private static readonly DiagnosticDescriptor Rule = new (
Diagnostics.IdUncachedRegex,
"Use of uncached static Regex function",
"Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern.",
"Usage",
DiagnosticSeverity.Warning,
true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public static readonly HashSet<string> BadFunctions =
[
"Count",
"EnumerateMatches",
"IsMatch",
"Match",
"Matches",
"Replace",
"Split"
];
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterOperationAction(CheckInvocation, OperationKind.Invocation);
}
private static void CheckInvocation(OperationAnalysisContext context)
{
if (context.Operation is not IInvocationOperation invocation)
return;
// All Regex functions we care about are static.
var targetMethod = invocation.TargetMethod;
if (!targetMethod.IsStatic)
return;
// Bail early.
if (targetMethod.ContainingType.Name != "Regex")
return;
var regexType = context.Compilation.GetTypeByMetadataName(RegexType);
if (!SymbolEqualityComparer.Default.Equals(regexType, targetMethod.ContainingType))
return;
if (!BadFunctions.Contains(targetMethod.Name))
return;
context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.Syntax.GetLocation()));
}
}

View File

@@ -239,6 +239,7 @@ namespace Robust.Build.Tasks
engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0,
$"{res.FilePath}: {e.Message}", "", "CompileRobustXaml"));
}
res.Remove();
}
return true;
}

View File

@@ -346,7 +346,7 @@ namespace Robust.Client.Input
{
if (binding.CanRepeat)
{
return SetBindState(binding, BoundKeyState.Down, uiOnly);
return SetBindState(binding, BoundKeyState.Down, uiOnly, isRepeat);
}
return true;
@@ -375,7 +375,7 @@ namespace Robust.Client.Input
SetBindState(binding, BoundKeyState.Up);
}
private bool SetBindState(KeyBinding binding, BoundKeyState state, bool uiOnly = false)
private bool SetBindState(KeyBinding binding, BoundKeyState state, bool uiOnly = false, bool isRepeat = false)
{
if (binding.BindingType == KeyBindingType.Command && state == BoundKeyState.Down)
{
@@ -387,6 +387,7 @@ namespace Robust.Client.Input
// I honestly have no idea what the best solution here is.
// note from the future: context switches won't cause re-entrancy anymore because InputContextContainer defers context switches
DebugTools.Assert(!_currentlyFindingViewport, "Re-entrant key events??");
DebugTools.Assert(!isRepeat || binding.CanRepeat);
try
{
@@ -399,7 +400,7 @@ namespace Robust.Client.Input
binding.State = state;
var eventArgs = new BoundKeyEventArgs(binding.Function, binding.State,
MouseScreenPosition, binding.CanFocus);
MouseScreenPosition, binding.CanFocus, isRepeat);
// UI returns true here into blockPass if it wants to prevent us from giving input events
// to the viewport, but doesn't want it hard-handled so we keep processing possible key actions.

View File

@@ -130,7 +130,7 @@ public sealed class TileSpawningUIController : UIController
_window.TileList.Clear();
IEnumerable<ITileDefinition> tileDefs = _tiles;
IEnumerable<ITileDefinition> tileDefs = _tiles.Where(def => !def.EditorHidden);
if (!string.IsNullOrEmpty(searchStr))
{

View File

@@ -13,7 +13,7 @@ namespace Robust.Client.UserInterface
return Task.FromResult<Stream?>(null);
}
public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null)
public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true)
{
return Task.FromResult<(Stream fileStream, bool alreadyExisted)?>(null);
}

View File

@@ -51,7 +51,7 @@ namespace Robust.Client.UserInterface
return await OpenFileNfd(filters);
}
public async Task<(Stream, bool)?> SaveFile(FileDialogFilters? filters)
public async Task<(Stream, bool)?> SaveFile(FileDialogFilters? filters, bool truncate = true)
{
var name = await GetSaveFileName(filters);
if (name == null)
@@ -61,7 +61,7 @@ namespace Robust.Client.UserInterface
try
{
return (File.Open(name, FileMode.Open), true);
return (File.Open(name, truncate ? FileMode.Truncate : FileMode.Open), true);
}
catch (FileNotFoundException)
{

View File

@@ -28,6 +28,7 @@ namespace Robust.Client.UserInterface
/// The file stream the user chose to save to, and whether the file already existed.
/// Null if the user cancelled the action.
/// </returns>
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null);
/// <param name="truncate">Should we truncate an existing file to 0-size then write or append.</param>
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true);
}
}

View File

@@ -114,8 +114,10 @@ internal partial class UserInterfaceManager
args.Handle();
}
// Attempt to ensure that keybind-up events only get raised after a single keybind-down.
DebugTools.Assert(!_focusedControls.ContainsKey(args.Function));
// Attempt to ensure that keybind-up events get raised after a keybind-down.
DebugTools.Assert(!_focusedControls.TryGetValue(args.Function, out var existing)
|| !existing.VisibleInTree
|| args.IsRepeat && existing == control);
_focusedControls[args.Function] = control;
OnKeyBindDown?.Invoke(control);
@@ -124,7 +126,7 @@ internal partial class UserInterfaceManager
public void KeyBindUp(BoundKeyEventArgs args)
{
// Only raise keybind-up for the control on which we previously raised keybind-down
if (!_focusedControls.Remove(args.Function, out var control) || control.Disposed)
if (!_focusedControls.Remove(args.Function, out var control) || !control.VisibleInTree)
return;
var guiArgs = new GUIBoundKeyEventArgs(args.Function, args.State, args.PointerLocation, args.CanFocus,

View File

@@ -29,6 +29,7 @@ public static class Diagnostics
public const string IdComponentPauseNoParentAttribute = "RA0023";
public const string IdComponentPauseWrongTypeAttribute = "RA0024";
public const string IdDependencyFieldAssigned = "RA0025";
public const string IdUncachedRegex = "RA0026";
public static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");

View File

@@ -51,10 +51,15 @@ internal sealed class PvsSession(ICommonSession session, ResizableMemoryRegion<P
public (GameTick ToTick, List<PvsIndex> PreviouslySent)? LastSent;
/// <summary>
/// Visible chunks, sorted by proximity to the clients's viewers;
/// Visible chunks, sorted by proximity to the client's viewers.
/// </summary>
public readonly List<(PvsChunk Chunk, float ChebyshevDistance)> Chunks = new();
/// <summary>
/// Unsorted set of visible chunks. Used to construct the <see cref="Chunks"/> list.
/// </summary>
public readonly HashSet<PvsChunk> ChunkSet = new();
/// <summary>
/// Squared distance ta all of the visible chunks.
/// </summary>
@@ -117,6 +122,7 @@ internal sealed class PvsSession(ICommonSession session, ResizableMemoryRegion<P
{
PlayerStates.Clear();
Chunks.Clear();
ChunkSet.Clear();
States.Clear();
State = null;
}

View File

@@ -90,15 +90,13 @@ internal sealed partial class PvsSystem
foreach (var session in _sessions)
{
session.Chunks.Clear();
session.ChunkSet.Clear();
GetSessionViewers(session);
foreach (var eye in session.Viewers)
{
GetVisibleChunks(eye, session.Chunks);
GetVisibleChunks(eye, session.ChunkSet);
}
// The list of visible chunks should be unique.
DebugTools.Assert(session.Chunks.Select(x => x.Chunk).ToHashSet().Count == session.Chunks.Count);
}
DebugTools.Assert(_dirtyChunks.ToHashSet().Count == _dirtyChunks.Count);
DebugTools.Assert(_cleanChunks.ToHashSet().Count == _cleanChunks.Count);
@@ -108,7 +106,7 @@ internal sealed partial class PvsSystem
/// Get the chunks visible to a single entity and add them to a player's set of visible chunks.
/// </summary>
private void GetVisibleChunks(Entity<TransformComponent, EyeComponent?> eye,
List<(PvsChunk Chunk, float ChebyshevDistance)> playerChunks)
HashSet<PvsChunk> chunks)
{
var (viewPos, range, mapUid) = CalcViewBounds(eye);
if (mapUid is not {} map)
@@ -121,7 +119,7 @@ internal sealed partial class PvsSystem
if (!_chunks.TryGetValue(loc, out var chunk))
continue;
playerChunks.Add((chunk, default));
chunks.Add(chunk);
if (chunk.UpdateQueued)
continue;
@@ -147,7 +145,7 @@ internal sealed partial class PvsSystem
if (!_chunks.TryGetValue(loc, out var chunk))
continue;
playerChunks.Add((chunk, default));
chunks.Add(chunk);
if (chunk.UpdateQueued)
continue;

View File

@@ -137,15 +137,19 @@ internal sealed partial class PvsSystem
if (!CullingEnabled || session.DisableCulling)
return;
var chunkSet = session.ChunkSet;
var chunks = session.Chunks;
var distances = session.ChunkDistanceSq;
DebugTools.AssertEqual(chunks.Count, 0);
distances.Clear();
distances.EnsureCapacity(chunks.Count);
distances.EnsureCapacity(chunkSet.Count);
chunks.EnsureCapacity(chunkSet.Count);
// Assemble list of chunks and their distances to the nearest eye.
foreach (ref var tuple in CollectionsMarshal.AsSpan(chunks))
foreach(var chunk in chunkSet)
{
var chunk = tuple.Chunk;
var dist = float.MaxValue;
var chebDist = float.MaxValue;
@@ -165,7 +169,7 @@ internal sealed partial class PvsSystem
}
distances.Add(dist);
tuple.ChebyshevDistance = chebDist;
chunks.Add((chunk, chebDist));
}
// Sort chunks based on distances

View File

@@ -68,7 +68,7 @@ namespace Robust.Server.ServerStatus
if (auth != _watchdogToken)
{
// Holy shit nobody read these logs please.
_sawmill.Info(@"Failed auth: ""{0}"" vs ""{1}""", auth, _watchdogToken);
_sawmill.Verbose(@"Failed auth: ""{0}"" vs ""{1}""", auth, _watchdogToken);
await context.RespondErrorAsync(HttpStatusCode.Unauthorized);
return true;
}

View File

@@ -368,6 +368,21 @@ namespace Robust.Shared
public static readonly CVarDef<float> NetHappyEyeballsDelay =
CVarDef.Create("net.happy_eyeballs_delay", 0.025f, CVar.CLIENTONLY);
/// <summary>
/// Controls whether the networking library will log warning messages.
/// </summary>
/// <remarks>
/// Disabling this should make the networking layer more resilient against some DDoS attacks.
/// </remarks>
public static readonly CVarDef<bool> NetLidgrenLogWarning =
CVarDef.Create("net.lidgren_log_warning", true);
/// <summary>
/// Controls whether the networking library will log error messages.
/// </summary>
public static readonly CVarDef<bool> NetLidgrenLogError =
CVarDef.Create("net.lidgren_log_error", true);
/**
* SUS
*/

View File

@@ -294,6 +294,9 @@ public struct ValueList<T> : IEnumerable<T>
if (Capacity < capacity)
Grow(capacity);
if (capacity == 0)
return capacity;
return _items!.Length;
}

View File

@@ -442,7 +442,14 @@ Types:
AddressFamily: { }
System.Numerics:
BitOperations: { All: True }
Complex: { All: True }
Matrix3x2: { All: True }
Matrix4x4: { All: True }
Plane: { All: True }
Quaternion: { All: True }
Vector2: { All: True }
Vector3: { All: True }
Vector4: { All: True }
System.Reflection:
Assembly:
Methods:

View File

@@ -289,6 +289,12 @@ namespace Robust.Shared.GameObjects
return GetRegistration(componentType).Name;
}
[Pure]
public string GetComponentName<T>() where T : IComponent, new()
{
return GetRegistration<T>().Name;
}
[Pure]
public string GetComponentName(ushort netID)
{
@@ -324,7 +330,7 @@ namespace Robust.Shared.GameObjects
public ComponentRegistration GetRegistration<T>() where T : IComponent, new()
{
return GetRegistration(typeof(T));
return GetRegistration(CompIdx.Index<T>());
}
public ComponentRegistration GetRegistration(IComponent component)

View File

@@ -2,45 +2,35 @@ using System;
using System.Collections.Generic;
using Robust.Shared.Enums;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects.Components.Localization
namespace Robust.Shared.GameObjects.Components.Localization;
/// <summary>
/// Overrides grammar attributes specified in prototypes or localization files.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
// [Access(typeof(GrammarSystem))] TODO access
public sealed partial class GrammarComponent : Component
{
/// <summary>
/// Overrides grammar attributes specified in prototypes or localization files.
/// </summary>
[RegisterComponent]
[NetworkedComponent()]
public sealed partial class GrammarComponent : Component
[DataField, AutoNetworkedField]
public Dictionary<string, string> Attributes = new();
[ViewVariables]
public Gender? Gender
{
[DataField("attributes")]
public Dictionary<string, string> Attributes { get; private set; } = new();
get => Attributes.TryGetValue("gender", out var g) ? Enum.Parse<Gender>(g, true) : null;
[Obsolete("Use GrammarSystem.SetGender instead")]
set => IoCManager.Resolve<IEntityManager>().System<GrammarSystem>().SetGender((Owner, this), value);
}
[ViewVariables]
public Gender? Gender
{
get => Attributes.TryGetValue("gender", out var g) ? Enum.Parse<Gender>(g, true) : null;
set
{
if (value.HasValue)
Attributes["gender"] = value.Value.ToString();
else
Attributes.Remove("gender");
}
}
[ViewVariables]
public bool? ProperNoun
{
get => Attributes.TryGetValue("proper", out var g) ? bool.Parse(g) : null;
set
{
if (value.HasValue)
Attributes["proper"] = value.Value.ToString();
else
Attributes.Remove("proper");
}
}
[ViewVariables]
public bool? ProperNoun
{
get => Attributes.TryGetValue("proper", out var g) ? bool.Parse(g) : null;
[Obsolete("Use GrammarSystem.SetProperNoun instead")]
set => IoCManager.Resolve<IEntityManager>().System<GrammarSystem>().SetProperNoun((Owner, this), value);
}
}

View File

@@ -0,0 +1,39 @@
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects.Components.Localization;
namespace Robust.Shared.GameObjects;
public sealed class GrammarSystem : EntitySystem
{
public void Clear(Entity<GrammarComponent> grammar)
{
grammar.Comp.Attributes.Clear();
Dirty(grammar);
}
public bool TryGet(Entity<GrammarComponent> grammar, string key, [NotNullWhen(true)] out string? value)
{
return grammar.Comp.Attributes.TryGetValue(key, out value);
}
public void Set(Entity<GrammarComponent> grammar, string key, string? value)
{
if (value == null)
grammar.Comp.Attributes.Remove(key);
else
grammar.Comp.Attributes[key] = value;
Dirty(grammar);
}
public void SetGender(Entity<GrammarComponent> grammar, Gender? gender)
{
Set(grammar, "gender", gender?.ToString());
}
public void SetProperNoun(Entity<GrammarComponent> grammar, bool? proper)
{
Set(grammar, "proper", proper?.ToString());
}
}

View File

@@ -19,7 +19,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// The last received state object sent from the server.
/// </summary>
protected BoundUserInterfaceState? State { get; private set; }
protected internal BoundUserInterfaceState? State { get; internal set; }
protected BoundUserInterface(EntityUid owner, Enum uiKey)
{

View File

@@ -167,7 +167,7 @@ namespace Robust.Shared.GameObjects
if (eventHandler == null)
throw new ArgumentNullException(nameof(eventHandler));
var order = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
var order = CreateOrderingData(orderType, before, after);
SubscribeEventCommon<T>(source, subscriber,
(ref Unit ev) => eventHandler(Unsafe.As<Unit, T>(ref ev)), eventHandler, order, false);
@@ -187,7 +187,7 @@ namespace Robust.Shared.GameObjects
EntityEventRefHandler<T> eventHandler,
Type orderType, Type[]? before = null, Type[]? after = null) where T : notnull
{
var order = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
var order = CreateOrderingData(orderType, before, after);
SubscribeEventCommon<T>(source, subscriber, (ref Unit ev) =>
{

View File

@@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using Robust.Shared.Collections;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
@@ -13,6 +14,7 @@ internal sealed partial class EntityEventBus : IEventBus
{
private IEntityManager _entMan;
private IComponentFactory _comFac;
private IReflectionManager _reflection;
// Data on individual events. Used to check ordering info and fire broadcast events.
private FrozenDictionary<Type, EventData> _eventData = FrozenDictionary<Type, EventData>.Empty;
@@ -29,26 +31,36 @@ internal sealed partial class EntityEventBus : IEventBus
// See EventTable declaration for layout details
internal Dictionary<EntityUid, EventTable> _entEventTables = new();
// CompType -> EventType -> Handler
internal FrozenDictionary<Type, DirectedRegistration>?[] _entSubscriptions = default!;
/// <summary>
/// Array of component events and their handlers. The array is indexed by a component's
/// <see cref="CompIdx.Value"/>, while the dictionary is indexed by the event type. This does not include events
/// with the <see cref="ComponentEventAttribute"/>
/// </summary>
internal FrozenDictionary<Type, DirectedRegistration>[] _eventSubs = default!;
// Variant of _entSubscriptions that omits any events with the ComponentEventAttribute
internal FrozenDictionary<Type, DirectedRegistration>?[] _entSubscriptionsNoCompEv = default!;
/// <summary>
/// Variant of <see cref="_eventSubs"/> that also includes events with the <see cref="ComponentEventAttribute"/>
/// </summary>
internal FrozenDictionary<Type, DirectedRegistration>[] _compEventSubs = default!;
// pre-freeze _entSubscriptions data
internal Dictionary<Type, DirectedRegistration>?[] _entSubscriptionsUnfrozen =
Array.Empty<Dictionary<Type, DirectedRegistration>?>();
// pre-freeze event subscription data
internal Dictionary<Type, DirectedRegistration>?[] _eventSubsUnfrozen =
Array.Empty<Dictionary<Type, DirectedRegistration>>();
// EventType -> { CompType1, ... CompType N }
/// <summary>
/// Inverse of <see cref="_eventSubs"/>, mapping event types to sets of components.
/// </summary>
private Dictionary<Type, HashSet<CompIdx>> _eventSubsInv = new();
// Only required to sort ordered subscriptions, which only happens during initialization
// so doesn't need to be a frozen dictionary.
private Dictionary<Type, HashSet<CompIdx>> _entSubscriptionsInv = new();
// prevents shitcode, get your subscriptions figured out before you start spawning entities
private bool _subscriptionLock;
public bool IgnoreUnregisteredComponents;
private readonly List<Type> _childrenTypesTemp = [];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ref Unit ExtractUnitRef(ref object obj, Type objType)
{

View File

@@ -6,6 +6,7 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.Collections;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects
@@ -117,10 +118,12 @@ namespace Robust.Shared.GameObjects
/// Constructs a new instance of <see cref="EntityEventBus"/>.
/// </summary>
/// <param name="entMan">The entity manager to watch for entity/component events.</param>
public EntityEventBus(IEntityManager entMan)
/// <param name="reflection">The reflection manager to use when finding derived types.</param>
public EntityEventBus(IEntityManager entMan, IReflectionManager reflection)
{
_entMan = entMan;
_comFac = entMan.ComponentFactory;
_reflection = reflection;
// Dynamic handling of components is only for RobustUnitTest compatibility spaghetti.
_comFac.ComponentsAdded += ComFacOnComponentsAdded;
@@ -248,7 +251,7 @@ namespace Robust.Shared.GameObjects
void EventHandler(EntityUid uid, IComponent comp, ref TEvent args)
=> handler(uid, (TComp)comp, args);
var orderData = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
var orderData = CreateOrderingData(orderType, before, after);
EntSubscribe<TEvent>(
CompIdx.Index<TComp>(),
@@ -281,7 +284,7 @@ namespace Robust.Shared.GameObjects
void EventHandler(EntityUid uid, IComponent comp, ref TEvent args)
=> handler(uid, (TComp)comp, ref args);
var orderData = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
var orderData = CreateOrderingData(orderType, before, after);
EntSubscribe<TEvent>(
CompIdx.Index<TComp>(),
@@ -300,7 +303,7 @@ namespace Robust.Shared.GameObjects
void EventHandler(EntityUid uid, IComponent comp, ref TEvent args)
=> handler(new Entity<TComp>(uid, (TComp) comp), ref args);
var orderData = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
var orderData = CreateOrderingData(orderType, before, after);
EntSubscribe<TEvent>(
CompIdx.Index<TComp>(),
@@ -327,7 +330,7 @@ namespace Robust.Shared.GameObjects
foreach (var reg in regs)
{
CompIdx.RefArray(ref _entSubscriptionsUnfrozen, reg.Idx) ??= new();
CompIdx.RefArray(ref _eventSubsUnfrozen, reg.Idx) ??= new();
}
}
@@ -351,29 +354,33 @@ namespace Robust.Shared.GameObjects
_subscriptionLock = true;
_eventData = _eventDataUnfrozen.ToFrozenDictionary();
_entSubscriptions = _entSubscriptionsUnfrozen
.Select(x => x?.ToFrozenDictionary())
// Find last non-null entry.
var last = 0;
for (var i = 0; i < _eventSubsUnfrozen.Length; i++)
{
var entry = _eventSubsUnfrozen[i];
if (entry != null)
last = i;
}
// TODO PERFORMANCE
// make this only contain events that actually use comp-events
// Assuming it makes the frozen dictionaries more specialized and thus faster.
// AFAIK currently only MapInit is both a comp-event and a general event.
// It should probably be changed to just be a comp event.
_compEventSubs = _eventSubsUnfrozen
.Take(last+1)
.Select(dict => dict?.ToFrozenDictionary()!)
.ToArray();
_entSubscriptionsNoCompEv = _entSubscriptionsUnfrozen.Select(FreezeWithoutComponentEvent).ToArray();
_eventSubs = _eventSubsUnfrozen
.Take(last+1)
.Select(dict => dict?.Where(x => !IsComponentEvent(x.Key)).ToFrozenDictionary()!)
.ToArray();
CalcOrdering();
}
/// <summary>
/// Freezes a dictionary while committing events with the <see cref="ComponentEventAttribute"/>.
/// This avoids unnecessarily adding one-off events to the list of subscriptions.
/// </summary>
private FrozenDictionary<Type, DirectedRegistration>? FreezeWithoutComponentEvent(
Dictionary<Type, DirectedRegistration>? input)
{
if (input == null)
return null;
return input.Where(x => !IsComponentEvent(x.Key))
.ToFrozenDictionary();
}
private bool IsComponentEvent(Type t)
{
var isCompEv = _eventData[t].ComponentEvent;
@@ -395,8 +402,8 @@ namespace Robust.Shared.GameObjects
if (_subscriptionLock)
throw new InvalidOperationException("Subscription locked.");
if (compType.Value >= _entSubscriptionsUnfrozen.Length
|| _entSubscriptionsUnfrozen[compType.Value] is not { } compSubs)
if (compType.Value >= _eventSubsUnfrozen.Length
|| _eventSubsUnfrozen[compType.Value] is not { } compSubs)
{
if (IgnoreUnregisteredComponents)
return;
@@ -411,10 +418,11 @@ namespace Robust.Shared.GameObjects
}
compSubs.Add(eventType, registration);
_entSubscriptionsInv.GetOrNew(eventType).Add(compType);
RegisterCommon(eventType, registration.Ordering, out var data);
data.ComponentEvent = eventType.HasCustomAttribute<ComponentEventAttribute>();
if (!data.ComponentEvent)
_eventSubsInv.GetOrNew(eventType).Add(compType);
}
private void EntSubscribe<TEvent>(
@@ -438,8 +446,8 @@ namespace Robust.Shared.GameObjects
if (_subscriptionLock)
throw new InvalidOperationException("Subscription locked.");
if (compType.Value >= _entSubscriptionsUnfrozen.Length
|| _entSubscriptionsUnfrozen[compType.Value] is not { } compSubs)
if (compType.Value >= _eventSubsUnfrozen.Length
|| _eventSubsUnfrozen[compType.Value] is not { } compSubs)
{
if (IgnoreUnregisteredComponents)
return;
@@ -449,7 +457,7 @@ namespace Robust.Shared.GameObjects
var removed = compSubs.Remove(eventType);
if (removed)
_entSubscriptionsInv[eventType].Remove(compType);
_eventSubsInv[eventType].Remove(compType);
}
private void EntAddEntity(EntityUid euid)
@@ -469,7 +477,7 @@ namespace Robust.Shared.GameObjects
DebugTools.Assert(_subscriptionLock);
var eventTable = _entEventTables[euid];
var compSubs = _entSubscriptionsNoCompEv[compType.Value]!;
var compSubs = _eventSubs[compType.Value];
foreach (var evType in compSubs.Keys)
{
@@ -528,13 +536,17 @@ namespace Robust.Shared.GameObjects
private void EntRemoveComponent(EntityUid euid, CompIdx compType)
{
var eventTable = _entEventTables[euid];
var compSubs = _entSubscriptions[compType.Value]!;
var compSubs = _eventSubs[compType.Value];
foreach (var evType in compSubs.Keys)
{
DebugTools.Assert(!_eventData[evType].ComponentEvent);
ref var dictIdx = ref CollectionsMarshal.GetValueRefOrNullRef(eventTable.EventIndices, evType);
if (Unsafe.IsNullRef(ref dictIdx))
{
DebugTools.Assert("This should not be possible. Were the events for this component never added?");
continue;
}
ref var updateNext = ref dictIdx;
@@ -610,9 +622,7 @@ namespace Robust.Shared.GameObjects
ref Unit args)
where TEvent : notnull
{
var compSubs = _entSubscriptions[baseType.Value]!;
if (compSubs.TryGetValue(typeof(TEvent), out var reg))
if (_compEventSubs[baseType.Value].TryGetValue(typeof(TEvent), out var reg))
reg.Handler(euid, component, ref args);
}
@@ -634,7 +644,7 @@ namespace Robust.Shared.GameObjects
return false;
}
enumerator = new(eventType, startEntry, eventTable.ComponentLists, _entSubscriptions, euid, _entMan);
enumerator = new(eventType, startEntry, eventTable.ComponentLists, _eventSubs, euid, _entMan);
return true;
}
@@ -644,10 +654,10 @@ namespace Robust.Shared.GameObjects
_eventDataUnfrozen.Clear();
_entEventTables.Clear();
_inverseEventSubscriptions.Clear();
_entSubscriptions = default!;
_entSubscriptionsNoCompEv = default!;
_compEventSubs = default!;
_eventSubs = default!;
_eventData = FrozenDictionary<Type, EventData>.Empty;
foreach (var sub in _entSubscriptionsUnfrozen)
foreach (var sub in _eventSubsUnfrozen)
{
sub?.Clear();
}
@@ -660,18 +670,19 @@ namespace Robust.Shared.GameObjects
// punishment for use-after-free
_entMan = null!;
_comFac = null!;
_reflection = null!;
_entEventTables = null!;
_entSubscriptions = null!;
_entSubscriptionsNoCompEv = null!;
_entSubscriptionsUnfrozen = null!;
_entSubscriptionsInv = null!;
_compEventSubs = null!;
_eventSubs = null!;
_eventSubsUnfrozen = null!;
_eventSubsInv = null!;
}
private struct SubscriptionsEnumerator
{
private readonly Type _eventType;
private readonly EntityUid _uid;
private readonly FrozenDictionary<Type, DirectedRegistration>?[] _subscriptions;
private readonly FrozenDictionary<Type, DirectedRegistration>[] _subscriptions;
private readonly IEntityManager _entityManager;
private readonly EventTableListEntry[] _list;
private int _idx;
@@ -680,7 +691,7 @@ namespace Robust.Shared.GameObjects
Type eventType,
int startEntry,
EventTableListEntry[] list,
FrozenDictionary<Type, DirectedRegistration>?[] subscriptions,
FrozenDictionary<Type, DirectedRegistration>[] subscriptions,
EntityUid uid,
IEntityManager entityManager)
{
@@ -707,7 +718,7 @@ namespace Robust.Shared.GameObjects
_idx = entry.Next;
var compType = entry.Component;
var compSubs = _subscriptions[compType.Value]!;
var compSubs = _subscriptions[compType.Value];
if (!compSubs.TryGetValue(_eventType, out registration))
{

View File

@@ -59,10 +59,10 @@ namespace Robust.Shared.GameObjects
// Collect all subscriptions, broadcast and ordered.
IEnumerable<OrderedRegistration> regs = sub.BroadcastRegistrations;
if (_entSubscriptionsInv.TryGetValue(eventType, out var comps))
if (_eventSubsInv.TryGetValue(eventType, out var comps))
{
regs = regs.Concat(comps
.Select(c => _entSubscriptions[c.Value])
.Select(c => _eventSubs[c.Value])
.Where(c => c != null)
.Select(c => c![eventType]));
}
@@ -200,5 +200,33 @@ namespace Robust.Shared.GameObjects
}
}
}
private OrderingData CreateOrderingData(Type orderType, Type[]? before, Type[]? after)
{
AddChildrenTypes(ref before);
AddChildrenTypes(ref after);
return new OrderingData(orderType, before ?? [], after ?? []);
}
private void AddChildrenTypes(ref Type[]? original)
{
if (original == null || original.Length == 0)
return;
_childrenTypesTemp.Clear();
foreach (var beforeType in original)
{
foreach (var child in _reflection.GetAllChildren(beforeType))
{
_childrenTypesTemp.Add(child);
}
}
if (_childrenTypesTemp.Count > 0)
{
Array.Resize(ref original, original.Length + _childrenTypesTemp.Count);
_childrenTypesTemp.CopyTo(original, original.Length - _childrenTypesTemp.Count);
}
}
}
}

View File

@@ -1586,6 +1586,13 @@ namespace Robust.Shared.GameObjects
return default;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public TComp1 Comp(EntityUid uid)
{
return GetComponent(uid);
}
#region Internal
/// <summary>

View File

@@ -15,6 +15,7 @@ using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using Robust.Shared.Profiling;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Timing;
@@ -40,6 +41,7 @@ namespace Robust.Shared.GameObjects
[IoC.Dependency] private readonly ISerializationManager _serManager = default!;
[IoC.Dependency] private readonly ProfManager _prof = default!;
[IoC.Dependency] private readonly INetManager _netMan = default!;
[IoC.Dependency] private readonly IReflectionManager _reflection = default!;
// I feel like PJB might shed me for putting a system dependency here, but its required for setting entity
// positions on spawn....
@@ -125,7 +127,7 @@ namespace Robust.Shared.GameObjects
if (Initialized)
throw new InvalidOperationException("Initialize() called multiple times");
_eventBus = new EntityEventBus(this);
_eventBus = new EntityEventBus(this, _reflection);
InitializeComponents();
_metaReg = _componentFactory.GetRegistration(typeof(MetaDataComponent));
@@ -281,6 +283,7 @@ namespace Robust.Shared.GameObjects
#region Entity Management
/// <inheritdoc />
public EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null)
{
return CreateEntity(prototypeName, out _, overrides);

View File

@@ -109,6 +109,17 @@ namespace Robust.Shared.GameObjects
_subscriptions.Add(new SubBroadcast<EntitySessionMessage<T>>(src));
}
protected void SubscribeLocalEvent<TComp, TEvent>(
EntityEventRefHandler<TComp, TEvent> handler,
Type[]? before = null, Type[]? after = null)
where TComp : IComponent
where TEvent : notnull
{
EntityManager.EventBus.SubscribeLocalEvent(handler, GetType(), before, after);
_subscriptions.Add(new SubLocal<TComp, TEvent>());
}
/// <seealso cref="SubscribeLocalEvent{TComp, TEvent}(ComponentEventRefHandler{TComp, TEvent}, Type[], Type[])"/>
// [Obsolete("Subscribe to the event by ref instead (ComponentEventRefHandler)")]
protected void SubscribeLocalEvent<TComp, TEvent>(
@@ -133,17 +144,6 @@ namespace Robust.Shared.GameObjects
_subscriptions.Add(new SubLocal<TComp, TEvent>());
}
protected void SubscribeLocalEvent<TComp, TEvent>(
EntityEventRefHandler<TComp, TEvent> handler,
Type[]? before = null, Type[]? after = null)
where TComp : IComponent
where TEvent : notnull
{
EntityManager.EventBus.SubscribeLocalEvent(handler, GetType(), before, after);
_subscriptions.Add(new SubLocal<TComp, TEvent>());
}
private void ShutdownSubscriptions()
{
foreach (var sub in _subscriptions)

View File

@@ -160,6 +160,9 @@ namespace Robust.Shared.GameObjects
[Pure]
string GetComponentName(Type componentType);
[Pure]
string GetComponentName<T>() where T : IComponent, new();
/// <summary>
/// Gets the name of a component, throwing an exception if it does not exist.
/// </summary>

View File

@@ -71,12 +71,43 @@ namespace Robust.Shared.GameObjects
/// </summary>
public event Action? AfterEntityFlush;
/// <summary>
/// Creates an uninitialized entity.
/// </summary>
/// <param name="prototypeName"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
/// <param name="euid">Does nothing. Used to be the forced EntityUid of the new entity.</param>
/// <param name="overrides"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
/// <returns><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></returns>
[Obsolete($"Use one of the other {nameof(CreateEntityUninitialized)} overloads. euid no longer does anything.")]
EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null);
/// <summary>
/// Creates an uninitialized entity.
/// </summary>
/// <param name="prototypeName"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
/// <param name="overrides"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
/// <returns><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></returns>
EntityUid CreateEntityUninitialized(string? prototypeName, ComponentRegistry? overrides = null);
/// <summary>
/// Creates an uninitialized entity and sets its position to the EntityCoordinates provided.
/// </summary>
/// <param name="prototypeName"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
/// <param name="coordinates">Coordinates to set position and parent of the newly spawned entity to.</param>
/// <param name="overrides"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
/// <returns><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></returns>
EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null);
/// <summary>
/// Creates an uninitialized entity and puts it on the grid or map at the MapCoordinates provided.
/// </summary>
/// <param name="prototypeName">Name of the <see cref="EntityPrototype"/> to spawn.</param>
/// <param name="coordinates">Coordinates to place the newly spawned entity.</param>
/// <param name="overrides">Overrides to add or remove components that differ from the prototype.</param>
/// <param name="rotation">Map rotation to set the newly spawned entity to.</param>
/// <returns>A new uninitialized entity.</returns>
/// <remarks>If there is a grid at the <paramref name="coordinates"/>, the entity will be parented to the grid.
/// Otherwise, it will be parented to the map.</remarks>
EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!);
void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null);

View File

@@ -24,7 +24,7 @@ internal sealed class PrototypeReloadSystem : EntitySystem
if (!eventArgs.ByType.TryGetValue(typeof(EntityPrototype), out var set))
return;
var query = EntityQueryEnumerator<MetaDataComponent>();
var query = AllEntityQuery<MetaDataComponent>();
while (query.MoveNext(out var uid, out var metadata))
{
var id = metadata.EntityPrototype?.ID;

View File

@@ -10,6 +10,7 @@ using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Reflection;
using Robust.Shared.Threading;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -20,6 +21,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
[Dependency] private readonly IDynamicTypeFactory _factory = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly IParallelManager _parallel = default!;
[Dependency] private readonly IReflectionManager _reflection = default!;
[Dependency] private readonly ISharedPlayerManager _player = default!;
[Dependency] private readonly SharedTransformSystem _transforms = default!;
@@ -29,6 +31,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
private EntityQuery<UserInterfaceComponent> _uiQuery;
private EntityQuery<UserInterfaceUserComponent> _userQuery;
private ActorRangeCheckJob _rangeJob;
public override void Initialize()
{
base.Initialize();
@@ -38,6 +42,12 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
_uiQuery = GetEntityQuery<UserInterfaceComponent>();
_userQuery = GetEntityQuery<UserInterfaceUserComponent>();
_rangeJob = new()
{
System = this,
XformQuery = _xformQuery,
};
SubscribeAllEvent<BoundUIWrapMessage>((msg, args) =>
{
if (args.SenderSession.AttachedEntity is not { } player)
@@ -48,6 +58,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
SubscribeLocalEvent<UserInterfaceComponent, OpenBoundInterfaceMessage>(OnUserInterfaceOpen);
SubscribeLocalEvent<UserInterfaceComponent, CloseBoundInterfaceMessage>(OnUserInterfaceClosed);
SubscribeLocalEvent<UserInterfaceComponent, ComponentStartup>(OnUserInterfaceStartup);
SubscribeLocalEvent<UserInterfaceComponent, ComponentShutdown>(OnUserInterfaceShutdown);
SubscribeLocalEvent<UserInterfaceComponent, ComponentGetState>(OnUserInterfaceGetState);
SubscribeLocalEvent<UserInterfaceComponent, ComponentHandleState>(OnUserInterfaceHandleState);
@@ -55,28 +66,10 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentShutdown>(OnActorShutdown);
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentGetStateAttemptEvent>(OnGetStateAttempt);
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentGetState>(OnActorGetState);
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentHandleState>(OnActorHandleState);
_player.PlayerStatusChanged += OnStatusChange;
}
private void OnStatusChange(object? sender, SessionStatusEventArgs e)
{
var attachedEnt = e.Session.AttachedEntity;
if (attachedEnt == null)
return;
// Content can't handle it yet sadly :(
CloseUserUis(attachedEnt.Value);
}
public override void Shutdown()
{
base.Shutdown();
_player.PlayerStatusChanged -= OnStatusChange;
}
/// <summary>
@@ -135,6 +128,11 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
#region User
private void OnActorShutdown(Entity<UserInterfaceUserComponent> ent, ref ComponentShutdown args)
{
CloseUserUis((ent.Owner, ent.Comp));
}
private void OnGetStateAttempt(Entity<UserInterfaceUserComponent> ent, ref ComponentGetStateAttemptEvent args)
{
if (args.Cancelled || args.Player?.AttachedEntity != ent.Owner)
@@ -207,11 +205,10 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
foreach (var key in keys)
{
if (!uiComp.ClientOpenInterfaces.TryGetValue(key, out var cBui))
if (!uiComp.ClientOpenInterfaces.Remove(key, out var cBui))
continue;
cBui.Dispose();
uiComp.ClientOpenInterfaces.Remove(key);
}
}
}
@@ -233,10 +230,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
Dirty(ent);
// If the actor is also deleting then don't worry about updating what they have open.
if (!TerminatingOrDeleted(actor))
if (!TerminatingOrDeleted(actor) && _userQuery.TryComp(actor, out var actorComp))
{
var actorComp = EnsureComp<UserInterfaceUserComponent>(actor);
if (actorComp.OpenInterfaces.TryGetValue(ent.Owner, out var keys))
{
keys.Remove(args.UiKey);
@@ -282,6 +277,20 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
EnsureClientBui(ent, args.UiKey, ent.Comp.Interfaces[args.UiKey]);
}
private void OnUserInterfaceStartup(Entity<UserInterfaceComponent> ent, ref ComponentStartup args)
{
// PlayerAttachedEvent will catch some of these.
foreach (var (key, bui) in ent.Comp.ClientOpenInterfaces)
{
bui.Open();
if (ent.Comp.States.TryGetValue(key, out var state))
{
bui.UpdateState(state);
}
}
}
private void OnUserInterfaceShutdown(EntityUid uid, UserInterfaceComponent component, ComponentShutdown args)
{
foreach (var bui in component.ClientOpenInterfaces.Values)
@@ -387,17 +396,21 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
if (!ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui))
continue;
cBui.State = buiState;
cBui.UpdateState(buiState);
}
// If UI not open then open it
var attachedEnt = _player.LocalEntity;
// If we get the first state for an ent coming in then don't open BUIs yet, just defer it until later.
var open = ent.Comp.LifeStage > ComponentLifeStage.Added;
if (attachedEnt != null)
{
foreach (var (key, value) in ent.Comp.Interfaces)
{
EnsureClientBui(ent, key, value);
EnsureClientBui(ent, key, value, open);
}
}
}
@@ -405,7 +418,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
/// <summary>
/// Opens a client's BUI if not already open and applies the state to it.
/// </summary>
private void EnsureClientBui(Entity<UserInterfaceComponent> entity, Enum key, InterfaceData data)
private void EnsureClientBui(Entity<UserInterfaceComponent> entity, Enum key, InterfaceData data, bool open = true)
{
// If it's out BUI open it up and apply the state, otherwise do nothing.
var player = _player.LocalEntity;
@@ -428,10 +441,16 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
var boundUserInterface = (BoundUserInterface) _factory.CreateInstance(type, [entity.Owner, key]);
entity.Comp.ClientOpenInterfaces[key] = boundUserInterface;
// This is just so we don't open while applying UI states.
if (!open)
return;
boundUserInterface.Open();
if (entity.Comp.States.TryGetValue(key, out var buiState))
{
boundUserInterface.State = buiState;
boundUserInterface.UpdateState(buiState);
}
}
@@ -877,6 +896,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
public override void Update(float frameTime)
{
var query = AllEntityQuery<ActiveUserInterfaceComponent, UserInterfaceComponent>();
// Run these in parallel because it's expensive.
_rangeJob.ActorRanges.Clear();
// Handles closing the BUI if actors move out of range of them.
while (query.MoveNext(out var uid, out _, out var uiComp))
@@ -890,24 +911,26 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
if (data.InteractionRange <= 0f || actors.Count == 0)
continue;
// Okay so somehow UISystem is high up on the server profile
// If that's actually still a problem turn this into an IParallelRobustJob and slam all the UIs in parallel.
var xform = _xformQuery.GetComponent(uid);
var coordinates = xform.Coordinates;
var mapId = xform.MapID;
for (var i = actors.Count - 1; i >= 0; i--)
foreach (var actor in actors)
{
var actor = actors[i];
if (CheckRange(uid, key, data, actor, coordinates, mapId))
continue;
// Using the non-predicted one here seems fine?
CloseUi((uid, uiComp), key, actor);
_rangeJob.ActorRanges.Add((uid, key, data, actor, false));
}
}
}
_parallel.ProcessNow(_rangeJob, _rangeJob.ActorRanges.Count);
foreach (var data in _rangeJob.ActorRanges)
{
var uid = data.Ui;
var actor = data.Actor;
var key = data.Key;
if (data.Result || Deleted(uid) || Deleted(actor) || !_uiQuery.TryComp(uid, out var uiComp))
continue;
CloseUi((uid, uiComp), key, actor);
}
}
/// <summary>
@@ -921,12 +944,12 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
EntityCoordinates uiCoordinates,
MapId uiMap)
{
if (!_xformQuery.TryGetComponent(actor, out var actorXform) || actorXform.MapID != uiMap)
return false;
if (_ignoreUIRangeQuery.HasComponent(actor))
return true;
if (!_xformQuery.TryGetComponent(actor, out var actorXform))
return false;
// Handle pluggable BoundUserInterfaceCheckRangeEvent
var checkRangeEvent = new BoundUserInterfaceCheckRangeEvent(uid, key, data, actor);
RaiseLocalEvent(uid, ref checkRangeEvent, broadcast: true);
@@ -944,6 +967,32 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
return uiCoordinates.InRange(EntityManager, _transforms, actorXform.Coordinates, data.InteractionRange);
}
/// <summary>
/// Used for running UI raycast checks in parallel.
/// </summary>
private record struct ActorRangeCheckJob() : IParallelRobustJob
{
public EntityQuery<TransformComponent> XformQuery;
public SharedUserInterfaceSystem System;
public readonly List<(EntityUid Ui, Enum Key, InterfaceData Data, EntityUid Actor, bool Result)> ActorRanges = new();
public void Execute(int index)
{
var data = ActorRanges[index];
if (!XformQuery.TryComp(data.Ui, out var uiXform))
{
data.Result = false;
}
else
{
data.Result = System.CheckRange(data.Ui, data.Key, data.Data, data.Actor, uiXform.Coordinates, uiXform.MapID);
}
ActorRanges[index] = data;
}
}
}
/// <summary>

View File

@@ -33,6 +33,11 @@ namespace Robust.Shared.Input
public bool Handled { get; private set; }
/// <summary>
/// Is this a repeated keypress (i.e., are they holding down the key)?
/// </summary>
public readonly bool IsRepeat;
/// <summary>
/// Constructs a new instance of <see cref="BoundKeyEventArgs"/>.
/// </summary>
@@ -47,6 +52,23 @@ namespace Robust.Shared.Input
CanFocus = canFocus;
}
/// <summary>
/// Constructs a new instance of <see cref="BoundKeyEventArgs"/>.
/// </summary>
/// <param name="function">Bound key that that is changing.</param>
/// <param name="state">New state of the function.</param>
/// <param name="pointerLocation">Current Pointer location in screen coordinates.</param>
/// <param name="isRepeat"></param>
public BoundKeyEventArgs(
BoundKeyFunction function,
BoundKeyState state,
ScreenCoordinates pointerLocation,
bool canFocus,
bool isRepeat = false) : this(function, state, pointerLocation, canFocus)
{
IsRepeat = isRepeat;
}
/// <summary>
/// Mark this event as handled.
/// </summary>

View File

@@ -17,6 +17,8 @@ namespace Robust.Shared.Localization
{
internal sealed partial class LocalizationManager
{
private static readonly Regex RegexWordMatch = new Regex(@"\w+");
private void AddBuiltInFunctions(FluentBundle bundle)
{
// Grammatical gender / pronouns
@@ -108,7 +110,7 @@ namespace Robust.Shared.Localization
var a = new LocValueString("a");
var an = new LocValueString("an");
var m = Regex.Match(input, @"\w+");
var m = RegexWordMatch.Match(input);
if (m.Success)
{
word = m.Groups[0].Value;

View File

@@ -55,5 +55,10 @@ namespace Robust.Shared.Map
/// </summary>
/// <param name="id">The new tile ID for this tile definition.</param>
void AssignTileId(ushort id);
/// <summary>
/// Allows you to hide tiles from the tile spawn menu.
/// </summary>
bool EditorHidden => false;
}
}

View File

@@ -249,6 +249,9 @@ namespace Robust.Shared.Network
IsServer = isServer;
_config.OnValueChanged(CVars.NetLidgrenLogWarning, LidgrenLogWarningChanged);
_config.OnValueChanged(CVars.NetLidgrenLogError, LidgrenLogErrorChanged);
_config.OnValueChanged(CVars.NetVerbose, NetVerboseChanged);
if (isServer)
{
@@ -272,6 +275,22 @@ namespace Robust.Shared.Network
}
}
private void LidgrenLogWarningChanged(bool newValue)
{
foreach (var netPeer in _netPeers)
{
netPeer.Peer.Configuration.SetMessageTypeEnabled(NetIncomingMessageType.WarningMessage, newValue);
}
}
private void LidgrenLogErrorChanged(bool newValue)
{
foreach (var netPeer in _netPeers)
{
netPeer.Peer.Configuration.SetMessageTypeEnabled(NetIncomingMessageType.ErrorMessage, newValue);
}
}
private void OnAuthModeChanged(int mode)
{
Auth = (AuthMode)mode;
@@ -422,6 +441,8 @@ namespace Robust.Shared.Network
_config.UnsubValueChanged(CVars.NetFakeLagMin, _fakeLagMinChanged);
_config.UnsubValueChanged(CVars.NetFakeLagRand, _fakeLagRandomChanged);
_config.UnsubValueChanged(CVars.NetFakeDuplicates, FakeDuplicatesChanged);
_config.UnsubValueChanged(CVars.NetLidgrenLogWarning, LidgrenLogWarningChanged);
_config.UnsubValueChanged(CVars.NetLidgrenLogError, LidgrenLogErrorChanged);
_serializer.ClientHandshakeComplete -= OnSerializerOnClientHandshakeComplete;
@@ -576,6 +597,14 @@ namespace Robust.Shared.Network
// ping the client once per second.
netConfig.PingInterval = 1f;
netConfig.SetMessageTypeEnabled(
NetIncomingMessageType.WarningMessage,
_config.GetCVar(CVars.NetLidgrenLogWarning));
netConfig.SetMessageTypeEnabled(
NetIncomingMessageType.ErrorMessage,
_config.GetCVar(CVars.NetLidgrenLogError));
var poolSize = _config.GetCVar(CVars.NetPoolSize);
if (poolSize <= 0)

View File

@@ -48,6 +48,9 @@ public abstract partial class SharedJointSystem
private void OnRelayShutdown(EntityUid uid, JointRelayTargetComponent component, ComponentShutdown args)
{
if (_gameTiming.ApplyingState)
return;
foreach (var relay in component.Relayed)
{
if (TerminatingOrDeleted(relay) || !_jointsQuery.TryGetComponent(relay, out var joint))

View File

@@ -1,4 +1,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
@@ -42,3 +45,59 @@ public readonly record struct EntProtoId(string Id) : IEquatable<string>, ICompa
public override string ToString() => Id ?? string.Empty;
}
/// <inheritdoc cref="EntProtoId"/>
[Serializable]
public readonly record struct EntProtoId<T>(string Id) : IEquatable<string>, IComparable<EntProtoId> where T : IComponent, new()
{
public static implicit operator string(EntProtoId<T> protoId)
{
return protoId.Id;
}
public static implicit operator EntProtoId(EntProtoId<T> protoId)
{
return new EntProtoId(protoId.Id);
}
public static implicit operator EntProtoId<T>(string id)
{
return new EntProtoId<T>(id);
}
public static implicit operator EntProtoId<T>?(string? id)
{
return id == null ? default(EntProtoId<T>?) : new EntProtoId<T>(id);
}
public bool Equals(string? other)
{
return Id == other;
}
public int CompareTo(EntProtoId other)
{
return string.Compare(Id, other.Id, StringComparison.Ordinal);
}
public override string ToString() => Id ?? string.Empty;
public T Get(IPrototypeManager? prototypes, IComponentFactory compFactory)
{
prototypes ??= IoCManager.Resolve<IPrototypeManager>();
var proto = prototypes.Index(this);
if (!proto.TryGetComponent(out T? comp, compFactory))
{
throw new ArgumentException($"{nameof(EntityPrototype)} {proto.ID} has no {nameof(T)}");
}
return comp;
}
public bool TryGet([NotNullWhen(true)] out T? comp, IPrototypeManager? prototypes, IComponentFactory compFactory)
{
prototypes ??= IoCManager.Resolve<IPrototypeManager>();
var proto = prototypes.Index(this);
return proto.TryGetComponent(out comp, compFactory);
}
}

View File

@@ -11,6 +11,7 @@ using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Prototypes
@@ -168,28 +169,37 @@ namespace Robust.Shared.Prototypes
_loc = IoCManager.Resolve<ILocalizationManager>();
}
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component, IComponentFactory? factory = null) where T : IComponent
[Obsolete("Pass in IComponentFactory")]
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component)
where T : IComponent
{
if (factory == null)
{
factory = IoCManager.Resolve<IComponentFactory>();
}
var compName = IoCManager.Resolve<IComponentFactory>().GetComponentName(typeof(T));
return TryGetComponent(compName, out component);
}
var compName = factory.GetComponentName(typeof(T));
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component, IComponentFactory factory) where T : IComponent, new()
{
var compName = factory.GetComponentName<T>();
return TryGetComponent(compName, out component);
}
public bool TryGetComponent<T>(string name, [NotNullWhen(true)] out T? component) where T : IComponent
{
DebugTools.AssertEqual(IoCManager.Resolve<IComponentFactory>().GetComponentName(typeof(T)), name);
if (!Components.TryGetValue(name, out var componentUnCast))
{
component = default;
return false;
}
// There are no duplicate component names
// TODO Sanity check with names being in an attribute of the type instead
component = (T) componentUnCast.Component;
if (componentUnCast.Component is not T cast)
{
component = default;
return false;
}
component = cast;
return true;
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization.Markdown;
@@ -440,5 +441,7 @@ namespace Robust.Shared.Serialization.Manager
}
#endregion
public bool TryGetVariableType(Type type, string variableName, [NotNullWhen(true)] out Type? variableType);
}
}

View File

@@ -264,6 +264,26 @@ namespace Robust.Shared.Serialization.Manager
return dataDefinition != null;
}
public bool TryGetVariableType(Type type, string variableName, [NotNullWhen(true)] out Type? variableType)
{
if (!TryGetDefinition(type, out var definition))
{
variableType = null;
return false;
}
var foundFieldDef = definition.BaseFieldDefinitions.FirstOrDefault(fieldDef => fieldDef?.Attribute is DataFieldAttribute attr && attr.Tag==variableName, null);
if(foundFieldDef != null)
{
variableType = foundFieldDef.BackingField.FieldType;
return true;
}
else
{
variableType = null;
return false;
}
}
private Type ResolveConcreteType(Type baseType, string typeName)
{
var type = ReflectionManager.YamlTypeTagLookup(baseType, typeName);

View File

@@ -1,8 +1,11 @@
using Robust.Shared.IoC;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
@@ -40,3 +43,49 @@ public sealed class EntProtoIdSerializer : ITypeSerializer<EntProtoId, ValueData
return source;
}
}
/// <summary>
/// Serializer used automatically for <see cref="EntProtoId"/> types.
/// </summary>
[TypeSerializer]
public sealed class EntProtoIdSerializer<T> : ITypeSerializer<EntProtoId<T>, ValueDataNode>, ITypeCopyCreator<EntProtoId<T>> where T : IComponent, new()
{
public ValidationNode Validate(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context = null)
{
var prototypes = dependencies.Resolve<IPrototypeManager>();
if (!prototypes.TryGetMapping(typeof(EntityPrototype), node.Value, out var mapping))
return new ErrorNode(node, $"No {nameof(EntityPrototype)} found with id {node.Value} that has a {typeof(T).Name}");
if (!mapping.TryGet("components", out SequenceDataNode? components))
return new ErrorNode(node, $"{nameof(EntityPrototype)} {node.Value} doesn't have a {typeof(T).Name}.");
var compFactory = dependencies.Resolve<IComponentFactory>();
var registration = compFactory.GetRegistration<T>();
foreach (var componentNode in components)
{
if (componentNode is MappingDataNode component &&
component.TryGet("type", out ValueDataNode? compName) &&
compName.Value == registration.Name)
{
return new ValidatedValueNode(node);
}
}
return new ErrorNode(node, $"{nameof(EntityPrototype)} {node.Value} doesn't have a {typeof(T).Name}.");
}
public EntProtoId<T> Read(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null, InstantiationDelegate<EntProtoId<T>>? instanceProvider = null)
{
return new EntProtoId<T>(node.Value);
}
public DataNode Write(ISerializationManager serialization, EntProtoId<T> value, IDependencyCollection dependencies, bool alwaysWrite = false, ISerializationContext? context = null)
{
return new ValueDataNode(value.Id);
}
public EntProtoId<T> CreateCopy(ISerializationManager serializationManager, EntProtoId<T> source, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
return source;
}
}

View File

@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Reflection;
using Robust.UnitTesting.Shared.Reflection;
namespace Robust.UnitTesting.Shared.GameObjects
@@ -21,6 +21,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
var compInstance = new MetaDataComponent();
var entManMock = new Mock<IEntityManager>();
var reflectMock = new Mock<IReflectionManager>();
compFactory.RegisterClass<MetaDataComponent>();
entManMock.Setup(m => m.ComponentFactory).Returns(compFactory);
@@ -35,7 +36,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
entManMock.Setup(m => m.GetComponentInternal(entUid, CompIdx.Index<MetaDataComponent>()))
.Returns(compInstance);
var bus = new EntityEventBus(entManMock.Object);
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
// Subscribe
@@ -80,6 +81,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
CompIdx.Index<MetaDataComponent>());
var compFacMock = new Mock<IComponentFactory>();
var reflectMock = new Mock<IReflectionManager>();
compFacMock.Setup(m => m.GetRegistration(CompIdx.Index<MetaDataComponent>())).Returns(compRegistration);
compFacMock.Setup(m => m.GetAllRegistrations()).Returns(new[] { compRegistration });
@@ -92,7 +94,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
entManMock.Setup(m => m.GetComponent(entUid, typeof(MetaDataComponent)))
.Returns(compInstance);
var bus = new EntityEventBus(entManMock.Object);
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
// Subscribe
@@ -137,6 +139,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
CompIdx.Index<MetaDataComponent>());
var compFacMock = new Mock<IComponentFactory>();
var reflectMock = new Mock<IReflectionManager>();
compFacMock.Setup(m => m.GetRegistration(CompIdx.Index<MetaDataComponent>())).Returns(compRegistration);
compFacMock.Setup(m => m.GetAllRegistrations()).Returns(new[] { compRegistration });
@@ -149,7 +152,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
entManMock.Setup(m => m.GetComponent(entUid, typeof(MetaDataComponent)))
.Returns(compInstance);
var bus = new EntityEventBus(entManMock.Object);
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
// Subscribe
@@ -184,6 +187,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
var entManMock = new Mock<IEntityManager>();
var compFacMock = new Mock<IComponentFactory>();
var reflectMock = new Mock<IReflectionManager>();
List<ComponentRegistration> allRefTypes = new();
void Setup<T>(out T instance) where T : IComponent, new()
@@ -208,7 +212,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
compFacMock.Setup(m => m.GetAllRegistrations()).Returns(allRefTypes.ToArray());
entManMock.Setup(m => m.ComponentFactory).Returns(compFacMock.Object);
var bus = new EntityEventBus(entManMock.Object);
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
// Subscribe

View File

@@ -2,6 +2,7 @@ using System;
using Moq;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Reflection;
namespace Robust.UnitTesting.Shared.GameObjects
{
@@ -12,8 +13,9 @@ namespace Robust.UnitTesting.Shared.GameObjects
{
var compFacMock = new Mock<IComponentFactory>();
var entManMock = new Mock<IEntityManager>();
var reflectMock = new Mock<IReflectionManager>();
entManMock.SetupGet(e => e.ComponentFactory).Returns(compFacMock.Object);
var bus = new EntityEventBus(entManMock.Object);
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
return bus;
}