mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91d4b32c66 | ||
|
|
e788f03a28 | ||
|
|
77654a1628 | ||
|
|
f3af813b57 | ||
|
|
0623baedcf | ||
|
|
2ade6c04c5 | ||
|
|
a9df9097c1 | ||
|
|
755dac719f | ||
|
|
7095a58685 | ||
|
|
16e68a4351 | ||
|
|
0152f9d1d8 | ||
|
|
16d916796a | ||
|
|
e865157432 | ||
|
|
24d5ce4bd4 | ||
|
|
662195e4ff | ||
|
|
d00fd6f736 | ||
|
|
2d58c1071d | ||
|
|
a9db89d023 | ||
|
|
684cabf3e6 | ||
|
|
a4f51f0cd9 | ||
|
|
a8ddd837c8 | ||
|
|
82aace7997 | ||
|
|
01ce244b7b | ||
|
|
58aa6e5c75 | ||
|
|
4818c3aab4 | ||
|
|
3b6adeb5ff | ||
|
|
889b8351be | ||
|
|
ac37b0a131 | ||
|
|
f6f1fc425a | ||
|
|
7476628840 | ||
|
|
668cdbe76b | ||
|
|
a0a6e9b111 | ||
|
|
06d28f04e6 | ||
|
|
57897161d0 | ||
|
|
c4c528478e | ||
|
|
a6c295b89c | ||
|
|
165913a4de | ||
|
|
675dfdaabd | ||
|
|
fab172d6f6 | ||
|
|
e75c1659f6 | ||
|
|
0c440a8fc9 | ||
|
|
0c2c8f352a | ||
|
|
0a4a2b7a36 | ||
|
|
b5b59c1d2f | ||
|
|
f4f0967fdc | ||
|
|
3d69766112 | ||
|
|
d1eb3438d5 | ||
|
|
8f6b189d29 | ||
|
|
ef8b278b47 | ||
|
|
c53ce2c907 | ||
|
|
f063aa3ea1 | ||
|
|
30f63254ef | ||
|
|
30a5b6152c | ||
|
|
910a7f8bff | ||
|
|
526a88293e | ||
|
|
22cd840b83 | ||
|
|
415c518bc7 | ||
|
|
2417dbb0e0 | ||
|
|
005673a957 | ||
|
|
942db3120c | ||
|
|
c0a5fab19e | ||
|
|
d16c62b132 | ||
|
|
7da22557fe | ||
|
|
92f47c0f20 | ||
|
|
9576d0739f | ||
|
|
10f25faabf | ||
|
|
74831a177e | ||
|
|
88d3168913 | ||
|
|
902519093c | ||
|
|
366266a8ae | ||
|
|
a22cce7783 | ||
|
|
e5e738b8cd | ||
|
|
b8f6e83473 | ||
|
|
c5fb186c57 | ||
|
|
131d7f5422 | ||
|
|
217996f1ed | ||
|
|
fc718d68a5 | ||
|
|
d7d9578803 | ||
|
|
9bbeb54569 | ||
|
|
1ea7071ffb |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
192
RELEASE-NOTES.md
192
RELEASE-NOTES.md
@@ -54,6 +54,198 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
* Add entity categories for prototypes and deprecate the `noSpawn` tag.
|
||||
* Add missing proxy method for `TryGetEntityData`.
|
||||
* Add NetForceAckThreshold cvar to forcibly update acks for late clients.
|
||||
|
||||
### Internal
|
||||
|
||||
* Use CollectionMarshals in PVS and DynamicTree.
|
||||
* Make the proxy methods use MetaQuery / TransformQuery.
|
||||
|
||||
|
||||
## 161.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add more DebugTools assert variations.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Don't attempt to insert entities into deleted containers.
|
||||
* Try to fix oldestAck not being set correctly leading to deletion history getting bloated for pvs.
|
||||
|
||||
|
||||
## 161.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Point light animations now need to use different component fields in order to animate the lights. `Enabled` should be replaced with `AnimatedEnable` and `Radius` should be replaced with `AnimatedRadius`
|
||||
|
||||
### New features
|
||||
|
||||
* EntProtoId is now net-serializable
|
||||
* Added print_pvs_ack command to debug PVS issues.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixes AngleTypeParser not using InvariantCulture
|
||||
* Fixed a bug that was causing `MetaDataComponent.LastComponentRemoved` to be updated improperly.
|
||||
|
||||
### Other
|
||||
|
||||
* The string representation of client-side entities now looks nicer and simply uses a 'c' prefix.
|
||||
|
||||
|
||||
## 160.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add optional MetaDataComponent args to Entitymanager methods.
|
||||
|
||||
### Internal
|
||||
|
||||
* Move _netComponents onto MetaDataComponent.
|
||||
* Remove some component resolves internally on adding / removing components.
|
||||
|
||||
|
||||
## 160.0.2
|
||||
|
||||
### Other
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- type: entity
|
||||
id: debugRotation
|
||||
abstract: true
|
||||
suffix: DEBUG
|
||||
categories: [ debug ]
|
||||
components:
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
|
||||
17
Resources/EnginePrototypes/entityCategory.yml
Normal file
17
Resources/EnginePrototypes/entityCategory.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
# debug related entities
|
||||
- type: entityCategory
|
||||
id: debug
|
||||
name: entity-category-name-debug
|
||||
description: entity-category-desc-debug
|
||||
|
||||
# entities that spawn other entities
|
||||
- type: entityCategory
|
||||
id: spawner
|
||||
name: entity-category-name-spawner
|
||||
description: entity-category-desc-spawner
|
||||
|
||||
# entities that should be hidden from the spawn menu
|
||||
- type: entityCategory
|
||||
id: hideSpawnMenu
|
||||
name: entity-category-name-hide
|
||||
description: entity-category-desc-hide
|
||||
8
Resources/Locale/en-US/entity-category.ftl
Normal file
8
Resources/Locale/en-US/entity-category.ftl
Normal file
@@ -0,0 +1,8 @@
|
||||
entity-category-name-debug = Debug
|
||||
entity-category-desc-debug = Entity prototypes intended for debugging & testing.
|
||||
|
||||
entity-category-name-spawner = Spawner
|
||||
entity-category-desc-spawner = Entity prototypes that spawn other entities.
|
||||
|
||||
entity-category-name-hide = Hidden
|
||||
entity-category-desc-hide = Entity prototypes that should be hidden from the spawn menu
|
||||
@@ -1,5 +1,6 @@
|
||||
## ViewVariablesInstanceEntity
|
||||
|
||||
view-variables = View Variables
|
||||
view-variable-instance-entity-server-components-add-component-button-placeholder = Add Component
|
||||
view-variable-instance-entity-client-variables-tab-title = Client Variables
|
||||
view-variable-instance-entity-client-components-tab-title = Client Components
|
||||
@@ -8,4 +9,4 @@ view-variable-instance-entity-server-components-tab-title = Server Components
|
||||
view-variable-instance-entity-client-components-search-bar-placeholder = Search
|
||||
view-variable-instance-entity-server-components-search-bar-placeholder = Search
|
||||
view-variable-instance-entity-add-window-server-components = Add Component [S]
|
||||
view-variable-instance-entity-add-window-client-components = Add Component [C]
|
||||
view-variable-instance-entity-add-window-client-components = Add Component [C]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -54,7 +53,7 @@ public sealed class ClientDirtySystem : EntitySystem
|
||||
|
||||
var uid = args.BaseArgs.Owner;
|
||||
var comp = args.BaseArgs.Component;
|
||||
if (!_timing.InPrediction || !comp.NetSyncEnabled || IsClientSide(uid))
|
||||
if (!_timing.InPrediction || !comp.NetSyncEnabled || IsClientSide(uid, args.Meta))
|
||||
return;
|
||||
|
||||
// Was this component added during prediction? If yes, then there is no need to re-add it when resetting.
|
||||
|
||||
@@ -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;
|
||||
@@ -47,10 +49,18 @@ namespace Robust.Client.GameStates
|
||||
= new();
|
||||
|
||||
// Game state dictionaries that get used every tick.
|
||||
private readonly Dictionary<EntityUid, (NetEntity NetEntity, bool EnteringPvs, GameTick LastApplied, EntityState? curState, EntityState? nextState)> _toApply = new();
|
||||
private readonly Dictionary<EntityUid, (NetEntity NetEntity, MetaDataComponent Meta, bool EnteringPvs, GameTick LastApplied, EntityState? curState, EntityState? nextState)> _toApply = new();
|
||||
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);
|
||||
@@ -497,11 +517,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
countReset += 1;
|
||||
|
||||
var netComps = _entityManager.GetNetComponentsOrNull(entity);
|
||||
if (netComps == null)
|
||||
continue;
|
||||
|
||||
foreach (var (netId, comp) in netComps.Value)
|
||||
foreach (var (netId, comp) in meta.NetComponents)
|
||||
{
|
||||
if (!comp.NetSyncEnabled)
|
||||
continue;
|
||||
@@ -549,13 +565,13 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
foreach (var netId in netIds)
|
||||
{
|
||||
if (_entities.HasComponent(entity, netId))
|
||||
if (meta.NetComponents.ContainsKey(netId))
|
||||
continue;
|
||||
|
||||
if (!last.TryGetValue(netId, out var state))
|
||||
continue;
|
||||
|
||||
var comp = _entityManager.AddComponent(entity, netId);
|
||||
var comp = _entityManager.AddComponent(entity, netId, meta);
|
||||
|
||||
if (_sawmill.Level <= LogLevel.Debug)
|
||||
_sawmill.Debug($" A component was removed: {comp.GetType()}");
|
||||
@@ -592,16 +608,15 @@ 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 = _entityManager.GetEntity(netEntity);
|
||||
var compData = new Dictionary<ushort, ComponentState>();
|
||||
outputData.Add(netEntity, compData);
|
||||
var (createdEntity, meta) = _entityManager.GetEntityData(netEntity);
|
||||
var compData = _compDataPool.Get();
|
||||
_outputData.Add(netEntity, compData);
|
||||
|
||||
foreach (var (netId, component) in _entityManager.GetNetComponents(createdEntity))
|
||||
foreach (var (netId, component) in meta.NetComponents)
|
||||
{
|
||||
if (!component.NetSyncEnabled)
|
||||
continue;
|
||||
@@ -612,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)
|
||||
@@ -690,11 +712,9 @@ 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);
|
||||
_toApply.Add(uid, (es.NetEntity, false, GameTick.Zero, es, null));
|
||||
|
||||
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.
|
||||
@@ -725,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;
|
||||
@@ -742,7 +760,7 @@ namespace Robust.Client.GameStates
|
||||
continue;
|
||||
}
|
||||
|
||||
_toApply.Add(uid, (es.NetEntity, isEnteringPvs, meta.LastStateApplied, es, null));
|
||||
_toApply.Add(uid.Value, (es.NetEntity, meta, isEnteringPvs, meta.LastStateApplied, es, null));
|
||||
meta.LastStateApplied = curState.ToSequence;
|
||||
}
|
||||
|
||||
@@ -756,44 +774,46 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
foreach (var es in nextState.EntityStates.Span)
|
||||
{
|
||||
if (!_entityManager.TryGetEntity(es.NetEntity, out var uid))
|
||||
if (!_entityManager.TryGetEntityData(es.NetEntity, out var uid, out var meta))
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(metas.HasComponent(uid));
|
||||
|
||||
// Does the next state actually have any future information about this entity that could be used for interpolation?
|
||||
if (es.EntityLastModified != nextState.ToSequence)
|
||||
continue;
|
||||
|
||||
if (_toApply.TryGetValue(uid.Value, out var state))
|
||||
_toApply[uid.Value] = (es.NetEntity, 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, 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] = (_entityManager.GetNetEntity(uid, 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"))
|
||||
{
|
||||
foreach (var (entity, data) in _toApply)
|
||||
{
|
||||
HandleEntityState(entity, data.NetEntity, _entities.EventBus, data.curState,
|
||||
HandleEntityState(entity, data.NetEntity, data.Meta, _entities.EventBus, data.curState,
|
||||
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
|
||||
|
||||
if (!data.EnteringPvs)
|
||||
@@ -806,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));
|
||||
@@ -817,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);
|
||||
}
|
||||
@@ -834,7 +854,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessDeletions(delSpan, xforms, metas, xformSys);
|
||||
ProcessDeletions(delSpan, xforms, xformSys);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -871,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>();
|
||||
@@ -892,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;
|
||||
}
|
||||
|
||||
@@ -917,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);
|
||||
}
|
||||
@@ -932,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.
|
||||
@@ -1003,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)
|
||||
{
|
||||
@@ -1025,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)
|
||||
@@ -1043,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.
|
||||
@@ -1055,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)
|
||||
@@ -1133,7 +1154,7 @@ namespace Robust.Client.GameStates
|
||||
#endif
|
||||
}
|
||||
|
||||
private void HandleEntityState(EntityUid uid, NetEntity netEntity, IEventBus bus, EntityState? curState,
|
||||
private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState,
|
||||
EntityState? nextState, GameTick lastApplied, GameTick toTick, bool enteringPvs)
|
||||
{
|
||||
_compStateWork.Clear();
|
||||
@@ -1141,16 +1162,17 @@ namespace Robust.Client.GameStates
|
||||
// First remove any deleted components
|
||||
if (curState?.NetComponents != null)
|
||||
{
|
||||
RemQueue<Component> toRemove = new();
|
||||
foreach (var (id, comp) in _entities.GetNetComponents(uid))
|
||||
_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);
|
||||
_entities.RemoveComponent(uid, comp, meta);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1163,12 +1185,11 @@ namespace Robust.Client.GameStates
|
||||
// the entity. most notably, all entities will have been ejected from their containers.
|
||||
foreach (var (id, state) in _processor.GetLastServerStates(netEntity))
|
||||
{
|
||||
if (!_entityManager.TryGetComponent(uid, id, out var comp))
|
||||
if (!meta.NetComponents.TryGetValue(id, out var comp))
|
||||
{
|
||||
comp = _compFactory.GetComponent(id);
|
||||
var newComp = (Component)comp;
|
||||
newComp.Owner = uid;
|
||||
_entityManager.AddComponent(uid, newComp, true);
|
||||
comp = (Component) _compFactory.GetComponent(id);
|
||||
comp.Owner = uid;
|
||||
_entityManager.AddComponent(uid, comp, true, metadata: meta);
|
||||
}
|
||||
|
||||
_compStateWork[id] = (comp, state, null);
|
||||
@@ -1178,12 +1199,11 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
foreach (var compChange in curState.ComponentChanges.Span)
|
||||
{
|
||||
if (!_entityManager.TryGetComponent(uid, compChange.NetID, out var comp))
|
||||
if (!meta.NetComponents.TryGetValue(compChange.NetID, out var comp))
|
||||
{
|
||||
comp = _compFactory.GetComponent(compChange.NetID);
|
||||
var newComp = (Component)comp;
|
||||
newComp.Owner = uid;
|
||||
_entityManager.AddComponent(uid, newComp, true);
|
||||
comp = (Component) _compFactory.GetComponent(compChange.NetID);
|
||||
comp.Owner = uid;
|
||||
_entityManager.AddComponent(uid, comp, true, metadata:meta);
|
||||
}
|
||||
else if (compChange.LastModifiedTick <= lastApplied && lastApplied != GameTick.Zero)
|
||||
continue;
|
||||
@@ -1199,17 +1219,20 @@ namespace Robust.Client.GameStates
|
||||
if (compState.LastModifiedTick != toTick + 1)
|
||||
continue;
|
||||
|
||||
if (!_entityManager.TryGetComponent(uid, compState.NetID, out var comp))
|
||||
if (!meta.NetComponents.TryGetValue(compState.NetID, out var comp))
|
||||
{
|
||||
// The component can be null here due to interp, because the NEXT state will have a new
|
||||
// component, but the component does not yet exist.
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1226,14 +1249,19 @@ namespace Robust.Client.GameStates
|
||||
if (netId == null)
|
||||
continue;
|
||||
|
||||
if (_compStateWork.ContainsKey(netId.Value) ||
|
||||
!_entityManager.TryGetComponent(uid, type, 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1247,10 +1275,10 @@ namespace Robust.Client.GameStates
|
||||
catch (Exception e)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
_sawmill.Error($"Failed to apply comp state: entity={comp.Owner}, comp={comp.GetType()}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(HandleEntityState)}");
|
||||
_sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(HandleEntityState)}");
|
||||
#else
|
||||
_sawmill.Error($"Failed to apply comp state: entity={uid}, comp={comp.GetType()}");
|
||||
_sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}");
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
@@ -1398,12 +1426,11 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var (id, state) in lastState)
|
||||
{
|
||||
if (!_entityManager.TryGetComponent(uid, id, out var comp))
|
||||
if (!meta.NetComponents.TryGetValue(id, out var comp))
|
||||
{
|
||||
comp = _compFactory.GetComponent(id);
|
||||
var newComp = (Component)comp;
|
||||
newComp.Owner = uid;
|
||||
_entityManager.AddComponent(uid, newComp, true);
|
||||
comp = (Component) _compFactory.GetComponent(id);
|
||||
comp.Owner = uid;
|
||||
_entityManager.AddComponent(uid, comp, true, meta);
|
||||
}
|
||||
|
||||
var handleState = new ComponentHandleState(state, null);
|
||||
@@ -1411,20 +1438,24 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
// ensure we don't have any extra components
|
||||
RemQueue<Component> toRemove = new();
|
||||
foreach (var (id, comp) in _entities.GetNetComponents(uid))
|
||||
_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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -38,8 +38,7 @@ namespace Robust.Client.Graphics
|
||||
event Action<WindowDestroyedEventArgs> Destroyed;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the window has been definitively closed.
|
||||
/// This means the window must not be used anymore (it is disposed).
|
||||
/// Raised when the window has been resized.
|
||||
/// </summary>
|
||||
event Action<WindowResizedEventArgs> Resized;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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" />
|
||||
|
||||
12
Robust.Client/Spawners/TimedDespawnSystem.cs
Normal file
12
Robust.Client/Spawners/TimedDespawnSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
using static Robust.Client.UserInterface.Controls.LineEdit;
|
||||
@@ -199,7 +200,7 @@ public sealed class EntitySpawningUIController : UIController
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prototype.NoSpawn)
|
||||
if (prototype.HideSpawnMenu)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -98,6 +98,11 @@ namespace Robust.Client.UserInterface.Controls
|
||||
OnTextChanged?.Invoke(new LineEditEventArgs(this, _text));
|
||||
}
|
||||
|
||||
public void ForceSubmitText()
|
||||
{
|
||||
OnTextEntered?.Invoke(new LineEditEventArgs(this, _text));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The text
|
||||
/// </summary>
|
||||
@@ -607,7 +612,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
if (Editable)
|
||||
{
|
||||
OnTextEntered?.Invoke(new LineEditEventArgs(this, _text));
|
||||
ForceSubmitText();
|
||||
}
|
||||
|
||||
args.Handle();
|
||||
|
||||
@@ -50,13 +50,13 @@ namespace Robust.Client.UserInterface.Controls
|
||||
Action<Range> ev = _scrollValueChanged;
|
||||
_hScrollBar = new HScrollBar
|
||||
{
|
||||
Visible = false,
|
||||
Visible = _hScrollEnabled,
|
||||
VerticalAlignment = VAlignment.Bottom,
|
||||
HorizontalAlignment = HAlignment.Stretch
|
||||
};
|
||||
_vScrollBar = new VScrollBar
|
||||
{
|
||||
Visible = false,
|
||||
Visible = _vScrollEnabled,
|
||||
VerticalAlignment = VAlignment.Stretch,
|
||||
HorizontalAlignment = HAlignment.Right
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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..."};
|
||||
|
||||
@@ -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 += _ =>
|
||||
{
|
||||
|
||||
@@ -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--;
|
||||
|
||||
@@ -879,7 +879,7 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (netId, component) in EntityManager.GetNetComponents(entity))
|
||||
foreach (var component in metadata.NetComponents.Values)
|
||||
{
|
||||
var compName = _factory.GetComponentName(component.GetType());
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Robust.Server.GameObjects;
|
||||
public sealed class ServerMetaDataSystem : MetaDataSystem
|
||||
{
|
||||
[Dependency] private readonly PvsSystem _pvsSystem = default!;
|
||||
private EntityQuery<MetaDataComponent> _mQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -16,6 +17,7 @@ public sealed class ServerMetaDataSystem : MetaDataSystem
|
||||
|
||||
EntityManager.ComponentAdded += OnComponentAdded;
|
||||
EntityManager.ComponentRemoved += OnComponentRemoved;
|
||||
_mQuery = GetEntityQuery<MetaDataComponent>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -45,7 +47,7 @@ public sealed class ServerMetaDataSystem : MetaDataSystem
|
||||
if (obj.Terminating || !removed.NetSyncEnabled || (!removed.SessionSpecific && !removed.SendOnlyToOwner))
|
||||
return;
|
||||
|
||||
foreach (var (_, comp) in EntityManager.GetNetComponents(obj.BaseArgs.Owner))
|
||||
foreach (var comp in obj.Meta.NetComponents.Values)
|
||||
{
|
||||
if (comp.LifeStage >= ComponentLifeStage.Removing)
|
||||
continue;
|
||||
@@ -55,7 +57,7 @@ public sealed class ServerMetaDataSystem : MetaDataSystem
|
||||
}
|
||||
|
||||
// remove the flag
|
||||
MetaData(obj.BaseArgs.Owner).Flags &= ~MetaDataFlags.SessionSpecific;
|
||||
obj.Meta.Flags &= ~MetaDataFlags.SessionSpecific;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -67,19 +69,10 @@ public sealed class ServerMetaDataSystem : MetaDataSystem
|
||||
if ((meta.Flags & MetaDataFlags.SessionSpecific) == 0)
|
||||
return;
|
||||
|
||||
foreach (var (_, comp) in EntityManager.GetNetComponents(uid))
|
||||
foreach (var (_, comp) in meta.NetComponents)
|
||||
{
|
||||
if (comp.SessionSpecific || comp.SendOnlyToOwner)
|
||||
Dirty(uid, comp);
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetVisibilityMask(EntityUid uid, int value, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (!Resolve(uid, ref meta) || meta.VisibilityMask == value)
|
||||
return;
|
||||
|
||||
base.SetVisibilityMask(uid, value, meta);
|
||||
_pvsSystem.MarkDirty(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Robust.Server.GameStates;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
@@ -6,11 +7,18 @@ namespace Robust.Server.GameObjects
|
||||
{
|
||||
public sealed class VisibilitySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MetaDataSystem _metaSys = default!;
|
||||
[Dependency] private readonly PvsSystem _pvs = default!;
|
||||
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
private EntityQuery<MetaDataComponent> _metaQuery;
|
||||
private EntityQuery<VisibilityComponent> _visiblityQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
_visiblityQuery = GetEntityQuery<VisibilityComponent>();
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChange);
|
||||
EntityManager.EntityInitialized += OnEntityInit;
|
||||
}
|
||||
@@ -82,27 +90,58 @@ namespace Robust.Server.GameObjects
|
||||
RefreshVisibility(uid);
|
||||
}
|
||||
|
||||
public void RefreshVisibility(EntityUid uid, MetaDataComponent? metaDataComponent = null, VisibilityComponent? visibilityComponent = null)
|
||||
public void RefreshVisibility(EntityUid uid,
|
||||
VisibilityComponent? visibilityComponent = null,
|
||||
MetaDataComponent? meta = null)
|
||||
{
|
||||
if (Resolve(uid, ref metaDataComponent, false))
|
||||
_metaSys.SetVisibilityMask(uid, GetVisibilityMask(uid, visibilityComponent), metaDataComponent);
|
||||
if (!_metaQuery.Resolve(uid, ref meta, false))
|
||||
return;
|
||||
|
||||
// Iterate up through parents and calculate the cumulative visibility mask.
|
||||
var mask = GetParentVisibilityMask(uid, visibilityComponent);
|
||||
|
||||
// Iterate down through children and propagate mask changes.
|
||||
RecursivelyApplyVisibility(uid, mask, meta);
|
||||
}
|
||||
|
||||
private void RecursivelyApplyVisibility(EntityUid uid, int mask, MetaDataComponent meta)
|
||||
{
|
||||
if (meta.VisibilityMask == mask)
|
||||
return;
|
||||
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
meta.VisibilityMask = mask;
|
||||
_pvs.MarkDirty(uid, xform);
|
||||
|
||||
foreach (var child in xform.ChildEntities)
|
||||
{
|
||||
if (!_metaQuery.TryGetComponent(child, out var childMeta))
|
||||
continue;
|
||||
|
||||
var childMask = mask;
|
||||
|
||||
if (_visiblityQuery.TryGetComponent(child, out VisibilityComponent? hildVis))
|
||||
childMask |= hildVis.Layer;
|
||||
|
||||
RecursivelyApplyVisibility(child, childMask, childMeta);
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Use overload that takes an EntityUid instead")]
|
||||
public void RefreshVisibility(VisibilityComponent visibilityComponent)
|
||||
{
|
||||
RefreshVisibility(visibilityComponent.Owner, null, visibilityComponent);
|
||||
RefreshVisibility(visibilityComponent.Owner, visibilityComponent);
|
||||
}
|
||||
|
||||
private int GetVisibilityMask(EntityUid uid, VisibilityComponent? visibilityComponent = null, TransformComponent? xform = null)
|
||||
private int GetParentVisibilityMask(EntityUid uid, VisibilityComponent? visibilityComponent = null)
|
||||
{
|
||||
int visMask = 1; // apparently some content expects everything to have the first bit/flag set to true.
|
||||
if (Resolve(uid, ref visibilityComponent, false))
|
||||
if (_visiblityQuery.Resolve(uid, ref visibilityComponent, false))
|
||||
visMask |= visibilityComponent.Layer;
|
||||
|
||||
// Include parent vis masks
|
||||
if (Resolve(uid, ref xform) && xform.ParentUid.IsValid())
|
||||
visMask |= GetVisibilityMask(xform.ParentUid);
|
||||
if (_xformQuery.TryGetComponent(uid, out var xform) && xform.ParentUid.IsValid())
|
||||
visMask |= GetParentVisibilityMask(xform.ParentUid);
|
||||
|
||||
return visMask;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -139,9 +141,6 @@ namespace Robust.Server.GameObjects
|
||||
{
|
||||
_networkManager.RegisterNetMessage<MsgEntity>(HandleEntityNetworkMessage);
|
||||
|
||||
// For syncing component deletions.
|
||||
ComponentRemoved += OnComponentRemoved;
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
|
||||
_configurationManager.OnValueChanged(CVars.NetLogLateMsg, b => _logLateMsgs = b, true);
|
||||
@@ -168,15 +167,6 @@ namespace Robust.Server.GameObjects
|
||||
return _lastProcessedSequencesCmd[session];
|
||||
}
|
||||
|
||||
private void OnComponentRemoved(RemovedComponentEventArgs e)
|
||||
{
|
||||
if (e.Terminating || !e.BaseArgs.Component.NetSyncEnabled)
|
||||
return;
|
||||
|
||||
if (TryGetComponent(e.BaseArgs.Owner, out MetaDataComponent? meta))
|
||||
meta.LastComponentRemoved = _gameTiming.CurTick;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SendSystemNetworkMessage(EntityEventArgs message, bool recordReplay = true)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -92,9 +92,9 @@ namespace Robust.Server.GameStates
|
||||
/// <summary>
|
||||
/// Marks an entity's current chunk as dirty.
|
||||
/// </summary>
|
||||
internal void MarkDirty(EntityUid uid)
|
||||
internal void MarkDirty(EntityUid uid, TransformComponent xform)
|
||||
{
|
||||
var coordinates = _transform.GetMoverCoordinates(uid);
|
||||
var coordinates = _transform.GetMoverCoordinates(uid, xform);
|
||||
_entityPvsCollection.MarkDirty(_entityPvsCollection.GetChunkIndex(coordinates));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Server.Configuration;
|
||||
@@ -24,7 +25,7 @@ namespace Robust.Server.GameStates;
|
||||
internal sealed partial class PvsSystem : EntitySystem
|
||||
{
|
||||
[Shared.IoC.Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IMapManagerInternal _mapManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly INetworkedMapManager _mapManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IParallelManager _parallelManager = default!;
|
||||
[Shared.IoC.Dependency] private readonly IServerGameStateManager _serverGameStateManager = default!;
|
||||
@@ -187,6 +188,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
Log.Warning(sb.ToString());
|
||||
|
||||
sessionData.LastSeenAt.Clear();
|
||||
sessionData.LastLeftView.Clear();
|
||||
|
||||
if (sessionData.Overflow != null)
|
||||
{
|
||||
@@ -227,6 +229,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
public void CullDeletionHistory(GameTick oldestAck)
|
||||
{
|
||||
_entityPvsCollection.CullDeletionHistoryUntil(oldestAck);
|
||||
_mapManager.CullDeletionHistory(oldestAck);
|
||||
}
|
||||
|
||||
#region PVSCollection methods to maybe make public someday:tm:
|
||||
@@ -254,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);
|
||||
}
|
||||
@@ -313,7 +317,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
// since elements are cached grid-/map-relative, we don't need to update a given grids/maps children
|
||||
DebugTools.Assert(!_mapManager.IsGrid(uid) && !_mapManager.IsMap(uid));
|
||||
|
||||
var indices = PVSCollection<EntityUid>.GetChunkIndices(coordinates.Position);
|
||||
var indices = PVSCollection<NetEntity>.GetChunkIndices(coordinates.Position);
|
||||
if (xform.GridUid != null)
|
||||
_entityPvsCollection.UpdateIndex(metadata.NetEntity, xform.GridUid.Value, indices, forceDirty: forceDirty);
|
||||
else
|
||||
@@ -648,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);
|
||||
|
||||
@@ -667,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));
|
||||
@@ -677,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)
|
||||
@@ -705,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)
|
||||
@@ -732,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);
|
||||
}
|
||||
}
|
||||
@@ -743,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();
|
||||
@@ -753,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);
|
||||
@@ -822,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))
|
||||
{
|
||||
@@ -859,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;
|
||||
@@ -882,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,
|
||||
@@ -900,15 +916,22 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
// As every map is parented to uid 0 in the tree we still need to get their children, plus because we go top-down
|
||||
// we may find duplicate parents with children we haven't encountered before
|
||||
// on different chunks (this is especially common with direct grid children)
|
||||
if (!toSend.ContainsKey(currentNodeIndex))
|
||||
|
||||
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(toSend, currentNodeIndex, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
var (entered, shouldAdd) = ProcessEntry(in currentNodeIndex, lastAcked, lastSent, lastSeen,
|
||||
var (entered, shouldAdd) = ProcessEntry(in currentNodeIndex, lastAcked, lastSent, lastSeen, lastLeft, fromTick,
|
||||
ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
|
||||
if (!shouldAdd)
|
||||
{
|
||||
// In the majority of instances entities do get added.
|
||||
// So its better to add and maybe remove, rather than checking ContainsKey() and then maybe adding it.
|
||||
toSend.Remove(currentNodeIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
AddToSendSet(in currentNodeIndex, metaDataCache[currentNodeIndex], toSend, fromTick, in entered, ref entStateCount);
|
||||
AddToSendSet(in currentNodeIndex, metaDataCache[currentNodeIndex], ref value, toSend, fromTick, in entered, ref entStateCount);
|
||||
}
|
||||
|
||||
var node = tree[currentNodeIndex];
|
||||
@@ -927,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,
|
||||
@@ -942,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))
|
||||
{
|
||||
@@ -952,24 +976,20 @@ 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
|
||||
// send the new parents, which may otherwise be delayed because of the PVS budget..
|
||||
if (!toSend.ContainsKey(netEntity))
|
||||
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(toSend, netEntity, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
// TODO PERFORMANCE.
|
||||
// ProcessEntry() unnecessarily checks lastSent.ContainsKey() and maybe lastSeen.Contains(). Given that at this
|
||||
// point the budgets are just ignored, this should just bypass those checks. But then again 99% of the time this
|
||||
// is just the player's own entity + maybe a singularity. So currently not all that performance intensive.
|
||||
var (entered, _) = ProcessEntry(in netEntity, lastAcked, lastSent, lastSeen, ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
AddToSendSet(in netEntity, metadata, toSend, fromTick, in entered, ref entStateCount);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -981,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,
|
||||
@@ -996,24 +1017,26 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
var metadata = _metaQuery.GetComponent(child);
|
||||
var childNetEntity = metadata.NetEntity;
|
||||
|
||||
if (!toSend.ContainsKey(childNetEntity))
|
||||
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(toSend, childNetEntity, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
var (entered, _) = ProcessEntry(in childNetEntity, lastAcked, lastSent, lastSeen, ref newEntityCount,
|
||||
var (entered, _) = ProcessEntry(in childNetEntity, lastAcked, lastSent, lastSeen, lastLeft, fromTick, ref newEntityCount,
|
||||
ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
|
||||
AddToSendSet(in childNetEntity, metadata, toSend, fromTick, in entered, ref entStateCount);
|
||||
AddToSendSet(in childNetEntity, metadata, ref value, toSend, fromTick, in entered, ref entStateCount);
|
||||
}
|
||||
|
||||
RecursivelyAddChildren(childXform, lastAcked, lastSent, toSend, lastSeen, fromTick, ref newEntityCount,
|
||||
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,
|
||||
@@ -1021,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)
|
||||
@@ -1044,10 +1066,13 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
return (entered, true);
|
||||
}
|
||||
|
||||
private void AddToSendSet(in NetEntity netEntity, MetaDataComponent metaDataComponent, Dictionary<NetEntity, PvsEntityVisibility> toSend, GameTick fromTick, in bool entered, ref int entStateCount)
|
||||
private void AddToSendSet(in NetEntity netEntity, MetaDataComponent metaDataComponent,
|
||||
ref PvsEntityVisibility vis, Dictionary<NetEntity, PvsEntityVisibility> toSend,
|
||||
GameTick fromTick, in bool entered, ref int entStateCount)
|
||||
{
|
||||
if (metaDataComponent.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
toSend.Remove(netEntity);
|
||||
var rep = new EntityStringRepresentation(GetEntity(netEntity), metaDataComponent.EntityDeleted, metaDataComponent.EntityName, metaDataComponent.EntityPrototype?.ID);
|
||||
Log.Error($"Attempted to add a deleted entity to PVS send set: '{rep}'. Trace:\n{Environment.StackTrace}");
|
||||
return;
|
||||
@@ -1055,7 +1080,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
if (entered)
|
||||
{
|
||||
toSend.Add(netEntity, PvsEntityVisibility.Entered);
|
||||
vis = PvsEntityVisibility.Entered;
|
||||
entStateCount++;
|
||||
return;
|
||||
}
|
||||
@@ -1063,12 +1088,12 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
if (metaDataComponent.EntityLastModifiedTick <= fromTick)
|
||||
{
|
||||
//entity has been sent before and hasnt been updated since
|
||||
toSend.Add(netEntity, PvsEntityVisibility.StayedUnchanged);
|
||||
vis = PvsEntityVisibility.StayedUnchanged;
|
||||
return;
|
||||
}
|
||||
|
||||
//add us
|
||||
toSend.Add(netEntity, PvsEntityVisibility.StayedChanged);
|
||||
vis = PvsEntityVisibility.StayedChanged;
|
||||
entStateCount++;
|
||||
}
|
||||
|
||||
@@ -1204,7 +1229,7 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
|
||||
bool sendCompList = meta.LastComponentRemoved > fromTick;
|
||||
HashSet<ushort>? netComps = sendCompList ? new() : null;
|
||||
|
||||
foreach (var (netId, component) in EntityManager.GetNetComponents(entityUid))
|
||||
foreach (var (netId, component) in meta.NetComponents)
|
||||
{
|
||||
if (!component.NetSyncEnabled)
|
||||
continue;
|
||||
@@ -1253,7 +1278,7 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
|
||||
|
||||
HashSet<ushort> netComps = new();
|
||||
|
||||
foreach (var (netId, component) in EntityManager.GetNetComponents(entityUid))
|
||||
foreach (var (netId, component) in meta.NetComponents)
|
||||
{
|
||||
if (!component.NetSyncEnabled)
|
||||
continue;
|
||||
@@ -1358,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>
|
||||
@@ -1380,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)
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -24,6 +24,7 @@ using SharpZstd.Interop;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Prometheus;
|
||||
using Robust.Server.Replays;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
@@ -48,6 +49,7 @@ namespace Robust.Server.GameStates
|
||||
[Dependency] private readonly IServerEntityNetworkManager _entityNetworkManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IParallelManager _parallelMgr = default!;
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
|
||||
private static readonly Histogram _usageHistogram = Metrics.CreateHistogram("robust_game_state_update_usage",
|
||||
"Amount of time spent processing different parts of the game state update", new HistogramConfiguration
|
||||
@@ -83,6 +85,25 @@ namespace Robust.Server.GameStates
|
||||
_parallelMgr.AddAndInvokeParallelCountChanged(ResetParallelism);
|
||||
|
||||
_cfg.OnValueChanged(CVars.NetPVSCompressLevel, _ => ResetParallelism(), true);
|
||||
|
||||
// temporary command for debugging PVS bugs.
|
||||
_conHost.RegisterCommand("print_pvs_ack", PrintPvsAckInfo);
|
||||
}
|
||||
|
||||
private void PrintPvsAckInfo(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
var ack = _pvs.PlayerData.Min(x => x.Value.LastReceivedAck);
|
||||
var players = _pvs.PlayerData
|
||||
.Where(x => x.Value.LastReceivedAck == ack)
|
||||
.Select(x => x.Key)
|
||||
.Select(x => $"{x.Name} ({_entityManager.ToPrettyString(x.AttachedEntity)})");
|
||||
|
||||
shell.WriteLine($@"Current tick: {_gameTiming.CurTick}
|
||||
Stored oldest acked tick: {_lastOldestAck}
|
||||
Deletion history size: {_pvs.EntityPVSCollection.GetDeletedIndices(GameTick.First)?.Count ?? 0}
|
||||
Actual oldest: {ack}
|
||||
Oldest acked clients: {string.Join(", ", players)}
|
||||
");
|
||||
}
|
||||
|
||||
private void ResetParallelism()
|
||||
@@ -147,15 +168,6 @@ namespace Robust.Server.GameStates
|
||||
/// <inheritdoc />
|
||||
public void SendGameStateUpdate()
|
||||
{
|
||||
if (!_networkManager.IsConnected)
|
||||
{
|
||||
// Prevent deletions piling up if we have no clients.
|
||||
_pvs.CullDeletionHistory(GameTick.MaxValue);
|
||||
_mapManager.CullDeletionHistory(GameTick.MaxValue);
|
||||
_pvs.CleanupDirty(Enumerable.Empty<IPlayerSession>());
|
||||
return;
|
||||
}
|
||||
|
||||
var players = _playerManager.ServerSessions.Where(o => o.Status == SessionStatus.InGame).ToArray();
|
||||
|
||||
// Update entity positions in PVS chunks/collections
|
||||
@@ -195,14 +207,21 @@ namespace Robust.Server.GameStates
|
||||
_pvs.CleanupDirty(players);
|
||||
}
|
||||
|
||||
// keep the deletion history buffers clean
|
||||
if (oldestAck > _lastOldestAck)
|
||||
if (oldestAck == GameTick.MaxValue)
|
||||
{
|
||||
using var _ = _usageHistogram.WithLabels("Cull History").NewTimer();
|
||||
_lastOldestAck = oldestAck;
|
||||
_pvs.CullDeletionHistory(oldestAck);
|
||||
_mapManager.CullDeletionHistory(oldestAck);
|
||||
// There were no connected players?
|
||||
// In that case we just clear all deletion history.
|
||||
_pvs.CullDeletionHistory(GameTick.MaxValue);
|
||||
_lastOldestAck = GameTick.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldestAck == _lastOldestAck)
|
||||
return;
|
||||
|
||||
_lastOldestAck = oldestAck;
|
||||
using var __ = _usageHistogram.WithLabels("Cull History").NewTimer();
|
||||
_pvs.CullDeletionHistory(oldestAck);
|
||||
}
|
||||
|
||||
private GameTick SendStates(IPlayerSession[] players, PvsData? pvsData)
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace Robust.Server.Prototypes
|
||||
|
||||
public override void LoadDefaultPrototypes(Dictionary<Type, HashSet<string>>? changed = null)
|
||||
{
|
||||
LoadDirectory(new("/EnginePrototypes/"), changed: changed);
|
||||
LoadDirectory(_server.Options.PrototypeDirectory, changed: changed);
|
||||
ResolveResults();
|
||||
}
|
||||
|
||||
12
Robust.Server/Spawners/TimedDespawnSystem.cs
Normal file
12
Robust.Server/Spawners/TimedDespawnSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>9</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -35,9 +36,15 @@ namespace Robust.Shared.Maths
|
||||
/// </summary>
|
||||
[FieldOffset(sizeof(float) * 3)] public float Top;
|
||||
|
||||
[NonSerialized]
|
||||
[FieldOffset(sizeof(float) * 0)] public Vector2 BottomLeft;
|
||||
|
||||
[NonSerialized]
|
||||
[FieldOffset(sizeof(float) * 2)] public Vector2 TopRight;
|
||||
|
||||
[NonSerialized]
|
||||
[FieldOffset(sizeof(float) * 0)] public System.Numerics.Vector4 AsVector4;
|
||||
|
||||
public readonly Vector2 BottomRight
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -141,6 +148,11 @@ namespace Robust.Shared.Maths
|
||||
return new Box2(min, max);
|
||||
}
|
||||
|
||||
public readonly bool HasNan()
|
||||
{
|
||||
return Vector128.EqualsAny(AsVector4.AsVector128(), Vector128.Create(float.NaN));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public readonly bool Intersects(in Box2 other)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -13,6 +13,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.Containers
|
||||
@@ -111,6 +112,7 @@ namespace Robust.Shared.Containers
|
||||
DebugTools.Assert(ownerTransform == null || ownerTransform.Owner == Owner);
|
||||
DebugTools.Assert(physics == null || physics.Owner == toinsert);
|
||||
DebugTools.Assert(!ExpectedEntities.Contains(entMan.GetNetEntity(toinsert)));
|
||||
DebugTools.Assert(Manager.Containers.ContainsKey(ID));
|
||||
|
||||
var physicsQuery = entMan.GetEntityQuery<PhysicsComponent>();
|
||||
var transformQuery = entMan.GetEntityQuery<TransformComponent>();
|
||||
@@ -121,6 +123,26 @@ namespace Robust.Shared.Containers
|
||||
var jointSys = entMan.EntitySysManager.GetEntitySystem<SharedJointSystem>();
|
||||
var containerSys = entMan.EntitySysManager.GetEntitySystem<SharedContainerSystem>();
|
||||
|
||||
// If someone is attempting to insert an entity into a container that is getting deleted, then we will
|
||||
// automatically delete that entity. I.e., the insertion automatically "succeeds" and both entities get deleted.
|
||||
// This is consistent with what happens if you attempt to attach an entity to a terminating parent.
|
||||
|
||||
if (!entMan.TryGetComponent(Owner, out MetaDataComponent? ownerMeta))
|
||||
{
|
||||
Logger.ErrorS("container",
|
||||
$"Attempted to insert an entity {entMan.ToPrettyString(toinsert)} into a non-existent entity.");
|
||||
entMan.QueueDeleteEntity(toinsert);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ownerMeta.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
Logger.ErrorS("container",
|
||||
$"Attempted to insert an entity {entMan.ToPrettyString(toinsert)} into an entity that is terminating. Entity: {entMan.ToPrettyString(Owner)}.");
|
||||
entMan.QueueDeleteEntity(toinsert);
|
||||
return false;
|
||||
}
|
||||
|
||||
//Verify we can insert into this container
|
||||
if (!force && !containerSys.CanInsert(toinsert, this))
|
||||
return false;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ namespace Robust.Shared.Containers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnRemove()
|
||||
protected internal override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -46,10 +46,13 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public readonly bool Terminating;
|
||||
|
||||
public RemovedComponentEventArgs(ComponentEventArgs baseArgs, bool terminating)
|
||||
public readonly MetaDataComponent Meta;
|
||||
|
||||
public RemovedComponentEventArgs(ComponentEventArgs baseArgs, bool terminating, MetaDataComponent meta)
|
||||
{
|
||||
BaseArgs = baseArgs;
|
||||
Terminating = terminating;
|
||||
Meta = meta;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
@@ -39,16 +41,35 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
[Access(typeof(SharedPointLightSystem))]
|
||||
[DataField("enabled")]
|
||||
public bool Enabled = true;
|
||||
|
||||
// TODO ECS animations
|
||||
[Animatable]
|
||||
public bool Enabled { get; set; } = true;
|
||||
public bool AnimatedEnable
|
||||
{
|
||||
[Obsolete]
|
||||
get => Enabled;
|
||||
|
||||
[Obsolete]
|
||||
set => IoCManager.Resolve<IEntityManager>().System<SharedPointLightSystem>().SetEnabled(Owner, value, this);
|
||||
}
|
||||
|
||||
// TODO ECS animations
|
||||
[Animatable]
|
||||
public float AnimatedRadius
|
||||
{
|
||||
[Obsolete]
|
||||
get => Radius;
|
||||
[Obsolete]
|
||||
set => IoCManager.Resolve<IEntityManager>().System<SharedPointLightSystem>().SetRadius(Owner, value, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How far the light projects.
|
||||
/// </summary>
|
||||
[DataField("radius")]
|
||||
[Access(typeof(SharedPointLightSystem))]
|
||||
[Animatable]
|
||||
public float Radius { get; set; } = 5f;
|
||||
public float Radius = 5f;
|
||||
|
||||
[ViewVariables]
|
||||
public bool ContainerOccluded;
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
@@ -63,6 +64,12 @@ namespace Robust.Shared.GameObjects
|
||||
[DataField("desc")] internal string? _entityDescription;
|
||||
internal EntityPrototype? _entityPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// The components attached to the entity that are currently networked.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
internal readonly Dictionary<ushort, Component> NetComponents = new();
|
||||
|
||||
/// <summary>
|
||||
/// Network identifier for this entity.
|
||||
/// </summary>
|
||||
@@ -87,7 +94,8 @@ namespace Robust.Shared.GameObjects
|
||||
public GameTick LastStateApplied { get; internal set; } = GameTick.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// This is the most recent tick at which some component was removed from this entity.
|
||||
/// This is the most recent tick at which a networked component was removed from this entity.
|
||||
/// Currently only reliable server-side, client side prediction may cause the value to be wrong.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public GameTick LastComponentRemoved { get; internal set; } = GameTick.Zero;
|
||||
@@ -181,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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
@@ -35,9 +36,6 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private static readonly ComponentState DefaultComponentState = new();
|
||||
|
||||
private readonly Dictionary<EntityUid, Dictionary<ushort, Component>> _netComponents
|
||||
= new(EntityCapacity);
|
||||
|
||||
private readonly Dictionary<Type, Dictionary<EntityUid, Component>> _entTraitDict
|
||||
= new();
|
||||
|
||||
@@ -70,7 +68,6 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public void ClearComponents()
|
||||
{
|
||||
_netComponents.Clear();
|
||||
_entCompIndex.Clear();
|
||||
_deleteSet.Clear();
|
||||
foreach (var dict in _entTraitDict.Values)
|
||||
@@ -125,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
|
||||
@@ -158,30 +155,30 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
public Component AddComponent(EntityUid uid, ushort netId)
|
||||
public Component AddComponent(EntityUid uid, ushort netId, MetaDataComponent? meta = null)
|
||||
{
|
||||
var newComponent = (Component)_componentFactory.GetComponent(netId);
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
newComponent.Owner = uid;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
AddComponent(uid, newComponent);
|
||||
AddComponent(uid, newComponent, metadata: meta);
|
||||
return newComponent;
|
||||
}
|
||||
|
||||
@@ -219,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)
|
||||
@@ -232,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>();
|
||||
@@ -249,7 +247,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddComponent<T>(EntityUid uid, T component, bool overwrite = false) where T : Component
|
||||
public void AddComponent<T>(EntityUid uid, T component, bool overwrite = false, MetaDataComponent? metadata = null) where T : Component
|
||||
{
|
||||
if (!uid.IsValid() || !EntityExists(uid))
|
||||
throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid));
|
||||
@@ -267,35 +265,51 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
AddComponentInternal(uid, component, overwrite, false);
|
||||
AddComponentInternal(uid, component, overwrite, false, metadata);
|
||||
}
|
||||
|
||||
private void AddComponentInternal<T>(EntityUid uid, T component, bool overwrite, bool skipInit) where T : Component
|
||||
private void AddComponentInternal<T>(EntityUid uid, T component, bool overwrite, bool skipInit, MetaDataComponent? metadata = null) where T : Component
|
||||
{
|
||||
// get interface aliases for mapping
|
||||
var reg = _componentFactory.GetRegistration(component);
|
||||
AddComponentInternal(uid, component, reg, overwrite, skipInit, metadata);
|
||||
}
|
||||
|
||||
private void AddComponentInternal<T>(EntityUid uid, T component, ComponentRegistration reg, bool overwrite, bool skipInit, MetaDataComponent? metadata = null) where T : Component
|
||||
{
|
||||
// We can't use typeof(T) here in case T is just Component
|
||||
DebugTools.Assert(component is MetaDataComponent ||
|
||||
GetComponent<MetaDataComponent>(uid).EntityLifeStage < EntityLifeStage.Terminating,
|
||||
(metadata ?? MetaQuery.GetComponent(uid)).EntityLifeStage < EntityLifeStage.Terminating,
|
||||
$"Attempted to add a {component.GetType().Name} component to an entity ({ToPrettyString(uid)}) while it is terminating");
|
||||
|
||||
// get interface aliases for mapping
|
||||
var reg = _componentFactory.GetRegistration(component);
|
||||
|
||||
// Check that there is no existing component.
|
||||
var type = reg.Idx;
|
||||
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);
|
||||
// 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
|
||||
@@ -303,14 +317,8 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
// the main comp grid keeps this in sync
|
||||
var netId = reg.NetID.Value;
|
||||
|
||||
if (!_netComponents.TryGetValue(uid, out var netSet))
|
||||
{
|
||||
netSet = new Dictionary<ushort, Component>(NetComponentCapacity);
|
||||
_netComponents.Add(uid, netSet);
|
||||
}
|
||||
|
||||
netSet.Add(netId, component);
|
||||
metadata ??= MetaQuery.GetComponentInternal(uid);
|
||||
metadata.NetComponents.Add(netId, component);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -321,12 +329,12 @@ namespace Robust.Shared.GameObjects
|
||||
ComponentAdded?.Invoke(eventArgs);
|
||||
_eventBus.OnComponentAdded(eventArgs);
|
||||
|
||||
component.LifeAddToEntity(this, reg.Idx);
|
||||
LifeAddToEntity(component, reg.Idx);
|
||||
|
||||
if (skipInit)
|
||||
return;
|
||||
|
||||
var metadata = GetComponent<MetaDataComponent>(uid);
|
||||
metadata ??= MetaQuery.GetComponentInternal(uid);
|
||||
|
||||
if (!metadata.EntityInitialized && !metadata.EntityInitializing)
|
||||
return;
|
||||
@@ -334,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);
|
||||
@@ -345,45 +353,48 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool RemoveComponent<T>(EntityUid uid)
|
||||
public bool RemoveComponent<T>(EntityUid uid, MetaDataComponent? meta = null)
|
||||
{
|
||||
return RemoveComponent(uid, typeof(T));
|
||||
return RemoveComponent(uid, typeof(T), meta);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool RemoveComponent(EntityUid uid, Type type)
|
||||
public bool RemoveComponent(EntityUid uid, Type type, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (!TryGetComponent(uid, type, out var comp))
|
||||
return false;
|
||||
|
||||
RemoveComponentImmediate((Component)comp, uid, false);
|
||||
RemoveComponentImmediate((Component)comp, uid, false, meta);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool RemoveComponent(EntityUid uid, ushort netId)
|
||||
public bool RemoveComponent(EntityUid uid, ushort netId, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (!TryGetComponent(uid, netId, out var comp))
|
||||
if (!MetaQuery.Resolve(uid, ref meta))
|
||||
return false;
|
||||
|
||||
RemoveComponentImmediate((Component)comp, uid, false);
|
||||
if (!TryGetComponent(uid, netId, out var comp, meta))
|
||||
return false;
|
||||
|
||||
RemoveComponentImmediate((Component)comp, uid, false, meta);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RemoveComponent(EntityUid uid, IComponent component)
|
||||
public void RemoveComponent(EntityUid uid, IComponent component, MetaDataComponent? meta = null)
|
||||
{
|
||||
RemoveComponent(uid, (Component)component);
|
||||
RemoveComponent(uid, (Component)component, meta);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RemoveComponent(EntityUid uid, Component component)
|
||||
public void RemoveComponent(EntityUid uid, Component component, MetaDataComponent? meta = null)
|
||||
{
|
||||
RemoveComponentImmediate(component, uid, false);
|
||||
RemoveComponentImmediate(component, uid, false, meta);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -405,9 +416,12 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool RemoveComponentDeferred(EntityUid uid, ushort netId)
|
||||
public bool RemoveComponentDeferred(EntityUid uid, ushort netId, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (!TryGetComponent(uid, netId, out var comp))
|
||||
if (!MetaQuery.Resolve(uid, ref meta))
|
||||
return false;
|
||||
|
||||
if (!TryGetComponent(uid, netId, out var comp, meta))
|
||||
return false;
|
||||
|
||||
RemoveComponentDeferred((Component)comp, uid, false);
|
||||
@@ -443,22 +457,28 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveComponents(EntityUid uid)
|
||||
public void RemoveComponents(EntityUid uid, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (!MetaQuery.Resolve(uid, ref meta))
|
||||
return;
|
||||
|
||||
foreach (var comp in InSafeOrder(_entCompIndex[uid]))
|
||||
{
|
||||
RemoveComponentImmediate(comp, uid, false);
|
||||
RemoveComponentImmediate(comp, uid, false, meta);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void DisposeComponents(EntityUid uid)
|
||||
public void DisposeComponents(EntityUid uid, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (!MetaQuery.Resolve(uid, ref meta))
|
||||
return;
|
||||
|
||||
foreach (var comp in InSafeOrder(_entCompIndex[uid]))
|
||||
{
|
||||
try
|
||||
{
|
||||
RemoveComponentImmediate(comp, uid, true);
|
||||
RemoveComponentImmediate(comp, uid, true, meta);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -467,7 +487,6 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
_entCompIndex.Remove(uid);
|
||||
_netComponents.Remove(uid);
|
||||
}
|
||||
|
||||
private void RemoveComponentDeferred(Component component, EntityUid uid, bool terminating)
|
||||
@@ -497,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)
|
||||
@@ -508,7 +527,8 @@ namespace Robust.Shared.GameObjects
|
||||
#endif
|
||||
}
|
||||
|
||||
private void RemoveComponentImmediate(Component component, EntityUid uid, bool terminating)
|
||||
private void RemoveComponentImmediate(Component component, EntityUid uid, bool terminating,
|
||||
MetaDataComponent? meta)
|
||||
{
|
||||
if (component.Deleted)
|
||||
{
|
||||
@@ -528,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
|
||||
}
|
||||
@@ -541,10 +561,7 @@ namespace Robust.Shared.GameObjects
|
||||
_runtimeLog.LogException(e, nameof(RemoveComponentImmediate));
|
||||
}
|
||||
#endif
|
||||
var eventArgs = new RemovedComponentEventArgs(new ComponentEventArgs(component, uid), terminating);
|
||||
ComponentRemoved?.Invoke(eventArgs);
|
||||
_eventBus.OnComponentRemoved(eventArgs);
|
||||
DeleteComponent(uid, component, terminating);
|
||||
DeleteComponent(uid, component, terminating, meta);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -565,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
|
||||
}
|
||||
@@ -579,30 +596,34 @@ namespace Robust.Shared.GameObjects
|
||||
_runtimeLog.LogException(e, nameof(CullRemovedComponents));
|
||||
}
|
||||
#endif
|
||||
var eventArgs = new RemovedComponentEventArgs(new ComponentEventArgs(component, uid), false);
|
||||
ComponentRemoved?.Invoke(eventArgs);
|
||||
_eventBus.OnComponentRemoved(eventArgs);
|
||||
|
||||
DeleteComponent(uid, component, false);
|
||||
}
|
||||
|
||||
_deleteSet.Clear();
|
||||
}
|
||||
|
||||
private void DeleteComponent(EntityUid entityUid, Component component, bool terminating)
|
||||
private void DeleteComponent(EntityUid entityUid, Component component, bool terminating, MetaDataComponent? metadata = null)
|
||||
{
|
||||
var type = component.GetType();
|
||||
var reg = _componentFactory.GetRegistration(type);
|
||||
if (!MetaQuery.ResolveInternal(entityUid, ref metadata))
|
||||
return;
|
||||
|
||||
if (!terminating && reg.NetID != null && _netComponents.TryGetValue(entityUid, out var netSet))
|
||||
var eventArgs = new RemovedComponentEventArgs(new ComponentEventArgs(component, entityUid), false, metadata);
|
||||
ComponentRemoved?.Invoke(eventArgs);
|
||||
_eventBus.OnComponentRemoved(eventArgs);
|
||||
|
||||
var reg = _componentFactory.GetRegistration(component);
|
||||
DebugTools.Assert(component.Networked == (reg.NetID != null));
|
||||
|
||||
if (!terminating && reg.NetID != null)
|
||||
{
|
||||
if (netSet.Count == 1)
|
||||
_netComponents.Remove(entityUid);
|
||||
else
|
||||
netSet.Remove(reg.NetID.Value);
|
||||
if (!metadata.NetComponents.Remove(reg.NetID.Value))
|
||||
_sawmill.Error($"Entity {ToPrettyString(entityUid, metadata)} did not have {component.GetType().Name} in its networked component dictionary during component deletion.");
|
||||
|
||||
if (component.NetSyncEnabled)
|
||||
DirtyEntity(entityUid);
|
||||
{
|
||||
DirtyEntity(entityUid, metadata);
|
||||
metadata.LastComponentRemoved = _gameTiming.CurTick;
|
||||
}
|
||||
}
|
||||
|
||||
_entTraitArray[reg.Idx.Value].Remove(entityUid);
|
||||
@@ -610,6 +631,10 @@ namespace Robust.Shared.GameObjects
|
||||
// TODO if terminating the entity, maybe defer this?
|
||||
// _entCompIndex.Remove(uid) gets called later on anyways.
|
||||
_entCompIndex.Remove(entityUid, component);
|
||||
|
||||
|
||||
DebugTools.Assert(_netMan.IsClient // Client side prediction can set LastComponentRemoved to some future tick,
|
||||
|| metadata.EntityLastModifiedTick >= metadata.LastComponentRemoved);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -651,23 +676,25 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool HasComponent(EntityUid uid, ushort netId)
|
||||
public bool HasComponent(EntityUid uid, ushort netId, MetaDataComponent? meta = null)
|
||||
{
|
||||
return _netComponents.TryGetValue(uid, out var netSet)
|
||||
&& netSet.ContainsKey(netId);
|
||||
if (!MetaQuery.Resolve(uid, ref meta))
|
||||
return false;
|
||||
|
||||
return meta.NetComponents.ContainsKey(netId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool HasComponent(EntityUid? uid, ushort netId)
|
||||
public bool HasComponent(EntityUid? uid, ushort netId, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (!uid.HasValue)
|
||||
{
|
||||
DebugTools.AssertNull(meta);
|
||||
return false;
|
||||
}
|
||||
|
||||
return _netComponents.TryGetValue(uid.Value, out var netSet)
|
||||
&& netSet.ContainsKey(netId);
|
||||
return HasComponent(uid.Value, netId, meta);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -752,9 +779,9 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IComponent GetComponent(EntityUid uid, ushort netId)
|
||||
public IComponent GetComponent(EntityUid uid, ushort netId, MetaDataComponent? meta = null)
|
||||
{
|
||||
return _netComponents[uid][netId];
|
||||
return (meta ?? MetaQuery.GetComponentInternal(uid)).NetComponents[netId];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -763,7 +790,7 @@ namespace Robust.Shared.GameObjects
|
||||
var dict = _entTraitArray[type.Value];
|
||||
if (dict.TryGetValue(uid, out var comp))
|
||||
{
|
||||
return comp;
|
||||
return comp;
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException($"Entity {uid} does not have a component of type {type}");
|
||||
@@ -868,10 +895,10 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetComponent(EntityUid uid, ushort netId, [MaybeNullWhen(false)] out IComponent component)
|
||||
public bool TryGetComponent(EntityUid uid, ushort netId, [MaybeNullWhen(false)] out IComponent component, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (_netComponents.TryGetValue(uid, out var netSet)
|
||||
&& netSet.TryGetValue(netId, out var comp))
|
||||
if (MetaQuery.TryGetComponentInternal(uid, out var metadata)
|
||||
&& metadata.NetComponents.TryGetValue(netId, out var comp))
|
||||
{
|
||||
component = comp;
|
||||
return true;
|
||||
@@ -883,23 +910,16 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, ushort netId,
|
||||
[MaybeNullWhen(false)] out IComponent component)
|
||||
[MaybeNullWhen(false)] out IComponent component, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (!uid.HasValue)
|
||||
{
|
||||
DebugTools.AssertNull(meta);
|
||||
component = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_netComponents.TryGetValue(uid.Value, out var netSet)
|
||||
&& netSet.TryGetValue(netId, out var comp))
|
||||
{
|
||||
component = comp;
|
||||
return true;
|
||||
}
|
||||
|
||||
component = default;
|
||||
return false;
|
||||
return TryGetComponent(uid.Value, netId, out component, meta);
|
||||
}
|
||||
|
||||
public EntityQuery<TComp1> GetEntityQuery<TComp1>() where TComp1 : Component
|
||||
@@ -967,17 +987,18 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetComponentEnumerable GetNetComponents(EntityUid uid)
|
||||
public NetComponentEnumerable GetNetComponents(EntityUid uid, MetaDataComponent? meta = null)
|
||||
{
|
||||
return new NetComponentEnumerable(_netComponents[uid]);
|
||||
meta ??= MetaQuery.GetComponentInternal(uid);
|
||||
return new NetComponentEnumerable(meta.NetComponents);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetComponentEnumerable? GetNetComponentsOrNull(EntityUid uid)
|
||||
public NetComponentEnumerable? GetNetComponentsOrNull(EntityUid uid, MetaDataComponent? meta = null)
|
||||
{
|
||||
return _netComponents.TryGetValue(uid, out var data)
|
||||
? new NetComponentEnumerable(data)
|
||||
: null;
|
||||
return MetaQuery.Resolve(uid, ref meta)
|
||||
? new NetComponentEnumerable(meta.NetComponents)
|
||||
: null;
|
||||
}
|
||||
|
||||
#region Join Functions
|
||||
|
||||
99
Robust.Shared/GameObjects/EntityManager.LifeCycle.cs
Normal file
99
Robust.Shared/GameObjects/EntityManager.LifeCycle.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,22 @@ public partial class EntityManager
|
||||
return true;
|
||||
}
|
||||
|
||||
entity = EntityUid.Invalid;
|
||||
entity = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetEntityData(NetEntity nEntity, [NotNullWhen(true)] out EntityUid? entity, [NotNullWhen(true)] out MetaDataComponent? meta)
|
||||
{
|
||||
if (NetEntityLookup.TryGetValue(nEntity, out var went))
|
||||
{
|
||||
entity = went.Item1;
|
||||
meta = went.Item2;
|
||||
return true;
|
||||
}
|
||||
|
||||
entity = null;
|
||||
meta = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -77,7 +92,7 @@ public partial class EntityManager
|
||||
{
|
||||
if (nEntity == null)
|
||||
{
|
||||
entity = EntityUid.Invalid;
|
||||
entity = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -89,7 +104,7 @@ public partial class EntityManager
|
||||
{
|
||||
if (uid == EntityUid.Invalid)
|
||||
{
|
||||
netEntity = NetEntity.Invalid;
|
||||
netEntity = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -110,7 +125,7 @@ public partial class EntityManager
|
||||
{
|
||||
if (uid == null)
|
||||
{
|
||||
netEntity = NetEntity.Invalid;
|
||||
netEntity = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ public partial class EntityManager
|
||||
ComponentRegistry? overrides = null)
|
||||
{
|
||||
uid = null;
|
||||
if (!_xformQuery.Resolve(target, ref xform))
|
||||
if (!TransformQuery.Resolve(target, ref xform))
|
||||
return false;
|
||||
|
||||
if (!xform.ParentUid.IsValid())
|
||||
@@ -157,7 +157,7 @@ public partial class EntityManager
|
||||
|
||||
public EntityUid SpawnNextToOrDrop(string? protoName, EntityUid target, TransformComponent? xform = null, ComponentRegistry? overrides = null)
|
||||
{
|
||||
xform ??= _xformQuery.GetComponent(target);
|
||||
xform ??= TransformQuery.GetComponent(target);
|
||||
if (!xform.ParentUid.IsValid())
|
||||
return Spawn(protoName);
|
||||
|
||||
@@ -181,7 +181,7 @@ public partial class EntityManager
|
||||
|| !container.Insert(uid, this))
|
||||
{
|
||||
|
||||
xform ??= _xformQuery.GetComponent(containerUid);
|
||||
xform ??= TransformQuery.GetComponent(containerUid);
|
||||
if (xform.ParentUid.IsValid())
|
||||
_xforms.PlaceNextToOrDrop(uid, containerUid, targetXform: xform);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Prometheus;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
@@ -31,13 +33,14 @@ namespace Robust.Shared.GameObjects
|
||||
[IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[IoC.Dependency] private readonly ISerializationManager _serManager = default!;
|
||||
[IoC.Dependency] private readonly ProfManager _prof = default!;
|
||||
[IoC.Dependency] private readonly INetManager _netMan = default!;
|
||||
|
||||
// I feel like PJB might shed me for putting a system dependency here, but its required for setting entity
|
||||
// positions on spawn....
|
||||
private SharedTransformSystem _xforms = default!;
|
||||
|
||||
protected EntityQuery<MetaDataComponent> MetaQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
public EntityQuery<MetaDataComponent> MetaQuery;
|
||||
public EntityQuery<TransformComponent> TransformQuery;
|
||||
|
||||
#endregion Dependencies
|
||||
|
||||
@@ -85,6 +88,9 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private string _xformName = string.Empty;
|
||||
|
||||
private ComponentRegistration _metaReg = default!;
|
||||
private ComponentRegistration _xformReg = default!;
|
||||
|
||||
private SharedMapSystem _mapSystem = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
@@ -111,7 +117,9 @@ namespace Robust.Shared.GameObjects
|
||||
_eventBus = new EntityEventBus(this);
|
||||
|
||||
InitializeComponents();
|
||||
_xformName = _componentFactory.GetComponentName(typeof(TransformComponent));
|
||||
_metaReg = _componentFactory.GetRegistration(typeof(MetaDataComponent));
|
||||
_xformReg = _componentFactory.GetRegistration(typeof(TransformComponent));
|
||||
_xformName = _xformReg.Name;
|
||||
_sawmill = LogManager.GetSawmill("entity");
|
||||
_resolveSawmill = LogManager.GetSawmill("resolve");
|
||||
|
||||
@@ -233,7 +241,7 @@ namespace Robust.Shared.GameObjects
|
||||
_mapSystem = System<SharedMapSystem>();
|
||||
_xforms = System<SharedTransformSystem>();
|
||||
MetaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
TransformQuery = GetEntityQuery<TransformComponent>();
|
||||
}
|
||||
|
||||
public virtual void Shutdown()
|
||||
@@ -303,28 +311,28 @@ 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);
|
||||
_xforms.SetCoordinates(newEntity, _xformQuery.GetComponent(newEntity), coordinates, unanchor: false);
|
||||
var newEntity = CreateEntity(prototypeName, out _, overrides);
|
||||
_xforms.SetCoordinates(newEntity, TransformQuery.GetComponent(newEntity), coordinates, unanchor: false);
|
||||
return newEntity;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
|
||||
{
|
||||
var newEntity = CreateEntity(prototypeName, overrides);
|
||||
var transform = _xformQuery.GetComponent(newEntity);
|
||||
var newEntity = CreateEntity(prototypeName, out _, overrides);
|
||||
var transform = TransformQuery.GetComponent(newEntity);
|
||||
|
||||
if (coordinates.MapId == MapId.Nullspace)
|
||||
{
|
||||
@@ -363,18 +371,8 @@ namespace Robust.Shared.GameObjects
|
||||
public virtual void DirtyEntity(EntityUid uid, MetaDataComponent? metadata = null)
|
||||
{
|
||||
// We want to retrieve MetaDataComponent even if its Deleted flag is set.
|
||||
if (metadata == null)
|
||||
{
|
||||
if (!_entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].TryGetValue(uid, out var component))
|
||||
throw new KeyNotFoundException($"Entity {uid} does not exist, cannot dirty it.");
|
||||
metadata = (MetaDataComponent)component;
|
||||
}
|
||||
else
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
DebugTools.Assert(metadata.Owner == uid);
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
if (!MetaQuery.ResolveInternal(uid, ref metadata))
|
||||
return;
|
||||
|
||||
if (metadata.EntityLastModifiedTick == _gameTiming.CurTick)
|
||||
return;
|
||||
@@ -400,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;
|
||||
}
|
||||
@@ -437,15 +436,20 @@ 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(
|
||||
EntityUid uid,
|
||||
MetaDataComponent metadata)
|
||||
{
|
||||
var transform = _xformQuery.GetComponent(uid);
|
||||
var transform = TransformQuery.GetComponent(uid);
|
||||
metadata.EntityLifeStage = EntityLifeStage.Terminating;
|
||||
|
||||
try
|
||||
@@ -473,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 = _xformQuery.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).
|
||||
@@ -485,7 +492,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
try
|
||||
{
|
||||
_xforms.DetachParentToNull(uid, transform);
|
||||
_xforms.DetachParentToNull(uid, transform, parentXform);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -497,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)
|
||||
{
|
||||
@@ -515,7 +525,7 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
try
|
||||
{
|
||||
component.LifeShutdown(this);
|
||||
LifeShutdown(component);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -540,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)
|
||||
@@ -559,7 +569,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public bool EntityExists(EntityUid uid)
|
||||
{
|
||||
return _entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].ContainsKey(uid);
|
||||
return MetaQuery.HasComponentInternal(uid);
|
||||
}
|
||||
|
||||
public bool EntityExists(EntityUid? uid)
|
||||
@@ -578,12 +588,12 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public bool Deleted(EntityUid uid)
|
||||
{
|
||||
return !_entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].TryGetValue(uid, out var comp) || ((MetaDataComponent) comp).EntityDeleted;
|
||||
return !MetaQuery.TryGetComponentInternal(uid, out var comp) || comp.EntityDeleted;
|
||||
}
|
||||
|
||||
public bool Deleted([NotNullWhen(false)] EntityUid? uid)
|
||||
{
|
||||
return !uid.HasValue || !_entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].TryGetValue(uid.Value, out var comp) || ((MetaDataComponent) comp).EntityDeleted;
|
||||
return !uid.HasValue || !MetaQuery.TryGetComponentInternal(uid.Value, out var comp) || comp.EntityDeleted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -645,10 +655,12 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
Entities.Add(uid);
|
||||
// add the required MetaDataComponent directly.
|
||||
AddComponentInternal(uid, metadata, false, false);
|
||||
AddComponentInternal(uid, metadata, _metaReg, false, true, metadata);
|
||||
|
||||
// allocate the required TransformComponent
|
||||
AddComponent<TransformComponent>(uid);
|
||||
var xformComp = Unsafe.As<TransformComponent>(_componentFactory.GetComponent(_xformReg));
|
||||
xformComp.Owner = uid;
|
||||
AddComponentInternal(uid, xformComp, false, true, metadata);
|
||||
|
||||
return 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);
|
||||
@@ -706,7 +718,7 @@ namespace Robust.Shared.GameObjects
|
||||
StartEntity(entity);
|
||||
|
||||
// If the map we're initializing the entity on is initialized, run map init on it.
|
||||
if (_mapManager.IsMapInitialized(mapId ?? _xformQuery.GetComponent(entity).MapID))
|
||||
if (_mapManager.IsMapInitialized(mapId ?? TransformQuery.GetComponent(entity).MapID))
|
||||
RunMapInit(entity, meta);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -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
|
||||
|
||||
@@ -61,7 +61,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Deleted(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
return true;
|
||||
|
||||
return metaData.EntityDeleted;
|
||||
@@ -73,7 +73,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TerminatingOrDeleted(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
return true;
|
||||
|
||||
return metaData.EntityLifeStage >= EntityLifeStage.Terminating;
|
||||
@@ -104,7 +104,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected EntityLifeStage LifeStage(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
throw CompNotFound<MetaDataComponent>(uid);
|
||||
|
||||
return metaData.EntityLifeStage;
|
||||
@@ -169,7 +169,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryLifeStage(EntityUid uid, [NotNullWhen(true)] out EntityLifeStage? lifeStage, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
{
|
||||
lifeStage = null;
|
||||
return false;
|
||||
@@ -227,7 +227,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected string Name(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if(!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
throw CompNotFound<MetaDataComponent>(uid);
|
||||
|
||||
return metaData.EntityName;
|
||||
@@ -240,7 +240,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected string Description(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if(!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
throw CompNotFound<MetaDataComponent>(uid);
|
||||
|
||||
return metaData.EntityDescription;
|
||||
@@ -253,7 +253,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected EntityPrototype? Prototype(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
throw CompNotFound<MetaDataComponent>(uid);
|
||||
|
||||
return metaData.EntityPrototype;
|
||||
@@ -265,7 +265,7 @@ public partial class EntitySystem
|
||||
/// <exception cref="KeyNotFoundException">Thrown when the entity doesn't exist.</exception>
|
||||
protected GameTick LastModifiedTick(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
throw CompNotFound<MetaDataComponent>(uid);
|
||||
|
||||
return metaData.EntityLastModifiedTick;
|
||||
@@ -278,7 +278,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Paused(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
throw CompNotFound<MetaDataComponent>(uid);
|
||||
|
||||
return metaData.EntityPaused;
|
||||
@@ -291,7 +291,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void SetPaused(EntityUid uid, bool paused, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
throw CompNotFound<MetaDataComponent>(uid);
|
||||
|
||||
EntityManager.EntitySysManager.GetEntitySystem<MetaDataSystem>().SetEntityPaused(uid, paused, metaData);
|
||||
@@ -302,12 +302,12 @@ public partial class EntitySystem
|
||||
/// </summary>
|
||||
/// <returns>Whether the operation succeeded.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryDirty(EntityUid uid)
|
||||
protected bool TryDirty(EntityUid uid, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Exists(uid))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
return false;
|
||||
|
||||
DirtyEntity(uid);
|
||||
DirtyEntity(uid, metaData);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryName(EntityUid uid, [NotNullWhen(true)] out string? name, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
{
|
||||
name = null;
|
||||
return false;
|
||||
@@ -335,7 +335,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryDescription(EntityUid uid, [NotNullWhen(true)] out string? description, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
{
|
||||
description = null;
|
||||
return false;
|
||||
@@ -352,7 +352,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryPrototype(EntityUid uid, [NotNullWhen(true)] out EntityPrototype? prototype, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
{
|
||||
prototype = null;
|
||||
return false;
|
||||
@@ -369,7 +369,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryLastModifiedTick(EntityUid uid, [NotNullWhen(true)] out GameTick? lastModifiedTick, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
{
|
||||
lastModifiedTick = null;
|
||||
return false;
|
||||
@@ -386,7 +386,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryPaused(EntityUid uid, [NotNullWhen(true)] out bool? paused, MetaDataComponent? metaData = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metaData, false))
|
||||
if (!EntityManager.MetaQuery.Resolve(uid, ref metaData, false))
|
||||
{
|
||||
paused = null;
|
||||
return false;
|
||||
@@ -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
|
||||
|
||||
@@ -458,6 +463,20 @@ public partial class EntitySystem
|
||||
return EntityManager.TryGetComponent(uid, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryGetComponent<T>(EntityUid, out T)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryComp(EntityUid uid, [NotNullWhen(true)] out TransformComponent? comp)
|
||||
{
|
||||
return EntityManager.TransformQuery.TryGetComponent(uid, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryGetComponent<T>(EntityUid, out T)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryComp(EntityUid uid, [NotNullWhen(true)] out MetaDataComponent? comp)
|
||||
{
|
||||
return EntityManager.MetaQuery.TryGetComponent(uid, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryGetComponent<T>(EntityUid?, out T)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryComp<T>([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out T? comp)
|
||||
@@ -471,6 +490,30 @@ public partial class EntitySystem
|
||||
return EntityManager.TryGetComponent(uid.Value, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryGetComponent<T>(EntityUid?, out T)"/>
|
||||
protected bool TryComp([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TransformComponent? comp)
|
||||
{
|
||||
if (!uid.HasValue)
|
||||
{
|
||||
comp = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
return EntityManager.TransformQuery.TryGetComponent(uid.Value, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryGetComponent<T>(EntityUid?, out T)"/>
|
||||
protected bool TryComp([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out MetaDataComponent? comp)
|
||||
{
|
||||
if (!uid.HasValue)
|
||||
{
|
||||
comp = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
return EntityManager.MetaQuery.TryGetComponent(uid.Value, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.GetComponents"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected IEnumerable<IComponent> AllComps(EntityUid uid)
|
||||
@@ -492,7 +535,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected TransformComponent Transform(EntityUid uid)
|
||||
{
|
||||
return EntityManager.GetComponent<TransformComponent>(uid);
|
||||
return EntityManager.TransformQuery.GetComponent(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -502,7 +545,7 @@ public partial class EntitySystem
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected MetaDataComponent MetaData(EntityUid uid)
|
||||
{
|
||||
return EntityManager.GetComponent<MetaDataComponent>(uid);
|
||||
return EntityManager.MetaQuery.GetComponent(uid);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -511,6 +554,13 @@ public partial class EntitySystem
|
||||
return EntityManager.GetEntityData(nuid);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryGetEntityData(NetEntity nuid, [NotNullWhen(true)] out EntityUid? uid,
|
||||
[NotNullWhen(true)] out MetaDataComponent? meta)
|
||||
{
|
||||
return EntityManager.TryGetEntityData(nuid, out uid, out meta);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Component Has
|
||||
@@ -915,9 +965,9 @@ public partial class EntitySystem
|
||||
#region NetEntities
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool IsClientSide(EntityUid entity)
|
||||
protected bool IsClientSide(EntityUid entity, MetaDataComponent? meta = null)
|
||||
{
|
||||
return EntityManager.IsClientSide(entity);
|
||||
return EntityManager.IsClientSide(entity, meta);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -27,11 +27,27 @@ 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Resolve{TComp}"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref MetaDataComponent? component,
|
||||
bool logMissing = true)
|
||||
{
|
||||
return EntityManager.MetaQuery.Resolve(uid, ref component);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Resolve{TComp}"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TransformComponent? component,
|
||||
bool logMissing = true)
|
||||
{
|
||||
return EntityManager.TransformQuery.Resolve(uid, ref component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the components on the entity for the null component references.
|
||||
/// </summary>
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Adds a Component with a given network id to an entity.
|
||||
/// </summary>
|
||||
Component AddComponent(EntityUid uid, ushort netId);
|
||||
Component AddComponent(EntityUid uid, ushort netId, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Adds an uninitialized Component type to an entity.
|
||||
@@ -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>
|
||||
@@ -70,7 +71,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="uid">Entity being modified.</param>
|
||||
/// <param name="component">Component to add.</param>
|
||||
/// <param name="overwrite">Should it overwrite existing components?</param>
|
||||
void AddComponent<T>(EntityUid uid, T component, bool overwrite = false) where T : Component;
|
||||
void AddComponent<T>(EntityUid uid, T component, bool overwrite = false, MetaDataComponent? metadata = null) where T : Component;
|
||||
|
||||
/// <summary>
|
||||
/// Removes the component with the specified reference type,
|
||||
@@ -78,7 +79,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The component reference type to remove.</typeparam>
|
||||
/// <param name="uid">Entity UID to modify.</param>
|
||||
bool RemoveComponent<T>(EntityUid uid);
|
||||
bool RemoveComponent<T>(EntityUid uid, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the component with a specified type.
|
||||
@@ -86,7 +87,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="uid">Entity UID to modify.</param>
|
||||
/// <param name="type">A trait or component type to check for.</param>
|
||||
/// <returns>Returns false if the entity did not have the specified component.</returns>
|
||||
bool RemoveComponent(EntityUid uid, Type type);
|
||||
bool RemoveComponent(EntityUid uid, Type type, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the component with a specified network ID.
|
||||
@@ -94,14 +95,14 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="uid">Entity UID to modify.</param>
|
||||
/// <param name="netID">Network ID of the component to remove.</param>
|
||||
/// <returns>Returns false if the entity did not have the specified component.</returns>
|
||||
bool RemoveComponent(EntityUid uid, ushort netID);
|
||||
bool RemoveComponent(EntityUid uid, ushort netID, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified component. Throws if the given component does not belong to the entity.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity UID to modify.</param>
|
||||
/// <param name="component">Component to remove.</param>
|
||||
void RemoveComponent(EntityUid uid, IComponent component);
|
||||
void RemoveComponent(EntityUid uid, IComponent component, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Immediately shuts down a component, but defers the removal and deletion until the end of the tick.
|
||||
@@ -125,7 +126,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="uid">Entity UID to modify.</param>
|
||||
/// <param name="netID">Network ID of the component to remove.</param>
|
||||
/// <returns>Returns false if the entity did not have the specified component.</returns>
|
||||
bool RemoveComponentDeferred(EntityUid uid, ushort netID);
|
||||
bool RemoveComponentDeferred(EntityUid uid, ushort netID, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Immediately shuts down a component, but defers the removal and deletion until the end of the tick.
|
||||
@@ -147,7 +148,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// Removes all components from an entity, except the required components.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity UID to modify.</param>
|
||||
void RemoveComponents(EntityUid uid);
|
||||
void RemoveComponents(EntityUid uid, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Removes ALL components from an entity. This includes the required components,
|
||||
@@ -155,7 +156,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// used when deleting an entity.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity UID to modify.</param>
|
||||
void DisposeComponents(EntityUid uid);
|
||||
void DisposeComponents(EntityUid uid, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the entity has a component type.
|
||||
@@ -187,7 +188,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="uid">Entity UID to check.</param>
|
||||
/// <param name="type">A trait or component type to check for.</param>
|
||||
/// <returns>True if the entity has the component type, otherwise false.</returns>
|
||||
bool HasComponent(EntityUid ?uid, Type type);
|
||||
bool HasComponent(EntityUid? uid, Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the entity has a component with a given network ID. This does not check
|
||||
@@ -196,7 +197,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="uid">Entity UID to check.</param>
|
||||
/// <param name="netId">Network ID to check for.</param>
|
||||
/// <returns>True if the entity has a component with the given network ID, otherwise false.</returns>
|
||||
bool HasComponent(EntityUid uid, ushort netId);
|
||||
bool HasComponent(EntityUid uid, ushort netId, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the entity has a component with a given network ID. This does not check
|
||||
@@ -205,7 +206,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="uid">Entity UID to check.</param>
|
||||
/// <param name="netId">Network ID to check for.</param>
|
||||
/// <returns>True if the entity has a component with the given network ID, otherwise false.</returns>
|
||||
bool HasComponent(EntityUid? uid, ushort netId);
|
||||
bool HasComponent(EntityUid? uid, ushort netId, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// This method will always return a component for a certain entity, adding it if it's not there already.
|
||||
@@ -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>
|
||||
@@ -255,7 +256,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="uid">Entity UID to look on.</param>
|
||||
/// <param name="netId">Network ID of the component to retrieve.</param>
|
||||
/// <returns>The component with the specified network id.</returns>
|
||||
IComponent GetComponent(EntityUid uid, ushort netId);
|
||||
IComponent GetComponent(EntityUid uid, ushort netId, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the component of a specific type, even if it has been marked for deletion.
|
||||
@@ -318,7 +319,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="netId">Component Network ID to check for.</param>
|
||||
/// <param name="component">Component with the specified network id.</param>
|
||||
/// <returns>If the component existed in the entity.</returns>
|
||||
bool TryGetComponent(EntityUid uid, ushort netId, [NotNullWhen(true)] out IComponent? component);
|
||||
bool TryGetComponent(EntityUid uid, ushort netId, [NotNullWhen(true)] out IComponent? component, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the component with a specified network ID. This does not check
|
||||
@@ -328,7 +329,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="netId">Component Network ID to check for.</param>
|
||||
/// <param name="component">Component with the specified network id.</param>
|
||||
/// <returns>If the component existed in the entity.</returns>
|
||||
bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, ushort netId, [NotNullWhen(true)] out IComponent? component);
|
||||
bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, ushort netId, [NotNullWhen(true)] out IComponent? component, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a cached struct enumerator with the specified component.
|
||||
@@ -364,7 +365,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity UID to look on.</param>
|
||||
/// <returns>All components that have a network ID.</returns>
|
||||
NetComponentEnumerable GetNetComponents(EntityUid uid);
|
||||
NetComponentEnumerable GetNetComponents(EntityUid uid, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns ALL networked components on an entity, including deleted ones. Returns null if the entity does
|
||||
@@ -372,7 +373,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity UID to look on.</param>
|
||||
/// <returns>All components that have a network ID.</returns>
|
||||
public NetComponentEnumerable? GetNetComponentsOrNull(EntityUid uid);
|
||||
public NetComponentEnumerable? GetNetComponentsOrNull(EntityUid uid, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a component state.
|
||||
|
||||
@@ -22,6 +22,12 @@ public partial interface IEntityManager
|
||||
/// </summary>
|
||||
public bool TryGetEntity(NetEntity? nEntity, [NotNullWhen(true)] out EntityUid? entity);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to returns the corresponding local <see cref="EntityUid"/> along with the metdata component.
|
||||
/// </summary>
|
||||
public bool TryGetEntityData(NetEntity nEntity, [NotNullWhen(true)] out EntityUid? entity,
|
||||
[NotNullWhen(true)] out MetaDataComponent? meta);
|
||||
|
||||
/// <summary>
|
||||
/// TryGet version of <see cref="GetNetEntity"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -4,6 +4,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
@@ -47,7 +48,14 @@ public readonly struct NetEntity : IEquatable<NetEntity>, IComparable<NetEntity>
|
||||
/// </summary>
|
||||
public static NetEntity Parse(ReadOnlySpan<char> uid)
|
||||
{
|
||||
return new NetEntity(int.Parse(uid));
|
||||
if (uid[0] != 'c')
|
||||
return new NetEntity(int.Parse(uid));
|
||||
|
||||
if (uid.Length == 1)
|
||||
throw new FormatException($"'c' is not a valid NetEntity");
|
||||
|
||||
var id = int.Parse(uid.Slice(1));
|
||||
return new NetEntity(id | ClientEntity);
|
||||
}
|
||||
|
||||
public static bool TryParse(ReadOnlySpan<char> uid, out NetEntity entity)
|
||||
@@ -121,6 +129,9 @@ public readonly struct NetEntity : IEquatable<NetEntity>, IComparable<NetEntity>
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
if (IsClientSide())
|
||||
return $"c{Id & ~ClientEntity}";
|
||||
|
||||
return Id.ToString();
|
||||
}
|
||||
|
||||
@@ -135,6 +146,14 @@ public readonly struct NetEntity : IEquatable<NetEntity>, IComparable<NetEntity>
|
||||
ReadOnlySpan<char> format,
|
||||
IFormatProvider? provider)
|
||||
{
|
||||
if (IsClientSide())
|
||||
{
|
||||
return FormatHelpers.TryFormatInto(
|
||||
destination,
|
||||
out charsWritten,
|
||||
$"c{Id & ~ClientEntity}");
|
||||
}
|
||||
|
||||
return Id.TryFormat(destination, out charsWritten);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
@@ -495,7 +491,7 @@ public abstract partial class SharedTransformSystem
|
||||
throw new InvalidOperationException($"Attempted to parent entity {ToPrettyString(uid)} to non-existent entity {value.EntityId}");
|
||||
}
|
||||
|
||||
if (newParent.LifeStage > ComponentLifeStage.Running || LifeStage(value.EntityId) > EntityLifeStage.MapInitialized)
|
||||
if (newParent.LifeStage >= ComponentLifeStage.Stopping || LifeStage(value.EntityId) >= EntityLifeStage.Terminating)
|
||||
{
|
||||
DetachParentToNull(uid, xform);
|
||||
if (_netMan.IsServer || IsClientSide(uid))
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
43
Robust.Shared/Localization/LocId.cs
Normal file
43
Robust.Shared/Localization/LocId.cs
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public record struct PhysicsHull()
|
||||
e.Normalize();
|
||||
|
||||
// discard points left of e and find point furthest to the right of e
|
||||
var rightPoints = new Vector2[PhysicsConstants.MaxPolygonVertices];
|
||||
Span<Vector2> rightPoints = stackalloc Vector2[PhysicsConstants.MaxPolygonVertices];
|
||||
var rightCount = 0;
|
||||
|
||||
var bestIndex = 0;
|
||||
|
||||
@@ -24,6 +24,8 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -131,7 +133,7 @@ namespace Robust.Shared.Physics
|
||||
|
||||
aabb ??= _extractAabb(item);
|
||||
|
||||
if (HasNaNs(aabb.Value))
|
||||
if (aabb.Value.HasNan())
|
||||
{
|
||||
_nodeLookup[item] = DynamicTree.Proxy.Free;
|
||||
return true;
|
||||
@@ -179,28 +181,25 @@ namespace Robust.Shared.Physics
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public bool Update(in T item, Box2? newBox = null)
|
||||
{
|
||||
if (!TryGetProxy(item, out var proxy))
|
||||
{
|
||||
ref var proxy = ref CollectionsMarshal.GetValueRefOrNullRef(_nodeLookup, item);
|
||||
if (Unsafe.IsNullRef(ref proxy))
|
||||
return false;
|
||||
}
|
||||
|
||||
newBox ??= _extractAabb(item);
|
||||
|
||||
if (HasNaNs(newBox.Value))
|
||||
if (newBox.Value.HasNan())
|
||||
{
|
||||
if (proxy == DynamicTree.Proxy.Free)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_b2Tree.DestroyProxy(proxy);
|
||||
_nodeLookup[item] = DynamicTree.Proxy.Free;
|
||||
proxy = DynamicTree.Proxy.Free;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (proxy == DynamicTree.Proxy.Free)
|
||||
{
|
||||
_nodeLookup[item] = _b2Tree.CreateProxy(newBox.Value, item);
|
||||
proxy = _b2Tree.CreateProxy(newBox.Value, item);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -331,36 +330,30 @@ namespace Robust.Shared.Physics
|
||||
public void AddOrUpdate(T item, Box2? aabb = null)
|
||||
{
|
||||
aabb ??= _extractAabb(item);
|
||||
if (!_nodeLookup.TryGetValue(item, out var proxy))
|
||||
|
||||
ref var proxy = ref CollectionsMarshal.GetValueRefOrAddDefault(_nodeLookup, item, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
_nodeLookup[item] = HasNaNs(aabb.Value) ? DynamicTree.Proxy.Free : _b2Tree.CreateProxy(aabb.Value, item);
|
||||
proxy = aabb.Value.HasNan() ? DynamicTree.Proxy.Free : _b2Tree.CreateProxy(aabb.Value, item);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasNaNs(aabb.Value))
|
||||
if (aabb.Value.HasNan())
|
||||
{
|
||||
if (proxy == DynamicTree.Proxy.Free)
|
||||
return;
|
||||
|
||||
_b2Tree.DestroyProxy(proxy);
|
||||
_nodeLookup[item] = DynamicTree.Proxy.Free;
|
||||
proxy = DynamicTree.Proxy.Free;
|
||||
return;
|
||||
}
|
||||
|
||||
if (proxy == DynamicTree.Proxy.Free)
|
||||
_nodeLookup[item] = _b2Tree.CreateProxy(aabb.Value, item);
|
||||
proxy = _b2Tree.CreateProxy(aabb.Value, item);
|
||||
else
|
||||
_b2Tree.MoveProxy(proxy, aabb.Value, Vector2.Zero);
|
||||
}
|
||||
|
||||
private static bool HasNaNs(in Box2 box)
|
||||
{
|
||||
return float.IsNaN(box.Left)
|
||||
|| float.IsNaN(box.Top)
|
||||
|| float.IsNaN(box.Bottom)
|
||||
|| float.IsNaN(box.Right);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG_DYNAMIC_TREE")]
|
||||
[Conditional("DEBUG_DYNAMIC_TREE_ASSERTS")]
|
||||
[DebuggerNonUserCode]
|
||||
|
||||
@@ -365,5 +365,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
/// Is this a special contact for grid-grid collisions
|
||||
/// </summary>
|
||||
Grid = 1 << 2,
|
||||
|
||||
Deleting = 1 << 3,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,8 +189,6 @@ namespace Robust.Shared.Physics.Systems
|
||||
_lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase, physicsMap);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (updates)
|
||||
{
|
||||
var resetMass = fixture.Density > 0f;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
|
||||
namespace Robust.Shared.Prototypes;
|
||||
|
||||
@@ -10,7 +12,8 @@ namespace Robust.Shared.Prototypes;
|
||||
/// This will be automatically validated by <see cref="EntProtoIdSerializer"/> if used in data fields.
|
||||
/// </remarks>
|
||||
/// <remarks><seealso cref="ProtoId{T}"/> for a wrapper of other prototype kinds.</remarks>
|
||||
public readonly record struct EntProtoId(string Id)
|
||||
[Serializable, NetSerializable]
|
||||
public readonly record struct EntProtoId(string Id) : IEquatable<string>, IComparable<EntProtoId>
|
||||
{
|
||||
public static implicit operator string(EntProtoId protoId)
|
||||
{
|
||||
@@ -21,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;
|
||||
}
|
||||
|
||||
25
Robust.Shared/Prototypes/EntityCategoryPrototype.cs
Normal file
25
Robust.Shared/Prototypes/EntityCategoryPrototype.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.Prototypes;
|
||||
|
||||
/// <summary>
|
||||
/// Prototype that represents game entities.
|
||||
/// </summary>
|
||||
[Prototype("entityCategory")]
|
||||
public sealed class EntityCategoryPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Localized name of the category, for use in entity spawn menus.
|
||||
/// </summary>
|
||||
[DataField("name")]
|
||||
public string? Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Localized description of the category, for use in entity spawn menus.
|
||||
/// </summary>
|
||||
[DataField("description")]
|
||||
public string? Description { get; private set; }
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -12,6 +11,7 @@ using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Prototypes
|
||||
@@ -27,6 +27,9 @@ namespace Robust.Shared.Prototypes
|
||||
|
||||
private static readonly Dictionary<string, string> LocPropertiesDefault = new();
|
||||
|
||||
[ValidatePrototypeId<EntityCategoryPrototype>]
|
||||
private const string HideCategory = "hideSpawnMenu";
|
||||
|
||||
// LOCALIZATION NOTE:
|
||||
// Localization-related properties in here are manually localized in LocalizationManager.
|
||||
// As such, they should NOT be inherited to avoid confusing the system.
|
||||
@@ -57,6 +60,10 @@ namespace Robust.Shared.Prototypes
|
||||
[DataField("suffix")]
|
||||
public string? SetSuffix { get; private set; }
|
||||
|
||||
[DataField("categories")]
|
||||
[AlwaysPushInheritance]
|
||||
public HashSet<string> Categories = new();
|
||||
|
||||
[ViewVariables]
|
||||
public IReadOnlyDictionary<string, string> LocProperties => _locPropertiesSet ?? LocPropertiesDefault;
|
||||
|
||||
@@ -92,8 +99,11 @@ namespace Robust.Shared.Prototypes
|
||||
[ViewVariables]
|
||||
[NeverPushInheritance]
|
||||
[DataField("noSpawn")]
|
||||
[Obsolete("Use the HideSpawnMenu")]
|
||||
public bool NoSpawn { get; private set; }
|
||||
|
||||
public bool HideSpawnMenu => Categories.Contains(HideCategory) || NoSpawn;
|
||||
|
||||
[DataField("placement")]
|
||||
private EntityPlacementProperties PlacementProperties = new();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
|
||||
using System;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
|
||||
|
||||
namespace Robust.Shared.Prototypes;
|
||||
|
||||
@@ -11,7 +12,8 @@ namespace Robust.Shared.Prototypes;
|
||||
/// This will be automatically validated by <see cref="ProtoIdSerializer{T}"/> if used in data fields.
|
||||
/// </remarks>
|
||||
/// <remarks><seealso cref="EntProtoId"/> for an <see cref="EntityPrototype"/> alias.</remarks>
|
||||
public readonly record struct ProtoId<T>(string Id) where T : class, IPrototype
|
||||
[Serializable]
|
||||
public readonly record struct ProtoId<T>(string Id) : IEquatable<string>, IComparable<ProtoId<T>> where T : class, IPrototype
|
||||
{
|
||||
public static implicit operator string(ProtoId<T> protoId)
|
||||
{
|
||||
@@ -22,4 +24,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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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<A>(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<B>(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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,14 +112,40 @@ public partial class SerializationManager
|
||||
}, (node, this));
|
||||
}
|
||||
|
||||
private SequenceDataNode PushInheritanceSequence(SequenceDataNode child, SequenceDataNode _)
|
||||
private SequenceDataNode PushInheritanceSequence(SequenceDataNode child, SequenceDataNode parent)
|
||||
{
|
||||
return child; //todo implement different inheritancebehaviours for yamlfield
|
||||
//todo implement different inheritancebehaviours for yamlfield
|
||||
// I have NFI what this comment means.
|
||||
|
||||
var result = new SequenceDataNode(child.Count + parent.Count);
|
||||
foreach (var entry in parent)
|
||||
{
|
||||
result.Add(entry);
|
||||
}
|
||||
foreach (var entry in child)
|
||||
{
|
||||
result.Add(entry);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private MappingDataNode PushInheritanceMapping(MappingDataNode child, MappingDataNode _)
|
||||
private MappingDataNode PushInheritanceMapping(MappingDataNode child, MappingDataNode parent)
|
||||
{
|
||||
return child; //todo implement different inheritancebehaviours for yamlfield
|
||||
//todo implement different inheritancebehaviours for yamlfield
|
||||
// I have NFI what this comment means.
|
||||
|
||||
var result = new MappingDataNode(child.Count + parent.Count);
|
||||
foreach (var (k, v) in parent)
|
||||
{
|
||||
result[k] = v;
|
||||
}
|
||||
foreach (var (k, v) in child)
|
||||
{
|
||||
result[k] = v;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private MappingDataNode PushInheritanceDefinition(MappingDataNode child, MappingDataNode parent,
|
||||
|
||||
@@ -0,0 +1,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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
45
Robust.Shared/Spawners/SharedTimedDespawnSystem.cs
Normal file
45
Robust.Shared/Spawners/SharedTimedDespawnSystem.cs
Normal 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);
|
||||
}
|
||||
20
Robust.Shared/Spawners/TimedDespawnComponent.cs
Normal file
20
Robust.Shared/Spawners/TimedDespawnComponent.cs
Normal 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;
|
||||
}
|
||||
9
Robust.Shared/Spawners/TimedDespawnEvent.cs
Normal file
9
Robust.Shared/Spawners/TimedDespawnEvent.cs
Normal 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;
|
||||
@@ -34,7 +34,7 @@ public sealed class AngleTypeParser : TypeParser<Angle>
|
||||
|
||||
if (word.EndsWith("deg"))
|
||||
{
|
||||
if (!float.TryParse(word[..^3], out var f))
|
||||
if (!float.TryParse(word[..^3], CultureInfo.InvariantCulture, out var f))
|
||||
{
|
||||
error = new InvalidAngle(word);
|
||||
result = null;
|
||||
@@ -47,7 +47,7 @@ public sealed class AngleTypeParser : TypeParser<Angle>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!float.TryParse(word, out var f))
|
||||
if (!float.TryParse(word, CultureInfo.InvariantCulture, out var f))
|
||||
{
|
||||
error = new InvalidAngle(word);
|
||||
result = null;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Utility
|
||||
{
|
||||
@@ -36,6 +38,70 @@ namespace Robust.Shared.Utility
|
||||
throw new DebugAssertException();
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[AssertionMethod]
|
||||
public static void AssertEqual(object? objA, object? objB)
|
||||
{
|
||||
if (ReferenceEquals(objA, objB))
|
||||
return;
|
||||
|
||||
if (objA == null || !objA.Equals(objB))
|
||||
throw new DebugAssertException($"Expected: {objB ?? "null"} but was {objA ?? "null"}");
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[AssertionMethod]
|
||||
public static void AssertEqual(object? objA, object? objB, string message)
|
||||
{
|
||||
if (ReferenceEquals(objA, objB))
|
||||
return;
|
||||
|
||||
if (objA == null || !objA.Equals(objB))
|
||||
throw new DebugAssertException($"{message}\nExpected: {objB ?? "null"} but was {objA ?? "null"}");
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[AssertionMethod]
|
||||
public static void AssertNotEqual(object? objA, object? objB)
|
||||
{
|
||||
if (ReferenceEquals(objA, objB))
|
||||
throw new DebugAssertException($"Expected: not {objB ?? "null"}");
|
||||
|
||||
if (objA == null || !objA.Equals(objB))
|
||||
return;
|
||||
|
||||
throw new DebugAssertException($"Expected: not {objB}");
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[AssertionMethod]
|
||||
public static void AssertNotEqual(object? objA, object? objB, string message)
|
||||
{
|
||||
if (ReferenceEquals(objA, objB))
|
||||
throw new DebugAssertException($"{message}\nExpected: not {objB ?? "null"}");
|
||||
|
||||
if (objA == null || !objA.Equals(objB))
|
||||
return;
|
||||
|
||||
throw new DebugAssertException($"{message}\nExpected: not {objB}");
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[AssertionMethod]
|
||||
public static void AssertOwner(EntityUid? uid, IComponent? component)
|
||||
{
|
||||
if (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.
|
||||
// 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}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An assertion that will <see langword="throw" /> an exception if the
|
||||
/// <paramref name="condition" /> is not true.
|
||||
|
||||
@@ -43,14 +43,7 @@ namespace Robust.Shared.Utility
|
||||
public bool Add(TKey key, TValue value)
|
||||
{
|
||||
InitializedCheck();
|
||||
|
||||
if (_index.TryGetValue(key, out var set))
|
||||
{
|
||||
return set.Add(value);
|
||||
}
|
||||
|
||||
_index.Add(key, new HashSet<TValue> {value});
|
||||
return true;
|
||||
return _index.GetOrNew(key).Add(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user