mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed6fff32c1 | ||
|
|
97af8db1aa | ||
|
|
1904207358 | ||
|
|
939840ddab | ||
|
|
a6c295b89c | ||
|
|
165913a4de | ||
|
|
675dfdaabd | ||
|
|
fab172d6f6 | ||
|
|
e75c1659f6 | ||
|
|
0c440a8fc9 | ||
|
|
0c2c8f352a | ||
|
|
0a4a2b7a36 | ||
|
|
b5b59c1d2f | ||
|
|
f4f0967fdc | ||
|
|
3d69766112 | ||
|
|
d1eb3438d5 | ||
|
|
8f6b189d29 | ||
|
|
ef8b278b47 | ||
|
|
c53ce2c907 | ||
|
|
f063aa3ea1 | ||
|
|
30f63254ef | ||
|
|
30a5b6152c | ||
|
|
910a7f8bff | ||
|
|
526a88293e | ||
|
|
22cd840b83 | ||
|
|
415c518bc7 | ||
|
|
2417dbb0e0 | ||
|
|
005673a957 | ||
|
|
942db3120c | ||
|
|
c0a5fab19e | ||
|
|
d16c62b132 | ||
|
|
7da22557fe | ||
|
|
92f47c0f20 | ||
|
|
9576d0739f | ||
|
|
10f25faabf | ||
|
|
74831a177e |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,6 +54,82 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 162.2.3
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
* Add entity categories for prototypes and deprecate the `noSpawn` tag.
|
||||
* Add missing proxy method for `TryGetEntityData`.
|
||||
* Add NetForceAckThreshold cvar to forcibly update acks for late clients.
|
||||
|
||||
### Internal
|
||||
|
||||
* Use CollectionMarshals in PVS and DynamicTree.
|
||||
* Make the proxy methods use MetaQuery / TransformQuery.
|
||||
|
||||
|
||||
## 161.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add more DebugTools assert variations.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Don't attempt to insert entities into deleted containers.
|
||||
* Try to fix oldestAck not being set correctly leading to deletion history getting bloated for pvs.
|
||||
|
||||
|
||||
## 161.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- type: entity
|
||||
id: debugRotation
|
||||
abstract: true
|
||||
suffix: DEBUG
|
||||
categories: [ debug ]
|
||||
components:
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
|
||||
17
Resources/EnginePrototypes/entityCategory.yml
Normal file
17
Resources/EnginePrototypes/entityCategory.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
# debug related entities
|
||||
- type: entityCategory
|
||||
id: debug
|
||||
name: entity-category-name-debug
|
||||
description: entity-category-desc-debug
|
||||
|
||||
# entities that spawn other entities
|
||||
- type: entityCategory
|
||||
id: spawner
|
||||
name: entity-category-name-spawner
|
||||
description: entity-category-desc-spawner
|
||||
|
||||
# entities that should be hidden from the spawn menu
|
||||
- type: entityCategory
|
||||
id: hideSpawnMenu
|
||||
name: entity-category-name-hide
|
||||
description: entity-category-desc-hide
|
||||
8
Resources/Locale/en-US/entity-category.ftl
Normal file
8
Resources/Locale/en-US/entity-category.ftl
Normal file
@@ -0,0 +1,8 @@
|
||||
entity-category-name-debug = Debug
|
||||
entity-category-desc-debug = Entity prototypes intended for debugging & testing.
|
||||
|
||||
entity-category-name-spawner = Spawner
|
||||
entity-category-desc-spawner = Entity prototypes that spawn other entities.
|
||||
|
||||
entity-category-name-hide = Hidden
|
||||
entity-category-desc-hide = Entity prototypes that should be hidden from the spawn menu
|
||||
@@ -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" />
|
||||
|
||||
@@ -10,6 +10,7 @@ using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
using static Robust.Client.UserInterface.Controls.LineEdit;
|
||||
@@ -199,7 +200,7 @@ public sealed class EntitySpawningUIController : UIController
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prototype.NoSpawn)
|
||||
if (prototype.HideSpawnMenu)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Server.Configuration;
|
||||
@@ -24,7 +25,7 @@ namespace Robust.Server.GameStates;
|
||||
internal sealed partial class PvsSystem : EntitySystem
|
||||
{
|
||||
[Shared.IoC.Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IMapManagerInternal _mapManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly INetworkedMapManager _mapManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IParallelManager _parallelManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IServerGameStateManager _serverGameStateManager = default!;
|
||||
@@ -227,6 +228,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
public void CullDeletionHistory(GameTick oldestAck)
|
||||
{
|
||||
_entityPvsCollection.CullDeletionHistoryUntil(oldestAck);
|
||||
_mapManager.CullDeletionHistory(oldestAck);
|
||||
}
|
||||
|
||||
#region PVSCollection methods to maybe make public someday:tm:
|
||||
@@ -648,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);
|
||||
|
||||
@@ -667,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));
|
||||
@@ -677,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)
|
||||
@@ -900,15 +898,22 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
// As every map is parented to uid 0 in the tree we still need to get their children, plus because we go top-down
|
||||
// we may find duplicate parents with children we haven't encountered before
|
||||
// on different chunks (this is especially common with direct grid children)
|
||||
if (!toSend.ContainsKey(currentNodeIndex))
|
||||
|
||||
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(toSend, currentNodeIndex, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
var (entered, shouldAdd) = ProcessEntry(in currentNodeIndex, lastAcked, lastSent, lastSeen,
|
||||
ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
|
||||
if (!shouldAdd)
|
||||
{
|
||||
// In the majority of instances entities do get added.
|
||||
// So its better to add and maybe remove, rather than checking ContainsKey() and then maybe adding it.
|
||||
toSend.Remove(currentNodeIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
AddToSendSet(in currentNodeIndex, metaDataCache[currentNodeIndex], toSend, fromTick, in entered, ref entStateCount);
|
||||
AddToSendSet(in currentNodeIndex, metaDataCache[currentNodeIndex], ref value, toSend, fromTick, in entered, ref entStateCount);
|
||||
}
|
||||
|
||||
var node = tree[currentNodeIndex];
|
||||
@@ -957,14 +962,15 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
// to the toSend set, it doesn't guarantee that its parents have been. E.g., if a player ghost just teleported
|
||||
// to follow a far away entity, the player's own entity is still being sent, but we need to ensure that we also
|
||||
// send the new parents, which may otherwise be delayed because of the PVS budget..
|
||||
if (!toSend.ContainsKey(netEntity))
|
||||
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(toSend, netEntity, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
// TODO PERFORMANCE.
|
||||
// ProcessEntry() unnecessarily checks lastSent.ContainsKey() and maybe lastSeen.Contains(). Given that at this
|
||||
// point the budgets are just ignored, this should just bypass those checks. But then again 99% of the time this
|
||||
// is just the player's own entity + maybe a singularity. So currently not all that performance intensive.
|
||||
var (entered, _) = ProcessEntry(in netEntity, lastAcked, lastSent, lastSeen, ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
AddToSendSet(in netEntity, metadata, toSend, fromTick, in entered, ref entStateCount);
|
||||
AddToSendSet(in netEntity, metadata, ref value, toSend, fromTick, in entered, ref entStateCount);
|
||||
}
|
||||
|
||||
if (addChildren)
|
||||
@@ -996,12 +1002,13 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
var metadata = _metaQuery.GetComponent(child);
|
||||
var childNetEntity = metadata.NetEntity;
|
||||
|
||||
if (!toSend.ContainsKey(childNetEntity))
|
||||
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(toSend, childNetEntity, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
var (entered, _) = ProcessEntry(in childNetEntity, lastAcked, lastSent, lastSeen, ref newEntityCount,
|
||||
ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
|
||||
AddToSendSet(in childNetEntity, metadata, toSend, fromTick, in entered, ref entStateCount);
|
||||
AddToSendSet(in childNetEntity, metadata, ref value, toSend, fromTick, in entered, ref entStateCount);
|
||||
}
|
||||
|
||||
RecursivelyAddChildren(childXform, lastAcked, lastSent, toSend, lastSeen, fromTick, ref newEntityCount,
|
||||
@@ -1044,10 +1051,13 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
return (entered, true);
|
||||
}
|
||||
|
||||
private void AddToSendSet(in NetEntity netEntity, MetaDataComponent metaDataComponent, Dictionary<NetEntity, PvsEntityVisibility> toSend, GameTick fromTick, in bool entered, ref int entStateCount)
|
||||
private void AddToSendSet(in NetEntity netEntity, MetaDataComponent metaDataComponent,
|
||||
ref PvsEntityVisibility vis, Dictionary<NetEntity, PvsEntityVisibility> toSend,
|
||||
GameTick fromTick, in bool entered, ref int entStateCount)
|
||||
{
|
||||
if (metaDataComponent.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
toSend.Remove(netEntity);
|
||||
var rep = new EntityStringRepresentation(GetEntity(netEntity), metaDataComponent.EntityDeleted, metaDataComponent.EntityName, metaDataComponent.EntityPrototype?.ID);
|
||||
Log.Error($"Attempted to add a deleted entity to PVS send set: '{rep}'. Trace:\n{Environment.StackTrace}");
|
||||
return;
|
||||
@@ -1055,7 +1065,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
if (entered)
|
||||
{
|
||||
toSend.Add(netEntity, PvsEntityVisibility.Entered);
|
||||
vis = PvsEntityVisibility.Entered;
|
||||
entStateCount++;
|
||||
return;
|
||||
}
|
||||
@@ -1063,12 +1073,12 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
if (metaDataComponent.EntityLastModifiedTick <= fromTick)
|
||||
{
|
||||
//entity has been sent before and hasnt been updated since
|
||||
toSend.Add(netEntity, PvsEntityVisibility.StayedUnchanged);
|
||||
vis = PvsEntityVisibility.StayedUnchanged;
|
||||
return;
|
||||
}
|
||||
|
||||
//add us
|
||||
toSend.Add(netEntity, PvsEntityVisibility.StayedChanged);
|
||||
vis = PvsEntityVisibility.StayedChanged;
|
||||
entStateCount++;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -99,10 +99,10 @@ namespace Robust.Server.GameStates
|
||||
.Select(x => $"{x.Name} ({_entityManager.ToPrettyString(x.AttachedEntity)})");
|
||||
|
||||
shell.WriteLine($@"Current tick: {_gameTiming.CurTick}
|
||||
Last Acked tick: {_lastOldestAck}
|
||||
Deletion history size: {_pvs.EntityPVSCollection.GetDeletedIndices(_lastOldestAck)?.Count ?? 0}
|
||||
Actual last acked tick: {ack}
|
||||
Last ack players: {string.Join(", ", players)}
|
||||
Stored oldest acked tick: {_lastOldestAck}
|
||||
Deletion history size: {_pvs.EntityPVSCollection.GetDeletedIndices(GameTick.First)?.Count ?? 0}
|
||||
Actual oldest: {ack}
|
||||
Oldest acked clients: {string.Join(", ", players)}
|
||||
");
|
||||
}
|
||||
|
||||
@@ -168,15 +168,6 @@ Last ack players: {string.Join(", ", players)}
|
||||
/// <inheritdoc />
|
||||
public void SendGameStateUpdate()
|
||||
{
|
||||
if (!_networkManager.IsConnected)
|
||||
{
|
||||
// Prevent deletions piling up if we have no clients.
|
||||
_pvs.CullDeletionHistory(GameTick.MaxValue);
|
||||
_mapManager.CullDeletionHistory(GameTick.MaxValue);
|
||||
_pvs.CleanupDirty(Enumerable.Empty<IPlayerSession>());
|
||||
return;
|
||||
}
|
||||
|
||||
var players = _playerManager.ServerSessions.Where(o => o.Status == SessionStatus.InGame).ToArray();
|
||||
|
||||
// Update entity positions in PVS chunks/collections
|
||||
@@ -216,14 +207,21 @@ Last ack players: {string.Join(", ", players)}
|
||||
_pvs.CleanupDirty(players);
|
||||
}
|
||||
|
||||
// keep the deletion history buffers clean
|
||||
if (oldestAck > _lastOldestAck)
|
||||
if (oldestAck == GameTick.MaxValue)
|
||||
{
|
||||
using var _ = _usageHistogram.WithLabels("Cull History").NewTimer();
|
||||
_lastOldestAck = oldestAck;
|
||||
_pvs.CullDeletionHistory(oldestAck);
|
||||
_mapManager.CullDeletionHistory(oldestAck);
|
||||
// There were no connected players?
|
||||
// In that case we just clear all deletion history.
|
||||
_pvs.CullDeletionHistory(GameTick.MaxValue);
|
||||
_lastOldestAck = GameTick.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldestAck == _lastOldestAck)
|
||||
return;
|
||||
|
||||
_lastOldestAck = oldestAck;
|
||||
using var __ = _usageHistogram.WithLabels("Cull History").NewTimer();
|
||||
_pvs.CullDeletionHistory(oldestAck);
|
||||
}
|
||||
|
||||
private GameTick SendStates(IPlayerSession[] players, PvsData? pvsData)
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace Robust.Server.Prototypes
|
||||
|
||||
public override void LoadDefaultPrototypes(Dictionary<Type, HashSet<string>>? changed = null)
|
||||
{
|
||||
LoadDirectory(new("/EnginePrototypes/"), changed: changed);
|
||||
LoadDirectory(_server.Options.PrototypeDirectory, changed: changed);
|
||||
ResolveResults();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -35,9 +36,15 @@ namespace Robust.Shared.Maths
|
||||
/// </summary>
|
||||
[FieldOffset(sizeof(float) * 3)] public float Top;
|
||||
|
||||
[NonSerialized]
|
||||
[FieldOffset(sizeof(float) * 0)] public Vector2 BottomLeft;
|
||||
|
||||
[NonSerialized]
|
||||
[FieldOffset(sizeof(float) * 2)] public Vector2 TopRight;
|
||||
|
||||
[NonSerialized]
|
||||
[FieldOffset(sizeof(float) * 0)] public System.Numerics.Vector4 AsVector4;
|
||||
|
||||
public readonly Vector2 BottomRight
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -141,6 +148,11 @@ namespace Robust.Shared.Maths
|
||||
return new Box2(min, max);
|
||||
}
|
||||
|
||||
public readonly bool HasNan()
|
||||
{
|
||||
return Vector128.EqualsAny(AsVector4.AsVector128(), Vector128.Create(float.NaN));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public readonly bool Intersects(in Box2 other)
|
||||
|
||||
@@ -13,6 +13,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.Containers
|
||||
@@ -111,6 +112,7 @@ namespace Robust.Shared.Containers
|
||||
DebugTools.Assert(ownerTransform == null || ownerTransform.Owner == Owner);
|
||||
DebugTools.Assert(physics == null || physics.Owner == toinsert);
|
||||
DebugTools.Assert(!ExpectedEntities.Contains(entMan.GetNetEntity(toinsert)));
|
||||
DebugTools.Assert(Manager.Containers.ContainsKey(ID));
|
||||
|
||||
var physicsQuery = entMan.GetEntityQuery<PhysicsComponent>();
|
||||
var transformQuery = entMan.GetEntityQuery<TransformComponent>();
|
||||
@@ -121,6 +123,26 @@ namespace Robust.Shared.Containers
|
||||
var jointSys = entMan.EntitySysManager.GetEntitySystem<SharedJointSystem>();
|
||||
var containerSys = entMan.EntitySysManager.GetEntitySystem<SharedContainerSystem>();
|
||||
|
||||
// If someone is attempting to insert an entity into a container that is getting deleted, then we will
|
||||
// automatically delete that entity. I.e., the insertion automatically "succeeds" and both entities get deleted.
|
||||
// This is consistent with what happens if you attempt to attach an entity to a terminating parent.
|
||||
|
||||
if (!entMan.TryGetComponent(Owner, out MetaDataComponent? ownerMeta))
|
||||
{
|
||||
Logger.ErrorS("container",
|
||||
$"Attempted to insert an entity {entMan.ToPrettyString(toinsert)} into a non-existent entity.");
|
||||
entMan.QueueDeleteEntity(toinsert);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ownerMeta.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
Logger.ErrorS("container",
|
||||
$"Attempted to insert an entity {entMan.ToPrettyString(toinsert)} into an entity that is terminating. Entity: {entMan.ToPrettyString(Owner)}.");
|
||||
entMan.QueueDeleteEntity(toinsert);
|
||||
return false;
|
||||
}
|
||||
|
||||
//Verify we can insert into this container
|
||||
if (!force && !containerSys.CanInsert(toinsert, this))
|
||||
return false;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -95,7 +95,7 @@ public partial class EntityManager
|
||||
ComponentRegistry? overrides = null)
|
||||
{
|
||||
uid = null;
|
||||
if (!_xformQuery.Resolve(target, ref xform))
|
||||
if (!TransformQuery.Resolve(target, ref xform))
|
||||
return false;
|
||||
|
||||
if (!xform.ParentUid.IsValid())
|
||||
@@ -157,7 +157,7 @@ public partial class EntityManager
|
||||
|
||||
public EntityUid SpawnNextToOrDrop(string? protoName, EntityUid target, TransformComponent? xform = null, ComponentRegistry? overrides = null)
|
||||
{
|
||||
xform ??= _xformQuery.GetComponent(target);
|
||||
xform ??= TransformQuery.GetComponent(target);
|
||||
if (!xform.ParentUid.IsValid())
|
||||
return Spawn(protoName);
|
||||
|
||||
@@ -181,7 +181,7 @@ public partial class EntityManager
|
||||
|| !container.Insert(uid, this))
|
||||
{
|
||||
|
||||
xform ??= _xformQuery.GetComponent(containerUid);
|
||||
xform ??= TransformQuery.GetComponent(containerUid);
|
||||
if (xform.ParentUid.IsValid())
|
||||
_xforms.PlaceNextToOrDrop(uid, containerUid, targetXform: xform);
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ namespace Robust.Shared.GameObjects
|
||||
// positions on spawn....
|
||||
private SharedTransformSystem _xforms = default!;
|
||||
|
||||
protected EntityQuery<MetaDataComponent> MetaQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
public EntityQuery<MetaDataComponent> MetaQuery;
|
||||
public EntityQuery<TransformComponent> TransformQuery;
|
||||
|
||||
#endregion Dependencies
|
||||
|
||||
@@ -241,7 +241,7 @@ namespace Robust.Shared.GameObjects
|
||||
_mapSystem = System<SharedMapSystem>();
|
||||
_xforms = System<SharedTransformSystem>();
|
||||
MetaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
TransformQuery = GetEntityQuery<TransformComponent>();
|
||||
}
|
||||
|
||||
public virtual void Shutdown()
|
||||
@@ -324,7 +324,7 @@ namespace Robust.Shared.GameObjects
|
||||
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
|
||||
{
|
||||
var newEntity = CreateEntity(prototypeName, overrides);
|
||||
_xforms.SetCoordinates(newEntity, _xformQuery.GetComponent(newEntity), coordinates, unanchor: false);
|
||||
_xforms.SetCoordinates(newEntity, TransformQuery.GetComponent(newEntity), coordinates, unanchor: false);
|
||||
return newEntity;
|
||||
}
|
||||
|
||||
@@ -332,7 +332,7 @@ namespace Robust.Shared.GameObjects
|
||||
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
|
||||
{
|
||||
var newEntity = CreateEntity(prototypeName, overrides);
|
||||
var transform = _xformQuery.GetComponent(newEntity);
|
||||
var transform = TransformQuery.GetComponent(newEntity);
|
||||
|
||||
if (coordinates.MapId == MapId.Nullspace)
|
||||
{
|
||||
@@ -371,18 +371,8 @@ namespace Robust.Shared.GameObjects
|
||||
public virtual void DirtyEntity(EntityUid uid, MetaDataComponent? metadata = null)
|
||||
{
|
||||
// We want to retrieve MetaDataComponent even if its Deleted flag is set.
|
||||
if (metadata == null)
|
||||
{
|
||||
if (!_entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].TryGetValue(uid, out var component))
|
||||
throw new KeyNotFoundException($"Entity {uid} does not exist, cannot dirty it.");
|
||||
metadata = (MetaDataComponent)component;
|
||||
}
|
||||
else
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
DebugTools.Assert(metadata.Owner == uid);
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
if (!MetaQuery.ResolveInternal(uid, ref metadata))
|
||||
return;
|
||||
|
||||
if (metadata.EntityLastModifiedTick == _gameTiming.CurTick)
|
||||
return;
|
||||
@@ -453,7 +443,7 @@ namespace Robust.Shared.GameObjects
|
||||
EntityUid uid,
|
||||
MetaDataComponent metadata)
|
||||
{
|
||||
var transform = _xformQuery.GetComponent(uid);
|
||||
var transform = TransformQuery.GetComponent(uid);
|
||||
metadata.EntityLifeStage = EntityLifeStage.Terminating;
|
||||
|
||||
try
|
||||
@@ -485,7 +475,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
// Note about this method: #if EXCEPTION_TOLERANCE is not used here because we're gonna it in the future...
|
||||
var netEntity = GetNetEntity(uid, metadata);
|
||||
var transform = _xformQuery.GetComponent(uid);
|
||||
var transform = TransformQuery.GetComponent(uid);
|
||||
|
||||
// Detach the base entity to null before iterating over children
|
||||
// This also ensures that the entity-lookup updates don't have to be re-run for every child (which recurses up the transform hierarchy).
|
||||
@@ -567,7 +557,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public bool EntityExists(EntityUid uid)
|
||||
{
|
||||
return _entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].ContainsKey(uid);
|
||||
return MetaQuery.HasComponentInternal(uid);
|
||||
}
|
||||
|
||||
public bool EntityExists(EntityUid? uid)
|
||||
@@ -586,12 +576,12 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public bool Deleted(EntityUid uid)
|
||||
{
|
||||
return !_entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].TryGetValue(uid, out var comp) || ((MetaDataComponent) comp).EntityDeleted;
|
||||
return !MetaQuery.TryGetComponentInternal(uid, out var comp) || comp.EntityDeleted;
|
||||
}
|
||||
|
||||
public bool Deleted([NotNullWhen(false)] EntityUid? uid)
|
||||
{
|
||||
return !uid.HasValue || !_entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].TryGetValue(uid.Value, out var comp) || ((MetaDataComponent) comp).EntityDeleted;
|
||||
return !uid.HasValue || !MetaQuery.TryGetComponentInternal(uid.Value, out var comp) || comp.EntityDeleted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -716,7 +706,7 @@ namespace Robust.Shared.GameObjects
|
||||
StartEntity(entity);
|
||||
|
||||
// If the map we're initializing the entity on is initialized, run map init on it.
|
||||
if (_mapManager.IsMapInitialized(mapId ?? _xformQuery.GetComponent(entity).MapID))
|
||||
if (_mapManager.IsMapInitialized(mapId ?? TransformQuery.GetComponent(entity).MapID))
|
||||
RunMapInit(entity, meta);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -61,7 +61,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Deleted(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
return true;
|
||||
|
||||
return metaData.EntityDeleted;
|
||||
@@ -73,7 +73,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TerminatingOrDeleted(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
return true;
|
||||
|
||||
return metaData.EntityLifeStage >= EntityLifeStage.Terminating;
|
||||
@@ -104,7 +104,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected EntityLifeStage LifeStage(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
throw CompNotFound<MetaDataComponent>(uid);
|
||||
|
||||
return metaData.EntityLifeStage;
|
||||
@@ -169,7 +169,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryLifeStage(EntityUid uid, [NotNullWhen(true)] out EntityLifeStage? lifeStage, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
{
|
||||
lifeStage = null;
|
||||
return false;
|
||||
@@ -227,7 +227,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected string Name(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if(!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
throw CompNotFound<MetaDataComponent>(uid);
|
||||
|
||||
return metaData.EntityName;
|
||||
@@ -240,7 +240,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected string Description(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if(!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
throw CompNotFound<MetaDataComponent>(uid);
|
||||
|
||||
return metaData.EntityDescription;
|
||||
@@ -253,7 +253,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected EntityPrototype? Prototype(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
throw CompNotFound<MetaDataComponent>(uid);
|
||||
|
||||
return metaData.EntityPrototype;
|
||||
@@ -265,7 +265,7 @@ public partial class EntitySystem
|
||||
/// <exception cref="KeyNotFoundException">Thrown when the entity doesn't exist.</exception>
|
||||
protected GameTick LastModifiedTick(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
throw CompNotFound<MetaDataComponent>(uid);
|
||||
|
||||
return metaData.EntityLastModifiedTick;
|
||||
@@ -278,7 +278,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Paused(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
throw CompNotFound<MetaDataComponent>(uid);
|
||||
|
||||
return metaData.EntityPaused;
|
||||
@@ -291,7 +291,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void SetPaused(EntityUid uid, bool paused, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
throw CompNotFound<MetaDataComponent>(uid);
|
||||
|
||||
EntityManager.EntitySysManager.GetEntitySystem<MetaDataSystem>().SetEntityPaused(uid, paused, metaData);
|
||||
@@ -302,12 +302,12 @@ public partial class EntitySystem
|
||||
/// </summary>
|
||||
/// <returns>Whether the operation succeeded.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryDirty(EntityUid uid)
|
||||
protected bool TryDirty(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Exists(uid))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
return false;
|
||||
|
||||
DirtyEntity(uid);
|
||||
DirtyEntity(uid, metaData);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryName(EntityUid uid, [NotNullWhen(true)] out string? name, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
{
|
||||
name = null;
|
||||
return false;
|
||||
@@ -335,7 +335,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryDescription(EntityUid uid, [NotNullWhen(true)] out string? description, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
{
|
||||
description = null;
|
||||
return false;
|
||||
@@ -352,7 +352,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryPrototype(EntityUid uid, [NotNullWhen(true)] out EntityPrototype? prototype, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
{
|
||||
prototype = null;
|
||||
return false;
|
||||
@@ -369,7 +369,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryLastModifiedTick(EntityUid uid, [NotNullWhen(true)] out GameTick? lastModifiedTick, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
{
|
||||
lastModifiedTick = null;
|
||||
return false;
|
||||
@@ -386,7 +386,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryPaused(EntityUid uid, [NotNullWhen(true)] out bool? paused, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
{
|
||||
paused = null;
|
||||
return false;
|
||||
@@ -458,6 +458,20 @@ public partial class EntitySystem
|
||||
return EntityManager.TryGetComponent(uid, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryGetComponent<T>(EntityUid, out T)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryComp(EntityUid uid, [NotNullWhen(true)] out TransformComponent? comp)
|
||||
{
|
||||
return EntityManager.TransformQuery.TryGetComponent(uid, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryGetComponent<T>(EntityUid, out T)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryComp(EntityUid uid, [NotNullWhen(true)] out MetaDataComponent? comp)
|
||||
{
|
||||
return EntityManager.MetaQuery.TryGetComponent(uid, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryGetComponent<T>(EntityUid?, out T)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryComp<T>([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out T? comp)
|
||||
@@ -471,6 +485,30 @@ public partial class EntitySystem
|
||||
return EntityManager.TryGetComponent(uid.Value, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryGetComponent<T>(EntityUid?, out T)"/>
|
||||
protected bool TryComp([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TransformComponent? comp)
|
||||
{
|
||||
if (!uid.HasValue)
|
||||
{
|
||||
comp = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
return EntityManager.TransformQuery.TryGetComponent(uid.Value, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryGetComponent<T>(EntityUid?, out T)"/>
|
||||
protected bool TryComp([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out MetaDataComponent? comp)
|
||||
{
|
||||
if (!uid.HasValue)
|
||||
{
|
||||
comp = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
return EntityManager.MetaQuery.TryGetComponent(uid.Value, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.GetComponents"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected IEnumerable<IComponent> AllComps(EntityUid uid)
|
||||
@@ -492,7 +530,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected TransformComponent Transform(EntityUid uid)
|
||||
{
|
||||
return EntityManager.GetComponent<TransformComponent>(uid);
|
||||
return EntityManager.TransformQuery.GetComponent(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -502,7 +540,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected MetaDataComponent MetaData(EntityUid uid)
|
||||
{
|
||||
return EntityManager.GetComponent<MetaDataComponent>(uid);
|
||||
return EntityManager.MetaQuery.GetComponent(uid);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -511,6 +549,13 @@ public partial class EntitySystem
|
||||
return EntityManager.GetEntityData(nuid);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryGetEntityData(NetEntity nuid, [NotNullWhen(true)] out EntityUid? uid,
|
||||
[NotNullWhen(true)] out MetaDataComponent? meta)
|
||||
{
|
||||
return EntityManager.TryGetEntityData(nuid, out uid, out meta);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Component Has
|
||||
|
||||
@@ -32,6 +32,22 @@ namespace Robust.Shared.GameObjects
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Resolve{TComp}"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref MetaDataComponent? component,
|
||||
bool logMissing = true)
|
||||
{
|
||||
return EntityManager.MetaQuery.Resolve(uid, ref component);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Resolve{TComp}"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TransformComponent? component,
|
||||
bool logMissing = true)
|
||||
{
|
||||
return EntityManager.TransformQuery.Resolve(uid, ref component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the components on the entity for the null component references.
|
||||
/// </summary>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -495,7 +495,7 @@ public abstract partial class SharedTransformSystem
|
||||
throw new InvalidOperationException($"Attempted to parent entity {ToPrettyString(uid)} to non-existent entity {value.EntityId}");
|
||||
}
|
||||
|
||||
if (newParent.LifeStage > ComponentLifeStage.Running || LifeStage(value.EntityId) > EntityLifeStage.MapInitialized)
|
||||
if (newParent.LifeStage >= ComponentLifeStage.Stopping || LifeStage(value.EntityId) >= EntityLifeStage.Terminating)
|
||||
{
|
||||
DetachParentToNull(uid, xform);
|
||||
if (_netMan.IsServer || IsClientSide(uid))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -24,6 +24,8 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -131,7 +133,7 @@ namespace Robust.Shared.Physics
|
||||
|
||||
aabb ??= _extractAabb(item);
|
||||
|
||||
if (HasNaNs(aabb.Value))
|
||||
if (aabb.Value.HasNan())
|
||||
{
|
||||
_nodeLookup[item] = DynamicTree.Proxy.Free;
|
||||
return true;
|
||||
@@ -179,28 +181,25 @@ namespace Robust.Shared.Physics
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public bool Update(in T item, Box2? newBox = null)
|
||||
{
|
||||
if (!TryGetProxy(item, out var proxy))
|
||||
{
|
||||
ref var proxy = ref CollectionsMarshal.GetValueRefOrNullRef(_nodeLookup, item);
|
||||
if (Unsafe.IsNullRef(ref proxy))
|
||||
return false;
|
||||
}
|
||||
|
||||
newBox ??= _extractAabb(item);
|
||||
|
||||
if (HasNaNs(newBox.Value))
|
||||
if (newBox.Value.HasNan())
|
||||
{
|
||||
if (proxy == DynamicTree.Proxy.Free)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_b2Tree.DestroyProxy(proxy);
|
||||
_nodeLookup[item] = DynamicTree.Proxy.Free;
|
||||
proxy = DynamicTree.Proxy.Free;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (proxy == DynamicTree.Proxy.Free)
|
||||
{
|
||||
_nodeLookup[item] = _b2Tree.CreateProxy(newBox.Value, item);
|
||||
proxy = _b2Tree.CreateProxy(newBox.Value, item);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -331,36 +330,30 @@ namespace Robust.Shared.Physics
|
||||
public void AddOrUpdate(T item, Box2? aabb = null)
|
||||
{
|
||||
aabb ??= _extractAabb(item);
|
||||
if (!_nodeLookup.TryGetValue(item, out var proxy))
|
||||
|
||||
ref var proxy = ref CollectionsMarshal.GetValueRefOrAddDefault(_nodeLookup, item, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
_nodeLookup[item] = HasNaNs(aabb.Value) ? DynamicTree.Proxy.Free : _b2Tree.CreateProxy(aabb.Value, item);
|
||||
proxy = aabb.Value.HasNan() ? DynamicTree.Proxy.Free : _b2Tree.CreateProxy(aabb.Value, item);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasNaNs(aabb.Value))
|
||||
if (aabb.Value.HasNan())
|
||||
{
|
||||
if (proxy == DynamicTree.Proxy.Free)
|
||||
return;
|
||||
|
||||
_b2Tree.DestroyProxy(proxy);
|
||||
_nodeLookup[item] = DynamicTree.Proxy.Free;
|
||||
proxy = DynamicTree.Proxy.Free;
|
||||
return;
|
||||
}
|
||||
|
||||
if (proxy == DynamicTree.Proxy.Free)
|
||||
_nodeLookup[item] = _b2Tree.CreateProxy(aabb.Value, item);
|
||||
proxy = _b2Tree.CreateProxy(aabb.Value, item);
|
||||
else
|
||||
_b2Tree.MoveProxy(proxy, aabb.Value, Vector2.Zero);
|
||||
}
|
||||
|
||||
private static bool HasNaNs(in Box2 box)
|
||||
{
|
||||
return float.IsNaN(box.Left)
|
||||
|| float.IsNaN(box.Top)
|
||||
|| float.IsNaN(box.Bottom)
|
||||
|| float.IsNaN(box.Right);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG_DYNAMIC_TREE")]
|
||||
[Conditional("DEBUG_DYNAMIC_TREE_ASSERTS")]
|
||||
[DebuggerNonUserCode]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
25
Robust.Shared/Prototypes/EntityCategoryPrototype.cs
Normal file
25
Robust.Shared/Prototypes/EntityCategoryPrototype.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.Prototypes;
|
||||
|
||||
/// <summary>
|
||||
/// Prototype that represents game entities.
|
||||
/// </summary>
|
||||
[Prototype("entityCategory")]
|
||||
public sealed class EntityCategoryPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Localized name of the category, for use in entity spawn menus.
|
||||
/// </summary>
|
||||
[DataField("name")]
|
||||
public string? Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Localized description of the category, for use in entity spawn menus.
|
||||
/// </summary>
|
||||
[DataField("description")]
|
||||
public string? Description { get; private set; }
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -12,6 +11,7 @@ using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Prototypes
|
||||
@@ -27,6 +27,9 @@ namespace Robust.Shared.Prototypes
|
||||
|
||||
private static readonly Dictionary<string, string> LocPropertiesDefault = new();
|
||||
|
||||
[ValidatePrototypeId<EntityCategoryPrototype>]
|
||||
private const string HideCategory = "hideSpawnMenu";
|
||||
|
||||
// LOCALIZATION NOTE:
|
||||
// Localization-related properties in here are manually localized in LocalizationManager.
|
||||
// As such, they should NOT be inherited to avoid confusing the system.
|
||||
@@ -57,6 +60,10 @@ namespace Robust.Shared.Prototypes
|
||||
[DataField("suffix")]
|
||||
public string? SetSuffix { get; private set; }
|
||||
|
||||
[DataField("categories")]
|
||||
[AlwaysPushInheritance]
|
||||
public HashSet<string> Categories = new();
|
||||
|
||||
[ViewVariables]
|
||||
public IReadOnlyDictionary<string, string> LocProperties => _locPropertiesSet ?? LocPropertiesDefault;
|
||||
|
||||
@@ -92,8 +99,11 @@ namespace Robust.Shared.Prototypes
|
||||
[ViewVariables]
|
||||
[NeverPushInheritance]
|
||||
[DataField("noSpawn")]
|
||||
[Obsolete("Use the HideSpawnMenu")]
|
||||
public bool NoSpawn { get; private set; }
|
||||
|
||||
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>
|
||||
|
||||
@@ -112,14 +112,40 @@ public partial class SerializationManager
|
||||
}, (node, this));
|
||||
}
|
||||
|
||||
private SequenceDataNode PushInheritanceSequence(SequenceDataNode child, SequenceDataNode _)
|
||||
private SequenceDataNode PushInheritanceSequence(SequenceDataNode child, SequenceDataNode parent)
|
||||
{
|
||||
return child; //todo implement different inheritancebehaviours for yamlfield
|
||||
//todo implement different inheritancebehaviours for yamlfield
|
||||
// I have NFI what this comment means.
|
||||
|
||||
var result = new SequenceDataNode(child.Count + parent.Count);
|
||||
foreach (var entry in parent)
|
||||
{
|
||||
result.Add(entry);
|
||||
}
|
||||
foreach (var entry in child)
|
||||
{
|
||||
result.Add(entry);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private MappingDataNode PushInheritanceMapping(MappingDataNode child, MappingDataNode _)
|
||||
private MappingDataNode PushInheritanceMapping(MappingDataNode child, MappingDataNode parent)
|
||||
{
|
||||
return child; //todo implement different inheritancebehaviours for yamlfield
|
||||
//todo implement different inheritancebehaviours for yamlfield
|
||||
// I have NFI what this comment means.
|
||||
|
||||
var result = new MappingDataNode(child.Count + parent.Count);
|
||||
foreach (var (k, v) in parent)
|
||||
{
|
||||
result[k] = v;
|
||||
}
|
||||
foreach (var (k, v) in child)
|
||||
{
|
||||
result[k] = v;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private MappingDataNode PushInheritanceDefinition(MappingDataNode child, MappingDataNode parent,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Utility
|
||||
{
|
||||
@@ -36,6 +38,67 @@ namespace Robust.Shared.Utility
|
||||
throw new DebugAssertException();
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[AssertionMethod]
|
||||
public static void AssertEqual(object? objA, object? objB)
|
||||
{
|
||||
if (ReferenceEquals(objA, objB))
|
||||
return;
|
||||
|
||||
if (objA == null || !objA.Equals(objB))
|
||||
throw new DebugAssertException($"Expected: {objB ?? "null"} but was {objA ?? "null"}");
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[AssertionMethod]
|
||||
public static void AssertEqual(object? objA, object? objB, string message)
|
||||
{
|
||||
if (ReferenceEquals(objA, objB))
|
||||
return;
|
||||
|
||||
if (objA == null || !objA.Equals(objB))
|
||||
throw new DebugAssertException($"{message}\nExpected: {objB ?? "null"} but was {objA ?? "null"}");
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[AssertionMethod]
|
||||
public static void AssertNotEqual(object? objA, object? objB)
|
||||
{
|
||||
if (ReferenceEquals(objA, objB))
|
||||
throw new DebugAssertException($"Expected: not {objB ?? "null"}");
|
||||
|
||||
if (objA == null || !objA.Equals(objB))
|
||||
return;
|
||||
|
||||
throw new DebugAssertException($"Expected: not {objB}");
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[AssertionMethod]
|
||||
public static void AssertNotEqual(object? objA, object? objB, string message)
|
||||
{
|
||||
if (ReferenceEquals(objA, objB))
|
||||
throw new DebugAssertException($"{message}\nExpected: not {objB ?? "null"}");
|
||||
|
||||
if (objA == null || !objA.Equals(objB))
|
||||
return;
|
||||
|
||||
throw new DebugAssertException($"{message}\nExpected: not {objB}");
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[AssertionMethod]
|
||||
public static void AssertOwner(EntityUid? uid, IComponent component)
|
||||
{
|
||||
if (uid == null)
|
||||
throw new DebugAssertException($"Null entity uid cannot own a component. Component: {component.GetType().Name}");
|
||||
|
||||
// Whenever .owner is removed this will need to be replaced by something.
|
||||
// We need some way to ensure that people don't mix up uids & components when calling methods.
|
||||
if (component.Owner != uid)
|
||||
throw new DebugAssertException($"Entity {uid} is not the owner of the component. Component: {component.GetType().Name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An assertion that will <see langword="throw" /> an exception if the
|
||||
/// <paramref name="condition" /> is not true.
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -223,13 +223,14 @@ namespace Robust.UnitTesting.Server
|
||||
container.RegisterInstance<IConsoleHost>(new Mock<IConsoleHost>().Object); //Console is technically a frontend, we want to run headless
|
||||
container.Register<IEntityManager, EntityManager>();
|
||||
container.Register<EntityManager, EntityManager>();
|
||||
container.Register<IMapManager, MapManager>();
|
||||
container.Register<IMapManager, NetworkedMapManager>();
|
||||
container.Register<INetworkedMapManager, NetworkedMapManager>();
|
||||
container.Register<IMapManagerInternal, NetworkedMapManager>();
|
||||
container.Register<ISerializationManager, SerializationManager>();
|
||||
container.Register<IPrototypeManager, ServerPrototypeManager>();
|
||||
container.Register<IComponentFactory, ComponentFactory>();
|
||||
container.Register<IEntitySystemManager, EntitySystemManager>();
|
||||
container.Register<IManifoldManager, CollisionManager>();
|
||||
container.Register<IMapManagerInternal, MapManager>();
|
||||
container.Register<INetManager, NetManager>();
|
||||
container.Register<IAuthManager, AuthManager>();
|
||||
container.Register<ITileDefinitionManager, TileDefinitionManager>();
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
6
global.json
Normal file
6
global.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "7.0.100",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user