Compare commits

...

23 Commits

Author SHA1 Message Date
Pieter-Jan Briers
1904207358 Version: 162.2.2 2024-03-10 20:50:29 +01:00
Pieter-Jan Briers
939840ddab Backport 859f150404
(cherry picked from commit 24d5c26fa6)
(cherry picked from commit 688efac67b634c613539b783a9fb6e679948cd53)
2024-03-10 20:50:29 +01:00
DrSmugleaf
a6c295b89c Version: 162.2.1 2023-09-28 17:55:17 -07:00
DrSmugleaf
165913a4de Add IComparable to ProtoId, EntProtoId and LocId (#4460) 2023-09-28 17:53:23 -07:00
metalgearsloth
675dfdaabd Fix scroll containers invalidating on first scroll (#4449) 2023-09-28 16:58:18 -07:00
Kara
fab172d6f6 Allow force submitting line edits (#4455) 2023-09-28 11:07:55 -07:00
metalgearsloth
e75c1659f6 Version: 162.2.0 2023-09-28 20:25:52 +10:00
DrSmugleaf
0c440a8fc9 Localize "View Variables" (#4454) 2023-09-28 20:22:54 +10:00
DrSmugleaf
0c2c8f352a Add LocId and LocIdSerializer (#4456) 2023-09-28 20:22:17 +10:00
DrSmugleaf
0a4a2b7a36 Add nullable conversion operators to ProtoId and EntProtoId (#4447) 2023-09-28 20:18:45 +10:00
metalgearsloth
b5b59c1d2f Use CollectionsMarshal in clientgamestatemanager (#4453) 2023-09-28 20:13:38 +10:00
metalgearsloth
f4f0967fdc Fix double contact deletion throwing (#4450) 2023-09-28 20:12:45 +10:00
metalgearsloth
3d69766112 Use CollectionsMarshal for mergeimplicitdata (#4451) 2023-09-28 20:12:22 +10:00
DrSmugleaf
d1eb3438d5 Add support for automatically networking entity lists and sets (#4446) 2023-09-25 18:02:53 +10:00
ElectroJr
8f6b189d29 Version: 162.1.1 2023-09-19 17:59:16 -04:00
Leon Friedrich
ef8b278b47 Fix HideSpawnMenu (#4437) 2023-09-20 07:57:50 +10:00
metalgearsloth
c53ce2c907 Version: 162.1.0 2023-09-19 23:19:39 +10:00
Leon Friedrich
f063aa3ea1 Use CollectionsMarshal in RobustTree (#4429) 2023-09-19 23:17:27 +10:00
Leon Friedrich
30f63254ef Mark ProtoId<T> as serializable (#4430) 2023-09-19 23:13:14 +10:00
Leon Friedrich
30a5b6152c Slightly improve AddToChunkSetRecursively() (#4432) 2023-09-19 23:13:00 +10:00
Leon Friedrich
910a7f8bff Fix visibility layers not updating on children (#4431) 2023-09-19 23:12:52 +10:00
Leon Friedrich
526a88293e Use CollectionsMarshal in AddComponentInternal() (#4435) 2023-09-19 23:09:41 +10:00
metalgearsloth
22cd840b83 Revert "Add force ack threshold (#4423)" (#4436) 2023-09-19 18:36:41 +10:00
34 changed files with 532 additions and 177 deletions

View File

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

View File

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

View File

@@ -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]

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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" />

View File

@@ -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();

View File

@@ -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
};

View File

@@ -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..."};

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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));
}
}

View File

@@ -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)

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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>

View File

@@ -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.

View 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);
}
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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,
}
}

View File

@@ -189,8 +189,6 @@ namespace Robust.Shared.Physics.Systems
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase, physicsMap);
}
if (updates)
{
var resetMass = fixture.Density > 0f;

View File

@@ -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)
{

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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 />

View 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));
}
}
}