mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-06 17:58:05 +02:00
Clean up Client ContainerSystem (#6563)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
co-authored by
metalgearsloth
parent
c5bf078717
commit
5d053dfabb
@@ -11,353 +11,350 @@ using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Shared.Containers.ContainerManagerComponent;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed partial class ContainerSystem : SharedContainerSystem
|
||||
{
|
||||
public sealed partial class ContainerSystem : SharedContainerSystem
|
||||
[Dependency] private IRobustSerializer _serializer = default!;
|
||||
[Dependency] private IDynamicTypeFactoryInternal _dynFactory = default!;
|
||||
[Dependency] private PointLightSystem _lightSys = default!;
|
||||
[Dependency] private SpriteSystem _sprite = default!;
|
||||
|
||||
[Dependency] private EntityQuery<PointLightComponent> _pointLightQuery = default!;
|
||||
[Dependency] private EntityQuery<SpriteComponent> _spriteQuery = default!;
|
||||
|
||||
private readonly HashSet<EntityUid> _updateQueue = new();
|
||||
|
||||
public readonly Dictionary<NetEntity, BaseContainer> ExpectedEntities = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
[Dependency] private IRobustSerializer _serializer = default!;
|
||||
[Dependency] private IDynamicTypeFactoryInternal _dynFactory = default!;
|
||||
[Dependency] private PointLightSystem _lightSys = default!;
|
||||
base.Initialize();
|
||||
|
||||
private EntityQuery<PointLightComponent> _pointLightQuery;
|
||||
private EntityQuery<SpriteComponent> _spriteQuery;
|
||||
EntityManager.EntityInitialized += HandleEntityInitialized;
|
||||
SubscribeLocalEvent<ContainerManagerComponent, ComponentHandleState>(HandleComponentState);
|
||||
|
||||
private readonly HashSet<EntityUid> _updateQueue = new();
|
||||
UpdatesBefore.Add(typeof(SpriteSystem));
|
||||
}
|
||||
|
||||
public readonly Dictionary<NetEntity, BaseContainer> ExpectedEntities = new();
|
||||
public override void Shutdown()
|
||||
{
|
||||
EntityManager.EntityInitialized -= HandleEntityInitialized;
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
protected override void ValidateMissingEntity(EntityUid uid, BaseContainer cont, EntityUid missing)
|
||||
{
|
||||
var netEntity = GetNetEntity(missing);
|
||||
DebugTools.Assert(ExpectedEntities.TryGetValue(netEntity, out var expectedContainer) && expectedContainer == cont && cont.ExpectedEntities.Contains(netEntity));
|
||||
}
|
||||
|
||||
private void HandleEntityInitialized(Entity<MetaDataComponent> ent)
|
||||
{
|
||||
var (uid, meta) = ent;
|
||||
if (!RemoveExpectedEntity(meta.NetEntity, out var container))
|
||||
return;
|
||||
|
||||
Insert((uid, TransformQuery.GetComponent(uid), MetaQuery.GetComponent(uid), null), container, force: true);
|
||||
}
|
||||
|
||||
public override void ShutdownContainer(BaseContainer container)
|
||||
{
|
||||
foreach (var ent in container.ExpectedEntities)
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_pointLightQuery = GetEntityQuery<PointLightComponent>();
|
||||
_spriteQuery = GetEntityQuery<SpriteComponent>();
|
||||
|
||||
EntityManager.EntityInitialized += HandleEntityInitialized;
|
||||
SubscribeLocalEvent<ContainerManagerComponent, ComponentHandleState>(HandleComponentState);
|
||||
|
||||
UpdatesBefore.Add(typeof(SpriteSystem));
|
||||
if (ExpectedEntities.Remove(ent, out var c))
|
||||
DebugTools.Assert(c == container);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
EntityManager.EntityInitialized -= HandleEntityInitialized;
|
||||
base.Shutdown();
|
||||
}
|
||||
base.ShutdownContainer(container);
|
||||
}
|
||||
|
||||
protected override void ValidateMissingEntity(EntityUid uid, BaseContainer cont, EntityUid missing)
|
||||
{
|
||||
var netEntity = GetNetEntity(missing);
|
||||
DebugTools.Assert(ExpectedEntities.TryGetValue(netEntity, out var expectedContainer) && expectedContainer == cont && cont.ExpectedEntities.Contains(netEntity));
|
||||
}
|
||||
private void HandleComponentState(EntityUid uid, ContainerManagerComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not ContainerManagerComponentState cast)
|
||||
return;
|
||||
|
||||
private void HandleEntityInitialized(Entity<MetaDataComponent> ent)
|
||||
{
|
||||
var (uid, meta) = ent;
|
||||
if (!RemoveExpectedEntity(meta.NetEntity, out var container))
|
||||
return;
|
||||
var xform = TransformQuery.GetComponent(uid);
|
||||
|
||||
Insert((uid, TransformQuery.GetComponent(uid), MetaQuery.GetComponent(uid), null), container, force: true);
|
||||
}
|
||||
|
||||
public override void ShutdownContainer(BaseContainer container)
|
||||
// Delete now-gone containers.
|
||||
var toDelete = new ValueList<string>();
|
||||
foreach (var (id, container) in component.Containers)
|
||||
{
|
||||
foreach (var ent in container.ExpectedEntities)
|
||||
if (cast.Containers.TryGetValue(id, out var castContainer))
|
||||
{
|
||||
if (ExpectedEntities.Remove(ent, out var c))
|
||||
DebugTools.Assert(c == container);
|
||||
DebugTools.Assert(castContainer.ContainerType == container.GetType().Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
base.ShutdownContainer(container);
|
||||
foreach (var entity in container.ContainedEntities.ToArray())
|
||||
{
|
||||
Remove(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)),
|
||||
container,
|
||||
force: true,
|
||||
reparent: false
|
||||
);
|
||||
|
||||
DebugTools.Assert(!container.Contains(entity));
|
||||
}
|
||||
|
||||
ShutdownContainer(container);
|
||||
toDelete.Add(id);
|
||||
}
|
||||
|
||||
private void HandleComponentState(EntityUid uid, ContainerManagerComponent component, ref ComponentHandleState args)
|
||||
foreach (var dead in toDelete.Span)
|
||||
{
|
||||
if (args.Current is not ContainerManagerComponentState cast)
|
||||
return;
|
||||
component.Containers.Remove(dead);
|
||||
}
|
||||
|
||||
var xform = TransformQuery.GetComponent(uid);
|
||||
// Add new containers and update existing contents.
|
||||
|
||||
// Delete now-gone containers.
|
||||
var toDelete = new ValueList<string>();
|
||||
foreach (var (id, container) in component.Containers)
|
||||
foreach (var (id, data) in cast.Containers)
|
||||
{
|
||||
if (!component.Containers.TryGetValue(id, out var container))
|
||||
{
|
||||
if (cast.Containers.ContainsKey(id))
|
||||
var type = _serializer.FindSerializedType(typeof(BaseContainer), data.ContainerType);
|
||||
container = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type!, inject: false);
|
||||
container.Init(this, id, (uid, component));
|
||||
component.Containers.Add(id, container);
|
||||
}
|
||||
|
||||
DebugTools.Assert(container.ID == id);
|
||||
container.ShowContents = data.ShowContents;
|
||||
container.OccludesLight = data.OccludesLight;
|
||||
|
||||
// Remove gone entities.
|
||||
var toRemove = new ValueList<EntityUid>();
|
||||
|
||||
DebugTools.Assert(!container.Contains(EntityUid.Invalid));
|
||||
|
||||
var stateNetEnts = data.ContainedEntities;
|
||||
var stateEnts = GetEntityArray(stateNetEnts); // No need to ensure entities.
|
||||
|
||||
foreach (var entity in container.ContainedEntities)
|
||||
{
|
||||
if (!stateEnts.Contains(entity))
|
||||
toRemove.Add(entity);
|
||||
}
|
||||
|
||||
foreach (var entity in toRemove.Span)
|
||||
{
|
||||
Remove(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)),
|
||||
container,
|
||||
force: true,
|
||||
reparent: false
|
||||
);
|
||||
|
||||
DebugTools.Assert(!container.Contains(entity));
|
||||
}
|
||||
|
||||
// Remove entities that were expected, but have been removed from the container.
|
||||
var removedExpected = new ValueList<NetEntity>();
|
||||
foreach (var netEntity in container.ExpectedEntities)
|
||||
{
|
||||
if (!stateNetEnts.Contains(netEntity))
|
||||
removedExpected.Add(netEntity);
|
||||
}
|
||||
|
||||
foreach (var entityUid in removedExpected.Span)
|
||||
{
|
||||
RemoveExpectedEntity(entityUid, out _);
|
||||
}
|
||||
|
||||
// Add new entities.
|
||||
for (var i = 0; i < stateNetEnts.Length; i++)
|
||||
{
|
||||
var entity = stateEnts[i];
|
||||
var netEnt = stateNetEnts[i];
|
||||
if (!entity.IsValid())
|
||||
{
|
||||
DebugTools.Assert(cast.Containers[id].ContainerType == container.GetType().Name);
|
||||
DebugTools.Assert(netEnt.IsValid());
|
||||
AddExpectedEntity(netEnt, container);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var entity in container.ContainedEntities.ToArray())
|
||||
{
|
||||
Remove(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)),
|
||||
container,
|
||||
force: true,
|
||||
reparent: false
|
||||
);
|
||||
var meta = MetaData(entity);
|
||||
DebugTools.Assert(meta.NetEntity == netEnt);
|
||||
|
||||
DebugTools.Assert(!container.Contains(entity));
|
||||
// If an entity is currently in the shadow realm, it means we probably left PVS and are now getting
|
||||
// back into range. We do not want to directly insert this entity, as IF the container and entity
|
||||
// transform states did not get sent simultaneously, the entity's transform will be modified by the
|
||||
// insert operation. This means it will then be reset to the shadow realm, causing it to be ejected
|
||||
// from the container. It would then subsequently be parented to the container without ever being
|
||||
// re-inserted, leading to the client seeing what should be hidden entities attached to
|
||||
// containers/players.
|
||||
if ((meta.Flags & MetaDataFlags.Detached) != 0)
|
||||
{
|
||||
AddExpectedEntity(netEnt, container);
|
||||
continue;
|
||||
}
|
||||
|
||||
ShutdownContainer(container);
|
||||
toDelete.Add(id);
|
||||
}
|
||||
if (container.Contains(entity))
|
||||
continue;
|
||||
|
||||
foreach (var dead in toDelete.Span)
|
||||
{
|
||||
component.Containers.Remove(dead);
|
||||
}
|
||||
RemoveExpectedEntity(netEnt, out _);
|
||||
Insert(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity), null),
|
||||
container,
|
||||
xform,
|
||||
force: true
|
||||
);
|
||||
|
||||
// Add new containers and update existing contents.
|
||||
|
||||
foreach (var (id, data) in cast.Containers)
|
||||
{
|
||||
if (!component.Containers.TryGetValue(id, out var container))
|
||||
{
|
||||
var type = _serializer.FindSerializedType(typeof(BaseContainer), data.ContainerType);
|
||||
container = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type!, inject: false);
|
||||
container.Init(this, id, (uid, component));
|
||||
component.Containers.Add(id, container);
|
||||
}
|
||||
|
||||
DebugTools.Assert(container.ID == id);
|
||||
container.ShowContents = data.ShowContents;
|
||||
container.OccludesLight = data.OccludesLight;
|
||||
|
||||
// Remove gone entities.
|
||||
var toRemove = new ValueList<EntityUid>();
|
||||
|
||||
DebugTools.Assert(!container.Contains(EntityUid.Invalid));
|
||||
|
||||
var stateNetEnts = data.ContainedEntities;
|
||||
var stateEnts = GetEntityArray(stateNetEnts); // No need to ensure entities.
|
||||
|
||||
foreach (var entity in container.ContainedEntities)
|
||||
{
|
||||
if (!stateEnts.Contains(entity))
|
||||
toRemove.Add(entity);
|
||||
}
|
||||
|
||||
foreach (var entity in toRemove.Span)
|
||||
{
|
||||
Remove(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)),
|
||||
container,
|
||||
force: true,
|
||||
reparent: false
|
||||
);
|
||||
|
||||
DebugTools.Assert(!container.Contains(entity));
|
||||
}
|
||||
|
||||
// Remove entities that were expected, but have been removed from the container.
|
||||
var removedExpected = new ValueList<NetEntity>();
|
||||
foreach (var netEntity in container.ExpectedEntities)
|
||||
{
|
||||
if (!stateNetEnts.Contains(netEntity))
|
||||
removedExpected.Add(netEntity);
|
||||
}
|
||||
|
||||
foreach (var entityUid in removedExpected.Span)
|
||||
{
|
||||
RemoveExpectedEntity(entityUid, out _);
|
||||
}
|
||||
|
||||
// Add new entities.
|
||||
for (var i = 0; i < stateNetEnts.Length; i++)
|
||||
{
|
||||
var entity = stateEnts[i];
|
||||
var netEnt = stateNetEnts[i];
|
||||
if (!entity.IsValid())
|
||||
{
|
||||
DebugTools.Assert(netEnt.IsValid());
|
||||
AddExpectedEntity(netEnt, container);
|
||||
continue;
|
||||
}
|
||||
|
||||
var meta = MetaData(entity);
|
||||
DebugTools.Assert(meta.NetEntity == netEnt);
|
||||
|
||||
// If an entity is currently in the shadow realm, it means we probably left PVS and are now getting
|
||||
// back into range. We do not want to directly insert this entity, as IF the container and entity
|
||||
// transform states did not get sent simultaneously, the entity's transform will be modified by the
|
||||
// insert operation. This means it will then be reset to the shadow realm, causing it to be ejected
|
||||
// from the container. It would then subsequently be parented to the container without ever being
|
||||
// re-inserted, leading to the client seeing what should be hidden entities attached to
|
||||
// containers/players.
|
||||
if ((meta.Flags & MetaDataFlags.Detached) != 0)
|
||||
{
|
||||
AddExpectedEntity(netEnt, container);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (container.Contains(entity))
|
||||
continue;
|
||||
|
||||
RemoveExpectedEntity(netEnt, out _);
|
||||
Insert(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity), null),
|
||||
container,
|
||||
xform,
|
||||
force: true
|
||||
);
|
||||
|
||||
DebugTools.Assert(container.Contains(entity));
|
||||
}
|
||||
DebugTools.Assert(container.Contains(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnParentChanged(ref EntParentChangedMessage message)
|
||||
protected override void OnParentChanged(ref EntParentChangedMessage message)
|
||||
{
|
||||
base.OnParentChanged(ref message);
|
||||
|
||||
var xform = message.Transform;
|
||||
|
||||
if (xform.MapID != MapId.Nullspace)
|
||||
_updateQueue.Add(message.Entity);
|
||||
|
||||
// If an entity warped in from null-space (i.e., re-entered PVS) and got attached to a container, do the same checks as for newly initialized entities.
|
||||
if (message.OldParent != null && message.OldParent.Value.IsValid())
|
||||
return;
|
||||
|
||||
if (!RemoveExpectedEntity(GetNetEntity(message.Entity), out var container))
|
||||
return;
|
||||
|
||||
if (xform.ParentUid != container.Owner)
|
||||
{
|
||||
base.OnParentChanged(ref message);
|
||||
|
||||
var xform = message.Transform;
|
||||
|
||||
if (xform.MapID != MapId.Nullspace)
|
||||
_updateQueue.Add(message.Entity);
|
||||
|
||||
// If an entity warped in from null-space (i.e., re-entered PVS) and got attached to a container, do the same checks as for newly initialized entities.
|
||||
if (message.OldParent != null && message.OldParent.Value.IsValid())
|
||||
return;
|
||||
|
||||
if (!RemoveExpectedEntity(GetNetEntity(message.Entity), out var container))
|
||||
return;
|
||||
|
||||
if (xform.ParentUid != container.Owner)
|
||||
{
|
||||
// This container is expecting an entity... but it got parented to some other entity???
|
||||
// Ah well, the sever should send a new container state that updates expected entities so just ignore it for now.
|
||||
return;
|
||||
}
|
||||
|
||||
Insert(message.Entity, container, force: true);
|
||||
// This container is expecting an entity... but it got parented to some other entity???
|
||||
// Ah well, the sever should send a new container state that updates expected entities so just ignore it for now.
|
||||
return;
|
||||
}
|
||||
|
||||
public void AddExpectedEntity(NetEntity netEntity, BaseContainer container)
|
||||
{
|
||||
Insert(message.Entity, container, force: true);
|
||||
}
|
||||
|
||||
public void AddExpectedEntity(NetEntity netEntity, BaseContainer container)
|
||||
{
|
||||
#if DEBUG
|
||||
var uid = GetEntity(netEntity);
|
||||
var uid = GetEntity(netEntity);
|
||||
|
||||
if (TryComp(uid, out MetaDataComponent? meta))
|
||||
{
|
||||
DebugTools.Assert((meta.Flags & (MetaDataFlags.Detached | MetaDataFlags.InContainer)) == MetaDataFlags.Detached,
|
||||
$"Adding entity {ToPrettyString(uid)} to list of expected entities for container {container.ID} in {ToPrettyString(container.Owner)}, despite it already being in a container.");
|
||||
}
|
||||
if (TryComp(uid, out MetaDataComponent? meta))
|
||||
{
|
||||
DebugTools.Assert((meta.Flags & (MetaDataFlags.Detached | MetaDataFlags.InContainer)) == MetaDataFlags.Detached,
|
||||
$"Adding entity {ToPrettyString(uid)} to list of expected entities for container {container.ID} in {ToPrettyString(container.Owner)}, despite it already being in a container.");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!ExpectedEntities.TryAdd(netEntity, container))
|
||||
if (!ExpectedEntities.TryAdd(netEntity, container))
|
||||
{
|
||||
// It is possible that we were expecting this entity in one container, but it has now moved to another
|
||||
// container, and this entity's state is just being applied before the old container is getting updated.
|
||||
var oldContainer = ExpectedEntities[netEntity];
|
||||
ExpectedEntities[netEntity] = container;
|
||||
DebugTools.Assert(oldContainer.ExpectedEntities.Contains(netEntity),
|
||||
$"Entity {netEntity} is expected, but not expected in the given container? Container: {oldContainer.ID} in {ToPrettyString(oldContainer.Owner)}");
|
||||
oldContainer.ExpectedEntities.Remove(netEntity);
|
||||
}
|
||||
|
||||
DebugTools.Assert(!container.ExpectedEntities.Contains(netEntity),
|
||||
$"Contained entity {netEntity} was not yet expected by the system, but was already expected by the container: {container.ID} in {ToPrettyString(container.Owner)}");
|
||||
container.ExpectedEntities.Add(netEntity);
|
||||
}
|
||||
|
||||
public bool RemoveExpectedEntity(NetEntity netEntity, [NotNullWhen(true)] out BaseContainer? container)
|
||||
{
|
||||
if (!ExpectedEntities.Remove(netEntity, out container))
|
||||
return false;
|
||||
|
||||
DebugTools.Assert(container.ExpectedEntities.Contains(netEntity),
|
||||
$"While removing expected contained entity {ToPrettyString(netEntity)}, the entity was missing from the container expected set. Container: {container.ID} in {ToPrettyString(container.Owner)}");
|
||||
container.ExpectedEntities.Remove(netEntity);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
foreach (var toUpdate in _updateQueue)
|
||||
{
|
||||
if (Deleted(toUpdate))
|
||||
continue;
|
||||
|
||||
UpdateEntityRecursively(toUpdate);
|
||||
}
|
||||
|
||||
_updateQueue.Clear();
|
||||
}
|
||||
|
||||
private void UpdateEntityRecursively(EntityUid entity)
|
||||
{
|
||||
// Recursively go up parents and containers to see whether both sprites and lights need to be occluded
|
||||
// Could maybe optimise this more by checking nearest parent that has sprite / light and whether it's container
|
||||
// occluded but this probably isn't a big perf issue.
|
||||
var xform = TransformQuery.GetComponent(entity);
|
||||
var parent = xform.ParentUid;
|
||||
var child = entity;
|
||||
var spriteOccluded = false;
|
||||
var lightOccluded = false;
|
||||
|
||||
while (parent.IsValid() && (!spriteOccluded || !lightOccluded))
|
||||
{
|
||||
var parentXform = TransformQuery.GetComponent(parent);
|
||||
if (TryComp<ContainerManagerComponent>(parent, out var manager) && TryGetContainingContainer(parent, child, out var container, manager))
|
||||
{
|
||||
// It is possible that we were expecting this entity in one container, but it has now moved to another
|
||||
// container, and this entity's state is just being applied before the old container is getting updated.
|
||||
var oldContainer = ExpectedEntities[netEntity];
|
||||
ExpectedEntities[netEntity] = container;
|
||||
DebugTools.Assert(oldContainer.ExpectedEntities.Contains(netEntity),
|
||||
$"Entity {netEntity} is expected, but not expected in the given container? Container: {oldContainer.ID} in {ToPrettyString(oldContainer.Owner)}");
|
||||
oldContainer.ExpectedEntities.Remove(netEntity);
|
||||
spriteOccluded = spriteOccluded || !container.ShowContents;
|
||||
lightOccluded = lightOccluded || container.OccludesLight;
|
||||
}
|
||||
|
||||
DebugTools.Assert(!container.ExpectedEntities.Contains(netEntity),
|
||||
$"Contained entity {netEntity} was not yet expected by the system, but was already expected by the container: {container.ID} in {ToPrettyString(container.Owner)}");
|
||||
container.ExpectedEntities.Add(netEntity);
|
||||
child = parent;
|
||||
parent = parentXform.ParentUid;
|
||||
}
|
||||
|
||||
public bool RemoveExpectedEntity(NetEntity netEntity, [NotNullWhen(true)] out BaseContainer? container)
|
||||
{
|
||||
if (!ExpectedEntities.Remove(netEntity, out container))
|
||||
return false;
|
||||
// Alright so
|
||||
// This is the CBT bit.
|
||||
// The issue is we need to go through the children and re-check whether they are or are not contained.
|
||||
// if they are contained then the occlusion values may need updating for all those children
|
||||
UpdateEntity(entity, xform, spriteOccluded, lightOccluded);
|
||||
}
|
||||
|
||||
DebugTools.Assert(container.ExpectedEntities.Contains(netEntity),
|
||||
$"While removing expected contained entity {ToPrettyString(netEntity)}, the entity was missing from the container expected set. Container: {container.ID} in {ToPrettyString(container.Owner)}");
|
||||
container.ExpectedEntities.Remove(netEntity);
|
||||
return true;
|
||||
private void UpdateEntity(
|
||||
EntityUid entity,
|
||||
TransformComponent xform,
|
||||
bool spriteOccluded,
|
||||
bool lightOccluded)
|
||||
{
|
||||
if (_spriteQuery.TryGetComponent(entity, out var sprite))
|
||||
{
|
||||
_sprite.SetContainerOccluded((entity, sprite), spriteOccluded);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
if (_pointLightQuery.TryGetComponent(entity, out var light))
|
||||
_lightSys.SetContainerOccluded(entity, lightOccluded, light);
|
||||
|
||||
foreach (var toUpdate in _updateQueue)
|
||||
// Try to avoid TryComp if we already know stuff is occluded.
|
||||
if ((!spriteOccluded || !lightOccluded) && TryComp<ContainerManagerComponent>(entity, out var manager))
|
||||
{
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
if (Deleted(toUpdate))
|
||||
continue;
|
||||
// Thank god it's by value and not by ref.
|
||||
var childSpriteOccluded = spriteOccluded;
|
||||
var childLightOccluded = lightOccluded;
|
||||
|
||||
UpdateEntityRecursively(toUpdate);
|
||||
}
|
||||
|
||||
_updateQueue.Clear();
|
||||
}
|
||||
|
||||
private void UpdateEntityRecursively(EntityUid entity)
|
||||
{
|
||||
// Recursively go up parents and containers to see whether both sprites and lights need to be occluded
|
||||
// Could maybe optimise this more by checking nearest parent that has sprite / light and whether it's container
|
||||
// occluded but this probably isn't a big perf issue.
|
||||
var xform = TransformQuery.GetComponent(entity);
|
||||
var parent = xform.ParentUid;
|
||||
var child = entity;
|
||||
var spriteOccluded = false;
|
||||
var lightOccluded = false;
|
||||
|
||||
while (parent.IsValid() && (!spriteOccluded || !lightOccluded))
|
||||
{
|
||||
var parentXform = TransformQuery.GetComponent(parent);
|
||||
if (TryComp<ContainerManagerComponent>(parent, out var manager) && TryGetContainingContainer(parent, child, out var container, manager))
|
||||
// We already know either sprite or light is not occluding so need to check container.
|
||||
if (TryGetContainingContainer(entity, child, out var container, manager))
|
||||
{
|
||||
spriteOccluded = spriteOccluded || !container.ShowContents;
|
||||
lightOccluded = lightOccluded || container.OccludesLight;
|
||||
childSpriteOccluded = childSpriteOccluded || !container.ShowContents;
|
||||
childLightOccluded = childLightOccluded || container.OccludesLight;
|
||||
}
|
||||
|
||||
child = parent;
|
||||
parent = parentXform.ParentUid;
|
||||
UpdateEntity(child, TransformQuery.GetComponent(child), childSpriteOccluded, childLightOccluded);
|
||||
}
|
||||
|
||||
// Alright so
|
||||
// This is the CBT bit.
|
||||
// The issue is we need to go through the children and re-check whether they are or are not contained.
|
||||
// if they are contained then the occlusion values may need updating for all those children
|
||||
UpdateEntity(entity, xform, spriteOccluded, lightOccluded);
|
||||
}
|
||||
|
||||
private void UpdateEntity(
|
||||
EntityUid entity,
|
||||
TransformComponent xform,
|
||||
bool spriteOccluded,
|
||||
bool lightOccluded)
|
||||
else
|
||||
{
|
||||
if (_spriteQuery.TryGetComponent(entity, out var sprite))
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
sprite.ContainerOccluded = spriteOccluded;
|
||||
}
|
||||
|
||||
if (_pointLightQuery.TryGetComponent(entity, out var light))
|
||||
_lightSys.SetContainerOccluded(entity, lightOccluded, light);
|
||||
|
||||
// Try to avoid TryComp if we already know stuff is occluded.
|
||||
if ((!spriteOccluded || !lightOccluded) && TryComp<ContainerManagerComponent>(entity, out var manager))
|
||||
{
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
// Thank god it's by value and not by ref.
|
||||
var childSpriteOccluded = spriteOccluded;
|
||||
var childLightOccluded = lightOccluded;
|
||||
|
||||
// We already know either sprite or light is not occluding so need to check container.
|
||||
if (TryGetContainingContainer(entity, child, out var container, manager))
|
||||
{
|
||||
childSpriteOccluded = childSpriteOccluded || !container.ShowContents;
|
||||
childLightOccluded = childLightOccluded || container.OccludesLight;
|
||||
}
|
||||
|
||||
UpdateEntity(child, TransformQuery.GetComponent(child), childSpriteOccluded, childLightOccluded);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
UpdateEntity(child, TransformQuery.GetComponent(child), spriteOccluded, lightOccluded);
|
||||
}
|
||||
UpdateEntity(child, TransformQuery.GetComponent(child), spriteOccluded, lightOccluded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user