using System; using System.Collections.Generic; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components.UserInterface; using Robust.Shared.Interfaces.Network; using Robust.Shared.Interfaces.Reflection; using Robust.Shared.IoC; using Robust.Shared.Serialization; using IComponent = Robust.Shared.Interfaces.GameObjects.IComponent; namespace Robust.Client.GameObjects.Components.UserInterface { public class ClientUserInterfaceComponent : SharedUserInterfaceComponent { private readonly Dictionary _openInterfaces = new Dictionary(); private Dictionary _interfaceData; #pragma warning disable 649 [Dependency] private readonly IReflectionManager _reflectionManager; [Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory; #pragma warning restore 649 public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); const string cache = "ui_cache"; if (serializer.TryGetCacheData(cache, out Dictionary interfaceData)) { _interfaceData = interfaceData; return; } var data = serializer.ReadDataFieldCached("interfaces", new List()); interfaceData = new Dictionary(); foreach (var prototypeData in data) { interfaceData[prototypeData.UiKey] = prototypeData; } serializer.SetCacheData(cache, interfaceData); _interfaceData = interfaceData; } public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null) { base.HandleMessage(message, netChannel, component); switch (message) { case BoundInterfaceMessageWrapMessage wrapped: // Double nested switches who needs readability anyways. switch (wrapped.Message) { case OpenBoundInterfaceMessage _: if (_openInterfaces.ContainsKey(wrapped.UiKey)) { return; } OpenInterface(wrapped); break; case CloseBoundInterfaceMessage _: Close(wrapped.UiKey, true); break; default: if (_openInterfaces.TryGetValue(wrapped.UiKey, out var bi)) { bi.InternalReceiveMessage(wrapped.Message); } break; } break; } } private void OpenInterface(BoundInterfaceMessageWrapMessage wrapped) { var data = _interfaceData[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[]{this, wrapped.UiKey}); boundInterface.Open(); _openInterfaces[wrapped.UiKey] = boundInterface; } internal void Close(object uiKey, bool remoteCall) { if (!_openInterfaces.TryGetValue(uiKey, out var boundUserInterface)) { return; } if(!remoteCall) SendMessage(new CloseBoundInterfaceMessage(), uiKey); _openInterfaces.Remove(uiKey); boundUserInterface.Dispose(); } internal void SendMessage(BoundUserInterfaceMessage message, object uiKey) { SendNetworkMessage(new BoundInterfaceMessageWrapMessage(message, uiKey)); } } /// /// An abstract class to override to implement bound user interfaces. /// public abstract class BoundUserInterface : IDisposable { protected ClientUserInterfaceComponent Owner { get; } protected object UiKey { get; } /// /// The last received state object sent from the server. /// protected BoundUserInterfaceState State { get; private set; } protected BoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) { Owner = owner; UiKey = uiKey; } /// /// Invoked when the UI is opened. /// Do all creation and opening of things like windows in here. /// protected internal virtual void Open() { } /// /// Invoked when the server uses SetState. /// protected virtual void UpdateState(BoundUserInterfaceState state) { } /// /// Invoked when the server sends an arbitrary message. /// protected virtual void ReceiveMessage(BoundUserInterfaceMessage message) { } /// /// Invoked to close the UI. /// protected void Close() { Owner.Close(UiKey, false); } /// /// Sends a message to the server-side UI. /// protected void SendMessage(BoundUserInterfaceMessage message) { Owner.SendMessage(message, UiKey); } internal void InternalReceiveMessage(BoundUserInterfaceMessage message) { switch (message) { case UpdateBoundStateMessage updateBoundStateMessage: State = updateBoundStateMessage.State; UpdateState(State); break; default: ReceiveMessage(message); break; } } ~BoundUserInterface() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { } } }