mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Try fix client-side BUI error spam (#5208)
This commit is contained in:
@@ -1,15 +1,10 @@
|
||||
using Robust.Server.GameStates;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Robust.Server.GameObjects;
|
||||
|
||||
public sealed class ServerMetaDataSystem : MetaDataSystem
|
||||
{
|
||||
[Dependency] private readonly PvsSystem _pvsSystem = default!;
|
||||
private EntityQuery<MetaDataComponent> _mQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -18,7 +13,6 @@ public sealed class ServerMetaDataSystem : MetaDataSystem
|
||||
|
||||
EntityManager.ComponentAdded += OnComponentAdded;
|
||||
EntityManager.ComponentRemoved += OnComponentRemoved;
|
||||
_mQuery = GetEntityQuery<MetaDataComponent>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
|
||||
@@ -5,7 +5,7 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[RegisterComponent]
|
||||
public sealed partial class ActiveUserInterfaceComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// Actors that currently have interfaces open.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<Enum, List<EntityUid>> Actors = new();
|
||||
public Dictionary<Enum, HashSet<EntityUid>> Actors = new();
|
||||
|
||||
/// <summary>
|
||||
/// Legacy data, new BUIs should be using comp states.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
@@ -9,17 +7,13 @@ namespace Robust.Shared.GameObjects;
|
||||
/// <summary>
|
||||
/// Stores data about this entity and what BUIs they have open.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
/// <remarks>
|
||||
/// This component is implicitly networked via <see cref="UserInterfaceComponent"/>.
|
||||
/// I.e., the other component is authoritative about what UIs are open
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
public sealed partial class UserInterfaceUserComponent : Component
|
||||
{
|
||||
public override bool SessionSpecific => true;
|
||||
|
||||
[DataField]
|
||||
public Dictionary<EntityUid, List<Enum>> OpenInterfaces = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
internal sealed class UserInterfaceUserComponentState : IComponentState
|
||||
{
|
||||
public Dictionary<NetEntity, List<Enum>> OpenInterfaces = new();
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Reflection;
|
||||
@@ -67,9 +67,6 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
|
||||
|
||||
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentShutdown>(OnActorShutdown);
|
||||
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentGetStateAttemptEvent>(OnGetStateAttempt);
|
||||
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentGetState>(OnActorGetState);
|
||||
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentHandleState>(OnActorHandleState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -133,42 +130,6 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
CloseUserUis((ent.Owner, ent.Comp));
|
||||
}
|
||||
|
||||
private void OnGetStateAttempt(Entity<UserInterfaceUserComponent> ent, ref ComponentGetStateAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled || args.Player?.AttachedEntity != ent.Owner)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnActorGetState(Entity<UserInterfaceUserComponent> ent, ref ComponentGetState args)
|
||||
{
|
||||
var interfaces = new Dictionary<NetEntity, List<Enum>>();
|
||||
|
||||
foreach (var (buid, data) in ent.Comp.OpenInterfaces)
|
||||
{
|
||||
interfaces[GetNetEntity(buid)] = data;
|
||||
}
|
||||
|
||||
args.State = new UserInterfaceUserComponentState()
|
||||
{
|
||||
OpenInterfaces = interfaces,
|
||||
};
|
||||
}
|
||||
|
||||
private void OnActorHandleState(Entity<UserInterfaceUserComponent> ent, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not UserInterfaceUserComponentState state)
|
||||
return;
|
||||
|
||||
// TODO: Allocate less.
|
||||
ent.Comp.OpenInterfaces.Clear();
|
||||
|
||||
foreach (var (nent, data) in state.OpenInterfaces)
|
||||
{
|
||||
var openEnt = EnsureEntity<ActorComponent>(nent, ent.Owner);
|
||||
ent.Comp.OpenInterfaces[openEnt] = data;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void OnPlayerAttached(PlayerAttachedEvent ev)
|
||||
@@ -182,6 +143,9 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
if (!_uiQuery.TryGetComponent(uid, out var uiComp))
|
||||
continue;
|
||||
|
||||
// Player can now receive information about open UIs
|
||||
Dirty(uid, uiComp);
|
||||
|
||||
foreach (var key in keys)
|
||||
{
|
||||
if (!uiComp.Interfaces.TryGetValue(key, out var data))
|
||||
@@ -203,6 +167,9 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
if (!_uiQuery.TryGetComponent(uid, out var uiComp))
|
||||
continue;
|
||||
|
||||
// Player can no longer receive information about open UIs
|
||||
Dirty(uid, uiComp);
|
||||
|
||||
foreach (var key in keys)
|
||||
{
|
||||
if (!uiComp.ClientOpenInterfaces.Remove(key, out var cBui))
|
||||
@@ -215,11 +182,14 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
|
||||
private void OnUserInterfaceClosed(Entity<UserInterfaceComponent> ent, ref CloseBoundInterfaceMessage args)
|
||||
{
|
||||
CloseUi(ent, args.Actor, args.UiKey);
|
||||
CloseUiInternal(ent!, args.UiKey, args.Actor);
|
||||
}
|
||||
|
||||
private void CloseUi(Entity<UserInterfaceComponent> ent, EntityUid actor, Enum key)
|
||||
private void CloseUiInternal(Entity<UserInterfaceComponent?> ent, Enum key, EntityUid actor)
|
||||
{
|
||||
if (!_uiQuery.Resolve(ent.Owner, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
var actors = ent.Comp.Actors[key];
|
||||
actors.Remove(actor);
|
||||
|
||||
@@ -229,16 +199,16 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
Dirty(ent);
|
||||
|
||||
// If the actor is also deleting then don't worry about updating what they have open.
|
||||
if (!TerminatingOrDeleted(actor) && _userQuery.TryComp(actor, out var actorComp))
|
||||
if (!TerminatingOrDeleted(actor)
|
||||
&& _userQuery.TryComp(actor, out var actorComp)
|
||||
&& actorComp.OpenInterfaces.TryGetValue(ent.Owner, out var keys))
|
||||
{
|
||||
if (actorComp.OpenInterfaces.TryGetValue(ent.Owner, out var keys))
|
||||
keys.Remove(key);
|
||||
if (keys.Count == 0)
|
||||
{
|
||||
keys.Remove(key);
|
||||
|
||||
if (keys.Count == 0)
|
||||
actorComp.OpenInterfaces.Remove(ent.Owner);
|
||||
|
||||
Dirty(actor, actorComp);
|
||||
actorComp.OpenInterfaces.Remove(ent.Owner);
|
||||
if (actorComp.OpenInterfaces.Count == 0)
|
||||
RemCompDeferred<UserInterfaceUserComponent>(actor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,23 +227,29 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
|
||||
private void OnUserInterfaceOpen(Entity<UserInterfaceComponent> ent, ref OpenBoundInterfaceMessage args)
|
||||
{
|
||||
OpenUiInternal(ent!, args.UiKey, args.Actor);
|
||||
}
|
||||
|
||||
private void OpenUiInternal(Entity<UserInterfaceComponent?> ent, Enum key, EntityUid actor)
|
||||
{
|
||||
if (!_uiQuery.Resolve(ent.Owner, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
// Similar to the close method this handles actually opening a UI, it just gets relayed here
|
||||
EnsureComp<ActiveUserInterfaceComponent>(ent.Owner);
|
||||
|
||||
var actor = args.Actor;
|
||||
var actorComp = EnsureComp<UserInterfaceUserComponent>(actor);
|
||||
|
||||
// Let state handling open the UI clientside.
|
||||
actorComp.OpenInterfaces.GetOrNew(ent.Owner).Add(args.UiKey);
|
||||
ent.Comp.Actors.GetOrNew(args.UiKey).Add(actor);
|
||||
actorComp.OpenInterfaces.GetOrNew(ent.Owner).Add(key);
|
||||
ent.Comp.Actors.GetOrNew(key).Add(actor);
|
||||
Dirty(ent);
|
||||
Dirty(actor, actorComp);
|
||||
|
||||
var ev = new BoundUIOpenedEvent(args.UiKey, ent.Owner, args.Actor);
|
||||
var ev = new BoundUIOpenedEvent(key, ent.Owner, actor);
|
||||
RaiseLocalEvent(ent.Owner, ev);
|
||||
|
||||
// If we're client we want this handled immediately.
|
||||
EnsureClientBui(ent, args.UiKey, ent.Comp.Interfaces[args.UiKey]);
|
||||
EnsureClientBui(ent!, key, ent.Comp.Interfaces[key]);
|
||||
}
|
||||
|
||||
private void OnUserInterfaceStartup(Entity<UserInterfaceComponent> ent, ref ComponentStartup args)
|
||||
@@ -299,7 +275,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
actors.AddRange(acts);
|
||||
foreach (var actor in actors)
|
||||
{
|
||||
CloseUi(ent, actor, key);
|
||||
CloseUiInternal(ent!, key, actor);
|
||||
DebugTools.Assert(!acts.Contains(actor));
|
||||
}
|
||||
|
||||
@@ -311,20 +287,29 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
|
||||
private void OnUserInterfaceGetState(Entity<UserInterfaceComponent> ent, ref ComponentGetState args)
|
||||
{
|
||||
// TODO delta states.
|
||||
// I.e., don't resend the whole BUI state just because a new user opened it.
|
||||
|
||||
var actors = new Dictionary<Enum, List<NetEntity>>();
|
||||
var states = new Dictionary<Enum, BoundUserInterfaceState>();
|
||||
args.State = new UserInterfaceComponent.UserInterfaceComponentState(actors, ent.Comp.States);
|
||||
|
||||
foreach (var (key, acts) in ent.Comp.Actors)
|
||||
// Ensure that only the player that currently has the UI open gets to know what they have it open.
|
||||
if (args.ReplayState)
|
||||
{
|
||||
actors[key] = GetNetEntityList(acts);
|
||||
foreach (var (key, acts) in ent.Comp.Actors)
|
||||
{
|
||||
actors[key] = GetNetEntityList(acts);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (key, state) in ent.Comp.States)
|
||||
else if (args.Player.AttachedEntity is { } player)
|
||||
{
|
||||
states[key] = state;
|
||||
var netPlayer = new List<NetEntity> { GetNetEntity(player) };
|
||||
foreach (var (key, acts) in ent.Comp.Actors)
|
||||
{
|
||||
if (acts.Contains(player))
|
||||
actors[key] = netPlayer;
|
||||
}
|
||||
}
|
||||
|
||||
args.State = new UserInterfaceComponent.UserInterfaceComponentState(actors, states);
|
||||
}
|
||||
|
||||
private void OnUserInterfaceHandleState(Entity<UserInterfaceComponent> ent, ref ComponentHandleState args)
|
||||
@@ -332,48 +317,50 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
if (args.Current is not UserInterfaceComponent.UserInterfaceComponentState state)
|
||||
return;
|
||||
|
||||
var toRemove = new ValueList<Enum>();
|
||||
|
||||
foreach (var (key, actors) in state.Actors)
|
||||
{
|
||||
ref var existing = ref CollectionsMarshal.GetValueRefOrAddDefault(ent.Comp.Actors, key, out _);
|
||||
|
||||
existing ??= new List<EntityUid>();
|
||||
|
||||
existing.Clear();
|
||||
existing.AddRange(EnsureEntityList<UserInterfaceComponent>(actors, ent.Owner));
|
||||
}
|
||||
|
||||
foreach (var key in ent.Comp.Actors.Keys)
|
||||
{
|
||||
if (state.Actors.ContainsKey(key))
|
||||
continue;
|
||||
|
||||
toRemove.Add(key);
|
||||
if (!state.Actors.ContainsKey(key))
|
||||
CloseUi(ent!, key);
|
||||
}
|
||||
|
||||
foreach (var key in toRemove)
|
||||
var toRemoveActors = new ValueList<EntityUid>();
|
||||
var newSet = new HashSet<EntityUid>();
|
||||
foreach (var (key, stateActors) in state.Actors)
|
||||
{
|
||||
ent.Comp.Actors.Remove(key);
|
||||
var actors = ent.Comp.Actors.GetOrNew(key);
|
||||
|
||||
newSet.Clear();
|
||||
foreach (var netEntity in stateActors)
|
||||
{
|
||||
var uid = EnsureEntity<UserInterfaceComponent>(netEntity, ent.Owner);
|
||||
if (uid.IsValid())
|
||||
newSet.Add(uid);
|
||||
}
|
||||
|
||||
foreach (var actor in newSet)
|
||||
{
|
||||
if (!actors.Contains(actor))
|
||||
OpenUiInternal(ent!, key, actor);
|
||||
}
|
||||
|
||||
foreach (var actor in actors)
|
||||
{
|
||||
if (!newSet.Contains(actor))
|
||||
toRemoveActors.Add(actor);
|
||||
}
|
||||
|
||||
foreach (var actor in toRemoveActors)
|
||||
{
|
||||
CloseUiInternal(ent!, key, actor);
|
||||
}
|
||||
}
|
||||
|
||||
toRemove.Clear();
|
||||
|
||||
// State handling
|
||||
foreach (var key in ent.Comp.States.Keys)
|
||||
{
|
||||
if (state.States.ContainsKey(key))
|
||||
continue;
|
||||
|
||||
toRemove.Add(key);
|
||||
if (!state.States.ContainsKey(key))
|
||||
ent.Comp.States.Remove(key);
|
||||
}
|
||||
|
||||
foreach (var key in toRemove)
|
||||
{
|
||||
ent.Comp.States.Remove(key);
|
||||
}
|
||||
|
||||
toRemove.Clear();
|
||||
var attachedEnt = _player.LocalEntity;
|
||||
|
||||
// Check if the UI is open by us, otherwise dispose of it.
|
||||
@@ -386,11 +373,6 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
}
|
||||
|
||||
bui.Dispose();
|
||||
toRemove.Add(key);
|
||||
}
|
||||
|
||||
foreach (var key in toRemove)
|
||||
{
|
||||
ent.Comp.ClientOpenInterfaces.Remove(key);
|
||||
}
|
||||
|
||||
@@ -519,16 +501,14 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false))
|
||||
return;
|
||||
|
||||
if (!entity.Comp.Actors.TryGetValue(key, out var actors))
|
||||
if (!entity.Comp.Actors.TryGetValue(key, out var actorSet))
|
||||
return;
|
||||
|
||||
for (var i = actors.Count - 1; i >= 0; i--)
|
||||
var actors = actorSet.ToArray();
|
||||
foreach (var actor in actors)
|
||||
{
|
||||
var actor = actors[i];
|
||||
CloseUi(entity, key, actor);
|
||||
CloseUiInternal(entity, key, actor);
|
||||
}
|
||||
|
||||
DebugTools.Assert(actors.Count == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -563,19 +543,16 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// Rely upon the client telling us.
|
||||
if (predicted)
|
||||
if (!predicted)
|
||||
{
|
||||
if (_timing.IsFirstTimePredicted)
|
||||
{
|
||||
// Not guaranteed to open so rely upon the event handling it.
|
||||
// Also lets client request it to be opened remotely too.
|
||||
EntityManager.RaisePredictiveEvent(new BoundUIWrapMessage(GetNetEntity(entity.Owner), new CloseBoundInterfaceMessage(), key));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OnMessageReceived(new BoundUIWrapMessage(GetNetEntity(entity.Owner), new CloseBoundInterfaceMessage(), key), actor.Value);
|
||||
CloseUiInternal(entity, key, actor.Value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
EntityManager.RaisePredictiveEvent(new BoundUIWrapMessage(GetNetEntity(entity.Owner), new CloseBoundInterfaceMessage(), key));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -800,17 +777,15 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
if (actor.Comp.OpenInterfaces.Count == 0)
|
||||
return;
|
||||
|
||||
var copied = new Dictionary<EntityUid, List<Enum>>(actor.Comp.OpenInterfaces);
|
||||
var enumCopy = new ValueList<Enum>();
|
||||
|
||||
foreach (var (uid, enums) in copied)
|
||||
foreach (var (uid, enums) in actor.Comp.OpenInterfaces)
|
||||
{
|
||||
enumCopy.Clear();
|
||||
enumCopy.AddRange(enums);
|
||||
|
||||
foreach (var key in enumCopy)
|
||||
{
|
||||
CloseUi(uid, key, actor.Owner);
|
||||
CloseUiInternal(uid, key, actor.Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -823,9 +798,16 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
if (!_uiQuery.Resolve(entity.Owner, ref entity.Comp, false))
|
||||
return;
|
||||
|
||||
entity.Comp.Actors.Clear();
|
||||
entity.Comp.States.Clear();
|
||||
Dirty(entity);
|
||||
var toClose = new ValueList<EntityUid>();
|
||||
foreach (var (key, actors) in entity.Comp.Actors)
|
||||
{
|
||||
toClose.Clear();
|
||||
toClose.AddRange(actors);
|
||||
foreach (var actor in toClose)
|
||||
{
|
||||
CloseUiInternal(entity, key, actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -838,7 +820,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
|
||||
foreach (var key in entity.Comp.Interfaces.Keys)
|
||||
{
|
||||
CloseUi(entity, key, actor);
|
||||
CloseUiInternal(entity, key, actor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -36,6 +37,7 @@ namespace Robust.Shared.GameStates
|
||||
/// If true, this state is intended for replays or some other server spectator entity, not for specific
|
||||
/// clients.
|
||||
/// </summary>
|
||||
[MemberNotNullWhen(false, nameof(Player))]
|
||||
public bool ReplayState => Player == null;
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user