mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1904207358 | ||
|
|
939840ddab | ||
|
|
a6c295b89c | ||
|
|
165913a4de | ||
|
|
675dfdaabd | ||
|
|
fab172d6f6 | ||
|
|
e75c1659f6 | ||
|
|
0c440a8fc9 | ||
|
|
0c2c8f352a | ||
|
|
0a4a2b7a36 | ||
|
|
b5b59c1d2f | ||
|
|
f4f0967fdc | ||
|
|
3d69766112 | ||
|
|
d1eb3438d5 | ||
|
|
8f6b189d29 | ||
|
|
ef8b278b47 | ||
|
|
c53ce2c907 | ||
|
|
f063aa3ea1 | ||
|
|
30f63254ef | ||
|
|
30a5b6152c | ||
|
|
910a7f8bff | ||
|
|
526a88293e | ||
|
|
22cd840b83 |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,6 +54,53 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 162.2.2
|
||||
|
||||
|
||||
## 162.2.1
|
||||
|
||||
|
||||
## 162.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add support for automatically networking entity lists and sets.
|
||||
* Add nullable conversion operators for ProtoIds.
|
||||
* Add LocId serializer for validation.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix deleting a contact inside of collision events throwing.
|
||||
* Localize VV.
|
||||
|
||||
### Internal
|
||||
|
||||
* Use CollectionsMarshal in GameStateManager.
|
||||
|
||||
|
||||
## 162.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixes "NoSpawn" entities appearing in the spawn menu.
|
||||
|
||||
|
||||
## 162.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Mark ProtoId as NetSerializable.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Temporarily revert NetForceAckThreshold change as it can lead to client stalling.
|
||||
* Fix eye visibility layers not updating on children when a parent changes.
|
||||
|
||||
### Internal
|
||||
|
||||
* Use CollectionsMarshal in RobustTree and AddComponentInternal.
|
||||
|
||||
|
||||
## 162.0.0
|
||||
|
||||
### New features
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
## ViewVariablesInstanceEntity
|
||||
|
||||
view-variables = View Variables
|
||||
view-variable-instance-entity-server-components-add-component-button-placeholder = Add Component
|
||||
view-variable-instance-entity-client-variables-tab-title = Client Variables
|
||||
view-variable-instance-entity-client-components-tab-title = Client Components
|
||||
@@ -8,4 +9,4 @@ view-variable-instance-entity-server-components-tab-title = Server Components
|
||||
view-variable-instance-entity-client-components-search-bar-placeholder = Search
|
||||
view-variable-instance-entity-server-components-search-bar-placeholder = Search
|
||||
view-variable-instance-entity-add-window-server-components = Add Component [S]
|
||||
view-variable-instance-entity-add-window-client-components = Add Component [C]
|
||||
view-variable-instance-entity-add-window-client-components = Add Component [C]
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Input;
|
||||
@@ -759,25 +760,29 @@ namespace Robust.Client.GameStates
|
||||
if (es.EntityLastModified != nextState.ToSequence)
|
||||
continue;
|
||||
|
||||
if (_toApply.TryGetValue(uid.Value, out var state))
|
||||
_toApply[uid.Value] = (es.NetEntity, meta, state.EnteringPvs, state.LastApplied, state.curState, es);
|
||||
ref var state = ref CollectionsMarshal.GetValueRefOrAddDefault(_toApply, uid.Value, out var exists);
|
||||
|
||||
if (exists)
|
||||
state = (es.NetEntity, meta, state.EnteringPvs, state.LastApplied, state.curState, es);
|
||||
else
|
||||
_toApply[uid.Value] = (es.NetEntity, meta, false, GameTick.Zero, null, es);
|
||||
state = (es.NetEntity, meta, false, GameTick.Zero, null, es);
|
||||
}
|
||||
}
|
||||
|
||||
// Check pending states and see if we need to force any entities to re-run component states.
|
||||
foreach (var uid in _pendingReapplyNetStates.Keys)
|
||||
{
|
||||
// State already being re-applied so don't bulldoze it.
|
||||
if (_toApply.ContainsKey(uid))
|
||||
continue;
|
||||
|
||||
// Original entity referencing the NetEntity may have been deleted.
|
||||
if (!metas.TryGetComponent(uid, out var meta))
|
||||
continue;
|
||||
|
||||
_toApply[uid] = (meta.NetEntity, meta, false, GameTick.Zero, null, null);
|
||||
// State already being re-applied so don't bulldoze it.
|
||||
ref var state = ref CollectionsMarshal.GetValueRefOrAddDefault(_toApply, uid, out var exists);
|
||||
|
||||
if (exists)
|
||||
continue;
|
||||
|
||||
state = (meta.NetEntity, meta, false, GameTick.Zero, null, null);
|
||||
}
|
||||
|
||||
var queuedBroadphaseUpdates = new List<(EntityUid, TransformComponent)>(enteringPvs);
|
||||
@@ -1198,10 +1203,13 @@ namespace Robust.Client.GameStates
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_compStateWork.TryGetValue(compState.NetID, out var state))
|
||||
_compStateWork[compState.NetID] = (comp, state.curState, compState.State);
|
||||
ref var state =
|
||||
ref CollectionsMarshal.GetValueRefOrAddDefault(_compStateWork, compState.NetID, out var exists);
|
||||
|
||||
if (exists)
|
||||
state = (comp, state.curState, compState.State);
|
||||
else
|
||||
_compStateWork[compState.NetID] = (comp, null, compState.State);
|
||||
state = (comp, null, compState.State);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1218,14 +1226,19 @@ namespace Robust.Client.GameStates
|
||||
if (netId == null)
|
||||
continue;
|
||||
|
||||
if (_compStateWork.ContainsKey(netId.Value) ||
|
||||
!meta.NetComponents.TryGetValue(netId.Value, out var comp) ||
|
||||
if (!meta.NetComponents.TryGetValue(netId.Value, out var comp) ||
|
||||
!lastState.TryGetValue(netId.Value, out var lastCompState))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_compStateWork[netId.Value] = (comp, lastCompState, null);
|
||||
ref var compState =
|
||||
ref CollectionsMarshal.GetValueRefOrAddDefault(_compStateWork, netId.Value, out var exists);
|
||||
|
||||
if (exists)
|
||||
continue;
|
||||
|
||||
compState = (comp, lastCompState, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
@@ -361,9 +362,11 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var (netId, implicitCompState) in implicitEntState)
|
||||
{
|
||||
if (!fullRep.TryGetValue(netId, out var serverState))
|
||||
ref var serverState = ref CollectionsMarshal.GetValueRefOrAddDefault(fullRep, netId, out var exists);
|
||||
|
||||
if (!exists)
|
||||
{
|
||||
fullRep.Add(netId, implicitCompState);
|
||||
serverState = implicitCompState;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -379,7 +382,7 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
serverDelta.ApplyToFullState(implicitCompState);
|
||||
fullRep[netId] = implicitCompState;
|
||||
serverState = implicitCompState;
|
||||
DebugTools.Assert(implicitCompState is IComponentDeltaState d && d.FullState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' != 'True'" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.NFluidsynth" Version="0.1.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.7" />
|
||||
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" PrivateAssets="compile" />
|
||||
|
||||
@@ -98,6 +98,11 @@ namespace Robust.Client.UserInterface.Controls
|
||||
OnTextChanged?.Invoke(new LineEditEventArgs(this, _text));
|
||||
}
|
||||
|
||||
public void ForceSubmitText()
|
||||
{
|
||||
OnTextEntered?.Invoke(new LineEditEventArgs(this, _text));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The text
|
||||
/// </summary>
|
||||
@@ -607,7 +612,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
if (Editable)
|
||||
{
|
||||
OnTextEntered?.Invoke(new LineEditEventArgs(this, _text));
|
||||
ForceSubmitText();
|
||||
}
|
||||
|
||||
args.Handle();
|
||||
|
||||
@@ -50,13 +50,13 @@ namespace Robust.Client.UserInterface.Controls
|
||||
Action<Range> ev = _scrollValueChanged;
|
||||
_hScrollBar = new HScrollBar
|
||||
{
|
||||
Visible = false,
|
||||
Visible = _hScrollEnabled,
|
||||
VerticalAlignment = VAlignment.Bottom,
|
||||
HorizontalAlignment = HAlignment.Stretch
|
||||
};
|
||||
_vScrollBar = new VScrollBar
|
||||
{
|
||||
Visible = false,
|
||||
Visible = _vScrollEnabled,
|
||||
VerticalAlignment = VAlignment.Stretch,
|
||||
HorizontalAlignment = HAlignment.Right
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ using Robust.Client.ViewVariables.Editors;
|
||||
using Robust.Client.ViewVariables.Instances;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
@@ -232,7 +233,7 @@ namespace Robust.Client.ViewVariables
|
||||
instance = new ViewVariablesInstanceObject(this, _robustSerializer);
|
||||
}
|
||||
|
||||
var window = new DefaultWindow {Title = "View Variables"};
|
||||
var window = new DefaultWindow {Title = Loc.GetString("view-variables")};
|
||||
instance.Initialize(window, obj);
|
||||
window.OnClose += () => _closeInstance(instance, false);
|
||||
_windows.Add(instance, window);
|
||||
@@ -250,7 +251,7 @@ namespace Robust.Client.ViewVariables
|
||||
{
|
||||
var window = new DefaultWindow
|
||||
{
|
||||
Title = "View Variables",
|
||||
Title = Loc.GetString("view-variables"),
|
||||
SetSize = _defaultWindowSize
|
||||
};
|
||||
var loadingLabel = new Label {Text = "Retrieving remote object data from server..."};
|
||||
|
||||
@@ -75,13 +75,4 @@ public sealed class ServerMetaDataSystem : MetaDataSystem
|
||||
Dirty(uid, comp);
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetVisibilityMask(EntityUid uid, int value, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (!Resolve(uid, ref meta) || meta.VisibilityMask == value)
|
||||
return;
|
||||
|
||||
base.SetVisibilityMask(uid, value, meta);
|
||||
_pvsSystem.MarkDirty(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Robust.Server.GameStates;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
@@ -6,11 +7,18 @@ namespace Robust.Server.GameObjects
|
||||
{
|
||||
public sealed class VisibilitySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MetaDataSystem _metaSys = default!;
|
||||
[Dependency] private readonly PvsSystem _pvs = default!;
|
||||
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
private EntityQuery<MetaDataComponent> _metaQuery;
|
||||
private EntityQuery<VisibilityComponent> _visiblityQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
_visiblityQuery = GetEntityQuery<VisibilityComponent>();
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChange);
|
||||
EntityManager.EntityInitialized += OnEntityInit;
|
||||
}
|
||||
@@ -82,27 +90,58 @@ namespace Robust.Server.GameObjects
|
||||
RefreshVisibility(uid);
|
||||
}
|
||||
|
||||
public void RefreshVisibility(EntityUid uid, MetaDataComponent? metaDataComponent = null, VisibilityComponent? visibilityComponent = null)
|
||||
public void RefreshVisibility(EntityUid uid,
|
||||
VisibilityComponent? visibilityComponent = null,
|
||||
MetaDataComponent? meta = null)
|
||||
{
|
||||
if (Resolve(uid, ref metaDataComponent, false))
|
||||
_metaSys.SetVisibilityMask(uid, GetVisibilityMask(uid, visibilityComponent), metaDataComponent);
|
||||
if (!_metaQuery.Resolve(uid, ref meta, false))
|
||||
return;
|
||||
|
||||
// Iterate up through parents and calculate the cumulative visibility mask.
|
||||
var mask = GetParentVisibilityMask(uid, visibilityComponent);
|
||||
|
||||
// Iterate down through children and propagate mask changes.
|
||||
RecursivelyApplyVisibility(uid, mask, meta);
|
||||
}
|
||||
|
||||
private void RecursivelyApplyVisibility(EntityUid uid, int mask, MetaDataComponent meta)
|
||||
{
|
||||
if (meta.VisibilityMask == mask)
|
||||
return;
|
||||
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
meta.VisibilityMask = mask;
|
||||
_pvs.MarkDirty(uid, xform);
|
||||
|
||||
foreach (var child in xform.ChildEntities)
|
||||
{
|
||||
if (!_metaQuery.TryGetComponent(child, out var childMeta))
|
||||
continue;
|
||||
|
||||
var childMask = mask;
|
||||
|
||||
if (_visiblityQuery.TryGetComponent(child, out VisibilityComponent? hildVis))
|
||||
childMask |= hildVis.Layer;
|
||||
|
||||
RecursivelyApplyVisibility(child, childMask, childMeta);
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Use overload that takes an EntityUid instead")]
|
||||
public void RefreshVisibility(VisibilityComponent visibilityComponent)
|
||||
{
|
||||
RefreshVisibility(visibilityComponent.Owner, null, visibilityComponent);
|
||||
RefreshVisibility(visibilityComponent.Owner, visibilityComponent);
|
||||
}
|
||||
|
||||
private int GetVisibilityMask(EntityUid uid, VisibilityComponent? visibilityComponent = null, TransformComponent? xform = null)
|
||||
private int GetParentVisibilityMask(EntityUid uid, VisibilityComponent? visibilityComponent = null)
|
||||
{
|
||||
int visMask = 1; // apparently some content expects everything to have the first bit/flag set to true.
|
||||
if (Resolve(uid, ref visibilityComponent, false))
|
||||
if (_visiblityQuery.Resolve(uid, ref visibilityComponent, false))
|
||||
visMask |= visibilityComponent.Layer;
|
||||
|
||||
// Include parent vis masks
|
||||
if (Resolve(uid, ref xform) && xform.ParentUid.IsValid())
|
||||
visMask |= GetVisibilityMask(xform.ParentUid);
|
||||
if (_xformQuery.TryGetComponent(uid, out var xform) && xform.ParentUid.IsValid())
|
||||
visMask |= GetParentVisibilityMask(xform.ParentUid);
|
||||
|
||||
return visMask;
|
||||
}
|
||||
|
||||
@@ -92,9 +92,9 @@ namespace Robust.Server.GameStates
|
||||
/// <summary>
|
||||
/// Marks an entity's current chunk as dirty.
|
||||
/// </summary>
|
||||
internal void MarkDirty(EntityUid uid)
|
||||
internal void MarkDirty(EntityUid uid, TransformComponent xform)
|
||||
{
|
||||
var coordinates = _transform.GetMoverCoordinates(uid);
|
||||
var coordinates = _transform.GetMoverCoordinates(uid, xform);
|
||||
_entityPvsCollection.MarkDirty(_entityPvsCollection.GetChunkIndex(coordinates));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,16 +35,10 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
public const float ChunkSize = 8;
|
||||
|
||||
// TODO make this a cvar. Make it in terms of seconds and tie it to tick rate?
|
||||
// Main issue is that I CBF figuring out the logic for handling it changing mid-game.
|
||||
public const int DirtyBufferSize = 20;
|
||||
// Note: If a client has ping higher than TickBuffer / TickRate, then the server will treat every entity as if it
|
||||
// had entered PVS for the first time. Note that due to the PVS budget, this buffer is easily overwhelmed.
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="CVars.NetForceAckThreshold"/>.
|
||||
/// </summary>
|
||||
public int ForceAckThreshold { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of pooled objects
|
||||
/// </summary>
|
||||
@@ -145,7 +139,6 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
_configManager.OnValueChanged(CVars.NetPVS, SetPvs, true);
|
||||
_configManager.OnValueChanged(CVars.NetMaxUpdateRange, OnViewsizeChanged, true);
|
||||
_configManager.OnValueChanged(CVars.NetForceAckThreshold, OnForceAckChanged, true);
|
||||
|
||||
_serverGameStateManager.ClientAck += OnClientAck;
|
||||
_serverGameStateManager.ClientRequestFull += OnClientRequestFull;
|
||||
@@ -163,7 +156,6 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
_configManager.UnsubValueChanged(CVars.NetPVS, SetPvs);
|
||||
_configManager.UnsubValueChanged(CVars.NetMaxUpdateRange, OnViewsizeChanged);
|
||||
_configManager.UnsubValueChanged(CVars.NetForceAckThreshold, OnForceAckChanged);
|
||||
|
||||
_serverGameStateManager.ClientAck -= OnClientAck;
|
||||
_serverGameStateManager.ClientRequestFull -= OnClientRequestFull;
|
||||
@@ -219,11 +211,6 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
_viewSize = obj * 2;
|
||||
}
|
||||
|
||||
private void OnForceAckChanged(int value)
|
||||
{
|
||||
ForceAckThreshold = value;
|
||||
}
|
||||
|
||||
private void SetPvs(bool value)
|
||||
{
|
||||
_seenAllEnts.Clear();
|
||||
@@ -663,16 +650,16 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private bool AddToChunkSetRecursively(in EntityUid uid, in NetEntity netEntity, MetaDataComponent mComp,
|
||||
private void AddToChunkSetRecursively(in EntityUid uid, in NetEntity netEntity, MetaDataComponent mComp,
|
||||
int visMask, RobustTree<NetEntity> tree, Dictionary<NetEntity, MetaDataComponent> set)
|
||||
{
|
||||
if (set.ContainsKey(netEntity))
|
||||
return true;
|
||||
|
||||
// TODO: Don't need to know about parents so no longer need to use bool for this method.
|
||||
// If the eye is missing ANY layer this entity or any of its parents belongs to, it is considered invisible.
|
||||
// If the eye is missing ANY layer that this entity is on, or any layer that any of its parents belongs to, then
|
||||
// it is considered invisible.
|
||||
if ((visMask & mComp.VisibilityMask) != mComp.VisibilityMask)
|
||||
return false;
|
||||
return;
|
||||
|
||||
if (!set.TryAdd(netEntity, mComp))
|
||||
return; // already sending
|
||||
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
|
||||
@@ -682,8 +669,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
{
|
||||
DebugTools.Assert(_mapManager.IsGrid(uid) || _mapManager.IsMap(uid));
|
||||
tree.Set(netEntity);
|
||||
set.Add(netEntity, mComp);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
DebugTools.Assert(!_mapManager.IsGrid(uid) && !_mapManager.IsMap(uid));
|
||||
@@ -692,15 +678,12 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
var parentMeta = _metaQuery.GetComponent(parent);
|
||||
var parentNetEntity = parentMeta.NetEntity;
|
||||
|
||||
if (!AddToChunkSetRecursively(in parent, in parentNetEntity, parentMeta, visMask, tree, set)) //did we just fail to add the parent?
|
||||
{
|
||||
return false; //we failed? suppose we dont get added either
|
||||
}
|
||||
// Child should have all o the same flags as the parent.
|
||||
DebugTools.Assert((parentMeta.VisibilityMask & mComp.VisibilityMask) == parentMeta.VisibilityMask);
|
||||
|
||||
//i want it to crash here if it gets added double bc that shouldnt happen and will add alot of unneeded cycles
|
||||
// Add our parent.
|
||||
AddToChunkSetRecursively(in parent, in parentNetEntity, parentMeta, visMask, tree, set);
|
||||
tree.Set(netEntity, parentNetEntity);
|
||||
set.Add(netEntity, mComp);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal (List<EntityState>? updates, List<NetEntity>? deletions, List<NetEntity>? leftPvs, GameTick fromTick)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -89,56 +89,76 @@ public sealed class RobustTree<T> where T : notnull
|
||||
throw new InvalidOperationException("Node neither had a parent nor was a RootNode.");
|
||||
}
|
||||
|
||||
public TreeNode Set(T rootNode)
|
||||
public void Set(T rootNode)
|
||||
{
|
||||
//root node, for now
|
||||
if (_nodeIndex.TryGetValue(rootNode, out var node))
|
||||
ref var node = ref CollectionsMarshal.GetValueRefOrAddDefault(_nodeIndex, rootNode, out var exists);
|
||||
|
||||
if (exists)
|
||||
{
|
||||
if(!RootNodes.Contains(rootNode))
|
||||
throw new InvalidOperationException("Node already exists as non-root node.");
|
||||
return node;
|
||||
return;
|
||||
}
|
||||
|
||||
node = new TreeNode(rootNode);
|
||||
_nodeIndex.Add(rootNode, node);
|
||||
RootNodes.Add(rootNode);
|
||||
return node;
|
||||
if(!RootNodes.Add(rootNode))
|
||||
throw new InvalidOperationException("Non-existent node was already a root node?");
|
||||
}
|
||||
|
||||
public TreeNode Set(T child, T parent)
|
||||
public void Set(T child, T parent)
|
||||
{
|
||||
if (!_nodeIndex.TryGetValue(parent, out var parentNode))
|
||||
parentNode = Set(parent);
|
||||
|
||||
if (parentNode.Children == null)
|
||||
// Code block for where parentNode is a valid ref
|
||||
{
|
||||
_nodeIndex[parent] = parentNode = parentNode.WithChildren(_pool.Get());
|
||||
}
|
||||
ref var parentNode = ref CollectionsMarshal.GetValueRefOrAddDefault(_nodeIndex, parent, out var parentExists);
|
||||
|
||||
if (_nodeIndex.TryGetValue(child, out var existingNode))
|
||||
{
|
||||
if (RootNodes.Contains(child))
|
||||
// If parent does not exist we make it a new root node.
|
||||
if (!parentExists)
|
||||
{
|
||||
parentNode.Children!.Add(existingNode.Value);
|
||||
RootNodes.Remove(child);
|
||||
_parents.Add(child, parent);
|
||||
return existingNode;
|
||||
parentNode = new TreeNode(parent);
|
||||
if (!RootNodes.Add(parent))
|
||||
{
|
||||
_nodeIndex.Remove(parent);
|
||||
throw new InvalidOperationException("Non-existent node was already a root node?");
|
||||
}
|
||||
}
|
||||
|
||||
if (!_parents.TryGetValue(child, out var previousParent) || _nodeIndex.TryGetValue(previousParent, out var previousParentNode))
|
||||
throw new InvalidOperationException("Could not find old parent for non-root node.");
|
||||
|
||||
previousParentNode.Children?.Remove(existingNode.Value);
|
||||
parentNode.Children!.Add(existingNode.Value);
|
||||
_parents[child] = parent;
|
||||
return existingNode;
|
||||
var children = parentNode.Children;
|
||||
if (children == null)
|
||||
{
|
||||
children = _pool.Get();
|
||||
parentNode = parentNode.WithChildren(children);
|
||||
DebugTools.AssertNotNull(_nodeIndex[parent].Children);
|
||||
}
|
||||
children.Add(child);
|
||||
}
|
||||
|
||||
existingNode = new TreeNode(child);
|
||||
_nodeIndex.Add(child, existingNode);
|
||||
parentNode.Children!.Add(existingNode.Value);
|
||||
_parents.Add(child, parent);
|
||||
return existingNode;
|
||||
// No longer safe to access parentNode ref after this.
|
||||
|
||||
ref var node = ref CollectionsMarshal.GetValueRefOrAddDefault(_nodeIndex, child, out var childExists);
|
||||
if (!childExists)
|
||||
{
|
||||
// This is the path that PVS should take 99% of the time.
|
||||
node = new TreeNode(child);
|
||||
_parents.Add(child, parent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (RootNodes.Remove(child))
|
||||
{
|
||||
DebugTools.Assert(!_parents.ContainsKey(child));
|
||||
_parents.Add(child, parent);
|
||||
return;
|
||||
}
|
||||
|
||||
ref var parentEntry = ref CollectionsMarshal.GetValueRefOrAddDefault(_parents, child, out var previousParentExists);
|
||||
if (!previousParentExists || !_nodeIndex.TryGetValue(parentEntry!, out var previousParentNode))
|
||||
{
|
||||
parentEntry = parent;
|
||||
throw new InvalidOperationException("Could not find old parent for non-root node.");
|
||||
}
|
||||
|
||||
previousParentNode.Children?.Remove(child);
|
||||
parentEntry = parent;
|
||||
}
|
||||
|
||||
public readonly struct TreeNode : IEquatable<TreeNode>
|
||||
|
||||
@@ -373,21 +373,6 @@ Oldest acked clients: {string.Join(", ", players)}
|
||||
// If the state is too big we let Lidgren send it reliably. This is to avoid a situation where a state is so
|
||||
// large that it (or part of it) consistently gets dropped. When we send reliably, we immediately update the
|
||||
// ack so that the next state will not also be huge.
|
||||
//
|
||||
// We also do this if the client's last ack is too old. This helps prevent things like the entity deletion
|
||||
// history from becoming too bloated if a bad client fails to send acks for whatever reason.
|
||||
|
||||
if (_gameTiming.CurTick.Value > lastAck.Value + _pvs.ForceAckThreshold)
|
||||
{
|
||||
stateUpdateMessage.ForceSendReliably = true;
|
||||
|
||||
// Aside from the time shortly after connecting, this shouldn't be common. If it is happening,
|
||||
// something is probably wrong. If it is more frequent than I think, this can be downgraded to a warning.
|
||||
var connectedTime = (DateTime.UtcNow - session.ConnectedTime).TotalMinutes;
|
||||
if (lastAck > GameTick.Zero && connectedTime > 1)
|
||||
_logger.Error($"Client {session} exceeded ack-tick threshold. Last ack: {lastAck}. Cur tick: {_gameTiming.CurTick}. Connect time: {connectedTime} minutes");
|
||||
}
|
||||
|
||||
if (stateUpdateMessage.ShouldSendReliably())
|
||||
{
|
||||
sessionData.LastReceivedAck = _gameTiming.CurTick;
|
||||
|
||||
@@ -16,12 +16,15 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
private const string MemberAttributeName = "Robust.Shared.Analyzers.AutoNetworkedFieldAttribute";
|
||||
private const string GlobalEntityUidName = "global::Robust.Shared.GameObjects.EntityUid";
|
||||
private const string GlobalNullableEntityUidName = "global::Robust.Shared.GameObjects.EntityUid?";
|
||||
private const string GlobalEntityCoordinatesName = "global::Robust.Shared.Map.EntityCoordinates?";
|
||||
private const string GlobalNullableEntityCoordinatesName = "global::Robust.Shared.Map.EntityCoordinates";
|
||||
private const string GlobalEntityCoordinatesName = "global::Robust.Shared.Map.EntityCoordinates";
|
||||
private const string GlobalNullableEntityCoordinatesName = "global::Robust.Shared.Map.EntityCoordinates?";
|
||||
private const string GlobalEntityUidSetName = "global::System.Collections.Generic.HashSet<global::Robust.Shared.GameObjects.EntityUid>";
|
||||
private const string GlobalNetEntityUidSetName = "global::System.Collections.Generic.HashSet<global::Robust.Shared.GameObjects.NetEntity>";
|
||||
private const string GlobalEntityUidListName = "global::System.Collections.Generic.List<global::Robust.Shared.GameObjects.EntityUid>";
|
||||
private const string GlobalNetEntityUidListName = "global::System.Collections.Generic.List<global::Robust.Shared.GameObjects.NetEntity>";
|
||||
|
||||
private static string GenerateSource(in GeneratorExecutionContext context, INamedTypeSymbol classSymbol, CSharpCompilation comp, bool raiseAfterAutoHandle)
|
||||
{
|
||||
// Debugger.Launch();
|
||||
var nameSpace = classSymbol.ContainingNamespace.ToDisplayString();
|
||||
var componentName = classSymbol.Name;
|
||||
var stateName = $"{componentName}_AutoState";
|
||||
@@ -172,6 +175,26 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
component.{name} = state.{name};");
|
||||
}
|
||||
|
||||
break;
|
||||
case GlobalEntityUidSetName:
|
||||
stateFields.Append($@"
|
||||
public {GlobalNetEntityUidSetName} {name} = default!;");
|
||||
|
||||
getStateInit.Append($@"
|
||||
{name} = GetNetEntitySet(component.{name}),");
|
||||
handleStateSetters.Append($@"
|
||||
component.{name} = EnsureEntitySet<{componentName}>(state.{name}, uid);");
|
||||
|
||||
break;
|
||||
case GlobalEntityUidListName:
|
||||
stateFields.Append($@"
|
||||
public {GlobalNetEntityUidListName} {name} = default!;");
|
||||
|
||||
getStateInit.Append($@"
|
||||
{name} = GetNetEntityList(component.{name}),");
|
||||
handleStateSetters.Append($@"
|
||||
component.{name} = EnsureEntityList<{componentName}>(state.{name}, uid);");
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,13 +172,6 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<float> NetMaxUpdateRange =
|
||||
CVarDef.Create("net.maxupdaterange", 12.5f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum allowed delay between the current tick and a client's last acknowledged tick before we send the
|
||||
/// next game state reliably and simply force update the acked tick,
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> NetForceAckThreshold =
|
||||
CVarDef.Create("net.force_ack_threshold", 40, CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// This limits the number of new entities that can be sent to a client in a single game state. This exists to
|
||||
/// avoid stuttering on the client when it has to spawn a bunch of entities in a single tick. If ever entity
|
||||
|
||||
@@ -189,15 +189,8 @@ namespace Robust.Shared.GameObjects
|
||||
/// <remarks>
|
||||
/// Every entity will always have the first bit set to true.
|
||||
/// </remarks>
|
||||
[Access(typeof(MetaDataSystem))]
|
||||
public int VisibilityMask = 1;
|
||||
|
||||
[UsedImplicitly, ViewVariables(VVAccess.ReadWrite)]
|
||||
private int VVVisibilityMask
|
||||
{
|
||||
get => VisibilityMask;
|
||||
set => IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<MetaDataSystem>().SetVisibilityMask(Owner, value, this);
|
||||
}
|
||||
[ViewVariables] // TODO ACCESS RRestrict writing to server-side visibility system
|
||||
public int VisibilityMask { get; internal set; }= 1;
|
||||
|
||||
[ViewVariables]
|
||||
public bool EntityPaused => PauseTime != null;
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Log;
|
||||
@@ -285,17 +286,29 @@ namespace Robust.Shared.GameObjects
|
||||
var dict = _entTraitArray[type.Value];
|
||||
DebugTools.Assert(dict != null);
|
||||
|
||||
if (dict.TryGetValue(uid, out var duplicate))
|
||||
// Code block to restrict access to ref comp.
|
||||
{
|
||||
if (!overwrite && !duplicate.Deleted)
|
||||
throw new InvalidOperationException(
|
||||
$"Component reference type {component.GetType().Name} already occupied by {duplicate}");
|
||||
ref var comp = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, uid, out var exists);
|
||||
if (exists)
|
||||
{
|
||||
if (!overwrite && !comp!.Deleted)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Component reference type {reg.Name} already occupied by {comp}");
|
||||
}
|
||||
|
||||
RemoveComponentImmediate(duplicate, uid, false, metadata);
|
||||
// This will invalidate the comp ref as it removes the key from the dictionary.
|
||||
// This is inefficient, but component overriding rarely ever happens.
|
||||
RemoveComponentImmediate(comp!, uid, false, metadata);
|
||||
dict.Add(uid, component);
|
||||
}
|
||||
else
|
||||
{
|
||||
comp = component;
|
||||
}
|
||||
}
|
||||
|
||||
// actually ADD the component
|
||||
dict.Add(uid, component);
|
||||
_entCompIndex.Add(uid, component);
|
||||
|
||||
// add the component to the netId grid
|
||||
|
||||
@@ -148,12 +148,6 @@ public abstract class MetaDataSystem : EntitySystem
|
||||
|
||||
component.Flags &= ~ev.ToRemove;
|
||||
}
|
||||
|
||||
public virtual void SetVisibilityMask(EntityUid uid, int value, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (Resolve(uid, ref meta))
|
||||
meta.VisibilityMask = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.Localization
|
||||
@@ -23,7 +22,7 @@ namespace Robust.Shared.Localization
|
||||
public interface ILocalizationManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a language approrpiate string represented by the supplied messageId.
|
||||
/// Gets a language appropriate string represented by the supplied messageId.
|
||||
/// </summary>
|
||||
/// <param name="messageId">Unique Identifier for a translated message.</param>
|
||||
/// <returns>
|
||||
@@ -31,6 +30,13 @@ namespace Robust.Shared.Localization
|
||||
/// </returns>
|
||||
string GetString(string messageId);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the specified id has been registered, without checking its arguments.
|
||||
/// </summary>
|
||||
/// <param name="messageId">Unique Identifier for a translated message.</param>
|
||||
/// <returns>true if it exists, even if it requires any parameters to be passed.</returns>
|
||||
bool HasString(string messageId);
|
||||
|
||||
/// <summary>
|
||||
/// Try- version of <see cref="GetString(string)"/>
|
||||
/// </summary>
|
||||
@@ -46,7 +52,7 @@ namespace Robust.Shared.Localization
|
||||
string GetString(string messageId, params (string, object)[] args);
|
||||
|
||||
/// <summary>
|
||||
/// Try- version of <see cref="GetString(string, ValueTuple{string, object}[])"/>
|
||||
/// Try- version of <see cref="GetString(string, (string, object)[])"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Does not log a warning if the message does not exist.
|
||||
|
||||
41
Robust.Shared/Localization/LocId.cs
Normal file
41
Robust.Shared/Localization/LocId.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
|
||||
namespace Robust.Shared.Localization;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper type for a localization string id.
|
||||
/// </summary>
|
||||
/// <param name="Id">The id of the localization string.</param>
|
||||
/// <remarks>
|
||||
/// This will be automatically validated by <see cref="LocIdSerializer"/> if used in data fields.</remarks>
|
||||
/// <seealso cref="Loc.GetString(string)"/>
|
||||
[Serializable, NetSerializable]
|
||||
public readonly record struct LocId(string Id) : IEquatable<string>, IComparable<LocId>
|
||||
{
|
||||
public static implicit operator string(LocId locId)
|
||||
{
|
||||
return locId.Id;
|
||||
}
|
||||
|
||||
public static implicit operator LocId(string id)
|
||||
{
|
||||
return new LocId(id);
|
||||
}
|
||||
|
||||
public static implicit operator LocId?(string? id)
|
||||
{
|
||||
return id == null ? default(LocId?) : new LocId(id);
|
||||
}
|
||||
|
||||
public bool Equals(string? other)
|
||||
{
|
||||
return Id == other;
|
||||
}
|
||||
|
||||
public int CompareTo(LocId other)
|
||||
{
|
||||
return string.Compare(Id, other.Id, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Linguini.Bundle;
|
||||
using Linguini.Bundle.Builder;
|
||||
using Linguini.Bundle.Errors;
|
||||
@@ -55,7 +54,6 @@ namespace Robust.Shared.Localization
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
public string GetString(string messageId, params (string, object)[] args0)
|
||||
{
|
||||
if (_defaultCulture == null)
|
||||
@@ -70,6 +68,11 @@ namespace Robust.Shared.Localization
|
||||
return msg;
|
||||
}
|
||||
|
||||
public bool HasString(string messageId)
|
||||
{
|
||||
return HasMessage(messageId, out _);
|
||||
}
|
||||
|
||||
public bool TryGetString(string messageId, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
return TryGetString(messageId, out value, null);
|
||||
|
||||
@@ -95,8 +95,6 @@ namespace Robust.Shared.Network.Messages
|
||||
MsgSize = buffer.LengthBytes;
|
||||
}
|
||||
|
||||
public bool ForceSendReliably;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this state message is large enough to warrant being sent reliably.
|
||||
/// This is only valid after
|
||||
@@ -105,7 +103,7 @@ namespace Robust.Shared.Network.Messages
|
||||
public bool ShouldSendReliably()
|
||||
{
|
||||
DebugTools.Assert(_hasWritten, "Attempted to determine sending method before determining packet size.");
|
||||
return ForceSendReliably || MsgSize > ReliableThreshold;
|
||||
return MsgSize > ReliableThreshold;
|
||||
}
|
||||
|
||||
public override NetDeliveryMethod DeliveryMethod
|
||||
|
||||
@@ -365,5 +365,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
/// Is this a special contact for grid-grid collisions
|
||||
/// </summary>
|
||||
Grid = 1 << 2,
|
||||
|
||||
Deleting = 1 << 3,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,8 +189,6 @@ namespace Robust.Shared.Physics.Systems
|
||||
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase, physicsMap);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (updates)
|
||||
{
|
||||
var resetMass = fixture.Density > 0f;
|
||||
|
||||
@@ -312,12 +312,17 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
public void DestroyContact(Contact contact)
|
||||
{
|
||||
// Don't recursive update or we're in for a bad time.
|
||||
if ((contact.Flags & ContactFlags.Deleting) != 0x0)
|
||||
return;
|
||||
|
||||
Fixture fixtureA = contact.FixtureA!;
|
||||
Fixture fixtureB = contact.FixtureB!;
|
||||
var bodyA = contact.BodyA!;
|
||||
var bodyB = contact.BodyB!;
|
||||
var aUid = contact.EntityA;
|
||||
var bUid = contact.EntityB;
|
||||
contact.Flags |= ContactFlags.Deleting;
|
||||
|
||||
if (contact.IsTouching)
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Shared.Prototypes;
|
||||
/// </remarks>
|
||||
/// <remarks><seealso cref="ProtoId{T}"/> for a wrapper of other prototype kinds.</remarks>
|
||||
[Serializable, NetSerializable]
|
||||
public readonly record struct EntProtoId(string Id)
|
||||
public readonly record struct EntProtoId(string Id) : IEquatable<string>, IComparable<EntProtoId>
|
||||
{
|
||||
public static implicit operator string(EntProtoId protoId)
|
||||
{
|
||||
@@ -24,4 +24,19 @@ public readonly record struct EntProtoId(string Id)
|
||||
{
|
||||
return new EntProtoId(id);
|
||||
}
|
||||
|
||||
public static implicit operator EntProtoId?(string? id)
|
||||
{
|
||||
return id == null ? default(EntProtoId?) : new EntProtoId(id);
|
||||
}
|
||||
|
||||
public bool Equals(string? other)
|
||||
{
|
||||
return Id == other;
|
||||
}
|
||||
|
||||
public int CompareTo(EntProtoId other)
|
||||
{
|
||||
return string.Compare(Id, other.Id, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace Robust.Shared.Prototypes
|
||||
[Obsolete("Use the HideSpawnMenu")]
|
||||
public bool NoSpawn { get; private set; }
|
||||
|
||||
public bool HideSpawnMenu => Categories.Contains(HideCategory);
|
||||
public bool HideSpawnMenu => Categories.Contains(HideCategory) || NoSpawn;
|
||||
|
||||
[DataField("placement")]
|
||||
private EntityPlacementProperties PlacementProperties = new();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
|
||||
using System;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
|
||||
|
||||
namespace Robust.Shared.Prototypes;
|
||||
|
||||
@@ -11,7 +12,8 @@ namespace Robust.Shared.Prototypes;
|
||||
/// This will be automatically validated by <see cref="ProtoIdSerializer{T}"/> if used in data fields.
|
||||
/// </remarks>
|
||||
/// <remarks><seealso cref="EntProtoId"/> for an <see cref="EntityPrototype"/> alias.</remarks>
|
||||
public readonly record struct ProtoId<T>(string Id) where T : class, IPrototype
|
||||
[Serializable]
|
||||
public readonly record struct ProtoId<T>(string Id) : IEquatable<string>, IComparable<ProtoId<T>> where T : class, IPrototype
|
||||
{
|
||||
public static implicit operator string(ProtoId<T> protoId)
|
||||
{
|
||||
@@ -22,4 +24,19 @@ public readonly record struct ProtoId<T>(string Id) where T : class, IPrototype
|
||||
{
|
||||
return new ProtoId<T>(id);
|
||||
}
|
||||
|
||||
public static implicit operator ProtoId<T>?(string? id)
|
||||
{
|
||||
return id == null ? default(ProtoId<T>?) : new ProtoId<T>(id);
|
||||
}
|
||||
|
||||
public bool Equals(string? other)
|
||||
{
|
||||
return Id == other;
|
||||
}
|
||||
|
||||
public int CompareTo(ProtoId<T> other)
|
||||
{
|
||||
return string.Compare(Id, other.Id, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<PackageReference Include="Linguini.Bundle" Version="0.1.3" />
|
||||
<PackageReference Include="SharpZstd.Interop" Version="1.5.2-beta2" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.7" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" PrivateAssets="compile" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
using static Robust.Shared.Serialization.Manager.ISerializationManager;
|
||||
|
||||
namespace Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
|
||||
/// <summary>
|
||||
/// Serializer used automatically for <see cref="LocId"/> types.
|
||||
/// </summary>
|
||||
[TypeSerializer]
|
||||
public sealed class LocIdSerializer : ITypeSerializer<LocId, ValueDataNode>, ITypeCopyCreator<LocId>
|
||||
{
|
||||
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||
{
|
||||
var loc = dependencies.Resolve<ILocalizationManager>();
|
||||
if (loc.HasString(node.Value))
|
||||
return new ValidatedValueNode(node);
|
||||
|
||||
return new ErrorNode(node, $"No localization message found with id {node.Value}");
|
||||
}
|
||||
|
||||
public LocId Read(ISerializationManager serializationManager, ValueDataNode node, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null, InstantiationDelegate<LocId>? instanceProvider = null)
|
||||
{
|
||||
return new LocId(node.Value);
|
||||
}
|
||||
|
||||
public DataNode Write(ISerializationManager serializationManager, LocId value, IDependencyCollection dependencies, bool alwaysWrite = false, ISerializationContext? context = null)
|
||||
{
|
||||
return new ValueDataNode(value);
|
||||
}
|
||||
|
||||
public LocId CreateCopy(ISerializationManager serializationManager, LocId source, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -43,14 +43,7 @@ namespace Robust.Shared.Utility
|
||||
public bool Add(TKey key, TValue value)
|
||||
{
|
||||
InitializedCheck();
|
||||
|
||||
if (_index.TryGetValue(key, out var set))
|
||||
{
|
||||
return set.Add(value);
|
||||
}
|
||||
|
||||
_index.Add(key, new HashSet<TValue> {value});
|
||||
return true;
|
||||
return _index.GetOrNew(key).Add(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
131
Robust.UnitTesting/Shared/GameState/VisibilityTest.cs
Normal file
131
Robust.UnitTesting/Shared/GameState/VisibilityTest.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameState;
|
||||
|
||||
public sealed partial class VisibilityTest : RobustIntegrationTest
|
||||
{
|
||||
/// <summary>
|
||||
/// This tests checks that entity visibility masks are recursively applied to children.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task UnknownEntityTest()
|
||||
{
|
||||
var server = StartServer();
|
||||
|
||||
var xforms = server.System<SharedTransformSystem>();
|
||||
var vis = server.System<VisibilitySystem>();
|
||||
|
||||
const int RequiredMask = 1;
|
||||
// All entities need to have this mask set ... which defeat the whole point of that bit?
|
||||
|
||||
// Spawn a stack of entities
|
||||
int N = 6;
|
||||
var ents = new EntityUid[N];
|
||||
var metaComp = new MetaDataComponent[N];
|
||||
var visComp = new VisibilityComponent[N];
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
var ent = server.EntMan.Spawn();
|
||||
|
||||
ents[i] = ent;
|
||||
metaComp[i] = server.EntMan.GetComponent<MetaDataComponent>(ent);
|
||||
visComp[i] = server.EntMan.AddComponent<VisibilityComponent>(ent);
|
||||
|
||||
vis.AddLayer(ent, visComp[i], 1 << i);
|
||||
if (i > 0)
|
||||
xforms.SetParent(ent, ents[i - 1]);
|
||||
}
|
||||
});
|
||||
|
||||
// Each entity's visibility mask should include the parent's mask
|
||||
var mask = RequiredMask;
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
mask |= 1 << i;
|
||||
var meta = metaComp[i];
|
||||
Assert.That(meta.VisibilityMask, Is.EqualTo(mask));
|
||||
}
|
||||
|
||||
// Adding a layer to the root entity's mask will apply it to all children
|
||||
var extraMask = 1 << (N + 1);
|
||||
mask = RequiredMask | extraMask;
|
||||
vis.AddLayer(ents[0], visComp[0], extraMask);
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
mask |= 1 << i;
|
||||
var meta = metaComp[i];
|
||||
Assert.That(meta.VisibilityMask, Is.EqualTo(mask));
|
||||
}
|
||||
|
||||
// Removing the removes it from all children.
|
||||
vis.RemoveLayer(ents[0], visComp[0], extraMask);
|
||||
mask = RequiredMask;
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
mask |= 1 << i;
|
||||
var meta = metaComp[i];
|
||||
Assert.That(meta.VisibilityMask, Is.EqualTo(mask));
|
||||
}
|
||||
|
||||
// Detaching an entity from the stack updates it, and it's children's mask
|
||||
var split = N / 2;
|
||||
await server.WaitPost(() => xforms.SetParent(ents[split], EntityUid.Invalid));
|
||||
|
||||
mask = RequiredMask;
|
||||
for (int i = 0; i < split; i++)
|
||||
{
|
||||
mask |= 1 << i;
|
||||
var meta = metaComp[i];
|
||||
Assert.That(meta.VisibilityMask, Is.EqualTo(mask));
|
||||
}
|
||||
|
||||
mask = RequiredMask;
|
||||
for (int i = split; i < N; i++)
|
||||
{
|
||||
mask |= 1 << i;
|
||||
var meta = metaComp[i];
|
||||
Assert.That(meta.VisibilityMask, Is.EqualTo(mask));
|
||||
}
|
||||
|
||||
// Re-attaching the entity also updates the masks.
|
||||
await server.WaitPost(() => xforms.SetParent(ents[split], ents[split-1]));
|
||||
mask = RequiredMask;
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
mask |= 1 << i;
|
||||
var meta = metaComp[i];
|
||||
Assert.That(meta.VisibilityMask, Is.EqualTo(mask));
|
||||
}
|
||||
|
||||
// Setting a mask on a child does not propagate upwards, only downwards
|
||||
vis.AddLayer(ents[split], visComp[split], extraMask);
|
||||
mask = RequiredMask;
|
||||
for (int i = 0; i < split; i++)
|
||||
{
|
||||
mask |= 1 << i;
|
||||
var meta = metaComp[i];
|
||||
Assert.That(meta.VisibilityMask, Is.EqualTo(mask));
|
||||
}
|
||||
|
||||
mask |= extraMask;
|
||||
for (int i = split; i < N; i++)
|
||||
{
|
||||
mask |= 1 << i;
|
||||
var meta = metaComp[i];
|
||||
Assert.That(meta.VisibilityMask, Is.EqualTo(mask));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user