Compare commits

...

58 Commits

Author SHA1 Message Date
Pieter-Jan Briers
10ab707427 Version: 167.0.2 2024-03-10 21:27:10 +01:00
Pieter-Jan Briers
f061a8c018 global.json force .NET 7
(cherry picked from commit 6211cf2e03)
2024-03-10 21:27:00 +01:00
Pieter-Jan Briers
91d4b32c66 Version: 167.0.1 2024-03-10 20:50:30 +01:00
Pieter-Jan Briers
e788f03a28 Backport 859f150404
(cherry picked from commit 24d5c26fa6)
(cherry picked from commit 688efac67b634c613539b783a9fb6e679948cd53)
2024-03-10 20:50:30 +01:00
metalgearsloth
77654a1628 Version: 167.0.0 2023-10-17 23:51:47 +11:00
Leon Friedrich
f3af813b57 Transform interpolation fixes (#4488) 2023-10-17 23:47:45 +11:00
Leon Friedrich
0623baedcf Fix PVS bug and add new test (#4444)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2023-10-17 23:33:42 +11:00
Leon Friedrich
2ade6c04c5 Adds more joint debug asserts (#4490) 2023-10-15 11:04:27 +11:00
Kara
a9df9097c1 Kill ContainerHelpers (#4486) 2023-10-15 03:58:25 +11:00
Kara
755dac719f Kill ComponentExt (#4485) 2023-10-15 03:58:04 +11:00
ElectroJr
7095a58685 Version: 166.0.0 2023-10-10 22:23:51 -04:00
Leon Friedrich
16e68a4351 Make PVS session overrides recursive (#4480) 2023-10-11 13:16:00 +11:00
DrSmugleaf
0152f9d1d8 Move Component lifecycle methods to EntityManager (#4483) 2023-10-10 17:34:40 -07:00
DrSmugleaf
16d916796a Add doc clarification to CopyByRef inheritance for the future coder (#4476) 2023-10-10 17:34:24 -07:00
Leon Friedrich
e865157432 Make AssertOwner() accept nullable components (#4475) 2023-10-10 04:41:26 +11:00
metalgearsloth
24d5ce4bd4 Make AddCompUninit obsolete (#4470) 2023-10-10 04:41:10 +11:00
deltanedas
662195e4ff add SortedSetSerializer (#4473) 2023-10-10 04:29:42 +11:00
DrSmugleaf
d00fd6f736 Make AnimationPlayerSystem only log a warning for networked components when the property is automatically networked (#4477) 2023-10-10 04:08:56 +11:00
Leon Friedrich
2d58c1071d Make ExpandPvsEvent lists nullable (#4479) 2023-10-10 04:07:59 +11:00
Leon Friedrich
a9db89d023 Fix nullable NetEntity conversion (#4481) 2023-10-10 04:07:30 +11:00
Leon Friedrich
684cabf3e6 Use metadata query in ToPrettyString() (#4478) 2023-10-09 19:07:31 +11:00
ElectroJr
a4f51f0cd9 Fix release notes 2023-10-08 12:41:45 -04:00
ElectroJr
a8ddd837c8 Version: 165.0.0 2023-10-08 12:39:03 -04:00
Leon Friedrich
82aace7997 Fix SplitContainer.MinDraggableWidth not working with mouse-blocking children. (#4439) 2023-10-09 03:28:23 +11:00
Leon Friedrich
01ce244b7b Validate default values of ProtoId and EntProtoId fields (#4462) 2023-10-02 03:49:45 +11:00
metalgearsloth
58aa6e5c75 Version: 164.0.0 2023-09-30 15:19:43 +10:00
DrSmugleaf
4818c3aab4 Make auto comp states infer when data should be cloned (#4461) 2023-09-30 15:14:10 +10:00
Leon Friedrich
3b6adeb5ff Reduce transform resolves in RecursiveDeleteEntity() (#4468) 2023-09-30 15:12:17 +10:00
metalgearsloth
889b8351be Version: 163.0.0 2023-09-30 14:40:55 +10:00
metalgearsloth
ac37b0a131 Move TimedDespawn to engine (#4448) 2023-09-30 14:35:28 +10:00
Leon Friedrich
f6f1fc425a Use ToPrettyString() in component resolve errors (#4467) 2023-09-30 14:31:40 +10:00
Leon Friedrich
7476628840 Give map and grid entities a default name (#4464) 2023-09-30 14:09:37 +10:00
Leon Friedrich
668cdbe76b Allow adding/removing of widgets in sub-controls (#4443) 2023-09-30 14:08:52 +10:00
Leon Friedrich
a0a6e9b111 Fix render error spam (#4463) 2023-09-30 14:07:27 +10:00
Leon Friedrich
06d28f04e6 Add ExecuteCommand() (#4466) 2023-09-30 14:07:07 +10:00
Leon Friedrich
57897161d0 Fix console backspace exception (#4465) 2023-09-30 14:05:29 +10:00
metalgearsloth
c4c528478e Use gamestate fields to avoid heap allocs (#4452) 2023-09-29 15:03:50 +10: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
87 changed files with 2007 additions and 1076 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,142 @@ END TEMPLATE-->
*None yet*
## 167.0.2
## 167.0.1
## 167.0.0
### Breaking changes
* Remove ComponentExtensions.
* Remove ContainerHelpers.
* Change some TransformSystem methods to fix clientside lerping.
### Bugfixes
* Fixed PVS bugs from dropped entity states.
### Other
* Add more joint debug asserts.
## 166.0.0
### Breaking changes
* EntityUid-NetEntity conversion methods now return null when given a null value, rather than returning an invalid id.
* ExpandPvsEvent now defaults to using null lists to reduce allocations.
* Various component lifestage related methods have been moved from the `Component` class to `EntityManager`.
* Session/client specific PVS overrides are now always recursive, which means that all children of the overriden entity will also get sent.
### New features
* Added a SortedSet yaml serializer.
### Other
* AddComponentUninitialized is now marked as obsolete and will be removed in the future.
* DebugTools.AssertOwner() now accepts null components.
## 165.0.0
### Breaking changes
* The arguments of `SplitContainer`s resize-finished event have changed.
### New features
* The YAML validator now checks the default values of ProtoId<T> and EntProtoId data fields.
### Bugfixes
* The minimum draggable area of split containers now blocks mouse inputs.
## 164.0.0
### Breaking changes
* Make automatic component states infer cloneData.
* Removed cloneData from AutoNetworkedFieldAttribute. This is now automatically inferred.
### Internal
* Reduce Transform GetComponents in RecursiveDeleteEntity.
## 163.0.0
### Breaking changes
* Moved TimedDespawn to engine for a component that deletes the attached entity after a timer has elapsed.
### New features
* Add ExecuteCommand for integration tests.
* Allow adding / removing widgets of cub-controls.
* Give maps / grids a default name to help with debugging.
* Use ToPrettyString in component resolve errors to help with debugging.
### Bugfixes
* Fix console backspace exception.
* Fix rendering invalid maps spamming exceptions every frame.
### Internal
* Move ClientGameStatemanager local variables to fields to avoid re-allocating every tick.
## 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

@@ -43,9 +43,9 @@ namespace Robust.Client.GameObjects
base.FlushEntities();
}
EntityUid IClientEntityManagerInternal.CreateEntity(string? prototypeName)
EntityUid IClientEntityManagerInternal.CreateEntity(string? prototypeName, out MetaDataComponent metadata)
{
return base.CreateEntity(prototypeName);
return base.CreateEntity(prototypeName, out metadata);
}
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity, MetaDataComponent? meta)
@@ -93,14 +93,10 @@ namespace Robust.Client.GameObjects
base.Dirty(uid, component, meta);
}
[return: NotNullIfNotNull("uid")]
public override EntityStringRepresentation? ToPrettyString(EntityUid? uid)
public override EntityStringRepresentation ToPrettyString(EntityUid uid, MetaDataComponent? metaDataComponent = null)
{
if (uid == null)
return null;
if (_playerManager.LocalPlayer?.ControlledEntity == uid)
return base.ToPrettyString(uid).Value with { Session = _playerManager.LocalPlayer.Session };
return base.ToPrettyString(uid) with { Session = _playerManager.LocalPlayer.Session };
return base.ToPrettyString(uid);
}

View File

@@ -4,7 +4,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
using TerraFX.Interop.Windows;
namespace Robust.Client.GameObjects
{
@@ -117,7 +116,7 @@ namespace Robust.Client.GameObjects
if (compTrack.ComponentType == null)
{
_sawmill.Error($"Attempted to play a component animation without any component specified.");
_sawmill.Error("Attempted to play a component animation without any component specified.");
return;
}
@@ -136,8 +135,14 @@ namespace Robust.Client.GameObjects
// In principle there is nothing wrong with this, as long as the property of the component being
// animated is not part of the networked state and setting it does not dirty the component. Hence only a
// warning in debug mode.
if (reg.NetID != null)
_sawmill.Warning($"Playing a component animation on a networked component {reg.Name} belonging to {ToPrettyString(component.Owner)}");
if (reg.NetID != null && compTrack.Property != null)
{
if (animatedComp.GetType().GetProperty(compTrack.Property) is { } property &&
property.HasCustomAttribute<AutoNetworkedFieldAttribute>())
{
_sawmill.Warning($"Playing a component animation on a networked component {reg.Name} belonging to {ToPrettyString(component.Owner)}");
}
}
}
#endif

View File

@@ -1,51 +1,41 @@
using System.Numerics;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
namespace Robust.Client.GameObjects;
public sealed partial class TransformSystem
{
public override void SetLocalPosition(TransformComponent xform, Vector2 value)
public override void SetLocalPosition(EntityUid uid, Vector2 value, TransformComponent? xform = null)
{
xform.PrevPosition = xform._localPosition;
if (!XformQuery.Resolve(uid, ref xform))
return;
xform.NextPosition = value;
xform.LerpParent = xform.ParentUid;
base.SetLocalPosition(xform, value);
ActivateLerp(xform);
ActivateLerp(uid, xform);
base.SetLocalPosition(uid, value, xform);
}
public override void SetLocalPositionNoLerp(TransformComponent xform, Vector2 value)
public override void SetLocalRotation(EntityUid uid, Angle value, TransformComponent? xform = null)
{
xform.NextPosition = null;
xform.LerpParent = EntityUid.Invalid;
base.SetLocalPositionNoLerp(xform, value);
if (!XformQuery.Resolve(uid, ref xform))
return;
xform.NextRotation = value;
ActivateLerp(uid, xform);
base.SetLocalRotation(uid, value, xform);
}
public override void SetLocalRotationNoLerp(TransformComponent xform, Angle angle)
public override void SetLocalPositionRotation(EntityUid uid, Vector2 pos, Angle rot, TransformComponent? xform = null)
{
xform.NextRotation = null;
xform.LerpParent = EntityUid.Invalid;
base.SetLocalRotationNoLerp(xform, angle);
}
if (!XformQuery.Resolve(uid, ref xform))
return;
public override void SetLocalRotation(TransformComponent xform, Angle angle)
{
xform.PrevRotation = xform._localRotation;
xform.NextRotation = angle;
xform.LerpParent = xform.ParentUid;
base.SetLocalRotation(xform, angle);
ActivateLerp(xform);
}
public override void SetLocalPositionRotation(TransformComponent xform, Vector2 pos, Angle rot)
{
xform.PrevPosition = xform._localPosition;
xform.NextPosition = pos;
xform.PrevRotation = xform._localRotation;
xform.NextRotation = rot;
xform.LerpParent = xform.ParentUid;
base.SetLocalPositionRotation(xform, pos, rot);
ActivateLerp(xform);
ActivateLerp(uid, xform);
base.SetLocalPositionRotation(uid, pos, rot, xform);
}
}

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using JetBrains.Annotations;
@@ -25,11 +24,6 @@ namespace Robust.Client.GameObjects
private const float MinInterpolationDistance = 0.001f;
private const float MinInterpolationDistanceSquared = MinInterpolationDistance * MinInterpolationDistance;
private const double MinInterpolationAngle = Math.PI / 720;
// 45 degrees.
private const double MaxInterpolationAngle = Math.PI / 4;
[Dependency] private readonly IGameTiming _gameTiming = default!;
// Only keep track of transforms actively lerping.
@@ -48,21 +42,77 @@ namespace Robust.Client.GameObjects
_lerpingTransforms.Clear();
}
public override void ActivateLerp(TransformComponent xform)
public override void ActivateLerp(EntityUid uid, TransformComponent xform)
{
if (xform.ActivelyLerping)
// This lerping logic is pretty convoluted and generally assumes that the client does not mispredict.
// A more foolproof solution would be to just cache the coordinates at which any given entity was most
// recently rendered and using that as the lerp origin. However that'd require enumerating over all entities
// every tick which is pretty icky.
// The general considerations are:
// - If the client receives a server state for an entity moving from a->b and predicts nothing else, then it
// should show the entity lerping.
// - If the client predicts an entity will move while already lerping due to a state-application, it should
// clear the state's lerp, under the assumption that the client predicted the state and already rendered
// the entity in the final position.
// - If the client predicts that an entity moves, then we only lerp if this is the first time that the tick
// was predicted. I.e., we assume the entity was already rendered in it's final of that lerp.
// - If the client predicts that an entity should lerp twice in the same tick, then we need to combine them.
// I.e. moving from a->b then b->c, the client should lerp from a->c.
// If the client predicts an entity moves while already lerping, it should clear the
// predict a->b, lerp a->b
// predicted a->b, then predict b->c. Lerp b->c
// predicted a->b, then predict b->c. Lerp b->c
// predicted a->b, predicted b->c, then predict c->d. Lerp c->d
// server state a->b, then predicted b->c, lerp b->c
// server state a->b, then predicted b->c, then predict d, lerp b->c
if (_gameTiming.ApplyingState)
{
if (xform.ActivelyLerping)
{
// This should not happen, but can happen if some bad component state application code modifies an entity's coordinates.
Log.Error($"Entity {(ToPrettyString(uid))} tried to lerp twice while applying component states.");
return;
}
_lerpingTransforms.Add(xform);
xform.ActivelyLerping = true;
xform.PredictedLerp = false;
xform.LerpParent = xform.ParentUid;
xform.PrevRotation = xform._localRotation;
xform.PrevPosition = xform._localPosition;
xform.LastLerp = _gameTiming.CurTick;
return;
}
xform.ActivelyLerping = true;
_lerpingTransforms.Add(xform);
}
xform.LastLerp = _gameTiming.CurTick;
if (!_gameTiming.IsFirstTimePredicted)
{
xform.ActivelyLerping = false;
return;
}
public override void DeactivateLerp(TransformComponent component)
{
// this should cause the lerp to do nothing
component.NextPosition = null;
component.NextRotation = null;
component.LerpParent = EntityUid.Invalid;
if (!xform.ActivelyLerping)
{
_lerpingTransforms.Add(xform);
xform.ActivelyLerping = true;
xform.PredictedLerp = true;
xform.PrevRotation = xform._localRotation;
xform.PrevPosition = xform._localPosition;
xform.LerpParent = xform.ParentUid;
return;
}
if (!xform.PredictedLerp || xform.LerpParent != xform.ParentUid)
{
// Existing lerp was not due to prediction, but due to state application. That lerp should already
// have been rendered, so we will start a new lerp from the current position.
xform.PrevRotation = xform._localRotation;
xform.PrevPosition = xform._localPosition;
xform.LerpParent = xform.ParentUid;
}
}
public override void FrameUpdate(float frameTime)
@@ -74,11 +124,13 @@ namespace Robust.Client.GameObjects
for (var i = 0; i < _lerpingTransforms.Count; i++)
{
var transform = _lerpingTransforms[i];
var uid = transform.Owner;
var found = false;
// Only lerp if parent didn't change.
// E.g. entering lockers would do it.
if (transform.LerpParent == transform.ParentUid
if (transform.ActivelyLerping
&& transform.LerpParent == transform.ParentUid
&& transform.ParentUid.IsValid()
&& !transform.Deleted)
{
@@ -90,8 +142,7 @@ namespace Robust.Client.GameObjects
if (distance is > MinInterpolationDistanceSquared and < MaxInterpolationDistanceSquared)
{
transform.LocalPosition = Vector2.Lerp(lerpSource, lerpDest, step);
// Setting LocalPosition clears LerpPosition so fix that.
SetLocalPositionNoLerp(uid, Vector2.Lerp(lerpSource, lerpDest, step), transform);
transform.NextPosition = lerpDest;
found = true;
}
@@ -101,15 +152,9 @@ namespace Robust.Client.GameObjects
{
var lerpDest = transform.NextRotation.Value;
var lerpSource = transform.PrevRotation;
var distance = Math.Abs(Angle.ShortestDistance(lerpDest, lerpSource));
if (distance is > MinInterpolationAngle and < MaxInterpolationAngle)
{
transform.LocalRotation = Angle.Lerp(lerpSource, lerpDest, step);
// Setting LocalRotation clears LerpAngle so fix that.
transform.NextRotation = lerpDest;
found = true;
}
SetLocalRotationNoLerp(uid, Angle.Lerp(lerpSource, lerpDest, step), transform);
transform.NextRotation = lerpDest;
found = true;
}
}

View File

@@ -6,7 +6,7 @@ namespace Robust.Client.GameObjects
{
// These methods are used by the Game State Manager.
EntityUid CreateEntity(string? prototypeName);
EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata);
void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null);

View File

@@ -4,7 +4,9 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using Microsoft.Extensions.ObjectPool;
using Robust.Client.GameObjects;
using Robust.Client.Input;
using Robust.Client.Physics;
@@ -51,6 +53,14 @@ namespace Robust.Client.GameStates
private readonly Dictionary<NetEntity, EntityState> _toCreate = new();
private readonly Dictionary<ushort, (IComponent Component, ComponentState? curState, ComponentState? nextState)> _compStateWork = new();
private readonly Dictionary<EntityUid, HashSet<Type>> _pendingReapplyNetStates = new();
private readonly HashSet<NetEntity> _stateEnts = new();
private readonly List<EntityUid> _toDelete = new();
private readonly List<Component> _toRemove = new();
private readonly Dictionary<NetEntity, Dictionary<ushort, ComponentState>> _outputData = new();
private readonly List<(EntityUid, TransformComponent)> _queuedBroadphaseUpdates = new();
private readonly ObjectPool<Dictionary<ushort, ComponentState>> _compDataPool =
new DefaultObjectPool<Dictionary<ushort, ComponentState>>(new DictPolicy<ushort, ComponentState>(), 256);
private uint _metaCompNetId;
@@ -101,6 +111,13 @@ namespace Robust.Client.GameStates
public event Action<MsgStateLeavePvs>? PvsLeave;
#if DEBUG
/// <summary>
/// If true, this will cause received game states to be ignored. Used by integration tests.
/// </summary>
public bool DropStates;
#endif
/// <inheritdoc />
public void Initialize()
{
@@ -193,6 +210,10 @@ namespace Robust.Client.GameStates
private void HandleStateMessage(MsgState message)
{
#if DEBUG
if (DropStates)
return;
#endif
// We ONLY ack states that are definitely going to get applied. Otherwise the sever might assume that we
// applied a state containing entity-creation information, which it would then no longer send to us when
// we re-encounter this entity
@@ -267,18 +288,15 @@ namespace Robust.Client.GameStates
continue;
}
if (PredictionNeedsResetting)
try
{
try
{
ResetPredictedEntities();
}
catch (Exception e)
{
// avoid exception spam from repeatedly trying to reset the same entity.
_entitySystemManager.GetEntitySystem<ClientDirtySystem>().Reset();
_runtimeLog.LogException(e, "ResetPredictedEntities");
}
ResetPredictedEntities();
}
catch (Exception e)
{
// avoid exception spam from repeatedly trying to reset the same entity.
_entitySystemManager.GetEntitySystem<ClientDirtySystem>().Reset();
_runtimeLog.LogException(e, "ResetPredictedEntities");
}
// If we were waiting for a new state, we are now applying it.
@@ -467,20 +485,22 @@ namespace Robust.Client.GameStates
public void ResetPredictedEntities()
{
PredictionNeedsResetting = false;
using var _ = _prof.Group("ResetPredictedEntities");
using var __ = _timing.StartStateApplicationArea();
// This is terrible, and I hate it. This also needs to run even when prediction is disabled.
_entitySystemManager.GetEntitySystem<SharedGridTraversalSystem>().QueuedEvents.Clear();
_entitySystemManager.GetEntitySystem<TransformSystem>().Reset();
if (!PredictionNeedsResetting)
return;
PredictionNeedsResetting = false;
var countReset = 0;
var system = _entitySystemManager.GetEntitySystem<ClientDirtySystem>();
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
RemQueue<Component> toRemove = new();
// This is terrible, and I hate it.
_entitySystemManager.GetEntitySystem<SharedGridTraversalSystem>().QueuedEvents.Clear();
_entitySystemManager.GetEntitySystem<TransformSystem>().Reset();
foreach (var entity in system.DirtyEntities)
{
DebugTools.Assert(toRemove.Count == 0);
@@ -588,14 +608,13 @@ namespace Robust.Client.GameStates
/// </remarks>
private void MergeImplicitData(IEnumerable<NetEntity> createdEntities)
{
var outputData = new Dictionary<NetEntity, Dictionary<ushort, ComponentState>>();
var bus = _entityManager.EventBus;
foreach (var netEntity in createdEntities)
{
var (createdEntity, meta) = _entityManager.GetEntityData(netEntity);
var compData = new Dictionary<ushort, ComponentState>();
outputData.Add(netEntity, compData);
var compData = _compDataPool.Get();
_outputData.Add(netEntity, compData);
foreach (var (netId, component) in meta.NetComponents)
{
@@ -608,7 +627,14 @@ namespace Robust.Client.GameStates
}
}
_processor.MergeImplicitData(outputData);
_processor.MergeImplicitData(_outputData);
foreach (var data in _outputData.Values)
{
_compDataPool.Return(data);
}
_outputData.Clear();
}
private void AckGameState(GameTick sequence)
@@ -686,12 +712,10 @@ namespace Robust.Client.GameStates
if (metaState == null)
throw new MissingMetadataException(es.NetEntity);
var uid = _entities.CreateEntity(metaState.PrototypeId);
var uid = _entities.CreateEntity(metaState.PrototypeId, out var newMeta);
_toCreate.Add(es.NetEntity, es);
var newMeta = metas.GetComponent(uid);
_toApply.Add(uid, (es.NetEntity, newMeta, false, GameTick.Zero, es, null));
// Client creates a client-side net entity for the newly created entity.
// We need to clear this mapping before assigning the real net id.
// TODO NetEntity Jank: prevent the client from creating this in the first place.
@@ -721,9 +745,7 @@ namespace Robust.Client.GameStates
if (_toCreate.ContainsKey(es.NetEntity))
continue;
var uid = _entityManager.GetEntity(es.NetEntity);
if (!metas.TryGetComponent(uid, out var meta))
if (!_entityManager.TryGetEntityData(es.NetEntity, out var uid, out var meta))
continue;
bool isEnteringPvs = (meta.Flags & MetaDataFlags.Detached) != 0;
@@ -738,7 +760,7 @@ namespace Robust.Client.GameStates
continue;
}
_toApply.Add(uid, (es.NetEntity, meta, isEnteringPvs, meta.LastStateApplied, es, null));
_toApply.Add(uid.Value, (es.NetEntity, meta, isEnteringPvs, meta.LastStateApplied, es, null));
meta.LastStateApplied = curState.ToSequence;
}
@@ -759,28 +781,32 @@ 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);
_queuedBroadphaseUpdates.Clear();
// Apply entity states.
using (_prof.Group("Apply States"))
@@ -800,7 +826,7 @@ namespace Robust.Client.GameStates
DebugTools.Assert(xform.Broadphase == BroadphaseData.Invalid);
xform.Broadphase = null;
if (!_toApply.TryGetValue(xform.ParentUid, out var parent) || !parent.EnteringPvs)
queuedBroadphaseUpdates.Add((entity, xform));
_queuedBroadphaseUpdates.Add((entity, xform));
}
_prof.WriteValue("Count", ProfData.Int32(_toApply.Count));
@@ -811,7 +837,7 @@ namespace Robust.Client.GameStates
{
try
{
foreach (var (uid, xform) in queuedBroadphaseUpdates)
foreach (var (uid, xform) in _queuedBroadphaseUpdates)
{
lookupSys.FindAndAddToEntityTree(uid, true, xform);
}
@@ -828,7 +854,7 @@ namespace Robust.Client.GameStates
{
try
{
ProcessDeletions(delSpan, xforms, metas, xformSys);
ProcessDeletions(delSpan, xforms, xformSys);
}
catch (Exception e)
{
@@ -865,17 +891,17 @@ namespace Robust.Client.GameStates
_sawmill.Info($"Resetting all entity states to tick {state.ToSequence}.");
// Construct hashset for set.Contains() checks.
_stateEnts.Clear();
var entityStates = state.EntityStates.Span;
var stateEnts = new HashSet<NetEntity>();
foreach (var entState in entityStates)
{
stateEnts.Add(entState.NetEntity);
_stateEnts.Add(entState.NetEntity);
}
var xforms = _entities.GetEntityQuery<TransformComponent>();
var xformSys = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
var toDelete = new List<EntityUid>(Math.Max(64, _entities.EntityCount - stateEnts.Count));
_toDelete.Clear();
// Client side entities won't need the transform, but that should always be a tiny minority of entities
var metaQuery = _entityManager.AllEntityQueryEnumerator<MetaDataComponent, TransformComponent>();
@@ -886,14 +912,16 @@ namespace Robust.Client.GameStates
if (metadata.NetEntity.IsClientSide())
{
if (deleteClientEntities)
toDelete.Add(ent);
_toDelete.Add(ent);
continue;
}
if (stateEnts.Contains(netEnt))
if (_stateEnts.Contains(netEnt))
{
if (resetAllEntities || metadata.LastStateApplied > state.ToSequence)
metadata.LastStateApplied = GameTick.Zero; // TODO track last-state-applied for individual components? Is it even worth it?
continue;
}
@@ -911,13 +939,14 @@ namespace Robust.Client.GameStates
&& !deleteClientEntities // don't add duplicates
&& _entities.IsClientSide(child.Value))
{
toDelete.Add(child.Value);
_toDelete.Add(child.Value);
}
}
toDelete.Add(ent);
_toDelete.Add(ent);
}
foreach (var ent in toDelete)
foreach (var ent in _toDelete)
{
_entities.DeleteEntity(ent);
}
@@ -926,7 +955,6 @@ namespace Robust.Client.GameStates
private void ProcessDeletions(
ReadOnlySpan<NetEntity> delSpan,
EntityQuery<TransformComponent> xforms,
EntityQuery<MetaDataComponent> metas,
SharedTransformSystem xformSys)
{
// Processing deletions is non-trivial, because by default deletions will also delete all child entities.
@@ -997,6 +1025,7 @@ namespace Robust.Client.GameStates
// things like container insertion and ejection.
using var _ = _prof.Group("Leave PVS");
detached.EnsureCapacity(toDetach.Count);
foreach (var (tick, ents) in toDetach)
{
@@ -1019,9 +1048,7 @@ namespace Robust.Client.GameStates
{
foreach (var netEntity in entities)
{
var ent = _entityManager.GetEntity(netEntity);
if (!metas.TryGetComponent(ent, out var meta))
if (!_entityManager.TryGetEntityData(netEntity, out var ent, out var meta))
continue;
if (meta.LastStateApplied > maxTick)
@@ -1037,10 +1064,10 @@ namespace Robust.Client.GameStates
if (lastStateApplied.HasValue)
meta.LastStateApplied = lastStateApplied.Value;
var xform = xforms.GetComponent(ent);
var xform = xforms.GetComponent(ent.Value);
if (xform.ParentUid.IsValid())
{
lookupSys.RemoveFromEntityTree(ent, xform);
lookupSys.RemoveFromEntityTree(ent.Value, xform);
xform.Broadphase = BroadphaseData.Invalid;
// In some cursed scenarios an entity inside of a container can leave PVS without the container itself leaving PVS.
@@ -1049,13 +1076,13 @@ namespace Robust.Client.GameStates
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
metas.TryGetComponent(xform.ParentUid, out var containerMeta) &&
(containerMeta.Flags & MetaDataFlags.Detached) == 0 &&
containerSys.TryGetContainingContainer(xform.ParentUid, ent, out container, null, true))
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container, null, true))
{
container.Remove(ent, _entities, xform, meta, false, true);
container.Remove(ent.Value, _entities, xform, meta, false, true);
}
meta._flags |= MetaDataFlags.Detached;
xformSys.DetachParentToNull(ent, xform);
xformSys.DetachParentToNull(ent.Value, xform);
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0);
if (container != null)
@@ -1135,14 +1162,15 @@ namespace Robust.Client.GameStates
// First remove any deleted components
if (curState?.NetComponents != null)
{
RemQueue<Component> toRemove = new();
_toRemove.Clear();
foreach (var (id, comp) in meta.NetComponents)
{
if (comp.NetSyncEnabled && !curState.NetComponents.Contains(id))
toRemove.Add(comp);
_toRemove.Add(comp);
}
foreach (var comp in toRemove)
foreach (var comp in _toRemove)
{
_entities.RemoveComponent(uid, comp, meta);
}
@@ -1198,10 +1226,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 +1249,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);
}
}
@@ -1402,20 +1438,24 @@ namespace Robust.Client.GameStates
}
// ensure we don't have any extra components
RemQueue<Component> toRemove = new();
_toRemove.Clear();
foreach (var (id, comp) in meta.NetComponents)
{
if (comp.NetSyncEnabled && !lastState.ContainsKey(id))
toRemove.Add(comp);
_toRemove.Add(comp);
}
foreach (var comp in toRemove)
foreach (var comp in _toRemove)
{
_entities.RemoveComponent(uid, comp);
}
}
#endregion
public bool IsQueuedForDetach(NetEntity entity)
=> _processor.IsQueuedForDetach(entity);
void IPostInjectInit.PostInject()
{
_sawmill = _logMan.GetSawmill(CVars.NetPredict.Name);

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);
}
}
@@ -401,6 +404,18 @@ namespace Robust.Client.GameStates
return _lastStateFullRep.TryGetValue(entity, out dictionary);
}
public bool IsQueuedForDetach(NetEntity entity)
{
// This isn't fast, but its just meant for use in tests & debug asserts.
foreach (var msg in _pvsDetachMessages.Values)
{
if (msg.Contains(entity))
return true;
}
return false;
}
public int CalculateBufferSize(GameTick fromTick)
{
bool foundState;

View File

@@ -1,19 +1,19 @@
using System;
using Robust.Shared.Enums;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using Robust.Shared.Containers;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
namespace Robust.Client.GameStates
{
internal sealed class NetInterpOverlay : Overlay
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -24,6 +24,11 @@ namespace Robust.Client.GameStates
private readonly SharedContainerSystem _container;
private readonly SharedTransformSystem _xform;
/// <summary>
/// When an entity stops lerping the overlay will continue to draw a box around the entity for this amount of time.
/// </summary>
public static readonly TimeSpan Delay = TimeSpan.FromSeconds(2f);
public NetInterpOverlay(EntityLookupSystem lookup)
{
IoCManager.InjectDependencies(this);
@@ -40,8 +45,8 @@ namespace Robust.Client.GameStates
var worldHandle = (DrawingHandleWorld) handle;
var viewport = args.WorldAABB;
var query = _entityManager.AllEntityQueryEnumerator<PhysicsComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var physics, out var transform))
var query = _entityManager.AllEntityQueryEnumerator<TransformComponent>();
while (query.MoveNext(out var uid, out var transform))
{
// if not on the same map, continue
if (transform.MapID != _eyeManager.CurrentMap || _container.IsEntityInContainer(uid))
@@ -50,8 +55,8 @@ namespace Robust.Client.GameStates
if (transform.GridUid == uid)
continue;
// This entity isn't lerping, no need to draw debug info for it
if(transform.NextPosition == null)
var delta = (_timing.CurTick.Value - transform.LastLerp.Value) * _timing.TickPeriod;
if(!transform.ActivelyLerping && delta > Delay)
continue;
var aabb = _lookup.GetWorldAABB(uid);
@@ -61,7 +66,9 @@ namespace Robust.Client.GameStates
continue;
var (pos, rot) = _xform.GetWorldPositionRotation(transform, _entityManager.GetEntityQuery<TransformComponent>());
var boxOffset = transform.NextPosition.Value - transform.LocalPosition;
var boxOffset = transform.NextPosition != null
? transform.NextPosition.Value - transform.LocalPosition
: default;
var worldOffset = (rot - transform.LocalRotation).RotateVec(boxOffset);
var nextPos = pos + worldOffset;

View File

@@ -338,10 +338,12 @@ namespace Robust.Client.Graphics.Clyde
}
var mapId = eye.Position.MapId;
if (mapId == MapId.Nullspace)
return;
// If this map has lighting disabled, return
var mapUid = _mapManager.GetMapEntityId(mapId);
if (!_entityManager.GetComponent<MapComponent>(mapUid).LightingEnabled)
if (!_entityManager.TryGetComponent<MapComponent>(mapUid, out var map) || !map.LightingEnabled)
{
return;
}

View File

@@ -82,11 +82,8 @@ namespace Robust.Client.Physics
continue;
}
xform.PrevPosition = position;
xform.PrevRotation = rotation;
xform.LerpParent = parentUid;
xform.NextPosition = xform.LocalPosition;
xform.NextRotation = xform.LocalRotation;
// Transform system will handle lerping.
_transform.SetLocalPositionRotation(uid, xform.LocalPosition, xform.LocalRotation, xform);
}
component.LerpData.Clear();

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

@@ -0,0 +1,12 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Spawners;
namespace Robust.Client.Spawners;
public sealed class TimedDespawnSystem : SharedTimedDespawnSystem
{
protected override bool CanDelete(EntityUid uid)
{
return IsClientSide(uid);
}
}

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

@@ -2,6 +2,7 @@ using System;
using System.Numerics;
using Robust.Shared.Input;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface.Controls
@@ -14,7 +15,17 @@ namespace Robust.Client.UserInterface.Controls
/// for each enum value to see how the different options work.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public SplitResizeMode ResizeMode { get; set; }
public SplitResizeMode ResizeMode
{
get => _resizeMode;
set
{
_resizeMode = value;
_splitDragArea.Visible = value != SplitResizeMode.NotResizable;
}
}
private SplitResizeMode _resizeMode = SplitResizeMode.RespectChildrenMinSize;
/// <summary>
/// Width of the split in virtual pixels
@@ -30,7 +41,7 @@ namespace Robust.Client.UserInterface.Controls
}
}
private float _splitWidth;
private float _splitWidth = 10;
/// <summary>
/// This width determines the minimum size of the draggable area around the split. This has no effect if it
@@ -38,11 +49,13 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
public float MinDraggableWidth = 10f;
public float DraggableWidth => MathF.Max(MinDraggableWidth, _splitWidth);
/// <summary>
/// Virtual pixel offset from the edge beyond which the split cannot be moved.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float SplitEdgeSeparation { get; set; }
public float SplitEdgeSeparation { get; set; } = 10;
private float _splitStart;
@@ -58,6 +71,7 @@ namespace Robust.Client.UserInterface.Controls
_splitStart = value - _splitWidth / 2;
ClampSplitCenter();
InvalidateMeasure();
OnSplitResized?.Invoke();
}
}
@@ -81,13 +95,51 @@ namespace Robust.Client.UserInterface.Controls
}
}
private SplitState _splitState;
private bool _dragging;
private SplitState _splitState = SplitState.Auto;
private SplitOrientation _orientation;
private SplitStretchDirection _stretchDirection;
private SplitStretchDirection _stretchDirection = SplitStretchDirection.BottomRight;
private bool _dragging;
private float _dragOffset;
private bool Vertical => Orientation == SplitOrientation.Vertical;
private readonly SplitDragControl _splitDragArea = new();
/// <summary>
/// The upper/left control in the split container.
/// </summary>
public Control? First
{
get
{
if (ChildCount < 3)
return null;
DebugTools.AssertNotEqual(GetChild(0), _splitDragArea);
return GetChild(0);
}
}
/// <summary>
/// The lower/right control in the split container.
/// </summary>
public Control? Second
{
get
{
if (ChildCount < 3)
return null;
DebugTools.AssertNotEqual(GetChild(1), _splitDragArea);
return GetChild(1);
}
}
public (Control First, Control Second)? Splits => ChildCount < 3 ? null : (GetChild(0), GetChild(1));
public event Action? OnSplitResizeFinished;
public event Action? OnSplitResized;
/// <summary>
/// Whether the split position should be set manually or automatically.
/// </summary>
@@ -131,73 +183,53 @@ namespace Robust.Client.UserInterface.Controls
public SplitContainer()
{
MouseFilter = MouseFilterMode.Stop;
_splitState = SplitState.Auto;
_stretchDirection = SplitStretchDirection.BottomRight;
_dragging = false;
ResizeMode = SplitResizeMode.RespectChildrenMinSize;
SplitWidth = 10;
SplitEdgeSeparation = 10;
AddChild(_splitDragArea);
_splitDragArea.Visible = _resizeMode != SplitResizeMode.NotResizable;
_splitDragArea.DefaultCursorShape = Vertical ? CursorShape.VResize : CursorShape.HResize;
_splitDragArea.OnMouseUp += StopDragging;
_splitDragArea.OnMouseDown += StartDragging;
_splitDragArea.OnMouseMove += OnMove;
}
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
private void OnMove(GUIMouseMoveEventArgs args)
{
base.MouseMove(args);
if (ResizeMode == SplitResizeMode.NotResizable)
return;
if (ResizeMode == SplitResizeMode.NotResizable) return;
if (!_dragging)
return;
if (_dragging)
{
var newOffset = Vertical ? args.RelativePosition.Y : args.RelativePosition.X;
SplitCenter = newOffset;
DefaultCursorShape = Vertical ? CursorShape.VResize : CursorShape.HResize;
}
else
{
// on mouseover, check if they are over the split and change the cursor accordingly
var cursor = CursorShape.Arrow;
if (CanDragAt(args.RelativePosition))
{
cursor = Vertical ? CursorShape.VResize : CursorShape.HResize;
}
DefaultCursorShape = cursor;
}
// Source control might be either the container, or the separator.
// So we manually calculate the relative coordinates wrt the container.
var relative = args.GlobalPosition - GlobalPosition;
SplitCenter = Vertical ? relative.Y - _dragOffset : relative.X + _dragOffset;
}
protected internal override void KeyBindDown(GUIBoundKeyEventArgs args)
protected override void ChildAdded(Control newChild)
{
base.KeyBindDown(args);
if (ResizeMode == SplitResizeMode.NotResizable) return;
if (_dragging || args.Function != EngineKeyFunctions.UIClick) return;
if (CanDragAt(args.RelativePosition))
{
_dragging = true;
_splitState = SplitState.Manual;
}
base.ChildAdded(newChild);
_splitDragArea.SetPositionLast();
}
protected internal override void KeyBindUp(GUIBoundKeyEventArgs args)
public void StartDragging(GUIBoundKeyEventArgs args)
{
base.KeyBindUp(args);
if (ResizeMode == SplitResizeMode.NotResizable || _dragging)
return;
if (args.Function != EngineKeyFunctions.UIClick) return;
_dragging = true;
_dragOffset = DraggableWidth / 2 - (Vertical ? args.RelativePosition.Y : args.RelativePosition.X);
_splitState = SplitState.Manual;
DefaultCursorShape = Vertical ? CursorShape.VResize : CursorShape.HResize;
}
private void StopDragging(GUIBoundKeyEventArgs args)
{
if (!_dragging)
return;
_dragging = false;
DefaultCursorShape = CursorShape.Arrow;
}
private bool CanDragAt(Vector2 relativePosition)
{
var distance = Vertical
? Math.Abs(relativePosition.Y - SplitCenter)
: Math.Abs(relativePosition.X - SplitCenter);
return distance <= _splitWidth || distance <= MinDraggableWidth;
OnSplitResizeFinished?.Invoke();
}
/// <summary>
@@ -218,7 +250,7 @@ namespace Robust.Client.UserInterface.Controls
desiredSplit += Vertical ? desiredSize.Value.Y - Size.Y : desiredSize.Value.X - Size.X;
_splitStart = MathHelper.Clamp(desiredSplit, SplitEdgeSeparation, splitMax);
if (ResizeMode == SplitResizeMode.RespectChildrenMinSize && ChildCount == 2)
if (ResizeMode == SplitResizeMode.RespectChildrenMinSize && ChildCount == 3)
{
var first = GetChild(0);
var second = GetChild(1);
@@ -234,7 +266,7 @@ namespace Robust.Client.UserInterface.Controls
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
if (ChildCount != 2)
if (ChildCount < 3)
{
return finalSize;
}
@@ -287,15 +319,23 @@ namespace Robust.Client.UserInterface.Controls
}
}
// location & size of the draggable area may be larger than the split area.
var dragCenter = _splitStart + _splitWidth / 2;
var dragWidth = DraggableWidth;
var dragStart = dragCenter - dragWidth / 2;
var dragEnd = dragCenter + dragWidth / 2;
if (Vertical)
{
first.Arrange(new UIBox2(0, 0, finalSize.X, _splitStart));
second.Arrange(new UIBox2(0, _splitStart + _splitWidth, finalSize.X, finalSize.Y));
_splitDragArea.Arrange(new UIBox2(0, dragStart, finalSize.X, dragEnd));
}
else
{
first.Arrange(new UIBox2(0, 0, _splitStart, finalSize.Y));
second.Arrange(new UIBox2(_splitStart + _splitWidth, 0, finalSize.X, finalSize.Y));
_splitDragArea.Arrange(new UIBox2(dragStart, 0, dragEnd, finalSize.Y));
}
return finalSize;
@@ -303,7 +343,7 @@ namespace Robust.Client.UserInterface.Controls
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
if (ChildCount != 2)
if (ChildCount < 3)
{
return Vector2.Zero;
}
@@ -420,5 +460,40 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
TopLeft,
}
/// <summary>
/// Simple control to intercept mous events and redirect them to the parent.
/// </summary>
private sealed class SplitDragControl : Control
{
public event Action<GUIBoundKeyEventArgs>? OnMouseDown;
public event Action<GUIBoundKeyEventArgs>? OnMouseUp;
public event Action<GUIMouseMoveEventArgs>? OnMouseMove;
public SplitDragControl()
{
MouseFilter = MouseFilterMode.Stop;
}
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
{
base.MouseMove(args);
OnMouseMove?.Invoke(args);
}
protected internal override void KeyBindDown(GUIBoundKeyEventArgs args)
{
base.KeyBindDown(args);
if (args.Function == EngineKeyFunctions.UIClick)
OnMouseDown?.Invoke(args);
}
protected internal override void KeyBindUp(GUIBoundKeyEventArgs args)
{
base.KeyBindUp(args);
if (args.Function == EngineKeyFunctions.UIClick)
OnMouseUp?.Invoke(args);
}
}
}
}

View File

@@ -69,12 +69,11 @@ public abstract class UIScreen : LayoutContainer
public void RemoveWidget<T>() where T : UIWidget, new()
{
if (_widgets.TryGetValue(typeof(T), out var widget))
{
RemoveChild(widget);
}
if (!_widgets.Remove(typeof(T), out var widget))
return;
_widgets.Remove(typeof(T));
widget.Parent?.RemoveChild(widget);
RemoveChildren(widget);
}
internal void OnRemoved()
@@ -103,6 +102,14 @@ public abstract class UIScreen : LayoutContainer
AddChild(widget);
}
public void AddWidgetDirect(UIWidget widget)
{
if (!_widgets.TryAdd(widget.GetType(), widget))
throw new Exception("Tried to add duplicate widget to screen!");
RegisterChildren(widget);
}
public T? GetWidget<T>() where T : UIWidget, new()
{
return (T?) _widgets.GetValueOrDefault(typeof(T));

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

@@ -270,7 +270,7 @@ namespace Robust.Client.ViewVariables.Instances
button.OnPressed += _ =>
{
ViewVariablesManager.OpenVV(
new ViewVariablesComponentSelector(_entityManager.GetNetEntity(_entity), componentType.FullName));
new ViewVariablesComponentSelector(_netEntity, componentType.FullName));
};
removeButton.OnPressed += _ =>
{

View File

@@ -196,7 +196,7 @@ namespace Robust.Server.Console
break;
case ConsoleKey.Backspace:
if (currentBuffer.Length > 0)
if (currentBuffer.Length > 0 && internalCursor > 0)
{
currentBuffer = currentBuffer.Remove(internalCursor - 1, 1);
internalCursor--;

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

@@ -42,6 +42,7 @@ namespace Robust.Server.GameObjects
#endif
private ISawmill _netEntSawmill = default!;
private EntityQuery<ActorComponent> _actorQuery;
public override void Initialize()
{
@@ -53,6 +54,12 @@ namespace Robust.Server.GameObjects
base.Initialize();
}
public override void Startup()
{
base.Startup();
_actorQuery = GetEntityQuery<ActorComponent>();
}
EntityUid IServerEntityManagerInternal.AllocEntity(EntityPrototype? prototype)
{
return AllocEntity(prototype, out _);
@@ -78,15 +85,15 @@ namespace Robust.Server.GameObjects
StartEntity(entity);
}
private protected override EntityUid CreateEntity(string? prototypeName, IEntityLoadContext? context = null)
private protected override EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
{
if (prototypeName == null)
return base.CreateEntity(prototypeName, context);
return base.CreateEntity(prototypeName, out metadata, context);
if (!PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype))
throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}");
var entity = base.CreateEntity(prototype, context);
var entity = base.CreateEntity(prototype, out metadata, context);
// At this point in time, all data configure on the entity *should* be purely from the prototype.
// As such, we can reset the modified ticks to Zero,
@@ -109,15 +116,10 @@ namespace Robust.Server.GameObjects
}
}
[return: NotNullIfNotNull("uid")]
public override EntityStringRepresentation? ToPrettyString(EntityUid? uid)
public override EntityStringRepresentation ToPrettyString(EntityUid uid, MetaDataComponent? metadata = null)
{
if (uid == null)
return null;
TryGetComponent(uid, out ActorComponent? actor);
return base.ToPrettyString(uid).Value with { Session = actor?.PlayerSession };
_actorQuery.TryGetComponent(uid, out ActorComponent? actor);
return base.ToPrettyString(uid) with { Session = actor?.PlayerSession };
}
#region IEntityNetworkManager impl

View File

@@ -95,7 +95,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
/// <summary>
/// List of <see cref="TIndex"/> that should always get sent to a certain <see cref="ICommonSession"/>.
/// </summary>
private readonly Dictionary<ICommonSession, HashSet<TIndex>> _localOverrides = new();
private readonly Dictionary<ICommonSession, HashSet<TIndex>> _sessionOverrides = new();
/// <summary>
/// Which <see cref="TIndex"/> where last seen/sent to a certain <see cref="ICommonSession"/>.
@@ -207,7 +207,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
public bool TryGetChunk(EntityUid gridId, Vector2i chunkIndices, [NotNullWhen(true)] out HashSet<TIndex>? indices) =>
_gridChunkContents[gridId].TryGetValue(chunkIndices, out indices);
public HashSet<TIndex>.Enumerator GetElementsForSession(ICommonSession session) => _localOverrides[session].GetEnumerator();
public HashSet<TIndex>.Enumerator GetSessionOverrides(ICommonSession session) => _sessionOverrides[session].GetEnumerator();
private void AddIndexInternal(TIndex index, IIndexLocation location, HashSet<IChunkIndexLocation> dirtyChunks)
{
@@ -226,10 +226,10 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
gridLoc.Add(index);
dirtyChunks.Add(gridChunkLocation);
break;
case LocalOverride localOverride:
// might be gone due to disconnects
if(!_localOverrides.ContainsKey(localOverride.Session)) return;
_localOverrides[localOverride.Session].Add(index);
case SessionOverride sessionOverride:
if (!_sessionOverrides.TryGetValue(sessionOverride.Session, out var set))
return;
set.Add(index);
break;
case MapChunkLocation mapChunkLocation:
// might be gone due to map-deletions
@@ -253,16 +253,14 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
switch (location)
{
case GlobalOverride global:
if (global.Recursive)
_globalRecursiveOverrides.Remove(index);
else
_globalOverrides.Remove(index);
var set = global.Recursive ? _globalRecursiveOverrides : _globalOverrides;
set.Remove(index);
break;
case GridChunkLocation gridChunkLocation:
_gridChunkContents[gridChunkLocation.GridId][gridChunkLocation.ChunkIndices].Remove(index);
break;
case LocalOverride localOverride:
_localOverrides[localOverride.Session].Remove(index);
case SessionOverride sessionOverride:
_sessionOverrides.GetValueOrDefault(sessionOverride.Session)?.Remove(index);
break;
case MapChunkLocation mapChunkLocation:
_mapChunkContents[mapChunkLocation.MapId][mapChunkLocation.ChunkIndices].Remove(index);
@@ -276,7 +274,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
/// <inheritdoc />
public bool AddPlayer(ICommonSession session)
{
return _localOverrides.TryAdd(session, new()) & _lastSeen.TryAdd(session, new());
return _sessionOverrides.TryAdd(session, new()) & _lastSeen.TryAdd(session, new());
}
/// <inheritdoc />
@@ -292,7 +290,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
/// <inheritdoc />
public bool RemovePlayer(ICommonSession session)
{
if (_localOverrides.Remove(session, out var indices))
if (_sessionOverrides.Remove(session, out var indices))
{
foreach (var index in indices)
{
@@ -412,7 +410,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
return;
}
if (!removeFromOverride && oldLocation is LocalOverride)
if (!removeFromOverride && oldLocation is SessionOverride)
return;
if (oldLocation is GlobalOverride global &&
@@ -426,28 +424,29 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
/// <summary>
/// Updates an <see cref="TIndex"/> to be sent to a specific <see cref="ICommonSession"/> at all times.
/// This will always also send all children of the given entity.
/// </summary>
/// <param name="index">The <see cref="TIndex"/> to update.</param>
/// <param name="session">The <see cref="ICommonSession"/> receiving the object.</param>
/// <param name="removeFromOverride">An index at an override position will not be updated unless you set this flag.</param>
public void UpdateIndex(TIndex index, ICommonSession session, bool removeFromOverride = false)
public void AddSessionOverride(TIndex index, ICommonSession session, bool removeFromOverride)
{
if (!TryGetLocation(index, out var oldLocation))
{
RegisterUpdate(index, new LocalOverride(session));
RegisterUpdate(index, new SessionOverride(session));
return;
}
if (!removeFromOverride || oldLocation is GlobalOverride)
if (!removeFromOverride && oldLocation is GlobalOverride)
return;
if (oldLocation is LocalOverride local &&
if (oldLocation is SessionOverride local &&
(!removeFromOverride || local.Session == session))
{
return;
}
RegisterUpdate(index, new LocalOverride(session));
RegisterUpdate(index, new SessionOverride(session));
}
/// <summary>
@@ -460,7 +459,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
{
if (!removeFromOverride
&& TryGetLocation(index, out var oldLocation)
&& oldLocation is GlobalOverride or LocalOverride)
&& oldLocation is GlobalOverride or SessionOverride)
{
return;
}
@@ -510,7 +509,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
_indexLocations.TryGetValue(index, out var oldLocation);
//removeFromOverride is false 99% of the time.
if ((bufferedLocation ?? oldLocation) is GlobalOverride or LocalOverride && !removeFromOverride)
if ((bufferedLocation ?? oldLocation) is GlobalOverride or SessionOverride && !removeFromOverride)
return;
if (oldLocation is GridChunkLocation oldGrid &&
@@ -541,7 +540,7 @@ public sealed class PVSCollection<TIndex> : IPVSCollection where TIndex : ICompa
_indexLocations.TryGetValue(index, out var oldLocation);
//removeFromOverride is false 99% of the time.
if ((bufferedLocation ?? oldLocation) is GlobalOverride or LocalOverride && !removeFromOverride)
if ((bufferedLocation ?? oldLocation) is GlobalOverride or SessionOverride && !removeFromOverride)
return;
// Is this entity just returning to its old location?
@@ -641,14 +640,14 @@ public struct GlobalOverride : IIndexLocation
}
}
public struct LocalOverride : IIndexLocation
public struct SessionOverride : IIndexLocation
{
public LocalOverride(ICommonSession session)
public SessionOverride(ICommonSession session)
{
Session = session;
}
public ICommonSession Session { get; init; }
public readonly ICommonSession Session;
}
#endregion

View File

@@ -22,13 +22,14 @@ public sealed class PvsOverrideSystem : EntitySystem
}
/// <summary>
/// Used to ensure that an entity is always sent to a specific client. Overrides any global or pre-existing
/// client-specific overrides.
/// Used to ensure that an entity is always sent to a specific client. By default this overrides any global or pre-existing
/// client-specific overrides. Unlike global overrides, this is always recursive.
/// </summary>
/// <param name="removeExistingOverride">Whether or not to supersede existing overrides.</param>
public void AddSessionOverride(EntityUid uid, ICommonSession session,bool removeExistingOverride = true)
/// <param name="recursive">If true, this will also recursively send any children of the given index.</param>
public void AddSessionOverride(EntityUid uid, ICommonSession session, bool removeExistingOverride = true)
{
_pvs.EntityPVSCollection.UpdateIndex(GetNetEntity(uid), session, removeExistingOverride);
_pvs.EntityPVSCollection.AddSessionOverride(GetNetEntity(uid), session, removeExistingOverride);
}
/// <summary>

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;
@@ -196,6 +188,7 @@ internal sealed partial class PvsSystem : EntitySystem
Log.Warning(sb.ToString());
sessionData.LastSeenAt.Clear();
sessionData.LastLeftView.Clear();
if (sessionData.Overflow != null)
{
@@ -219,11 +212,6 @@ internal sealed partial class PvsSystem : EntitySystem
_viewSize = obj * 2;
}
private void OnForceAckChanged(int value)
{
ForceAckThreshold = value;
}
private void SetPvs(bool value)
{
_seenAllEnts.Clear();
@@ -269,6 +257,7 @@ internal sealed partial class PvsSystem : EntitySystem
foreach (var sessionData in PlayerData.Values)
{
sessionData.LastSeenAt.Remove(metadata.NetEntity);
sessionData.LastLeftView.Remove(metadata.NetEntity);
if (sessionData.SentEntities.TryGetValue(previousTick, out var ents))
ents.Remove(metadata.NetEntity);
}
@@ -663,16 +652,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 +671,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 +680,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)
@@ -720,6 +705,7 @@ internal sealed partial class PvsSystem : EntitySystem
sessionData.SentEntities.TryGetValue(toTick - 1, out var lastSent);
var lastAcked = sessionData.LastAcked?.Data;
var lastSeen = sessionData.LastSeenAt;
var lastLeft = sessionData.LastLeftView;
var visibleEnts = _visSetPool.Get();
if (visibleEnts.Count != 0)
@@ -747,7 +733,7 @@ internal sealed partial class PvsSystem : EntitySystem
foreach (var rootNode in cache.Value.tree.RootNodes)
{
RecursivelyAddTreeNode(in rootNode, cache.Value.tree, lastAcked, lastSent, visibleEnts, lastSeen, cache.Value.metadata, stack, in fromTick,
RecursivelyAddTreeNode(in rootNode, cache.Value.tree, lastAcked, lastSent, visibleEnts, lastSeen, lastLeft, cache.Value.metadata, stack, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
}
}
@@ -758,7 +744,7 @@ internal sealed partial class PvsSystem : EntitySystem
{
var netEntity = globalEnumerator.Current;
var uid = GetEntity(netEntity);
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in fromTick,
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, lastLeft, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
}
globalEnumerator.Dispose();
@@ -768,39 +754,45 @@ internal sealed partial class PvsSystem : EntitySystem
{
var netEntity = globalRecursiveEnumerator.Current;
var uid = GetEntity(netEntity);
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, in fromTick,
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, lastLeft, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true);
}
globalRecursiveEnumerator.Dispose();
var localEnumerator = _entityPvsCollection.GetElementsForSession(session);
while (localEnumerator.MoveNext())
var sessionOverrides = _entityPvsCollection.GetSessionOverrides(session);
while (sessionOverrides.MoveNext())
{
var netEntity = localEnumerator.Current;
var netEntity = sessionOverrides.Current;
var uid = GetEntity(netEntity);
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen,in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
RecursivelyAddOverride(in uid, lastAcked, lastSent, visibleEnts, lastSeen, lastLeft, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true);
}
localEnumerator.Dispose();
sessionOverrides.Dispose();
foreach (var viewerEntity in viewers)
{
RecursivelyAddOverride(in viewerEntity, lastAcked, lastSent, visibleEnts, lastSeen, in fromTick,
RecursivelyAddOverride(in viewerEntity, lastAcked, lastSent, visibleEnts, lastSeen, lastLeft, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
}
var expandEvent = new ExpandPvsEvent(session);
RaiseLocalEvent(ref expandEvent);
foreach (var entityUid in expandEvent.Entities)
if (expandEvent.Entities != null)
{
RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
foreach (var entityUid in expandEvent.Entities)
{
RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen, lastLeft, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
}
}
foreach (var entityUid in expandEvent.RecursiveEntities)
if (expandEvent.RecursiveEntities != null)
{
RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen,in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true);
foreach (var entityUid in expandEvent.RecursiveEntities)
{
RecursivelyAddOverride(in entityUid, lastAcked, lastSent, visibleEnts, lastSeen, lastLeft, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget, true);
}
}
var entityStates = new List<EntityState>(entStateCount);
@@ -837,7 +829,7 @@ internal sealed partial class PvsSystem : EntitySystem
}
// tell a client to detach entities that have left their view
var leftView = ProcessLeavePVS(visibleEnts, lastSent);
var leftView = ProcessLeavePvs(visibleEnts, lastSent, lastLeft);
if (sessionData.SentEntities.Add(toTick, visibleEnts, out var oldEntry))
{
@@ -874,18 +866,26 @@ internal sealed partial class PvsSystem : EntitySystem
/// Figure out what entities are no longer visible to the client. These entities are sent reliably to the client
/// in a separate net message.
/// </summary>
private List<NetEntity>? ProcessLeavePVS(
private List<NetEntity>? ProcessLeavePvs(
Dictionary<NetEntity, PvsEntityVisibility> visibleEnts,
Dictionary<NetEntity, PvsEntityVisibility>? lastSent)
Dictionary<NetEntity, PvsEntityVisibility>? lastSent,
Dictionary<NetEntity, GameTick> lastLeft)
{
if (lastSent == null)
return null;
var leftView = new List<NetEntity>();
var tick = _gameTiming.CurTick;
var minSize = Math.Max(0, lastSent.Count - lastSent.Count);
var leftView = new List<NetEntity>(minSize);
foreach (var netEntity in lastSent.Keys)
{
if (!visibleEnts.ContainsKey(netEntity))
{
leftView.Add(netEntity);
lastLeft[netEntity] = tick;
}
}
return leftView.Count > 0 ? leftView : null;
@@ -897,6 +897,7 @@ internal sealed partial class PvsSystem : EntitySystem
Dictionary<NetEntity, PvsEntityVisibility>? lastSent,
Dictionary<NetEntity, PvsEntityVisibility> toSend,
Dictionary<NetEntity, GameTick> lastSeen,
Dictionary<NetEntity, GameTick> lastLeft,
Dictionary<NetEntity, MetaDataComponent> metaDataCache,
Stack<NetEntity> stack,
in GameTick fromTick,
@@ -919,7 +920,7 @@ internal sealed partial class PvsSystem : EntitySystem
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(toSend, currentNodeIndex, out var exists);
if (!exists)
{
var (entered, shouldAdd) = ProcessEntry(in currentNodeIndex, lastAcked, lastSent, lastSeen,
var (entered, shouldAdd) = ProcessEntry(in currentNodeIndex, lastAcked, lastSent, lastSeen, lastLeft, fromTick,
ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
if (!shouldAdd)
@@ -949,6 +950,7 @@ internal sealed partial class PvsSystem : EntitySystem
Dictionary<NetEntity, PvsEntityVisibility>? lastSent,
Dictionary<NetEntity, PvsEntityVisibility> toSend,
Dictionary<NetEntity, GameTick> lastSeen,
Dictionary<NetEntity, GameTick> lastLeft,
in GameTick fromTick,
ref int newEntityCount,
ref int enteredEntityCount,
@@ -964,7 +966,7 @@ internal sealed partial class PvsSystem : EntitySystem
var xform = _xformQuery.GetComponent(uid);
var parent = xform.ParentUid;
if (parent.IsValid() && !RecursivelyAddOverride(in parent, lastAcked, lastSent, toSend, lastSeen, in fromTick,
if (parent.IsValid() && !RecursivelyAddOverride(in parent, lastAcked, lastSent, toSend, lastSeen, lastLeft, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget,
in enteredEntityBudget))
{
@@ -974,7 +976,6 @@ internal sealed partial class PvsSystem : EntitySystem
var metadata = _metaQuery.GetComponent(uid);
var netEntity = metadata.NetEntity;
//did we already get added?
// Note that we check this AFTER adding parents. This is because while this entity may already have been added
// 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
@@ -982,17 +983,13 @@ internal sealed partial class PvsSystem : EntitySystem
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);
var (entered, _) = ProcessEntry(in netEntity, lastAcked, lastSent, lastSeen, lastLeft, fromTick, ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
AddToSendSet(in netEntity, metadata, ref value, toSend, fromTick, in entered, ref entStateCount);
}
if (addChildren)
{
RecursivelyAddChildren(xform, lastAcked, lastSent, toSend, lastSeen, fromTick, ref newEntityCount,
RecursivelyAddChildren(xform, lastAcked, lastSent, toSend, lastSeen, lastLeft, fromTick, ref newEntityCount,
ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
}
@@ -1004,6 +1001,7 @@ internal sealed partial class PvsSystem : EntitySystem
Dictionary<NetEntity, PvsEntityVisibility>? lastSent,
Dictionary<NetEntity, PvsEntityVisibility> toSend,
Dictionary<NetEntity, GameTick> lastSeen,
Dictionary<NetEntity, GameTick> lastLeft,
in GameTick fromTick,
ref int newEntityCount,
ref int enteredEntityCount,
@@ -1022,22 +1020,23 @@ internal sealed partial class PvsSystem : EntitySystem
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(toSend, childNetEntity, out var exists);
if (!exists)
{
var (entered, _) = ProcessEntry(in childNetEntity, lastAcked, lastSent, lastSeen, ref newEntityCount,
var (entered, _) = ProcessEntry(in childNetEntity, lastAcked, lastSent, lastSeen, lastLeft, fromTick, ref newEntityCount,
ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
AddToSendSet(in childNetEntity, metadata, ref value, toSend, fromTick, in entered, ref entStateCount);
}
RecursivelyAddChildren(childXform, lastAcked, lastSent, toSend, lastSeen, fromTick, ref newEntityCount,
RecursivelyAddChildren(childXform, lastAcked, lastSent, toSend, lastSeen, lastLeft, fromTick, ref newEntityCount,
ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
}
}
private (bool Entered, bool ShouldAdd) ProcessEntry(
in NetEntity netEntity,
private (bool Entered, bool ShouldAdd) ProcessEntry(in NetEntity netEntity,
Dictionary<NetEntity, PvsEntityVisibility>? lastAcked,
Dictionary<NetEntity, PvsEntityVisibility>? lastSent,
Dictionary<NetEntity, GameTick> lastSeen,
Dictionary<NetEntity, GameTick> lastLeft,
GameTick fromTick,
ref int newEntityCount,
ref int enteredEntityCount,
in int newEntityBudget,
@@ -1045,16 +1044,15 @@ internal sealed partial class PvsSystem : EntitySystem
{
var enteredSinceLastSent = lastSent == null || !lastSent.ContainsKey(netEntity);
var entered = enteredSinceLastSent || // OR, entered since last ack:
lastAcked == null || !lastAcked.ContainsKey(netEntity);
var entered = enteredSinceLastSent
|| lastAcked == null
|| !lastAcked.ContainsKey(netEntity) // entered since last acked
|| lastLeft.GetValueOrDefault(netEntity) >= fromTick; // Just in case a packet was lost. I love dictionary lookups
// If the entity is entering, but we already sent this entering entity in the last message, we won't add it to
// the budget. Chances are the packet will arrive in a nice and orderly fashion, and the client will stick to
// their requested budget. However this can cause issues if a packet gets dropped, because a player may create
// 2x or more times the normal entity creation budget.
//
// The fix for that would be to just also give the PVS budget a client-side aspect that controls entity creation
// rate.
if (enteredSinceLastSent)
{
if (newEntityCount >= newEntityBudget || enteredEntityCount >= enteredEntityBudget)
@@ -1385,6 +1383,11 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
/// </summary>
public readonly Dictionary<NetEntity, GameTick> LastSeenAt = new();
/// <summary>
/// Tick at which an entity last left a player's PVS view.
/// </summary>
public readonly Dictionary<NetEntity, GameTick> LastLeftView = new();
/// <summary>
/// <see cref="SentEntities"/> overflow in case a player's last ack is more than <see cref="DirtyBufferSize"/> ticks behind the current tick.
/// </summary>
@@ -1407,20 +1410,20 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
}
[ByRefEvent]
public readonly struct ExpandPvsEvent
public struct ExpandPvsEvent
{
public readonly IPlayerSession Session;
/// <summary>
/// List of entities that will get added to this session's PVS set.
/// </summary>
public readonly List<EntityUid> Entities = new();
public List<EntityUid>? Entities;
/// <summary>
/// List of entities that will get added to this session's PVS set. Unlike <see cref="Entities"/> this will also
/// recursively add all children of the given entity.
/// </summary>
public readonly List<EntityUid> RecursiveEntities = new();
public List<EntityUid>? RecursiveEntities;
public ExpandPvsEvent(IPlayerSession session)
{

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

@@ -0,0 +1,12 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Spawners;
namespace Robust.Server.Spawners;
public sealed class TimedDespawnSystem : SharedTimedDespawnSystem
{
protected override bool CanDelete(EntityUid uid)
{
return true;
}
}

View File

@@ -94,7 +94,7 @@ namespace Robust.Server.ViewVariables
}
if (value is EntityUid uid)
return IoCManager.Resolve<IEntityManager>().GetNetEntity(uid);
return IoCManager.Resolve<IEntityManager>().GetComponentOrNull<MetaDataComponent>(uid)?.NetEntity ?? NetEntity.Invalid;
var valType = value.GetType();
if (!valType.IsValueType)

View File

@@ -6,6 +6,7 @@ using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using static Microsoft.CodeAnalysis.SymbolDisplayFormat;
namespace Robust.Shared.CompNetworkGenerator
{
@@ -14,20 +15,31 @@ namespace Robust.Shared.CompNetworkGenerator
{
private const string ClassAttributeName = "Robust.Shared.Analyzers.AutoGenerateComponentStateAttribute";
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 const string GlobalDictionaryName = "global::System.Collections.Generic.Dictionary<TKey, TValue>";
private const string GlobalHashSetName = "global::System.Collections.Generic.HashSet<T>";
private const string GlobalListName = "global::System.Collections.Generic.List<T>";
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";
var members = classSymbol.GetMembers();
var fields = new List<(ITypeSymbol Type, string FieldName, AttributeData Attribute)>();
var fields = new List<(ITypeSymbol Type, string FieldName)>();
var fieldAttr = comp.GetTypeByMetadataName(MemberAttributeName);
foreach (var mem in members)
@@ -44,7 +56,7 @@ namespace Robust.Shared.CompNetworkGenerator
switch (mem)
{
case IFieldSymbol field:
fields.Add((field.Type, field.Name, attribute));
fields.Add((field.Type, field.Name));
break;
case IPropertySymbol prop:
{
@@ -80,7 +92,7 @@ namespace Robust.Shared.CompNetworkGenerator
continue;
}
fields.Add((prop.Type, prop.Name, attribute));
fields.Add((prop.Type, prop.Name));
break;
}
}
@@ -118,9 +130,9 @@ namespace Robust.Shared.CompNetworkGenerator
// component.Count = state.Count;
var handleStateSetters = new StringBuilder();
foreach (var (type, name, attribute) in fields)
foreach (var (type, name) in fields)
{
var typeDisplayStr = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var typeDisplayStr = type.ToDisplayString(FullyQualifiedFormat);
var nullable = type.NullableAnnotation == NullableAnnotation.Annotated;
var nullableAnnotation = nullable ? "?" : string.Empty;
@@ -147,12 +159,32 @@ namespace Robust.Shared.CompNetworkGenerator
handleStateSetters.Append($@"
component.{name} = EnsureCoordinates<{componentName}>(state.{name}, uid);");
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;
default:
stateFields.Append($@"
public {typeDisplayStr} {name} = default!;");
if (attribute.ConstructorArguments[0].Value is bool val && val)
if (IsCloneType(type))
{
// get first ctor arg of the field attribute, which determines whether the field should be cloned
// (like if its a dict or list)
@@ -160,7 +192,9 @@ namespace Robust.Shared.CompNetworkGenerator
{name} = component.{name},");
handleStateSetters.Append($@"
if (state.{name} != null)
if (state.{name} == null)
component.{name} = null;
else
component.{name} = new(state.{name});");
}
else
@@ -330,5 +364,20 @@ public partial class {componentName}
}
context.RegisterForSyntaxNotifications(() => new NameReferenceSyntaxReceiver());
}
private static bool IsCloneType(ITypeSymbol type)
{
if (type is not INamedTypeSymbol named || !named.IsGenericType)
{
return false;
}
var constructed = named.ConstructedFrom.ToDisplayString(FullyQualifiedFormat);
return constructed switch
{
GlobalDictionaryName or GlobalHashSetName or GlobalListName => true,
_ => false
};
}
}
}

View File

@@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>9</LangVersion>
</PropertyGroup>
<ItemGroup>

View File

@@ -32,20 +32,6 @@ public sealed class AutoGenerateComponentStateAttribute : Attribute
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class AutoNetworkedFieldAttribute : Attribute
{
/// <summary>
/// Determines whether the data should be wrapped in a new() when setting in get/handlestate
/// e.g. for cloning collections like dictionaries or hashsets which is sometimes necessary.
/// </summary>
/// <remarks>
/// This should only be true if the type actually has a constructor that takes in itself.
/// </remarks>
[UsedImplicitly]
public bool CloneData;
public AutoNetworkedFieldAttribute(bool cloneData=false)
{
CloneData = cloneData;
}
}
/// <summary>

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

@@ -1,191 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Utility;
namespace Robust.Shared.Containers
{
/// <summary>
/// Helper functions for the container system.
/// </summary>
[PublicAPI]
public static class ContainerHelpers
{
/// <summary>
/// Am I inside a container? Only checks the direct parent. To see if the entity, or any parent entity, is
/// inside a container, use <see cref="ContainerSystem.IsEntityOrParentInContainer"/>
/// </summary>
/// <param name="entity">Entity that might be inside a container.</param>
/// <returns>If the entity is inside of a container.</returns>
[Obsolete("Use ContainerSystem.IsEntityInContainer() instead")]
public static bool IsInContainer(this EntityUid entity,
IEntityManager? entMan = null)
{
IoCManager.Resolve(ref entMan);
return (entMan.GetComponent<MetaDataComponent>(entity).Flags & MetaDataFlags.InContainer) == MetaDataFlags.InContainer;
}
/// <summary>
/// Tries to find the container manager that this entity is inside (if any).
/// </summary>
/// <param name="entity">Entity that might be inside a container.</param>
/// <param name="manager">The container manager that this entity is inside of.</param>
/// <returns>If a container manager was found.</returns>
public static bool TryGetContainerMan(this EntityUid entity, [NotNullWhen(true)] out ContainerManagerComponent? manager, IEntityManager? entMan = null)
{
IoCManager.Resolve(ref entMan);
DebugTools.Assert(entMan.EntityExists(entity));
var parent = entMan.GetComponent<TransformComponent>(entity).ParentUid;
if (parent.IsValid() && TryGetManagerComp(parent, out manager, entMan) && manager.ContainsEntity(entity))
return true;
manager = default;
return false;
}
/// <summary>
/// Tries to find the container that this entity is inside (if any).
/// </summary>
/// <param name="entity">Entity that might be inside a container.</param>
/// <param name="container">The container that this entity is inside of.</param>
/// <returns>If a container was found.</returns>
[Obsolete("Use ContainerSystem.TryGetContainingContainer() instead")]
public static bool TryGetContainer(this EntityUid entity, [NotNullWhen(true)] out BaseContainer? container, IEntityManager? entMan = null)
{
IoCManager.Resolve(ref entMan);
DebugTools.Assert(entMan.EntityExists(entity));
if (TryGetContainerMan(entity, out var manager, entMan))
return manager.TryGetContainer(entity, out container);
container = default;
return false;
}
/// <summary>
/// Attempts to remove an entity from its container, if any.
/// <see cref="SharedContainerSystem.TryRemoveFromContainer"/>
/// </summary>
/// <param name="entity">Entity that might be inside a container.</param>
/// <param name="force">Whether to forcibly remove the entity from the container.</param>
/// <param name="wasInContainer">Whether the entity was actually inside a container or not.</param>
/// <returns>If the entity could be removed. Also returns false if it wasn't inside a container.</returns>
[Obsolete("Use SharedContainerSystem.TryRemoveFromContainer() instead")]
public static bool TryRemoveFromContainer(this EntityUid entity, bool force, out bool wasInContainer, IEntityManager? entMan = null)
{
return IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SharedContainerSystem>()
.TryRemoveFromContainer(entity, force, out wasInContainer);
}
/// <summary>
/// Attempts to remove an entity from its container, if any.
/// <see cref="SharedContainerSystem.TryRemoveFromContainer"/>
/// </summary>
/// <param name="entity">Entity that might be inside a container.</param>
/// <param name="force">Whether to forcibly remove the entity from the container.</param>
/// <returns>If the entity could be removed. Also returns false if it wasn't inside a container.</returns>
[Obsolete("Use SharedContainerSystem.TryRemoveFromContainer() instead")]
public static bool TryRemoveFromContainer(this EntityUid entity, bool force = false, IEntityManager? entMan = null)
{
return IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SharedContainerSystem>()
.TryRemoveFromContainer(entity, force);
}
/// <summary>
/// Attempts to remove all entities in a container.
/// <see cref="SharedContainerSystem.EmptyContainer"/>
/// </summary>
[Obsolete("Use SharedContainerSystem.EmptyContainer() instead")]
public static void EmptyContainer(this BaseContainer container, bool force = false, EntityCoordinates? moveTo = null,
bool attachToGridOrMap = false, IEntityManager? entMan = null)
{
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SharedContainerSystem>()
.EmptyContainer(container, force, moveTo, attachToGridOrMap);
}
/// <summary>
/// Attempts to remove and delete all entities in a container.
/// <see cref="SharedContainerSystem.CleanContainer"/>
/// </summary>
[Obsolete("Use SharedContainerSystem.CleanContainer() instead")]
public static void CleanContainer(this BaseContainer container, IEntityManager? entMan = null)
{
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SharedContainerSystem>()
.CleanContainer(container);
}
/// <summary>
/// <see cref="SharedContainerSystem.AttachParentToContainerOrGrid"/>
/// </summary>
[Obsolete("Use SharedContainerSystem.AttachParentToContainerOrGrid() instead")]
public static void AttachParentToContainerOrGrid(this TransformComponent transform, IEntityManager? entMan = null)
{
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SharedContainerSystem>()
.AttachParentToContainerOrGrid(transform);
}
/// <summary>
/// <see cref="SharedContainerSystem.TryGetManagerComp"/>
/// </summary>
[Obsolete("Use SharedContainerSystem.TryGetManagerComp() instead")]
private static bool TryGetManagerComp(this EntityUid entity, [NotNullWhen(true)] out ContainerManagerComponent? manager, IEntityManager? entMan = null)
{
IoCManager.Resolve(ref entMan);
return entMan.System<SharedContainerSystem>()
.TryGetManagerComp(entity, out manager);
}
/// <summary>
/// Shortcut method to make creation of containers easier.
/// Creates a new container on the entity and gives it back to you.
/// </summary>
/// <param name="entity">The entity to create the container for.</param>
/// <param name="containerId"></param>
/// <returns>The new container.</returns>
/// <exception cref="ArgumentException">Thrown if there already is a container with the specified ID.</exception>
/// <seealso cref="BaseContainerManager.MakeContainer{T}(string)" />
[Obsolete("Use ContainerSystem.MakeContainer() instead")]
public static T CreateContainer<T>(this EntityUid entity, string containerId, IEntityManager? entMan = null)
where T : BaseContainer
{
IoCManager.Resolve(ref entMan);
var containermanager = entMan.EnsureComponent<ContainerManagerComponent>(entity);
return containermanager.MakeContainer<T>(containerId);
}
[Obsolete("Use ContainerSystem.EnsureContainer() instead")]
public static T EnsureContainer<T>(this EntityUid entity, string containerId, IEntityManager? entMan = null)
where T : BaseContainer
{
IoCManager.Resolve(ref entMan);
return EnsureContainer<T>(entity, containerId, out _, entMan);
}
[Obsolete("Use ContainerSystem.EnsureContainer() instead")]
public static T EnsureContainer<T>(this EntityUid entity, string containerId, out bool alreadyExisted, IEntityManager? entMan = null)
where T : BaseContainer
{
IoCManager.Resolve(ref entMan);
var containerManager = entMan.EnsureComponent<ContainerManagerComponent>(entity);
if (!containerManager.TryGetContainer(containerId, out var existing))
{
alreadyExisted = false;
return containerManager.MakeContainer<T>(containerId);
}
if (!(existing is T container))
{
throw new InvalidOperationException(
$"The container exists but is of a different type: {existing.GetType()}");
}
alreadyExisted = true;
return container;
}
}
}

View File

@@ -39,7 +39,7 @@ namespace Robust.Shared.Containers
}
/// <inheritdoc />
protected override void OnRemove()
protected internal override void OnRemove()
{
base.OnRemove();

View File

@@ -2,11 +2,8 @@ using System;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
@@ -36,7 +33,7 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
[ViewVariables]
public ComponentLifeStage LifeStage { get; private set; } = ComponentLifeStage.PreAdd;
public ComponentLifeStage LifeStage { get; internal set; } = ComponentLifeStage.PreAdd;
/// <summary>
/// If true, and if this is a networked component, then component data will only be sent to players if their
@@ -51,93 +48,6 @@ namespace Robust.Shared.GameObjects
/// </summary>
public virtual bool SessionSpecific => false;
/// <summary>
/// Increases the life stage from <see cref="ComponentLifeStage.PreAdd" /> to <see cref="ComponentLifeStage.Added" />,
/// after raising a <see cref="ComponentAdd"/> event.
/// </summary>
internal void LifeAddToEntity(IEntityManager entManager, CompIdx type)
{
DebugTools.Assert(LifeStage == ComponentLifeStage.PreAdd);
LifeStage = ComponentLifeStage.Adding;
CreationTick = entManager.CurrentTick;
// networked components are assumed to be dirty when added to entities. See also: ClearTicks()
LastModifiedTick = entManager.CurrentTick;
entManager.EventBus.RaiseComponentEvent(this, type, CompAddInstance);
LifeStage = ComponentLifeStage.Added;
}
/// <summary>
/// Increases the life stage from <see cref="ComponentLifeStage.Added" /> to <see cref="ComponentLifeStage.Initialized" />,
/// calling <see cref="Initialize" />.
/// </summary>
internal void LifeInitialize(IEntityManager entManager, CompIdx type)
{
DebugTools.Assert(LifeStage == ComponentLifeStage.Added);
LifeStage = ComponentLifeStage.Initializing;
entManager.EventBus.RaiseComponentEvent(this, type, CompInitInstance);
LifeStage = ComponentLifeStage.Initialized;
}
/// <summary>
/// Increases the life stage from <see cref="ComponentLifeStage.Initialized" /> to
/// <see cref="ComponentLifeStage.Running" />, calling <see cref="Startup" />.
/// </summary>
internal void LifeStartup(IEntityManager entManager)
{
DebugTools.Assert(LifeStage == ComponentLifeStage.Initialized);
LifeStage = ComponentLifeStage.Starting;
entManager.EventBus.RaiseComponentEvent(this, CompStartupInstance);
LifeStage = ComponentLifeStage.Running;
}
/// <summary>
/// Increases the life stage from <see cref="ComponentLifeStage.Running" /> to <see cref="ComponentLifeStage.Stopped" />,
/// calling <see cref="Shutdown" />.
/// </summary>
/// <remarks>
/// Components are allowed to remove themselves in their own Startup function.
/// </remarks>
internal void LifeShutdown(IEntityManager entManager)
{
DebugTools.Assert(LifeStage is >= ComponentLifeStage.Initializing and < ComponentLifeStage.Stopping);
if (LifeStage <= ComponentLifeStage.Initialized)
{
// Component was never started, no shutdown logic necessary. Simply mark it as stopped.
LifeStage = ComponentLifeStage.Stopped;
return;
}
LifeStage = ComponentLifeStage.Stopping;
entManager.EventBus.RaiseComponentEvent(this, CompShutdownInstance);
LifeStage = ComponentLifeStage.Stopped;
}
/// <summary>
/// Increases the life stage from <see cref="ComponentLifeStage.Stopped" /> to <see cref="ComponentLifeStage.Deleted" />,
/// calling <see cref="OnRemove" />.
/// </summary>
internal void LifeRemoveFromEntity(IEntityManager entManager)
{
// can be called at any time after PreAdd, including inside other life stage events.
DebugTools.Assert(LifeStage != ComponentLifeStage.PreAdd);
LifeStage = ComponentLifeStage.Removing;
entManager.EventBus.RaiseComponentEvent(this, CompRemoveInstance);
OnRemove();
#if DEBUG
if (LifeStage != ComponentLifeStage.Deleted)
{
DebugTools.Assert($"Component {this.GetType().Name} did not call base {nameof(OnRemove)} in derived method.");
}
#endif
}
/// <inheritdoc />
[ViewVariables]
public bool Initialized => LifeStage >= ComponentLifeStage.Initializing;
@@ -152,24 +62,18 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
[ViewVariables]
public GameTick CreationTick { get; private set; }
public GameTick CreationTick { get; internal set; }
/// <inheritdoc />
[ViewVariables]
public GameTick LastModifiedTick { get; internal set; }
private static readonly ComponentAdd CompAddInstance = new();
private static readonly ComponentInit CompInitInstance = new();
private static readonly ComponentStartup CompStartupInstance = new();
private static readonly ComponentShutdown CompShutdownInstance = new();
private static readonly ComponentRemove CompRemoveInstance = new();
/// <summary>
/// Called when the component is removed from an entity.
/// Shuts down the component.
/// The component has already been marked as deleted in the component manager.
/// </summary>
protected virtual void OnRemove()
protected internal virtual void OnRemove()
{
LifeStage = ComponentLifeStage.Deleted;
}

View File

@@ -1,75 +0,0 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.IoC;
using Robust.Shared.Log;
namespace Robust.Shared.GameObjects
{
[PublicAPI]
public static class ComponentExt
{
/// <summary>
/// Convenience wrapper to implement "create component if it does not already exist".
/// Always gives you back a component, and creates it if it does not exist yet.
/// </summary>
/// <param name="entity">The entity to fetch or create the component on.</param>
/// <typeparam name="T">The type of the component to fetch or create.</typeparam>
/// <returns>The existing component, or the new component if none existed yet.</returns>
[Obsolete]
public static T EnsureComponent<T>(this EntityUid entity) where T : Component, new()
{
var entMan = IoCManager.Resolve<IEntityManager>();
if (entMan.TryGetComponent(entity, out T? component))
{
return component;
}
return entMan.AddComponent<T>(entity);
}
/// <summary>
/// Convenience wrapper to implement "create component if it does not already exist".
/// Always gives you back a component, and creates it if it does not exist yet.
/// </summary>
/// <param name="entity">The entity to fetch or create the component on.</param>
/// <typeparam name="T">The type of the component to fetch or create.</typeparam>
/// <param name="warning">
/// The custom warning message to log if the component did not exist already.
/// Defaults to a predetermined warning if null.
/// </param>
/// <returns>The existing component, or the new component if none existed yet.</returns>
[Obsolete]
public static T EnsureComponentWarn<T>(this EntityUid entity, string? warning = null) where T : Component, new()
{
var entMan = IoCManager.Resolve<IEntityManager>();
if (entMan.TryGetComponent(entity, out T? component))
{
return component;
}
warning ??= $"Entity {entity} at {entMan.GetComponent<TransformComponent>(entity).MapPosition} did not have a {typeof(T)}";
Logger.Warning(warning);
return entMan.AddComponent<T>(entity);
}
[Obsolete]
public static IComponent SetAndDirtyIfChanged<TValue>(
this IComponent comp,
ref TValue backingField,
TValue value)
{
if (EqualityComparer<TValue>.Default.Equals(backingField, value))
{
return comp;
}
backingField = value;
comp.Dirty();
return comp;
}
}
}

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

@@ -85,8 +85,9 @@ namespace Robust.Shared.GameObjects
[ViewVariables]
public Angle PrevRotation { get; internal set; }
[ViewVariables(VVAccess.ReadWrite)]
internal bool ActivelyLerping { get; set; }
[ViewVariables] public bool ActivelyLerping;
[ViewVariables] public GameTick LastLerp = GameTick.Zero;
[ViewVariables] internal readonly HashSet<EntityUid> _children = new();
@@ -397,7 +398,8 @@ namespace Robust.Shared.GameObjects
[ViewVariables] public int ChildCount => _children.Count;
[ViewVariables] internal EntityUid LerpParent { get; set; }
[ViewVariables] public EntityUid LerpParent;
public bool PredictedLerp;
/// <summary>
/// Detaches this entity from its parent.

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;
@@ -121,7 +122,7 @@ namespace Robust.Shared.GameObjects
foreach (var comp in comps)
{
if (comp is { LifeStage: ComponentLifeStage.Added })
comp.LifeInitialize(this, CompIdx.Index(comp.GetType()));
LifeInitialize(comp, CompIdx.Index(comp.GetType()));
}
#if DEBUG
@@ -154,20 +155,20 @@ namespace Robust.Shared.GameObjects
// Init transform first, we always have it.
var transform = GetComponent<TransformComponent>(uid);
if (transform.LifeStage == ComponentLifeStage.Initialized)
transform.LifeStartup(this);
LifeStartup(transform);
// Init physics second if it exists.
if (TryGetComponent<PhysicsComponent>(uid, out var phys)
&& phys.LifeStage == ComponentLifeStage.Initialized)
{
phys.LifeStartup(this);
LifeStartup(phys);
}
// Do rest of components.
foreach (var comp in comps)
{
if (comp is { LifeStage: ComponentLifeStage.Initialized })
comp.LifeStartup(this);
LifeStartup(comp);
}
}
@@ -215,10 +216,10 @@ namespace Robust.Shared.GameObjects
return;
if (!Comp.Initialized)
Comp.LifeInitialize(_entMan, CompType);
((EntityManager) _entMan).LifeInitialize(Comp, CompType);
if (metadata.EntityInitialized && !Comp.Running)
Comp.LifeStartup(_entMan);
((EntityManager) _entMan).LifeStartup(Comp);
}
public static implicit operator T(CompInitializeHandle<T> handle)
@@ -228,6 +229,7 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
[Obsolete]
public CompInitializeHandle<T> AddComponentUninitialized<T>(EntityUid uid) where T : Component, new()
{
var reg = _componentFactory.GetRegistration<T>();
@@ -285,17 +287,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
@@ -315,7 +329,7 @@ namespace Robust.Shared.GameObjects
ComponentAdded?.Invoke(eventArgs);
_eventBus.OnComponentAdded(eventArgs);
component.LifeAddToEntity(this, reg.Idx);
LifeAddToEntity(component, reg.Idx);
if (skipInit)
return;
@@ -328,10 +342,10 @@ namespace Robust.Shared.GameObjects
if (component.Networked)
DirtyEntity(uid, metadata);
component.LifeInitialize(this, reg.Idx);
LifeInitialize(component, reg.Idx);
if (metadata.EntityInitialized)
component.LifeStartup(this);
LifeStartup(component);
if (metadata.EntityLifeStage >= EntityLifeStage.MapInitialized)
EventBus.RaiseComponentEvent(component, MapInitEventInstance);
@@ -502,7 +516,7 @@ namespace Robust.Shared.GameObjects
}
if (component.LifeStage >= ComponentLifeStage.Initialized && component.LifeStage <= ComponentLifeStage.Running)
component.LifeShutdown(this);
LifeShutdown(component);
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
@@ -534,10 +548,10 @@ namespace Robust.Shared.GameObjects
}
if (component.Running)
component.LifeShutdown(this);
LifeShutdown(component);
if (component.LifeStage != ComponentLifeStage.PreAdd)
component.LifeRemoveFromEntity(this); // Sets delete
LifeRemoveFromEntity(component); // Sets delete
#if EXCEPTION_TOLERANCE
}
@@ -568,11 +582,11 @@ namespace Robust.Shared.GameObjects
{
// TODO add options to cancel deferred deletion?
_sawmill.Warning($"Found a running component while culling deferred deletions, owner={ToPrettyString(uid)}, type={component.GetType()}");
component.LifeShutdown(this);
LifeShutdown(component);
}
if (component.LifeStage != ComponentLifeStage.PreAdd)
component.LifeRemoveFromEntity(this);
LifeRemoveFromEntity(component);
#if EXCEPTION_TOLERANCE
}

View File

@@ -0,0 +1,99 @@
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
public partial class EntityManager
{
private static readonly ComponentAdd CompAddInstance = new();
private static readonly ComponentInit CompInitInstance = new();
private static readonly ComponentStartup CompStartupInstance = new();
private static readonly ComponentShutdown CompShutdownInstance = new();
private static readonly ComponentRemove CompRemoveInstance = new();
/// <summary>
/// Increases the life stage from <see cref="ComponentLifeStage.PreAdd" /> to <see cref="ComponentLifeStage.Added" />,
/// after raising a <see cref="ComponentAdd"/> event.
/// </summary>
internal void LifeAddToEntity(Component component, CompIdx type)
{
DebugTools.Assert(component.LifeStage == ComponentLifeStage.PreAdd);
component.LifeStage = ComponentLifeStage.Adding;
component.CreationTick = CurrentTick;
// networked components are assumed to be dirty when added to entities. See also: ClearTicks()
component.LastModifiedTick = CurrentTick;
EventBus.RaiseComponentEvent(component, type, CompAddInstance);
component.LifeStage = ComponentLifeStage.Added;
}
/// <summary>
/// Increases the life stage from <see cref="ComponentLifeStage.Added" /> to <see cref="ComponentLifeStage.Initialized" />,
/// calling <see cref="Initialize" />.
/// </summary>
internal void LifeInitialize(Component component, CompIdx type)
{
DebugTools.Assert(component.LifeStage == ComponentLifeStage.Added);
component.LifeStage = ComponentLifeStage.Initializing;
EventBus.RaiseComponentEvent(component, type, CompInitInstance);
component.LifeStage = ComponentLifeStage.Initialized;
}
/// <summary>
/// Increases the life stage from <see cref="ComponentLifeStage.Initialized" /> to
/// <see cref="ComponentLifeStage.Running" />, calling <see cref="Startup" />.
/// </summary>
internal void LifeStartup(Component component)
{
DebugTools.Assert(component.LifeStage == ComponentLifeStage.Initialized);
component.LifeStage = ComponentLifeStage.Starting;
EventBus.RaiseComponentEvent(component, CompStartupInstance);
component.LifeStage = ComponentLifeStage.Running;
}
/// <summary>
/// Increases the life stage from <see cref="ComponentLifeStage.Running" /> to <see cref="ComponentLifeStage.Stopped" />,
/// calling <see cref="Shutdown" />.
/// </summary>
/// <remarks>
/// Components are allowed to remove themselves in their own Startup function.
/// </remarks>
internal void LifeShutdown(Component component)
{
DebugTools.Assert(component.LifeStage is >= ComponentLifeStage.Initializing and < ComponentLifeStage.Stopping);
if (component.LifeStage <= ComponentLifeStage.Initialized)
{
// Component was never started, no shutdown logic necessary. Simply mark it as stopped.
component.LifeStage = ComponentLifeStage.Stopped;
return;
}
component.LifeStage = ComponentLifeStage.Stopping;
EventBus.RaiseComponentEvent(component, CompShutdownInstance);
component.LifeStage = ComponentLifeStage.Stopped;
}
/// <summary>
/// Increases the life stage from <see cref="ComponentLifeStage.Stopped" /> to <see cref="ComponentLifeStage.Deleted" />,
/// calling <see cref="Component.OnRemove" />.
/// </summary>
internal void LifeRemoveFromEntity(Component component)
{
// can be called at any time after PreAdd, including inside other life stage events.
DebugTools.Assert(component.LifeStage != ComponentLifeStage.PreAdd);
component.LifeStage = ComponentLifeStage.Removing;
EventBus.RaiseComponentEvent(component, CompRemoveInstance);
component.OnRemove();
#if DEBUG
if (component.LifeStage != ComponentLifeStage.Deleted)
{
DebugTools.Assert($"Component {component.GetType().Name} did not call base {nameof(component.OnRemove)} in derived method.");
}
#endif
}
}

View File

@@ -68,7 +68,7 @@ public partial class EntityManager
return true;
}
entity = EntityUid.Invalid;
entity = null;
return false;
}
@@ -92,7 +92,7 @@ public partial class EntityManager
{
if (nEntity == null)
{
entity = EntityUid.Invalid;
entity = null;
return false;
}
@@ -104,7 +104,7 @@ public partial class EntityManager
{
if (uid == EntityUid.Invalid)
{
netEntity = NetEntity.Invalid;
netEntity = null;
return false;
}
@@ -125,7 +125,7 @@ public partial class EntityManager
{
if (uid == null)
{
netEntity = NetEntity.Invalid;
netEntity = null;
return false;
}

View File

@@ -311,19 +311,19 @@ namespace Robust.Shared.GameObjects
public EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null)
{
return CreateEntity(prototypeName, overrides);
return CreateEntity(prototypeName, out _, overrides);
}
/// <inheritdoc />
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, ComponentRegistry? overrides = null)
{
return CreateEntity(prototypeName, overrides);
return CreateEntity(prototypeName, out _, overrides);
}
/// <inheritdoc />
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
{
var newEntity = CreateEntity(prototypeName, overrides);
var newEntity = CreateEntity(prototypeName, out _, overrides);
_xforms.SetCoordinates(newEntity, TransformQuery.GetComponent(newEntity), coordinates, unanchor: false);
return newEntity;
}
@@ -331,7 +331,7 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
{
var newEntity = CreateEntity(prototypeName, overrides);
var newEntity = CreateEntity(prototypeName, out _, overrides);
var transform = TransformQuery.GetComponent(newEntity);
if (coordinates.MapId == MapId.Nullspace)
@@ -398,6 +398,7 @@ namespace Robust.Shared.GameObjects
if (component.LifeStage >= ComponentLifeStage.Removing || !component.NetSyncEnabled)
return;
DebugTools.AssertOwner(uid, component);
DirtyEntity(uid, meta);
component.LastModifiedTick = CurrentTick;
}
@@ -435,8 +436,13 @@ namespace Robust.Shared.GameObjects
// Notify all entities they are being terminated prior to detaching & deleting
RecursiveFlagEntityTermination(e, meta);
var xform = TransformQuery.GetComponent(e);
TransformComponent? parentXform = null;
if (xform.ParentUid.IsValid())
TransformQuery.Resolve(xform.ParentUid, ref parentXform);
// Then actually delete them
RecursiveDeleteEntity(e, meta);
RecursiveDeleteEntity(e, meta, xform, parentXform);
}
private void RecursiveFlagEntityTermination(
@@ -471,11 +477,14 @@ namespace Robust.Shared.GameObjects
private void RecursiveDeleteEntity(
EntityUid uid,
MetaDataComponent metadata)
MetaDataComponent metadata,
TransformComponent transform,
TransformComponent? parentXform)
{
DebugTools.Assert(transform.ParentUid.IsValid() == (parentXform != null));
DebugTools.Assert(parentXform == null || parentXform.ChildEntities.Contains(uid));
// 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 = 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).
@@ -483,7 +492,7 @@ namespace Robust.Shared.GameObjects
{
try
{
_xforms.DetachParentToNull(uid, transform);
_xforms.DetachParentToNull(uid, transform, parentXform);
}
catch (Exception e)
{
@@ -495,7 +504,10 @@ namespace Robust.Shared.GameObjects
{
try
{
RecursiveDeleteEntity(child, MetaQuery.GetComponent(child));
var childMeta = MetaQuery.GetComponent(child);
var childXform = TransformQuery.GetComponent(child);
DebugTools.AssertEqual(childXform.ParentUid, uid);
RecursiveDeleteEntity(child, childMeta, childXform, transform);
}
catch(Exception e)
{
@@ -513,7 +525,7 @@ namespace Robust.Shared.GameObjects
{
try
{
component.LifeShutdown(this);
LifeShutdown(component);
}
catch (Exception e)
{
@@ -538,7 +550,7 @@ namespace Robust.Shared.GameObjects
_eventBus.OnEntityDeleted(uid);
Entities.Remove(uid);
// Need to get the ID above before MetadataComponent shutdown but only remove it after everything else is done.
NetEntityLookup.Remove(netEntity);
NetEntityLookup.Remove(metadata.NetEntity);
}
public virtual void QueueDeleteEntity(EntityUid? uid)
@@ -656,23 +668,23 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Allocates an entity and loads components but does not do initialization.
/// </summary>
private protected virtual EntityUid CreateEntity(string? prototypeName, IEntityLoadContext? context = null)
private protected virtual EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
{
if (prototypeName == null)
return AllocEntity(out _);
return AllocEntity(out metadata);
if (!PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype))
throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}");
return CreateEntity(prototype, context);
return CreateEntity(prototype, out metadata, context);
}
/// <summary>
/// Allocates an entity and loads components but does not do initialization.
/// </summary>
private protected EntityUid CreateEntity(EntityPrototype prototype, IEntityLoadContext? context = null)
private protected EntityUid CreateEntity(EntityPrototype prototype, out MetaDataComponent metadata, IEntityLoadContext? context = null)
{
var entity = AllocEntity(prototype, out var metadata);
var entity = AllocEntity(prototype, out metadata);
try
{
EntityPrototype.LoadEntity(metadata.EntityPrototype, entity, ComponentFactory, this, _serManager, context);
@@ -740,36 +752,34 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
[return: NotNullIfNotNull("uid")]
public virtual EntityStringRepresentation? ToPrettyString(EntityUid? uid)
public EntityStringRepresentation? ToPrettyString(EntityUid? uid, MetaDataComponent? metadata = null)
{
// We want to retrieve the MetaData component even if it is deleted.
if (uid == null)
return null;
return uid == null ? null : ToPrettyString(uid.Value, metadata);
}
if (!_entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].TryGetValue(uid.Value, out var component))
return new EntityStringRepresentation(uid.Value, true);
/// <inheritdoc />
public virtual EntityStringRepresentation ToPrettyString(EntityUid uid, MetaDataComponent? metadata = null)
{
if (!MetaQuery.Resolve(uid, ref metadata, false))
return new EntityStringRepresentation(uid, true);
var metadata = (MetaDataComponent) component;
return ToPrettyString(uid.Value, metadata);
return new EntityStringRepresentation(uid, metadata.EntityDeleted, metadata.EntityName, metadata.EntityPrototype?.ID);
}
/// <inheritdoc />
[return: NotNullIfNotNull("netEntity")]
public EntityStringRepresentation? ToPrettyString(NetEntity? netEntity)
{
return ToPrettyString(GetEntity(netEntity));
return netEntity == null ? null : ToPrettyString(netEntity.Value);
}
public EntityStringRepresentation ToPrettyString(EntityUid uid)
=> ToPrettyString((EntityUid?) uid).Value;
/// <inheritdoc />
public EntityStringRepresentation ToPrettyString(NetEntity netEntity)
=> ToPrettyString((NetEntity?) netEntity).Value;
private EntityStringRepresentation ToPrettyString(EntityUid uid, MetaDataComponent metadata)
{
return new EntityStringRepresentation(uid, metadata.EntityDeleted, metadata.EntityName, metadata.EntityPrototype?.ID);
if (!TryGetEntityData(netEntity, out var uid, out var meta))
return new EntityStringRepresentation(EntityUid.Invalid, true);
return ToPrettyString(uid.Value, meta);
}
#endregion Entity Management

View File

@@ -399,9 +399,9 @@ public partial class EntitySystem
/// <inheritdoc cref="IEntityManager.ToPrettyString"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[return: NotNullIfNotNull("uid")]
protected EntityStringRepresentation? ToPrettyString(EntityUid? uid)
protected EntityStringRepresentation? ToPrettyString(EntityUid? uid, MetaDataComponent? metadata = null)
{
return EntityManager.ToPrettyString(uid);
return EntityManager.ToPrettyString(uid, metadata);
}
/// <inheritdoc cref="IEntityManager.ToPrettyString"/>
@@ -412,15 +412,20 @@ public partial class EntitySystem
return EntityManager.ToPrettyString(netEntity);
}
/// <inheritdoc cref="IEntityManager.ToPrettyString"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityStringRepresentation ToPrettyString(EntityUid uid, MetaDataComponent? metadata)
=> EntityManager.ToPrettyString(uid, metadata);
/// <inheritdoc cref="IEntityManager.ToPrettyString"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityStringRepresentation ToPrettyString(EntityUid uid)
=> ToPrettyString((EntityUid?) uid).Value;
=> EntityManager.ToPrettyString(uid);
/// <inheritdoc cref="IEntityManager.ToPrettyString"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityStringRepresentation ToPrettyString(NetEntity netEntity)
=> ToPrettyString((NetEntity?) netEntity).Value;
=> EntityManager.ToPrettyString(netEntity);
#endregion

View File

@@ -27,7 +27,7 @@ namespace Robust.Shared.GameObjects
var found = EntityManager.TryGetComponent(uid, out component);
if(logMissing && !found)
Log.Error($"Can't resolve \"{typeof(TComp)}\" on entity {uid}!\n{new StackTrace(1, true)}");
Log.Error($"Can't resolve \"{typeof(TComp)}\" on entity {ToPrettyString(uid)}!\n{new StackTrace(1, true)}");
return found;
}

View File

@@ -61,6 +61,7 @@ namespace Robust.Shared.GameObjects
/// <typeparam name="T">Concrete component type to add.</typeparam>
/// <param name="uid">Entity being modified.</param>
/// <returns>Component initialization handle. When you are done setting up the component, make sure to dispose this.</returns>
[Obsolete]
EntityManager.CompInitializeHandle<T> AddComponentUninitialized<T>(EntityUid uid) where T : Component, new();
/// <summary>
@@ -221,7 +222,7 @@ namespace Robust.Shared.GameObjects
/// <param name="uid">Entity to modify.</param>
/// <param name="component">The output component after being ensured.</param>
/// <typeparam name="T">Component to add.</typeparam>
/// <returns>The component in question</returns>
/// <returns>True if the component already existed</returns>
bool EnsureComponent<T>(EntityUid uid, out T component) where T : Component, new();
/// <summary>

View File

@@ -127,7 +127,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Returns a string representation of an entity with various information regarding it.
/// </summary>
EntityStringRepresentation ToPrettyString(EntityUid uid);
EntityStringRepresentation ToPrettyString(EntityUid uid, MetaDataComponent? metadata = null);
/// <summary>
/// Returns a string representation of an entity with various information regarding it.
@@ -138,7 +138,7 @@ namespace Robust.Shared.GameObjects
/// Returns a string representation of an entity with various information regarding it.
/// </summary>
[return: NotNullIfNotNull("uid")]
EntityStringRepresentation? ToPrettyString(EntityUid? uid);
EntityStringRepresentation? ToPrettyString(EntityUid? uid, MetaDataComponent? metadata = null);
/// <summary>
/// Returns a string representation of an entity with various information regarding it.

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

@@ -149,7 +149,7 @@ public abstract partial class SharedTransformSystem
/// </summary>
public bool ContainsEntity(TransformComponent xform, EntityUid entity)
{
return ContainsEntity(xform, entity, _xformQuery);
return ContainsEntity(xform, entity, XformQuery);
}
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
@@ -161,7 +161,7 @@ public abstract partial class SharedTransformSystem
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
public bool ContainsEntity(TransformComponent xform, TransformComponent entityTransform)
{
return ContainsEntity(xform, entityTransform, _xformQuery);
return ContainsEntity(xform, entityTransform, XformQuery);
}
/// <inheritdoc cref="ContainsEntity(Robust.Shared.GameObjects.TransformComponent,Robust.Shared.GameObjects.EntityUid)"/>
@@ -222,7 +222,7 @@ public abstract partial class SharedTransformSystem
if (!component._mapIdInitialized)
{
FindMapIdAndSet(uid, component, EntityManager, _xformQuery, _mapManager);
FindMapIdAndSet(uid, component, EntityManager, XformQuery, _mapManager);
component._mapIdInitialized = true;
}
@@ -232,7 +232,7 @@ public abstract partial class SharedTransformSystem
// Note that _children is a HashSet<EntityUid>,
// so duplicate additions (which will happen) don't matter.
var parentXform = _xformQuery.GetComponent(component.ParentUid);
var parentXform = XformQuery.GetComponent(component.ParentUid);
if (parentXform.LifeStage > ComponentLifeStage.Running || LifeStage(component.ParentUid) > EntityLifeStage.MapInitialized)
{
var msg = $"Attempted to re-parent to a terminating object. Entity: {ToPrettyString(component.ParentUid)}, new parent: {ToPrettyString(uid)}";
@@ -298,7 +298,7 @@ public abstract partial class SharedTransformSystem
if (!xform._parent.IsValid())
return;
var parentXform = _xformQuery.GetComponent(xform._parent);
var parentXform = XformQuery.GetComponent(xform._parent);
InitializeGridUid(xform._parent, parentXform);
xform._gridUid = parentXform._gridUid;
}
@@ -356,7 +356,7 @@ public abstract partial class SharedTransformSystem
DebugTools.Assert(!HasComp<MapGridComponent>(uid));
DebugTools.Assert(gridId == null || HasComp<MapGridComponent>(gridId));
xformQuery ??= _xformQuery;
xformQuery ??= XformQuery;
SetGridIdRecursive(uid, xform, gridId, xformQuery.Value);
}
@@ -379,27 +379,26 @@ public abstract partial class SharedTransformSystem
#region Local Position
public void SetLocalPosition(EntityUid uid, Vector2 value, TransformComponent? xform = null)
[Obsolete("use override with EntityUid")]
public void SetLocalPosition(TransformComponent xform, Vector2 value)
{
if (!Resolve(uid, ref xform)) return;
SetLocalPosition(xform, value);
SetLocalPosition(xform.Owner, value, xform);
}
public virtual void SetLocalPosition(TransformComponent xform, Vector2 value)
{
#pragma warning disable CS0618
xform.LocalPosition = value;
#pragma warning restore CS0618
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public virtual void SetLocalPosition(EntityUid uid, Vector2 value, TransformComponent? xform = null)
=> SetLocalPositionNoLerp(uid, value, xform);
[Obsolete("use override with EntityUid")]
public void SetLocalPositionNoLerp(TransformComponent xform, Vector2 value)
=> SetLocalPositionNoLerp(xform.Owner, value, xform);
public void SetLocalPositionNoLerp(EntityUid uid, Vector2 value, TransformComponent? xform = null)
{
if (!Resolve(uid, ref xform)) return;
SetLocalPositionNoLerp(xform, value);
}
if (!XformQuery.Resolve(uid, ref xform))
return;
public virtual void SetLocalPositionNoLerp(TransformComponent xform, Vector2 value)
{
#pragma warning disable CS0618
xform.LocalPosition = value;
#pragma warning restore CS0618
@@ -409,27 +408,24 @@ public abstract partial class SharedTransformSystem
#region Local Rotation
public void SetLocalRotationNoLerp(EntityUid uid, Angle angle)
public void SetLocalRotationNoLerp(EntityUid uid, Angle value, TransformComponent? xform = null)
{
SetLocalRotationNoLerp(_xformQuery.GetComponent(uid), angle);
}
if (!XformQuery.Resolve(uid, ref xform))
return;
public virtual void SetLocalRotationNoLerp(TransformComponent xform, Angle angle)
{
xform.LocalRotation = angle;
}
public void SetLocalRotation(EntityUid uid, Angle value, TransformComponent? xform = null)
{
if (!Resolve(uid, ref xform)) return;
SetLocalRotation(xform, value);
}
public virtual void SetLocalRotation(TransformComponent xform, Angle value)
{
xform.LocalRotation = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public virtual void SetLocalRotation(EntityUid uid, Angle value, TransformComponent? xform = null)
=> SetLocalRotationNoLerp(uid, value, xform);
[Obsolete("use override with EntityUid")]
public void SetLocalRotation(TransformComponent xform, Angle value)
{
SetLocalRotation(xform.Owner, value, xform);
}
#endregion
#region Coordinates
@@ -487,7 +483,7 @@ public abstract partial class SharedTransformSystem
if (value.EntityId.IsValid())
{
if (!_xformQuery.Resolve(value.EntityId, ref newParent, false))
if (!XformQuery.Resolve(value.EntityId, ref newParent, false))
{
DetachParentToNull(uid, xform);
if (_netMan.IsServer || IsClientSide(uid))
@@ -525,13 +521,13 @@ public abstract partial class SharedTransformSystem
}
recursiveUid = recursiveXform.ParentUid;
recursiveXform = _xformQuery.GetComponent(recursiveUid);
recursiveXform = XformQuery.GetComponent(recursiveUid);
}
}
}
if (xform._parent.IsValid())
_xformQuery.Resolve(xform._parent, ref oldParent);
XformQuery.Resolve(xform._parent, ref oldParent);
oldParent?._children.Remove(uid);
newParent?._children.Add(uid);
@@ -541,7 +537,7 @@ public abstract partial class SharedTransformSystem
if (newParent != null)
{
xform.ChangeMapId(newParent.MapID, _xformQuery);
xform.ChangeMapId(newParent.MapID, XformQuery);
if (!xform._gridInitialized)
InitializeGridUid(uid, xform);
@@ -554,11 +550,11 @@ public abstract partial class SharedTransformSystem
}
else
{
xform.ChangeMapId(MapId.Nullspace, _xformQuery);
xform.ChangeMapId(MapId.Nullspace, XformQuery);
if (!xform._gridInitialized)
InitializeGridUid(uid, xform);
else
SetGridId(uid, xform, null, _xformQuery);
SetGridId(uid, xform, null, XformQuery);
}
if (xform.Initialized)
@@ -593,7 +589,7 @@ public abstract partial class SharedTransformSystem
public void ReparentChildren(EntityUid oldUid, EntityUid uid)
{
ReparentChildren(oldUid, uid, _xformQuery);
ReparentChildren(oldUid, uid, XformQuery);
}
/// <summary>
@@ -620,29 +616,29 @@ public abstract partial class SharedTransformSystem
public TransformComponent? GetParent(EntityUid uid)
{
return GetParent(_xformQuery.GetComponent(uid));
return GetParent(XformQuery.GetComponent(uid));
}
public TransformComponent? GetParent(TransformComponent xform)
{
if (!xform.ParentUid.IsValid())
return null;
return _xformQuery.GetComponent(xform.ParentUid);
return XformQuery.GetComponent(xform.ParentUid);
}
public EntityUid GetParentUid(EntityUid uid)
{
return _xformQuery.GetComponent(uid).ParentUid;
return XformQuery.GetComponent(uid).ParentUid;
}
public void SetParent(EntityUid uid, EntityUid parent)
{
SetParent(uid, _xformQuery.GetComponent(uid), parent, _xformQuery);
SetParent(uid, XformQuery.GetComponent(uid), parent, XformQuery);
}
public void SetParent(EntityUid uid, TransformComponent xform, EntityUid parent, TransformComponent? parentXform = null)
{
SetParent(uid, xform, parent, _xformQuery, parentXform);
SetParent(uid, xform, parent, XformQuery, parentXform);
}
public void SetParent(EntityUid uid, TransformComponent xform, EntityUid parent, EntityQuery<TransformComponent> xformQuery, TransformComponent? parentXform = null)
@@ -671,9 +667,7 @@ public abstract partial class SharedTransformSystem
#endregion
#region States
public virtual void ActivateLerp(TransformComponent xform) { }
public virtual void DeactivateLerp(TransformComponent xform) { }
public virtual void ActivateLerp(EntityUid uid, TransformComponent xform) { }
internal void OnGetState(EntityUid uid, TransformComponent component, ref ComponentGetState args)
{
@@ -745,24 +739,18 @@ public abstract partial class SharedTransformSystem
RaiseLocalEvent(uid, ref ev, true);
}
xform.PrevPosition = newState.LocalPosition;
xform.PrevRotation = newState.Rotation;
xform._noLocalRotation = newState.NoLocalRotation;
DebugTools.Assert(xform.ParentUid == parent, "Transform state failed to set parent");
DebugTools.Assert(xform.Anchored == newState.Anchored, "Transform state failed to set anchored");
}
if (args.Next is TransformComponentState nextTransform)
if (args.Next is TransformComponentState nextTransform
&& nextTransform.ParentID == GetNetEntity(xform.ParentUid))
{
xform.NextPosition = nextTransform.LocalPosition;
xform.NextRotation = nextTransform.Rotation;
xform.LerpParent = GetEntity(nextTransform.ParentID);
ActivateLerp(xform);
}
else
{
DeactivateLerp(xform);
ActivateLerp(uid, xform);
}
}
@@ -773,7 +761,7 @@ public abstract partial class SharedTransformSystem
[Pure]
public Matrix3 GetWorldMatrix(EntityUid uid)
{
return GetWorldMatrix(_xformQuery.GetComponent(uid), _xformQuery);
return GetWorldMatrix(XformQuery.GetComponent(uid), XformQuery);
}
// Temporary until it's moved here
@@ -781,7 +769,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Matrix3 GetWorldMatrix(TransformComponent component)
{
return GetWorldMatrix(component, _xformQuery);
return GetWorldMatrix(component, XformQuery);
}
[Pure]
@@ -807,7 +795,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector2 GetWorldPosition(EntityUid uid)
{
return GetWorldPosition(_xformQuery.GetComponent(uid));
return GetWorldPosition(XformQuery.GetComponent(uid));
}
// Temporary until it's moved here
@@ -819,7 +807,7 @@ public abstract partial class SharedTransformSystem
while (component.ParentUid != component.MapUid && component.ParentUid.IsValid())
{
component = _xformQuery.GetComponent(component.ParentUid);
component = XformQuery.GetComponent(component.ParentUid);
pos = component._localRotation.RotateVec(pos) + component._localPosition;
}
@@ -842,7 +830,7 @@ public abstract partial class SharedTransformSystem
[Pure]
public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(EntityUid uid)
{
return GetWorldPositionRotation(_xformQuery.GetComponent(uid));
return GetWorldPositionRotation(XformQuery.GetComponent(uid));
}
[Pure]
@@ -853,7 +841,7 @@ public abstract partial class SharedTransformSystem
while (component.ParentUid != component.MapUid && component.ParentUid.IsValid())
{
component = _xformQuery.GetComponent(component.ParentUid);
component = XformQuery.GetComponent(component.ParentUid);
pos = component._localRotation.RotateVec(pos) + component._localPosition;
angle += component._localRotation;
}
@@ -947,7 +935,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetWorldPosition(TransformComponent component, Vector2 worldPos)
{
SetWorldPosition(component, worldPos, _xformQuery);
SetWorldPosition(component, worldPos, XformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -973,7 +961,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Angle GetWorldRotation(EntityUid uid)
{
return GetWorldRotation(_xformQuery.GetComponent(uid), _xformQuery);
return GetWorldRotation(XformQuery.GetComponent(uid), XformQuery);
}
// Temporary until it's moved here
@@ -981,7 +969,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Angle GetWorldRotation(TransformComponent component)
{
return GetWorldRotation(component, _xformQuery);
return GetWorldRotation(component, XformQuery);
}
[Pure]
@@ -1040,43 +1028,47 @@ public abstract partial class SharedTransformSystem
#region Set Position+Rotation
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetWorldPositionRotation(EntityUid uid, Vector2 worldPos, Angle worldRot, EntityQuery<TransformComponent> xformQuery)
{
var component = xformQuery.GetComponent(uid);
SetWorldPositionRotation(component, worldPos, worldRot, xformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete("Use override with EntityUid")]
public void SetWorldPositionRotation(TransformComponent component, Vector2 worldPos, Angle worldRot)
{
SetWorldPositionRotation(component, worldPos, worldRot, _xformQuery);
SetWorldPositionRotation(component.Owner, worldPos, worldRot, component);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetWorldPositionRotation(TransformComponent component, Vector2 worldPos, Angle worldRot, EntityQuery<TransformComponent> xformQuery)
public void SetWorldPositionRotation(EntityUid uid, Vector2 worldPos, Angle worldRot, TransformComponent? component = null)
{
if (!XformQuery.Resolve(uid, ref component))
return;
if (!component._parent.IsValid())
{
DebugTools.Assert("Parent is invalid while attempting to set WorldPosition - did you try to move root node?");
return;
}
var (curWorldPos, curWorldRot) = GetWorldPositionRotation(component, xformQuery);
var (curWorldPos, curWorldRot) = GetWorldPositionRotation(component);
var negativeParentWorldRot = component.LocalRotation - curWorldRot;
var newLocalPos = component.LocalPosition + negativeParentWorldRot.RotateVec(worldPos - curWorldPos);
var newLocalRot = component.LocalRotation + worldRot - curWorldRot;
SetLocalPositionRotation(component, newLocalPos, newLocalRot);
SetLocalPositionRotation(uid, newLocalPos, newLocalRot, component);
}
[Obsolete("Use override with EntityUid")]
public void SetLocalPositionRotation(TransformComponent xform, Vector2 pos, Angle rot)
=> SetLocalPositionRotation(xform.Owner, pos, rot, xform);
/// <summary>
/// Simultaneously set the position and rotation. This is better than setting individually, as it reduces the number of move events and matrix rebuilding operations.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public virtual void SetLocalPositionRotation(TransformComponent xform, Vector2 pos, Angle rot)
public virtual void SetLocalPositionRotation(EntityUid uid, Vector2 pos, Angle rot, TransformComponent? xform = null)
{
if (!XformQuery.Resolve(uid, ref xform))
return;
if (!xform._parent.IsValid())
{
DebugTools.Assert("Parent is invalid while attempting to set WorldPosition - did you try to move root node?");
@@ -1097,14 +1089,14 @@ public abstract partial class SharedTransformSystem
DebugTools.Assert(!xform.NoLocalRotation || xform.LocalRotation == 0);
Dirty(xform.Owner, xform);
Dirty(uid, xform);
xform.MatricesDirty = true;
if (!xform.Initialized)
return;
var moveEvent = new MoveEvent(xform.Owner, oldPosition, xform.Coordinates, oldRotation, rot, xform, _gameTiming.ApplyingState);
RaiseLocalEvent(xform.Owner, ref moveEvent, true);
var moveEvent = new MoveEvent(uid, oldPosition, xform.Coordinates, oldRotation, rot, xform, _gameTiming.ApplyingState);
RaiseLocalEvent(uid, ref moveEvent, true);
}
#endregion
@@ -1114,14 +1106,14 @@ public abstract partial class SharedTransformSystem
[Pure]
public Matrix3 GetInvWorldMatrix(EntityUid uid)
{
return GetInvWorldMatrix(_xformQuery.GetComponent(uid), _xformQuery);
return GetInvWorldMatrix(XformQuery.GetComponent(uid), XformQuery);
}
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Matrix3 GetInvWorldMatrix(TransformComponent component)
{
return GetInvWorldMatrix(component, _xformQuery);
return GetInvWorldMatrix(component, XformQuery);
}
[Pure]
@@ -1146,14 +1138,14 @@ public abstract partial class SharedTransformSystem
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix)
GetWorldPositionRotationMatrix(EntityUid uid)
{
return GetWorldPositionRotationMatrix(_xformQuery.GetComponent(uid), _xformQuery);
return GetWorldPositionRotationMatrix(XformQuery.GetComponent(uid), XformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix)
GetWorldPositionRotationMatrix(TransformComponent xform)
{
return GetWorldPositionRotationMatrix(xform, _xformQuery);
return GetWorldPositionRotationMatrix(xform, XformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1177,13 +1169,13 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 InvWorldMatrix) GetWorldPositionRotationInvMatrix(EntityUid uid)
{
return GetWorldPositionRotationInvMatrix(_xformQuery.GetComponent(uid));
return GetWorldPositionRotationInvMatrix(XformQuery.GetComponent(uid));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 InvWorldMatrix) GetWorldPositionRotationInvMatrix(TransformComponent xform)
{
return GetWorldPositionRotationInvMatrix(xform, _xformQuery);
return GetWorldPositionRotationInvMatrix(xform, XformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1207,14 +1199,14 @@ public abstract partial class SharedTransformSystem
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix, Matrix3 InvWorldMatrix)
GetWorldPositionRotationMatrixWithInv(EntityUid uid)
{
return GetWorldPositionRotationMatrixWithInv(_xformQuery.GetComponent(uid), _xformQuery);
return GetWorldPositionRotationMatrixWithInv(XformQuery.GetComponent(uid), XformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3 WorldMatrix, Matrix3 InvWorldMatrix)
GetWorldPositionRotationMatrixWithInv(TransformComponent xform)
{
return GetWorldPositionRotationMatrixWithInv(xform, _xformQuery);
return GetWorldPositionRotationMatrixWithInv(xform, XformQuery);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1237,8 +1229,8 @@ public abstract partial class SharedTransformSystem
#region AttachToGridOrMap
public void AttachToGridOrMap(EntityUid uid, TransformComponent? xform = null)
{
if (_xformQuery.Resolve(uid, ref xform))
AttachToGridOrMap(uid, xform, _xformQuery);
if (XformQuery.Resolve(uid, ref xform))
AttachToGridOrMap(uid, xform, XformQuery);
}
public void AttachToGridOrMap(EntityUid uid, TransformComponent xform, EntityQuery<TransformComponent> query)
@@ -1278,15 +1270,15 @@ public abstract partial class SharedTransformSystem
{
coordinates = null;
if (!_xformQuery.Resolve(uid, ref xform))
if (!XformQuery.Resolve(uid, ref xform))
return false;
if (!xform.ParentUid.IsValid())
return false;
EntityUid newParent;
var oldPos = GetWorldPosition(xform, _xformQuery);
if (_mapManager.TryFindGridAt(xform.MapID, oldPos, _xformQuery, out var gridUid, out _))
var oldPos = GetWorldPosition(xform, XformQuery);
if (_mapManager.TryFindGridAt(xform.MapID, oldPos, XformQuery, out var gridUid, out _))
{
newParent = gridUid;
}
@@ -1299,7 +1291,7 @@ public abstract partial class SharedTransformSystem
return false;
}
coordinates = new(newParent, GetInvWorldMatrix(newParent, _xformQuery).Transform(oldPos));
coordinates = new(newParent, GetInvWorldMatrix(newParent, XformQuery).Transform(oldPos));
return true;
}
#endregion
@@ -1308,7 +1300,7 @@ public abstract partial class SharedTransformSystem
public void DetachParentToNull(EntityUid uid, TransformComponent xform)
{
_xformQuery.TryGetComponent(xform.ParentUid, out var oldXform);
XformQuery.TryGetComponent(xform.ParentUid, out var oldXform);
DetachParentToNull(uid, xform, oldXform);
}
@@ -1369,7 +1361,7 @@ public abstract partial class SharedTransformSystem
{
if (LifeStage(uid) > EntityLifeStage.Initialized)
{
SetGridId(uid, component, uid, _xformQuery);
SetGridId(uid, component, uid, XformQuery);
return;
}
component._gridInitialized = true;
@@ -1383,10 +1375,10 @@ public abstract partial class SharedTransformSystem
public void PlaceNextToOrDrop(EntityUid uid, EntityUid target,
TransformComponent? xform = null, TransformComponent? targetXform = null)
{
if (!_xformQuery.Resolve(target, ref targetXform))
if (!XformQuery.Resolve(target, ref targetXform))
return;
if (!_xformQuery.Resolve(uid, ref xform))
if (!XformQuery.Resolve(uid, ref xform))
return;
var meta = _metaQuery.GetComponent(target);

View File

@@ -26,7 +26,7 @@ namespace Robust.Shared.GameObjects
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<MetaDataComponent> _metaQuery;
private EntityQuery<TransformComponent> _xformQuery;
protected EntityQuery<TransformComponent> XformQuery;
private readonly Queue<MoveEvent> _gridMoves = new();
private readonly Queue<MoveEvent> _otherMoves = new();
@@ -39,7 +39,7 @@ namespace Robust.Shared.GameObjects
_gridQuery = GetEntityQuery<MapGridComponent>();
_metaQuery = GetEntityQuery<MetaDataComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
XformQuery = GetEntityQuery<TransformComponent>();
SubscribeLocalEvent<TileChangedEvent>(MapManagerOnTileChanged);
SubscribeLocalEvent<TransformComponent, ComponentInit>(OnCompInit);
@@ -85,17 +85,17 @@ namespace Robust.Shared.GameObjects
if (!TryComp(gridId, out BroadphaseComponent? lookup) || !_mapManager.TryGetGrid(gridId, out var grid))
return;
if (!_xformQuery.TryGetComponent(gridId, out var gridXform))
if (!XformQuery.TryGetComponent(gridId, out var gridXform))
return;
if (!_xformQuery.TryGetComponent(gridXform.MapUid, out var mapTransform))
if (!XformQuery.TryGetComponent(gridXform.MapUid, out var mapTransform))
return;
var aabb = _lookup.GetLocalBounds(tileIndices, grid.TileSize);
foreach (var entity in _lookup.GetEntitiesIntersecting(lookup, aabb, LookupFlags.Uncontained | LookupFlags.Approximate))
{
if (!_xformQuery.TryGetComponent(entity, out var xform) || xform.ParentUid != gridId)
if (!XformQuery.TryGetComponent(entity, out var xform) || xform.ParentUid != gridId)
continue;
if (!aabb.Contains(xform.LocalPosition))
@@ -148,7 +148,7 @@ namespace Robust.Shared.GameObjects
public EntityCoordinates GetMoverCoordinates(EntityUid uid)
{
return GetMoverCoordinates(uid, _xformQuery.GetComponent(uid));
return GetMoverCoordinates(uid, XformQuery.GetComponent(uid));
}
public EntityCoordinates GetMoverCoordinates(EntityUid uid, TransformComponent xform)
@@ -168,11 +168,11 @@ namespace Robust.Shared.GameObjects
DebugTools.Assert(!_mapManager.IsGrid(uid) && !_mapManager.IsMap(uid));
// Not parented to grid so convert their pos back to the grid.
var worldPos = GetWorldPosition(xform, _xformQuery);
var worldPos = GetWorldPosition(xform, XformQuery);
return xform.GridUid == null
? new EntityCoordinates(xform.MapUid ?? xform.ParentUid, worldPos)
: new EntityCoordinates(xform.GridUid.Value, _xformQuery.GetComponent(xform.GridUid.Value).InvLocalMatrix.Transform(worldPos));
: new EntityCoordinates(xform.GridUid.Value, XformQuery.GetComponent(xform.GridUid.Value).InvLocalMatrix.Transform(worldPos));
}
public EntityCoordinates GetMoverCoordinates(EntityCoordinates coordinates, EntityQuery<TransformComponent> xformQuery)
@@ -191,7 +191,7 @@ namespace Robust.Shared.GameObjects
if (!parentUid.IsValid())
return coordinates;
var parentXform = _xformQuery.GetComponent(parentUid);
var parentXform = XformQuery.GetComponent(parentUid);
// GriddUid is only set after init.
if (!parentXform._gridInitialized)
@@ -209,11 +209,11 @@ namespace Robust.Shared.GameObjects
DebugTools.Assert(!_mapManager.IsGrid(parentUid) && !_mapManager.IsMap(parentUid));
// Not parented to grid so convert their pos back to the grid.
var worldPos = GetWorldMatrix(parentXform, _xformQuery).Transform(coordinates.Position);
var worldPos = GetWorldMatrix(parentXform, XformQuery).Transform(coordinates.Position);
return parentXform.GridUid == null
? new EntityCoordinates(mapId ?? parentUid, worldPos)
: new EntityCoordinates(parentXform.GridUid.Value, _xformQuery.GetComponent(parentXform.GridUid.Value).InvLocalMatrix.Transform(worldPos));
: new EntityCoordinates(parentXform.GridUid.Value, XformQuery.GetComponent(parentXform.GridUid.Value).InvLocalMatrix.Transform(worldPos));
}
/// <summary>
@@ -231,15 +231,15 @@ namespace Robust.Shared.GameObjects
// Is the entity directly parented to the grid?
if (xform.GridUid == xform.ParentUid)
return (xform.Coordinates, GetWorldRotation(xform, _xformQuery));
return (xform.Coordinates, GetWorldRotation(xform, XformQuery));
DebugTools.Assert(!_mapManager.IsGrid(uid) && !_mapManager.IsMap(uid));
var (pos, worldRot) = GetWorldPositionRotation(xform, _xformQuery);
var (pos, worldRot) = GetWorldPositionRotation(xform, XformQuery);
var coords = xform.GridUid == null
? new EntityCoordinates(xform.MapUid ?? xform.ParentUid, pos)
: new EntityCoordinates(xform.GridUid.Value, _xformQuery.GetComponent(xform.GridUid.Value).InvLocalMatrix.Transform(pos));
: new EntityCoordinates(xform.GridUid.Value, XformQuery.GetComponent(xform.GridUid.Value).InvLocalMatrix.Transform(pos));
return (coords, worldRot);
}

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,43 @@
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);
}
public override string ToString() => Id ?? String.Empty;
}

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

@@ -151,7 +151,9 @@ internal partial class MapManager
var fallbackParentEuid = GetMapEntityIdOrThrow(currentMapId);
EntityManager.GetComponent<TransformComponent>(gridEnt).AttachParent(fallbackParentEuid);
EntityManager.InitializeComponents(gridEnt);
var meta = EntityManager.GetComponent<MetaDataComponent>(gridEnt);
EntityManager.System<MetaDataSystem>().SetEntityName(gridEnt, $"grid", meta);
EntityManager.InitializeComponents(gridEnt, meta);
EntityManager.StartComponents(gridEnt);
return grid;
}

View File

@@ -236,8 +236,10 @@ internal partial class MapManager
var mapComp = EntityManager.AddComponent<MapComponent>(newEnt);
mapComp.MapId = actualId;
EntityManager.Dirty(mapComp);
EntityManager.InitializeComponents(newEnt);
var meta = EntityManager.GetComponent<MetaDataComponent>(newEnt);
EntityManager.System<MetaDataSystem>().SetEntityName(newEnt, $"map {actualId}", meta);
EntityManager.Dirty(newEnt, mapComp, meta);
EntityManager.InitializeComponents(newEnt, meta);
EntityManager.StartComponents(newEnt);
_sawmill.Debug($"Binding map {actualId} to entity {newEnt}");
}

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

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics.Joints;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
@@ -110,5 +109,29 @@ public abstract partial class SharedJointSystem
}
Dirty(uid, component);
#if DEBUG
if (component.Relay == null)
return;
if (TryComp(uid, out JointComponent? jointComp))
{
foreach (var joint in jointComp.Joints.Values)
{
DebugTools.AssertNotEqual(joint.BodyAUid, component.Relay);
DebugTools.AssertNotEqual(joint.BodyBUid, component.Relay);
}
}
if (TryComp(component.Relay, out JointComponent? relayJointComp))
{
foreach (var joint in relayJointComp.Joints.Values)
{
DebugTools.AssertNotEqual(joint.BodyAUid, uid);
DebugTools.AssertNotEqual(joint.BodyBUid, uid);
}
}
#endif
}
}

View File

@@ -140,6 +140,8 @@ public abstract partial class SharedJointSystem : EntitySystem
jointComponentA ??= EnsureComp<JointComponent>(aUid);
jointComponentB ??= EnsureComp<JointComponent>(bUid);
DebugTools.Assert(jointComponentA.Owner == aUid && jointComponentB.Owner == bUid);
DebugTools.AssertNotEqual(jointComponentA.Relay, bUid);
DebugTools.AssertNotEqual(jointComponentB.Relay, aUid);
var jointsA = jointComponentA.Joints;
var jointsB = jointComponentB.Joints;
@@ -190,10 +192,10 @@ public abstract partial class SharedJointSystem : EntitySystem
_physics.WakeBody(aUid, body: bodyA);
_physics.WakeBody(bUid, body: bodyB);
Dirty(bodyA);
Dirty(bodyB);
Dirty(jointComponentA);
Dirty(jointComponentB);
Dirty(aUid, bodyA);
Dirty(bUid, bodyB);
Dirty(aUid, jointComponentA);
Dirty(bUid, jointComponentB);
// Also flag these for checking juusssttt in case.
_dirtyJoints.Add(jointComponentA);

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

@@ -423,16 +423,19 @@ public abstract partial class SharedPhysicsSystem
var uidA = joint.BodyAUid;
var uidB = joint.BodyBUid;
DebugTools.AssertNotEqual(uidA, uidB);
if (jointQuery.TryGetComponent(uidA, out var jointCompA) &&
jointCompA.Relay != null)
{
DebugTools.AssertNotEqual(uidB, jointCompA.Relay.Value);
uidA = jointCompA.Relay.Value;
}
if (jointQuery.TryGetComponent(uidB, out var jointCompB) &&
jointCompB.Relay != null)
{
DebugTools.AssertNotEqual(uidA, jointCompB.Relay.Value);
uidB = jointCompB.Relay.Value;
}

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,21 @@ 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);
}
public override string ToString() => Id ?? string.Empty;
}

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,21 @@ 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);
}
public override string ToString() => Id ?? string.Empty;
}

View File

@@ -60,24 +60,18 @@ public partial class PrototypeManager
if (!TryGetFieldPrototype(field, out var proto, out var canBeNull, out var canBeEmpty))
return;
if (field.FieldType != typeof(string))
{
errors.Add($"Prototype id field failed validation. Field is not a string. Field: {field.Name} in {type.FullName}");
return;
}
if (!TryGetFieldValue(field, type, ref instance, errors, out var value))
return;
if (value == null)
var id = value?.ToString();
if (id == null)
{
if (!canBeNull)
errors.Add($"Prototype id field failed validation. Const/Static fields should not be null. Field: {field.Name} in {type.FullName}");
errors.Add($"Prototype id field failed validation. Fields should not be null. Field: {field.Name} in {type.FullName}");
return;
}
var id = (string) value;
if (string.IsNullOrWhiteSpace(id))
{
if (!canBeEmpty)
@@ -141,7 +135,6 @@ public partial class PrototypeManager
canBeNull = false;
canBeEmpty = false;
// Check for a [PrototypeId] attribute.
var attrib = field.GetCustomAttribute(typeof(ValidatePrototypeIdAttribute<>), false);
if (attrib != null)
{
@@ -149,16 +142,39 @@ public partial class PrototypeManager
return true;
}
// Next, check for a data field attribute.
if (!field.TryGetCustomAttribute(out DataFieldAttribute? dataField))
return false;
var fieldType = field.FieldType;
canBeEmpty = dataField.Required;
DebugTools.Assert(!field.IsStatic);
// Resolve nullable structs
if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
fieldType = fieldType.GetGenericArguments().Single();
canBeNull = true;
}
if (fieldType == typeof(EntProtoId))
{
proto = typeof(EntityPrototype);
return true;
}
if (fieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ProtoId<>))
{
proto = field.FieldType.GetGenericArguments().Single();
return true;
}
// As far as I know there is no way to check for the nullability of a string field, so we will assume that null
// values imply that the field itself is properly marked as nullable.
canBeNull = true;
if (dataField.CustomTypeSerializer == null)
return false;
// Check that this is a prototype id serializer
if (!dataField.CustomTypeSerializer.IsGenericType)
return false;
@@ -166,12 +182,6 @@ public partial class PrototypeManager
return false;
proto = dataField.CustomTypeSerializer.GetGenericArguments().First();
canBeEmpty = dataField.Required;
// We will assume null values imply that the field itself is marked as nullable.
// Unless someone can tell me how to figure out the nullability of a string field.
canBeNull = true;
return true;
}
}

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

@@ -1,14 +1,61 @@
using System;
namespace Robust.Shared.Serialization.Manager.Attributes
namespace Robust.Shared.Serialization.Manager.Attributes;
/// <summary>
/// Makes a type always be copied by reference when using it as the generic parameter in
/// <see cref="ISerializationManager.CopyTo"/> and <see cref="ISerializationManager.CreateCopy"/>.
/// This means that the source instance is returned directly.
/// This attribute is not inherited.
/// <remarks>
/// Note that when calling any of the generic <see cref="ISerializationManager.CopyTo{T}"/> and
/// <see cref="ISerializationManager.CreateCopy{T}"/> methods, this attribute will only be respected
/// if the generic parameter passed to the copying methods has this attribute.
/// For example, if a copy method is called with a generic parameter T that is not annotated with this attribute,
/// but the actual type of the source parameter is annotated with this attribute, it will not be copied by ref.
/// Conversely, if the generic parameter T is annotated with this attribute, but the actual type of the source
/// is an inheritor which is not annotated with this attribute, it will still be copied by ref.
/// If the generic parameter T is a type derived from another that is annotated with the attribute,
/// but it itself is not annotated with this attribute, source will not be copied by ref as this attribute
/// is not inherited.
/// <code>
/// public class A {}
///
/// [CopyByRef]
/// public class B : A {}
///
/// public class C : B {}
///
/// public class Copier(ISerializationManager manager)
/// {
/// var a = new A();
/// var b = new B();
/// var c = new C();
///
/// // false, not copied by ref
/// manager.CreateCopy(a) == a
///
/// // false, not copied by ref
/// manager.CreateCopy&lt;A&gt;(b) == b
///
/// // true, copied by ref
/// manager.CreateCopy(b) == b
///
/// // false, not copied by ref
/// manager.CreateCopy(c) == c
///
/// // true, copied by ref
/// manager.CreateCopy&lt;B&gt;(c) == c
/// }
/// </code>
/// </remarks>
/// </summary>
[AttributeUsage(
AttributeTargets.Class |
AttributeTargets.Struct |
AttributeTargets.Enum |
AttributeTargets.Interface,
Inherited = false)]
public sealed class CopyByRefAttribute : Attribute
{
[AttributeUsage(
AttributeTargets.Class |
AttributeTargets.Struct |
AttributeTargets.Enum |
AttributeTargets.Interface,
Inherited = false)]
public sealed class CopyByRefAttribute : Attribute
{
}
}

View File

@@ -0,0 +1,76 @@
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
using System.Collections.Generic;
using System.Linq;
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
[TypeSerializer]
public sealed class SortedSetSerializer<T> :
ITypeSerializer<SortedSet<T>, SequenceDataNode>,
ITypeCopyCreator<SortedSet<T>>
{
SortedSet<T> ITypeReader<SortedSet<T>, SequenceDataNode>.Read(ISerializationManager serializationManager,
SequenceDataNode node,
IDependencyCollection dependencies,
SerializationHookContext hookCtx,
ISerializationContext? context,
ISerializationManager.InstantiationDelegate<SortedSet<T>>? instanceProvider)
{
var set = instanceProvider != null ? instanceProvider() : new SortedSet<T>();
foreach (var dataNode in node.Sequence)
{
set.Add(serializationManager.Read<T>(dataNode, hookCtx, context));
}
return set;
}
ValidationNode ITypeValidator<SortedSet<T>, SequenceDataNode>.Validate(ISerializationManager serializationManager,
SequenceDataNode node, IDependencyCollection dependencies, ISerializationContext? context)
{
var list = new List<ValidationNode>();
foreach (var elem in node.Sequence)
{
list.Add(serializationManager.ValidateNode<T>(elem, context));
}
return new ValidatedSequenceNode(list);
}
public DataNode Write(ISerializationManager serializationManager, SortedSet<T> value,
IDependencyCollection dependencies, bool alwaysWrite = false,
ISerializationContext? context = null)
{
var sequence = new SequenceDataNode();
foreach (var elem in value)
{
sequence.Add(serializationManager.WriteValue(elem, alwaysWrite, context));
}
return sequence;
}
SortedSet<T> ITypeCopyCreator<SortedSet<T>>.CreateCopy(ISerializationManager serializationManager, SortedSet<T> source,
IDependencyCollection dependencies,
SerializationHookContext hookCtx,
ISerializationContext? context)
{
var target = new SortedSet<T>();
foreach (var val in source)
{
target.Add(serializationManager.CreateCopy(val, hookCtx, context));
}
return target;
}
}

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

@@ -0,0 +1,45 @@
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
namespace Robust.Shared.Spawners;
public abstract class SharedTimedDespawnSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
public override void Initialize()
{
base.Initialize();
UpdatesOutsidePrediction = true;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
// AAAAAAAAAAAAAAAAAAAAAAAAAAA
// Client both needs to predict this, but also can't properly handle prediction resetting.
if (!_timing.IsFirstTimePredicted)
return;
var query = EntityQueryEnumerator<TimedDespawnComponent>();
while (query.MoveNext(out var uid, out var comp))
{
comp.Lifetime -= frameTime;
if (!CanDelete(uid))
continue;
if (comp.Lifetime <= 0)
{
var ev = new TimedDespawnEvent();
RaiseLocalEvent(uid, ref ev);
QueueDel(uid);
}
}
}
protected abstract bool CanDelete(EntityUid uid);
}

View File

@@ -0,0 +1,20 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.Spawners;
/// <summary>
/// Put this component on something you would like to despawn after a certain amount of time
/// </summary>
/// <remarks>
/// NOT networked as we don't want clients predicting networked entity deletions.
/// </remarks>
[RegisterComponent]
public sealed partial class TimedDespawnComponent : Component
{
/// <summary>
/// How long the entity will exist before despawning
/// </summary>
[DataField("lifetime")]
public float Lifetime = 5f;
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.GameObjects;
namespace Robust.Shared.Spawners;
/// <summary>
/// Raised directed on an entity when its timed despawn is over.
/// </summary>
[ByRefEvent]
public readonly record struct TimedDespawnEvent;

View File

@@ -88,13 +88,16 @@ namespace Robust.Shared.Utility
[Conditional("DEBUG")]
[AssertionMethod]
public static void AssertOwner(EntityUid? uid, IComponent component)
public static void AssertOwner(EntityUid? uid, IComponent? component)
{
if (component == null)
return;
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.
// As long as components are just reference types, we could just get the component and check if the references are equal?
if (component.Owner != uid)
throw new DebugAssertException($"Entity {uid} is not the owner of the component. Component: {component.GetType().Name}");
}

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

@@ -22,6 +22,7 @@ using Robust.Server.ServerStatus;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
@@ -269,6 +270,7 @@ namespace Robust.UnitTesting
public ISharedPlayerManager PlayerMan { get; private set; } = default!;
public IGameTiming Timing { get; private set; } = default!;
public IMapManager MapMan { get; private set; } = default!;
public IConsoleHost ConsoleHost { get; private set; } = default!;
protected virtual void ResolveIoC(IDependencyCollection deps)
{
@@ -278,6 +280,7 @@ namespace Robust.UnitTesting
PlayerMan = deps.Resolve<ISharedPlayerManager>();
Timing = deps.Resolve<IGameTiming>();
MapMan = deps.Resolve<IMapManager>();
ConsoleHost = deps.Resolve<IConsoleHost>();
}
public T System<T>() where T : IEntitySystem
@@ -295,6 +298,11 @@ namespace Robust.UnitTesting
return EntMan.GetComponent<MetaDataComponent>(uid);
}
public async Task ExecuteCommand(string cmd)
{
await WaitPost(() => ConsoleHost.ExecuteCommand(cmd));
}
/// <summary>
/// Whether the instance is still alive.
/// "Alive" indicates that it is able to receive and process commands.

View File

@@ -128,6 +128,7 @@ namespace Robust.UnitTesting
// Required components for the engine to work
// Why are we still here? Just to suffer? Why can't we just use [RegisterComponent] magic?
// TODO End Suffering.
var compFactory = deps.Resolve<IComponentFactory>();
if (!compFactory.AllRegisteredTypes.Contains(typeof(EyeComponent)))
@@ -225,6 +226,11 @@ namespace Robust.UnitTesting
compFactory.RegisterClass<Gravity2DComponent>();
}
if (!compFactory.AllRegisteredTypes.Contains(typeof(ActorComponent)))
{
compFactory.RegisterClass<ActorComponent>();
}
// So by default EntityManager does its own EntitySystemManager initialize during Startup.
// We want to bypass this and load our own systems hence we will manually initialize it here.
entMan.Initialize();

View File

@@ -222,9 +222,9 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
Assert.That(entManager.GetComponent<TransformComponent>(containerEntity).ChildCount, Is.EqualTo(1));
Assert.That(entManager.GetComponent<TransformComponent>(containerEntity).ChildEntities.First(), Is.EqualTo(insertEntity));
result = insertEntity.TryGetContainerMan(out var resultContainerMan);
result = containerSys.TryGetContainingContainer(insertEntity, out var resultContainerMan);
Assert.That(result, Is.True);
Assert.That(resultContainerMan, Is.EqualTo(container.Manager));
Assert.That(resultContainerMan?.Manager, Is.EqualTo(container.Manager));
}
[Test]

View File

@@ -0,0 +1,214 @@
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using NUnit.Framework;
using Robust.Client.GameStates;
using Robust.Client.Timing;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Timing;
using cIPlayerManager = Robust.Client.Player.IPlayerManager;
using sIPlayerManager = Robust.Server.Player.IPlayerManager;
namespace Robust.UnitTesting.Server.GameStates;
public sealed class PvsReEntryTest : RobustIntegrationTest
{
#if DEBUG
/// <summary>
/// Checks that there are no issues when an entity enters, leaves, then enters pvs while the client drops packets.
/// </summary>
[Test]
public async Task TestLossyReEntry()
{
var server = StartServer();
var client = StartClient();
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
var mapMan = server.ResolveDependency<IMapManager>();
var sEntMan = server.ResolveDependency<IEntityManager>();
var confMan = server.ResolveDependency<IConfigurationManager>();
var sPlayerMan = server.ResolveDependency<sIPlayerManager>();
var xforms = sEntMan.System<SharedTransformSystem>();
var stateMan = (ClientGameStateManager) client.ResolveDependency<IClientGameStateManager>();
var cEntMan = client.ResolveDependency<IEntityManager>();
var netMan = client.ResolveDependency<IClientNetManager>();
var cPlayerMan = client.ResolveDependency<cIPlayerManager>();
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
client.Post(() => netMan.ClientConnect(null!, 0, null!));
server.Post(() => confMan.SetCVar(CVars.NetPVS, true));
for (int i = 0; i < 10; i++)
{
await server.WaitRunTicks(1);
await client.WaitRunTicks(1);
}
// Ensure client & server ticks are synced.
// Client runs 1 tick ahead
{
var sTick = (int)server.Timing.CurTick.Value;
var cTick = (int)client.Timing.CurTick.Value;
var delta = cTick - sTick;
if (delta > 1)
await server.WaitRunTicks(delta - 1);
else if (delta < 1)
await client.WaitRunTicks(1 - delta);
sTick = (int)server.Timing.CurTick.Value;
cTick = (int)client.Timing.CurTick.Value;
delta = cTick - sTick;
Assert.That(delta, Is.EqualTo(1));
}
// Set up map and spawn player
EntityUid map = default;
NetEntity player = default;
NetEntity entity = default;
EntityCoordinates coords = default;
await server.WaitPost(() =>
{
var mapId = mapMan.CreateMap();
map = mapMan.GetMapEntityId(mapId);
coords = new(map, default);
var playerUid = sEntMan.SpawnEntity(null, coords);
var entUid = sEntMan.SpawnEntity(null, coords);
entity = sEntMan.GetNetEntity(entUid);
player = sEntMan.GetNetEntity(playerUid);
// Attach player.
var session = (IPlayerSession) sPlayerMan.Sessions.First();
sEntMan.System<ActorSystem>().Attach(playerUid, session);
session.JoinGame();
});
for (int i = 0; i < 10; i++)
{
await server.WaitRunTicks(1);
await client.WaitRunTicks(1);
}
Assert.That(player, Is.Not.EqualTo(NetEntity.Invalid));
Assert.That(entity, Is.Not.EqualTo(NetEntity.Invalid));
// Check player got properly attached, and has received the other entity.
MetaDataComponent? meta = default!;
await client.WaitPost(() =>
{
Assert.That(cEntMan.TryGetEntityData(entity, out _, out meta));
Assert.That(cEntMan.TryGetEntity(player, out var cPlayerUid));
Assert.That(cPlayerMan.LocalPlayer?.ControlledEntity, Is.EqualTo(cPlayerUid));
Assert.That(meta!.Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.None));
Assert.That(stateMan.IsQueuedForDetach(entity), Is.False);
});
var lastDirty = meta.LastModifiedTick;
Assert.That(lastDirty, Is.GreaterThan(GameTick.Zero));
// Move the player outside of the entity's PVS range
// note that we move the PLAYER not the entity, as we don't want to dirty the entity.
var farAway = new EntityCoordinates(map, new Vector2(100, 100));
await server.WaitPost( () => xforms.SetCoordinates(sEntMan.GetEntity(player), farAway));
for (int i = 0; i < 10; i++)
{
await server.WaitRunTicks(1);
await client.WaitRunTicks(1);
}
// Client should have detached the entity to null space.
Assert.That(meta!.Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.Detached));
Assert.That(stateMan.IsQueuedForDetach(entity), Is.False);
// Move the player back into range
await server.WaitPost( () => xforms.SetCoordinates(sEntMan.GetEntity(player), coords));
for (int i = 0; i < 10; i++)
{
await server.WaitRunTicks(1);
await client.WaitRunTicks(1);
}
// Entity is back in pvs range
Assert.That(meta!.Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.None));
Assert.That(stateMan.IsQueuedForDetach(entity), Is.False);
// Oh no, the client is going through a tunnel!
stateMan.DropStates = true;
var timing = client.ResolveDependency<IClientGameTiming>();
var lastRealTick = timing.LastRealTick;
for (int i = 0; i < 5; i++)
{
await server.WaitRunTicks(1);
await client.WaitRunTicks(1);
}
// Even though the client is receiving no new states, it will still have applied some from the state buffer.
Assert.That(timing.LastRealTick, Is.GreaterThan(lastRealTick));
lastRealTick = timing.LastRealTick;
// Move the player outside of pvs range.
Assert.That(meta!.Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.None));
await server.WaitPost(() => xforms.SetCoordinates(sEntMan.GetEntity(player), farAway));
for (int i = 0; i < 5; i++)
{
await server.WaitRunTicks(1);
await client.WaitRunTicks(1);
}
// Client should have exhausted the buffer -- client has not been applying any states.
Assert.That(timing.LastRealTick, Is.EqualTo(lastRealTick));
Assert.That(meta!.Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.None));
// However pvs-leave messages are sent separately, and are sent reliably. So they are still being received.
// Though in a realistic scenario they should probably be delayed somewhat.
Assert.That(stateMan.IsQueuedForDetach(entity), Is.True);
// Move the entity back into range
await server.WaitPost( () => xforms.SetCoordinates(sEntMan.GetEntity(player), coords));
for (int i = 0; i < 5; i++)
{
await server.WaitRunTicks(1);
await client.WaitRunTicks(1);
}
// Still hasn't been applying states.
Assert.That(timing.LastRealTick, Is.EqualTo(lastRealTick));
Assert.That(meta!.Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.None));
// The pvs-leave message has not yet been processed, detaching is still queued.
Assert.That(stateMan.IsQueuedForDetach(entity), Is.True);
// Client clears the tunnel, starts receiving states again.
stateMan.DropStates = false;
for (int i = 0; i < 10; i++)
{
await server.WaitRunTicks(1);
await client.WaitRunTicks(1);
}
// Entity should be in PVS range, client should know about it:
Assert.That(timing.LastRealTick, Is.GreaterThan(lastRealTick));
Assert.That(meta!.Flags & MetaDataFlags.Detached, Is.EqualTo(MetaDataFlags.None));
Assert.That(stateMan.IsQueuedForDetach(entity), Is.False);
// The entity itself should not have been dirtied since creation -- only the player has been moving.
// If the test moves the entity instead of the player, then the test doesn't actually work.
Assert.That(meta.LastModifiedTick, Is.EqualTo(lastDirty));
}
#endif
}

View File

@@ -61,7 +61,6 @@ entities:
var compFactory = IoCManager.Resolve<IComponentFactory>();
compFactory.RegisterClass<MapDeserializeTestComponent>();
compFactory.RegisterClass<VisibilityComponent>();
compFactory.RegisterClass<ActorComponent>();
compFactory.RegisterClass<IgnoreUIRangeComponent>();
compFactory.GenerateNetIds();
IoCManager.Resolve<ISerializationManager>().Initialize();

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

View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
using NUnit.Framework;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
// ReSharper disable AccessToStaticMemberViaDerivedType
namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers;
[TestFixture]
[TestOf(typeof(SortedSetSerializer<>))]
public sealed class SortedSetSerializerTest : SerializationTest
{
[Test]
public void SerializationTest()
{
var list = new SortedSet<string> {"A", "B", "C"};
var node = Serialization.WriteValueAs<SequenceDataNode>(list);
Assert.That(node.Cast<ValueDataNode>(0).Value, Is.EqualTo("A"));
Assert.That(node.Cast<ValueDataNode>(1).Value, Is.EqualTo("B"));
Assert.That(node.Cast<ValueDataNode>(2).Value, Is.EqualTo("C"));
}
[Test]
public void DeserializationTest()
{
var list = new SortedSet<string> {"A", "B", "C"};
var node = new SequenceDataNode("A", "B", "C");
var deserializedList = Serialization.Read<SortedSet<string>>(node, notNullableOverride: true);
Assert.That(deserializedList, Is.EqualTo(list));
}
}

6
global.json Normal file
View File

@@ -0,0 +1,6 @@
{
"sdk": {
"version": "7.0.100",
"rollForward": "latestFeature"
}
}