Ecs's UserInterfaceComponent. (#4079)

This commit is contained in:
TemporalOroboros
2023-07-08 09:02:28 -07:00
committed by GitHub
parent bde8c2c6e8
commit 75f0cf9dd7
4 changed files with 132 additions and 200 deletions

View File

@@ -1,104 +1,19 @@
using System;
using System.Collections.Generic;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[RegisterComponent, ComponentReference(typeof(SharedUserInterfaceComponent))]
public sealed class ClientUserInterfaceComponent : SharedUserInterfaceComponent, ISerializationHooks
public sealed class ClientUserInterfaceComponent : SharedUserInterfaceComponent
{
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _netMan = default!;
internal readonly Dictionary<Enum, BoundUserInterface> _openInterfaces =
new();
[ViewVariables]
internal readonly Dictionary<Enum, PrototypeData> _interfaces = new();
[ViewVariables]
public IEnumerable<BoundUserInterface> Interfaces => _openInterfaces.Values;
void ISerializationHooks.AfterDeserialization()
{
_interfaces.Clear();
foreach (var data in _interfaceData)
{
_interfaces[data.UiKey] = data;
}
}
internal void MessageReceived(BoundUIWrapMessage msg)
{
switch (msg.Message)
{
case OpenBoundInterfaceMessage _:
if (_openInterfaces.ContainsKey(msg.UiKey))
{
return;
}
OpenInterface(msg);
break;
case CloseBoundInterfaceMessage _:
Close(msg.UiKey, true);
break;
default:
if (_openInterfaces.TryGetValue(msg.UiKey, out var bi))
{
bi.InternalReceiveMessage(msg.Message);
}
break;
}
}
private void OpenInterface(BoundUIWrapMessage wrapped)
{
var data = _interfaces[wrapped.UiKey];
// TODO: This type should be cached, but I'm too lazy.
var type = _reflectionManager.LooseGetType(data.ClientType);
var boundInterface =
(BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new object[] {this, wrapped.UiKey});
boundInterface.Open();
_openInterfaces[wrapped.UiKey] = boundInterface;
var playerSession = _playerManager.LocalPlayer?.Session;
if(playerSession != null)
_entityManager.EventBus.RaiseLocalEvent(Owner, new BoundUIOpenedEvent(wrapped.UiKey, Owner, playerSession), true);
}
internal void Close(Enum uiKey, bool remoteCall)
{
if (!_openInterfaces.TryGetValue(uiKey, out var boundUserInterface))
{
return;
}
if (!remoteCall)
SendMessage(new CloseBoundInterfaceMessage(), uiKey);
_openInterfaces.Remove(uiKey);
boundUserInterface.Dispose();
var playerSession = _playerManager.LocalPlayer?.Session;
if(playerSession != null)
_entityManager.EventBus.RaiseLocalEvent(Owner, new BoundUIClosedEvent(uiKey, Owner, playerSession), true);
}
internal void SendMessage(BoundUserInterfaceMessage message, Enum uiKey)
{
_netMan.SendSystemNetworkMessage(new BoundUIWrapMessage(Owner, message, uiKey));
}
public readonly Dictionary<Enum, BoundUserInterface> OpenInterfaces = new();
}
/// <summary>
@@ -106,17 +21,22 @@ namespace Robust.Client.GameObjects
/// </summary>
public abstract class BoundUserInterface : IDisposable
{
protected ClientUserInterfaceComponent Owner { get; }
[Dependency] protected readonly IEntityManager EntMan = default!;
protected readonly UserInterfaceSystem UiSystem = default!;
public readonly Enum UiKey;
public EntityUid Owner { get; }
/// <summary>
/// The last received state object sent from the server.
/// </summary>
protected BoundUserInterfaceState? State { get; private set; }
protected BoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey)
protected BoundUserInterface(EntityUid owner, Enum uiKey)
{
IoCManager.InjectDependencies(this);
UiSystem = EntMan.System<UserInterfaceSystem>();
Owner = owner;
UiKey = uiKey;
}
@@ -148,7 +68,7 @@ namespace Robust.Client.GameObjects
/// </summary>
public void Close()
{
Owner.Close(UiKey, false);
UiSystem.TryCloseUi(Owner, UiKey);
}
/// <summary>
@@ -156,7 +76,7 @@ namespace Robust.Client.GameObjects
/// </summary>
public void SendMessage(BoundUserInterfaceMessage message)
{
Owner.SendMessage(message, UiKey);
UiSystem.SendUiMessage(this, message);
}
internal void InternalReceiveMessage(BoundUserInterfaceMessage message)

View File

@@ -2,25 +2,40 @@ using JetBrains.Annotations;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using System;
namespace Robust.Client.GameObjects
{
[UsedImplicitly]
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
{
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<BoundUIWrapMessage>(MessageReceived);
SubscribeLocalEvent<ClientUserInterfaceComponent, ComponentInit>(OnUserInterfaceInit);
SubscribeLocalEvent<ClientUserInterfaceComponent, ComponentShutdown>(OnUserInterfaceShutdown);
}
private void OnUserInterfaceInit(EntityUid uid, ClientUserInterfaceComponent component, ComponentInit args)
{
component._interfaces.Clear();
foreach (var data in component._interfaceData)
{
component._interfaces[data.UiKey] = data;
}
}
private void OnUserInterfaceShutdown(EntityUid uid, ClientUserInterfaceComponent component, ComponentShutdown args)
{
foreach (var bui in component.Interfaces)
foreach (var bui in component.OpenInterfaces.Values)
{
bui.Dispose();
}
@@ -29,25 +44,88 @@ namespace Robust.Client.GameObjects
private void MessageReceived(BoundUIWrapMessage ev)
{
var uid = ev.Entity;
if (!EntityManager.TryGetComponent<ClientUserInterfaceComponent>(uid, out var cmp))
if (!TryComp<ClientUserInterfaceComponent>(uid, out var cmp))
return;
var uiKey = ev.UiKey;
var message = ev.Message;
// This should probably not happen at this point, but better make extra sure!
if(_playerManager.LocalPlayer != null)
message.Session = _playerManager.LocalPlayer.Session;
message.Entity = uid;
message.UiKey = ev.UiKey;
message.UiKey = uiKey;
// Raise as object so the correct type is used.
RaiseLocalEvent(uid, (object)message, true);
cmp.MessageReceived(ev);
switch (message)
{
case OpenBoundInterfaceMessage _:
TryOpenUi(uid, uiKey, cmp);
break;
case CloseBoundInterfaceMessage _:
TryCloseUi(uid, uiKey, remoteCall: true, uiComp: cmp);
break;
default:
if (cmp.OpenInterfaces.TryGetValue(uiKey, out var bui))
bui.InternalReceiveMessage(message);
break;
}
}
internal void Send(BoundUIWrapMessage msg)
private bool TryOpenUi(EntityUid uid, Enum uiKey, ClientUserInterfaceComponent? uiComp = null)
{
RaiseNetworkEvent(msg);
if (!Resolve(uid, ref uiComp))
return false;
if (uiComp.OpenInterfaces.ContainsKey(uiKey))
return false;
var data = uiComp._interfaces[uiKey];
// TODO: This type should be cached, but I'm too lazy.
var type = _reflectionManager.LooseGetType(data.ClientType);
var boundInterface =
(BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new object[] {uid, uiKey});
boundInterface.Open();
uiComp.OpenInterfaces[uiKey] = boundInterface;
var playerSession = _playerManager.LocalPlayer?.Session;
if(playerSession != null)
RaiseLocalEvent(uid, new BoundUIOpenedEvent(uiKey, uid, playerSession), true);
return true;
}
internal bool TryCloseUi(EntityUid uid, Enum uiKey, bool remoteCall = false, ClientUserInterfaceComponent? uiComp = null)
{
if (!Resolve(uid, ref uiComp))
return false;
if (!uiComp.OpenInterfaces.TryGetValue(uiKey, out var boundUserInterface))
return false;
if (!remoteCall)
SendUiMessage(boundUserInterface, new CloseBoundInterfaceMessage());
uiComp.OpenInterfaces.Remove(uiKey);
boundUserInterface.Dispose();
var playerSession = _playerManager.LocalPlayer?.Session;
if(playerSession != null)
RaiseLocalEvent(uid, new BoundUIClosedEvent(uiKey, uid, playerSession), true);
return true;
}
internal void SendUiMessage(BoundUserInterface bui, BoundUserInterfaceMessage msg)
{
RaiseNetworkEvent(new BoundUIWrapMessage(bui.Owner, msg, bui.UiKey));
}
}
}

View File

@@ -3,8 +3,7 @@ using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using static Robust.Shared.GameObjects.SharedUserInterfaceComponent;
namespace Robust.Server.GameObjects
@@ -16,22 +15,10 @@ namespace Robust.Server.GameObjects
/// <seealso cref="BoundUserInterface"/>
[PublicAPI]
[RegisterComponent, ComponentReference(typeof(SharedUserInterfaceComponent))]
public sealed class ServerUserInterfaceComponent : SharedUserInterfaceComponent, ISerializationHooks
public sealed class ServerUserInterfaceComponent : SharedUserInterfaceComponent
{
internal readonly Dictionary<Enum, BoundUserInterface> _interfaces =
new();
public IReadOnlyDictionary<Enum, BoundUserInterface> Interfaces => _interfaces;
void ISerializationHooks.AfterDeserialization()
{
_interfaces.Clear();
foreach (var prototypeData in _interfaceData)
{
_interfaces[prototypeData.UiKey] = new BoundUserInterface(prototypeData, this);
}
}
[ViewVariables]
public readonly Dictionary<Enum, BoundUserInterface> Interfaces = new();
}
[RegisterComponent]
@@ -49,8 +36,7 @@ namespace Robust.Server.GameObjects
public float InteractionRangeSqrd;
public Enum UiKey { get; }
public ServerUserInterfaceComponent Component { get; }
public EntityUid Owner => Component.Owner;
public EntityUid Owner { get; }
internal readonly HashSet<IPlayerSession> _subscribedSessions = new();
internal BoundUIWrapMessage? LastStateMsg;
@@ -66,72 +52,15 @@ namespace Robust.Server.GameObjects
/// </summary>
public IReadOnlySet<IPlayerSession> SubscribedSessions => _subscribedSessions;
[Obsolete("Use system events")]
public event Action<ServerBoundUserInterfaceMessage>? OnReceiveMessage;
public BoundUserInterface(PrototypeData data, ServerUserInterfaceComponent owner)
public BoundUserInterface(PrototypeData data, EntityUid owner)
{
RequireInputValidation = data.RequireInputValidation;
UiKey = data.UiKey;
Component = owner;
Owner = owner;
// One Abs(), because negative values imply no limit
InteractionRangeSqrd = data.InteractionRange * MathF.Abs(data.InteractionRange);
}
[Obsolete("Use UserInterfaceSystem")]
public void SetState(BoundUserInterfaceState state, IPlayerSession? session = null, bool clearOverrides = true)
{
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<UserInterfaceSystem>().SetUiState(this, state, session, clearOverrides);
}
[Obsolete("Use UserInterfaceSystem")]
public void Toggle(IPlayerSession session)
{
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<UserInterfaceSystem>().ToggleUi(this, session);
}
[Obsolete("Use UserInterfaceSystem")]
public bool Open(IPlayerSession session)
{
return IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<UserInterfaceSystem>().OpenUi(this, session);
}
[Obsolete("Use UserInterfaceSystem")]
public bool Close(IPlayerSession session)
{
return IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<UserInterfaceSystem>().CloseUi(this, session);
}
[Obsolete("Use UserInterfaceSystem")]
public void CloseAll()
{
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<UserInterfaceSystem>().CloseAll(this);
}
[Obsolete("Just check SubscribedSessions.Contains")]
public bool SessionHasOpen(IPlayerSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
return _subscribedSessions.Contains(session);
}
[Obsolete("Use UserInterfaceSystem")]
public void SendMessage(BoundUserInterfaceMessage message)
{
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<UserInterfaceSystem>().SendUiMessage(this, message);
}
[Obsolete("Use UserInterfaceSystem")]
public void SendMessage(BoundUserInterfaceMessage message, IPlayerSession session)
{
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<UserInterfaceSystem>().TrySendUiMessage(this, message, session);
}
internal void InvokeOnReceiveMessage(ServerBoundUserInterfaceMessage message)
{
OnReceiveMessage?.Invoke(message);
}
}
[PublicAPI]

View File

@@ -14,13 +14,12 @@ namespace Robust.Server.GameObjects
[UsedImplicitly]
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
{
[Dependency] private readonly IPlayerManager _playerMan = default!;
[Dependency] private readonly TransformSystem _xformSys = default!;
private readonly List<IPlayerSession> _sessionCache = new();
private Dictionary<IPlayerSession, List<BoundUserInterface>> _openInterfaces = new();
[Dependency] private readonly IPlayerManager _playerMan = default!;
private readonly Dictionary<IPlayerSession, List<BoundUserInterface>> _openInterfaces = new();
/// <inheritdoc />
public override void Initialize()
@@ -28,6 +27,7 @@ namespace Robust.Server.GameObjects
base.Initialize();
SubscribeNetworkEvent<BoundUIWrapMessage>(OnMessageReceived);
SubscribeLocalEvent<ServerUserInterfaceComponent, ComponentInit>(OnUserInterfaceInit);
SubscribeLocalEvent<ServerUserInterfaceComponent, ComponentShutdown>(OnUserInterfaceShutdown);
_playerMan.PlayerStatusChanged += OnPlayerStatusChanged;
}
@@ -53,6 +53,16 @@ namespace Robust.Server.GameObjects
}
}
private void OnUserInterfaceInit(EntityUid uid, ServerUserInterfaceComponent component, ComponentInit args)
{
component.Interfaces.Clear();
foreach (var prototypeData in component._interfaceData)
{
component.Interfaces[prototypeData.UiKey] = new BoundUserInterface(prototypeData, uid);
}
}
private void OnUserInterfaceShutdown(EntityUid uid, ServerUserInterfaceComponent component, ComponentShutdown args)
{
if (!TryComp(uid, out ActiveUserInterfaceComponent? activeUis))
@@ -73,15 +83,15 @@ namespace Robust.Server.GameObjects
if (!TryComp(uid, out ServerUserInterfaceComponent? uiComp) || args.SenderSession is not IPlayerSession session)
return;
if (!uiComp._interfaces.TryGetValue(msg.UiKey, out var ui))
if (!uiComp.Interfaces.TryGetValue(msg.UiKey, out var ui))
{
Log.Debug("Got BoundInterfaceMessageWrapMessage for unknown UI key: {0}", msg.UiKey);
Log.Debug($"Got BoundInterfaceMessageWrapMessage for unknown UI key: {msg.UiKey}");
return;
}
if (!ui.SubscribedSessions.Contains(session))
{
Log.Debug($"UI {msg.UiKey} got BoundInterfaceMessageWrapMessage from a client who was not subscribed: {session}", msg.UiKey);
Log.Debug($"UI {msg.UiKey} got BoundInterfaceMessageWrapMessage from a client who was not subscribed: {session}");
return;
}
@@ -109,11 +119,6 @@ namespace Robust.Server.GameObjects
// Raise as object so the correct type is used.
RaiseLocalEvent(uid, (object)message, true);
// Once we have populated our message's wrapped message, we will wrap it up into a message that can be sent
// to old component-code.
var WrappedUnwrappedMessageMessageMessage = new ServerBoundUserInterfaceMessage(message, session);
ui.InvokeOnReceiveMessage(WrappedUnwrappedMessageMessageMessage);
}
/// <inheritdoc />
@@ -194,7 +199,7 @@ namespace Robust.Server.GameObjects
private void ActivateInterface(BoundUserInterface ui)
{
EnsureComp<ActiveUserInterfaceComponent>(ui.Component.Owner).Interfaces.Add(ui);
EnsureComp<ActiveUserInterfaceComponent>(ui.Owner).Interfaces.Add(ui);
}
#region Get BUI
@@ -203,7 +208,7 @@ namespace Robust.Server.GameObjects
if (!Resolve(uid, ref ui))
return false;
return ui._interfaces.ContainsKey(uiKey);
return ui.Interfaces.ContainsKey(uiKey);
}
public BoundUserInterface GetUi(EntityUid uid, Enum uiKey, ServerUserInterfaceComponent? ui = null)
@@ -211,7 +216,7 @@ namespace Robust.Server.GameObjects
if (!Resolve(uid, ref ui))
throw new InvalidOperationException($"Cannot get {typeof(BoundUserInterface)} from an entity without {typeof(ServerUserInterfaceComponent)}!");
return ui._interfaces[uiKey];
return ui.Interfaces[uiKey];
}
public BoundUserInterface? GetUiOrNull(EntityUid uid, Enum uiKey, ServerUserInterfaceComponent? ui = null)
@@ -224,7 +229,7 @@ namespace Robust.Server.GameObjects
{
bui = null;
return Resolve(uid, ref ui, false) && ui._interfaces.TryGetValue(uiKey, out bui);
return Resolve(uid, ref ui, false) && ui.Interfaces.TryGetValue(uiKey, out bui);
}
/// <summary>
@@ -294,9 +299,9 @@ namespace Robust.Server.GameObjects
/// The player session to send this new state to.
/// Set to null for sending it to every subscribed player session.
/// </param>
public void SetUiState(BoundUserInterface bui, BoundUserInterfaceState state, IPlayerSession? session = null, bool clearOverrides = true)
public static void SetUiState(BoundUserInterface bui, BoundUserInterfaceState state, IPlayerSession? session = null, bool clearOverrides = true)
{
var msg = new BoundUIWrapMessage(bui.Component.Owner, new UpdateBoundStateMessage(state), bui.UiKey);
var msg = new BoundUIWrapMessage(bui.Owner, new UpdateBoundStateMessage(state), bui.UiKey);
if (session == null)
{
bui.LastStateMsg = msg;
@@ -356,9 +361,9 @@ namespace Robust.Server.GameObjects
return false;
_openInterfaces.GetOrNew(session).Add(bui);
RaiseLocalEvent(bui.Component.Owner, new BoundUIOpenedEvent(bui.UiKey, bui.Component.Owner, session));
RaiseLocalEvent(bui.Owner, new BoundUIOpenedEvent(bui.UiKey, bui.Owner, session));
RaiseNetworkEvent(new BoundUIWrapMessage(bui.Component.Owner, new OpenBoundInterfaceMessage(), bui.UiKey), session.ConnectedClient);
RaiseNetworkEvent(new BoundUIWrapMessage(bui.Owner, new OpenBoundInterfaceMessage(), bui.UiKey), session.ConnectedClient);
// Fun fact, clients needs to have BUIs open before they can receive the state.....
if (bui.LastStateMsg != null)
@@ -387,14 +392,14 @@ namespace Robust.Server.GameObjects
if (!bui._subscribedSessions.Remove(session))
return false;
RaiseNetworkEvent(new BoundUIWrapMessage(bui.Component.Owner, new CloseBoundInterfaceMessage(), bui.UiKey), session.ConnectedClient);
RaiseNetworkEvent(new BoundUIWrapMessage(bui.Owner, new CloseBoundInterfaceMessage(), bui.UiKey), session.ConnectedClient);
CloseShared(bui, session, activeUis);
return true;
}
private void CloseShared(BoundUserInterface bui, IPlayerSession session, ActiveUserInterfaceComponent? activeUis = null)
{
var owner = bui.Component.Owner;
var owner = bui.Owner;
bui._subscribedSessions.Remove(session);
bui.PlayerStateOverrides.Remove(session);
@@ -404,7 +409,7 @@ namespace Robust.Server.GameObjects
RaiseLocalEvent(owner, new BoundUIClosedEvent(bui.UiKey, owner, session));
if (bui._subscribedSessions.Count == 0)
DeactivateInterface(bui.Component.Owner, bui, activeUis);
DeactivateInterface(bui.Owner, bui, activeUis);
}
/// <summary>
@@ -465,7 +470,7 @@ namespace Robust.Server.GameObjects
/// </summary>
public void SendUiMessage(BoundUserInterface bui, BoundUserInterfaceMessage message)
{
var msg = new BoundUIWrapMessage(bui.Component.Owner, message, bui.UiKey);
var msg = new BoundUIWrapMessage(bui.Owner, message, bui.UiKey);
foreach (var session in bui.SubscribedSessions)
{
RaiseNetworkEvent(msg, session.ConnectedClient);
@@ -491,7 +496,7 @@ namespace Robust.Server.GameObjects
if (!bui.SubscribedSessions.Contains(session))
return false;
RaiseNetworkEvent(new BoundUIWrapMessage(bui.Component.Owner, message, bui.UiKey), session.ConnectedClient);
RaiseNetworkEvent(new BoundUIWrapMessage(bui.Owner, message, bui.UiKey), session.ConnectedClient);
return true;
}