Compare commits

...

49 Commits

Author SHA1 Message Date
metalgearsloth
415c518bc7 Version: 162.0.0 2023-09-19 17:40:46 +10:00
Leon Friedrich
2417dbb0e0 Use CollectionsMarshal in DynamicTree (#4433) 2023-09-19 15:58:02 +10:00
Leon Friedrich
005673a957 Add force ack threshold (#4423) 2023-09-19 15:16:01 +10:00
Leon Friedrich
942db3120c Make entity system proxy methods use Metadata & Transform queries (#4434) 2023-09-19 15:11:21 +10:00
Leon Friedrich
c0a5fab19e Add missing EntitySystem - EntityManager proxy method (#4427) 2023-09-18 12:17:46 +10:00
Leon Friedrich
d16c62b132 Use CollectionsMarshal in PVS (#4428) 2023-09-18 12:17:32 +10:00
Leon Friedrich
7da22557fe Add entity categories (#4356) 2023-09-18 12:14:26 +10:00
metalgearsloth
92f47c0f20 Version: 161.1.0 2023-09-18 11:48:03 +10:00
Leon Friedrich
9576d0739f Add more DebugTools assert variants (#4425) 2023-09-18 11:18:35 +10:00
Leon Friedrich
10f25faabf Try fix oldest ack issues (#4426) 2023-09-18 11:17:49 +10:00
Leon Friedrich
74831a177e Don't attempt to insert entities into deleted containers (#4424) 2023-09-18 10:57:14 +10:00
ElectroJr
88d3168913 Version: 161.0.0 2023-09-17 13:56:34 -04:00
Leon Friedrich
902519093c Add PVS debug command (#4422) 2023-09-18 03:49:57 +10:00
Leon Friedrich
366266a8ae Fix light animations not working (#4413) 2023-09-18 03:49:47 +10:00
Leon Friedrich
a22cce7783 Fix metadata assert (#4419) 2023-09-17 17:18:44 +10:00
metalgearsloth
e5e738b8cd Maybe fix heisentest (#4418) 2023-09-17 16:13:37 +10:00
metalgearsloth
b8f6e83473 Use stackalloc Span<Vector2> in ComputeHull (#4417) 2023-09-17 12:41:53 +10:00
Artur
c5fb186c57 Add missing InvariantCulture to AngleTypeParser (#4411) 2023-09-17 11:32:01 +10:00
Leon Friedrich
131d7f5422 Add string formatting for client-side NetEntity ids (#4415) 2023-09-17 11:29:54 +10:00
Leon Friedrich
217996f1ed Use ToPrettyString() in state error logs. (#4416) 2023-09-17 11:29:02 +10:00
DrSmugleaf
fc718d68a5 Fix IClydeWindow resized xmldoc (#4414) 2023-09-17 11:28:43 +10:00
Leon Friedrich
d7d9578803 Mark EntPrototId as NetSerializable (#4412) 2023-09-17 11:25:40 +10:00
metalgearsloth
9bbeb54569 Version: 160.1.0 2023-09-17 11:20:03 +10:00
metalgearsloth
1ea7071ffb Backport some arch comp net changes (#4408)
Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2023-09-17 11:03:11 +10:00
ElectroJr
196028b619 Version: 160.0.2 2023-09-16 14:50:34 -04:00
ElectroJr
c102da052f Make VV fields private 2023-09-16 14:47:27 -04:00
metalgearsloth
5d46cdcfa4 Add transform parent + container VVs (#4407) 2023-09-16 17:08:08 +10:00
metalgearsloth
cd646d3b07 Version: 160.0.0 2023-09-16 15:26:56 +10:00
Leon Friedrich
922165fa19 Include sensors in default entity lookups (#4406) 2023-09-16 15:22:54 +10:00
metalgearsloth
4879252e99 Remove comprefs entirely (#4367) 2023-09-15 21:52:47 +10:00
DrSmugleaf
0e21f5727a Version: 159.1.0 2023-09-15 03:41:17 -07:00
Leon Friedrich
3ce8a00389 Store metadata component in NetEntity lookup dictionary (#4400)
Co-authored-by: DrSmugleaf <drsmugleaf@gmail.com>
2023-09-15 02:01:44 -07:00
Leon Friedrich
9a283fe541 PVS NetEntity related changes (#4399) 2023-09-15 01:47:35 -07:00
Leon Friedrich
f3e3e64db3 Re-add entity check to fixture equality (#4397) 2023-09-15 18:44:24 +10:00
Leon Friedrich
4a4a135089 Make ToPrettyString() take in nullable EntityUids (#4396) 2023-09-15 01:43:39 -07:00
metalgearsloth
e323a67806 Remove 1 pvs getcomp (#4398) 2023-09-15 15:28:16 +10:00
Leon Friedrich
3a328ffdd5 More NetEntity VV fixes (#4395) 2023-09-15 13:37:18 +10:00
Leon Friedrich
bc5107e297 Misc NetEntity fixes for VV (#4390) 2023-09-15 10:11:28 +10:00
metalgearsloth
2abf33c9be Queue LightTree update on light states (#4385)
Might fix some more issues.
2023-09-15 06:03:25 +12:00
metalgearsloth
71c46828c2 Minor re-apply net state change (#4392)
Slightly faster than checking deleted first, at least for now.
2023-09-15 05:56:51 +12:00
DrSmugleaf
814d6fe2d0 Add ProtoId, EntProtoId and serializers (#4387) 2023-09-12 23:19:40 -07:00
DrSmugleaf
77b98b8308 Add ProtoId, EntProtoId and serializers (#4386) 2023-09-13 14:32:01 +10:00
metalgearsloth
34b0a7fc6d Fix tooltip bounds (#4384) 2023-09-13 13:23:34 +10:00
DrSmugleaf
1b6123c79f Make data field tags optional (#4382) 2023-09-13 12:58:46 +10:00
DrSmugleaf
1476f9d462 Make QueueDel work with nullable entity uids (#4381) 2023-09-13 12:58:03 +10:00
Leon Friedrich
d62efe7301 Add UnknownEntityDeleteTest (#4380) 2023-09-13 12:57:50 +10:00
metalgearsloth
6af0c88f27 Fix IEye not updating always (#4368) 2023-09-13 11:51:11 +10:00
metalgearsloth
5f05b0aa2a Version: 159.0.3 2023-09-13 11:43:34 +10:00
metalgearsloth
e6c335b6cd Fix nent deleted entity handling (#4379) 2023-09-13 11:22:21 +10:00
77 changed files with 1490 additions and 672 deletions

View File

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

View File

@@ -54,6 +54,90 @@ END TEMPLATE-->
*None yet*
## 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
* Transform component and containers have new convenience fields to make using VIewVariables easier.
## 160.0.0
### Breaking changes
* ComponentReference has now been entirely removed.
* Sensor / non-hard physics bodies are now included in EntityLookup by default.
## 159.1.0
## 159.0.3
### Bugfixes
* Fix potentially deleted entities having states re-applied when NetEntities come in.
## 159.0.2
### Bugfixes

View File

@@ -1,7 +1,7 @@
- type: entity
id: debugRotation
abstract: true
suffix: DEBUG
categories: [ debug ]
components:
- type: Sprite
netsync: false

View 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

View 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

View File

@@ -78,14 +78,7 @@ namespace Robust.Client.Console.Commands
message.Append($"net ID: {registration.NetID}");
}
message.Append($", References:");
shell.WriteLine(message.ToString());
foreach (var type in registration.References)
{
shell.WriteLine($" {type}");
}
}
catch (UnknownComponentException)
{

View File

@@ -35,14 +35,14 @@ public sealed partial class ClientEntityManager
if (NetEntityLookup.TryGetValue(nEntity, out var entity))
{
return entity;
return entity.Item1;
}
// Flag the callerEntity to have their state potentially re-run later.
var pending = PendingNetEntityStates.GetOrNew(nEntity);
pending.Add((typeof(T), callerEntity));
return entity;
return entity.Item1;
}
public override EntityCoordinates EnsureCoordinates<T>(NetCoordinates netCoordinates, EntityUid callerEntity)

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Prometheus;
using Robust.Client.GameStates;
using Robust.Client.Player;
@@ -65,9 +66,12 @@ namespace Robust.Client.GameObjects
base.DirtyEntity(uid, meta);
}
public override void QueueDeleteEntity(EntityUid uid)
public override void QueueDeleteEntity(EntityUid? uid)
{
if (IsClientSide(uid))
if (uid == null)
return;
if (IsClientSide(uid.Value))
{
base.QueueDeleteEntity(uid);
return;
@@ -78,7 +82,7 @@ namespace Robust.Client.GameObjects
// Client-side entity deletion is not supported and will cause errors.
if (_client.RunLevel == ClientRunLevel.Connected || _client.RunLevel == ClientRunLevel.InGame)
LogManager.RootSawmill.Error($"Predicting the queued deletion of a networked entity: {ToPrettyString(uid)}. Trace: {Environment.StackTrace}");
LogManager.RootSawmill.Error($"Predicting the queued deletion of a networked entity: {ToPrettyString(uid.Value)}. Trace: {Environment.StackTrace}");
}
/// <inheritdoc />
@@ -89,12 +93,16 @@ namespace Robust.Client.GameObjects
base.Dirty(uid, component, meta);
}
public override EntityStringRepresentation ToPrettyString(EntityUid uid)
[return: NotNullIfNotNull("uid")]
public override EntityStringRepresentation? ToPrettyString(EntityUid? uid)
{
if (uid == null)
return null;
if (_playerManager.LocalPlayer?.ControlledEntity == uid)
return base.ToPrettyString(uid) with { Session = _playerManager.LocalPlayer.Session };
else
return base.ToPrettyString(uid);
return base.ToPrettyString(uid).Value with { Session = _playerManager.LocalPlayer.Session };
return base.ToPrettyString(uid);
}
public override void RaisePredictiveEvent<T>(T msg)

View File

@@ -17,12 +17,18 @@ public sealed class EyeSystem : SharedEyeSystem
SubscribeLocalEvent<EyeComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<EyeComponent, PlayerDetachedEvent>(OnEyeDetached);
SubscribeLocalEvent<EyeComponent, PlayerAttachedEvent>(OnEyeAttached);
SubscribeLocalEvent<EyeComponent, AfterAutoHandleStateEvent>(OnEyeAutoState);
// Make sure this runs *after* entities have been moved by interpolation and movement.
UpdatesAfter.Add(typeof(TransformSystem));
UpdatesAfter.Add(typeof(PhysicsSystem));
}
private void OnEyeAutoState(EntityUid uid, EyeComponent component, ref AfterAutoHandleStateEvent args)
{
UpdateEye(component);
}
private void OnEyeAttached(EntityUid uid, EyeComponent component, PlayerAttachedEvent args)
{
// TODO: This probably shouldn't be nullable bruv.

View File

@@ -25,13 +25,15 @@ namespace Robust.Client.GameObjects
if (args.Current is not PointLightComponentState state)
return;
SetEnabled(uid, state.Enabled, component);
component.Enabled = state.Enabled;
component.Offset = state.Offset;
component.Softness = state.Softness;
component.CastShadows = state.CastShadows;
component.Energy = state.Energy;
component.Radius = state.Radius;
component.Color = state.Color;
_lightTree.QueueTreeUpdate(uid, component);
}
public override SharedPointLightComponent EnsureLight(EntityUid uid)

View File

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

View File

@@ -47,7 +47,7 @@ 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();
@@ -497,11 +497,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 +545,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()}");
@@ -597,11 +593,11 @@ namespace Robust.Client.GameStates
foreach (var netEntity in createdEntities)
{
var createdEntity = _entityManager.GetEntity(netEntity);
var (createdEntity, meta) = _entityManager.GetEntityData(netEntity);
var compData = new Dictionary<ushort, ComponentState>();
outputData.Add(netEntity, compData);
foreach (var (netId, component) in _entityManager.GetNetComponents(createdEntity))
foreach (var (netId, component) in meta.NetComponents)
{
if (!component.NetSyncEnabled)
continue;
@@ -692,9 +688,9 @@ namespace Robust.Client.GameStates
var uid = _entities.CreateEntity(metaState.PrototypeId);
_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.
@@ -742,7 +738,7 @@ namespace Robust.Client.GameStates
continue;
}
_toApply.Add(uid, (es.NetEntity, isEnteringPvs, meta.LastStateApplied, es, null));
_toApply.Add(uid, (es.NetEntity, meta, isEnteringPvs, meta.LastStateApplied, es, null));
meta.LastStateApplied = curState.ToSequence;
}
@@ -756,29 +752,32 @@ 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);
_toApply[uid.Value] = (es.NetEntity, meta, state.EnteringPvs, state.LastApplied, state.curState, es);
else
_toApply[uid.Value] = (es.NetEntity, false, GameTick.Zero, null, es);
_toApply[uid.Value] = (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;
_toApply[uid] = (_entityManager.GetNetEntity(uid), false, GameTick.Zero, null, null);
// Original entity referencing the NetEntity may have been deleted.
if (!metas.TryGetComponent(uid, out var meta))
continue;
_toApply[uid] = (meta.NetEntity, meta, false, GameTick.Zero, null, null);
}
var queuedBroadphaseUpdates = new List<(EntityUid, TransformComponent)>(enteringPvs);
@@ -788,7 +787,7 @@ namespace Robust.Client.GameStates
{
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)
@@ -1128,7 +1127,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();
@@ -1137,7 +1136,7 @@ namespace Robust.Client.GameStates
if (curState?.NetComponents != null)
{
RemQueue<Component> toRemove = new();
foreach (var (id, comp) in _entities.GetNetComponents(uid))
foreach (var (id, comp) in meta.NetComponents)
{
if (comp.NetSyncEnabled && !curState.NetComponents.Contains(id))
toRemove.Add(comp);
@@ -1145,7 +1144,7 @@ namespace Robust.Client.GameStates
foreach (var comp in toRemove)
{
_entities.RemoveComponent(uid, comp);
_entities.RemoveComponent(uid, comp, meta);
}
}
@@ -1158,12 +1157,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);
@@ -1173,12 +1171,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;
@@ -1194,7 +1191,7 @@ 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.
@@ -1222,7 +1219,7 @@ namespace Robust.Client.GameStates
continue;
if (_compStateWork.ContainsKey(netId.Value) ||
!_entityManager.TryGetComponent(uid, type, out var comp) ||
!meta.NetComponents.TryGetValue(netId.Value, out var comp) ||
!lastState.TryGetValue(netId.Value, out var lastCompState))
{
continue;
@@ -1242,10 +1239,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
}
@@ -1393,12 +1390,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);
@@ -1407,7 +1403,7 @@ namespace Robust.Client.GameStates
// ensure we don't have any extra components
RemQueue<Component> toRemove = new();
foreach (var (id, comp) in _entities.GetNetComponents(uid))
foreach (var (id, comp) in meta.NetComponents)
{
if (comp.NetSyncEnabled && !lastState.ContainsKey(id))
toRemove.Add(comp);

View File

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

View File

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

View File

@@ -44,7 +44,7 @@ namespace Robust.Client.UserInterface
LayoutContainer.SetPosition(tooltip, new Vector2(screenPosition.X, screenPosition.Y - combinedMinSize.Y));
var right = tooltip.Position.X + combinedMinSize.X;
var top = tooltip.Position.Y - combinedMinSize.Y;
var top = tooltip.Position.Y;
if (right > screenBounds.X)
{

View File

@@ -190,6 +190,11 @@ namespace Robust.Client.ViewVariables
return new VVPropEditorEntityUid();
}
if (type == typeof(NetEntity))
{
return new VVPropEditorNetEntity();
}
if (type == typeof(Color))
{
return new VVPropEditorColor();

View File

@@ -4,7 +4,6 @@ using Robust.Client.UserInterface.Controls;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.ViewVariables.Editors
@@ -39,7 +38,8 @@ namespace Robust.Client.ViewVariables.Editors
vvButton.OnPressed += e =>
{
IoCManager.Resolve<IConsoleHost>().ExecuteCommand($"vv {uid}");
var id = IoCManager.Resolve<IEntityManager>().GetNetEntity(uid);
IoCManager.Resolve<IConsoleHost>().ExecuteCommand($"vv {id}");
};
hBox.AddChild(lineEdit);

View File

@@ -0,0 +1,48 @@
using System.Numerics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.ViewVariables.Editors;
public sealed class VVPropEditorNetEntity : VVPropEditor
{
protected override Control MakeUI(object? value)
{
var hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
MinSize = new Vector2(200, 0)
};
var nuid = (NetEntity)value!;
var lineEdit = new LineEdit
{
Text = nuid.ToString(),
Editable = !ReadOnly,
HorizontalExpand = true,
};
if (!ReadOnly)
{
lineEdit.OnTextEntered += e =>
ValueChanged(NetEntity.Parse(e.Text));
}
var vvButton = new Button()
{
Text = "View",
};
vvButton.OnPressed += e =>
{
IoCManager.Resolve<IConsoleHost>().ExecuteCommand($"vv {nuid}");
};
hBox.AddChild(lineEdit);
hBox.AddChild(vvButton);
return hBox;
}
}

View File

@@ -469,8 +469,7 @@ namespace Robust.Client.ViewVariables.Instances
_entitySession = session;
_membersBlob = await ViewVariablesManager.RequestData<ViewVariablesBlobMembers>(session, new ViewVariablesRequestMembers());
var uid = (EntityUid) _membersBlob.MemberGroups.SelectMany(p => p.Item2).Single(p => p.Name == "Uid").Value;
var uid = (NetEntity) _membersBlob.MemberGroups.SelectMany(p => p.Item2).First(p => p.Value is NetEntity).Value;
Initialize(window, uid);
}

View File

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

View File

@@ -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,7 +69,7 @@ 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);

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Prometheus;
using Robust.Server.Player;
@@ -108,11 +109,15 @@ namespace Robust.Server.GameObjects
}
}
public override EntityStringRepresentation ToPrettyString(EntityUid uid)
[return: NotNullIfNotNull("uid")]
public override EntityStringRepresentation? ToPrettyString(EntityUid? uid)
{
if (uid == null)
return null;
TryGetComponent(uid, out ActorComponent? actor);
return base.ToPrettyString(uid) with { Session = actor?.PlayerSession };
return base.ToPrettyString(uid).Value with { Session = actor?.PlayerSession };
}
#region IEntityNetworkManager impl
@@ -134,9 +139,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);
@@ -163,15 +165,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)
{

View File

@@ -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!;
@@ -34,10 +35,16 @@ internal sealed partial class PvsSystem : EntitySystem
public const float ChunkSize = 8;
// TODO make this a cvar. Make it in terms of seconds and tie it to tick rate?
// Main issue is that I CBF figuring out the logic for handling it changing mid-game.
public const int DirtyBufferSize = 20;
// Note: If a client has ping higher than TickBuffer / TickRate, then the server will treat every entity as if it
// had entered PVS for the first time. Note that due to the PVS budget, this buffer is easily overwhelmed.
/// <summary>
/// See <see cref="CVars.NetForceAckThreshold"/>.
/// </summary>
public int ForceAckThreshold { get; private set; }
/// <summary>
/// Maximum number of pooled objects
/// </summary>
@@ -70,6 +77,9 @@ internal sealed partial class PvsSystem : EntitySystem
= new DefaultObjectPool<Dictionary<NetEntity, PvsEntityVisibility>>(
new DictPolicy<NetEntity, PvsEntityVisibility>(), MaxVisPoolSize);
private readonly ObjectPool<HashSet<EntityUid>> _uidSetPool
= new DefaultObjectPool<HashSet<EntityUid>>(new SetPolicy<EntityUid>(), MaxVisPoolSize);
private readonly ObjectPool<Stack<NetEntity>> _stackPool
= new DefaultObjectPool<Stack<NetEntity>>(
new StackPolicy<NetEntity>(), MaxVisPoolSize);
@@ -135,6 +145,7 @@ internal sealed partial class PvsSystem : EntitySystem
_configManager.OnValueChanged(CVars.NetPVS, SetPvs, true);
_configManager.OnValueChanged(CVars.NetMaxUpdateRange, OnViewsizeChanged, true);
_configManager.OnValueChanged(CVars.NetForceAckThreshold, OnForceAckChanged, true);
_serverGameStateManager.ClientAck += OnClientAck;
_serverGameStateManager.ClientRequestFull += OnClientRequestFull;
@@ -152,6 +163,7 @@ internal sealed partial class PvsSystem : EntitySystem
_configManager.UnsubValueChanged(CVars.NetPVS, SetPvs);
_configManager.UnsubValueChanged(CVars.NetMaxUpdateRange, OnViewsizeChanged);
_configManager.UnsubValueChanged(CVars.NetForceAckThreshold, OnForceAckChanged);
_serverGameStateManager.ClientAck -= OnClientAck;
_serverGameStateManager.ClientRequestFull -= OnClientRequestFull;
@@ -207,6 +219,11 @@ internal sealed partial class PvsSystem : EntitySystem
_viewSize = obj * 2;
}
private void OnForceAckChanged(int value)
{
ForceAckThreshold = value;
}
private void SetPvs(bool value)
{
_seenAllEnts.Clear();
@@ -224,6 +241,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:
@@ -310,7 +328,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
@@ -610,8 +628,8 @@ internal sealed partial class PvsSystem : EntitySystem
var tree = _treePool.Get();
foreach (var netEntity in chunk)
{
var uid = GetEntity(netEntity);
AddToChunkSetRecursively(in uid, in netEntity, visMask, tree, chunkSet);
var (uid, meta) = GetEntityData(netEntity);
AddToChunkSetRecursively(in uid, in netEntity, meta, visMask, tree, chunkSet);
#if DEBUG
var xform = _xformQuery.GetComponent(uid);
if (chunkLocation is MapChunkLocation)
@@ -645,13 +663,12 @@ internal sealed partial class PvsSystem : EntitySystem
}
}
private bool AddToChunkSetRecursively(in EntityUid uid, in NetEntity netEntity, int visMask, RobustTree<NetEntity> tree, Dictionary<NetEntity, MetaDataComponent> set)
private bool AddToChunkSetRecursively(in EntityUid uid, in NetEntity netEntity, MetaDataComponent mComp,
int visMask, RobustTree<NetEntity> tree, Dictionary<NetEntity, MetaDataComponent> set)
{
if (set.ContainsKey(netEntity))
return true;
var mComp = _metaQuery.GetComponent(uid);
// 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 ((visMask & mComp.VisibilityMask) != mComp.VisibilityMask)
@@ -672,13 +689,10 @@ internal sealed partial class PvsSystem : EntitySystem
DebugTools.Assert(!_mapManager.IsGrid(uid) && !_mapManager.IsMap(uid));
var parent = xform.ParentUid;
var parentNetEntity = _metaQuery.GetComponent(parent).NetEntity;
var parentMeta = _metaQuery.GetComponent(parent);
var parentNetEntity = parentMeta.NetEntity;
// TODO performance
// AddToChunkSetRecursively will result in a redundant set.ContainsKey() check.
// This can probably be avoided somehow
if (!set.ContainsKey(parentNetEntity) && //was the parent not yet added to toSend?
!AddToChunkSetRecursively(in parent, in parentNetEntity, visMask, tree, set)) //did we just fail to add the parent?
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
}
@@ -733,7 +747,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, stack, in fromTick,
RecursivelyAddTreeNode(in rootNode, cache.Value.tree, lastAcked, lastSent, visibleEnts, lastSeen, cache.Value.metadata, stack, in fromTick,
ref newEntityCount, ref enteredEntityCount, ref entStateCount, in newEntityBudget, in enteredEntityBudget);
}
}
@@ -749,7 +763,6 @@ internal sealed partial class PvsSystem : EntitySystem
}
globalEnumerator.Dispose();
var globalRecursiveEnumerator = _entityPvsCollection.GlobalRecursiveOverridesEnumerator;
while (globalRecursiveEnumerator.MoveNext())
{
@@ -794,9 +807,10 @@ internal sealed partial class PvsSystem : EntitySystem
foreach (var (netEntity, visiblity) in visibleEnts)
{
var uid = GetEntity(netEntity);
EntityUid uid;
MetaDataComponent meta;
#if DEBUG
uid = GetEntity(netEntity);
// if an entity is visible, its parents should always be visible.
DebugTools.Assert((_xformQuery.GetComponent(uid).ParentUid is not { Valid: true } parent) ||
visibleEnts.ContainsKey(_metaQuery.GetComponent(parent).NetEntity),
@@ -805,16 +819,18 @@ internal sealed partial class PvsSystem : EntitySystem
if (sessionData.RequestedFull)
{
entityStates.Add(GetFullEntityState(session, uid, _metaQuery.GetComponent(uid)));
(uid, meta) = GetEntityData(netEntity);
entityStates.Add(GetFullEntityState(session, uid, meta));
continue;
}
if (visiblity == PvsEntityVisibility.StayedUnchanged)
continue;
(uid, meta) = GetEntityData(netEntity);
var entered = visiblity == PvsEntityVisibility.Entered;
var entFromTick = entered ? lastSeen.GetValueOrDefault(netEntity) : fromTick;
var state = GetEntityState(session, uid, entFromTick, _metaQuery.GetComponent(uid));
var state = GetEntityState(session, uid, entFromTick, meta);
if (entered || !state.Empty)
entityStates.Add(state);
@@ -881,6 +897,7 @@ internal sealed partial class PvsSystem : EntitySystem
Dictionary<NetEntity, PvsEntityVisibility>? lastSent,
Dictionary<NetEntity, PvsEntityVisibility> toSend,
Dictionary<NetEntity, GameTick> lastSeen,
Dictionary<NetEntity, MetaDataComponent> metaDataCache,
Stack<NetEntity> stack,
in GameTick fromTick,
ref int newEntityCount,
@@ -898,16 +915,22 @@ internal sealed partial class PvsSystem : EntitySystem
// As every map is parented to uid 0 in the tree we still need to get their children, plus because we go top-down
// we may find duplicate parents with children we haven't encountered before
// on different chunks (this is especially common with direct grid children)
if (!toSend.ContainsKey(currentNodeIndex))
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(toSend, currentNodeIndex, out var exists);
if (!exists)
{
var (entered, shouldAdd) = ProcessEntry(in currentNodeIndex, lastAcked, lastSent, lastSeen,
ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
if (!shouldAdd)
{
// In the majority of instances entities do get added.
// So its better to add and maybe remove, rather than checking ContainsKey() and then maybe adding it.
toSend.Remove(currentNodeIndex);
continue;
}
var entity = GetEntity(currentNodeIndex);
AddToSendSet(in currentNodeIndex, _metaQuery.GetComponent(entity), toSend, fromTick, in entered, ref entStateCount);
AddToSendSet(in currentNodeIndex, metaDataCache[currentNodeIndex], ref value, toSend, fromTick, in entered, ref entStateCount);
}
var node = tree[currentNodeIndex];
@@ -949,21 +972,22 @@ internal sealed partial class PvsSystem : EntitySystem
}
var metadata = _metaQuery.GetComponent(uid);
var netEntity = GetNetEntity(uid, metadata);
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, _metaQuery.GetComponent(uid), toSend, fromTick, in entered, ref entStateCount);
AddToSendSet(in netEntity, metadata, ref value, toSend, fromTick, in entered, ref entStateCount);
}
if (addChildren)
@@ -993,14 +1017,15 @@ internal sealed partial class PvsSystem : EntitySystem
continue;
var metadata = _metaQuery.GetComponent(child);
var childNetEntity = GetNetEntity(child, metadata);
var childNetEntity = metadata.NetEntity;
if (!toSend.ContainsKey(childNetEntity))
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(toSend, childNetEntity, out var exists);
if (!exists)
{
var (entered, _) = ProcessEntry(in childNetEntity, lastAcked, lastSent, lastSeen, ref newEntityCount,
ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
AddToSendSet(in childNetEntity, metadata, toSend, fromTick, in entered, ref entStateCount);
AddToSendSet(in childNetEntity, metadata, ref value, toSend, fromTick, in entered, ref entStateCount);
}
RecursivelyAddChildren(childXform, lastAcked, lastSent, toSend, lastSeen, fromTick, ref newEntityCount,
@@ -1043,10 +1068,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;
@@ -1054,7 +1082,7 @@ internal sealed partial class PvsSystem : EntitySystem
if (entered)
{
toSend.Add(netEntity, PvsEntityVisibility.Entered);
vis = PvsEntityVisibility.Entered;
entStateCount++;
return;
}
@@ -1062,12 +1090,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++;
}
@@ -1077,7 +1105,7 @@ internal sealed partial class PvsSystem : EntitySystem
public (List<EntityState>?, List<NetEntity>?, GameTick fromTick) GetAllEntityStates(ICommonSession? player, GameTick fromTick, GameTick toTick)
{
List<EntityState>? stateEntities;
var toSend = new HashSet<EntityUid>();
var toSend = _uidSetPool.Get();
DebugTools.Assert(toSend.Count == 0);
bool enumerateAll = false;
@@ -1126,7 +1154,6 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
else
{
stateEntities = new();
var metaQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
for (var i = fromTick.Value + 1; i <= toTick.Value; i++)
{
if (!TryGetDirtyEntities(new GameTick(i), out var add, out var dirty))
@@ -1137,7 +1164,7 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
foreach (var uid in add)
{
if (!toSend.Add(uid) || !metaQuery.TryGetComponent(uid, out var md))
if (!toSend.Add(uid) || !_metaQuery.TryGetComponent(uid, out var md))
continue;
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
@@ -1164,7 +1191,7 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
foreach (var uid in dirty)
{
DebugTools.Assert(!add.Contains(uid));
if (!toSend.Add(uid) || !metaQuery.TryGetComponent(uid, out var md))
if (!toSend.Add(uid) || !_metaQuery.TryGetComponent(uid, out var md))
continue;
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized);
@@ -1179,6 +1206,7 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
}
}
_uidSetPool.Return(toSend);
var deletions = _entityPvsCollection.GetDeletedIndices(fromTick);
if (stateEntities.Count == 0)
@@ -1203,7 +1231,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;
@@ -1252,7 +1280,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;
@@ -1276,34 +1304,28 @@ Transform last modified: {Transform(uid).LastModifiedTick}");
private EntityUid[] GetSessionViewers(ICommonSession session)
{
if (session.Status != SessionStatus.InGame)
if (session.Status != SessionStatus.InGame || session is not IPlayerSession sess)
return Array.Empty<EntityUid>();
// Fast path
if (sess.ViewSubscriptionCount == 0)
{
if (session.AttachedEntity == null)
return Array.Empty<EntityUid>();
return new[] { session.AttachedEntity.Value };
}
var viewers = new HashSet<EntityUid>();
if (session.AttachedEntity != null)
{
// Fast path
if (session is IPlayerSession { ViewSubscriptionCount: 0 })
{
return new[] { session.AttachedEntity.Value };
}
viewers.Add(session.AttachedEntity.Value);
}
// This is awful, but we're not gonna add the list of view subscriptions to common session.
if (session is IPlayerSession playerSession)
foreach (var uid in sess.ViewSubscriptions)
{
foreach (var uid in playerSession.ViewSubscriptions)
{
viewers.Add(uid);
}
viewers.Add(uid);
}
var viewerArray = viewers.ToArray();
return viewerArray;
return viewers.ToArray();
}
// Read Safe

View File

@@ -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)
@@ -210,8 +229,6 @@ namespace Robust.Server.GameStates
var inputSystem = _systemManager.GetEntitySystem<InputSystem>();
var opts = new ParallelOptions {MaxDegreeOfParallelism = _parallelMgr.ParallelProcessCount};
var oldestAckValue = GameTick.MaxValue.Value;
var tQuery = _entityManager.GetEntityQuery<TransformComponent>();
var mQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
// Replays process game states in parallel with players
Parallel.For(-1, players.Length, opts, _threadResourcesPool.Get, SendPlayer, _threadResourcesPool.Return);
@@ -225,7 +242,7 @@ namespace Robust.Server.GameStates
PvsEventSource.Log.WorkStart(_gameTiming.CurTick.Value, i, guid);
if (i >= 0)
SendStateUpdate(i, resource, inputSystem, players[i], pvsData, mQuery, tQuery, ref oldestAckValue);
SendStateUpdate(i, resource, inputSystem, players[i], pvsData, ref oldestAckValue);
else
_replay.Update();
@@ -304,8 +321,6 @@ namespace Robust.Server.GameStates
InputSystem inputSystem,
IPlayerSession session,
PvsData? pvsData,
EntityQuery<MetaDataComponent> mQuery,
EntityQuery<TransformComponent> tQuery,
ref uint oldestAckValue)
{
var channel = session.ConnectedClient;
@@ -358,6 +373,21 @@ namespace Robust.Server.GameStates
// If the state is too big we let Lidgren send it reliably. This is to avoid a situation where a state is so
// large that it (or part of it) consistently gets dropped. When we send reliably, we immediately update the
// ack so that the next state will not also be huge.
//
// We also do this if the client's last ack is too old. This helps prevent things like the entity deletion
// history from becoming too bloated if a bad client fails to send acks for whatever reason.
if (_gameTiming.CurTick.Value > lastAck.Value + _pvs.ForceAckThreshold)
{
stateUpdateMessage.ForceSendReliably = true;
// Aside from the time shortly after connecting, this shouldn't be common. If it is happening,
// something is probably wrong. If it is more frequent than I think, this can be downgraded to a warning.
var connectedTime = (DateTime.UtcNow - session.ConnectedTime).TotalMinutes;
if (lastAck > GameTick.Zero && connectedTime > 1)
_logger.Error($"Client {session} exceeded ack-tick threshold. Last ack: {lastAck}. Cur tick: {_gameTiming.CurTick}. Connect time: {connectedTime} minutes");
}
if (stateUpdateMessage.ShouldSendReliably())
{
sessionData.LastReceivedAck = _gameTiming.CurTick;

View File

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

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Robust.Server.ViewVariables.Traits;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Network.Messages;
using Robust.Shared.Prototypes;
@@ -92,6 +93,9 @@ namespace Robust.Server.ViewVariables
return null;
}
if (value is EntityUid uid)
return IoCManager.Resolve<IEntityManager>().GetNetEntity(uid);
var valType = value.GetType();
if (!valType.IsValueType)
{

View File

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

View File

@@ -218,7 +218,8 @@ namespace Robust.Shared.Scripting
public EntityPrototype? Prototype(EntityUid uid)
=> ent.GetComponent<MetaDataComponent>(uid).EntityPrototype;
public EntityStringRepresentation ToPrettyString(EntityUid uid)
[return: NotNullIfNotNull("uid")]
public EntityStringRepresentation? ToPrettyString(EntityUid? uid)
=> ent.ToPrettyString(uid);
public IEnumerable<IComponent> AllComps(EntityUid uid)

View File

@@ -172,6 +172,13 @@ namespace Robust.Shared
public static readonly CVarDef<float> NetMaxUpdateRange =
CVarDef.Create("net.maxupdaterange", 12.5f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// Maximum allowed delay between the current tick and a client's last acknowledged tick before we send the
/// next game state reliably and simply force update the acked tick,
/// </summary>
public static readonly CVarDef<int> NetForceAckThreshold =
CVarDef.Create("net.force_ack_threshold", 40, CVar.ARCHIVE | CVar.SERVERONLY);
/// <summary>
/// This limits the number of new entities that can be sent to a client in a single game state. This exists to
/// avoid stuttering on the client when it has to spawn a bunch of entities in a single tick. If ever entity

View File

@@ -11,7 +11,9 @@ using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
namespace Robust.Shared.Containers
@@ -25,9 +27,13 @@ namespace Robust.Shared.Containers
/// <summary>
/// Readonly collection of all the entities contained within this specific container
/// </summary>
[ViewVariables]
public abstract IReadOnlyList<EntityUid> ContainedEntities { get; }
// VV convenience field
[ViewVariables]
private IReadOnlyList<NetEntity> NetContainedEntities => ContainedEntities
.Select(o => IoCManager.Resolve<IEntityManager>().GetNetEntity(o)).ToList();
/// <summary>
/// Number of contained entities.
/// </summary>
@@ -106,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>();
@@ -116,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;

View File

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

View File

@@ -59,9 +59,6 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public event Action<ComponentRegistration>? ComponentAdded;
/// <inheritdoc />
public event Action<ComponentRegistration, CompIdx>? ComponentReferenceAdded;
/// <inheritdoc />
public event Action<string>? ComponentIgnoreAdded;
@@ -173,31 +170,6 @@ namespace Robust.Shared.GameObjects
}
}
private void RegisterReference(Type target, Type @interface)
{
if (_networkedComponents is not null)
throw new ComponentRegistrationLockException();
if (!types.ContainsKey(target))
{
throw new InvalidOperationException($"Unregistered type: {target}");
}
if (@interface == typeof(MetaDataComponent) || @interface == typeof(TransformComponent))
throw new InvalidOperationException("Cannot make Transform or Metadata a reference type!");
var idx = CompIdx.Index(@interface);
_idxToType[idx] = @interface;
var registration = types[target];
if (registration.References.Contains(idx))
{
throw new InvalidOperationException($"Attempted to register a reference twice: {@interface}");
}
registration.References.Add(idx);
ComponentReferenceAdded?.Invoke(registration, idx);
}
public void IgnoreMissingComponents(string postfix = "")
{
if (_ignoreMissingComponentPostfix != null && _ignoreMissingComponentPostfix != postfix)
@@ -333,7 +305,7 @@ namespace Robust.Shared.GameObjects
{
return GetRegistration(netID).Name;
}
public ComponentRegistration GetRegistration(ushort netID)
{
if (_networkedComponents is null)
@@ -450,28 +422,11 @@ namespace Robust.Shared.GameObjects
}
Register(type);
#pragma warning disable CS0618
foreach (var attribute in Attribute.GetCustomAttributes(type, typeof(ComponentReferenceAttribute)))
{
var cast = (ComponentReferenceAttribute) attribute;
#pragma warning restore CS0618
var refType = cast.ReferenceType;
if (!refType.IsAssignableFrom(type))
{
_sawmill.Error("Type {0} has reference for type it does not implement: {1}.", type, refType);
continue;
}
RegisterReference(type, refType);
}
}
public IEnumerable<CompIdx> GetAllRefTypes()
{
return AllRegistrations.SelectMany(r => r.References).Distinct();
return AllRegistrations.Select(x => x.Idx).Distinct();
}
/// <inheritdoc />

View File

@@ -1,30 +0,0 @@
using System;
namespace Robust.Shared.GameObjects;
/*
* Obsoleted because this has a massive performance cost and it is not worth it.
*/
/// <summary>
/// Marks a component as having a specific reference type,
/// for use with <see cref="RegisterComponentAttribute"/>.
/// </summary>
[Obsolete("Refactor your code to not use component references.")]
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
public sealed class ComponentReferenceAttribute : Attribute
{
/// <summary>
/// The type this component is a reference to.
/// </summary>
public Type ReferenceType { get; }
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="referenceType">The type this component is a reference to.</param>
public ComponentReferenceAttribute(Type referenceType)
{
ReferenceType = referenceType;
}
}

View File

@@ -33,8 +33,6 @@ public sealed class ComponentRegistration
/// </summary>
public Type Type { get; }
public ValueList<CompIdx> References;
// Internal for sandboxing.
// Avoid content passing an instance of this to ComponentFactory to get any type they want instantiated.
internal ComponentRegistration(string name, Type type, CompIdx idx)
@@ -42,7 +40,6 @@ public sealed class ComponentRegistration
Name = name;
Type = type;
Idx = idx;
References.Add(idx);
}
public override string ToString()

View File

@@ -9,7 +9,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
[RegisterComponent, NetworkedComponent, Access(typeof(SharedEyeSystem)), AutoGenerateComponentState]
[RegisterComponent, NetworkedComponent, Access(typeof(SharedEyeSystem)), AutoGenerateComponentState(true)]
public sealed partial class EyeComponent : Component
{
#region Client

View File

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

View File

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

View File

@@ -25,6 +25,10 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
// Currently this field just exists for VV. In future, it might become a real field
[ViewVariables]
private NetEntity NetParent => _entMan.GetNetEntity(_parent);
[DataField("parent")] internal EntityUid _parent;
[DataField("pos")] internal Vector2 _localPosition = Vector2.Zero; // holds offset from grid, or offset from parent
[DataField("rot")] internal Angle _localRotation; // local rotation

View File

@@ -116,7 +116,6 @@ namespace Robust.Shared.GameObjects
// Dynamic handling of components is only for RobustUnitTest compatibility spaghetti.
_comFac.ComponentAdded += ComFacOnComponentAdded;
_comFac.ComponentReferenceAdded += ComFacOnComponentReferenceAdded;
InitEntSubscriptionsArray();
}
@@ -310,11 +309,6 @@ namespace Robust.Shared.GameObjects
EntUnsubscribe(CompIdx.Index<TComp>(), typeof(TEvent));
}
private void ComFacOnComponentReferenceAdded(ComponentRegistration arg1, CompIdx arg2)
{
CompIdx.RefArray(ref _entSubscriptions, arg2) ??= new Dictionary<Type, DirectedRegistration>();
}
private void ComFacOnComponentAdded(ComponentRegistration obj)
{
CompIdx.RefArray(ref _entSubscriptions, obj.Idx) ??= new Dictionary<Type, DirectedRegistration>();
@@ -427,40 +421,35 @@ namespace Robust.Shared.GameObjects
private void EntAddComponent(EntityUid euid, CompIdx compType)
{
var eventTable = _entEventTables[euid];
var compSubs = _entSubscriptions[compType.Value]!;
var enumerator = EntGetReferences(compType);
while (enumerator.MoveNext(out var type))
foreach (var evType in compSubs.Keys)
{
var compSubs = _entSubscriptions[type.Value]!;
// Skip adding this to significantly reduce memory use and GC noise on entity create.
if (_eventData[evType].ComponentEvent)
continue;
foreach (var (evType, _) in compSubs)
{
// Skip adding this to significantly reduce memory use and GC noise on entity create.
if (_eventData[evType].ComponentEvent)
continue;
if (eventTable.Free < 0)
GrowEventTable(eventTable);
if (eventTable.Free < 0)
GrowEventTable(eventTable);
DebugTools.Assert(eventTable.Free >= 0);
DebugTools.Assert(eventTable.Free >= 0);
ref var eventStartIdx = ref CollectionsMarshal.GetValueRefOrAddDefault(
eventTable.EventIndices,
evType,
out var exists);
ref var eventStartIdx = ref CollectionsMarshal.GetValueRefOrAddDefault(
eventTable.EventIndices,
evType,
out var exists);
// Allocate linked list entry by popping free list.
var entryIdx = eventTable.Free;
ref var entry = ref eventTable.ComponentLists[entryIdx];
eventTable.Free = entry.Next;
// Allocate linked list entry by popping free list.
var entryIdx = eventTable.Free;
ref var entry = ref eventTable.ComponentLists[entryIdx];
eventTable.Free = entry.Next;
// Set it up
entry.Component = compType;
entry.Next = exists ? eventStartIdx : -1;
// Set it up
entry.Component = type;
entry.Next = exists ? eventStartIdx : -1;
// Assign new list entry to EventIndices dictionary.
eventStartIdx = entryIdx;
}
// Assign new list entry to EventIndices dictionary.
eventStartIdx = entryIdx;
}
}
@@ -493,51 +482,46 @@ namespace Robust.Shared.GameObjects
private void EntRemoveComponent(EntityUid euid, CompIdx compType)
{
var eventTable = _entEventTables[euid];
var compSubs = _entSubscriptions[compType.Value]!;
var enumerator = EntGetReferences(compType);
while (enumerator.MoveNext(out var type))
foreach (var evType in compSubs.Keys)
{
var compSubs = _entSubscriptions[type.Value]!;
ref var dictIdx = ref CollectionsMarshal.GetValueRefOrNullRef(eventTable.EventIndices, evType);
if (Unsafe.IsNullRef(ref dictIdx))
continue;
foreach (var (evType, _) in compSubs)
ref var updateNext = ref dictIdx;
// Go over linked list to find index of component.
var entryIdx = dictIdx;
ref var entry = ref Unsafe.NullRef<EventTableListEntry>();
while (true)
{
ref var dictIdx = ref CollectionsMarshal.GetValueRefOrNullRef(eventTable.EventIndices, evType);
if (Unsafe.IsNullRef(ref dictIdx))
continue;
ref var updateNext = ref dictIdx;
// Go over linked list to find index of component.
var entryIdx = dictIdx;
ref var entry = ref Unsafe.NullRef<EventTableListEntry>();
while (true)
entry = ref eventTable.ComponentLists[entryIdx];
if (entry.Component == compType)
{
entry = ref eventTable.ComponentLists[entryIdx];
if (entry.Component == type)
{
// Found
break;
}
entryIdx = entry.Next;
updateNext = ref entry.Next;
// Found
break;
}
if (entry.Next == -1 && Unsafe.AreSame(ref dictIdx, ref updateNext))
{
// Last entry for this event type, remove from dict.
eventTable.EventIndices.Remove(evType);
}
else
{
// Rewrite previous index to point to next in chain.
updateNext = entry.Next;
}
// Push entry back onto free list.
entry.Next = eventTable.Free;
eventTable.Free = entryIdx;
entryIdx = entry.Next;
updateNext = ref entry.Next;
}
if (entry.Next == -1 && Unsafe.AreSame(ref dictIdx, ref updateNext))
{
// Last entry for this event type, remove from dict.
eventTable.EventIndices.Remove(evType);
}
else
{
// Rewrite previous index to point to next in chain.
updateNext = entry.Next;
}
// Push entry back onto free list.
entry.Next = eventTable.Free;
eventTable.Free = entryIdx;
}
}
@@ -588,27 +572,15 @@ namespace Robust.Shared.GameObjects
bool dispatchByReference)
where TEvent : notnull
{
var enumerator = EntGetReferences(baseType);
while (enumerator.MoveNext(out var type))
{
var compSubs = _entSubscriptions[type.Value]!;
var compSubs = _entSubscriptions[baseType.Value]!;
if (!compSubs.TryGetValue(typeof(TEvent), out var reg))
continue;
if (!compSubs.TryGetValue(typeof(TEvent), out var reg))
return;
if (reg.ReferenceEvent != dispatchByReference)
ThrowByRefMisMatch();
if (reg.ReferenceEvent != dispatchByReference)
ThrowByRefMisMatch();
reg.Handler(euid, component, ref args);
}
}
/// <summary>
/// Enumerates the type's component references, returning the type itself last.
/// </summary>
private ReferencesEnumerator EntGetReferences(CompIdx type)
{
return new(type, _comFac.GetRegistration(type).References);
reg.Handler(euid, component, ref args);
}
/// <summary>
@@ -652,7 +624,6 @@ namespace Robust.Shared.GameObjects
public void Dispose()
{
_comFac.ComponentAdded -= ComFacOnComponentAdded;
_comFac.ComponentReferenceAdded -= ComFacOnComponentReferenceAdded;
// punishment for use-after-free
_entMan = null!;
@@ -662,43 +633,6 @@ namespace Robust.Shared.GameObjects
_entSubscriptionsInv = null!;
}
private struct ReferencesEnumerator
{
private readonly CompIdx _baseType;
private readonly ValueList<CompIdx> _list;
private readonly int _totalLength;
private int _idx;
public ReferencesEnumerator(CompIdx baseType, ValueList<CompIdx> list)
{
_baseType = baseType;
_list = list;
_totalLength = list.Count;
_idx = 0;
}
public bool MoveNext(out CompIdx type)
{
if (_idx >= _totalLength)
{
if (_idx++ == _totalLength)
{
type = _baseType;
return true;
}
type = default;
return false;
}
type = _list[_idx++];
if (type == _baseType)
return MoveNext(out type);
return true;
}
}
private struct SubscriptionsEnumerator
{
private readonly Type _eventType;

View File

@@ -35,9 +35,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();
@@ -62,7 +59,6 @@ namespace Robust.Shared.GameObjects
FillComponentDict();
_componentFactory.ComponentAdded += OnComponentAdded;
_componentFactory.ComponentReferenceAdded += OnComponentReferenceAdded;
}
/// <summary>
@@ -71,7 +67,6 @@ namespace Robust.Shared.GameObjects
/// </summary>
public void ClearComponents()
{
_netComponents.Clear();
_entCompIndex.Clear();
_deleteSet.Clear();
foreach (var dict in _entTraitDict.Values)
@@ -94,11 +89,6 @@ namespace Robust.Shared.GameObjects
#region Component Management
private void OnComponentReferenceAdded(ComponentRegistration reg, CompIdx type)
{
AddComponentRefType(type);
}
/// <inheritdoc />
public int Count<T>() where T : Component
{
@@ -181,13 +171,13 @@ namespace Robust.Shared.GameObjects
}
}
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;
}
@@ -255,7 +245,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));
@@ -273,53 +263,48 @@ 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);
// Check that there are no overlapping references.
foreach (var type in reg.References)
if (dict.TryGetValue(uid, out var duplicate))
{
var dict = _entTraitArray[type.Value];
if (!dict.TryGetValue(uid, out var duplicate))
continue;
if (!overwrite && !duplicate.Deleted)
throw new InvalidOperationException(
$"Component reference type {component.GetType().Name} already occupied by {duplicate}");
RemoveComponentImmediate(duplicate, uid, false);
RemoveComponentImmediate(duplicate, uid, false, metadata);
}
// add the component to the grid
foreach (var type in reg.References)
{
_entTraitArray[type.Value].Add(uid, component);
_entCompIndex.Add(uid, component);
}
// actually ADD the component
dict.Add(uid, component);
_entCompIndex.Add(uid, component);
// add the component to the netId grid
if (reg.NetID != null)
{
// 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
{
@@ -335,7 +320,7 @@ namespace Robust.Shared.GameObjects
if (skipInit)
return;
var metadata = GetComponent<MetaDataComponent>(uid);
metadata ??= MetaQuery.GetComponentInternal(uid);
if (!metadata.EntityInitialized && !metadata.EntityInitializing)
return;
@@ -354,45 +339,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 />
@@ -414,9 +402,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);
@@ -452,22 +443,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)
{
@@ -476,7 +473,6 @@ namespace Robust.Shared.GameObjects
}
_entCompIndex.Remove(uid);
_netComponents.Remove(uid);
}
private void RemoveComponentDeferred(Component component, EntityUid uid, bool terminating)
@@ -517,7 +513,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)
{
@@ -550,10 +547,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 />
@@ -588,39 +582,45 @@ 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 reg = _componentFactory.GetRegistration(component.GetType());
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;
}
}
foreach (var refType in reg.References)
{
_entTraitArray[refType.Value].Remove(entityUid);
}
_entTraitArray[reg.Idx.Value].Remove(entityUid);
// 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 />
@@ -662,23 +662,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 />
@@ -763,9 +765,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 />
@@ -774,7 +776,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}");
@@ -879,10 +881,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;
@@ -894,23 +896,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
@@ -978,17 +973,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

View File

@@ -16,7 +16,7 @@ public partial class EntityManager
/// Inverse lookup for net entities.
/// Regular lookup uses MetadataComponent.
/// </summary>
protected readonly Dictionary<NetEntity, EntityUid> NetEntityLookup = new(EntityCapacity);
protected readonly Dictionary<NetEntity, (EntityUid, MetaDataComponent)> NetEntityLookup = new(EntityCapacity);
/// <summary>
/// Clears an old inverse lookup for a particular entityuid.
@@ -34,7 +34,7 @@ public partial class EntityManager
internal void SetNetEntity(EntityUid uid, NetEntity netEntity, MetaDataComponent component)
{
DebugTools.Assert(!NetEntityLookup.ContainsKey(netEntity));
NetEntityLookup[netEntity] = uid;
NetEntityLookup[netEntity] = (uid, component);
component.NetEntity = netEntity;
}
@@ -64,7 +64,7 @@ public partial class EntityManager
{
if (NetEntityLookup.TryGetValue(nEntity, out var went))
{
entity = went;
entity = went.Item1;
return true;
}
@@ -72,6 +72,21 @@ public partial class EntityManager
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;
}
/// <inheritdoc />
public bool TryGetEntity(NetEntity? nEntity, [NotNullWhen(true)] out EntityUid? entity)
{
@@ -141,7 +156,15 @@ public partial class EntityManager
if (nEntity == NetEntity.Invalid)
return EntityUid.Invalid;
return NetEntityLookup.GetValueOrDefault(nEntity);
if (!NetEntityLookup.TryGetValue(nEntity, out var tuple))
return EntityUid.Invalid;
return tuple.Item1;
}
public (EntityUid, MetaDataComponent) GetEntityData(NetEntity nEntity)
{
return NetEntityLookup[nEntity];
}
/// <inheritdoc />

View File

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

View File

@@ -1,17 +1,18 @@
using Prometheus;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Profiling;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
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;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects
{
@@ -32,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
@@ -86,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!;
@@ -112,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");
@@ -234,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()
@@ -251,7 +258,6 @@ namespace Robust.Shared.GameObjects
public virtual void Cleanup()
{
_componentFactory.ComponentAdded -= OnComponentAdded;
_componentFactory.ComponentReferenceAdded -= OnComponentReferenceAdded;
ShuttingDown = true;
FlushEntities();
_entitySystemManager.Clear();
@@ -318,7 +324,7 @@ namespace Robust.Shared.GameObjects
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
{
var newEntity = CreateEntity(prototypeName, overrides);
_xforms.SetCoordinates(newEntity, _xformQuery.GetComponent(newEntity), coordinates, unanchor: false);
_xforms.SetCoordinates(newEntity, TransformQuery.GetComponent(newEntity), coordinates, unanchor: false);
return newEntity;
}
@@ -326,7 +332,7 @@ namespace Robust.Shared.GameObjects
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
{
var newEntity = CreateEntity(prototypeName, overrides);
var transform = _xformQuery.GetComponent(newEntity);
var transform = TransformQuery.GetComponent(newEntity);
if (coordinates.MapId == MapId.Nullspace)
{
@@ -365,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;
@@ -447,7 +443,7 @@ namespace Robust.Shared.GameObjects
EntityUid uid,
MetaDataComponent metadata)
{
var transform = _xformQuery.GetComponent(uid);
var transform = TransformQuery.GetComponent(uid);
metadata.EntityLifeStage = EntityLifeStage.Terminating;
try
@@ -479,7 +475,7 @@ namespace Robust.Shared.GameObjects
{
// Note about this method: #if EXCEPTION_TOLERANCE is not used here because we're gonna it in the future...
var netEntity = GetNetEntity(uid, metadata);
var transform = _xformQuery.GetComponent(uid);
var transform = TransformQuery.GetComponent(uid);
// Detach the base entity to null before iterating over children
// This also ensures that the entity-lookup updates don't have to be re-run for every child (which recurses up the transform hierarchy).
@@ -545,20 +541,23 @@ namespace Robust.Shared.GameObjects
NetEntityLookup.Remove(netEntity);
}
public virtual void QueueDeleteEntity(EntityUid uid)
public virtual void QueueDeleteEntity(EntityUid? uid)
{
if (!QueuedDeletionsSet.Add(uid))
if (uid == null)
return;
QueuedDeletions.Enqueue(uid);
EntityQueueDeleted?.Invoke(uid);
if (!QueuedDeletionsSet.Add(uid.Value))
return;
QueuedDeletions.Enqueue(uid.Value);
EntityQueueDeleted?.Invoke(uid.Value);
}
public bool IsQueuedForDeletion(EntityUid uid) => QueuedDeletionsSet.Contains(uid);
public bool EntityExists(EntityUid uid)
{
return _entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].ContainsKey(uid);
return MetaQuery.HasComponentInternal(uid);
}
public bool EntityExists(EntityUid? uid)
@@ -577,12 +576,12 @@ namespace Robust.Shared.GameObjects
public bool Deleted(EntityUid uid)
{
return !_entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].TryGetValue(uid, out var comp) || ((MetaDataComponent) comp).EntityDeleted;
return !MetaQuery.TryGetComponentInternal(uid, out var comp) || comp.EntityDeleted;
}
public bool Deleted([NotNullWhen(false)] EntityUid? uid)
{
return !uid.HasValue || !_entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].TryGetValue(uid.Value, out var comp) || ((MetaDataComponent) comp).EntityDeleted;
return !uid.HasValue || !MetaQuery.TryGetComponentInternal(uid.Value, out var comp) || comp.EntityDeleted;
}
/// <summary>
@@ -644,10 +643,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;
}
@@ -705,7 +706,7 @@ namespace Robust.Shared.GameObjects
StartEntity(entity);
// If the map we're initializing the entity on is initialized, run map init on it.
if (_mapManager.IsMapInitialized(mapId ?? _xformQuery.GetComponent(entity).MapID))
if (_mapManager.IsMapInitialized(mapId ?? TransformQuery.GetComponent(entity).MapID))
RunMapInit(entity, meta);
}
catch (Exception e)
@@ -738,23 +739,34 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
public virtual EntityStringRepresentation ToPrettyString(EntityUid uid)
[return: NotNullIfNotNull("uid")]
public virtual EntityStringRepresentation? ToPrettyString(EntityUid? uid)
{
// We want to retrieve the MetaData component even if it is deleted.
if (!_entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].TryGetValue(uid, out var component))
return new EntityStringRepresentation(uid, true);
if (uid == null)
return null;
if (!_entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].TryGetValue(uid.Value, out var component))
return new EntityStringRepresentation(uid.Value, true);
var metadata = (MetaDataComponent) component;
return ToPrettyString(uid, metadata);
return ToPrettyString(uid.Value, metadata);
}
/// <inheritdoc />
public EntityStringRepresentation ToPrettyString(NetEntity netEntity)
[return: NotNullIfNotNull("netEntity")]
public EntityStringRepresentation? ToPrettyString(NetEntity? netEntity)
{
return ToPrettyString(GetEntity(netEntity));
}
public EntityStringRepresentation ToPrettyString(EntityUid uid)
=> ToPrettyString((EntityUid?) uid).Value;
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);

View File

@@ -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;
@@ -398,18 +398,30 @@ public partial class EntitySystem
/// <inheritdoc cref="IEntityManager.ToPrettyString"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityStringRepresentation ToPrettyString(EntityUid uid)
[return: NotNullIfNotNull("uid")]
protected EntityStringRepresentation? ToPrettyString(EntityUid? uid)
{
return EntityManager.ToPrettyString(uid);
}
/// <inheritdoc cref="IEntityManager.ToPrettyString"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityStringRepresentation ToPrettyString(NetEntity netEntity)
[return: NotNullIfNotNull("netEntity")]
protected EntityStringRepresentation? ToPrettyString(NetEntity? netEntity)
{
return EntityManager.ToPrettyString(netEntity);
}
/// <inheritdoc cref="IEntityManager.ToPrettyString"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityStringRepresentation ToPrettyString(EntityUid uid)
=> ToPrettyString((EntityUid?) uid).Value;
/// <inheritdoc cref="IEntityManager.ToPrettyString"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityStringRepresentation ToPrettyString(NetEntity netEntity)
=> ToPrettyString((NetEntity?) netEntity).Value;
#endregion
#region Component Get
@@ -446,6 +458,20 @@ public partial class EntitySystem
return EntityManager.TryGetComponent(uid, out comp);
}
/// <inheritdoc cref="IEntityManager.TryGetComponent&lt;T&gt;(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&lt;T&gt;(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&lt;T&gt;(EntityUid?, out T)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool TryComp<T>([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out T? comp)
@@ -459,6 +485,30 @@ public partial class EntitySystem
return EntityManager.TryGetComponent(uid.Value, out comp);
}
/// <inheritdoc cref="IEntityManager.TryGetComponent&lt;T&gt;(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&lt;T&gt;(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)
@@ -480,7 +530,7 @@ public partial class EntitySystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected TransformComponent Transform(EntityUid uid)
{
return EntityManager.GetComponent<TransformComponent>(uid);
return EntityManager.TransformQuery.GetComponent(uid);
}
/// <summary>
@@ -490,7 +540,20 @@ 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)]
protected (EntityUid, MetaDataComponent) GetEntityData(NetEntity nuid)
{
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
@@ -651,7 +714,7 @@ public partial class EntitySystem
/// <inheritdoc cref="IEntityManager.QueueDeleteEntity(EntityUid)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void QueueDel(EntityUid uid)
protected void QueueDel(EntityUid? uid)
{
EntityManager.QueueDeleteEntity(uid);
}
@@ -897,9 +960,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)]

View File

@@ -32,6 +32,22 @@ namespace Robust.Shared.GameObjects
return found;
}
/// <inheritdoc cref="Resolve{TComp}"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref MetaDataComponent? component,
bool logMissing = true)
{
return EntityManager.MetaQuery.Resolve(uid, ref component);
}
/// <inheritdoc cref="Resolve{TComp}"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TransformComponent? component,
bool logMissing = true)
{
return EntityManager.TransformQuery.Resolve(uid, ref component);
}
/// <summary>
/// Resolves the components on the entity for the null component references.
/// </summary>

View File

@@ -190,8 +190,10 @@ namespace Robust.Shared.GameObjects
// This might seem useless, but it allows you to retrieve remote entities that don't exist on the client.
[ViewVariables]
private EntityUid Uid => this;
private EntityUid _uid => this;
[ViewVariables]
private NetEntity _netId => IoCManager.Resolve<IEntityManager>().GetNetEntity(this);
#endregion
}
}

View File

@@ -52,7 +52,6 @@ namespace Robust.Shared.GameObjects
public interface IComponentFactory
{
event Action<ComponentRegistration> ComponentAdded;
event Action<ComponentRegistration, CompIdx> ComponentReferenceAdded;
event Action<string> ComponentIgnoreAdded;
/// <summary>
@@ -156,7 +155,7 @@ namespace Robust.Shared.GameObjects
/// </exception>
[Pure]
string GetComponentName(Type componentType);
/// <summary>
/// Gets the name of a component, throwing an exception if it does not exist.
/// </summary>

View File

@@ -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.
@@ -70,7 +70,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 +78,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 +86,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 +94,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 +125,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 +147,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 +155,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 +187,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 +196,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 +205,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.
@@ -255,7 +255,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 +318,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 +328,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 +364,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 +372,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.

View File

@@ -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>
@@ -191,4 +197,10 @@ public partial interface IEntityManager
public NetCoordinates[] GetNetCoordinatesArray(EntityCoordinates[] entities);
public NetCoordinates?[] GetNetCoordinatesArray(EntityCoordinates?[] entities);
/// <summary>
/// Returns the corresponding local <see cref="EntityUid"/> along with the metdata component.
/// throws an exception if the net entity does not exist.
/// </summary>
(EntityUid, MetaDataComponent) GetEntityData(NetEntity nEntity);
}

View File

@@ -87,7 +87,7 @@ namespace Robust.Shared.GameObjects
public void Dirty(EntityUid uid, Component component, MetaDataComponent? meta = null);
public void QueueDeleteEntity(EntityUid uid);
public void QueueDeleteEntity(EntityUid? uid);
public bool IsQueuedForDeletion(EntityUid uid);
@@ -129,6 +129,23 @@ namespace Robust.Shared.GameObjects
/// </summary>
EntityStringRepresentation ToPrettyString(EntityUid uid);
/// <summary>
/// Returns a string representation of an entity with various information regarding it.
/// </summary>
EntityStringRepresentation ToPrettyString(NetEntity netEntity);
/// <summary>
/// Returns a string representation of an entity with various information regarding it.
/// </summary>
[return: NotNullIfNotNull("uid")]
EntityStringRepresentation? ToPrettyString(EntityUid? uid);
/// <summary>
/// Returns a string representation of an entity with various information regarding it.
/// </summary>
[return: NotNullIfNotNull("netEntity")]
EntityStringRepresentation? ToPrettyString(NetEntity? netEntity);
#endregion Entity Management
/// <summary>

View File

@@ -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)
@@ -84,7 +92,7 @@ public readonly struct NetEntity : IEquatable<NetEntity>, IComparable<NetEntity>
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
return obj is EntityUid id && Equals(id);
return obj is NetEntity id && Equals(id);
}
/// <inheritdoc />
@@ -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);
}
@@ -214,7 +233,7 @@ public readonly struct NetEntity : IEquatable<NetEntity>, IComparable<NetEntity>
}
[ViewVariables]
private EntityUid Uid
private EntityUid _uid
{
get
{
@@ -222,5 +241,7 @@ public readonly struct NetEntity : IEquatable<NetEntity>, IComparable<NetEntity>
}
}
[ViewVariables] private NetEntity _netId => this;
#endregion
}

View File

@@ -54,9 +54,11 @@ public enum LookupFlags : byte
/// </summary>
Sensors = 1 << 6,
Uncontained = Dynamic | Static | Sundries,
Uncontained = Dynamic | Static | Sundries | Sensors,
StaticSundries = Static | Sundries,
All = Contained | Dynamic | Static | Sundries | Sensors
}
/// <summary>
@@ -90,7 +92,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
/// <summary>
/// Returns all non-grid entities. Consider using your own flags if you wish for a faster query.
/// </summary>
public const LookupFlags DefaultFlags = LookupFlags.Contained | LookupFlags.Dynamic | LookupFlags.Static | LookupFlags.Sundries;
public const LookupFlags DefaultFlags = LookupFlags.All;
public override void Initialize()
{

View File

@@ -8,6 +8,20 @@ public abstract class SharedEyeSystem : EntitySystem
{
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
/// <summary>
/// Refreshes all values for IEye with the component.
/// </summary>
public void UpdateEye(EyeComponent component)
{
if (component._eye == null)
return;
component._eye.Offset = component.Offset;
component._eye.DrawFov = component.DrawFov;
component._eye.Rotation = component.Rotation;
component._eye.Zoom = component.Zoom;
}
public void SetOffset(EntityUid uid, Vector2 value, EyeComponent? eyeComponent = null)
{
if (!Resolve(uid, ref eyeComponent))

View File

@@ -152,7 +152,10 @@ namespace Robust.Shared.GameObjects
poly,
MapGridHelpers.CollisionGroup,
MapGridHelpers.CollisionGroup,
true);
true)
{
Owner = uid
};
#pragma warning restore CS0618
newFixtures.Add(($"grid_chunk-{bounds.Left}-{bounds.Bottom}", newFixture));

View File

@@ -495,7 +495,7 @@ public abstract partial class SharedTransformSystem
throw new InvalidOperationException($"Attempted to parent entity {ToPrettyString(uid)} to non-existent entity {value.EntityId}");
}
if (newParent.LifeStage > ComponentLifeStage.Running || LifeStage(value.EntityId) > EntityLifeStage.MapInitialized)
if (newParent.LifeStage >= ComponentLifeStage.Stopping || LifeStage(value.EntityId) >= EntityLifeStage.Terminating)
{
DetachParentToNull(uid, xform);
if (_netMan.IsServer || IsClientSide(uid))

View File

@@ -95,6 +95,8 @@ namespace Robust.Shared.Network.Messages
MsgSize = buffer.LengthBytes;
}
public bool ForceSendReliably;
/// <summary>
/// Whether this state message is large enough to warrant being sent reliably.
/// This is only valid after
@@ -103,7 +105,7 @@ namespace Robust.Shared.Network.Messages
public bool ShouldSendReliably()
{
DebugTools.Assert(_hasWritten, "Attempted to determine sending method before determining packet size.");
return MsgSize > ReliableThreshold;
return ForceSendReliably || MsgSize > ReliableThreshold;
}
public override NetDeliveryMethod DeliveryMethod

View File

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

View File

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

View File

@@ -23,6 +23,7 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
@@ -31,6 +32,7 @@ using Robust.Shared.Physics.Systems;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Physics.Dynamics
@@ -50,6 +52,9 @@ namespace Robust.Shared.Physics.Dynamics
[DataField("shape")]
public IPhysShape Shape { get; private set; } = new PhysShapeAabb();
[NonSerialized]
public EntityUid Owner;
/// <summary>
/// All of the other fixtures this fixture has a contact with.
/// </summary>
@@ -176,7 +181,10 @@ namespace Robust.Shared.Physics.Dynamics
{
if (other == null) return false;
return Equivalent(other);
// Owner field shouldn't be required, fixtures on other entities shouldn't be getting compared to each other.
// This is mainly here because it might've intruded some physics bugs, so this is here just in case.
DebugTools.Assert(Owner == other.Owner);
return Equivalent(other) && Owner == other.Owner;
}
}

View File

@@ -98,6 +98,7 @@ namespace Robust.Shared.Physics.Systems
}
manager.Fixtures.Add(fixtureId, fixture);
fixture.Owner = uid;
if (body.CanCollide && Resolve(uid, ref xform))
{
@@ -205,12 +206,14 @@ namespace Robust.Shared.Physics.Systems
// hence we'll just make sure its body is set and SharedBroadphaseSystem will deal with it later.
if (Resolve(uid, ref body, false))
{
foreach (var id in component.Fixtures.Keys)
foreach (var (id, fixture) in component.Fixtures)
{
if (string.IsNullOrEmpty(id))
{
throw new InvalidOperationException($"Tried to setup fixture on init for {ToPrettyString(uid)} with no ID!");
}
fixture.Owner = uid;
}
// Make sure all the right stuff is set on the body
@@ -235,6 +238,10 @@ namespace Robust.Shared.Physics.Systems
Log.Error($"Tried to apply fixture state for an entity without physics: {ToPrettyString(uid)}");
return;
}
foreach (var fixture in component.Fixtures.Values)
{
fixture.Owner = uid;
}
var toAddFixtures = new ValueList<(string Id, Fixture Fixture)>();
var toRemoveFixtures = new ValueList<(string Id, Fixture Fixture)>();
@@ -248,6 +255,7 @@ namespace Robust.Shared.Physics.Systems
var newFixture = new Fixture();
fixture.CopyTo(newFixture);
newFixtures.Add(id, newFixture);
newFixture.Owner = uid;
}
TransformComponent? xform = null;

View File

@@ -0,0 +1,27 @@
using System;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
namespace Robust.Shared.Prototypes;
/// <summary>
/// Wrapper type for an <see cref="EntityPrototype"/> with a given id.
/// </summary>
/// <param name="Id">The id of the prototype.</param>
/// <remarks>
/// 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>
[Serializable, NetSerializable]
public readonly record struct EntProtoId(string Id)
{
public static implicit operator string(EntProtoId protoId)
{
return protoId.Id;
}
public static implicit operator EntProtoId(string id)
{
return new EntProtoId(id);
}
}

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

View File

@@ -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);
[DataField("placement")]
private EntityPlacementProperties PlacementProperties = new();

View File

@@ -82,6 +82,12 @@ public interface IPrototypeManager
/// </exception>
T Index<T>(string id) where T : class, IPrototype;
/// <inheritdoc cref="Index{T}(string)"/>
EntityPrototype Index(EntProtoId id);
/// <inheritdoc cref="Index{T}(string)"/>
T Index<T>(ProtoId<T> id) where T : class, IPrototype;
/// <summary>
/// Index for a <see cref="IPrototype"/> by ID.
/// </summary>
@@ -95,9 +101,21 @@ public interface IPrototypeManager
/// </summary>
bool HasIndex<T>(string id) where T : class, IPrototype;
/// <inheritdoc cref="HasIndex{T}(string)"/>
bool HasIndex(EntProtoId id);
/// <inheritdoc cref="HasIndex{T}(string)"/>
bool HasIndex<T>(ProtoId<T> id) where T : class, IPrototype;
bool TryIndex<T>(string id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype;
bool TryIndex(Type kind, string id, [NotNullWhen(true)] out IPrototype? prototype);
/// <inheritdoc cref="TryIndex{T}(string, out T)"/>
bool TryIndex(EntProtoId id, [NotNullWhen(true)] out EntityPrototype? prototype);
/// <inheritdoc cref="TryIndex{T}(string, out T)"/>
bool TryIndex<T>(ProtoId<T> id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype;
bool HasMapping<T>(string id);
bool TryGetMapping(Type kind, string id, [NotNullWhen(true)] out MappingDataNode? mappings);

View File

@@ -0,0 +1,25 @@
using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
namespace Robust.Shared.Prototypes;
/// <summary>
/// Wrapper type for a prototype id of kind <see cref="T"/>.
/// </summary>
/// <param name="Id">The id of the prototype.</param>
/// <typeparam name="T">The kind of prototype to wrap, for example <see cref="TileAliasPrototype"/></typeparam>
/// <remarks>
/// 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
{
public static implicit operator string(ProtoId<T> protoId)
{
return protoId.Id;
}
public static implicit operator ProtoId<T>(string id)
{
return new ProtoId<T>(id);
}
}

View File

@@ -175,7 +175,7 @@ namespace Robust.Shared.Prototypes
throw new InvalidOperationException("No prototypes have been loaded yet.");
return _kinds.Keys;
}
/// <inheritdoc />
public T Index<T>(string id) where T : class, IPrototype
{
@@ -194,6 +194,18 @@ namespace Robust.Shared.Prototypes
}
}
/// <inheritdoc />
public EntityPrototype Index(EntProtoId id)
{
return Index<EntityPrototype>(id.Id);
}
/// <inheritdoc />
public T Index<T>(ProtoId<T> id) where T : class, IPrototype
{
return Index<T>(id.Id);
}
/// <inheritdoc />
public IPrototype Index(Type kind, string id)
{
@@ -582,6 +594,18 @@ namespace Robust.Shared.Prototypes
return index.Instances.ContainsKey(id);
}
/// <inheritdoc />
public bool HasIndex(EntProtoId id)
{
return HasIndex<EntityPrototype>(id.Id);
}
/// <inheritdoc />
public bool HasIndex<T>(ProtoId<T> id) where T : class, IPrototype
{
return HasIndex<T>(id.Id);
}
/// <inheritdoc />
public bool TryIndex<T>(string id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype
{
@@ -601,6 +625,18 @@ namespace Robust.Shared.Prototypes
return index.Instances.TryGetValue(id, out prototype);
}
/// <inheritdoc />
public bool TryIndex(EntProtoId id, [NotNullWhen(true)] out EntityPrototype? prototype)
{
return TryIndex(id.Id, out prototype);
}
/// <inheritdoc />
public bool TryIndex<T>(ProtoId<T> id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype
{
return TryIndex(id.Id, out prototype);
}
/// <inheritdoc />
public bool HasMapping<T>(string id)
{

View File

@@ -9,7 +9,11 @@ namespace Robust.Shared.Serialization.Manager.Attributes
[Virtual]
public class DataFieldAttribute : DataFieldBaseAttribute
{
public readonly string Tag;
/// <summary>
/// The name of this field in YAML.
/// If null, the name of the C# field will be used instead, with the first letter lowercased.
/// </summary>
public string? Tag { get; internal set; }
/// <summary>
/// Whether or not this field being mapped is required for the component to function.
@@ -18,14 +22,13 @@ namespace Robust.Shared.Serialization.Manager.Attributes
/// </summary>
public readonly bool Required;
public DataFieldAttribute(string tag, bool readOnly = false, int priority = 1, bool required = false, bool serverOnly = false, Type? customTypeSerializer = null) : base(readOnly, priority, serverOnly, customTypeSerializer)
public DataFieldAttribute(string? tag = null, bool readOnly = false, int priority = 1, bool required = false, bool serverOnly = false, Type? customTypeSerializer = null) : base(readOnly, priority, serverOnly, customTypeSerializer)
{
Tag = tag;
Required = required;
}
public override string ToString()
public override string? ToString()
{
return Tag;
}

View File

@@ -57,6 +57,17 @@ namespace Robust.Shared.Serialization.Manager.Definition
IsRecord = isRecord;
var fieldDefs = GetFieldDefinitions(manager, isRecord);
foreach (var field in fieldDefs)
{
if (field.Attribute is not DataFieldAttribute attribute ||
attribute.Tag != null)
{
continue;
}
var name = field.FieldInfo.Name.AsSpan();
attribute.Tag = $"{char.ToLowerInvariant(name[0])}{name[1..]}";
}
var dataFields = fieldDefs
.Select(f => f.Attribute)
@@ -65,7 +76,7 @@ namespace Robust.Shared.Serialization.Manager.Definition
Duplicates = dataFields
.Where(f =>
dataFields.Count(df => df.Tag == f.Tag) > 1)
.Select(f => f.Tag)
.Select(f => f.Tag!)
.Distinct()
.ToArray();

View File

@@ -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,
@@ -134,7 +160,8 @@ public partial class SerializationManager
if (field.Attribute is DataFieldAttribute dfa)
{
if(!processedTags.Add(dfa.Tag)) continue; //tag was already processed, probably because we are using the same tag in an include
// tag is set on data definition creation
if(!processedTags.Add(dfa.Tag!)) continue; //tag was already processed, probably because we are using the same tag in an include
var key = new ValueDataNode(dfa.Tag);
if (parent.TryGetValue(key, out var parentValue))
{

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
@@ -16,14 +15,11 @@ namespace Robust.Shared.Serialization.Manager;
public sealed partial class SerializationManager
{
[Obsolete]
public static Type[] SerializerInterfaces => _serializerInterfaces.ToArray();
/// <summary>
/// <see cref="CopyCreatorIndex"/>
/// <see cref="CopierIndex"/>
/// </summary>
private static readonly ImmutableArray<Type> _serializerInterfaces = new[]
private static readonly ImmutableArray<Type> SerializerInterfaces = new[]
{
typeof(ITypeReader<,>),
typeof(ITypeInheritanceHandler<,>),
@@ -34,12 +30,12 @@ public sealed partial class SerializationManager
}.ToImmutableArray();
/// <summary>
/// <see cref="_serializerInterfaces"/>
/// <see cref="SerializerInterfaces"/>
/// </summary>
private const int CopyCreatorIndex = 3;
/// <summary>
/// <see cref="_serializerInterfaces"/>
/// <see cref="SerializerInterfaces"/>
/// </summary>
private const int CopierIndex = 4;
@@ -94,7 +90,7 @@ public sealed partial class SerializationManager
{
public SerializerProvider(IEnumerable<Type> typeSerializers)
{
foreach (var serializerInterface in _serializerInterfaces)
foreach (var serializerInterface in SerializerInterfaces)
{
RegisterSerializerInterface(serializerInterface);
}
@@ -107,7 +103,7 @@ public sealed partial class SerializationManager
public SerializerProvider()
{
foreach (var serializerInterface in _serializerInterfaces)
foreach (var serializerInterface in SerializerInterfaces)
{
RegisterSerializerInterface(serializerInterface);
}
@@ -120,7 +116,7 @@ public sealed partial class SerializationManager
/// <summary>
/// Type serializers indexed by their type serializer and type
/// that they serialize.
/// <see cref="_serializerInterfaces"/> for the first index.
/// <see cref="SerializationManager.SerializerInterfaces"/> for the first index.
/// </summary>
private (object? Regular, object? Generic)[]?[] _typeSerializersArray = new (object? Regular, object? Generic)[]?[] { };
@@ -221,7 +217,7 @@ public sealed partial class SerializationManager
{
var serializerType = val.MakeGenericType(objectType.GetGenericArguments());
serializer = RegisterSerializer(serializerType)!;
RegisterIndexedSerializer(objectType, _serializerInterfaces.IndexOf(interfaceType), serializer, false);
RegisterIndexedSerializer(objectType, SerializerInterfaces.IndexOf(interfaceType), serializer, false);
return true;
}
}
@@ -299,7 +295,7 @@ public sealed partial class SerializationManager
if (arguments.Length != 1)
throw new InvalidGenericParameterCountException();
_typeSerializers.GetOrNew(typeInterface).Add(arguments[0], obj);
RegisterIndexedSerializer(arguments[0], _serializerInterfaces.IndexOf(typeInterface), obj, true);
RegisterIndexedSerializer(arguments[0], SerializerInterfaces.IndexOf(typeInterface), obj, true);
}
}
@@ -424,7 +420,7 @@ public sealed partial class SerializationManager
Array.Resize(ref _typeSerializersArray, (id + 1) * 2);
}
var array = new (object? Regular, object? Generic)[_serializerInterfaces.Length];
var array = new (object? Regular, object? Generic)[SerializerInterfaces.Length];
_typeSerializersArray[id] = array;
if (regular)

View File

@@ -75,16 +75,15 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
foreach (var componentName in components.Keys)
{
var registration = factory.GetRegistration(componentName);
foreach (var compType in registration.References)
{
if (referenceTypes.Contains(compType))
{
throw new InvalidOperationException(
$"Duplicate component reference in prototype: '{compType}'");
}
var compType = registration.Idx;
referenceTypes.Add(compType);
if (referenceTypes.Contains(compType))
{
throw new InvalidOperationException(
$"Duplicate component reference in prototype: '{compType}'");
}
referenceTypes.Add(compType);
}
return components;
@@ -139,16 +138,14 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
foreach (var componentName in components.Keys)
{
var registration = factory.GetRegistration(componentName);
var compType = registration.Idx;
foreach (var compType in registration.References)
if (referenceTypes.Contains(compType))
{
if (referenceTypes.Contains(compType))
{
return new ErrorNode(node, "Duplicate ComponentReference.");
}
referenceTypes.Add(compType);
return new ErrorNode(node, "Duplicate ComponentReference.");
}
referenceTypes.Add(compType);
}
return new ValidatedSequenceNode(list);
@@ -202,18 +199,15 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
{
foreach (var (childReg, idx) in newCompRegDict)
{
foreach (var x in reg.References)
if (childReg.Idx.Equals(reg.Idx))
{
if (childReg.References.Contains(x))
{
newCompReg[idx] = serializationManager.PushCompositionWithGenericNode(
reg.Type,
new[] { parent[mapping] },
newCompReg[idx],
context);
newCompReg[idx] = serializationManager.PushCompositionWithGenericNode(
reg.Type,
new[] { parent[mapping] },
newCompReg[idx],
context);
goto found;
}
goto found;
}
}

View File

@@ -0,0 +1,42 @@
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
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="EntProtoId"/> types.
/// </summary>
[TypeSerializer]
public sealed class EntProtoIdSerializer : ITypeSerializer<EntProtoId, ValueDataNode>, ITypeCopyCreator<EntProtoId>
{
public ValidationNode Validate(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context = null)
{
var prototypes = dependencies.Resolve<IPrototypeManager>();
if (prototypes.HasMapping<EntityPrototype>(node.Value))
return new ValidatedValueNode(node);
return new ErrorNode(node, $"No {nameof(EntityPrototype)} found with id {node.Value}");
}
public EntProtoId Read(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null, InstantiationDelegate<EntProtoId>? instanceProvider = null)
{
return new EntProtoId(node.Value);
}
public DataNode Write(ISerializationManager serialization, EntProtoId value, IDependencyCollection dependencies, bool alwaysWrite = false, ISerializationContext? context = null)
{
return new ValueDataNode(value.Id);
}
public EntProtoId CreateCopy(ISerializationManager serializationManager, EntProtoId source, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
return source;
}
}

View File

@@ -0,0 +1,43 @@
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
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.Generic;
/// <summary>
/// Serializer used automatically for <see cref="ProtoId{T}"/> types.
/// </summary>
/// <typeparam name="T">The type of the prototype for which the id is stored.</typeparam>
[TypeSerializer]
public sealed class ProtoIdSerializer<T> : ITypeSerializer<ProtoId<T>, ValueDataNode>, ITypeCopyCreator<ProtoId<T>> where T : class, IPrototype
{
public ValidationNode Validate(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context = null)
{
var prototypes = dependencies.Resolve<IPrototypeManager>();
if (prototypes.HasMapping<T>(node.Value))
return new ValidatedValueNode(node);
return new ErrorNode(node, $"No {typeof(T)} found with id {node.Value}");
}
public ProtoId<T> Read(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null, InstantiationDelegate<ProtoId<T>>? instanceProvider = null)
{
return new ProtoId<T>(node.Value);
}
public DataNode Write(ISerializationManager serialization, ProtoId<T> value, IDependencyCollection dependencies, bool alwaysWrite = false, ISerializationContext? context = null)
{
return new ValueDataNode(value.Id);
}
public ProtoId<T> CreateCopy(ISerializationManager serializationManager, ProtoId<T> source, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
return source;
}
}

View File

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

View File

@@ -1,8 +1,10 @@
using System;
using System.Data;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Shared.Utility
{
@@ -36,6 +38,67 @@ namespace Robust.Shared.Utility
throw new DebugAssertException();
}
[Conditional("DEBUG")]
[AssertionMethod]
public static void AssertEqual(object? objA, object? objB)
{
if (ReferenceEquals(objA, objB))
return;
if (objA == null || !objA.Equals(objB))
throw new DebugAssertException($"Expected: {objB ?? "null"} but was {objA ?? "null"}");
}
[Conditional("DEBUG")]
[AssertionMethod]
public static void AssertEqual(object? objA, object? objB, string message)
{
if (ReferenceEquals(objA, objB))
return;
if (objA == null || !objA.Equals(objB))
throw new DebugAssertException($"{message}\nExpected: {objB ?? "null"} but was {objA ?? "null"}");
}
[Conditional("DEBUG")]
[AssertionMethod]
public static void AssertNotEqual(object? objA, object? objB)
{
if (ReferenceEquals(objA, objB))
throw new DebugAssertException($"Expected: not {objB ?? "null"}");
if (objA == null || !objA.Equals(objB))
return;
throw new DebugAssertException($"Expected: not {objB}");
}
[Conditional("DEBUG")]
[AssertionMethod]
public static void AssertNotEqual(object? objA, object? objB, string message)
{
if (ReferenceEquals(objA, objB))
throw new DebugAssertException($"{message}\nExpected: not {objB ?? "null"}");
if (objA == null || !objA.Equals(objB))
return;
throw new DebugAssertException($"{message}\nExpected: not {objB}");
}
[Conditional("DEBUG")]
[AssertionMethod]
public static void AssertOwner(EntityUid? uid, IComponent component)
{
if (uid == null)
throw new DebugAssertException($"Null entity uid cannot own a component. Component: {component.GetType().Name}");
// Whenever .owner is removed this will need to be replaced by something.
// We need some way to ensure that people don't mix up uids & components when calling methods.
if (component.Owner != uid)
throw new DebugAssertException($"Entity {uid} is not the owner of the component. Component: {component.GetType().Name}");
}
/// <summary>
/// An assertion that will <see langword="throw" /> an exception if the
/// <paramref name="condition" /> is not true.

View File

@@ -223,13 +223,14 @@ namespace Robust.UnitTesting.Server
container.RegisterInstance<IConsoleHost>(new Mock<IConsoleHost>().Object); //Console is technically a frontend, we want to run headless
container.Register<IEntityManager, EntityManager>();
container.Register<EntityManager, EntityManager>();
container.Register<IMapManager, MapManager>();
container.Register<IMapManager, NetworkedMapManager>();
container.Register<INetworkedMapManager, NetworkedMapManager>();
container.Register<IMapManagerInternal, NetworkedMapManager>();
container.Register<ISerializationManager, SerializationManager>();
container.Register<IPrototypeManager, ServerPrototypeManager>();
container.Register<IComponentFactory, ComponentFactory>();
container.Register<IEntitySystemManager, EntitySystemManager>();
container.Register<IManifoldManager, CollisionManager>();
container.Register<IMapManagerInternal, MapManager>();
container.Register<INetManager, NetManager>();
container.Register<IAuthManager, AuthManager>();
container.Register<ITileDefinitionManager, TileDefinitionManager>();

View File

@@ -153,6 +153,156 @@ public sealed partial class ComponentStateTests : RobustIntegrationTest
}
}
/// <summary>
/// This is a variant of <see cref="UnknownEntityTest"/> that deletes one of the entities before the other entity gets sent.
/// </summary>
[Test]
public async Task UnknownEntityDeleteTest()
{
// The first chunk of the test just follows UnknownEntityTest
var compReg = () => IoCManager.Resolve<IComponentFactory>().RegisterClass<UnknownEntityTestComponent>();
var sysReg = () => IoCManager.Resolve<IEntitySystemManager>().LoadExtraSystemType<UnknownEntityTestComponent.UnknownEntityTestComponent_AutoNetworkSystem>();
var serverOpts = new ServerIntegrationOptions
{
Pool = false,
BeforeRegisterComponents = compReg,
BeforeStart = sysReg,
};
var clientOpts = new ClientIntegrationOptions
{
Pool = false,
BeforeRegisterComponents = compReg,
BeforeStart = sysReg,
};
var server = StartServer(serverOpts);
var client = StartClient(clientOpts);
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
var netMan = client.ResolveDependency<IClientNetManager>();
var xforms = server.System<SharedTransformSystem>();
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
client.Post(() => netMan.ClientConnect(null!, 0, null!));
server.Post(() => server.CfgMan.SetCVar(CVars.NetPVS, true));
// Set up map.
EntityUid map = default;
await server.WaitPost(() =>
{
var mapId = server.MapMan.CreateMap();
map = server.MapMan.GetMapEntityId(mapId);
});
await RunTicks();
// Spawn entities
var coordsA = new EntityCoordinates(map, default);
var coordsB = new EntityCoordinates(map, new Vector2(100, 100));
EntityUid player = default;
EntityUid cPlayer = default;
EntityUid serverEntA = default;
EntityUid serverEntB = default;
NetEntity serverNetA = default;
NetEntity serverNetB = default;
await server.WaitPost(() =>
{
// Attach player.
player = server.EntMan.Spawn();
var session = (IPlayerSession) server.PlayerMan.Sessions.First();
server.System<ActorSystem>().Attach(player, session);
session.JoinGame();
// Spawn test entities.
serverEntA = server.EntMan.SpawnAttachedTo(null, coordsA);
serverEntB = server.EntMan.SpawnAttachedTo(null, coordsB);
serverNetA = server.EntMan.GetNetEntity(serverEntA);
serverNetB = server.EntMan.GetNetEntity(serverEntB);
// Setup components
var cmp = server.EntMan.EnsureComponent<UnknownEntityTestComponent>(serverEntA);
cmp.Other = serverEntB;
server.EntMan.Dirty(serverEntA, cmp);
cmp = server.EntMan.EnsureComponent<UnknownEntityTestComponent>(serverEntB);
cmp.Other = serverEntA;
server.EntMan.Dirty(serverEntB, cmp);
});
await RunTicks();
// Check player got properly attached and only knows about the expected entities
await client.WaitPost(() =>
{
cPlayer = client.EntMan.GetEntity(server.EntMan.GetNetEntity(player));
Assert.That(client.AttachedEntity, Is.EqualTo(cPlayer));
Assert.That(client.EntMan.EntityExists(cPlayer));
Assert.That(client.EntMan.EntityExists(client.EntMan.GetEntity(serverNetA)), Is.False);
Assert.That(client.EntMan.EntityExists(client.EntMan.GetEntity(serverNetB)), Is.False);
});
// Move the player into PVS range of one of the entities.
await server.WaitPost(() => xforms.SetCoordinates(player, coordsB));
await RunTicks();
await client.WaitPost(() =>
{
var clientEntA = client.EntMan.GetEntity(serverNetA);
var clientEntB = client.EntMan.GetEntity(serverNetB);
Assert.That(client.EntMan.EntityExists(clientEntB), Is.True);
Assert.That(client.EntMan.EntityExists(client.EntMan.GetEntity(serverNetA)), Is.False);
Assert.That(client.EntMan.TryGetComponent(clientEntB, out UnknownEntityTestComponent? cmp));
Assert.That(cmp?.Other, Is.EqualTo(clientEntA));
});
// This is where the test difffers from UnknownEntityTest:
// We delete the entity that the player knows about before it receives the entity that it references.
await server.WaitPost(() =>
{
server.EntMan.DeleteEntity(serverEntB);
var comp = server.EntMan.GetComponent<UnknownEntityTestComponent>(serverEntA);
comp.Other = EntityUid.Invalid;
server.EntMan.Dirty(serverEntA, comp);
});
await RunTicks();
await client.WaitPost(() =>
{
var clientEntA = client.EntMan.GetEntity(serverNetA);
var clientEntB = client.EntMan.GetEntity(serverNetB);
Assert.That(clientEntA, Is.EqualTo(EntityUid.Invalid));
Assert.That(clientEntB, Is.EqualTo(EntityUid.Invalid));
});
// Move the player into PVS range of the other entity
await server.WaitPost(() => xforms.SetCoordinates(player, coordsA));
await RunTicks();
await client.WaitPost(() =>
{
var clientEntA = client.EntMan.GetEntity(serverNetA);
var clientEntB = client.EntMan.GetEntity(serverNetB);
Assert.That(clientEntB, Is.EqualTo(EntityUid.Invalid));
Assert.That(client.EntMan.EntityExists(clientEntA), Is.True);
Assert.That(client.EntMan.TryGetComponent(clientEntA, out UnknownEntityTestComponent? cmp));
Assert.That(cmp?.Other, Is.EqualTo(EntityUid.Invalid));
});
server.Post(() => server.CfgMan.SetCVar(CVars.NetPVS, false));
// wait for errors.
await RunTicks();
async Task RunTicks()
{
for (int i = 0; i < 10; i++)
{
await server!.WaitRunTicks(1);
await client!.WaitRunTicks(1);
}
}
}
}
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]