Compare commits

..

2 Commits

Author SHA1 Message Date
DrSmugleaf
318548a4d6 Make setter internal 2024-05-07 20:51:01 -07:00
DrSmugleaf
dde4dd06f6 Fix never setting BoundUserInterface.State 2024-05-07 19:15:48 -07:00
103 changed files with 712 additions and 1949 deletions

View File

@@ -7,18 +7,6 @@ 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 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" />

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,144 +54,6 @@ END TEMPLATE-->
*None yet*
## 223.2.0
### New features
* Added several new `FormattedMessage` methods for better exception tolerance when parsing markup. Several existing methods have been marked as obsolete, with new renamed methods taking their place.
## 223.1.2
### Bugfixes
* `MapGridComponent.LastTileModifiedTick` is now actually networked to clients.
## 223.1.1
### Bugfixes
* Fixed an exception caused by enum cvars using integer type values instead of enum values
## 223.1.0
### Other
* Various `ContainerSystem` methods have been obsoleted in favour of overrides that take in an `Entity` struct instead of `EntityUid`
* Various `EntityCoordinates` methods have been obsoleted with replacements added to `SharedTransformSystem`
## 223.0.0
### Breaking changes
* The `ComponentState` class is now abstract. Networked components that don't have state information now just return a null state.
* The way that delta component states work has changed. It now expects there to be two different state classes, only one of which should implement `IComponentDeltaState<TFullState>`
### New features
* A new `replay.checkpoint_min_interval` cvar has been added. It can be used to limit the frequency at which checkpoints are generated when loading a replay.
* Added `IConfigurationManager.OnCVarValueChanged`. This is a c# event that gets invoked whenever any cvar value changes.
### Bugfixes
* `IEyeManager.GetWorldViewbounds()` and `IEyeManager.GetWorldViewbounds()` should now return the correct bounds if the main viewport does not take up the whole screen.
### Other
* The default values of various replay related cvars have been changed to try and reduce memory usage.
## 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

@@ -1,57 +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.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

@@ -6,7 +6,6 @@ using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
using Robust.Shared.Serialization.Manager.Definition;
namespace Robust.Analyzers;
@@ -57,18 +56,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
"Make sure to add a setter."
);
private static readonly DiagnosticDescriptor DataFieldRedundantTagRule = new(
Diagnostics.IdDataFieldRedundantTag,
"Data field has redundant tag specified",
"Data field {0} in data definition {1} has an explicitly set tag that matches autogenerated tag",
"Usage",
DiagnosticSeverity.Info,
true,
"Make sure to remove the tag string from the data field attribute."
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule,
DataFieldRedundantTagRule
DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule
);
public override void Initialize(AnalysisContext context)
@@ -136,11 +125,6 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
}
if (HasRedundantTag(fieldSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
}
}
}
@@ -165,11 +149,6 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
}
if (HasRedundantTag(propertySymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
}
}
private static bool IsReadOnlyDataField(ITypeSymbol type, ISymbol field)
@@ -269,29 +248,6 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
return false;
}
private static bool HasRedundantTag(ISymbol symbol)
{
if (!IsDataField(symbol, out var _, out var attribute))
return false;
// No args, no problem
if (attribute.ConstructorArguments.Length == 0)
return false;
// If a tag is explicitly specified, it will be the first argument...
var tagArgument = attribute.ConstructorArguments[0];
// ...but the first arg could also something else, since tag is optional
// so we make sure that it's a string
if (tagArgument.Value is not string explicitName)
return false;
// Get the name that sourcegen would provide
var automaticName = DataDefinitionUtility.AutoGenerateTag(symbol.Name);
// If the explicit name matches the sourcegen name, we have a redundancy
return explicitName == automaticName;
}
private static bool IsImplicitDataDefinition(ITypeSymbol type)
{
if (HasAttribute(type, ImplicitDataDefinitionNamespace))

View File

@@ -1,5 +1,8 @@
#nullable enable
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
@@ -13,11 +16,8 @@ namespace Robust.Analyzers;
[ExportCodeFixProvider(LanguageNames.CSharp)]
public sealed class DefinitionFixer : CodeFixProvider
{
private const string DataFieldAttributeName = "DataField";
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable,
IdDataFieldRedundantTag
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable
);
public override Task RegisterCodeFixesAsync(CodeFixContext context)
@@ -34,8 +34,6 @@ public sealed class DefinitionFixer : CodeFixProvider
return RegisterDataFieldFix(context, diagnostic);
case IdDataFieldPropertyWritable:
return RegisterDataFieldPropertyFix(context, diagnostic);
case IdDataFieldRedundantTag:
return RegisterRedundantTagFix(context, diagnostic);
}
}
@@ -74,68 +72,6 @@ public sealed class DefinitionFixer : CodeFixProvider
return document.WithSyntaxRoot(root);
}
private static async Task RegisterRedundantTagFix(CodeFixContext context, Diagnostic diagnostic)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
var span = diagnostic.Location.SourceSpan;
var token = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType<MemberDeclarationSyntax>().First();
if (token == null)
return;
// Find the DataField attribute
AttributeSyntax? dataFieldAttribute = null;
foreach (var attributeList in token.AttributeLists)
{
foreach (var attribute in attributeList.Attributes)
{
if (attribute.Name.ToString() == DataFieldAttributeName)
{
dataFieldAttribute = attribute;
break;
}
}
if (dataFieldAttribute != null)
break;
}
if (dataFieldAttribute == null)
return;
context.RegisterCodeFix(CodeAction.Create(
"Remove explicitly set tag",
c => RemoveRedundantTag(context.Document, dataFieldAttribute, c),
"Remove explicitly set tag"
), diagnostic);
}
private static async Task<Document> RemoveRedundantTag(Document document, AttributeSyntax syntax, CancellationToken cancellation)
{
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
if (syntax.ArgumentList == null)
return document;
AttributeSyntax? newSyntax;
if (syntax.ArgumentList.Arguments.Count == 1)
{
// If this is the only argument, delete the ArgumentList so we don't leave empty parentheses
newSyntax = syntax.RemoveNode(syntax.ArgumentList, SyntaxRemoveOptions.KeepNoTrivia);
}
else
{
// Remove the first argument, which is the tag
var newArgs = syntax.ArgumentList.Arguments.RemoveAt(0);
var newArgList = syntax.ArgumentList.WithArguments(newArgs);
// Construct a new attribute with the tag removed
newSyntax = syntax.WithArgumentList(newArgList);
}
root = root!.ReplaceNode(syntax, newSyntax!);
return document.WithSyntaxRoot(root);
}
private static async Task RegisterDataFieldFix(CodeFixContext context, Diagnostic diagnostic)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);

View File

@@ -1,66 +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 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

@@ -16,11 +16,6 @@
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
<ItemGroup>
<!-- Needed for DataDefinitionAnalyzer. -->
<Compile Include="..\Robust.Shared\Serialization\Manager\Definition\DataDefinitionUtility.cs" LinkBase="Implementations" />
</ItemGroup>
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
<PropertyGroup>

View File

@@ -239,7 +239,6 @@ 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

@@ -225,7 +225,7 @@ namespace Robust.Client.Debugging
{
var viewBounds = args.WorldBounds;
var viewAABB = args.WorldAABB;
var mapId = args.MapId;
var mapId = _eyeManager.CurrentMap;
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Shapes) != 0)
{
@@ -373,7 +373,7 @@ namespace Robust.Client.Debugging
private void DrawScreen(DrawingHandleScreen screenHandle, OverlayDrawArgs args)
{
var mapId = args.MapId;
var mapId = _eyeManager.CurrentMap;
var mousePos = _inputManager.MouseScreenPosition;
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ShapeInfo) != 0x0)

View File

@@ -120,7 +120,7 @@ namespace Robust.Client.GameObjects
{
var type = _serializer.FindSerializedType(typeof(BaseContainer), data.ContainerType);
container = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type!, inject:false);
container.Init(this, id, (uid, component));
InitContainer(container, (uid, component), id);
component.Containers.Add(id, container);
}

View File

@@ -63,7 +63,7 @@ namespace Robust.Client.GameObjects
protected internal override void Draw(in OverlayDrawArgs args)
{
var map = args.MapId;
var map = _eyeManager.CurrentMap;
if (map == MapId.Nullspace) return;
foreach (var (_, treeComp) in _trees.GetIntersectingTrees(map, args.WorldBounds))

View File

@@ -70,7 +70,7 @@ namespace Robust.Client.GameObjects
protected internal override void Draw(in OverlayDrawArgs args)
{
var currentMap = args.MapId;
var currentMap = _eyeManager.CurrentMap;
var viewport = args.WorldBounds;
var worldHandle = args.WorldHandle;

View File

@@ -54,11 +54,11 @@ namespace Robust.Client.GameStates
private readonly HashSet<NetEntity> _stateEnts = new();
private readonly List<EntityUid> _toDelete = new();
private readonly List<IComponent> _toRemove = new();
private readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> _outputData = new();
private readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState>> _outputData = new();
private readonly List<(EntityUid, TransformComponent)> _queuedBroadphaseUpdates = new();
private readonly ObjectPool<Dictionary<ushort, IComponentState?>> _compDataPool =
new DefaultObjectPool<Dictionary<ushort, IComponentState?>>(new DictPolicy<ushort, IComponentState?>(), 256);
private readonly ObjectPool<Dictionary<ushort, IComponentState>> _compDataPool =
new DefaultObjectPool<Dictionary<ushort, IComponentState>>(new DictPolicy<ushort, IComponentState>(), 256);
private uint _metaCompNetId;
@@ -259,7 +259,7 @@ namespace Robust.Client.GameStates
public void UpdateFullRep(GameState state, bool cloneDelta = false)
=> _processor.UpdateFullRep(state, cloneDelta);
public Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> GetFullRep()
public Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep()
=> _processor.GetFullRep();
private void HandlePvsLeaveMessage(MsgStateLeavePvs message)
@@ -600,12 +600,8 @@ namespace Robust.Client.GameStates
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" A component was dirtied: {comp.GetType()}");
if (compState != null)
{
var handleState = new ComponentHandleState(compState, null);
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
}
var handleState = new ComponentHandleState(compState, null);
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
comp.LastModifiedTick = _timing.LastRealTick;
}
}
@@ -637,12 +633,8 @@ namespace Robust.Client.GameStates
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" A component was removed: {comp.GetType()}");
if (state != null)
{
var stateEv = new ComponentHandleState(state, null);
_entities.EventBus.RaiseComponentEvent(comp, ref stateEv);
}
var stateEv = new ComponentHandleState(state, null);
_entities.EventBus.RaiseComponentEvent(comp, ref stateEv);
comp.ClearCreationTick(); // don't undo the re-adding.
comp.LastModifiedTick = _timing.LastRealTick;
}
@@ -695,7 +687,7 @@ namespace Robust.Client.GameStates
DebugTools.Assert(component.NetSyncEnabled);
var state = _entityManager.GetComponentState(bus, component, null, GameTick.Zero);
DebugTools.Assert(state is not IComponentDeltaState);
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
compData.Add(netId, state);
}
}
@@ -1357,9 +1349,6 @@ namespace Robust.Client.GameStates
foreach (var (comp, cur, next) in _compStateWork.Values)
{
if (cur == null && next == null)
continue;
var handleState = new ComponentHandleState(cur, next);
bus.RaiseComponentEvent(comp, ref handleState);
}
@@ -1512,9 +1501,6 @@ namespace Robust.Client.GameStates
_entityManager.AddComponent(uid, comp, true, meta);
}
if (state == null)
continue;
var handleState = new ComponentHandleState(state, null);
_entityManager.EventBus.RaiseComponentEvent(comp, ref handleState);
}

View File

@@ -32,7 +32,7 @@ namespace Robust.Client.GameStates
/// <summary>
/// This dictionary stores the full most recently received server state of any entity. This is used whenever predicted entities get reset.
/// </summary>
internal readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> _lastStateFullRep
internal readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState>> _lastStateFullRep
= new();
/// <inheritdoc />
@@ -212,7 +212,7 @@ Had full state: {LastFullState != null}"
{
if (!_lastStateFullRep.TryGetValue(entityState.NetEntity, out var compData))
{
compData = new();
compData = new Dictionary<ushort, IComponentState>();
_lastStateFullRep.Add(entityState.NetEntity, compData);
}
@@ -221,20 +221,21 @@ Had full state: {LastFullState != null}"
var compState = change.State;
if (compState is IComponentDeltaState delta
&& !delta.FullState
&& compData.TryGetValue(change.NetID, out var old)) // May fail if relying on implicit data
{
DebugTools.Assert(old is not IComponentDeltaState, "last state is not a full state");
DebugTools.Assert(old is IComponentDeltaState oldDelta && oldDelta.FullState, "last state is not a full state");
if (cloneDelta)
{
compState = delta.CreateNewFullState(old!);
compState = delta.CreateNewFullState(old);
}
else
{
delta.ApplyToFullState(old!);
delta.ApplyToFullState(old);
compState = old;
}
DebugTools.Assert(compState is not IComponentDeltaState, "newly constructed state is not a full state");
DebugTools.Assert(compState is IComponentDeltaState newState && newState.FullState, "newly constructed state is not a full state");
}
compData[change.NetID] = compState;
@@ -390,7 +391,7 @@ Had full state: {LastFullState != null}"
LastFullStateRequested = null;
}
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> implicitData)
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState>> implicitData)
{
foreach (var (netEntity, implicitEntState) in implicitData)
{
@@ -398,7 +399,6 @@ Had full state: {LastFullState != null}"
foreach (var (netId, implicitCompState) in implicitEntState)
{
DebugTools.Assert(implicitCompState is not IComponentDeltaState);
ref var serverState = ref CollectionsMarshal.GetValueRefOrAddDefault(fullRep, netId, out var exists);
if (!exists)
@@ -407,32 +407,36 @@ Had full state: {LastFullState != null}"
continue;
}
if (serverState is not IComponentDeltaState serverDelta)
if (serverState is not IComponentDeltaState serverDelta || serverDelta.FullState)
continue;
DebugTools.AssertNotNull(implicitCompState);
// Server sent an initial delta state. This is fine as long as the client can infer an initial full
// state from the entity prototype.
serverDelta.ApplyToFullState(implicitCompState!);
if (implicitCompState is not IComponentDeltaState implicitDelta || !implicitDelta.FullState)
{
_logger.Error($"Server sent delta state and client failed to construct an implicit full state for entity {netEntity}");
continue;
}
serverDelta.ApplyToFullState(implicitCompState);
serverState = implicitCompState;
DebugTools.Assert(serverState is not IComponentDeltaState);
DebugTools.Assert(implicitCompState is IComponentDeltaState d && d.FullState);
}
}
}
public Dictionary<ushort, IComponentState?> GetLastServerStates(NetEntity netEntity)
public Dictionary<ushort, IComponentState> GetLastServerStates(NetEntity netEntity)
{
return _lastStateFullRep[netEntity];
}
public Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> GetFullRep()
public Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep()
{
return _lastStateFullRep;
}
public bool TryGetLastServerStates(NetEntity entity,
[NotNullWhen(true)] out Dictionary<ushort, IComponentState?>? dictionary)
[NotNullWhen(true)] out Dictionary<ushort, IComponentState>? dictionary)
{
return _lastStateFullRep.TryGetValue(entity, out dictionary);
}

View File

@@ -113,7 +113,7 @@ namespace Robust.Client.GameStates
/// <summary>
/// Returns the full collection of cached game states that are used to reset predicted entities.
/// </summary>
Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> GetFullRep();
Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep();
/// <summary>
/// This will perform some setup in order to reset the game to an earlier state. To fully reset the state

View File

@@ -83,13 +83,13 @@ namespace Robust.Client.GameStates
/// The data to merge.
/// It's a dictionary of entity ID -> (component net ID -> ComponentState)
/// </param>
void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> data);
void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState>> data);
/// <summary>
/// Get the last state data from the server for an entity.
/// </summary>
/// <returns>Dictionary (net ID -> ComponentState)</returns>
Dictionary<ushort, IComponentState?> GetLastServerStates(NetEntity entity);
Dictionary<ushort, IComponentState> GetLastServerStates(NetEntity entity);
/// <summary>
/// Calculate the number of applicable states in the game state buffer from a given tick.
@@ -99,6 +99,6 @@ namespace Robust.Client.GameStates
int GetApplicableStateCount(GameTick? fromTick);
bool TryGetLastServerStates(NetEntity entity,
[NotNullWhen(true)] out Dictionary<ushort, IComponentState?>? dictionary);
[NotNullWhen(true)] out Dictionary<ushort, IComponentState>? dictionary);
}
}

View File

@@ -49,7 +49,7 @@ namespace Robust.Client.GameStates
while (query.MoveNext(out var uid, out var transform))
{
// if not on the same map, continue
if (transform.MapID != args.MapId || _container.IsEntityInContainer(uid))
if (transform.MapID != _eyeManager.CurrentMap || _container.IsEntityInContainer(uid))
continue;
if (transform.GridUid == uid)

View File

@@ -64,22 +64,29 @@ namespace Robust.Client.Graphics
/// <inheritdoc />
public Box2 GetWorldViewport()
{
return GetWorldViewbounds().CalcBoundingBox();
var vpSize = _displayManager.ScreenSize;
var topLeft = ScreenToMap(Vector2.Zero);
var topRight = ScreenToMap(new Vector2(vpSize.X, 0));
var bottomRight = ScreenToMap(vpSize);
var bottomLeft = ScreenToMap(new Vector2(0, vpSize.Y));
var left = MathHelper.Min(topLeft.X, topRight.X, bottomRight.X, bottomLeft.X);
var bottom = MathHelper.Min(topLeft.Y, topRight.Y, bottomRight.Y, bottomLeft.Y);
var right = MathHelper.Max(topLeft.X, topRight.X, bottomRight.X, bottomLeft.X);
var top = MathHelper.Max(topLeft.Y, topRight.Y, bottomRight.Y, bottomLeft.Y);
return new Box2(left, bottom, right, top);
}
/// <inheritdoc />
public Box2Rotated GetWorldViewbounds()
{
// This is an inefficient and roundabout way of geting the viewport.
// But its a method that shouldn't get used much.
var vp = MainViewport as Control;
var vpSize = vp?.PixelSize ?? _displayManager.ScreenSize;
var vpSize = _displayManager.ScreenSize;
var topRight = ScreenToMap(new Vector2(vpSize.X, 0)).Position;
var bottomLeft = ScreenToMap(new Vector2(0, vpSize.Y)).Position;
// This assumes the main viewports eye and the main eye are the same.
var rotation = new Angle(CurrentEye.Rotation);
var center = (bottomLeft + topRight) / 2;

View File

@@ -1,5 +1,4 @@
using System;
using System.Numerics;
using System.Numerics;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
@@ -14,29 +13,26 @@ namespace Robust.Client.Graphics
public interface IEyeManager
{
/// <summary>
/// The primary eye, which is usually the eye associated with the main viewport.
/// The current eye that is being used to render the game.
/// </summary>
/// <remarks>
/// Generally, you should avoid using this whenever possible. E.g., when rendering overlays should use the
/// eye & viewbounds that gets passed to the draw method.
/// Setting this property to null will use the default eye.
/// </remarks>
IEye CurrentEye { get; set; }
IViewportControl MainViewport { get; set; }
[Obsolete]
/// <summary>
/// The ID of the map on which the current eye is "placed".
/// </summary>
MapId CurrentMap { get; }
/// <summary>
/// A world-space box that is at LEAST the area covered by the main viewport.
/// A world-space box that is at LEAST the area covered by the viewport.
/// May be larger due to say rotation.
/// </summary>
Box2 GetWorldViewport();
/// <summary>
/// A world-space box of the area visible in the main viewport.
/// </summary>
Box2Rotated GetWorldViewbounds();
/// <summary>
@@ -47,7 +43,7 @@ namespace Robust.Client.Graphics
void GetScreenProjectionMatrix(out Matrix3 projMatrix);
/// <summary>
/// Projects a point from world space to UI screen space using the main viewport.
/// Projects a point from world space to UI screen space using the current camera.
/// </summary>
/// <param name="point">Point in world to transform.</param>
/// <returns>Corresponding point in UI screen space.</returns>

View File

@@ -480,7 +480,7 @@ namespace Robust.Client.Graphics.Clyde
var worldBounds = CalcWorldBounds(viewport);
var worldAABB = worldBounds.CalcBoundingBox();
if (eye.Position.MapId != MapId.Nullspace)
if (_eyeManager.CurrentMap != MapId.Nullspace)
{
using (DebugGroup("Lights"))
using (_prof.Group("Lights"))
@@ -544,12 +544,9 @@ namespace Robust.Client.Graphics.Clyde
UIBox2.FromDimensions(Vector2.Zero, viewport.Size), new Color(1, 1, 1, 0.5f));
}
if (eye.Position.MapId != MapId.Nullspace)
using (_prof.Group("Overlays WS"))
{
using (_prof.Group("Overlays WS"))
{
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldAABB, worldBounds);
}
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldAABB, worldBounds);
}
_currentViewport = oldVp;

View File

@@ -664,7 +664,7 @@ namespace Robust.Client.Graphics.Clyde
fixed (byte* p = buffer)
{
GL.GetTexImage(TextureTarget.Texture2D, 0, PF.Rgba, PT.UnsignedByte, (IntPtr) p);
GL.GetnTexImage(TextureTarget.Texture2D, 0, PF.Rgba, PT.UnsignedByte, bufSize, (IntPtr) p);
}
GL.BindTexture(TextureTarget.Texture2D, curTexture2D);

View File

@@ -346,7 +346,7 @@ namespace Robust.Client.Input
{
if (binding.CanRepeat)
{
return SetBindState(binding, BoundKeyState.Down, uiOnly, isRepeat);
return SetBindState(binding, BoundKeyState.Down, uiOnly);
}
return true;
@@ -375,7 +375,7 @@ namespace Robust.Client.Input
SetBindState(binding, BoundKeyState.Up);
}
private bool SetBindState(KeyBinding binding, BoundKeyState state, bool uiOnly = false, bool isRepeat = false)
private bool SetBindState(KeyBinding binding, BoundKeyState state, bool uiOnly = false)
{
if (binding.BindingType == KeyBindingType.Command && state == BoundKeyState.Down)
{
@@ -387,7 +387,6 @@ 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
{
@@ -400,7 +399,7 @@ namespace Robust.Client.Input
binding.State = state;
var eventArgs = new BoundKeyEventArgs(binding.Function, binding.State,
MouseScreenPosition, binding.CanFocus, isRepeat);
MouseScreenPosition, binding.CanFocus);
// 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

@@ -19,7 +19,7 @@ namespace Robust.Client.Placement.Modes
public SnapgridCenter(PlacementManager pMan) : base(pMan) { }
public override void Render(in OverlayDrawArgs args)
public override void Render(DrawingHandleWorld handle)
{
if (Grid != null)
{
@@ -34,18 +34,18 @@ namespace Robust.Client.Placement.Modes
{
var from = ScreenToWorld(new Vector2(a, 0));
var to = ScreenToWorld(new Vector2(a, viewportSize.Y));
args.WorldHandle.DrawLine(from, to, new Color(0, 0, 1f));
handle.DrawLine(from, to, new Color(0, 0, 1f));
}
for (var a = gridstart.Y; a < viewportSize.Y; a += SnapSize * EyeManager.PixelsPerMeter)
{
var from = ScreenToWorld(new Vector2(0, a));
var to = ScreenToWorld(new Vector2(viewportSize.X, a));
args.WorldHandle.DrawLine(from, to, new Color(0, 0, 1f));
handle.DrawLine(from, to, new Color(0, 0, 1f));
}
}
// Draw grid BELOW the ghost thing.
base.Render(args);
base.Render(handle);
}
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)

View File

@@ -628,20 +628,20 @@ namespace Robust.Client.Placement
return true;
}
private void Render(in OverlayDrawArgs args)
private void Render(DrawingHandleWorld handle)
{
if (CurrentMode == null || !IsActive)
{
if (EraserRect.HasValue)
{
args.WorldHandle.UseShader(_drawingShader);
args.WorldHandle.DrawRect(EraserRect.Value, new Color(255, 0, 0, 50));
args.WorldHandle.UseShader(null);
handle.UseShader(_drawingShader);
handle.DrawRect(EraserRect.Value, new Color(255, 0, 0, 50));
handle.UseShader(null);
}
return;
}
CurrentMode.Render(args);
CurrentMode.Render(handle);
if (CurrentPermission is not {Range: > 0} ||
!CurrentMode.RangeRequired ||
@@ -650,7 +650,7 @@ namespace Robust.Client.Placement
var worldPos = EntityManager.GetComponent<TransformComponent>(controlled).WorldPosition;
args.WorldHandle.DrawCircle(worldPos, CurrentPermission.Range, new Color(1, 1, 1, 0.25f));
handle.DrawCircle(worldPos, CurrentPermission.Range, new Color(1, 1, 1, 0.25f));
}
private void HandleStartPlacement(MsgPlacement msg)

View File

@@ -88,7 +88,7 @@ namespace Robust.Client.Placement
/// <returns></returns>
public abstract bool IsValidPosition(EntityCoordinates position);
public virtual void Render(in OverlayDrawArgs args)
public virtual void Render(DrawingHandleWorld handle)
{
var uid = pManager.CurrentPlacementOverlayEntity;
if (!pManager.EntityManager.TryGetComponent(uid, out SpriteComponent? sprite) || !sprite.Visible)
@@ -125,8 +125,7 @@ namespace Robust.Client.Placement
var worldRot = pManager.EntityManager.GetComponent<TransformComponent>(coordinate.EntityId).WorldRotation + dirAng;
sprite.Color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor;
var rot = args.Viewport.Eye?.Rotation ?? default;
spriteSys.Render(uid.Value, sprite, args.WorldHandle, rot, worldRot, worldPos);
spriteSys.Render(uid.Value, sprite, handle, pManager.EyeManager.CurrentEye.Rotation, worldRot, worldPos);
}
}

View File

@@ -19,7 +19,7 @@ namespace Robust.Client.Placement
protected internal override void Draw(in OverlayDrawArgs args)
{
_manager.Render(args);
_manager.Render(args.WorldHandle);
}
}
}

View File

@@ -133,11 +133,6 @@ public sealed partial class ReplayLoadManager
var spawnedTracker = 0;
var stateTracker = 0;
var curState = state0;
var stats_due_ticks = 0;
var stats_due_spawned = 0;
var stats_due_state = 0;
for (var i = 1; i < states.Count; i++)
{
if (i % 10 == 0)
@@ -155,28 +150,11 @@ public sealed partial class ReplayLoadManager
serverTime[i] = GetTime(curState.ToSequence) - initialTime;
ticksSinceLastCheckpoint++;
// Don't create checkpoints too frequently no matter the circumstance
if (ticksSinceLastCheckpoint < _checkpointMinInterval)
continue;
// Check if enough time, spawned entities or changed states have occurred.
if (ticksSinceLastCheckpoint < _checkpointInterval
&& spawnedTracker < _checkpointEntitySpawnThreshold
&& stateTracker < _checkpointEntityStateThreshold)
{
continue;
// Track and update statistics about why checkpoints are getting created:
if (ticksSinceLastCheckpoint >= _checkpointInterval)
{
stats_due_ticks += 1;
}
else if (spawnedTracker >= _checkpointEntitySpawnThreshold)
{
stats_due_spawned += 1;
}
else if (stateTracker >= _checkpointEntityStateThreshold)
{
stats_due_state += 1;
}
ticksSinceLastCheckpoint = 0;
@@ -191,8 +169,7 @@ public sealed partial class ReplayLoadManager
checkPoints.Add(new CheckpointState(newState, timeBase, cvars, i, detached));
}
_sawmill.Info($"Finished generating {checkPoints.Count} checkpoints. Elapsed time: {st.Elapsed}. Checkpoint every {(float)states.Count / checkPoints.Count} ticks on average");
_sawmill.Info($"Checkpoint stats - Spawning: {stats_due_spawned} StateChanges: {stats_due_state} Ticks: {stats_due_ticks}. ");
_sawmill.Info($"Finished generating checkpoints. Elapsed time: {st.Elapsed}");
await callback(states.Count, states.Count, LoadingState.ProcessingFiles, false);
return (checkPoints.ToArray(), serverTime);
}
@@ -363,7 +340,7 @@ public sealed partial class ReplayLoadManager
#if DEBUG
foreach (var state in modifiedState.ComponentChanges.Value)
{
DebugTools.Assert(state.State is not IComponentDeltaState delta);
DebugTools.Assert(state.State is not IComponentDeltaState delta || delta.FullState);
}
#endif
continue;
@@ -376,7 +353,7 @@ public sealed partial class ReplayLoadManager
#if DEBUG
foreach (var state in entStates[entState.NetEntity].ComponentChanges.Span)
{
DebugTools.Assert(state.State is not IComponentDeltaState delta);
DebugTools.Assert(state.State is not IComponentDeltaState delta || delta.FullState);
}
#endif
}
@@ -407,20 +384,20 @@ public sealed partial class ReplayLoadManager
if (!newCompStates.Remove(existing.NetID, out var newCompState))
continue;
if (newCompState.State is not IComponentDeltaState delta)
if (newCompState.State is not IComponentDeltaState delta || delta.FullState)
{
combined[index] = newCompState;
continue;
}
DebugTools.Assert(existing.State != null && existing.State is not IComponentDeltaState);
combined[index] = new ComponentChange(existing.NetID, delta.CreateNewFullState(existing.State!), newCompState.LastModifiedTick);
DebugTools.Assert(existing.State is IComponentDeltaState fullDelta && fullDelta.FullState);
combined[index] = new ComponentChange(existing.NetID, delta.CreateNewFullState(existing.State), newCompState.LastModifiedTick);
}
foreach (var compChange in newCompStates.Values)
{
// I'm not 100% sure about this, but I think delta states should always be full states here?
DebugTools.Assert(compChange.State is not IComponentDeltaState delta);
DebugTools.Assert(compChange.State is not IComponentDeltaState delta || delta.FullState);
combined.Add(compChange);
}

View File

@@ -45,7 +45,7 @@ public sealed partial class ReplayLoadManager
continue;
var state = _entMan.GetComponentState(_entMan.EventBus, component, null, GameTick.Zero);
DebugTools.Assert(state is not IComponentDeltaState);
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
list.Add(new ComponentChange(netId, state, GameTick.Zero));
set.Add(netId);
}
@@ -61,7 +61,7 @@ public sealed partial class ReplayLoadManager
{
if (comp.NetID == _metaId)
{
var state = (MetaDataComponentState) comp.State!;
var state = (MetaDataComponentState) comp.State;
return state.PrototypeId;
}
}

View File

@@ -34,7 +34,6 @@ public sealed partial class ReplayLoadManager : IReplayLoadManager
private ushort _metaId;
private bool _initialized;
private int _checkpointInterval;
private int _checkpointMinInterval;
private int _checkpointEntitySpawnThreshold;
private int _checkpointEntityStateThreshold;
private ISawmill _sawmill = default!;
@@ -46,7 +45,6 @@ public sealed partial class ReplayLoadManager : IReplayLoadManager
_initialized = true;
_confMan.OnValueChanged(CVars.CheckpointInterval, value => _checkpointInterval = value, true);
_confMan.OnValueChanged(CVars.CheckpointMinInterval, value => _checkpointMinInterval = value, true);
_confMan.OnValueChanged(CVars.CheckpointEntitySpawnThreshold, value => _checkpointEntitySpawnThreshold = value,
true);
_confMan.OnValueChanged(CVars.CheckpointEntityStateThreshold, value => _checkpointEntityStateThreshold = value,

View File

@@ -41,12 +41,12 @@ internal sealed partial class ReplayPlaybackManager
skipEffectEvents = true;
ResetToNearestCheckpoint(value, false);
}
else if (value > Replay.CurrentIndex + _checkpointMinInterval)
else if (value > Replay.CurrentIndex + _checkpointInterval)
{
// If we are skipping many ticks into the future, we try to skip directly to a checkpoint instead of
// applying every tick.
var nextCheckpoint = GetNextCheckpoint(Replay, Replay.CurrentIndex);
if (nextCheckpoint.Index < value && nextCheckpoint.Index > Replay.CurrentIndex)
if (nextCheckpoint.Index < value)
ResetToNearestCheckpoint(value, false);
}

View File

@@ -51,7 +51,7 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
public ReplayData? Replay { get; private set; }
public NetUserId? Recorder => Replay?.Recorder;
private int _checkpointMinInterval;
private int _checkpointInterval;
private int _visualEventThreshold;
public uint? AutoPauseCountdown { get; set; }
public int? ScrubbingTarget { get; set; }
@@ -93,7 +93,7 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
_initialized = true;
_sawmill = _logMan.GetSawmill("replay");
_metaId = _factory.GetRegistration(typeof(MetaDataComponent)).NetID!.Value;
_confMan.OnValueChanged(CVars.CheckpointMinInterval, (value) => _checkpointMinInterval = value, true);
_confMan.OnValueChanged(CVars.CheckpointInterval, (value) => _checkpointInterval = value, true);
_confMan.OnValueChanged(CVars.ReplaySkipThreshold, (value) => _visualEventThreshold = value, true);
_client.RunLevelChanged += OnRunLevelChanged;
}

View File

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

View File

@@ -53,7 +53,7 @@ namespace Robust.Client.UserInterface.Controls
private TimeSpan? _lastClickTime;
private Vector2? _lastClickPosition;
private bool IsPlaceHolderVisible => !(HidePlaceHolderOnFocus && HasKeyboardFocus()) && string.IsNullOrEmpty(_text) && _placeHolder != null;
private bool IsPlaceHolderVisible => string.IsNullOrEmpty(_text) && _placeHolder != null;
public event Action<LineEditEventArgs>? OnTextChanged;
public event Action<LineEditEventArgs>? OnTextEntered;
@@ -186,8 +186,6 @@ namespace Robust.Client.UserInterface.Controls
public int SelectionLower => Math.Min(_selectionStart, _cursorPosition);
public int SelectionUpper => Math.Max(_selectionStart, _cursorPosition);
public bool HidePlaceHolderOnFocus { get; set; }
public bool IgnoreNext { get; set; }
private (int start, int length)? _imeData;

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

View File

@@ -28,7 +28,6 @@ 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>
/// <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);
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null);
}
}

View File

@@ -114,10 +114,8 @@ internal partial class UserInterfaceManager
args.Handle();
}
// 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);
// Attempt to ensure that keybind-up events only get raised after a single keybind-down.
DebugTools.Assert(!_focusedControls.ContainsKey(args.Function));
_focusedControls[args.Function] = control;
OnKeyBindDown?.Invoke(control);
@@ -126,7 +124,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.VisibleInTree)
if (!_focusedControls.Remove(args.Function, out var control) || control.Disposed)
return;
var guiArgs = new GUIBoundKeyEventArgs(args.Function, args.State, args.PointerLocation, args.CanFocus,

View File

@@ -29,8 +29,6 @@ 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 const string IdDataFieldRedundantTag = "RA0027";
public static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");

View File

@@ -1,4 +1,6 @@
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -34,7 +36,6 @@ public class Generator : IIncrementalGenerator
var (compilation, declarations) = source;
var builder = new StringBuilder();
var containingTypes = new Stack<INamedTypeSymbol>();
var declarationsGenerated = new HashSet<string>();
foreach (var declaration in declarations)
{
@@ -43,14 +44,6 @@ public class Generator : IIncrementalGenerator
var type = compilation.GetSemanticModel(declaration.SyntaxTree).GetDeclaredSymbol(declaration)!;
var symbolName = type
.ToDisplayString()
.Replace('<', '{')
.Replace('>', '}');
if (!declarationsGenerated.Add(symbolName))
continue;
var nonPartial = !IsPartial(declaration);
var namespaceString = type.ContainingNamespace.IsGlobalNamespace
@@ -114,6 +107,11 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
{{containingTypesEnd}}
""");
var symbolName = type
.ToDisplayString()
.Replace('<', '{')
.Replace('>', '}');
var sourceText = CSharpSyntaxTree
.ParseText(builder.ToString())
.GetRoot()

View File

@@ -51,15 +51,10 @@ internal sealed class PvsSession(ICommonSession session, ResizableMemoryRegion<P
public (GameTick ToTick, List<PvsIndex> PreviouslySent)? LastSent;
/// <summary>
/// Visible chunks, sorted by proximity to the client's viewers.
/// Visible chunks, sorted by proximity to the clients'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>
@@ -122,7 +117,6 @@ internal sealed class PvsSession(ICommonSession session, ResizableMemoryRegion<P
{
PlayerStates.Clear();
Chunks.Clear();
ChunkSet.Clear();
States.Clear();
State = null;
}

View File

@@ -90,13 +90,15 @@ 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.ChunkSet);
GetVisibleChunks(eye, session.Chunks);
}
// 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);
@@ -106,7 +108,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,
HashSet<PvsChunk> chunks)
List<(PvsChunk Chunk, float ChebyshevDistance)> playerChunks)
{
var (viewPos, range, mapUid) = CalcViewBounds(eye);
if (mapUid is not {} map)
@@ -119,7 +121,7 @@ internal sealed partial class PvsSystem
if (!_chunks.TryGetValue(loc, out var chunk))
continue;
chunks.Add(chunk);
playerChunks.Add((chunk, default));
if (chunk.UpdateQueued)
continue;
@@ -145,7 +147,7 @@ internal sealed partial class PvsSystem
if (!_chunks.TryGetValue(loc, out var chunk))
continue;
chunks.Add(chunk);
playerChunks.Add((chunk, default));
if (chunk.UpdateQueued)
continue;

View File

@@ -50,11 +50,9 @@ internal sealed partial class PvsSystem
continue;
var state = EntityManager.GetComponentState(bus, component, player, fromTick);
DebugTools.Assert(fromTick > component.CreationTick || state is not IComponentDeltaState delta || delta.FullState);
changed.Add(new ComponentChange(netId, state, component.LastModifiedTick));
if (state != null)
DebugTools.Assert(fromTick > component.CreationTick || state is not IComponentDeltaState);
if (sendCompList)
netComps!.Add(netId);
}
@@ -87,7 +85,7 @@ internal sealed partial class PvsSystem
continue;
var state = EntityManager.GetComponentState(bus, component, player, GameTick.Zero);
DebugTools.Assert(state is not IComponentDeltaState);
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
changed.Add(new ComponentChange(netId, state, component.LastModifiedTick));
netComps.Add(netId);
}

View File

@@ -137,19 +137,15 @@ 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(chunkSet.Count);
chunks.EnsureCapacity(chunkSet.Count);
distances.EnsureCapacity(chunks.Count);
// Assemble list of chunks and their distances to the nearest eye.
foreach(var chunk in chunkSet)
foreach (ref var tuple in CollectionsMarshal.AsSpan(chunks))
{
var chunk = tuple.Chunk;
var dist = float.MaxValue;
var chebDist = float.MaxValue;
@@ -169,7 +165,7 @@ internal sealed partial class PvsSystem
}
distances.Add(dist);
chunks.Add((chunk, chebDist));
tuple.ChebyshevDistance = chebDist;
}
// Sort chunks based on distances

View File

@@ -43,9 +43,6 @@
<Content Include="server_config.toml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="run_server.bat">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<EmbeddedResource Include="ExtraMappedSerializerStrings.txt">
<LogicalName>Robust.Server.ExtraMappedSerializerStrings.txt</LogicalName>
</EmbeddedResource>

View File

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

View File

@@ -1,2 +0,0 @@
Robust.Server.exe
pause

View File

@@ -52,7 +52,7 @@ tags = ""
# Must be in the form of an ss14:// or ss14s:// URI pointing to the status API.
server_url = ""
# Comma-separated list of URLs of hub servers to advertise to.
hub_urls = "https://hub.spacestation14.com/"
hub_urls = "https://central.spacestation14.io/hub/"
[build]
# *Absolutely all of these can be supplied using a "build.json" file*
@@ -98,5 +98,5 @@ hub_urls = "https://hub.spacestation14.com/"
# You should probably never EVER need to touch this, but if you need a custom auth server,
# (the auth server being the one which manages Space Station 14 accounts), you change it here.
# server = https://auth.spacestation14.com/
# server = https://central.spacestation14.io/auth/

View File

@@ -368,21 +368,6 @@ 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
*/
@@ -899,7 +884,7 @@ namespace Robust.Shared
CVarDef.Create("render.sprite_direction_bias", -0.05, CVar.ARCHIVE | CVar.CLIENTONLY);
public static readonly CVarDef<string> RenderFOVColor =
CVarDef.Create("render.fov_color", Color.Black.ToHex(), CVar.REPLICATED | CVar.SERVER);
CVarDef.Create("render.fov_color", Color.Black.ToHex(), CVar.ARCHIVE | CVar.CLIENTONLY);
/*
* CONTROLS
@@ -1647,20 +1632,15 @@ namespace Robust.Shared
/// </summary>
public static readonly CVarDef<int> ReplaySkipThreshold = CVarDef.Create("replay.skip_threshold", 30);
/// <summary>
/// Minimum number of ticks before a new checkpoint tick is generated (overrides SpawnThreshold and StateThreshold)
/// </summary>
public static readonly CVarDef<int> CheckpointMinInterval = CVarDef.Create("replay.checkpoint_min_interval", 60);
/// <summary>
/// Maximum number of ticks before a new checkpoint tick is generated.
/// </summary>
public static readonly CVarDef<int> CheckpointInterval = CVarDef.Create("replay.checkpoint_interval", 500);
public static readonly CVarDef<int> CheckpointInterval = CVarDef.Create("replay.checkpoint_interval", 200);
/// <summary>
/// Maximum number of entities that can be spawned before a new checkpoint tick is generated.
/// </summary>
public static readonly CVarDef<int> CheckpointEntitySpawnThreshold = CVarDef.Create("replay.checkpoint_entity_spawn_threshold", 1000);
public static readonly CVarDef<int> CheckpointEntitySpawnThreshold = CVarDef.Create("replay.checkpoint_entity_spawn_threshold", 100);
/// <summary>
/// Maximum number of entity states that can be applied before a new checkpoint tick is generated.

View File

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

View File

@@ -32,8 +32,6 @@ namespace Robust.Shared.Configuration
private ISawmill _sawmill = default!;
public event Action<CVarChangeInfo>? OnCVarValueChanged;
/// <summary>
/// Constructs a new ConfigurationManager.
/// </summary>
@@ -96,15 +94,15 @@ namespace Robust.Shared.Configuration
if (_configVars.TryGetValue(cvar, out var cfgVar))
{
// overwrite the value with the saved one
var oldValue = GetConfigVarValue(cfgVar);
changedInvokes.Add(SetupInvokeValueChanged(cfgVar, value, oldValue));
cfgVar.Value = value;
if (SetupInvokeValueChanged(cfgVar, value) is { } invoke)
changedInvokes.Add(invoke);
}
else
{
//or add another unregistered CVar
//Note: the initial defaultValue is null, but it will get overwritten when the cvar is registered.
cfgVar = new ConfigVar(cvar, null!, CVar.NONE) { Value = value };
//Note: the defaultValue is arbitrarily 0, it will get overwritten when the cvar is registered.
cfgVar = new ConfigVar(cvar, 0, CVar.NONE) { Value = value };
_configVars.Add(cvar, cfgVar);
}
@@ -129,26 +127,26 @@ namespace Robust.Shared.Configuration
}
var convertedValue = value;
if (cVar.Type != value.GetType())
if (!cVar.DefaultValue.GetType().IsEnum && cVar.DefaultValue.GetType() != value.GetType())
{
try
{
convertedValue = ConvertToCVarType(value, cVar.Type!);
convertedValue = ConvertToCVarType(value, cVar.DefaultValue.GetType());
}
catch
{
_sawmill.Error($"Override TOML parsed cvar does not match registered cvar type. Name: {cVarName}. Code Type: {cVar.Type}. Toml type: {value.GetType()}");
_sawmill.Error($"Override TOML parsed cvar does not match registered cvar type. Name: {cVarName}. Code Type: {cVar.DefaultValue.GetType()}. Toml type: {value.GetType()}");
continue;
}
}
cVar.DefaultValue = value;
if (cVar.OverrideValue == null && cVar.Value == null)
{
var oldValue = GetConfigVarValue(cVar);
callbackEvents.Add(SetupInvokeValueChanged(cVar, convertedValue, oldValue));
if (SetupInvokeValueChanged(cVar, convertedValue) is { } invoke)
callbackEvents.Add(invoke);
}
cVar.DefaultValue = convertedValue;
}
}
@@ -319,7 +317,6 @@ namespace Robust.Shared.Configuration
private void RegisterCVar(string name, Type type, object defaultValue, CVar flags)
{
DebugTools.AssertEqual(defaultValue.GetType(), type);
DebugTools.Assert(!type.IsEnum || type.GetEnumUnderlyingType() == typeof(int),
$"{name}: Enum cvars must have int as underlying type.");
@@ -338,7 +335,7 @@ namespace Robust.Shared.Configuration
if (cVar.Registered)
_sawmill.Error($"The variable '{name}' has already been registered.");
if (cVar.Value != null && type != cVar.Value.GetType())
if (!type.IsEnum && cVar.Value != null && !type.IsAssignableFrom(cVar.Value.GetType()))
{
try
{
@@ -353,7 +350,7 @@ namespace Robust.Shared.Configuration
cVar.DefaultValue = defaultValue;
cVar.Flags = flags;
cVar.Register();
cVar.Registered = true;
if (cVar.OverrideValue != null)
{
@@ -363,9 +360,10 @@ namespace Robust.Shared.Configuration
return;
}
var cvar = new ConfigVar(name, defaultValue, flags);
cvar.Register();
_configVars.Add(name, cvar);
_configVars.Add(name, new ConfigVar(name, defaultValue, flags)
{
Registered = true
});
}
public void OnValueChanged<T>(CVarDef<T> cVar, Action<T> onValueChanged, bool invokeImmediately = false)
@@ -414,11 +412,10 @@ namespace Robust.Shared.Configuration
public void OnValueChanged<T>(string name, CVarChanged<T> onValueChanged, bool invokeImmediately = false)
where T : notnull
{
object value;
using (Lock.WriteGuard())
{
var reg = _configVars[name];
value = GetConfigVarValue(reg);
reg.ValueChanged.AddInPlace(
(object value, in CVarChangeInfo info) => onValueChanged((T)value, info),
onValueChanged);
@@ -426,7 +423,7 @@ namespace Robust.Shared.Configuration
if (invokeImmediately)
{
onValueChanged(GetCVar<T>(name), new CVarChangeInfo(name, _gameTiming.CurTick, value, value));
onValueChanged(GetCVar<T>(name), new CVarChangeInfo(_gameTiming.CurTick));
}
}
@@ -515,13 +512,12 @@ namespace Robust.Shared.Configuration
{
if (!Equals(cVar.OverrideValueParsed ?? cVar.Value, value))
{
var oldValue = GetConfigVarValue(cVar);
invoke = SetupInvokeValueChanged(cVar, value, oldValue, intendedTick);
// Setting an overriden var just turns off the override, basically.
cVar.OverrideValue = null;
cVar.OverrideValueParsed = null;
cVar.Value = value;
invoke = SetupInvokeValueChanged(cVar, value, intendedTick);
}
}
else
@@ -547,14 +543,10 @@ namespace Robust.Shared.Configuration
if (!_configVars.TryGetValue(name, out var cVar) || !cVar.Registered)
throw new InvalidConfigurationException($"Trying to set unregistered variable '{name}'");
if (cVar.OverrideValue == null && cVar.Value == null)
{
var oldValue = GetConfigVarValue(cVar);
invoke = SetupInvokeValueChanged(cVar, value, oldValue);
}
cVar.DefaultValue = value;
if (cVar.OverrideValue == null && cVar.Value == null)
invoke = SetupInvokeValueChanged(cVar, value);
}
if (invoke != null)
@@ -595,7 +587,7 @@ namespace Robust.Shared.Configuration
}
// If it's null it's a string, since the rest is primitives which aren't null.
return cVar.Type!;
return cVar.DefaultValue.GetType();
}
protected static object GetConfigVarValue(ConfigVar cVar)
@@ -614,19 +606,18 @@ namespace Robust.Shared.Configuration
if (_configVars.TryGetValue(key, out var cfgVar))
{
cfgVar.OverrideValue = value;
if (!cfgVar.Registered)
continue;
var newValue = ParseOverrideValue(value, cfgVar.Type!);
var oldValue = GetConfigVarValue(cfgVar);
invokes.Add(SetupInvokeValueChanged(cfgVar, newValue, oldValue));
cfgVar.OverrideValueParsed = newValue;
if (cfgVar.Registered)
{
cfgVar.OverrideValueParsed = ParseOverrideValue(value, cfgVar.DefaultValue?.GetType());
if (SetupInvokeValueChanged(cfgVar, cfgVar.OverrideValueParsed) is { } invoke)
invokes.Add(invoke);
}
}
else
{
//or add another unregistered CVar
//Note: the initial defaultValue is null, but it will get overwritten when the cvar is registered.
var cVar = new ConfigVar(key, null!, CVar.NONE) { OverrideValue = value };
//Note: the defaultValue is arbitrarily 0, it will get overwritten when the cvar is registered.
var cVar = new ConfigVar(key, 0, CVar.NONE) { OverrideValue = value };
_configVars.Add(key, cVar);
}
}
@@ -705,20 +696,25 @@ namespace Robust.Shared.Configuration
}
}
private void InvokeValueChanged(in ValueChangedInvoke invoke)
private static void InvokeValueChanged(in ValueChangedInvoke invoke)
{
OnCVarValueChanged?.Invoke(invoke.Info);
foreach (var entry in invoke.Invoke.Entries)
{
entry.Value!.Invoke(invoke.Value, in invoke.Info);
}
}
private ValueChangedInvoke SetupInvokeValueChanged(ConfigVar var, object newValue, object oldValue, GameTick? tick = null)
private ValueChangedInvoke? SetupInvokeValueChanged(ConfigVar var, object value, GameTick? tick = null)
{
tick ??= _gameTiming.CurTick;
var info = new CVarChangeInfo(var.Name, tick.Value, newValue, oldValue);
return new ValueChangedInvoke(info, var.ValueChanged);
if (var.ValueChanged.Count == 0)
return null;
return new ValueChangedInvoke
{
Info = new CVarChangeInfo(tick ?? _gameTiming.CurTick),
Invoke = var.ValueChanged,
Value = value
};
}
private IEnumerable<(string cvar, object value)> ParseCVarValuesFromToml(Stream stream)
@@ -771,9 +767,6 @@ namespace Robust.Shared.Configuration
/// <returns></returns>
private static object ConvertToCVarType(object value, Type cVar)
{
if (cVar.IsEnum)
return Enum.Parse(cVar, value.ToString() ?? string.Empty);
return Convert.ChangeType(value, cVar);
}
@@ -793,15 +786,10 @@ namespace Robust.Shared.Configuration
public ConfigVar(string name, object defaultValue, CVar flags)
{
Name = name;
DefaultValue = defaultValue;
Flags = flags;
_defaultValue = defaultValue;
}
/// <summary>
/// The type of the cvar's value. This may be null until the cvar is registered.
/// </summary>
public Type? Type { get; internal set; }
/// <summary>
/// The name of the CVar.
/// </summary>
@@ -810,16 +798,7 @@ namespace Robust.Shared.Configuration
/// <summary>
/// The default value of this CVar.
/// </summary>
public object DefaultValue
{
get => _defaultValue;
set
{
if (Registered)
DebugTools.AssertEqual(value.GetType(), Type);
_defaultValue = value;
}
}
public object DefaultValue { get; set; }
/// <summary>
/// Optional flags to modify the behavior of this CVar.
@@ -829,45 +808,12 @@ namespace Robust.Shared.Configuration
/// <summary>
/// The current value of this CVar.
/// </summary>
public object? Value
{
get => _value;
set
{
if (value != null && Registered)
DebugTools.AssertEqual(value.GetType(), Type);
_value = value;
}
}
public object? Value { get; set; }
/// <summary>
/// Has this CVar been registered in code?
/// </summary>
public bool Registered { get; private set; }
public void Register()
{
if (Registered)
{
DebugTools.AssertNotNull(DefaultValue);
DebugTools.AssertEqual(DefaultValue.GetType(), Type);
DebugTools.Assert(Value == null || Value.GetType() == Type);
DebugTools.Assert(OverrideValueParsed == null || OverrideValueParsed.GetType() == Type);
return;
}
if (_defaultValue == null)
throw new NullReferenceException("Must specify default value before registering");
if (Value != null && DefaultValue.GetType() != Value.GetType())
throw new Exception($"The cvar value & default value must be of the same type");
if (OverrideValueParsed != null && DefaultValue.GetType() != OverrideValueParsed.GetType())
throw new Exception($"The cvar override value & default value must be of the same type");
Type = DefaultValue.GetType();
Registered = true;
}
public bool Registered { get; set; }
/// <summary>
/// Was the CVar present in the config file?
@@ -876,25 +822,12 @@ namespace Robust.Shared.Configuration
public bool ConfigModified;
public InvokeList<ValueChangedDelegate> ValueChanged;
private object _defaultValue;
private object? _value;
private object? _overrideValueParsed;
// We don't know what the type of the var is until it's registered.
// So we can't actually parse them until then.
// So we keep the raw string around.
public string? OverrideValue { get; set; }
public object? OverrideValueParsed
{
get => _overrideValueParsed;
set
{
if (value != null && Registered)
DebugTools.AssertEqual(value.GetType(), Type);
_overrideValueParsed = value;
}
}
public object? OverrideValueParsed { get; set; }
}
/// <summary>
@@ -903,14 +836,8 @@ namespace Robust.Shared.Configuration
private struct ValueChangedInvoke
{
public InvokeList<ValueChangedDelegate> Invoke;
public object Value => Info.NewValue;
public object Value;
public CVarChangeInfo Info;
public ValueChangedInvoke(CVarChangeInfo info, InvokeList<ValueChangedDelegate> invoke) : this()
{
Info = info;
Invoke = invoke;
}
}
protected delegate void ValueChangedDelegate(object value, in CVarChangeInfo info);

View File

@@ -10,11 +10,6 @@ namespace Robust.Shared.Configuration
/// </summary>
public readonly struct CVarChangeInfo
{
/// <summary>
/// The name of the cvar that was changed.
/// </summary>
public readonly string Name;
/// <summary>
/// The tick this CVar changed at.
/// </summary>
@@ -25,22 +20,9 @@ namespace Robust.Shared.Configuration
/// </remarks>
public readonly GameTick TickChanged;
/// <summary>
/// The new value.
/// </summary>
public readonly object NewValue;
/// <summary>
/// The previous value.
/// </summary>
public readonly object OldValue;
internal CVarChangeInfo(string name, GameTick tickChanged, object newValue, object oldValue)
internal CVarChangeInfo(GameTick tickChanged)
{
Name = name;
TickChanged = tickChanged;
NewValue = newValue;
OldValue = oldValue;
}
}
@@ -270,7 +252,5 @@ namespace Robust.Shared.Configuration
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
void UnsubValueChanged<T>(string name, CVarChanged<T> onValueChanged)
where T : notnull;
public event Action<CVarChangeInfo>? OnCVarValueChanged;
}
}

View File

@@ -1,8 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -15,19 +23,6 @@ namespace Robust.Shared.Containers
[ImplicitDataDefinitionForInheritors]
public abstract partial class BaseContainer
{
// Will be null until after the component has been initialized.
protected SharedContainerSystem? System;
[Access(typeof(SharedContainerSystem), typeof(ContainerManagerComponent))]
internal void Init(SharedContainerSystem system, string id, Entity<ContainerManagerComponent> owner)
{
DebugTools.Assert(ID == null || ID == id);
ID = id;
Owner = owner;
Manager = owner;
System = system;
}
/// <summary>
/// Readonly collection of all the entities contained within this specific container
/// </summary>

View File

@@ -2,8 +2,10 @@ using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.Containers
@@ -49,7 +51,14 @@ namespace Robust.Shared.Containers
if (!_containerList.Contains(contained))
return false;
System?.AssertInContainer(contained, this);
#if DEBUG
if (IoCManager.Resolve<IGameTiming>().ApplyingState)
return true;
var entMan = IoCManager.Resolve<IEntityManager>();
var flags = entMan.GetComponent<MetaDataComponent>(contained).Flags;
DebugTools.Assert((flags & MetaDataFlags.InContainer) != 0, $"Entity has bad container flags. Ent: {entMan.ToPrettyString(contained)}. Container: {ID}, Owner: {entMan.ToPrettyString(Owner)}");
#endif
return true;
}

View File

@@ -30,7 +30,9 @@ namespace Robust.Shared.Containers
{
foreach (var (id, container) in Containers)
{
container.Init(default!, id, (Owner, this));
container.ID = id;
container.Owner = Owner;
container.Manager = this;
}
}

View File

@@ -2,9 +2,11 @@ using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.Containers
@@ -57,7 +59,14 @@ namespace Robust.Shared.Containers
if (contained != ContainedEntity)
return false;
System?.AssertInContainer(contained, this);
#if DEBUG
if (IoCManager.Resolve<IGameTiming>().ApplyingState)
return true;
var entMan = IoCManager.Resolve<IEntityManager>();
var flags = entMan.GetComponent<MetaDataComponent>(contained).Flags;
DebugTools.Assert((flags & MetaDataFlags.InContainer) != 0, $"Entity has bad container flags. Ent: {entMan.ToPrettyString(contained)}. Container: {ID}, Owner: {entMan.ToPrettyString(Owner)}");
#endif
return true;
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.GameObjects;
@@ -13,7 +12,6 @@ using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.Containers
@@ -26,7 +24,6 @@ namespace Robust.Shared.Containers
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedJointSystem _joint = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private EntityQuery<ContainerManagerComponent> _managerQuery;
private EntityQuery<MapGridComponent> _gridQuery;
@@ -42,7 +39,6 @@ namespace Robust.Shared.Containers
base.Initialize();
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChanged);
SubscribeLocalEvent<ContainerManagerComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<ContainerManagerComponent, ComponentStartup>(OnStartupValidation);
SubscribeLocalEvent<ContainerManagerComponent, ComponentGetState>(OnContainerGetState);
SubscribeLocalEvent<ContainerManagerComponent, ComponentRemove>(OnContainerManagerRemove);
@@ -56,18 +52,9 @@ namespace Robust.Shared.Containers
TransformQuery = GetEntityQuery<TransformComponent>();
}
private void OnInit(Entity<ContainerManagerComponent> ent, ref ComponentInit args)
{
foreach (var (id, container) in ent.Comp.Containers)
{
container.Init(this, id, ent);
}
}
private void OnContainerGetState(EntityUid uid, ContainerManagerComponent component, ref ComponentGetState args)
{
Dictionary<string, ContainerManagerComponent.ContainerManagerComponentState.ContainerData> containerSet =
new(component.Containers.Count);
Dictionary<string, ContainerManagerComponent.ContainerManagerComponentState.ContainerData> containerSet = new(component.Containers.Count);
foreach (var container in component.Containers.Values)
{
@@ -78,11 +65,7 @@ namespace Robust.Shared.Containers
uidArr[index] = GetNetEntity(container.ContainedEntities[index]);
}
var sContainer =
new ContainerManagerComponent.ContainerManagerComponentState.ContainerData(container.GetType().Name,
container.ShowContents,
container.OccludesLight,
uidArr);
var sContainer = new ContainerManagerComponent.ContainerManagerComponentState.ContainerData(container.GetType().Name, container.ShowContents, container.OccludesLight, uidArr);
containerSet.Add(container.ID, sContainer);
}
@@ -113,12 +96,18 @@ namespace Robust.Shared.Containers
throw new ArgumentException($"Container with specified ID already exists: '{id}'");
var container = _dynFactory.CreateInstanceUnchecked<T>(typeof(T), inject: false);
container.Init(this, id, (uid, containerManager));
InitContainer(container, (uid, containerManager), id);
containerManager.Containers[id] = container;
Dirty(uid, containerManager);
return container;
}
protected void InitContainer(BaseContainer container, Entity<ContainerManagerComponent> containerEnt, string id)
{
DebugTools.AssertNull(container.ID);
((container.Owner, container.Manager), container.ID) = (containerEnt, id);
}
public virtual void ShutdownContainer(BaseContainer container)
{
container.InternalShutdown(EntityManager, this, _net.IsClient);
@@ -126,11 +115,7 @@ namespace Robust.Shared.Containers
container.ExpectedEntities.Clear();
}
public T EnsureContainer<T>(
EntityUid uid,
string id,
out bool alreadyExisted,
ContainerManagerComponent? containerManager = null)
public T EnsureContainer<T>(EntityUid uid, string id, out bool alreadyExisted, ContainerManagerComponent? containerManager = null)
where T : BaseContainer
{
if (!Resolve(uid, ref containerManager, false))
@@ -151,7 +136,7 @@ namespace Robust.Shared.Containers
}
public T EnsureContainer<T>(EntityUid uid, string id, ContainerManagerComponent? containerManager = null)
where T : BaseContainer
where T : BaseContainer
{
return EnsureContainer<T>(uid, id, out _, containerManager);
}
@@ -172,11 +157,7 @@ namespace Robust.Shared.Containers
return containerManager.Containers.ContainsKey(id);
}
public bool TryGetContainer(
EntityUid uid,
string id,
[NotNullWhen(true)] out BaseContainer? container,
ContainerManagerComponent? containerManager = null)
public bool TryGetContainer(EntityUid uid, string id, [NotNullWhen(true)] out BaseContainer? container, ContainerManagerComponent? containerManager = null)
{
if (!Resolve(uid, ref containerManager, false))
{
@@ -184,30 +165,16 @@ namespace Robust.Shared.Containers
return false;
}
if (!containerManager.Containers.TryGetValue(id, out container))
return false;
DebugTools.AssertEqual(container.ID, id);
DebugTools.AssertNotNull(container.Manager);
DebugTools.AssertNotEqual(container.Owner, EntityUid.Invalid);
return true;
return containerManager.Containers.TryGetValue(id, out container);
}
[Obsolete("Use variant without skipExistCheck argument")]
public bool TryGetContainingContainer(
EntityUid uid,
EntityUid containedUid,
[NotNullWhen(true)] out BaseContainer? container,
bool skipExistCheck)
public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out BaseContainer? container, bool skipExistCheck)
{
return TryGetContainingContainer(uid, containedUid, out container);
}
public bool TryGetContainingContainer(
EntityUid uid,
EntityUid containedUid,
[NotNullWhen(true)] out BaseContainer? container,
ContainerManagerComponent? containerManager = null)
public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out BaseContainer? container, ContainerManagerComponent? containerManager = null)
{
DebugTools.Assert(Exists(containedUid));
if (!Resolve(uid, ref containerManager, false))
@@ -229,10 +196,7 @@ namespace Robust.Shared.Containers
return false;
}
public bool ContainsEntity(
EntityUid uid,
EntityUid containedUid,
ContainerManagerComponent? containerManager = null)
public bool ContainsEntity(EntityUid uid, EntityUid containedUid, ContainerManagerComponent? containerManager = null)
{
DebugTools.Assert(Exists(containedUid));
if (!Resolve(uid, ref containerManager, false))
@@ -264,20 +228,13 @@ namespace Robust.Shared.Containers
foreach (var containers in containerManager.Containers.Values)
{
if (containers.Contains(toremove))
return Remove((toremove, containedXform, containedMeta),
containers,
reparent,
force,
destination,
localRotation);
return Remove((toremove, containedXform, containedMeta), containers, reparent, force, destination, localRotation);
}
return true; // If we don't contain the entity, it will always be removed
}
public ContainerManagerComponent.AllContainersEnumerable GetAllContainers(
EntityUid uid,
ContainerManagerComponent? containerManager = null)
public ContainerManagerComponent.AllContainersEnumerable GetAllContainers(EntityUid uid, ContainerManagerComponent? containerManager = null)
{
if (!Resolve(uid, ref containerManager))
return new ContainerManagerComponent.AllContainersEnumerable();
@@ -289,32 +246,20 @@ namespace Robust.Shared.Containers
#region Container Helpers
[Obsolete("Use Entity<T> variant")]
public bool TryGetContainingContainer(
EntityUid uid,
[NotNullWhen(true)] out BaseContainer? container,
MetaDataComponent? meta = null,
TransformComponent? transform = null)
{
return TryGetContainingContainer((uid, transform, meta), out container);
}
public bool TryGetContainingContainer(
Entity<TransformComponent?, MetaDataComponent?> ent,
[NotNullWhen(true)] out BaseContainer? container)
public bool TryGetContainingContainer(EntityUid uid, [NotNullWhen(true)] out BaseContainer? container, MetaDataComponent? meta = null, TransformComponent? transform = null)
{
container = null;
if (!Resolve(ent, ref ent.Comp2, false))
if (!Resolve(uid, ref meta, false))
return false;
if ((ent.Comp2.Flags & MetaDataFlags.InContainer) == MetaDataFlags.None)
if ((meta.Flags & MetaDataFlags.InContainer) == MetaDataFlags.None)
return false;
if (!Resolve(ent, ref ent.Comp1, false))
if (!Resolve(uid, ref transform, false))
return false;
return TryGetContainingContainer(ent.Comp1.ParentUid, ent, out container);
return TryGetContainingContainer(transform.ParentUid, uid, out container);
}
/// <summary>
@@ -411,19 +356,10 @@ namespace Robust.Shared.Containers
return TryFindComponentsOnEntityContainerOrParent(xform.ParentUid, entityQuery, foundComponents);
}
[Obsolete("Use Entity<T> variant")]
public bool IsInSameOrNoContainer(EntityUid user, EntityUid other)
{
return IsInSameOrNoContainer((user, null, null), (other, null, null));
}
/// <summary>
/// Returns true if the two entities are not contained, or are contained in the same container.
/// </summary>
public bool IsInSameOrNoContainer(
Entity<TransformComponent?, MetaDataComponent?> user,
Entity<TransformComponent?, MetaDataComponent?> other)
public bool IsInSameOrNoContainer(EntityUid user, EntityUid other)
{
var isUserContained = TryGetContainingContainer(user, out var userContainer);
var isOtherContained = TryGetContainingContainer(other, out var otherContainer);
@@ -438,33 +374,14 @@ namespace Robust.Shared.Containers
return userContainer == otherContainer;
}
[Obsolete("Use Entity<T> variant")]
public bool IsInSameOrParentContainer(EntityUid user, EntityUid other)
{
return IsInSameOrParentContainer((user, null), other);
}
/// <summary>
/// Returns true if the two entities are not contained, or are contained in the same container, or if one
/// entity contains the other (i.e., is the parent).
/// </summary>
public bool IsInSameOrParentContainer(
Entity<TransformComponent?, MetaDataComponent?> user,
Entity<TransformComponent?, MetaDataComponent?> other)
public bool IsInSameOrParentContainer(EntityUid user, EntityUid other)
{
return IsInSameOrParentContainer(user, other, out _, out _);
}
/// <inheritdoc cref="IsInSameOrParentContainer(Robust.Shared.GameObjects.Entity{Robust.Shared.GameObjects.TransformComponent?},Robust.Shared.GameObjects.Entity{Robust.Shared.GameObjects.TransformComponent?})"/>
public bool IsInSameOrParentContainer(
Entity<TransformComponent?, MetaDataComponent?> user,
Entity<TransformComponent?, MetaDataComponent?> other,
out BaseContainer? userContainer,
out BaseContainer? otherContainer)
{
var isUserContained = TryGetContainingContainer(user, out userContainer);
var isOtherContained = TryGetContainingContainer(other, out otherContainer);
var isUserContained = TryGetContainingContainer(user, out var userContainer);
var isOtherContained = TryGetContainingContainer(other, out var otherContainer);
// Both entities are not in a container
if (!isUserContained && !isOtherContained) return true;
@@ -479,21 +396,6 @@ namespace Robust.Shared.Containers
return userContainer == otherContainer;
}
[Obsolete("Use Entity<T> variant")]
public bool IsInSameOrTransparentContainer(
EntityUid user,
EntityUid other,
BaseContainer? userContainer = null,
BaseContainer? otherContainer = null,
bool userSeeInsideSelf = false)
{
return IsInSameOrTransparentContainer((user, null),
other,
userContainer,
otherContainer,
userSeeInsideSelf);
}
/// <summary>
/// Check whether a given entity can see another entity despite whatever containers they may be in.
/// </summary>
@@ -505,8 +407,8 @@ namespace Robust.Shared.Containers
/// this means that the two entity arguments are NOT interchangeable.
/// </remarks>
public bool IsInSameOrTransparentContainer(
Entity<TransformComponent?, MetaDataComponent?> user,
Entity<TransformComponent?, MetaDataComponent?> other,
EntityUid user,
EntityUid other,
BaseContainer? userContainer = null,
BaseContainer? otherContainer = null,
bool userSeeInsideSelf = false)
@@ -531,11 +433,11 @@ namespace Robust.Shared.Containers
// Is the user in a see-through container?
if (userContainer?.ShowContents ?? false)
return IsInSameOrTransparentContainer((userContainer.Owner, null, null), other, otherContainer: otherContainer);
return IsInSameOrTransparentContainer(userContainer.Owner, other, otherContainer: otherContainer);
// Is the other entity in a see-through container?
if (otherContainer?.ShowContents ?? false)
return IsInSameOrTransparentContainer(user, (otherContainer.Owner, null, null), userContainer: userContainer, userSeeInsideSelf: userSeeInsideSelf);
return IsInSameOrTransparentContainer(user, otherContainer.Owner, userContainer: userContainer, userSeeInsideSelf: userSeeInsideSelf);
return false;
}
@@ -701,16 +603,5 @@ namespace Robust.Shared.Containers
if (TryComp(message.OldParent, out ContainerManagerComponent? containerManager))
RemoveEntity(message.OldParent.Value, message.Entity, containerManager, message.Transform, meta, reparent: false, force: true);
}
[Conditional("DEBUG"), Access(typeof(BaseContainer))]
public void AssertInContainer(EntityUid uid, BaseContainer container)
{
if (_timing.ApplyingState)
return; // Entity might not yet have had its state updated.
var flags = MetaData(uid).Flags;
DebugTools.Assert((flags & MetaDataFlags.InContainer) != 0,
$"Entity has bad container flags. Ent: {ToPrettyString(uid)}. Container: {container.ID}, Owner: {ToPrettyString(container.Owner)}");
}
}
}

View File

@@ -442,14 +442,7 @@ 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,12 +289,6 @@ 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)
{
@@ -330,7 +324,7 @@ namespace Robust.Shared.GameObjects
public ComponentRegistration GetRegistration<T>() where T : IComponent, new()
{
return GetRegistration(CompIdx.Index<T>());
return GetRegistration(typeof(T));
}
public ComponentRegistration GetRegistration(IComponent component)

View File

@@ -1,56 +1,43 @@
using System;
using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects;
[RequiresSerializable]
[Serializable, NetSerializable]
[Virtual]
public abstract class ComponentState : IComponentState;
/// <summary>
/// Represents the state of a component for networking purposes.
/// </summary>
public interface IComponentState;
public interface IComponentDeltaState : IComponentState
namespace Robust.Shared.GameObjects
{
public void ApplyToFullState(IComponentState fullState);
public IComponentState CreateNewFullState(IComponentState fullState);
}
/// <summary>
/// Interface for component states that only contain partial state data. The actual delta state class should be a
/// separate class from the full component states.
/// </summary>
/// <typeparam name="TState">The full-state class associated with this partial state</typeparam>
public interface IComponentDeltaState<TState> : IComponentDeltaState where TState: IComponentState
{
/// <summary>
/// This function will apply the current delta state to the provided full state, modifying it in the process.
/// </summary>
public void ApplyToFullState(TState fullState);
/// <summary>
/// This function should take in a full state and return a new full state with the current delta applied, WITHOUT
/// modifying the original input state.
/// </summary>
public TState CreateNewFullState(TState fullState);
void IComponentDeltaState.ApplyToFullState(IComponentState fullState)
[RequiresSerializable]
[Serializable, NetSerializable]
[Virtual]
public class ComponentState : IComponentState
{
if (fullState is TState state)
ApplyToFullState(state);
else
throw new Exception($"Unexpected type. Expected {nameof(TState)} but got {fullState.GetType().Name}");
}
IComponentState IComponentDeltaState.CreateNewFullState(IComponentState fullState)
/// <summary>
/// Represents the state of a component for networking purposes.
/// </summary>
public interface IComponentState
{
if (fullState is TState state)
return CreateNewFullState(state);
else
throw new Exception($"Unexpected type. Expected {nameof(TState)} but got {fullState.GetType().Name}");
}
/// <summary>
/// Interface for components that support delta-states.
/// </summary>
public interface IComponentDeltaState
{
/// <summary>
/// Whether this state is a delta or full state.
/// </summary>
bool FullState { get; }
/// <summary>
/// This function will apply the current delta state to the provided full state, modifying it in the process.
/// </summary>
public void ApplyToFullState(IComponentState fullState);
/// <summary>
/// This function should take in a full state and return a new full state with the current delta applied,
/// WITHOUT modifying the original input state.
/// </summary>
public IComponentState CreateNewFullState(IComponentState fullState);
}
}

View File

@@ -2,35 +2,45 @@ 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;
/// <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
namespace Robust.Shared.GameObjects.Components.Localization
{
[DataField, AutoNetworkedField]
public Dictionary<string, string> Attributes = new();
[ViewVariables]
public Gender? Gender
/// <summary>
/// Overrides grammar attributes specified in prototypes or localization files.
/// </summary>
[RegisterComponent]
[NetworkedComponent()]
public sealed partial class GrammarComponent : Component
{
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);
}
[DataField("attributes")]
public Dictionary<string, string> Attributes { get; private set; } = new();
[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);
[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");
}
}
}
}

View File

@@ -1,39 +0,0 @@
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

@@ -71,17 +71,12 @@ namespace Robust.Shared.GameObjects
/// Raised whenever the server receives a BUI message from a client relating to a UI that requires input
/// validation.
/// </summary>
public sealed class BoundUserInterfaceMessageAttempt(
EntityUid actor,
EntityUid target,
Enum uiKey,
BoundUserInterfaceMessage message)
public sealed class BoundUserInterfaceMessageAttempt(EntityUid actor, EntityUid target, Enum uiKey)
: CancellableEntityEventArgs
{
public readonly EntityUid Actor = actor;
public readonly EntityUid Target = target;
public readonly Enum UiKey = uiKey;
public readonly BoundUserInterfaceMessage Message = message;
}
[NetSerializable, Serializable]
@@ -135,12 +130,12 @@ namespace Robust.Shared.GameObjects
}
[NetSerializable, Serializable]
public sealed class OpenBoundInterfaceMessage : BoundUserInterfaceMessage
internal sealed class OpenBoundInterfaceMessage : BoundUserInterfaceMessage
{
}
[NetSerializable, Serializable]
public sealed class CloseBoundInterfaceMessage : BoundUserInterfaceMessage
internal sealed class CloseBoundInterfaceMessage : BoundUserInterfaceMessage
{
}

View File

@@ -167,7 +167,7 @@ namespace Robust.Shared.GameObjects
if (eventHandler == null)
throw new ArgumentNullException(nameof(eventHandler));
var order = CreateOrderingData(orderType, before, after);
var order = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
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 = CreateOrderingData(orderType, before, after);
var order = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
SubscribeEventCommon<T>(source, subscriber, (ref Unit ev) =>
{

View File

@@ -5,7 +5,6 @@ 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;
@@ -14,7 +13,6 @@ 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;
@@ -31,36 +29,26 @@ internal sealed partial class EntityEventBus : IEventBus
// See EventTable declaration for layout details
internal Dictionary<EntityUid, EventTable> _entEventTables = new();
/// <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!;
// CompType -> EventType -> Handler
internal FrozenDictionary<Type, DirectedRegistration>?[] _entSubscriptions = default!;
/// <summary>
/// Variant of <see cref="_eventSubs"/> that also includes events with the <see cref="ComponentEventAttribute"/>
/// </summary>
internal FrozenDictionary<Type, DirectedRegistration>[] _compEventSubs = default!;
// Variant of _entSubscriptions that omits any events with the ComponentEventAttribute
internal FrozenDictionary<Type, DirectedRegistration>?[] _entSubscriptionsNoCompEv = default!;
// pre-freeze event subscription data
internal Dictionary<Type, DirectedRegistration>?[] _eventSubsUnfrozen =
Array.Empty<Dictionary<Type, DirectedRegistration>>();
// pre-freeze _entSubscriptions data
internal Dictionary<Type, DirectedRegistration>?[] _entSubscriptionsUnfrozen =
Array.Empty<Dictionary<Type, DirectedRegistration>?>();
/// <summary>
/// Inverse of <see cref="_eventSubs"/>, mapping event types to sets of components.
/// </summary>
private Dictionary<Type, HashSet<CompIdx>> _eventSubsInv = new();
// EventType -> { CompType1, ... CompType N }
// 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,7 +6,6 @@ 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
@@ -118,12 +117,10 @@ 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>
/// <param name="reflection">The reflection manager to use when finding derived types.</param>
public EntityEventBus(IEntityManager entMan, IReflectionManager reflection)
public EntityEventBus(IEntityManager entMan)
{
_entMan = entMan;
_comFac = entMan.ComponentFactory;
_reflection = reflection;
// Dynamic handling of components is only for RobustUnitTest compatibility spaghetti.
_comFac.ComponentsAdded += ComFacOnComponentsAdded;
@@ -251,7 +248,7 @@ namespace Robust.Shared.GameObjects
void EventHandler(EntityUid uid, IComponent comp, ref TEvent args)
=> handler(uid, (TComp)comp, args);
var orderData = CreateOrderingData(orderType, before, after);
var orderData = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
EntSubscribe<TEvent>(
CompIdx.Index<TComp>(),
@@ -284,7 +281,7 @@ namespace Robust.Shared.GameObjects
void EventHandler(EntityUid uid, IComponent comp, ref TEvent args)
=> handler(uid, (TComp)comp, ref args);
var orderData = CreateOrderingData(orderType, before, after);
var orderData = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
EntSubscribe<TEvent>(
CompIdx.Index<TComp>(),
@@ -303,7 +300,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 = CreateOrderingData(orderType, before, after);
var orderData = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
EntSubscribe<TEvent>(
CompIdx.Index<TComp>(),
@@ -330,7 +327,7 @@ namespace Robust.Shared.GameObjects
foreach (var reg in regs)
{
CompIdx.RefArray(ref _eventSubsUnfrozen, reg.Idx) ??= new();
CompIdx.RefArray(ref _entSubscriptionsUnfrozen, reg.Idx) ??= new();
}
}
@@ -354,33 +351,29 @@ namespace Robust.Shared.GameObjects
_subscriptionLock = true;
_eventData = _eventDataUnfrozen.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()!)
_entSubscriptions = _entSubscriptionsUnfrozen
.Select(x => x?.ToFrozenDictionary())
.ToArray();
_eventSubs = _eventSubsUnfrozen
.Take(last+1)
.Select(dict => dict?.Where(x => !IsComponentEvent(x.Key)).ToFrozenDictionary()!)
.ToArray();
_entSubscriptionsNoCompEv = _entSubscriptionsUnfrozen.Select(FreezeWithoutComponentEvent).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;
@@ -402,8 +395,8 @@ namespace Robust.Shared.GameObjects
if (_subscriptionLock)
throw new InvalidOperationException("Subscription locked.");
if (compType.Value >= _eventSubsUnfrozen.Length
|| _eventSubsUnfrozen[compType.Value] is not { } compSubs)
if (compType.Value >= _entSubscriptionsUnfrozen.Length
|| _entSubscriptionsUnfrozen[compType.Value] is not { } compSubs)
{
if (IgnoreUnregisteredComponents)
return;
@@ -418,11 +411,10 @@ 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>(
@@ -446,8 +438,8 @@ namespace Robust.Shared.GameObjects
if (_subscriptionLock)
throw new InvalidOperationException("Subscription locked.");
if (compType.Value >= _eventSubsUnfrozen.Length
|| _eventSubsUnfrozen[compType.Value] is not { } compSubs)
if (compType.Value >= _entSubscriptionsUnfrozen.Length
|| _entSubscriptionsUnfrozen[compType.Value] is not { } compSubs)
{
if (IgnoreUnregisteredComponents)
return;
@@ -457,7 +449,7 @@ namespace Robust.Shared.GameObjects
var removed = compSubs.Remove(eventType);
if (removed)
_eventSubsInv[eventType].Remove(compType);
_entSubscriptionsInv[eventType].Remove(compType);
}
private void EntAddEntity(EntityUid euid)
@@ -477,7 +469,7 @@ namespace Robust.Shared.GameObjects
DebugTools.Assert(_subscriptionLock);
var eventTable = _entEventTables[euid];
var compSubs = _eventSubs[compType.Value];
var compSubs = _entSubscriptionsNoCompEv[compType.Value]!;
foreach (var evType in compSubs.Keys)
{
@@ -536,17 +528,13 @@ namespace Robust.Shared.GameObjects
private void EntRemoveComponent(EntityUid euid, CompIdx compType)
{
var eventTable = _entEventTables[euid];
var compSubs = _eventSubs[compType.Value];
var compSubs = _entSubscriptions[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;
@@ -622,7 +610,9 @@ namespace Robust.Shared.GameObjects
ref Unit args)
where TEvent : notnull
{
if (_compEventSubs[baseType.Value].TryGetValue(typeof(TEvent), out var reg))
var compSubs = _entSubscriptions[baseType.Value]!;
if (compSubs.TryGetValue(typeof(TEvent), out var reg))
reg.Handler(euid, component, ref args);
}
@@ -644,7 +634,7 @@ namespace Robust.Shared.GameObjects
return false;
}
enumerator = new(eventType, startEntry, eventTable.ComponentLists, _eventSubs, euid, _entMan);
enumerator = new(eventType, startEntry, eventTable.ComponentLists, _entSubscriptions, euid, _entMan);
return true;
}
@@ -654,10 +644,10 @@ namespace Robust.Shared.GameObjects
_eventDataUnfrozen.Clear();
_entEventTables.Clear();
_inverseEventSubscriptions.Clear();
_compEventSubs = default!;
_eventSubs = default!;
_entSubscriptions = default!;
_entSubscriptionsNoCompEv = default!;
_eventData = FrozenDictionary<Type, EventData>.Empty;
foreach (var sub in _eventSubsUnfrozen)
foreach (var sub in _entSubscriptionsUnfrozen)
{
sub?.Clear();
}
@@ -670,19 +660,18 @@ namespace Robust.Shared.GameObjects
// punishment for use-after-free
_entMan = null!;
_comFac = null!;
_reflection = null!;
_entEventTables = null!;
_compEventSubs = null!;
_eventSubs = null!;
_eventSubsUnfrozen = null!;
_eventSubsInv = null!;
_entSubscriptions = null!;
_entSubscriptionsNoCompEv = null!;
_entSubscriptionsUnfrozen = null!;
_entSubscriptionsInv = 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;
@@ -691,7 +680,7 @@ namespace Robust.Shared.GameObjects
Type eventType,
int startEntry,
EventTableListEntry[] list,
FrozenDictionary<Type, DirectedRegistration>[] subscriptions,
FrozenDictionary<Type, DirectedRegistration>?[] subscriptions,
EntityUid uid,
IEntityManager entityManager)
{
@@ -718,7 +707,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 (_eventSubsInv.TryGetValue(eventType, out var comps))
if (_entSubscriptionsInv.TryGetValue(eventType, out var comps))
{
regs = regs.Concat(comps
.Select(c => _eventSubs[c.Value])
.Select(c => _entSubscriptions[c.Value])
.Where(c => c != null)
.Select(c => c![eventType]));
}
@@ -200,33 +200,5 @@ 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

@@ -36,6 +36,8 @@ namespace Robust.Shared.GameObjects
private const int EntityCapacity = 1024;
private const int NetComponentCapacity = 8;
private static readonly IComponentState DefaultComponentState = new ComponentState();
private FrozenDictionary<Type, Dictionary<EntityUid, IComponent>> _entTraitDict
= FrozenDictionary<Type, Dictionary<EntityUid, IComponent>>.Empty;
@@ -1402,13 +1404,13 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
public IComponentState? GetComponentState(IEventBus eventBus, IComponent component, ICommonSession? session, GameTick fromTick)
public IComponentState GetComponentState(IEventBus eventBus, IComponent component, ICommonSession? session, GameTick fromTick)
{
DebugTools.Assert(component.NetSyncEnabled, $"Attempting to get component state for an un-synced component: {component.GetType()}");
var getState = new ComponentGetState(session, fromTick);
eventBus.RaiseComponentEvent(component, ref getState);
return getState.State;
return getState.State ?? DefaultComponentState;
}
public bool CanGetComponentState(IEventBus eventBus, IComponent component, ICommonSession player)
@@ -1584,13 +1586,6 @@ namespace Robust.Shared.GameObjects
return default;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public TComp1 Comp(EntityUid uid)
{
return GetComponent(uid);
}
#region Internal
/// <summary>

View File

@@ -15,7 +15,6 @@ 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;
@@ -41,7 +40,6 @@ 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....
@@ -127,7 +125,7 @@ namespace Robust.Shared.GameObjects
if (Initialized)
throw new InvalidOperationException("Initialize() called multiple times");
_eventBus = new EntityEventBus(this, _reflection);
_eventBus = new EntityEventBus(this);
InitializeComponents();
_metaReg = _componentFactory.GetRegistration(typeof(MetaDataComponent));
@@ -283,7 +281,6 @@ namespace Robust.Shared.GameObjects
#region Entity Management
/// <inheritdoc />
public EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null)
{
return CreateEntity(prototypeName, out _, overrides);
@@ -324,7 +321,7 @@ namespace Robust.Shared.GameObjects
if (transform.Anchored && _mapManager.TryFindGridAt(coordinates, out var gridUid, out var grid))
{
coords = new EntityCoordinates(gridUid, _mapSystem.WorldToLocal(gridUid, grid, coordinates.Position));
_xforms.SetCoordinates(newEntity, transform, coords, rotation, unanchor: false);
_xforms.SetCoordinates(newEntity, transform, coords, unanchor: false);
}
else
{

View File

@@ -38,10 +38,11 @@ namespace Robust.Shared.GameObjects
[Serializable, NetSerializable]
public readonly struct ComponentChange
{
/// <summary>
// 15ish bytes to create a component (strings are big), 5 bytes to remove one
/// State data for the created/modified component, if any.
/// </summary>
public readonly IComponentState? State;
public readonly IComponentState State;
/// <summary>
/// The Network ID of the component to remove.
@@ -50,7 +51,7 @@ namespace Robust.Shared.GameObjects
public readonly GameTick LastModifiedTick;
public ComponentChange(ushort netId, IComponentState? state, GameTick lastModifiedTick)
public ComponentChange(ushort netId, IComponentState state, GameTick lastModifiedTick)
{
State = state;
NetID = netId;

View File

@@ -109,17 +109,6 @@ 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>(
@@ -144,6 +133,17 @@ 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,9 +160,6 @@ 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

@@ -404,7 +404,7 @@ namespace Robust.Shared.GameObjects
/// <param name="player">The player that is going to receive this state. Null implies that this state is for a replay.</param>
/// <param name="fromTick">The from tick, which indicates the range of data that must be included for delta-states.</param>
/// <returns>The component state of the component.</returns>
IComponentState? GetComponentState(IEventBus eventBus, IComponent component, ICommonSession? player, GameTick fromTick);
IComponentState GetComponentState(IEventBus eventBus, IComponent component, ICommonSession? player, GameTick fromTick);
/// <summary>
/// Checks if a certain player should get a component state.

View File

@@ -71,43 +71,12 @@ 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 = AllEntityQuery<MetaDataComponent>();
var query = EntityQueryEnumerator<MetaDataComponent>();
while (query.MoveNext(out var uid, out var metadata))
{
var id = metadata.EntityPrototype?.ID;

View File

@@ -244,51 +244,40 @@ public abstract partial class SharedMapSystem
private void OnGridHandleState(EntityUid uid, MapGridComponent component, ref ComponentHandleState args)
{
HashSet<MapChunk> modifiedChunks;
switch (args.Current)
if (args.Current is not MapGridComponentState state)
return;
DebugTools.Assert(component.ChunkSize == state.ChunkSize || component.Chunks.Count == 0,
"Can't modify chunk size of an existing grid.");
component.ChunkSize = state.ChunkSize;
if (state.ChunkData == null && state.FullGridData == null)
return;
var modifiedChunks = new HashSet<MapChunk>();
// delta state
if (state.ChunkData != null)
{
case MapGridComponentDeltaState delta:
foreach (var chunkData in state.ChunkData)
{
modifiedChunks = new();
DebugTools.Assert(component.ChunkSize == delta.ChunkSize || component.Chunks.Count == 0,
"Can't modify chunk size of an existing grid.");
component.ChunkSize = delta.ChunkSize;
if (delta.ChunkData == null)
return;
foreach (var chunkData in delta.ChunkData)
{
ApplyChunkData(uid, component, chunkData, modifiedChunks);
}
component.LastTileModifiedTick = delta.LastTileModifiedTick;
break;
ApplyChunkData(uid, component, chunkData, modifiedChunks);
}
case MapGridComponentState state:
}
// full state
if (state.FullGridData != null)
{
foreach (var index in component.Chunks.Keys)
{
modifiedChunks = new();
DebugTools.Assert(component.ChunkSize == state.ChunkSize || component.Chunks.Count == 0,
"Can't modify chunk size of an existing grid.");
component.LastTileModifiedTick = state.LastTileModifiedTick;
component.ChunkSize = state.ChunkSize;
foreach (var index in component.Chunks.Keys)
{
if (!state.FullGridData.ContainsKey(index))
ApplyChunkData(uid, component, ChunkDatum.CreateDeleted(index), modifiedChunks);
}
foreach (var (index, tiles) in state.FullGridData)
{
ApplyChunkData(uid, component, ChunkDatum.CreateModified(index, tiles), modifiedChunks);
}
break;
if (!state.FullGridData.ContainsKey(index))
ApplyChunkData(uid, component, ChunkDatum.CreateDeleted(index), modifiedChunks);
}
foreach (var (index, tiles) in state.FullGridData)
{
ApplyChunkData(uid, component, ChunkDatum.CreateModified(index, tiles), modifiedChunks);
}
default:
return;
}
var count = component.Chunks.Count;
@@ -420,7 +409,7 @@ public abstract partial class SharedMapSystem
}
}
args.State = new MapGridComponentDeltaState(component.ChunkSize, chunkData, component.LastTileModifiedTick);
args.State = new MapGridComponentState(component.ChunkSize, chunkData);
#if DEBUG
if (chunkData == null)
@@ -456,7 +445,7 @@ public abstract partial class SharedMapSystem
chunkData.Add(index, tileBuffer);
}
args.State = new MapGridComponentState(component.ChunkSize, chunkData, component.LastTileModifiedTick);
args.State = new MapGridComponentState(component.ChunkSize, chunkData);
#if DEBUG
foreach (var chunk in chunkData.Values)

View File

@@ -1,3 +1,5 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using Robust.Shared.Map;
@@ -34,23 +36,34 @@ public abstract partial class SharedTransformSystem
/// <returns>A new set of EntityCoordinates local to a new entity.</returns>
public EntityCoordinates WithEntityId(EntityCoordinates coordinates, EntityUid entity)
{
return entity == coordinates.EntityId
? coordinates
: ToCoordinates(entity, ToMapCoordinates(coordinates));
var mapPos = ToMapCoordinates(coordinates);
// You'd think this would throw like ToCoordinates does but TODO check that.
if (mapPos.MapId == MapId.Nullspace)
{
return new EntityCoordinates(entity, Vector2.Zero);
}
var xform = XformQuery.GetComponent(entity);
if (xform.MapID != mapPos.MapId)
{
return new EntityCoordinates(entity, Vector2.Zero);
}
var localPos = GetInvWorldMatrix(xform).Transform(mapPos.Position);
return new EntityCoordinates(entity, localPos);
}
/// <summary>
/// Converts entity-local coordinates into map terms.
/// </summary>
public MapCoordinates ToMapCoordinates(EntityCoordinates coordinates, bool logError = true)
public MapCoordinates ToMapCoordinates(EntityCoordinates coordinates)
{
if (!TryComp(coordinates.EntityId, out TransformComponent? xform))
{
if (logError)
Log.Error($"Attempted to convert coordinates with invalid entity: {coordinates}");
if (!IsValid(coordinates))
return MapCoordinates.Nullspace;
}
var xform = XformQuery.GetComponent(coordinates.EntityId);
var worldPos = GetWorldMatrix(xform).Transform(coordinates.Position);
return new MapCoordinates(worldPos, xform.MapID);
}
@@ -66,129 +79,17 @@ public abstract partial class SharedTransformSystem
}
/// <summary>
/// Creates EntityCoordinates given an entity and some MapCoordinates.
/// Creates EntityCoordinates given an entity and some MapCoordinates.
/// </summary>
public EntityCoordinates ToCoordinates(Entity<TransformComponent?> entity, MapCoordinates coordinates)
/// <exception cref="InvalidOperationException">If <see cref="entity"/> is not on the same map as the <see cref="coordinates"/>.</exception>
public EntityCoordinates ToCoordinates(EntityUid entity, MapCoordinates coordinates)
{
if (!Resolve(entity, ref entity.Comp, false))
{
Log.Error($"Attempted to convert coordinates with invalid entity: {coordinates}");
return default;
}
var xform = XformQuery.GetComponent(entity);
if (entity.Comp.MapID != coordinates.MapId)
{
Log.Error($"Attempted to convert map coordinates {coordinates} to entity coordinates on a different map. Entity: {ToPrettyString(entity)}");
return default;
}
if (xform.MapID != coordinates.MapId)
throw new InvalidOperationException("Entity is not on the same map!");
var localPos = GetInvWorldMatrix(entity.Comp).Transform(coordinates.Position);
var localPos = GetInvWorldMatrix(xform).Transform(coordinates.Position);
return new EntityCoordinates(entity, localPos);
}
/// <summary>
/// Creates map-relative <see cref="EntityCoordinates"/> given some <see cref="MapCoordinates"/>.
/// </summary>
public EntityCoordinates ToCoordinates(MapCoordinates coordinates)
{
if (_map.TryGetMap(coordinates.MapId, out var uid))
return ToCoordinates(uid.Value, coordinates);
Log.Error($"Attempted to convert map coordinates with unknown map id: {coordinates}");
return default;
}
/// <summary>
/// Returns the grid that the entity whose position the coordinates are relative to is on.
/// </summary>
public EntityUid? GetGrid(EntityCoordinates coordinates)
{
return GetGrid(coordinates.EntityId);
}
public EntityUid? GetGrid(Entity<TransformComponent?> entity)
{
return !Resolve(entity, ref entity.Comp) ? null : entity.Comp.GridUid;
}
/// <summary>
/// Returns the Map Id these coordinates are on.
/// </summary>
public MapId GetMapId(EntityCoordinates coordinates)
{
return GetMapId(coordinates.EntityId);
}
public MapId GetMapId(Entity<TransformComponent?> entity)
{
return !Resolve(entity, ref entity.Comp) ? MapId.Nullspace : entity.Comp.MapID;
}
/// <summary>
/// Returns the Map that these coordinates are on.
/// </summary>
public EntityUid? GetMap(EntityCoordinates coordinates)
{
return GetMap(coordinates.EntityId);
}
public EntityUid? GetMap(Entity<TransformComponent?> entity)
{
return !Resolve(entity, ref entity.Comp) ? null : entity.Comp.MapUid;
}
/// <summary>
/// Compares two sets of coordinates to see if they are in range of each other.
/// </summary>
/// <param name="range">maximum distance between the two sets of coordinates.</param>
/// <returns>True if the two points are within a given range.</returns>
public bool InRange(EntityCoordinates coordA, EntityCoordinates coordB, float range)
{
if (!coordA.EntityId.IsValid() || !coordB.EntityId.IsValid())
return false;
if (coordA.EntityId == coordB.EntityId)
return (coordA.Position - coordB.Position).LengthSquared() < range * range;
var mapA = ToMapCoordinates(coordA, logError:false);
var mapB = ToMapCoordinates(coordB, logError:false);
if (mapA.MapId != mapB.MapId || mapA.MapId == MapId.Nullspace)
return false;
return mapA.InRange(mapB, range);
}
/// <summary>
/// Compares the positions of two entities to see if they are within some specified distance of each other.
/// </summary>
public bool InRange(Entity<TransformComponent?> entA, Entity<TransformComponent?> entB, float range)
{
if (!Resolve(entA, ref entA.Comp))
return false;
if (!Resolve(entB, ref entB.Comp))
return false;
if (!entA.Comp.ParentUid.IsValid() || !entB.Comp.ParentUid.IsValid())
return false;
if (entA.Comp.ParentUid == entB.Comp.ParentUid)
return (entA.Comp.LocalPosition - entB.Comp.LocalPosition).LengthSquared() < range * range;
if (entA.Comp.ParentUid == entB.Owner)
return entA.Comp.LocalPosition.LengthSquared() < range * range;
if (entB.Comp.ParentUid == entA.Owner)
return entB.Comp.LocalPosition.LengthSquared() < range * range;
var mapA = GetMapCoordinates(entA!);
var mapB = GetMapCoordinates(entB!);
if (mapA.MapId != mapB.MapId || mapA.MapId == MapId.Nullspace)
return false;
return mapA.InRange(mapB, range);
}
}

View File

@@ -10,7 +10,6 @@ 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;
@@ -21,7 +20,6 @@ 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!;
@@ -31,8 +29,6 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
private EntityQuery<UserInterfaceComponent> _uiQuery;
private EntityQuery<UserInterfaceUserComponent> _userQuery;
private ActorRangeCheckJob _rangeJob;
public override void Initialize()
{
base.Initialize();
@@ -42,12 +38,6 @@ 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)
@@ -58,7 +48,6 @@ 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);
@@ -66,10 +55,28 @@ 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>
@@ -105,7 +112,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
// If it's a close message something else might try to cancel it but we want to force it.
if (msg.Message is not CloseBoundInterfaceMessage && ui.RequireInputValidation)
{
var attempt = new BoundUserInterfaceMessageAttempt(sender, uid, msg.UiKey, msg.Message);
var attempt = new BoundUserInterfaceMessageAttempt(sender, uid, msg.UiKey);
RaiseLocalEvent(attempt);
if (attempt.Cancelled)
return;
@@ -128,11 +135,6 @@ 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)
@@ -205,10 +207,11 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
foreach (var key in keys)
{
if (!uiComp.ClientOpenInterfaces.Remove(key, out var cBui))
if (!uiComp.ClientOpenInterfaces.TryGetValue(key, out var cBui))
continue;
cBui.Dispose();
uiComp.ClientOpenInterfaces.Remove(key);
}
}
}
@@ -230,8 +233,10 @@ 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) && _userQuery.TryComp(actor, out var actorComp))
if (!TerminatingOrDeleted(actor))
{
var actorComp = EnsureComp<UserInterfaceUserComponent>(actor);
if (actorComp.OpenInterfaces.TryGetValue(ent.Owner, out var keys))
{
keys.Remove(args.UiKey);
@@ -277,20 +282,6 @@ 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)
@@ -403,14 +394,11 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
// 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, open);
EnsureClientBui(ent, key, value);
}
}
}
@@ -418,7 +406,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, bool open = true)
private void EnsureClientBui(Entity<UserInterfaceComponent> entity, Enum key, InterfaceData data)
{
// If it's out BUI open it up and apply the state, otherwise do nothing.
var player = _player.LocalEntity;
@@ -441,11 +429,6 @@ 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))
@@ -892,31 +875,12 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
RaisePredictiveEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), msg, bui.UiKey));
}
public bool TryGetInterfaceData(Entity<UserInterfaceComponent?> entity,
Enum key,
[NotNullWhen(true)] out InterfaceData? data)
{
data = null;
return Resolve(entity, ref entity.Comp, false) && entity.Comp.Interfaces.TryGetValue(key, out data);
}
public float GetUiRange(Entity<UserInterfaceComponent?> entity, Enum key)
{
TryGetInterfaceData(entity, key, out var data);
return data?.InteractionRange ?? 0;
}
/// <inheritdoc />
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.
// TODO iterate over BUI users, not BUI entities.
// I.e., a user may have more than one BUI open, but its rare for a bui to be open by more than one user.
// This means we won't have to fetch the user's transform as frequently.
while (query.MoveNext(out var uid, out _, out var uiComp))
{
foreach (var (key, actors) in uiComp.Actors)
@@ -925,124 +889,106 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
var data = uiComp.Interfaces[key];
// Short-circuit
if (data.InteractionRange <= 0f)
if (data.InteractionRange <= 0f || actors.Count == 0)
continue;
foreach (var actor in actors)
// 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--)
{
_rangeJob.ActorRanges.Add((uid, key, data, actor, false));
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);
}
}
}
_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>
/// Verify that the subscribed clients are still in range of the interface.
/// </summary>
private bool CheckRange(
Entity<TransformComponent> UiEnt,
EntityUid uid,
Enum key,
InterfaceData data,
Entity<TransformComponent?> actor)
EntityUid actor,
EntityCoordinates uiCoordinates,
MapId uiMap)
{
if (!_xformQuery.Resolve(actor, ref actor.Comp) || actor.Comp.MapID != UiEnt.Comp.MapID)
if (_ignoreUIRangeQuery.HasComponent(actor))
return true;
if (!_xformQuery.TryGetComponent(actor, out var actorXform))
return false;
// Handle pluggable BoundUserInterfaceCheckRangeEvent
var checkRangeEvent = new BoundUserInterfaceCheckRangeEvent(UiEnt, key, data, actor!);
RaiseLocalEvent(UiEnt.Owner, ref checkRangeEvent, true);
var checkRangeEvent = new BoundUserInterfaceCheckRangeEvent(uid, key, data, actor);
RaiseLocalEvent(uid, ref checkRangeEvent, broadcast: true);
if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Pass)
return true;
// We only check if the range check should be ignored if it did not pass.
// The majority of the time the check will be passing and users generally do not have this component.
if (_ignoreUIRangeQuery.HasComponent(actor))
return true;
if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Fail)
return false;
DebugTools.Assert(checkRangeEvent.Result == BoundUserInterfaceRangeResult.Default);
return _transforms.InRange(UiEnt!, actor, data.InteractionRange);
}
if (uiMap != actorXform.MapID)
return false;
/// <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, uiXform), data.Key, data.Data, data.Actor);
}
ActorRanges[index] = data;
}
return uiCoordinates.InRange(EntityManager, _transforms, actorXform.Coordinates, data.InteractionRange);
}
}
/// <summary>
/// Raised by <see cref="UserInterfaceSystem"/> to check whether an interface is still accessible by its user.
/// The event is raised directed at the entity that owns the interface.
/// </summary>
[ByRefEvent]
[PublicAPI]
public struct BoundUserInterfaceCheckRangeEvent(
Entity<TransformComponent> target,
Enum uiKey,
InterfaceData data,
Entity<TransformComponent> actor)
public struct BoundUserInterfaceCheckRangeEvent
{
/// <summary>
/// The entity owning the UI being checked for.
/// </summary>
public readonly EntityUid Target = target;
public readonly EntityUid Target;
/// <summary>
/// The UI itself.
/// </summary>
/// <returns></returns>
public readonly Enum UiKey = uiKey;
public readonly Enum UiKey;
public readonly InterfaceData Data = data;
public readonly InterfaceData Data;
/// <summary>
/// The player for which the UI is being checked.
/// </summary>
public readonly Entity<TransformComponent> Actor = actor;
public readonly EntityUid Actor;
/// <summary>
/// The result of the range check.
/// </summary>
public BoundUserInterfaceRangeResult Result;
public BoundUserInterfaceCheckRangeEvent(
EntityUid target,
Enum uiKey,
InterfaceData data,
EntityUid actor)
{
Target = target;
UiKey = uiKey;
Data = data;
Actor = actor;
}
}
/// <summary>

View File

@@ -1,7 +1,6 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.GameStates
{
@@ -13,7 +12,6 @@ namespace Robust.Shared.GameStates
public ComponentHandleState(IComponentState? current, IComponentState? next)
{
DebugTools.Assert(current != null || next != null);
Current = current;
Next = next;
}

View File

@@ -33,11 +33,6 @@ 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>
@@ -52,23 +47,6 @@ 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,8 +17,6 @@ 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
@@ -110,7 +108,7 @@ namespace Robust.Shared.Localization
var a = new LocValueString("a");
var an = new LocValueString("an");
var m = RegexWordMatch.Match(input);
var m = Regex.Match(input, @"\w+");
if (m.Success)
{
word = m.Groups[0].Value;

View File

@@ -335,48 +335,48 @@ namespace Robust.Shared.Map.Components
/// Serialized state of a <see cref="MapGridComponentState"/>.
/// </summary>
[Serializable, NetSerializable]
internal sealed class MapGridComponentState(ushort chunkSize, Dictionary<Vector2i, Tile[]> fullGridData, GameTick lastTileModifiedTick) : ComponentState
internal sealed class MapGridComponentState : ComponentState, IComponentDeltaState
{
/// <summary>
/// The size of the chunks in the map grid.
/// </summary>
public ushort ChunkSize = chunkSize;
/// <summary>
/// Networked chunk data containing the full grid state.
/// </summary>
public Dictionary<Vector2i, Tile[]> FullGridData = fullGridData;
/// <summary>
/// Last game tick that the tile on the grid was modified.
/// </summary>
public GameTick LastTileModifiedTick = lastTileModifiedTick;
}
/// <summary>
/// Serialized state of a <see cref="MapGridComponentState"/>.
/// </summary>
[Serializable, NetSerializable]
internal sealed class MapGridComponentDeltaState(ushort chunkSize, List<ChunkDatum>? chunkData, GameTick lastTileModifiedTick)
: ComponentState, IComponentDeltaState<MapGridComponentState>
{
/// <summary>
/// The size of the chunks in the map grid.
/// </summary>
public readonly ushort ChunkSize = chunkSize;
public ushort ChunkSize;
/// <summary>
/// Networked chunk data.
/// </summary>
public readonly List<ChunkDatum>? ChunkData = chunkData;
public List<ChunkDatum>? ChunkData;
/// <summary>
/// Last game tick that the tile on the grid was modified.
/// Networked chunk data containing the full grid state.
/// </summary>
public GameTick LastTileModifiedTick = lastTileModifiedTick;
public Dictionary<Vector2i, Tile[]>? FullGridData;
public void ApplyToFullState(MapGridComponentState state)
public bool FullState => FullGridData != null;
/// <summary>
/// Constructs a new grid component delta state.
/// </summary>
public MapGridComponentState(ushort chunkSize, List<ChunkDatum>? chunkData)
{
ChunkSize = chunkSize;
ChunkData = chunkData;
}
/// <summary>
/// Constructs a new full component state.
/// </summary>
public MapGridComponentState(ushort chunkSize, Dictionary<Vector2i, Tile[]> fullGridData)
{
ChunkSize = chunkSize;
FullGridData = fullGridData;
}
public void ApplyToFullState(IComponentState fullState)
{
var state = (MapGridComponentState)fullState;
DebugTools.Assert(!FullState && state.FullState);
state.ChunkSize = ChunkSize;
if (ChunkData == null)
@@ -389,13 +389,14 @@ namespace Robust.Shared.Map.Components
else
state.FullGridData![data.Index] = data.TileData;
}
state.LastTileModifiedTick = LastTileModifiedTick;
}
public MapGridComponentState CreateNewFullState(MapGridComponentState state)
public IComponentState CreateNewFullState(IComponentState fullState)
{
var fullGridData = new Dictionary<Vector2i, Tile[]>(state.FullGridData.Count);
var state = (MapGridComponentState)fullState;
DebugTools.Assert(!FullState && state.FullState);
var fullGridData = new Dictionary<Vector2i, Tile[]>(state.FullGridData!.Count);
foreach (var (key, value) in state.FullGridData)
{
@@ -403,7 +404,7 @@ namespace Robust.Shared.Map.Components
Array.Copy(value, arr, value.Length);
}
var newState = new MapGridComponentState(ChunkSize, fullGridData, LastTileModifiedTick);
var newState = new MapGridComponentState(ChunkSize, fullGridData);
ApplyToFullState(newState);
return newState;
}

View File

@@ -5,6 +5,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Robust.Shared.Map
@@ -76,55 +77,109 @@ namespace Robust.Shared.Map
return true;
}
[Obsolete("Use SharedTransformSystem.ToMapCoordinates()")]
/// <summary>
/// Transforms this set of coordinates from the entity's local space to the map space.
/// </summary>
/// <param name="entityManager">Entity Manager containing the entity Id.</param>
/// <returns></returns>
[Obsolete("Use ToMap() with TransformSystem overload")]
public MapCoordinates ToMap(IEntityManager entityManager)
{
return ToMap(entityManager, entityManager.System<SharedTransformSystem>());
}
[Obsolete("Use SharedTransformSystem.ToMapCoordinates()")]
/// <summary>
/// Transforms this set of coordinates from the entity's local space to the map space.
/// </summary>
/// <param name="entityManager">Entity Manager containing the entity Id.</param>
/// <param name="transformSystem">Shared transform system for doing calculations.</param>
public MapCoordinates ToMap(IEntityManager entityManager, SharedTransformSystem transformSystem)
{
return transformSystem.ToMapCoordinates(this);
if(!IsValid(entityManager))
return MapCoordinates.Nullspace;
var transform = entityManager.GetComponent<TransformComponent>(EntityId);
var worldPos = transformSystem.GetWorldMatrix(transform).Transform(Position);
return new MapCoordinates(worldPos, transform.MapID);
}
[Obsolete("Use SharedTransformSystem.ToMapCoordinates()")]
/// <summary>
/// Transform this set of coordinates from the entity's local space to the map space.
/// </summary>
/// <param name="entityManager">Entity Manager containing the entity Id.</param>
/// <returns></returns>
[Obsolete("Use ToMapPos() with TransformSystem overload")]
public Vector2 ToMapPos(IEntityManager entityManager)
{
return ToMap(entityManager, entityManager.System<SharedTransformSystem>()).Position;
}
[Obsolete("Use SharedTransformSystem.ToMapCoordinates()")]
/// <summary>
/// Transform this set of coordinates from the entity's local space to the map space.
/// </summary>
/// <param name="entityManager">Entity Manager containing the entity Id.</param>
/// <param name="transformSystem">Shared transform system for doing calculations.</param>
public Vector2 ToMapPos(IEntityManager entityManager, SharedTransformSystem transformSystem)
{
return ToMap(entityManager, transformSystem).Position;
}
[Obsolete("Use SharedTransformSystem.ToCoordinates()")]
/// <summary>
/// Creates EntityCoordinates given an entity and some MapCoordinates.
/// </summary>
/// <exception cref="InvalidOperationException">If <see cref="entity"/> is not on the same map as the <see cref="coordinates"/>.</exception>
[Obsolete("Use FromMap() with TransformSystem overload")]
public static EntityCoordinates FromMap(EntityUid entity, MapCoordinates coordinates, IEntityManager? entMan = null)
{
IoCManager.Resolve(ref entMan);
return FromMap(entity, coordinates, entMan.System<SharedTransformSystem>(), entMan);
}
[Obsolete("Use SharedTransformSystem.ToCoordinates()")]
/// <summary>
/// Creates EntityCoordinates given an entity and some MapCoordinates.
/// </summary>
/// <exception cref="InvalidOperationException">If <see cref="entity"/> is not on the same map as the <see cref="coordinates"/>.</exception>
public static EntityCoordinates FromMap(EntityUid entity, MapCoordinates coordinates, SharedTransformSystem transformSystem, IEntityManager? entMan = null)
{
return transformSystem.ToCoordinates(entity, coordinates);
IoCManager.Resolve(ref entMan);
var transform = entMan.GetComponent<TransformComponent>(entity);
if(transform.MapID != coordinates.MapId)
throw new InvalidOperationException("Entity is not on the same map!");
var localPos = transformSystem.GetInvWorldMatrix(transform).Transform(coordinates.Position);
return new EntityCoordinates(entity, localPos);
}
[Obsolete("Use SharedTransformSystem.ToCoordinates()")]
/// <summary>
/// Creates EntityCoordinates given an entity Uid and some MapCoordinates.
/// </summary>
/// <param name="entityManager">Entity Manager containing the entity Id.</param>
/// <param name="entityUid"></param>
/// <param name="coordinates"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException">If <see cref="entityUid"/> is not on the same map as the <see cref="coordinates"/>.</exception>
[Obsolete("Use overload with other parameter order.")]
public static EntityCoordinates FromMap(IEntityManager entityManager, EntityUid entityUid, MapCoordinates coordinates)
{
return FromMap(entityUid, coordinates, entityManager.System<SharedTransformSystem>(), entityManager);
}
[Obsolete("Use SharedTransformSystem.ToCoordinates()")]
/// <summary>
/// Creates a set of EntityCoordinates given some MapCoordinates.
/// </summary>
/// <param name="mapManager"></param>
/// <param name="coordinates"></param>
public static EntityCoordinates FromMap(IMapManager mapManager, MapCoordinates coordinates)
{
return IoCManager.Resolve<IEntityManager>().System<SharedTransformSystem>().ToCoordinates(coordinates);
var mapId = coordinates.MapId;
var mapEntity = mapManager.GetMapEntityId(mapId);
return new EntityCoordinates(mapEntity, coordinates.Position);
}
/// <summary>
/// Converts this set of coordinates to Vector2i.
/// </summary>
[Obsolete("Use overload with TransformSystem")]
public Vector2i ToVector2i(IEntityManager entityManager, IMapManager mapManager)
{
@@ -172,10 +227,9 @@ namespace Robust.Shared.Map
/// <param name="entityManager">The Entity Manager holding this entity</param>
/// <param name="entityId">The entity that the new coordinates will be local to</param>
/// <returns>A new set of EntityCoordinates local to a new entity.</returns>
[Obsolete("Use SharedTransformSystem.WithEntityId()")]
public EntityCoordinates WithEntityId(IEntityManager entityManager, EntityUid entityId)
{
if (!entityManager.EntityExists(entityId))
if(!entityManager.EntityExists(entityId))
return new EntityCoordinates(entityId, Vector2.Zero);
return WithEntityId(entityId);
@@ -186,7 +240,6 @@ namespace Robust.Shared.Map
/// </summary>
/// <param name="entity">The entity that the new coordinates will be local to</param>
/// <returns>A new set of EntityCoordinates local to a new entity.</returns>
[Obsolete("Use SharedTransformSystem.WithEntityId()")]
public EntityCoordinates WithEntityId(EntityUid entity, IEntityManager? entMan = null)
{
IoCManager.Resolve(ref entMan);
@@ -198,13 +251,19 @@ namespace Robust.Shared.Map
/// </summary>
/// <param name="entity">The entity that the new coordinates will be local to</param>
/// <returns>A new set of EntityCoordinates local to a new entity.</returns>
[Obsolete("Use SharedTransformSystem.WithEntityId()")]
public EntityCoordinates WithEntityId(
EntityUid entity,
SharedTransformSystem transformSystem,
IEntityManager? entMan = null)
{
return transformSystem.WithEntityId(this, entity);
IoCManager.Resolve(ref entMan);
var mapPos = ToMap(entMan, transformSystem);
if(!IsValid(entMan) || entMan.GetComponent<TransformComponent>(entity).MapID != mapPos.MapId)
return new EntityCoordinates(entity, Vector2.Zero);
var localPos = transformSystem.GetInvWorldMatrix(entity).Transform(mapPos.Position);
return new EntityCoordinates(entity, localPos);
}
/// <summary>
@@ -213,7 +272,6 @@ namespace Robust.Shared.Map
/// </summary>
/// <param name="entityManager"></param>
/// <returns>Grid EntityUid this entity is on or null</returns>
[Obsolete("Use SharedTransformSystem.GetGrid()")]
public EntityUid? GetGridUid(IEntityManager entityManager)
{
return !IsValid(entityManager) ? null : entityManager.GetComponent<TransformComponent>(EntityId).GridUid;
@@ -225,7 +283,6 @@ namespace Robust.Shared.Map
/// </summary>
/// <param name="entityManager"></param>
/// <returns>Map Id these coordinates are on or <see cref="MapId.Nullspace"/></returns>
[Obsolete("Use SharedTransformSystem.GetMapId()")]
public MapId GetMapId(IEntityManager entityManager)
{
return !IsValid(entityManager) ? MapId.Nullspace : entityManager.GetComponent<TransformComponent>(EntityId).MapID;
@@ -237,7 +294,6 @@ namespace Robust.Shared.Map
/// </summary>
/// <param name="entityManager"></param>
/// <returns>Map Id these coordinates are on or null</returns>
[Obsolete("Use SharedTransformSystem.GetMap()")]
public EntityUid? GetMapUid(IEntityManager entityManager)
{
return !IsValid(entityManager) ? null : entityManager.GetComponent<TransformComponent>(EntityId).MapUid;
@@ -260,20 +316,38 @@ namespace Robust.Shared.Map
/// <param name="otherCoordinates">Other set of coordinates to use.</param>
/// <param name="range">maximum distance between the two sets of coordinates.</param>
/// <returns>True if the two points are within a given range.</returns>
[Obsolete("Use TransformSystem.InRange()")]
[Obsolete("Use overload with TransformSystem")]
public bool InRange(IEntityManager entityManager, EntityCoordinates otherCoordinates, float range)
{
return InRange(entityManager, entityManager.System<SharedTransformSystem>(), otherCoordinates, range);
}
[Obsolete("Use TransformSystem.InRange()")]
/// <summary>
/// Compares two sets of coordinates to see if they are in range of each other.
/// </summary>
/// <param name="entityManager">Entity Manager containing the two entity Ids.</param>
/// <param name="otherCoordinates">Other set of coordinates to use.</param>
/// <param name="range">maximum distance between the two sets of coordinates.</param>
/// <returns>True if the two points are within a given range.</returns>
public bool InRange(
IEntityManager entityManager,
SharedTransformSystem transformSystem,
EntityCoordinates otherCoordinates,
float range)
{
return transformSystem.InRange(this, otherCoordinates, range);
if (!IsValid(entityManager) || !otherCoordinates.IsValid(entityManager))
return false;
if (EntityId == otherCoordinates.EntityId)
return (otherCoordinates.Position - Position).LengthSquared() < range * range;
var mapCoordinates = ToMap(entityManager, transformSystem);
var otherMapCoordinates = otherCoordinates.ToMap(entityManager, transformSystem);
if (mapCoordinates.MapId != otherMapCoordinates.MapId)
return false;
return mapCoordinates.InRange(otherMapCoordinates, range);
}
/// <summary>

View File

@@ -55,10 +55,5 @@ 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

@@ -20,7 +20,7 @@ namespace Robust.Shared.Network
internal sealed class AuthManager : IAuthManager
{
public const string DefaultAuthServer = "https://auth.spacestation14.com/";
public const string DefaultAuthServer = "https://central.spacestation14.io/auth/";
public NetUserId? UserId { get; set; }
public string? Server { get; set; } = DefaultAuthServer;

View File

@@ -249,9 +249,6 @@ namespace Robust.Shared.Network
IsServer = isServer;
_config.OnValueChanged(CVars.NetLidgrenLogWarning, LidgrenLogWarningChanged);
_config.OnValueChanged(CVars.NetLidgrenLogError, LidgrenLogErrorChanged);
_config.OnValueChanged(CVars.NetVerbose, NetVerboseChanged);
if (isServer)
{
@@ -275,22 +272,6 @@ 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;
@@ -441,8 +422,6 @@ 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;
@@ -597,14 +576,6 @@ 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,9 +48,6 @@ 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,7 +1,4 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
@@ -45,59 +42,3 @@ 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,7 +11,6 @@ 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
@@ -60,9 +59,9 @@ namespace Robust.Shared.Prototypes
[DataField("suffix")]
public string? SetSuffix { get; private set; }
[DataField]
[DataField("categories")]
[AlwaysPushInheritance]
public HashSet<ProtoId<EntityCategoryPrototype>> Categories = new();
public HashSet<string> Categories = new();
[ViewVariables]
public IReadOnlyDictionary<string, string> LocProperties => _locPropertiesSet ?? LocPropertiesDefault;
@@ -169,37 +168,28 @@ namespace Robust.Shared.Prototypes
_loc = IoCManager.Resolve<ILocalizationManager>();
}
[Obsolete("Pass in IComponentFactory")]
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component)
where T : IComponent
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component, IComponentFactory? factory = null) where T : IComponent
{
var compName = IoCManager.Resolve<IComponentFactory>().GetComponentName(typeof(T));
return TryGetComponent(compName, out component);
}
if (factory == null)
{
factory = IoCManager.Resolve<IComponentFactory>();
}
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component, IComponentFactory factory) where T : IComponent, new()
{
var compName = factory.GetComponentName<T>();
var compName = factory.GetComponentName(typeof(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;
}
if (componentUnCast.Component is not T cast)
{
component = default;
return false;
}
component = cast;
// There are no duplicate component names
// TODO Sanity check with names being in an attribute of the type instead
component = (T) componentUnCast.Component;
return true;
}

View File

@@ -65,7 +65,8 @@ namespace Robust.Shared.Serialization.Manager.Definition
continue;
}
attribute.Tag = DataDefinitionUtility.AutoGenerateTag(field.FieldInfo.Name);
var name = field.FieldInfo.Name.AsSpan();
attribute.Tag = $"{char.ToLowerInvariant(name[0])}{name[1..]}";
}
var dataFields = fieldDefs

View File

@@ -1,12 +0,0 @@
using System;
namespace Robust.Shared.Serialization.Manager.Definition;
public class DataDefinitionUtility
{
public static string AutoGenerateTag(string name)
{
var span = name.AsSpan();
return $"{char.ToLowerInvariant(span[0])}{span.Slice(1).ToString()}";
}
}

View File

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

View File

@@ -264,26 +264,6 @@ 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,11 +1,8 @@
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
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;
@@ -43,49 +40,3 @@ 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,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Pidgin;
using Robust.Shared.Maths;
using static Pidgin.Parser;
@@ -17,51 +15,29 @@ public sealed partial class FormattedMessage
/// <returns>true if the markup is valid</returns>
public static bool ValidMarkup(string markup)
{
return TryParse(markup, out _, out _);
return ParseResult(markup).Success;
}
/// <summary>
/// Attempts to add markup. If an error occurs, it will do nothing and return an error message.
/// This method does NOT fall back to using the permissive parser (which parses invalid markup as text).
/// </summary>
public bool TryAddMarkup(string markup, [NotNullWhen(false)] out string? error)
{
if (!TryParse(markup, out var nodes, out error))
return false;
_nodes.AddRange(nodes);
return true;
}
[Obsolete("Use AddMarkupOrThrow or TryAddMarkup")]
public void AddMarkup(string markup) => AddMarkupOrThrow(markup);
/// <summary>
/// Parses the given markup and adds the resulting nodes to this formatted message
/// </summary>
/// <param name="markup">The markup to parse</param>
/// <exception cref="ParseException">Thrown when an error occurs while trying to parse the markup.</exception>
public void AddMarkupOrThrow(string markup)
public void AddMarkup(string markup)
{
_nodes.AddRange(ParseOrThrow(markup));
_nodes.AddRange(Parse(markup));
}
/// <summary>
/// Same as <see cref="AddMarkup"/> but will attempt to parse invalid markup tags as text.
/// Same as <see cref="AddMarkup"/> but will parse invalid markup tags as text.
/// </summary>
/// <exception cref="ParseException">Thrown when an error occurs even when using the permissive parser.</exception>
public void AddMarkupPermissive(string markup, out string? error)
public void AddMarkupPermissive(string markup)
{
_nodes.AddRange(ParsePermissive(markup, out error));
_nodes.AddRange(ParseSafe(markup));
}
/// <inheritdoc cref="AddMarkupPermissive(string,out string?)"/>
public void AddMarkupPermissive(string markup) => AddMarkupPermissive(markup, out _);
/// <summary>
/// Same as <see cref="AddMarkup"/> but adds a newline too.
/// </summary>
[Obsolete]
public void PushMarkup(string markup)
{
AddMarkup(markup);
@@ -76,37 +52,13 @@ public sealed partial class FormattedMessage
/// This parser doesn't use backtracking by chaining pidgins parsers in such a way that branches that don't apply
/// always fail on the first character
/// </summary>
private static List<MarkupNode> ParseOrThrow(string input) => ParseNodes.ParseOrThrow(input);
private static IEnumerable<MarkupNode> Parse(string input) => ParseNodes.ParseOrThrow(input);
/// <summary>
/// Attempt to parse the given input. Returns an error message if it fails. Does not fall back to the permissive parser
/// Same as <see cref="Parse"/> but uses backtracking once to ensure invalid markup just gets parsed as text
/// </summary>
public static bool TryParse(string input, [NotNullWhen(true)] out List<MarkupNode>? nodes, [NotNullWhen(false)] out string? error)
{
var result = ParseNodes.Parse(input);
if (result.Success)
{
nodes = result.Value;
error = null;
return true;
}
error = result.Error!.RenderErrorMessage();
nodes = null;
return false;
}
/// <summary>
/// Variant of <see cref="TryParse"/> that falls back to using the permissive parser if an error occurs.
/// </summary>
/// <exception cref="ParseException">Thrown when an error occurs even when using the permissive parser.</exception>
public static List<MarkupNode> ParsePermissive(string input, out string? error)
{
if (TryParse(input, out var nodes, out error))
return nodes;
return ParseNodesSafe.ParseOrThrow(input);
}
private static IEnumerable<MarkupNode> ParseSafe(string input) => ParseNodesSafe.ParseOrThrow(input);
private static Result<char, List<MarkupNode>> ParseResult(string input) => ParseNodes.Parse(input);
//TODO: Make Begin and End a cvar
// Parser definitions for reserved characters

View File

@@ -1,10 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using JetBrains.Annotations;
using Nett.Parser;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
@@ -51,72 +49,30 @@ public sealed partial class FormattedMessage
/// <param name="toCopy">The message to copy.</param>
public FormattedMessage(FormattedMessage toCopy)
{
_nodes = toCopy._nodes.ShallowClone();
_nodes = Extensions.ShallowClone<MarkupNode>(toCopy._nodes);
}
private FormattedMessage(List<MarkupNode> nodes)
{
_nodes = nodes;
}
/// <summary>
/// Attempt to create a new formatted message from some markup text. Returns an error if it fails.
/// </summary>
public static bool TryFromMarkup(string markup, [NotNullWhen(true)] out FormattedMessage? msg, [NotNullWhen(false)] out string? error)
{
if (!TryParse(markup, out var nodes, out error))
{
msg = null;
return false;
}
msg = new FormattedMessage(nodes);
return true;
}
/// <summary>
/// Attempt to create a new formatted message from some markup text.
/// </summary>
public static bool TryFromMarkup(string markup, [NotNullWhen(true)] out FormattedMessage? msg)
=> TryFromMarkup(markup, out msg, out _);
/// <summary>
/// Attempt to create a new formatted message from some markup text. Throws if the markup is invalid.
/// </summary>
/// <exception cref="ParseException">Thrown when an error occurs while trying to parse the markup.</exception>
public static FormattedMessage FromMarkupOrThrow(string markup)
public static FormattedMessage FromMarkup(string markup)
{
var msg = new FormattedMessage();
msg.AddMarkupOrThrow(markup);
msg.AddMarkup(markup);
return msg;
}
[Obsolete("Use FromMarkupOrThrow or TryFromMarkup")]
public static FormattedMessage FromMarkup(string markup) => FromMarkupOrThrow(markup);
public static FormattedMessage FromUnformatted(string text)
public static FormattedMessage FromUnformatted(string markup)
{
var msg = new FormattedMessage();
msg.AddText(text);
msg.AddText(markup);
return msg;
}
/// <summary>
/// Variant of <see cref="TryFromMarkup(string,out Robust.Shared.Utility.FormattedMessage?,out string?)"/> that
/// attempts to fall back to using the permissive parser that interprets invalid markup tags as normal text.
/// This may still throw if the permissive parser fails.
/// </summary>
/// <exception cref="ParseException">Thrown when an error occurs while trying to parse the markup.</exception>
public static FormattedMessage FromMarkupPermissive(string markup, out string? error)
public static FormattedMessage FromMarkupPermissive(string markup)
{
var msg = new FormattedMessage();
msg.AddMarkupPermissive(markup, out error);
msg.AddMarkupPermissive(markup);
return msg;
}
/// <inheritdoc cref="FromMarkupPermissive(string,out string?)"/>
public static FormattedMessage FromMarkupPermissive(string markup) => FromMarkupPermissive(markup, out _);
/// <summary>
/// Escape a string of text to be able to be formatted into markup.
/// </summary>
@@ -126,28 +82,13 @@ public sealed partial class FormattedMessage
}
/// <summary>
/// Remove all markup, leaving only the basic text content behind. Throws if it fails to parse the markup tags.
/// Remove all markup, leaving only the basic text content behind.
/// </summary>
/// <exception cref="ParseException">Thrown when an error occurs while trying to parse the markup.</exception>
public static string RemoveMarkupOrThrow(string markup)
public static string RemoveMarkup(string text)
{
return FromMarkupOrThrow(markup).ToString();
return FromMarkup(text).ToString();
}
/// <summary>
/// Attempts to remove all valid markup tags, leaving only the basic text content behind.
/// If this markup contains invalid tags that cannot be parsed, they will not be removed and will instead be trated
/// as normal text. Hence the output should probably only be parsed using try-parse the permissive parser.
/// </summary>
/// <exception cref="ParseException">Thrown when an error occurs while trying to fall back to the permissive parser.</exception>
public static string RemoveMarkupPermissive(string markup)
{
return FromMarkupPermissive(markup).ToString();
}
[Obsolete("Use RemoveMarkupOrThrow or RemoveMarkupPermissive")]
public static string RemoveMarkup(string markup) => RemoveMarkupOrThrow(markup);
/// <summary>
/// Adds a text node.
/// This node doesn't need to be closed with <see cref="Pop"/>.

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,7 +21,6 @@ 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);
@@ -36,7 +35,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
entManMock.Setup(m => m.GetComponentInternal(entUid, CompIdx.Index<MetaDataComponent>()))
.Returns(compInstance);
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
var bus = new EntityEventBus(entManMock.Object);
bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
// Subscribe
@@ -81,7 +80,6 @@ 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 });
@@ -94,7 +92,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
entManMock.Setup(m => m.GetComponent(entUid, typeof(MetaDataComponent)))
.Returns(compInstance);
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
var bus = new EntityEventBus(entManMock.Object);
bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
// Subscribe
@@ -139,7 +137,6 @@ 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 });
@@ -152,7 +149,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
entManMock.Setup(m => m.GetComponent(entUid, typeof(MetaDataComponent)))
.Returns(compInstance);
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
var bus = new EntityEventBus(entManMock.Object);
bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
// Subscribe
@@ -187,7 +184,6 @@ 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()
@@ -212,7 +208,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, reflectMock.Object);
var bus = new EntityEventBus(entManMock.Object);
bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
// Subscribe

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