mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
415c518bc7 | ||
|
|
2417dbb0e0 | ||
|
|
005673a957 | ||
|
|
942db3120c | ||
|
|
c0a5fab19e | ||
|
|
d16c62b132 | ||
|
|
7da22557fe | ||
|
|
92f47c0f20 | ||
|
|
9576d0739f | ||
|
|
10f25faabf | ||
|
|
74831a177e | ||
|
|
88d3168913 | ||
|
|
902519093c | ||
|
|
366266a8ae | ||
|
|
a22cce7783 | ||
|
|
e5e738b8cd | ||
|
|
b8f6e83473 | ||
|
|
c5fb186c57 | ||
|
|
131d7f5422 | ||
|
|
217996f1ed | ||
|
|
fc718d68a5 | ||
|
|
d7d9578803 | ||
|
|
9bbeb54569 | ||
|
|
1ea7071ffb | ||
|
|
196028b619 | ||
|
|
c102da052f | ||
|
|
5d46cdcfa4 | ||
|
|
cd646d3b07 | ||
|
|
922165fa19 | ||
|
|
4879252e99 | ||
|
|
0e21f5727a | ||
|
|
3ce8a00389 | ||
|
|
9a283fe541 | ||
|
|
f3e3e64db3 | ||
|
|
4a4a135089 | ||
|
|
e323a67806 | ||
|
|
3a328ffdd5 | ||
|
|
bc5107e297 | ||
|
|
2abf33c9be | ||
|
|
71c46828c2 | ||
|
|
814d6fe2d0 | ||
|
|
77b98b8308 | ||
|
|
34b0a7fc6d | ||
|
|
1b6123c79f | ||
|
|
1476f9d462 | ||
|
|
d62efe7301 | ||
|
|
6af0c88f27 | ||
|
|
5f05b0aa2a | ||
|
|
e6c335b6cd |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- type: entity
|
||||
id: debugRotation
|
||||
abstract: true
|
||||
suffix: DEBUG
|
||||
categories: [ debug ]
|
||||
components:
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
|
||||
17
Resources/EnginePrototypes/entityCategory.yml
Normal file
17
Resources/EnginePrototypes/entityCategory.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
# debug related entities
|
||||
- type: entityCategory
|
||||
id: debug
|
||||
name: entity-category-name-debug
|
||||
description: entity-category-desc-debug
|
||||
|
||||
# entities that spawn other entities
|
||||
- type: entityCategory
|
||||
id: spawner
|
||||
name: entity-category-name-spawner
|
||||
description: entity-category-desc-spawner
|
||||
|
||||
# entities that should be hidden from the spawn menu
|
||||
- type: entityCategory
|
||||
id: hideSpawnMenu
|
||||
name: entity-category-name-hide
|
||||
description: entity-category-desc-hide
|
||||
8
Resources/Locale/en-US/entity-category.ftl
Normal file
8
Resources/Locale/en-US/entity-category.ftl
Normal file
@@ -0,0 +1,8 @@
|
||||
entity-category-name-debug = Debug
|
||||
entity-category-desc-debug = Entity prototypes intended for debugging & testing.
|
||||
|
||||
entity-category-name-spawner = Spawner
|
||||
entity-category-desc-spawner = Entity prototypes that spawn other entities.
|
||||
|
||||
entity-category-name-hide = Hidden
|
||||
entity-category-desc-hide = Entity prototypes that should be hidden from the spawn menu
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
48
Robust.Client/ViewVariables/Editors/VVPropEditorNetEntity.cs
Normal file
48
Robust.Client/ViewVariables/Editors/VVPropEditorNetEntity.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -879,7 +879,7 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (netId, component) in EntityManager.GetNetComponents(entity))
|
||||
foreach (var component in metadata.NetComponents.Values)
|
||||
{
|
||||
var compName = _factory.GetComponentName(component.GetType());
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Robust.Server.GameObjects;
|
||||
public sealed class ServerMetaDataSystem : MetaDataSystem
|
||||
{
|
||||
[Dependency] private readonly PvsSystem _pvsSystem = default!;
|
||||
private EntityQuery<MetaDataComponent> _mQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -16,6 +17,7 @@ public sealed class ServerMetaDataSystem : MetaDataSystem
|
||||
|
||||
EntityManager.ComponentAdded += OnComponentAdded;
|
||||
EntityManager.ComponentRemoved += OnComponentRemoved;
|
||||
_mQuery = GetEntityQuery<MetaDataComponent>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -45,7 +47,7 @@ public sealed class ServerMetaDataSystem : MetaDataSystem
|
||||
if (obj.Terminating || !removed.NetSyncEnabled || (!removed.SessionSpecific && !removed.SendOnlyToOwner))
|
||||
return;
|
||||
|
||||
foreach (var (_, comp) in EntityManager.GetNetComponents(obj.BaseArgs.Owner))
|
||||
foreach (var comp in obj.Meta.NetComponents.Values)
|
||||
{
|
||||
if (comp.LifeStage >= ComponentLifeStage.Removing)
|
||||
continue;
|
||||
@@ -55,7 +57,7 @@ public sealed class ServerMetaDataSystem : MetaDataSystem
|
||||
}
|
||||
|
||||
// remove the flag
|
||||
MetaData(obj.BaseArgs.Owner).Flags &= ~MetaDataFlags.SessionSpecific;
|
||||
obj.Meta.Flags &= ~MetaDataFlags.SessionSpecific;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -67,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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
@@ -39,16 +41,35 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
[Access(typeof(SharedPointLightSystem))]
|
||||
[DataField("enabled")]
|
||||
public bool Enabled = true;
|
||||
|
||||
// TODO ECS animations
|
||||
[Animatable]
|
||||
public bool Enabled { get; set; } = true;
|
||||
public bool AnimatedEnable
|
||||
{
|
||||
[Obsolete]
|
||||
get => Enabled;
|
||||
|
||||
[Obsolete]
|
||||
set => IoCManager.Resolve<IEntityManager>().System<SharedPointLightSystem>().SetEnabled(Owner, value, this);
|
||||
}
|
||||
|
||||
// TODO ECS animations
|
||||
[Animatable]
|
||||
public float AnimatedRadius
|
||||
{
|
||||
[Obsolete]
|
||||
get => Radius;
|
||||
[Obsolete]
|
||||
set => IoCManager.Resolve<IEntityManager>().System<SharedPointLightSystem>().SetRadius(Owner, value, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How far the light projects.
|
||||
/// </summary>
|
||||
[DataField("radius")]
|
||||
[Access(typeof(SharedPointLightSystem))]
|
||||
[Animatable]
|
||||
public float Radius { get; set; } = 5f;
|
||||
public float Radius = 5f;
|
||||
|
||||
[ViewVariables]
|
||||
public bool ContainerOccluded;
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
@@ -63,6 +64,12 @@ namespace Robust.Shared.GameObjects
|
||||
[DataField("desc")] internal string? _entityDescription;
|
||||
internal EntityPrototype? _entityPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// The components attached to the entity that are currently networked.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
internal readonly Dictionary<ushort, Component> NetComponents = new();
|
||||
|
||||
/// <summary>
|
||||
/// Network identifier for this entity.
|
||||
/// </summary>
|
||||
@@ -87,7 +94,8 @@ namespace Robust.Shared.GameObjects
|
||||
public GameTick LastStateApplied { get; internal set; } = GameTick.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// This is the most recent tick at which some component was removed from this entity.
|
||||
/// This is the most recent tick at which a networked component was removed from this entity.
|
||||
/// Currently only reliable server-side, client side prediction may cause the value to be wrong.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public GameTick LastComponentRemoved { get; internal set; } = GameTick.Zero;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<T>(EntityUid, out T)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryComp(EntityUid uid, [NotNullWhen(true)] out TransformComponent? comp)
|
||||
{
|
||||
return EntityManager.TransformQuery.TryGetComponent(uid, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryGetComponent<T>(EntityUid, out T)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryComp(EntityUid uid, [NotNullWhen(true)] out MetaDataComponent? comp)
|
||||
{
|
||||
return EntityManager.MetaQuery.TryGetComponent(uid, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryGetComponent<T>(EntityUid?, out T)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool TryComp<T>([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out T? comp)
|
||||
@@ -459,6 +485,30 @@ public partial class EntitySystem
|
||||
return EntityManager.TryGetComponent(uid.Value, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryGetComponent<T>(EntityUid?, out T)"/>
|
||||
protected bool TryComp([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TransformComponent? comp)
|
||||
{
|
||||
if (!uid.HasValue)
|
||||
{
|
||||
comp = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
return EntityManager.TransformQuery.TryGetComponent(uid.Value, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryGetComponent<T>(EntityUid?, out T)"/>
|
||||
protected bool TryComp([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out MetaDataComponent? comp)
|
||||
{
|
||||
if (!uid.HasValue)
|
||||
{
|
||||
comp = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
return EntityManager.MetaQuery.TryGetComponent(uid.Value, out comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.GetComponents"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected IEnumerable<IComponent> AllComps(EntityUid uid)
|
||||
@@ -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)]
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -28,7 +28,7 @@ public record struct PhysicsHull()
|
||||
e.Normalize();
|
||||
|
||||
// discard points left of e and find point furthest to the right of e
|
||||
var rightPoints = new Vector2[PhysicsConstants.MaxPolygonVertices];
|
||||
Span<Vector2> rightPoints = stackalloc Vector2[PhysicsConstants.MaxPolygonVertices];
|
||||
var rightCount = 0;
|
||||
|
||||
var bestIndex = 0;
|
||||
|
||||
@@ -24,6 +24,8 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -131,7 +133,7 @@ namespace Robust.Shared.Physics
|
||||
|
||||
aabb ??= _extractAabb(item);
|
||||
|
||||
if (HasNaNs(aabb.Value))
|
||||
if (aabb.Value.HasNan())
|
||||
{
|
||||
_nodeLookup[item] = DynamicTree.Proxy.Free;
|
||||
return true;
|
||||
@@ -179,28 +181,25 @@ namespace Robust.Shared.Physics
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public bool Update(in T item, Box2? newBox = null)
|
||||
{
|
||||
if (!TryGetProxy(item, out var proxy))
|
||||
{
|
||||
ref var proxy = ref CollectionsMarshal.GetValueRefOrNullRef(_nodeLookup, item);
|
||||
if (Unsafe.IsNullRef(ref proxy))
|
||||
return false;
|
||||
}
|
||||
|
||||
newBox ??= _extractAabb(item);
|
||||
|
||||
if (HasNaNs(newBox.Value))
|
||||
if (newBox.Value.HasNan())
|
||||
{
|
||||
if (proxy == DynamicTree.Proxy.Free)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_b2Tree.DestroyProxy(proxy);
|
||||
_nodeLookup[item] = DynamicTree.Proxy.Free;
|
||||
proxy = DynamicTree.Proxy.Free;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (proxy == DynamicTree.Proxy.Free)
|
||||
{
|
||||
_nodeLookup[item] = _b2Tree.CreateProxy(newBox.Value, item);
|
||||
proxy = _b2Tree.CreateProxy(newBox.Value, item);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -331,36 +330,30 @@ namespace Robust.Shared.Physics
|
||||
public void AddOrUpdate(T item, Box2? aabb = null)
|
||||
{
|
||||
aabb ??= _extractAabb(item);
|
||||
if (!_nodeLookup.TryGetValue(item, out var proxy))
|
||||
|
||||
ref var proxy = ref CollectionsMarshal.GetValueRefOrAddDefault(_nodeLookup, item, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
_nodeLookup[item] = HasNaNs(aabb.Value) ? DynamicTree.Proxy.Free : _b2Tree.CreateProxy(aabb.Value, item);
|
||||
proxy = aabb.Value.HasNan() ? DynamicTree.Proxy.Free : _b2Tree.CreateProxy(aabb.Value, item);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasNaNs(aabb.Value))
|
||||
if (aabb.Value.HasNan())
|
||||
{
|
||||
if (proxy == DynamicTree.Proxy.Free)
|
||||
return;
|
||||
|
||||
_b2Tree.DestroyProxy(proxy);
|
||||
_nodeLookup[item] = DynamicTree.Proxy.Free;
|
||||
proxy = DynamicTree.Proxy.Free;
|
||||
return;
|
||||
}
|
||||
|
||||
if (proxy == DynamicTree.Proxy.Free)
|
||||
_nodeLookup[item] = _b2Tree.CreateProxy(aabb.Value, item);
|
||||
proxy = _b2Tree.CreateProxy(aabb.Value, item);
|
||||
else
|
||||
_b2Tree.MoveProxy(proxy, aabb.Value, Vector2.Zero);
|
||||
}
|
||||
|
||||
private static bool HasNaNs(in Box2 box)
|
||||
{
|
||||
return float.IsNaN(box.Left)
|
||||
|| float.IsNaN(box.Top)
|
||||
|| float.IsNaN(box.Bottom)
|
||||
|| float.IsNaN(box.Right);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG_DYNAMIC_TREE")]
|
||||
[Conditional("DEBUG_DYNAMIC_TREE_ASSERTS")]
|
||||
[DebuggerNonUserCode]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
27
Robust.Shared/Prototypes/EntProtoId.cs
Normal file
27
Robust.Shared/Prototypes/EntProtoId.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
25
Robust.Shared/Prototypes/EntityCategoryPrototype.cs
Normal file
25
Robust.Shared/Prototypes/EntityCategoryPrototype.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.Prototypes;
|
||||
|
||||
/// <summary>
|
||||
/// Prototype that represents game entities.
|
||||
/// </summary>
|
||||
[Prototype("entityCategory")]
|
||||
public sealed class EntityCategoryPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Localized name of the category, for use in entity spawn menus.
|
||||
/// </summary>
|
||||
[DataField("name")]
|
||||
public string? Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Localized description of the category, for use in entity spawn menus.
|
||||
/// </summary>
|
||||
[DataField("description")]
|
||||
public string? Description { get; private set; }
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -12,6 +11,7 @@ using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Prototypes
|
||||
@@ -27,6 +27,9 @@ namespace Robust.Shared.Prototypes
|
||||
|
||||
private static readonly Dictionary<string, string> LocPropertiesDefault = new();
|
||||
|
||||
[ValidatePrototypeId<EntityCategoryPrototype>]
|
||||
private const string HideCategory = "hideSpawnMenu";
|
||||
|
||||
// LOCALIZATION NOTE:
|
||||
// Localization-related properties in here are manually localized in LocalizationManager.
|
||||
// As such, they should NOT be inherited to avoid confusing the system.
|
||||
@@ -57,6 +60,10 @@ namespace Robust.Shared.Prototypes
|
||||
[DataField("suffix")]
|
||||
public string? SetSuffix { get; private set; }
|
||||
|
||||
[DataField("categories")]
|
||||
[AlwaysPushInheritance]
|
||||
public HashSet<string> Categories = new();
|
||||
|
||||
[ViewVariables]
|
||||
public IReadOnlyDictionary<string, string> LocProperties => _locPropertiesSet ?? LocPropertiesDefault;
|
||||
|
||||
@@ -92,8 +99,11 @@ namespace Robust.Shared.Prototypes
|
||||
[ViewVariables]
|
||||
[NeverPushInheritance]
|
||||
[DataField("noSpawn")]
|
||||
[Obsolete("Use the HideSpawnMenu")]
|
||||
public bool NoSpawn { get; private set; }
|
||||
|
||||
public bool HideSpawnMenu => Categories.Contains(HideCategory);
|
||||
|
||||
[DataField("placement")]
|
||||
private EntityPlacementProperties PlacementProperties = new();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
25
Robust.Shared/Prototypes/ProtoId.cs
Normal file
25
Robust.Shared/Prototypes/ProtoId.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public sealed class AngleTypeParser : TypeParser<Angle>
|
||||
|
||||
if (word.EndsWith("deg"))
|
||||
{
|
||||
if (!float.TryParse(word[..^3], out var f))
|
||||
if (!float.TryParse(word[..^3], CultureInfo.InvariantCulture, out var f))
|
||||
{
|
||||
error = new InvalidAngle(word);
|
||||
result = null;
|
||||
@@ -47,7 +47,7 @@ public sealed class AngleTypeParser : TypeParser<Angle>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!float.TryParse(word, out var f))
|
||||
if (!float.TryParse(word, CultureInfo.InvariantCulture, out var f))
|
||||
{
|
||||
error = new InvalidAngle(word);
|
||||
result = null;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Utility
|
||||
{
|
||||
@@ -36,6 +38,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.
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user