mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
ViewVariables Exorcism Part 1: Of paths and commands (#3214)
This commit is contained in:
committed by
GitHub
parent
80e390a74b
commit
d404494018
@@ -31,7 +31,9 @@ Template for new versions:
|
||||
|
||||
### Breaking changes
|
||||
|
||||
*None yet*
|
||||
* Removed `SI`, `SIoC`, `I`, `IoC`, `SE` and `CE` VV command prefixes.
|
||||
* `SI`, `SIoC`, `I` and `IoC` are replaced by VV paths under `/ioc/` and `/c/ioc/`.
|
||||
* `SE` and `CE` are replaced by VV paths under `/system/` and `/c/system`.
|
||||
|
||||
### New features
|
||||
|
||||
@@ -40,6 +42,14 @@ Template for new versions:
|
||||
* `net.mtu_expand`
|
||||
* `net.mtu_expand_frequency`
|
||||
* `net.mtu_expand_fail_attempts`
|
||||
* Added a whole load of features to ViewVariables.
|
||||
* Added VV Paths, which allow you to refer to an object by a path, e.g. `/entity/1234/Transform/WorldPosition`
|
||||
* Added VV Domains, which allow you to add "handlers" for the top-most VV Path segment, e.g. `/entity` is a domain and so is `/player`...
|
||||
* Added VV Type Handlers, which allow you to add "custom paths" under specific types, even dynamically!
|
||||
* Added VV Path networking, which allows you to read/write/invoke paths remotely, both from server to client and from client to server.
|
||||
* Added `vvread`, `vvwrite` and `vvinvoke` commands, which allow you to read, write and invoke VV paths.
|
||||
* Added autocompletion to all VV commands.
|
||||
* Please note that the VV GUI still remains the same. It will be updated to use these new features in the future.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client
|
||||
{
|
||||
@@ -113,8 +114,9 @@ namespace Robust.Client
|
||||
IoCManager.Register<IPlacementManager, PlacementManager>();
|
||||
IoCManager.Register<IOverlayManager, OverlayManager>();
|
||||
IoCManager.Register<IOverlayManagerInternal, OverlayManager>();
|
||||
IoCManager.Register<IViewVariablesManager, ViewVariablesManager>();
|
||||
IoCManager.Register<IViewVariablesManagerInternal, ViewVariablesManager>();
|
||||
IoCManager.Register<IViewVariablesManager, ClientViewVariablesManager>();
|
||||
IoCManager.Register<IClientViewVariablesManager, ClientViewVariablesManager>();
|
||||
IoCManager.Register<IClientViewVariablesManagerInternal, ClientViewVariablesManager>();
|
||||
IoCManager.Register<IClientConGroupController, ClientConGroupController>();
|
||||
IoCManager.Register<IScriptClient, ScriptClient>();
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ namespace Robust.Client.Console
|
||||
{
|
||||
private readonly ScriptConsoleClient _owner;
|
||||
|
||||
[field: Dependency] public override IViewVariablesManager vvm { get; } = default!;
|
||||
[field: Dependency] public override IClientViewVariablesManager vvm { get; } = default!;
|
||||
|
||||
public ScriptGlobalsImpl(ScriptConsoleClient owner)
|
||||
{
|
||||
@@ -243,7 +243,7 @@ namespace Robust.Client.Console
|
||||
[PublicAPI]
|
||||
public abstract class ScriptGlobals : ScriptGlobalsShared
|
||||
{
|
||||
public abstract IViewVariablesManager vvm { get; }
|
||||
public abstract IClientViewVariablesManager vvm { get; }
|
||||
|
||||
public abstract void vv(object a);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IOverlayManagerInternal _overlayManager = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
[Dependency] private readonly IViewVariablesManagerInternal _viewVariablesManager = default!;
|
||||
[Dependency] private readonly IClientViewVariablesManagerInternal _viewVariablesManager = default!;
|
||||
[Dependency] private readonly IDiscordRichPresence _discord = default!;
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
[Dependency] private readonly IClydeAudioInternal _clydeAudio = default!;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.ViewVariables;
|
||||
|
||||
internal sealed partial class ClientViewVariablesManager
|
||||
{
|
||||
private void InitializeDomains()
|
||||
{
|
||||
RegisterDomain("guihover", ResolveGuiHoverObject, ListGuiHoverPaths);
|
||||
}
|
||||
|
||||
private (ViewVariablesPath? path, string[] segments) ResolveGuiHoverObject(string path)
|
||||
{
|
||||
var segments = path.Split('/');
|
||||
|
||||
return (_userInterfaceManager.CurrentlyHovered != null
|
||||
? new ViewVariablesInstancePath(_userInterfaceManager.CurrentlyHovered)
|
||||
: null, segments);
|
||||
}
|
||||
|
||||
private IEnumerable<string>? ListGuiHoverPaths(string[] segments)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.ViewVariables.Editors;
|
||||
@@ -13,6 +14,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -20,8 +22,9 @@ using static Robust.Client.ViewVariables.Editors.VVPropEditorNumeric;
|
||||
|
||||
namespace Robust.Client.ViewVariables
|
||||
{
|
||||
internal sealed class ViewVariablesManager : ViewVariablesManagerShared, IViewVariablesManagerInternal
|
||||
internal sealed partial class ClientViewVariablesManager : ViewVariablesManager, IClientViewVariablesManagerInternal
|
||||
{
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||
[Dependency] private readonly IRobustSerializer _robustSerializer = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
@@ -41,8 +44,10 @@ namespace Robust.Client.ViewVariables
|
||||
private readonly Dictionary<uint, TaskCompletionSource<ViewVariablesBlob>> _requestedData
|
||||
= new();
|
||||
|
||||
public void Initialize()
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
InitializeDomains();
|
||||
_netManager.RegisterNetMessage<MsgViewVariablesOpenSession>(_netMessageOpenSession);
|
||||
_netManager.RegisterNetMessage<MsgViewVariablesRemoteData>(_netMessageRemoteData);
|
||||
_netManager.RegisterNetMessage<MsgViewVariablesCloseSession>(_netMessageCloseSession);
|
||||
@@ -230,6 +235,12 @@ namespace Robust.Client.ViewVariables
|
||||
window.Open();
|
||||
}
|
||||
|
||||
public void OpenVV(string path)
|
||||
{
|
||||
if (ReadPath(path) is {} obj)
|
||||
OpenVV(obj);
|
||||
}
|
||||
|
||||
public async void OpenVV(ViewVariablesObjectSelector selector)
|
||||
{
|
||||
var window = new DefaultWindow
|
||||
@@ -406,16 +417,28 @@ namespace Robust.Client.ViewVariables
|
||||
_requestedSessions.Remove(message.RequestId);
|
||||
tcs.SetException(new SessionDenyException(message.Reason));
|
||||
}
|
||||
|
||||
protected override bool CheckPermissions(INetChannel channel)
|
||||
{
|
||||
// Acquiesce, client!! Do what the server tells you.
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool TryGetSession(Guid guid, [NotNullWhen(true)] out ICommonSession? session)
|
||||
{
|
||||
session = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Virtual]
|
||||
public class SessionDenyException : Exception
|
||||
{
|
||||
public SessionDenyException(MsgViewVariablesDenySession.DenyReason reason)
|
||||
public SessionDenyException(ViewVariablesResponseCode reason)
|
||||
{
|
||||
Reason = reason;
|
||||
}
|
||||
|
||||
public MsgViewVariablesDenySession.DenyReason Reason { get; }
|
||||
public ViewVariablesResponseCode Reason { get; }
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ namespace Robust.Client.ViewVariables.Editors
|
||||
if (_selector is not ViewVariablesSessionRelativeSelector selector
|
||||
|| _localValue is not ViewVariablesBlobMembers.PrototypeReferenceToken protoToken) return;
|
||||
|
||||
var vvm = IoCManager.Resolve<IViewVariablesManagerInternal>();
|
||||
var vvm = IoCManager.Resolve<IClientViewVariablesManagerInternal>();
|
||||
|
||||
if (!vvm.TryGetSession(selector.SessionId, out var session)) return;
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Robust.Client.ViewVariables.Editors
|
||||
|
||||
private void OnInspectButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
var vvm = IoCManager.Resolve<IViewVariablesManager>();
|
||||
var vvm = IoCManager.Resolve<IClientViewVariablesManager>();
|
||||
|
||||
if(_selector != null)
|
||||
vvm.OpenVV(_selector);
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Robust.Client.ViewVariables.Editors
|
||||
{
|
||||
public sealed class VVPropEditorKeyValuePair : VVPropEditor
|
||||
{
|
||||
[Dependency] private readonly IViewVariablesManagerInternal _viewVariables = default!;
|
||||
[Dependency] private readonly IClientViewVariablesManagerInternal _viewVariables = default!;
|
||||
|
||||
private VVPropEditor? _propertyEditorK;
|
||||
private VVPropEditor? _propertyEditorV;
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Robust.Client.ViewVariables.Editors
|
||||
|
||||
private void ButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
var vvm = IoCManager.Resolve<IViewVariablesManager>();
|
||||
var vvm = IoCManager.Resolve<IClientViewVariablesManager>();
|
||||
if (_selector != null)
|
||||
{
|
||||
vvm.OpenVV(_selector);
|
||||
|
||||
@@ -2,7 +2,7 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.ViewVariables
|
||||
{
|
||||
public interface IViewVariablesManager
|
||||
public interface IClientViewVariablesManager : IViewVariablesManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Open a VV window for a locally existing object.
|
||||
@@ -10,6 +10,12 @@ namespace Robust.Client.ViewVariables
|
||||
/// <param name="obj">The object to VV.</param>
|
||||
void OpenVV(object obj);
|
||||
|
||||
/// <summary>
|
||||
/// Open a VV window for a locally existing object.
|
||||
/// </summary>
|
||||
/// <param name="path">The VV path to the object to VV.</param>
|
||||
void OpenVV(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Open a VV window for a remotely existing object.
|
||||
/// </summary>
|
||||
@@ -8,7 +8,7 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.ViewVariables
|
||||
{
|
||||
internal interface IViewVariablesManagerInternal : IViewVariablesManager
|
||||
internal interface IClientViewVariablesManagerInternal : IClientViewVariablesManager
|
||||
{
|
||||
void Initialize();
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace Robust.Client.ViewVariables
|
||||
/// Gets a collection of trait IDs that are agreed upon so <see cref="ViewVariablesInstanceObject"/> knows which traits to instantiate.
|
||||
/// </summary>
|
||||
/// <seealso cref="ViewVariablesBlobMetadata.Traits" />
|
||||
/// <seealso cref="ViewVariablesManagerShared.TraitIdsFor"/>
|
||||
/// <seealso cref="Shared.ViewVariables.ViewVariablesManager.TraitIdsFor"/>
|
||||
ICollection<object> TraitIdsFor(Type type);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
|
||||
private bool _serverLoaded;
|
||||
|
||||
public ViewVariablesInstanceEntity(IViewVariablesManagerInternal vvm, IEntityManager entityManager, IRobustSerializer robustSerializer) : base(vvm, robustSerializer)
|
||||
public ViewVariablesInstanceEntity(IClientViewVariablesManagerInternal vvm, IEntityManager entityManager, IRobustSerializer robustSerializer) : base(vvm, robustSerializer)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
public ViewVariablesRemoteSession? Session { get; private set; }
|
||||
public object? Object { get; private set; }
|
||||
|
||||
public ViewVariablesInstanceObject(IViewVariablesManagerInternal vvm, IRobustSerializer robustSerializer)
|
||||
public ViewVariablesInstanceObject(IClientViewVariablesManagerInternal vvm, IRobustSerializer robustSerializer)
|
||||
: base(vvm, robustSerializer) { }
|
||||
|
||||
public override void Initialize(DefaultWindow window, object obj)
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
{
|
||||
internal sealed class ViewVariablesTraitMembers : ViewVariablesTrait
|
||||
{
|
||||
private readonly IViewVariablesManagerInternal _vvm;
|
||||
private readonly IClientViewVariablesManagerInternal _vvm;
|
||||
private readonly IRobustSerializer _robustSerializer;
|
||||
|
||||
private BoxContainer _memberList = default!;
|
||||
@@ -27,7 +27,7 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
instance.AddTab("Members", _memberList);
|
||||
}
|
||||
|
||||
public ViewVariablesTraitMembers(IViewVariablesManagerInternal vvm, IRobustSerializer robustSerializer)
|
||||
public ViewVariablesTraitMembers(IClientViewVariablesManagerInternal vvm, IRobustSerializer robustSerializer)
|
||||
{
|
||||
_robustSerializer = robustSerializer;
|
||||
_vvm = vvm;
|
||||
|
||||
@@ -1,94 +1,51 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.IoC.Exceptions;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Robust.Shared.ViewVariables.Commands;
|
||||
|
||||
namespace Robust.Client.ViewVariables
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class ViewVariablesCommand : IConsoleCommand
|
||||
public sealed class ViewVariablesCommand : ViewVariablesBaseCommand, IConsoleCommand
|
||||
{
|
||||
public string Command => "vv";
|
||||
public string Description => "Opens View Variables.";
|
||||
public string Help => "Usage: vv <entity ID|IoC interface name|SIoC interface name>";
|
||||
[Dependency] private readonly IClientViewVariablesManager _cvvm = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "vv";
|
||||
public override string Description => "Opens View Variables.";
|
||||
public override string Help => "Usage: vv <path|entity ID|guihover>";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var vvm = IoCManager.Resolve<IViewVariablesManager>();
|
||||
// If you don't provide an entity ID, it opens the test class.
|
||||
// Spooky huh.
|
||||
if (args.Length == 0)
|
||||
{
|
||||
vvm.OpenVV(new VVTest());
|
||||
_cvvm.OpenVV(new ViewVariablesPathSelector("/vvtest"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 1)
|
||||
{
|
||||
shell.WriteError($"Incorrect number of arguments. Did you forget to quote a path?");
|
||||
return;
|
||||
}
|
||||
|
||||
var valArg = args[0];
|
||||
if (valArg.StartsWith("SI"))
|
||||
{
|
||||
if (valArg.StartsWith("SIoC"))
|
||||
valArg = valArg.Substring(4);
|
||||
|
||||
// Server-side IoC selector.
|
||||
var selector = new ViewVariablesIoCSelector(valArg.Substring(1));
|
||||
vvm.OpenVV(selector);
|
||||
if (valArg.StartsWith("/c"))
|
||||
{
|
||||
// Remove "/c" before calling method.
|
||||
_cvvm.OpenVV(valArg[2..]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (valArg.StartsWith("I"))
|
||||
if (valArg.StartsWith("/"))
|
||||
{
|
||||
if (valArg.StartsWith("IoC"))
|
||||
valArg = valArg.Substring(3);
|
||||
|
||||
// Client-side IoC selector.
|
||||
var reflection = IoCManager.Resolve<IReflectionManager>();
|
||||
if (!reflection.TryLooseGetType(valArg, out var type))
|
||||
{
|
||||
shell.WriteLine("Unable to find that type.");
|
||||
return;
|
||||
}
|
||||
|
||||
object obj;
|
||||
try
|
||||
{
|
||||
obj = IoCManager.ResolveType(type);
|
||||
}
|
||||
catch (UnregisteredTypeException)
|
||||
{
|
||||
shell.WriteLine("Unable to find that type.");
|
||||
return;
|
||||
}
|
||||
vvm.OpenVV(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
// Client side entity system.
|
||||
if (valArg.StartsWith("CE"))
|
||||
{
|
||||
valArg = valArg.Substring(2);
|
||||
var reflection = IoCManager.Resolve<IReflectionManager>();
|
||||
|
||||
if (!reflection.TryLooseGetType(valArg, out var type))
|
||||
{
|
||||
shell.WriteLine("Unable to find that type.");
|
||||
return;
|
||||
}
|
||||
|
||||
vvm.OpenVV(IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem(type));
|
||||
}
|
||||
|
||||
if (valArg.StartsWith("SE"))
|
||||
{
|
||||
// Server-side Entity system selector.
|
||||
var selector = new ViewVariablesEntitySystemSelector(valArg.Substring(2));
|
||||
vvm.OpenVV(selector);
|
||||
var selector = new ViewVariablesPathSelector(valArg);
|
||||
_cvvm.OpenVV(selector);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -101,7 +58,7 @@ namespace Robust.Client.ViewVariables
|
||||
shell.WriteLine("Not currently hovering any control.");
|
||||
return;
|
||||
}
|
||||
vvm.OpenVV(obj);
|
||||
_cvvm.OpenVV(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -116,37 +73,11 @@ namespace Robust.Client.ViewVariables
|
||||
if (!entityManager.EntityExists(entity))
|
||||
{
|
||||
shell.WriteLine("That entity does not exist locally. Attempting to open remote view...");
|
||||
vvm.OpenVV(new ViewVariablesEntitySelector(entity));
|
||||
_cvvm.OpenVV(new ViewVariablesEntitySelector(entity));
|
||||
return;
|
||||
}
|
||||
|
||||
vvm.OpenVV(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test class to test local VV easily without connecting to the server.
|
||||
/// </summary>
|
||||
private sealed class VVTest : IEnumerable<object>
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)] private int x = 10;
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<object, object> Dict => new() {{"a", "b"}, {"c", "d"}};
|
||||
|
||||
[ViewVariables]
|
||||
public List<object> List => new() {1, 2, 3, 4, 5, 6, 7, 8, 9, x, 11, 12, 13, 14, 15, this};
|
||||
|
||||
[ViewVariables] private Vector2 Vector = (50, 50);
|
||||
|
||||
public IEnumerator<object> GetEnumerator()
|
||||
{
|
||||
return List.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
_cvvm.OpenVV(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,10 @@ namespace Robust.Client.ViewVariables
|
||||
/// </summary>
|
||||
internal abstract class ViewVariablesInstance
|
||||
{
|
||||
public readonly IViewVariablesManagerInternal ViewVariablesManager;
|
||||
public readonly IClientViewVariablesManagerInternal ViewVariablesManager;
|
||||
protected readonly IRobustSerializer _robustSerializer;
|
||||
|
||||
protected ViewVariablesInstance(IViewVariablesManagerInternal vvm, IRobustSerializer robustSerializer)
|
||||
protected ViewVariablesInstance(IClientViewVariablesManagerInternal vvm, IRobustSerializer robustSerializer)
|
||||
{
|
||||
ViewVariablesManager = vvm;
|
||||
_robustSerializer = robustSerializer;
|
||||
@@ -53,7 +53,7 @@ namespace Robust.Client.ViewVariables
|
||||
{
|
||||
}
|
||||
|
||||
protected internal static IEnumerable<IGrouping<Type, Control>> LocalPropertyList(object obj, IViewVariablesManagerInternal vvm,
|
||||
protected internal static IEnumerable<IGrouping<Type, Control>> LocalPropertyList(object obj, IClientViewVariablesManagerInternal vvm,
|
||||
IRobustSerializer robustSerializer)
|
||||
{
|
||||
var styleOther = false;
|
||||
|
||||
@@ -21,10 +21,10 @@ namespace Robust.Client.ViewVariables
|
||||
|
||||
private readonly Label _bottomLabel;
|
||||
|
||||
private readonly IViewVariablesManagerInternal _viewVariablesManager;
|
||||
private readonly IClientViewVariablesManagerInternal _viewVariablesManager;
|
||||
private readonly IRobustSerializer _robustSerializer;
|
||||
|
||||
public ViewVariablesPropertyControl(IViewVariablesManagerInternal viewVars, IRobustSerializer robustSerializer)
|
||||
public ViewVariablesPropertyControl(IClientViewVariablesManagerInternal viewVars, IRobustSerializer robustSerializer)
|
||||
{
|
||||
MouseFilter = MouseFilterMode.Pass;
|
||||
|
||||
|
||||
@@ -328,7 +328,7 @@ namespace Robust.Server
|
||||
_playerManager.Initialize(MaxPlayers);
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
IoCManager.Resolve<IPlacementManager>().Initialize();
|
||||
IoCManager.Resolve<IViewVariablesHost>().Initialize();
|
||||
IoCManager.Resolve<IServerViewVariablesInternal>().Initialize();
|
||||
|
||||
// Call Init in game assemblies.
|
||||
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
|
||||
|
||||
@@ -25,6 +25,7 @@ using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Server
|
||||
{
|
||||
@@ -67,7 +68,8 @@ namespace Robust.Server
|
||||
IoCManager.Register<IStatusHost, StatusHost>();
|
||||
IoCManager.Register<ISystemConsoleManager, SystemConsoleManager>();
|
||||
IoCManager.Register<ITileDefinitionManager, TileDefinitionManager>();
|
||||
IoCManager.Register<IViewVariablesHost, ViewVariablesHost>();
|
||||
IoCManager.Register<IViewVariablesManager, ServerViewVariablesManager>();
|
||||
IoCManager.Register<IServerViewVariablesInternal, ServerViewVariablesManager>();
|
||||
IoCManager.Register<IWatchdogApi, WatchdogApi>();
|
||||
IoCManager.Register<IScriptHost, ScriptHost>();
|
||||
IoCManager.Register<IMetricsManager, MetricsManager>();
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Server.ViewVariables
|
||||
{
|
||||
internal interface IViewVariablesHost
|
||||
internal interface IServerViewVariablesInternal : IViewVariablesManager
|
||||
{
|
||||
void Initialize();
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Robust.Server.ViewVariables
|
||||
{
|
||||
internal interface IViewVariablesSession
|
||||
{
|
||||
IViewVariablesHost Host { get; }
|
||||
IServerViewVariablesInternal Host { get; }
|
||||
IRobustSerializer RobustSerializer { get; }
|
||||
NetUserId PlayerUser { get; }
|
||||
object Object { get; }
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Server.ViewVariables;
|
||||
|
||||
internal sealed partial class ServerViewVariablesManager
|
||||
{
|
||||
private void InitializeDomains()
|
||||
{
|
||||
RegisterDomain("player", ResolvePlayerObject, ListPlayerPaths);
|
||||
}
|
||||
|
||||
private (ViewVariablesPath? Path, string[] Segments) ResolvePlayerObject(string path)
|
||||
{
|
||||
var empty = (new ViewVariablesInstancePath(_playerManager), Array.Empty<string>());
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return empty;
|
||||
|
||||
var segments = path.Split('/');
|
||||
|
||||
if (segments.Length == 0)
|
||||
return empty;
|
||||
|
||||
var identifier = segments[0];
|
||||
|
||||
if (_playerManager.TryGetSessionByUsername(identifier, out var session))
|
||||
return (new ViewVariablesInstancePath(session), segments[1..]);
|
||||
|
||||
if (_playerManager.TryGetPlayerDataByUsername(identifier, out var data))
|
||||
return (new ViewVariablesInstancePath(data), segments[1..]);
|
||||
|
||||
if (!Guid.TryParse(identifier, out var guid))
|
||||
return EmptyResolve;
|
||||
|
||||
var netId = new NetUserId(guid);
|
||||
|
||||
if (_playerManager.TryGetSessionById(netId, out session))
|
||||
return (new ViewVariablesInstancePath(session), segments[1..]);
|
||||
|
||||
if (_playerManager.TryGetPlayerData(netId, out data))
|
||||
return (new ViewVariablesInstancePath(data), segments[1..]);
|
||||
|
||||
return EmptyResolve;
|
||||
}
|
||||
|
||||
private IEnumerable<string>? ListPlayerPaths(string[] segments)
|
||||
{
|
||||
if (segments.Length > 1)
|
||||
return null;
|
||||
|
||||
if (segments.Length == 1
|
||||
&& (_playerManager.TryGetSessionByUsername(segments[0], out _)
|
||||
|| Guid.TryParse(segments[0], out var guid)
|
||||
&& _playerManager.TryGetSessionById(new NetUserId(guid), out _)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _playerManager.Sessions
|
||||
.Select(s => s.Name)
|
||||
.Concat(_playerManager.Sessions
|
||||
.Select(s => s.UserId.UserId.ToString()));
|
||||
}
|
||||
}
|
||||
@@ -9,15 +9,15 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Shared.Network.Messages.MsgViewVariablesDenySession;
|
||||
|
||||
namespace Robust.Server.ViewVariables
|
||||
{
|
||||
internal sealed class ViewVariablesHost : ViewVariablesManagerShared, IViewVariablesHost
|
||||
internal sealed partial class ServerViewVariablesManager : ViewVariablesManager, IServerViewVariablesInternal
|
||||
{
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
@@ -31,8 +31,10 @@ namespace Robust.Server.ViewVariables
|
||||
|
||||
private uint _nextSessionId = 1;
|
||||
|
||||
public void Initialize()
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
InitializeDomains();
|
||||
_netManager.RegisterNetMessage<MsgViewVariablesReqSession>(_msgReqSession);
|
||||
_netManager.RegisterNetMessage<MsgViewVariablesReqData>(_msgReqData);
|
||||
_netManager.RegisterNetMessage<MsgViewVariablesModifyRemote>(_msgModifyRemote);
|
||||
@@ -99,7 +101,7 @@ namespace Robust.Server.ViewVariables
|
||||
|
||||
private void _msgReqSession(MsgViewVariablesReqSession message)
|
||||
{
|
||||
void Deny(DenyReason reason)
|
||||
void Deny(ViewVariablesResponseCode reason)
|
||||
{
|
||||
var denyMsg = new MsgViewVariablesDenySession();
|
||||
denyMsg.RequestId = message.RequestId;
|
||||
@@ -110,7 +112,7 @@ namespace Robust.Server.ViewVariables
|
||||
var player = _playerManager.GetSessionByChannel(message.MsgChannel);
|
||||
if (!_groupController.CanViewVar(player))
|
||||
{
|
||||
Deny(DenyReason.NoAccess);
|
||||
Deny(ViewVariablesResponseCode.NoAccess);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -124,7 +126,7 @@ namespace Robust.Server.ViewVariables
|
||||
if (compType == null ||
|
||||
!_entityManager.TryGetComponent(componentSelector.Entity, compType, out var component))
|
||||
{
|
||||
Deny(DenyReason.NoObject);
|
||||
Deny(ViewVariablesResponseCode.NoObject);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -135,7 +137,7 @@ namespace Robust.Server.ViewVariables
|
||||
{
|
||||
if (!_entityManager.EntityExists(entitySelector.Entity))
|
||||
{
|
||||
Deny(DenyReason.NoObject);
|
||||
Deny(ViewVariablesResponseCode.NoObject);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -148,7 +150,7 @@ namespace Robust.Server.ViewVariables
|
||||
|| relSession.PlayerUser != message.MsgChannel.UserId)
|
||||
{
|
||||
// TODO: logging?
|
||||
Deny(DenyReason.NoObject);
|
||||
Deny(ViewVariablesResponseCode.NoObject);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -157,25 +159,25 @@ namespace Robust.Server.ViewVariables
|
||||
{
|
||||
if (!relSession.TryGetRelativeObject(sessionRelativeSelector.PropertyIndex, out value))
|
||||
{
|
||||
Deny(DenyReason.InvalidRequest);
|
||||
Deny(ViewVariablesResponseCode.InvalidRequest);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
Deny(DenyReason.NoObject);
|
||||
Deny(ViewVariablesResponseCode.NoObject);
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("vv", "Exception while retrieving value for session. {0}", e);
|
||||
Deny(DenyReason.NoObject);
|
||||
Deny(ViewVariablesResponseCode.NoObject);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value == null || value.GetType().IsValueType)
|
||||
{
|
||||
Deny(DenyReason.NoObject);
|
||||
Deny(ViewVariablesResponseCode.NoObject);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -187,7 +189,7 @@ namespace Robust.Server.ViewVariables
|
||||
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
|
||||
if (!reflectionManager.TryLooseGetType(ioCSelector.TypeName, out var type))
|
||||
{
|
||||
Deny(DenyReason.InvalidRequest);
|
||||
Deny(ViewVariablesResponseCode.InvalidRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -199,15 +201,26 @@ namespace Robust.Server.ViewVariables
|
||||
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
|
||||
if (!reflectionManager.TryLooseGetType(esSelector.TypeName, out var type))
|
||||
{
|
||||
Deny(DenyReason.InvalidRequest);
|
||||
Deny(ViewVariablesResponseCode.InvalidRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
theObject = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem(type);
|
||||
break;
|
||||
}
|
||||
case ViewVariablesPathSelector paSelector:
|
||||
{
|
||||
if (ResolvePath(paSelector.Path)?.Get() is not {} obj)
|
||||
{
|
||||
Deny(ViewVariablesResponseCode.NoObject);
|
||||
return;
|
||||
}
|
||||
|
||||
theObject = obj;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Deny(DenyReason.InvalidRequest);
|
||||
Deny(ViewVariablesResponseCode.InvalidRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -272,5 +285,24 @@ namespace Robust.Server.ViewVariables
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool CheckPermissions(INetChannel channel)
|
||||
{
|
||||
return _playerManager.TryGetSessionByChannel(channel, out var session) && _groupController.CanViewVar(session);
|
||||
}
|
||||
|
||||
protected override bool TryGetSession(Guid guid, [NotNullWhen(true)] out ICommonSession? session)
|
||||
{
|
||||
if (guid != Guid.Empty
|
||||
&& _playerManager.TryGetSessionById(new NetUserId(guid), out var player)
|
||||
&& !_groupController.CanViewVar(player)) // Can't VV other admins.
|
||||
{
|
||||
session = player;
|
||||
return true;
|
||||
}
|
||||
|
||||
session = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Server.ViewVariables
|
||||
internal sealed class ViewVariablesSession : IViewVariablesSession
|
||||
{
|
||||
private readonly List<ViewVariablesTrait> _traits = new();
|
||||
public IViewVariablesHost Host { get; }
|
||||
public IServerViewVariablesInternal Host { get; }
|
||||
public IRobustSerializer RobustSerializer { get; }
|
||||
public NetUserId PlayerUser { get; }
|
||||
public object Object { get; }
|
||||
@@ -24,7 +24,7 @@ namespace Robust.Server.ViewVariables
|
||||
/// The session ID for this session. This is what the server and client use to talk about this session.
|
||||
/// </param>
|
||||
/// <param name="host">The view variables host owning this session.</param>
|
||||
public ViewVariablesSession(NetUserId playerUser, object o, uint sessionId, IViewVariablesHost host,
|
||||
public ViewVariablesSession(NetUserId playerUser, object o, uint sessionId, IServerViewVariablesInternal host,
|
||||
IRobustSerializer robustSerializer)
|
||||
{
|
||||
PlayerUser = playerUser;
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Robust.Shared.GameObjects
|
||||
#endif
|
||||
|
||||
private DependencyCollection _systemDependencyCollection = default!;
|
||||
private List<Type> _systemTypes = new();
|
||||
private readonly List<Type> _systemTypes = new();
|
||||
|
||||
private static readonly Histogram _tickUsageHistogram = Metrics.CreateHistogram("robust_entity_systems_update_usage",
|
||||
"Amount of time spent processing each entity system", new HistogramConfiguration
|
||||
@@ -349,6 +349,16 @@ namespace Robust.Shared.GameObjects
|
||||
_extraLoadedTypes.Add(typeof(T));
|
||||
}
|
||||
|
||||
public IEnumerable<Type> GetEntitySystemTypes()
|
||||
{
|
||||
return _systemTypes;
|
||||
}
|
||||
|
||||
public bool TryGetEntitySystem(Type sysType, [NotNullWhen(true)] out object? system)
|
||||
{
|
||||
return _systemDependencyCollection.TryResolveType(sysType, out system);
|
||||
}
|
||||
|
||||
public object GetEntitySystem(Type sysType)
|
||||
{
|
||||
return _systemDependencyCollection.ResolveType(sysType);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.IoC.Exceptions;
|
||||
|
||||
@@ -127,6 +128,8 @@ namespace Robust.Shared.GameObjects
|
||||
/// </exception>
|
||||
void LoadExtraSystemType<T>() where T : IEntitySystem, new();
|
||||
|
||||
IEnumerable<Type> GetEntitySystemTypes();
|
||||
bool TryGetEntitySystem(Type sysType, [NotNullWhen(true)] out object? system);
|
||||
object GetEntitySystem(Type sysType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,14 @@ namespace Robust.Shared.IoC
|
||||
_parentCollection = parentCollection;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Type> GetRegisteredTypes()
|
||||
{
|
||||
return _parentCollection != null
|
||||
? _services.Keys.Concat(_parentCollection.GetRegisteredTypes())
|
||||
: _services.Keys;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryResolveType<T>([NotNullWhen(true)] out T? instance)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.IoC.Exceptions;
|
||||
@@ -29,6 +30,11 @@ namespace Robust.Shared.IoC
|
||||
/// <seealso cref="IReflectionManager"/>
|
||||
public interface IDependencyCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerates over all registered types.
|
||||
/// </summary>
|
||||
IEnumerable<Type> GetRegisteredTypes();
|
||||
|
||||
/// <summary>
|
||||
/// Registers an interface to an implementation, to make it accessible to <see cref="DependencyCollection.Resolve{T}"/>
|
||||
/// <see cref="IDependencyCollection.BuildGraph"/> MUST be called after this method to make the new interface available.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
#nullable disable
|
||||
@@ -21,12 +22,12 @@ namespace Robust.Shared.Network.Messages
|
||||
/// <summary>
|
||||
/// Reason for why the request was denied.
|
||||
/// </summary>
|
||||
public DenyReason Reason { get; set; }
|
||||
public ViewVariablesResponseCode Reason { get; set; }
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
RequestId = buffer.ReadUInt32();
|
||||
Reason = (DenyReason)buffer.ReadUInt16();
|
||||
Reason = (ViewVariablesResponseCode)buffer.ReadUInt16();
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
@@ -34,23 +35,5 @@ namespace Robust.Shared.Network.Messages
|
||||
buffer.Write(RequestId);
|
||||
buffer.Write((ushort)Reason);
|
||||
}
|
||||
|
||||
public enum DenyReason : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// Come back with admin access.
|
||||
/// </summary>
|
||||
NoAccess = 401,
|
||||
|
||||
/// <summary>
|
||||
/// Object pointing to by the selector does not exist.
|
||||
/// </summary>
|
||||
NoObject = 404,
|
||||
|
||||
/// <summary>
|
||||
/// Request was invalid or something.
|
||||
/// </summary>
|
||||
InvalidRequest = 400,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@ namespace Robust.Shared.Prototypes
|
||||
{
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Returns an IEnumerable to iterate all registered prototype kind by their ID.
|
||||
/// </summary>
|
||||
IEnumerable<string> GetPrototypeKinds();
|
||||
|
||||
/// <summary>
|
||||
/// Return an IEnumerable to iterate all prototypes of a certain type.
|
||||
/// </summary>
|
||||
@@ -261,6 +266,11 @@ namespace Robust.Shared.Prototypes
|
||||
ReloadPrototypeTypes();
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetPrototypeKinds()
|
||||
{
|
||||
return _prototypeTypes.Keys;
|
||||
}
|
||||
|
||||
public IEnumerable<T> EnumeratePrototypes<T>() where T : class, IPrototype
|
||||
{
|
||||
if (!_hasEverBeenReloaded)
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Reflection
|
||||
{
|
||||
internal static class ReflectionExtensions
|
||||
{
|
||||
public static Type GetUnderlyingType(this MemberInfo member)
|
||||
internal static Type GetUnderlyingType(this MemberInfo member)
|
||||
{
|
||||
return member.MemberType switch
|
||||
{
|
||||
@@ -16,5 +18,64 @@ namespace Robust.Shared.Reflection
|
||||
_ => throw new ArgumentException("MemberInfo must be one of: EventInfo, FieldInfo, MethodInfo, PropertyInfo")
|
||||
};
|
||||
}
|
||||
|
||||
internal static object? GetValue(this MemberInfo member, object instance)
|
||||
{
|
||||
return member switch
|
||||
{
|
||||
FieldInfo field => field.GetValue(instance),
|
||||
PropertyInfo property => property.GetValue(instance),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(member))
|
||||
};
|
||||
}
|
||||
|
||||
internal static void SetValue(this MemberInfo member, object instance, object? value)
|
||||
{
|
||||
switch (member)
|
||||
{
|
||||
case FieldInfo field:
|
||||
{
|
||||
field.SetValue(instance, value);
|
||||
return;
|
||||
}
|
||||
case PropertyInfo property:
|
||||
{
|
||||
property.SetValue(instance, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static MemberInfo? GetSingleMember(this Type type, string member, Type? declaringType = null)
|
||||
{
|
||||
var members = type
|
||||
.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Where(m => m.Name == member)
|
||||
.ToArray();
|
||||
|
||||
if (members.Length == 0)
|
||||
return null;
|
||||
|
||||
if (declaringType != null)
|
||||
return members.SingleOrDefault(m => m.DeclaringType == declaringType);
|
||||
|
||||
return members.Length > 1
|
||||
// In case there's member hiding going on, grab the one declared by the type of the object by default.
|
||||
? members.SingleOrDefault(m => m.DeclaringType == type)
|
||||
: members[0];
|
||||
}
|
||||
|
||||
internal static PropertyInfo? GetIndexer(this Type type)
|
||||
{
|
||||
foreach (var pInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
{
|
||||
if (pInfo.GetIndexParameters().Length == 0)
|
||||
continue;
|
||||
|
||||
return pInfo;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
24
Robust.Shared/Serialization/YamlNoDocEndDotsFix.cs
Normal file
24
Robust.Shared/Serialization/YamlNoDocEndDotsFix.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.Core.Events;
|
||||
|
||||
namespace Robust.Shared.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// So by default, YamlDotNet appends "..." to the end of a serialized document.
|
||||
/// In the YAML spec, three dots signify the end of a document.
|
||||
/// If you're serializing a single document, this is pretty much useless. This emitter removes these dots entirely.
|
||||
/// </summary>
|
||||
public sealed class YamlNoDocEndDotsFix : IEmitter
|
||||
{
|
||||
private readonly IEmitter _next;
|
||||
|
||||
public YamlNoDocEndDotsFix(IEmitter next)
|
||||
{
|
||||
this._next = next;
|
||||
}
|
||||
|
||||
public void Emit(ParsingEvent @event)
|
||||
{
|
||||
_next.Emit(@event is DocumentEnd ? new DocumentEnd(true) : @event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Robust.Shared.ViewVariables.Commands;
|
||||
|
||||
public abstract class ViewVariablesBaseCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] protected readonly INetManager _netMan = default!;
|
||||
[Dependency] protected readonly IViewVariablesManager _vvm = default!;
|
||||
|
||||
public abstract string Command { get; }
|
||||
public abstract string Description { get; }
|
||||
public abstract string Help { get; }
|
||||
public abstract void Execute(IConsoleShell shell, string argStr, string[] args);
|
||||
|
||||
public virtual async ValueTask<CompletionResult> GetCompletionAsync(IConsoleShell shell, string[] args, CancellationToken cancel)
|
||||
{
|
||||
if (args.Length is 0 or > 1)
|
||||
return CompletionResult.Empty;
|
||||
|
||||
var path = args[0];
|
||||
|
||||
if(_netMan.IsClient)
|
||||
{
|
||||
if(path.StartsWith("/c"))
|
||||
return CompletionResult.FromOptions(
|
||||
_vvm.ListPath(path[2..], new())
|
||||
.Select(p => new CompletionOption($"/c{p}", null, CompletionOptionFlags.PartialCompletion)));
|
||||
|
||||
return CompletionResult.FromOptions((await _vvm.ListRemotePath(path, new()))
|
||||
.Select(p => new CompletionOption(p, null, CompletionOptionFlags.PartialCompletion))
|
||||
.Append(new CompletionOption("/c", "Client-side paths", CompletionOptionFlags.PartialCompletion)));
|
||||
}
|
||||
|
||||
return CompletionResult.FromOptions(
|
||||
_vvm.ListPath(path, new())
|
||||
.Select(p => new CompletionOption(p, null, CompletionOptionFlags.PartialCompletion)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Shared.ViewVariables.Commands;
|
||||
|
||||
public sealed class ViewVariablesInvokeCommand : ViewVariablesBaseCommand, IConsoleCommand
|
||||
{
|
||||
public override string Command => "vvinvoke";
|
||||
public override string Description => "Invoke/Call a path with arguments using VV.";
|
||||
public override string Help => $"{Command} <path> [arguments...]";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
shell.WriteError("Not enough arguments!");
|
||||
return;
|
||||
}
|
||||
|
||||
var path = args[0];
|
||||
var arguments = string.Join(string.Empty, args[1..]);
|
||||
|
||||
if (_netMan.IsClient)
|
||||
{
|
||||
if (!path.StartsWith("/c"))
|
||||
{
|
||||
_vvm.InvokeRemotePath(path, arguments);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove "/c"
|
||||
path = path[2..];
|
||||
}
|
||||
|
||||
var obj = _vvm.InvokePath(path, arguments);
|
||||
shell.WriteLine(obj?.ToString() ?? "null");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Robust.Shared.ViewVariables.Commands;
|
||||
|
||||
public sealed class ViewVariablesReadCommand : ViewVariablesBaseCommand, IConsoleCommand
|
||||
{
|
||||
public override string Command => "vvread";
|
||||
public override string Description => "Retrieve a path's value using VV.";
|
||||
public override string Help => $"{Command} <path>";
|
||||
|
||||
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
shell.WriteError("Not enough arguments!");
|
||||
return;
|
||||
}
|
||||
|
||||
var path = args[0];
|
||||
|
||||
if (_netMan.IsClient)
|
||||
{
|
||||
if (!path.StartsWith("/c"))
|
||||
{
|
||||
shell.WriteLine(await _vvm.ReadRemotePath(path) ?? "null");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove "/c"
|
||||
path = path[2..];
|
||||
}
|
||||
|
||||
// TODO: Maybe serialize this with serv3 before printing?
|
||||
var obj = _vvm.ReadPath(path);
|
||||
shell.WriteLine(obj?.ToString() ?? "null");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Robust.Shared.ViewVariables.Commands;
|
||||
|
||||
public sealed class ViewVariablesWriteCommand : ViewVariablesBaseCommand, IConsoleCommand
|
||||
{
|
||||
public override string Command => "vvwrite";
|
||||
public override string Description => "Modify a path's value using VV.";
|
||||
public override string Help => $"{Command} <path> <value>";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteError("Incorrect number of arguments!");
|
||||
return;
|
||||
}
|
||||
|
||||
var path = args[0];
|
||||
var value = args[1];
|
||||
|
||||
if (_netMan.IsClient)
|
||||
{
|
||||
if (!path.StartsWith("/c"))
|
||||
{
|
||||
_vvm.WriteRemotePath(path, value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove "/c"
|
||||
path = path[2..];
|
||||
}
|
||||
|
||||
_vvm.WritePath(path, value);
|
||||
}
|
||||
}
|
||||
64
Robust.Shared/ViewVariables/IViewVariablesManager.cs
Normal file
64
Robust.Shared/ViewVariables/IViewVariablesManager.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
namespace Robust.Shared.ViewVariables;
|
||||
|
||||
public interface IViewVariablesManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows you to register the handlers for a domain.
|
||||
/// Domains are the top-level segments of a VV path.
|
||||
/// They provide ViewVariables with access to any number of objects.
|
||||
/// A proper domain should only handle the next segment of the path.
|
||||
///
|
||||
/// <code>/entity/12345</code>
|
||||
///
|
||||
/// In the example above, "entity" would be a registered domain
|
||||
/// and "12345" would be an object (entity UID) that is resolved by it.
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="domain">The name of the domain to register.</param>
|
||||
/// <param name="resolveObject">The handler for resolving paths.</param>
|
||||
/// <param name="list">The handler for listing objects under the domain.</param>
|
||||
/// <seealso cref="UnregisterDomain"/>
|
||||
void RegisterDomain(string domain, DomainResolveObject resolveObject, DomainListPaths list);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters the handlers for a given domain.
|
||||
/// </summary>
|
||||
/// <param name="domain">The name of the domain to unregister.</param>
|
||||
/// <returns>Whether the domain existed and was able to be unregistered or not.</returns>
|
||||
/// <seealso cref="RegisterDomain"/>
|
||||
bool UnregisterDomain(string domain);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the type handler for a given type.
|
||||
/// Creates it if it didn't exist already. Allows you to register custom handlers for a type.
|
||||
/// Type handlers expand the paths available under a certain type on VV.
|
||||
///
|
||||
/// <code>/entity/12345/Custom</code>
|
||||
///
|
||||
/// In the example above, "Custom" could be a path that the type handler for <see cref="EntityUid"/> provided.
|
||||
/// It does not exist on the <see cref="EntityUid"/> declaration, but that does not matter:
|
||||
/// VV treats it the same as a "real" member under that type.
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>The type handler object, which allows you register handlers and paths for the type.</returns>
|
||||
/// <seealso cref="RegisterDomain"/>
|
||||
ViewVariablesTypeHandler<T> GetTypeHandler<T>();
|
||||
|
||||
/// <param name="path">The path to be resolved.</param>
|
||||
/// <returns>An object representing the path, or null if the path couldn't be resolved.</returns>
|
||||
ViewVariablesPath? ResolvePath(string path);
|
||||
object? ReadPath(string path);
|
||||
void WritePath(string path, string value);
|
||||
object? InvokePath(string path, string arguments);
|
||||
IEnumerable<string> ListPath(string path, VVListPathOptions options);
|
||||
|
||||
Task<string?> ReadRemotePath(string path, ICommonSession? session = null);
|
||||
Task WriteRemotePath(string path, string value, ICommonSession? session = null);
|
||||
Task<string?> InvokeRemotePath(string path, string arguments, ICommonSession? session = null);
|
||||
Task<IEnumerable<string>> ListRemotePath(string path, VVListPathOptions options, ICommonSession? session = null);
|
||||
}
|
||||
183
Robust.Shared/ViewVariables/MsgViewVariablesPath.cs
Normal file
183
Robust.Shared/ViewVariables/MsgViewVariablesPath.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.ViewVariables;
|
||||
|
||||
internal abstract class MsgViewVariablesPath : NetMessage
|
||||
{
|
||||
public override MsgGroups MsgGroup => MsgGroups.Command;
|
||||
|
||||
public uint RequestId { get; set; } = 0;
|
||||
public string Path { get; set; } = string.Empty;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
RequestId = buffer.ReadUInt32();
|
||||
Path = buffer.ReadString();
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer , IRobustSerializer serializer)
|
||||
{
|
||||
buffer.Write(RequestId);
|
||||
buffer.Write(Path);
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class MsgViewVariablesPathReq : MsgViewVariablesPath
|
||||
{
|
||||
public Guid Session { get; set; } = Guid.Empty;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
base.ReadFromBuffer(buffer, serializer);
|
||||
Session = buffer.ReadGuid();
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
base.WriteToBuffer(buffer, serializer);
|
||||
buffer.Write(Session);
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class MsgViewVariablesPathReqVal : MsgViewVariablesPathReq
|
||||
{
|
||||
public string? Value { get; set; } = null;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
base.ReadFromBuffer(buffer, serializer);
|
||||
Value = buffer.ReadString();
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
base.WriteToBuffer(buffer, serializer);
|
||||
buffer.Write(Value);
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class MsgViewVariablesPathRes : MsgViewVariablesPath
|
||||
{
|
||||
public string[] Response { get; set; } = Array.Empty<string>();
|
||||
public ViewVariablesResponseCode ResponseCode { get; set; } = ViewVariablesResponseCode.Ok;
|
||||
|
||||
internal MsgViewVariablesPathRes()
|
||||
{
|
||||
}
|
||||
|
||||
internal MsgViewVariablesPathRes(MsgViewVariablesPathReq req)
|
||||
{
|
||||
Path = req.Path;
|
||||
RequestId = req.RequestId;
|
||||
}
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
base.ReadFromBuffer(buffer, serializer);
|
||||
ResponseCode = (ViewVariablesResponseCode) buffer.ReadUInt16();
|
||||
var length = buffer.ReadInt32();
|
||||
Response = new string[length];
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
Response[i] = buffer.ReadString();
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
base.WriteToBuffer(buffer, serializer);
|
||||
buffer.Write((ushort)ResponseCode);
|
||||
buffer.Write(Response.Length);
|
||||
|
||||
foreach (var value in Response)
|
||||
{
|
||||
buffer.Write(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MsgViewVariablesReadPathReq : MsgViewVariablesPathReq
|
||||
{
|
||||
}
|
||||
|
||||
internal sealed class MsgViewVariablesReadPathRes : MsgViewVariablesPathRes
|
||||
{
|
||||
public MsgViewVariablesReadPathRes()
|
||||
{
|
||||
}
|
||||
|
||||
public MsgViewVariablesReadPathRes(MsgViewVariablesReadPathReq req) : base(req)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MsgViewVariablesWritePathReq : MsgViewVariablesPathReqVal
|
||||
{
|
||||
}
|
||||
|
||||
internal sealed class MsgViewVariablesWritePathRes : MsgViewVariablesPathRes
|
||||
{
|
||||
public MsgViewVariablesWritePathRes()
|
||||
{
|
||||
}
|
||||
|
||||
public MsgViewVariablesWritePathRes(MsgViewVariablesWritePathReq req) : base(req)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MsgViewVariablesInvokePathReq : MsgViewVariablesPathReqVal
|
||||
{
|
||||
}
|
||||
|
||||
internal sealed class MsgViewVariablesInvokePathRes : MsgViewVariablesPathRes
|
||||
{
|
||||
public MsgViewVariablesInvokePathRes()
|
||||
{
|
||||
}
|
||||
|
||||
public MsgViewVariablesInvokePathRes(MsgViewVariablesInvokePathReq req) : base(req)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MsgViewVariablesListPathReq : MsgViewVariablesPathReq
|
||||
{
|
||||
public VVListPathOptions Options { get; set; }
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
base.ReadFromBuffer(buffer, serializer);
|
||||
var length = buffer.ReadInt32();
|
||||
using var stream = buffer.ReadAlignedMemory(length);
|
||||
Options = serializer.Deserialize<VVListPathOptions>(stream);
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
base.WriteToBuffer(buffer, serializer);
|
||||
var stream = new MemoryStream();
|
||||
serializer.Serialize(stream, Options);
|
||||
|
||||
buffer.Write((int)stream.Length);
|
||||
buffer.Write(stream.AsSpan());
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MsgViewVariablesListPathRes : MsgViewVariablesPathRes
|
||||
{
|
||||
public MsgViewVariablesListPathRes()
|
||||
{
|
||||
}
|
||||
|
||||
public MsgViewVariablesListPathRes(MsgViewVariablesListPathReq req) : base(req)
|
||||
{
|
||||
}
|
||||
}
|
||||
20
Robust.Shared/ViewVariables/VVListPathOptions.cs
Normal file
20
Robust.Shared/ViewVariables/VVListPathOptions.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.ViewVariables;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
[Serializable, NetSerializable]
|
||||
public readonly struct VVListPathOptions
|
||||
{
|
||||
public VVAccess MinimumAccess { get; init; }
|
||||
public bool ListIndexers { get; init; }
|
||||
public int RemoteListLength { get; init; }
|
||||
|
||||
public VVListPathOptions()
|
||||
{
|
||||
MinimumAccess = VVAccess.ReadOnly;
|
||||
ListIndexers = true;
|
||||
RemoteListLength = ViewVariablesManager.MaxListPathResponseLength;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.ViewVariables
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute to make a property or field accessible to VV.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||
public sealed class ViewVariablesAttribute : Attribute
|
||||
{
|
||||
public readonly VVAccess Access = VVAccess.ReadOnly;
|
||||
@@ -21,6 +22,7 @@ namespace Robust.Shared.ViewVariables
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum VVAccess : byte
|
||||
{
|
||||
/// <summary>
|
||||
@@ -31,6 +33,6 @@ namespace Robust.Shared.ViewVariables
|
||||
/// <summary>
|
||||
/// This property is read and writable.
|
||||
/// </summary>
|
||||
ReadWrite,
|
||||
ReadWrite = 1,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Robust.Shared.ViewVariables
|
||||
/// For flexibility, these traits can be any object that the server/client need to understand each other.
|
||||
/// At the moment though the only thing they're used with is <see cref="ViewVariablesTraits"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="ViewVariablesManagerShared.TraitIdsFor" />
|
||||
/// <seealso cref="ViewVariablesManager.TraitIdsFor" />
|
||||
public List<object> Traits { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
294
Robust.Shared/ViewVariables/ViewVariablesManager.Domains.cs
Normal file
294
Robust.Shared/ViewVariables/ViewVariablesManager.Domains.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.ViewVariables;
|
||||
|
||||
public delegate (ViewVariablesPath? path, string[] segments) DomainResolveObject(string path);
|
||||
public delegate IEnumerable<string>? DomainListPaths(string[] segments);
|
||||
|
||||
internal abstract partial class ViewVariablesManager
|
||||
{
|
||||
protected static readonly (ViewVariablesPath? Path, string[] Segments) EmptyResolve = (null, Array.Empty<string>());
|
||||
|
||||
private readonly Dictionary<string, DomainData> _registeredDomains = new();
|
||||
protected readonly Dictionary<Guid, WeakReference<object>> _vvObjectStorage = new();
|
||||
|
||||
public void RegisterDomain(string domain, DomainResolveObject resolveObject, DomainListPaths list)
|
||||
{
|
||||
_registeredDomains.Add(domain, new DomainData(resolveObject, list));
|
||||
}
|
||||
|
||||
public bool UnregisterDomain(string domain)
|
||||
{
|
||||
return _registeredDomains.Remove(domain);
|
||||
}
|
||||
|
||||
private void InitializeDomains()
|
||||
{
|
||||
RegisterDomain("ioc", ResolveIoCObject, ListIoCPaths);
|
||||
RegisterDomain("entity", ResolveEntityObject, ListEntityPaths);
|
||||
RegisterDomain("system", ResolveEntitySystemObject, ListEntitySystemPaths);
|
||||
RegisterDomain("prototype", ResolvePrototypeObject, ListPrototypePaths);
|
||||
RegisterDomain("object", ResolveStoredObject, ListStoredObjectPaths);
|
||||
RegisterDomain("vvtest", ResolveVvTestObject, ListVvTestObjectPaths);
|
||||
}
|
||||
|
||||
private (ViewVariablesPath? Path, string[] Segments) ResolveIoCObject(string path)
|
||||
{
|
||||
var empty = (new ViewVariablesInstancePath(IoCManager.Instance), Array.Empty<string>());
|
||||
|
||||
if (string.IsNullOrEmpty(path) || IoCManager.Instance == null)
|
||||
return empty;
|
||||
|
||||
var segments = path.Split('/');
|
||||
|
||||
if (segments.Length == 0)
|
||||
return empty;
|
||||
|
||||
var service = segments[0];
|
||||
|
||||
if (!_reflectionMan.TryLooseGetType(service, out var type))
|
||||
return EmptyResolve;
|
||||
|
||||
return IoCManager.Instance.TryResolveType(type, out var obj)
|
||||
? (new ViewVariablesInstancePath(obj), segments[1..])
|
||||
: EmptyResolve;
|
||||
}
|
||||
|
||||
private IEnumerable<string>? ListIoCPaths(string[] segments)
|
||||
{
|
||||
if (segments.Length > 1 || IoCManager.Instance is not {} deps)
|
||||
return null;
|
||||
|
||||
if (segments.Length == 1
|
||||
&& _reflectionMan.TryLooseGetType(segments[0], out var type)
|
||||
&& deps.TryResolveType(type, out _))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return deps.GetRegisteredTypes()
|
||||
.Select(t => t.Name);
|
||||
}
|
||||
|
||||
private (ViewVariablesPath? Path, string[] Segments) ResolveEntityObject(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return EmptyResolve;
|
||||
|
||||
var segments = path.Split('/');
|
||||
|
||||
if (segments.Length == 0)
|
||||
return EmptyResolve;
|
||||
|
||||
if (!int.TryParse(segments[0], out var num) || num <= 0)
|
||||
return EmptyResolve;
|
||||
|
||||
var uid = new EntityUid(num);
|
||||
|
||||
return (new ViewVariablesInstancePath(uid), segments[1..]);
|
||||
}
|
||||
|
||||
private IEnumerable<string>? ListEntityPaths(string[] segments)
|
||||
{
|
||||
if (segments.Length > 1)
|
||||
return null;
|
||||
|
||||
if (segments.Length == 1
|
||||
&& EntityUid.TryParse(segments[0], out var u)
|
||||
&& _entMan.EntityExists(u))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _entMan.GetEntities()
|
||||
.Select(uid => uid.ToString());
|
||||
}
|
||||
|
||||
|
||||
public (ViewVariablesPath? Path, string[] Segments) ResolveEntitySystemObject(string path)
|
||||
{
|
||||
var entSysMan = _entMan.EntitySysManager;
|
||||
var empty = (new ViewVariablesInstancePath(entSysMan), Array.Empty<string>());
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return empty;
|
||||
|
||||
var segments = path.Split('/');
|
||||
|
||||
if (segments.Length == 0)
|
||||
return empty;
|
||||
|
||||
var sys = segments[0];
|
||||
|
||||
if (!_reflectionMan.TryLooseGetType(sys, out var type))
|
||||
return EmptyResolve;
|
||||
|
||||
return entSysMan.TryGetEntitySystem(type, out var obj)
|
||||
? (new ViewVariablesInstancePath(obj), segments[1..])
|
||||
: EmptyResolve;
|
||||
}
|
||||
|
||||
private IEnumerable<string>? ListEntitySystemPaths(string[] segments)
|
||||
{
|
||||
if (segments.Length > 1)
|
||||
return null;
|
||||
|
||||
var entSysMan = _entMan.EntitySysManager;
|
||||
|
||||
if (segments.Length == 1
|
||||
&& _reflectionMan.TryLooseGetType(segments[0], out var type)
|
||||
&& entSysMan.TryGetEntitySystem(type, out _))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _entMan.EntitySysManager
|
||||
.GetEntitySystemTypes()
|
||||
.Select(t => t.Name);
|
||||
}
|
||||
|
||||
private (ViewVariablesPath? Path, string[] Segments) ResolvePrototypeObject(string path)
|
||||
{
|
||||
var empty = (new ViewVariablesInstancePath(_protoMan), Array.Empty<string>());
|
||||
|
||||
if (string.IsNullOrEmpty(path) || IoCManager.Instance == null)
|
||||
return empty;
|
||||
|
||||
var segments = path.Split('/');
|
||||
|
||||
if (segments.Length <= 1)
|
||||
return empty;
|
||||
|
||||
var kind = segments[0];
|
||||
var id = segments[1];
|
||||
|
||||
if (!_protoMan.TryGetVariantType(kind, out var kindType)
|
||||
|| !_protoMan.TryIndex(kindType, id, out var prototype))
|
||||
return EmptyResolve;
|
||||
|
||||
return (new ViewVariablesInstancePath(prototype), segments[2..]);
|
||||
}
|
||||
|
||||
private IEnumerable<string>? ListPrototypePaths(string[] segments)
|
||||
{
|
||||
switch (segments.Length)
|
||||
{
|
||||
case 1 or 2:
|
||||
{
|
||||
var kind = segments[0];
|
||||
var prototype = segments.Length == 1 ? string.Empty : segments[1];
|
||||
|
||||
if(!_protoMan.HasVariant(kind))
|
||||
goto case 0;
|
||||
|
||||
if (_protoMan.TryIndex(_protoMan.GetVariantType(kind), prototype, out _))
|
||||
goto case default;
|
||||
|
||||
return _protoMan.EnumeratePrototypes(kind)
|
||||
.Select(p => $"{kind}/{p.ID}");
|
||||
}
|
||||
case 0:
|
||||
{
|
||||
return _protoMan
|
||||
.GetPrototypeKinds();
|
||||
}
|
||||
default:
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private (ViewVariablesPath? Path, string[] Segments) ResolveStoredObject(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return EmptyResolve;
|
||||
|
||||
var segments = path.Split('/');
|
||||
|
||||
if (segments.Length == 0
|
||||
|| !Guid.TryParse(segments[0], out var guid)
|
||||
|| !_vvObjectStorage.TryGetValue(guid, out var weakRef)
|
||||
|| !weakRef.TryGetTarget(out var obj))
|
||||
return EmptyResolve;
|
||||
|
||||
return (new ViewVariablesInstancePath(obj), segments[1..]);
|
||||
}
|
||||
|
||||
private IEnumerable<string>? ListStoredObjectPaths(string[] segments)
|
||||
{
|
||||
if (segments.Length > 1)
|
||||
return null;
|
||||
|
||||
if (segments.Length == 1
|
||||
&& Guid.TryParse(segments[0], out var guid)
|
||||
&& _vvObjectStorage.ContainsKey(guid))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _vvObjectStorage.Keys
|
||||
.Select(g => g.ToString());
|
||||
}
|
||||
|
||||
private (ViewVariablesPath? path, string[] segments) ResolveVvTestObject(string path)
|
||||
{
|
||||
var segments = path.Split('/');
|
||||
|
||||
return (new ViewVariablesInstancePath(new VvTest()), segments);
|
||||
}
|
||||
|
||||
private IEnumerable<string>? ListVvTestObjectPaths(string[] segments)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test class to test local VV easily without connecting to the server.
|
||||
/// </summary>
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
private sealed class VvTest : IEnumerable<object>
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)] private int x = 10;
|
||||
|
||||
[ViewVariables] public Dictionary<object, object> Dict => new() {{"a", "b"}, {"c", "d"}};
|
||||
|
||||
[ViewVariables] public List<object> List => new() {1, 2, 3, 4, 5, 6, 7, 8, 9, x, 11, 12, 13, 14, 15, this};
|
||||
|
||||
|
||||
[ViewVariables] public int[,] MultiDimensionalArray = new int[5, 2] {{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 0}};
|
||||
|
||||
|
||||
[ViewVariables] private Vector2 Vector = (50, 50);
|
||||
|
||||
public IEnumerator<object> GetEnumerator()
|
||||
{
|
||||
return List.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DomainData
|
||||
{
|
||||
public readonly DomainResolveObject ResolveObject;
|
||||
public readonly DomainListPaths List;
|
||||
|
||||
public DomainData(DomainResolveObject resolveObject, DomainListPaths list)
|
||||
{
|
||||
ResolveObject = resolveObject;
|
||||
List = list;
|
||||
}
|
||||
}
|
||||
}
|
||||
226
Robust.Shared/ViewVariables/ViewVariablesManager.PathListing.cs
Normal file
226
Robust.Shared/ViewVariables/ViewVariablesManager.PathListing.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
namespace Robust.Shared.ViewVariables;
|
||||
|
||||
internal abstract partial class ViewVariablesManager
|
||||
{
|
||||
public IEnumerable<string> ListPath(string path, VVListPathOptions options)
|
||||
{
|
||||
// Helper to return all domains' full paths.
|
||||
IEnumerable<string> Domains()
|
||||
=> _registeredDomains.Keys.Select(d => $"/{d}");
|
||||
|
||||
// Helper to return full paths when given a full path and a number of paths relative to it.
|
||||
IEnumerable<string> Full(string fullPath, IEnumerable<string> relativePaths)
|
||||
{
|
||||
if (!fullPath.StartsWith('/'))
|
||||
fullPath = $"/{fullPath}";
|
||||
if (fullPath.EndsWith('/'))
|
||||
fullPath = fullPath[..^1];
|
||||
|
||||
return relativePaths
|
||||
.Select(p
|
||||
=> p.StartsWith("[")
|
||||
? $"{fullPath}{p}" // Indexers for the path
|
||||
: string.Join('/', fullPath, p)); // Actual relative paths
|
||||
}
|
||||
|
||||
if (path.StartsWith('/'))
|
||||
path = path[1..];
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return Domains();
|
||||
|
||||
var segments = path.Split('/');
|
||||
|
||||
if (segments.Length == 0)
|
||||
return Domains();
|
||||
|
||||
var domain = segments[0];
|
||||
|
||||
// If the specified domain of the path does not exist, return a list of paths instead.
|
||||
if (!_registeredDomains.TryGetValue(domain, out var data))
|
||||
return Domains();
|
||||
|
||||
// Let the domain handle listing its paths...
|
||||
var domainList = data.List(segments[1..]);
|
||||
|
||||
// If the domain returned null, that means we're dealing with a path we need to resolve.
|
||||
if (domainList != null)
|
||||
return Full($"/{domain}", domainList);
|
||||
|
||||
// Expensive :'(
|
||||
var resolved = ResolvePath(path);
|
||||
|
||||
// Attempt to get an object from the path...
|
||||
if (resolved?.Get() is not {} obj)
|
||||
{
|
||||
// Okay maybe the last segment of the path is not full? Attempt to resolve the prior path
|
||||
segments = segments[..^1];
|
||||
path = string.Join('/', segments);
|
||||
resolved = ResolvePath(path);
|
||||
|
||||
// If not even that worked, we probably just have an invalid path here... Return nothing.
|
||||
if(resolved?.Get() is not {} priorObj)
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
obj = priorObj;
|
||||
}
|
||||
|
||||
// We need a place to store all the relative paths we find! TODO: Perhaps just yield instead?
|
||||
var paths = new List<string>();
|
||||
|
||||
var type = obj.GetType();
|
||||
|
||||
if (_typeHandlers.TryGetValue(type, out var typeData))
|
||||
{
|
||||
paths.AddRange(typeData.ListPath(resolved));
|
||||
}
|
||||
|
||||
// We also use a set here for unique names as we need to handle member hiding properly...
|
||||
// Starts with all custom paths, as those can hide the "native" members of the object.
|
||||
var uniqueMemberNames = new HashSet<string>(paths);
|
||||
|
||||
// For member hiding handling purposes, we handle the members declared by the object's type itself first.
|
||||
foreach (var memberInfo in type.GetMembers(MembersBindings).OrderBy(m => m.DeclaringType == type))
|
||||
{
|
||||
// Ignore the member if it's not a VV member.
|
||||
if (!ViewVariablesUtility.TryGetViewVariablesAccess(memberInfo, out var memberAccess))
|
||||
continue;
|
||||
|
||||
// Also take access level into account.
|
||||
if (memberAccess < options.MinimumAccess)
|
||||
continue;
|
||||
|
||||
var name = memberInfo.Name;
|
||||
|
||||
// If the member name is not unique, we adds the type specifier to it.
|
||||
if (!uniqueMemberNames.Add(name))
|
||||
name = @$"{name}{{{memberInfo.DeclaringType?.FullName ?? typeof(void).FullName}}}";
|
||||
|
||||
paths.Add(name);
|
||||
|
||||
var memberObj = memberInfo.GetValue(obj);
|
||||
|
||||
if(options.ListIndexers)
|
||||
ListIndexers(memberObj, name, paths);
|
||||
}
|
||||
|
||||
if(options.ListIndexers)
|
||||
ListIndexers(obj, string.Empty, paths);
|
||||
|
||||
return Full(path, paths);
|
||||
}
|
||||
|
||||
private void ListIndexers(object? obj, string name, List<string> paths)
|
||||
{
|
||||
switch (obj)
|
||||
{
|
||||
// Handle dictionaries and lists specially, for indexing purposes...
|
||||
case IDictionary dict:
|
||||
{
|
||||
var keyType = typeof(void);
|
||||
|
||||
if (dict.GetType().GenericTypeArguments is {Length: 2} generics)
|
||||
{
|
||||
// Assume the key type is the first entry...
|
||||
keyType = generics[0];
|
||||
}
|
||||
|
||||
foreach (var key in dict.Keys)
|
||||
{
|
||||
try
|
||||
{
|
||||
var type = key.GetType();
|
||||
string? tag = null;
|
||||
|
||||
// Handle cases such as "Dictionary<object, whatever>"
|
||||
if (type != keyType)
|
||||
tag = $"!type:{type.Name}";
|
||||
|
||||
// Forgive me, Paul... We use serv3 to serialize the value into its "text value".
|
||||
if (SerializeValue(type, key, tag) is not {} value)
|
||||
continue;
|
||||
|
||||
// Enclose in parentheses, in case there's a space in the value.
|
||||
if (value.Contains(' '))
|
||||
value = $"({value})";
|
||||
|
||||
paths.Add($"{name}[{value}]");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Array array:
|
||||
{
|
||||
var lowerBounds = Enumerable.Range(0, array.Rank)
|
||||
.Select(i => array.GetLowerBound(i))
|
||||
.ToArray();
|
||||
var upperBounds = Enumerable.Range(0, array.Rank)
|
||||
.Select(i => array.GetUpperBound(i))
|
||||
.ToArray();
|
||||
|
||||
var indices = new int[array.Rank];
|
||||
|
||||
lowerBounds.CopyTo(indices, 0);
|
||||
|
||||
while (true)
|
||||
{
|
||||
paths.Add($"{name}[{string.Join(',', indices)}]");
|
||||
|
||||
var finished = false;
|
||||
|
||||
for (var i = indices.Length - 1; i >= -1; i--)
|
||||
{
|
||||
// When at -1, this means that we've successfully iterated all dimensions of the array.
|
||||
if (i == -1)
|
||||
{
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
|
||||
ref var index = ref indices[i];
|
||||
index += 1;
|
||||
|
||||
if (index > upperBounds[i])
|
||||
{
|
||||
// We've gone over the upper bound, reset index and increase the next dimension's index.
|
||||
index = lowerBounds[i];
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (finished)
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// We handle Array specially instead of using IList here because of multi-dimensional arrays and variable-bounds arrays.
|
||||
case IList list:
|
||||
{
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
paths.Add($"{name}[{i}]");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
namespace Robust.Shared.ViewVariables;
|
||||
|
||||
internal abstract partial class ViewVariablesManager
|
||||
{
|
||||
private static readonly Regex IndexerRegex = new (@"\[[^\[]+\]", RegexOptions.Compiled);
|
||||
private static readonly Regex TypeSpecifierRegex = new (@"\{[^\{]+\}", RegexOptions.Compiled);
|
||||
|
||||
public ViewVariablesPath? ResolvePath(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return null;
|
||||
|
||||
if (path.StartsWith('/'))
|
||||
path = path[1..];
|
||||
|
||||
if (path.EndsWith('/'))
|
||||
path = path[..^1];
|
||||
|
||||
var segments = path.Split('/');
|
||||
|
||||
// Technically, this should never happen... But hey, better be safe than sorry?
|
||||
if (segments.Length == 0)
|
||||
return null;
|
||||
|
||||
var domain = segments[0];
|
||||
|
||||
if (!_registeredDomains.TryGetValue(domain, out var domainData))
|
||||
return null;
|
||||
|
||||
var (newPath, relativePath) = domainData.ResolveObject(string.Join('/', segments[1..]));
|
||||
|
||||
return ResolveRelativePath(newPath, relativePath);
|
||||
}
|
||||
|
||||
private ViewVariablesPath? ResolveRelativePath(ViewVariablesPath? path, string[] segments)
|
||||
{
|
||||
// Who needs recursion, am I right?
|
||||
while (true)
|
||||
{
|
||||
// Empty path, return current path. This can happen as we slowly take away segments from the array.
|
||||
if (segments.Length == 0)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
if (path?.Get() is not {} obj)
|
||||
return null;
|
||||
|
||||
var nextSegment = segments[0];
|
||||
|
||||
if (string.IsNullOrEmpty(nextSegment))
|
||||
{
|
||||
// Let's ignore that...
|
||||
segments = segments[1..];
|
||||
continue;
|
||||
}
|
||||
|
||||
var specifiers = TypeSpecifierRegex.Matches(nextSegment);
|
||||
var indexers = IndexerRegex.Matches(nextSegment);
|
||||
|
||||
var nextSegmentClean = TypeSpecifierRegex.Replace(
|
||||
IndexerRegex.Replace(nextSegment, string.Empty), string.Empty);
|
||||
|
||||
// Yeah, that's not valid bud.
|
||||
if (specifiers.Count > 1)
|
||||
return null;
|
||||
|
||||
VVAccess? access = null;
|
||||
|
||||
if (specifiers.Count == 1 || ResolveTypeHandlers(path, nextSegmentClean) is not {} customPath)
|
||||
{
|
||||
Type? declaringType = null;
|
||||
|
||||
if (specifiers.Count == 1 && _reflectionMan.GetType(specifiers[0].Value[1..^1]) is {} t)
|
||||
{
|
||||
declaringType = t;
|
||||
}
|
||||
|
||||
var memberInfo = obj.GetType().GetSingleMember(nextSegmentClean, declaringType);
|
||||
|
||||
if (memberInfo == null || !ViewVariablesUtility.TryGetViewVariablesAccess(memberInfo, out access))
|
||||
return null;
|
||||
|
||||
path = memberInfo switch
|
||||
{
|
||||
FieldInfo or PropertyInfo => new ViewVariablesFieldOrPropertyPath(obj, memberInfo),
|
||||
MethodInfo methodInfo => new ViewVariablesMethodPath(obj, methodInfo),
|
||||
_ => throw new InvalidOperationException("Invalid member! Must be a property, field or method.")
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
path = customPath;
|
||||
access = VVAccess.ReadWrite;
|
||||
}
|
||||
|
||||
// After this, obj is essentially the parent.
|
||||
|
||||
foreach (Match match in indexers)
|
||||
{
|
||||
path = ResolveIndexing(path, ParseArguments(match.Value[1..^1]), access.Value);
|
||||
}
|
||||
|
||||
segments = segments[1..];
|
||||
}
|
||||
}
|
||||
|
||||
private ViewVariablesPath? ResolveIndexing(ViewVariablesPath? path, string[] arguments, VVAccess access)
|
||||
{
|
||||
if (path?.Get() is not {} obj || arguments.Length == 0)
|
||||
return null;
|
||||
|
||||
var type = obj.GetType();
|
||||
|
||||
// Multidimensional arrays... More like, painful arrays.
|
||||
if (type.IsArray && type.GetArrayRank() > 1)
|
||||
{
|
||||
var getter = type.GetSingleMember("Get") as MethodInfo;
|
||||
var setter = type.GetSingleMember("Set") as MethodInfo;
|
||||
|
||||
if (getter == null && setter == null)
|
||||
return null;
|
||||
|
||||
var p = DeserializeArguments(
|
||||
getter?.GetParameters().Select(p => p.ParameterType).ToArray()
|
||||
?? setter!.GetParameters()[1..].Select(p => p.ParameterType).ToArray(),
|
||||
0, arguments);
|
||||
|
||||
object? Get()
|
||||
{
|
||||
return getter?.Invoke(obj, p);
|
||||
}
|
||||
|
||||
void Set(object? value)
|
||||
{
|
||||
if(p != null && access == VVAccess.ReadWrite)
|
||||
setter?.Invoke(obj, new[] {value}.Concat(p).ToArray());
|
||||
}
|
||||
|
||||
return new ViewVariablesFakePath(Get, Set, null, getter?.ReturnType ?? setter!.GetParameters()[0].ParameterType);
|
||||
}
|
||||
|
||||
// No indexer.
|
||||
if (type.GetIndexer() is not {} indexer)
|
||||
return null;
|
||||
|
||||
var parametersInfo = indexer.GetIndexParameters();
|
||||
|
||||
var parameters = DeserializeArguments(
|
||||
parametersInfo.Select(p => p.ParameterType).ToArray(),
|
||||
parametersInfo.Count(p => p.IsOptional),
|
||||
arguments);
|
||||
|
||||
if (parameters == null)
|
||||
return null;
|
||||
|
||||
return new ViewVariablesIndexedPath(obj, indexer, parameters, access);
|
||||
}
|
||||
|
||||
private ViewVariablesPath? ResolveTypeHandlers(ViewVariablesPath path, string relativePath)
|
||||
{
|
||||
if (path.Get() is not {} obj
|
||||
|| string.IsNullOrEmpty(relativePath)
|
||||
|| relativePath.Contains('/'))
|
||||
return null;
|
||||
|
||||
var origType = obj.GetType();
|
||||
var type = origType;
|
||||
|
||||
// First go through the inheritance chain, from current type to base types...
|
||||
while (type != null)
|
||||
{
|
||||
if (_typeHandlers.TryGetValue(type, out var data))
|
||||
{
|
||||
var newPath = data.HandlePath(path, relativePath);
|
||||
|
||||
if (newPath != null)
|
||||
return newPath;
|
||||
}
|
||||
|
||||
type = type.BaseType;
|
||||
}
|
||||
|
||||
// Then go through all the implemented interfaces, if any.
|
||||
foreach (var interfaceType in origType.GetInterfaces())
|
||||
{
|
||||
if (!_typeHandlers.TryGetValue(interfaceType, out var data))
|
||||
continue;
|
||||
|
||||
if (data.HandlePath(path, relativePath) is {} newPath)
|
||||
return newPath;
|
||||
}
|
||||
|
||||
// Not handled by a custom type handler!
|
||||
return null;
|
||||
}
|
||||
}
|
||||
393
Robust.Shared/ViewVariables/ViewVariablesManager.Remote.cs
Normal file
393
Robust.Shared/ViewVariables/ViewVariablesManager.Remote.cs
Normal file
@@ -0,0 +1,393 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Security.AccessControl;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
namespace Robust.Shared.ViewVariables;
|
||||
|
||||
internal abstract partial class ViewVariablesManager
|
||||
{
|
||||
internal const int MaxListPathResponseLength = 500;
|
||||
|
||||
private uint _nextReadRequestId = 0;
|
||||
private uint _nextWriteRequestId = 0;
|
||||
private uint _nextInvokeRequestId = 0;
|
||||
private uint _nextListRequestId = 0;
|
||||
|
||||
private readonly Dictionary<uint, TaskCompletionSource<string?>> _readRequests = new();
|
||||
private readonly Dictionary<uint, TaskCompletionSource> _writeRequests = new();
|
||||
private readonly Dictionary<uint, TaskCompletionSource<string?>> _invokeRequests = new();
|
||||
private readonly Dictionary<uint, TaskCompletionSource<IEnumerable<string>>> _listRequests = new();
|
||||
|
||||
private void InitializeRemote()
|
||||
{
|
||||
_netMan.RegisterNetMessage<MsgViewVariablesReadPathReq>(ReadRemotePathRequest);
|
||||
_netMan.RegisterNetMessage<MsgViewVariablesWritePathReq>(WriteRemotePathRequest);
|
||||
_netMan.RegisterNetMessage<MsgViewVariablesInvokePathReq>(InvokeRemotePathRequest);
|
||||
_netMan.RegisterNetMessage<MsgViewVariablesListPathReq>(ListRemotePathRequest);
|
||||
|
||||
_netMan.RegisterNetMessage<MsgViewVariablesReadPathRes>(ReadRemotePathResponse);
|
||||
_netMan.RegisterNetMessage<MsgViewVariablesWritePathRes>(WriteRemotePathResponse);
|
||||
_netMan.RegisterNetMessage<MsgViewVariablesInvokePathRes>(InvokeRemotePathResponse);
|
||||
_netMan.RegisterNetMessage<MsgViewVariablesListPathRes>(ListRemotePathResponse);
|
||||
}
|
||||
|
||||
public Task<string?> ReadRemotePath(string path, ICommonSession? session = null)
|
||||
{
|
||||
if (!_netMan.IsConnected || (_netMan.IsServer && session == null))
|
||||
return Task.FromResult<string?>(null);
|
||||
|
||||
var msg = new MsgViewVariablesReadPathReq()
|
||||
{
|
||||
RequestId = unchecked(_nextReadRequestId++),
|
||||
Path = path,
|
||||
Session = session?.UserId ?? Guid.Empty,
|
||||
};
|
||||
|
||||
var tsc = new TaskCompletionSource<string?>();
|
||||
_readRequests.Add(msg.RequestId, tsc);
|
||||
|
||||
SendMessage(msg, session?.ConnectedClient);
|
||||
return tsc.Task;
|
||||
}
|
||||
|
||||
public Task WriteRemotePath(string path, string value, ICommonSession? session = null)
|
||||
{
|
||||
if (!_netMan.IsConnected || (_netMan.IsServer && session == null))
|
||||
return Task.CompletedTask;
|
||||
|
||||
var msg = new MsgViewVariablesWritePathReq()
|
||||
{
|
||||
RequestId = unchecked(_nextWriteRequestId++),
|
||||
Path = path,
|
||||
Value = value,
|
||||
Session = session?.UserId ?? Guid.Empty,
|
||||
};
|
||||
|
||||
var tsc = new TaskCompletionSource();
|
||||
_writeRequests.Add(msg.RequestId, tsc);
|
||||
|
||||
SendMessage(msg, session?.ConnectedClient);
|
||||
return tsc.Task;
|
||||
}
|
||||
|
||||
public Task<string?> InvokeRemotePath(string path, string arguments, ICommonSession? session = null)
|
||||
{
|
||||
if (!_netMan.IsConnected || (_netMan.IsServer && session == null))
|
||||
return Task.FromResult<string?>(null);
|
||||
|
||||
var msg = new MsgViewVariablesInvokePathReq()
|
||||
{
|
||||
RequestId = unchecked(_nextInvokeRequestId++),
|
||||
Path = path,
|
||||
Value = arguments,
|
||||
Session = session?.UserId ?? Guid.Empty,
|
||||
};
|
||||
|
||||
var tsc = new TaskCompletionSource<string?>();
|
||||
_invokeRequests.Add(msg.RequestId, tsc);
|
||||
|
||||
SendMessage(msg, session?.ConnectedClient);
|
||||
return tsc.Task;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<string>> ListRemotePath(string path, VVListPathOptions options, ICommonSession? session = null)
|
||||
{
|
||||
if (!_netMan.IsConnected || (_netMan.IsServer && session == null))
|
||||
return Task.FromResult(Enumerable.Empty<string>());
|
||||
|
||||
var msg = new MsgViewVariablesListPathReq()
|
||||
{
|
||||
RequestId = unchecked(_nextListRequestId++),
|
||||
Path = path,
|
||||
Options = options,
|
||||
Session = session?.UserId ?? Guid.Empty,
|
||||
};
|
||||
|
||||
var tsc = new TaskCompletionSource<IEnumerable<string>>();
|
||||
_listRequests.Add(msg.RequestId, tsc);
|
||||
|
||||
SendMessage(msg, session?.ConnectedClient);
|
||||
return tsc.Task;
|
||||
}
|
||||
|
||||
private async void ReadRemotePathRequest(MsgViewVariablesReadPathReq req)
|
||||
{
|
||||
if (!CheckPermissions(req.MsgChannel))
|
||||
{
|
||||
SendMessage(new MsgViewVariablesReadPathRes(req)
|
||||
{
|
||||
ResponseCode = ViewVariablesResponseCode.NoAccess,
|
||||
}, req.MsgChannel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_netMan.IsServer && TryGetSession(req.Session, out var session))
|
||||
{
|
||||
var value = await ReadRemotePath(req.Path, session);
|
||||
SendMessage(new MsgViewVariablesReadPathRes(req)
|
||||
{
|
||||
Response = new []{value ?? "null"}
|
||||
}, req.MsgChannel);
|
||||
return;
|
||||
}
|
||||
|
||||
var obj = ReadPath(req.Path);
|
||||
|
||||
if (obj == null)
|
||||
{
|
||||
SendMessage(new MsgViewVariablesReadPathRes(req)
|
||||
{
|
||||
ResponseCode = ViewVariablesResponseCode.NoObject,
|
||||
}, req.MsgChannel);
|
||||
return;
|
||||
}
|
||||
|
||||
string val;
|
||||
|
||||
try
|
||||
{
|
||||
val = SerializeValue(obj.GetType(), obj) ?? obj.ToString() ?? "null";
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
val = obj.ToString() ?? "null";
|
||||
}
|
||||
|
||||
SendMessage(new MsgViewVariablesReadPathRes(req)
|
||||
{
|
||||
Response = new []{ val }
|
||||
}, req.MsgChannel);
|
||||
}
|
||||
|
||||
private async void WriteRemotePathRequest(MsgViewVariablesWritePathReq req)
|
||||
{
|
||||
if (!CheckPermissions(req.MsgChannel))
|
||||
{
|
||||
_netMan.ServerSendMessage(new MsgViewVariablesWritePathRes(req)
|
||||
{
|
||||
ResponseCode = ViewVariablesResponseCode.NoAccess,
|
||||
}, req.MsgChannel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_netMan.IsServer && TryGetSession(req.Session, out var session))
|
||||
{
|
||||
await WriteRemotePath(req.Path, req.Value ?? string.Empty, session);
|
||||
SendMessage(new MsgViewVariablesWritePathRes(req), req.MsgChannel);
|
||||
return;
|
||||
}
|
||||
|
||||
var path = ResolvePath(req.Path);
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
SendMessage(new MsgViewVariablesWritePathRes(req)
|
||||
{
|
||||
ResponseCode = ViewVariablesResponseCode.NoObject,
|
||||
}, req.MsgChannel);
|
||||
return;
|
||||
}
|
||||
|
||||
var value = req.Value != null ? DeserializeValue(path.Type, req.Value) : null;
|
||||
|
||||
try
|
||||
{
|
||||
path.Set(value);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
SendMessage(new MsgViewVariablesWritePathRes(req)
|
||||
{
|
||||
ResponseCode = ViewVariablesResponseCode.InvalidRequest,
|
||||
}, req.MsgChannel);
|
||||
return;
|
||||
}
|
||||
|
||||
SendMessage(new MsgViewVariablesWritePathRes(req), req.MsgChannel);
|
||||
}
|
||||
|
||||
private async void InvokeRemotePathRequest(MsgViewVariablesInvokePathReq req)
|
||||
{
|
||||
if (!CheckPermissions(req.MsgChannel))
|
||||
{
|
||||
_netMan.ServerSendMessage(new MsgViewVariablesInvokePathRes(req)
|
||||
{
|
||||
Path = req.Path, ResponseCode = ViewVariablesResponseCode.NoAccess,
|
||||
}, req.MsgChannel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_netMan.IsServer && TryGetSession(req.Session, out var session))
|
||||
{
|
||||
var retVal = await InvokeRemotePath(req.Path, req.Value ?? string.Empty, session);
|
||||
SendMessage(new MsgViewVariablesInvokePathRes(req)
|
||||
{
|
||||
Response = new []{retVal ?? "null"}
|
||||
}, req.MsgChannel);
|
||||
return;
|
||||
}
|
||||
|
||||
var path = ResolvePath(req.Path);
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
SendMessage(new MsgViewVariablesInvokePathRes(req)
|
||||
{
|
||||
ResponseCode = ViewVariablesResponseCode.NoObject,
|
||||
}, req.MsgChannel);
|
||||
return;
|
||||
}
|
||||
|
||||
var args = req.Value != null ? ParseArguments(req.Value) : Array.Empty<string>();
|
||||
var desArgs = DeserializeArguments(path.InvokeParameterTypes, (int)path.InvokeOptionalParameters, args);
|
||||
object? value;
|
||||
|
||||
try
|
||||
{
|
||||
value = path.Invoke(desArgs);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
SendMessage(new MsgViewVariablesInvokePathRes(req)
|
||||
{
|
||||
ResponseCode = ViewVariablesResponseCode.InvalidRequest,
|
||||
}, req.MsgChannel);
|
||||
return;
|
||||
}
|
||||
|
||||
string val;
|
||||
|
||||
try
|
||||
{
|
||||
val = SerializeValue(path.InvokeReturnType, value) ?? value?.ToString() ?? "null";
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
val = value?.ToString() ?? "null";
|
||||
}
|
||||
|
||||
SendMessage(new MsgViewVariablesInvokePathRes(req)
|
||||
{
|
||||
Response = new []{val},
|
||||
}, req.MsgChannel);
|
||||
}
|
||||
|
||||
private async void ListRemotePathRequest(MsgViewVariablesListPathReq req)
|
||||
{
|
||||
if (!CheckPermissions(req.MsgChannel))
|
||||
{
|
||||
_netMan.ServerSendMessage(new MsgViewVariablesListPathRes(req)
|
||||
{
|
||||
ResponseCode = ViewVariablesResponseCode.NoAccess,
|
||||
}, req.MsgChannel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_netMan.IsServer && TryGetSession(req.Session, out var session))
|
||||
{
|
||||
var response = await ListRemotePath(req.Path, req.Options, session);
|
||||
SendMessage(new MsgViewVariablesListPathRes(req)
|
||||
{
|
||||
Response = response.ToArray(),
|
||||
}, req.MsgChannel);
|
||||
return;
|
||||
}
|
||||
|
||||
var enumerable = ListPath(req.Path, req.Options)
|
||||
.OrderByDescending(p => p.StartsWith(req.Path))
|
||||
.Take(Math.Min(MaxListPathResponseLength, req.Options.RemoteListLength))
|
||||
.ToArray();
|
||||
|
||||
SendMessage(new MsgViewVariablesListPathRes(req)
|
||||
{
|
||||
Response = enumerable,
|
||||
}, req.MsgChannel);
|
||||
}
|
||||
|
||||
private void ReadRemotePathResponse(MsgViewVariablesReadPathRes res)
|
||||
{
|
||||
if (!_readRequests.Remove(res.RequestId, out var tsc))
|
||||
return;
|
||||
|
||||
if (res.ResponseCode != ViewVariablesResponseCode.Ok)
|
||||
{
|
||||
tsc.TrySetResult(null); // TODO: Use exceptions
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.Response.Length == 0)
|
||||
{
|
||||
tsc.TrySetResult(null);
|
||||
return;
|
||||
}
|
||||
|
||||
tsc.TrySetResult(res.Response[0]);
|
||||
}
|
||||
|
||||
private void WriteRemotePathResponse(MsgViewVariablesWritePathRes res)
|
||||
{
|
||||
if (!_writeRequests.Remove(res.RequestId, out var tsc))
|
||||
return;
|
||||
|
||||
// TODO: Use exceptions
|
||||
tsc.SetResult();
|
||||
}
|
||||
|
||||
private void InvokeRemotePathResponse(MsgViewVariablesInvokePathRes res)
|
||||
{
|
||||
if (!_invokeRequests.Remove(res.RequestId, out var tsc))
|
||||
return;
|
||||
|
||||
if (res.ResponseCode != ViewVariablesResponseCode.Ok)
|
||||
{
|
||||
tsc.TrySetResult(null); // TODO: Use exceptions
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.Response.Length == 0)
|
||||
{
|
||||
tsc.TrySetResult(null);
|
||||
return;
|
||||
}
|
||||
|
||||
tsc.TrySetResult(res.Response[0]);
|
||||
}
|
||||
|
||||
private void ListRemotePathResponse(MsgViewVariablesListPathRes res)
|
||||
{
|
||||
if (!_listRequests.Remove(res.RequestId, out var tsc))
|
||||
return;
|
||||
|
||||
if (res.ResponseCode != ViewVariablesResponseCode.Ok)
|
||||
{
|
||||
tsc.TrySetResult(Enumerable.Empty<string>()); // TODO: Use exceptions
|
||||
return;
|
||||
}
|
||||
|
||||
tsc.TrySetResult(res.Response);
|
||||
}
|
||||
|
||||
private void SendMessage(NetMessage msg, INetChannel? channel = null)
|
||||
{
|
||||
// I'm surprised this isn't a method in INetManager already...
|
||||
if (_netMan.IsServer)
|
||||
{
|
||||
if (channel == null)
|
||||
throw new ArgumentNullException(nameof(channel));
|
||||
|
||||
_netMan.ServerSendMessage(msg, channel);
|
||||
}
|
||||
else
|
||||
{
|
||||
_netMan.ClientSendMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract bool CheckPermissions(INetChannel channel);
|
||||
protected abstract bool TryGetSession(Guid guid, [NotNullWhen(true)] out ICommonSession? session);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Shared.ViewVariables;
|
||||
|
||||
internal abstract partial class ViewVariablesManager
|
||||
{
|
||||
private static string[] ParseArguments(string arguments)
|
||||
{
|
||||
var args = new List<string>();
|
||||
var parentheses = false;
|
||||
var builder = new StringBuilder();
|
||||
var i = 0;
|
||||
|
||||
while (i < arguments.Length)
|
||||
{
|
||||
var current = arguments[i];
|
||||
switch (current)
|
||||
{
|
||||
case '(':
|
||||
parentheses = true;
|
||||
break;
|
||||
case ')' when parentheses:
|
||||
parentheses = false;
|
||||
break;
|
||||
case ',' when !parentheses:
|
||||
args.Add(builder.ToString());
|
||||
builder.Clear();
|
||||
break;
|
||||
default:
|
||||
if (!parentheses && char.IsWhiteSpace(current))
|
||||
break;
|
||||
builder.Append(current);
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if(builder.Length != 0)
|
||||
args.Add(builder.ToString());
|
||||
|
||||
return args.ToArray();
|
||||
}
|
||||
|
||||
private object?[]? DeserializeArguments(Type[] argumentTypes, int optionalArguments, string[] arguments)
|
||||
{
|
||||
// Incorrect number of arguments!
|
||||
if (arguments.Length < argumentTypes.Length - optionalArguments || arguments.Length > argumentTypes.Length)
|
||||
return null;
|
||||
|
||||
var parameters = new List<object?>();
|
||||
|
||||
for (var i = 0; i < arguments.Length; i++)
|
||||
{
|
||||
var argument = arguments[i];
|
||||
var type = argumentTypes[i];
|
||||
|
||||
var value = DeserializeValue(type, argument);
|
||||
|
||||
parameters.Add(value);
|
||||
}
|
||||
|
||||
for (var i = 0; i < argumentTypes.Length - arguments.Length; i++)
|
||||
{
|
||||
parameters.Add(Type.Missing);
|
||||
}
|
||||
|
||||
return parameters.ToArray();
|
||||
}
|
||||
|
||||
private object? DeserializeValue(Type type, string value)
|
||||
{
|
||||
// Check if the argument is a VV path, and if not, deserialize the value with serv3.
|
||||
if (ResolvePath(value)?.Get() is {} resolved && resolved.GetType().IsAssignableTo(type))
|
||||
return resolved;
|
||||
|
||||
try
|
||||
{
|
||||
// Here we go serialization moment
|
||||
using TextReader stream = new StringReader(value);
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(stream);
|
||||
var document = yamlStream.Documents[0];
|
||||
var rootNode = document.RootNode;
|
||||
return _serMan.Read(type, rootNode.ToDataNode());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string? SerializeValue(Type type, object? value, string? nodeTag = null)
|
||||
{
|
||||
if (value == null || type == typeof(void))
|
||||
return null;
|
||||
|
||||
var objType = type;
|
||||
|
||||
var node = _serMan.WriteValue(type, value);
|
||||
|
||||
// Don't replace an existing tag if it's null.
|
||||
if(!string.IsNullOrEmpty(nodeTag))
|
||||
node.Tag = nodeTag;
|
||||
|
||||
var document = new YamlDocument(node.ToYamlNode());
|
||||
var stream = new YamlStream {document};
|
||||
|
||||
using var writer = new StringWriter(new StringBuilder());
|
||||
|
||||
// Remove the three funny dots from the end of the string...
|
||||
stream.Save(new YamlNoDocEndDotsFix(new YamlMappingFix(new Emitter(writer))), false);
|
||||
return writer.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.ViewVariables;
|
||||
|
||||
internal abstract partial class ViewVariablesManager
|
||||
{
|
||||
private readonly Dictionary<Type, ViewVariablesTypeHandler> _typeHandlers = new();
|
||||
|
||||
public ViewVariablesTypeHandler<T> GetTypeHandler<T>()
|
||||
{
|
||||
if (_typeHandlers.TryGetValue(typeof(T), out var h))
|
||||
return (ViewVariablesTypeHandler<T>)h;
|
||||
|
||||
var handler = new ViewVariablesTypeHandler<T>();
|
||||
_typeHandlers.Add(typeof(T), handler);
|
||||
return handler;
|
||||
}
|
||||
|
||||
private void InitializeTypeHandlers()
|
||||
{
|
||||
GetTypeHandler<EntityUid>()
|
||||
.AddHandler(EntityComponentHandler, EntityComponentList)
|
||||
.AddPath("Delete", uid => ViewVariablesPath.FromInvoker(_ => _entMan.DeleteEntity(uid)))
|
||||
.AddPath("QueueDelete", uid => ViewVariablesPath.FromInvoker(_ => _entMan.QueueDeleteEntity(uid)));
|
||||
}
|
||||
|
||||
private ViewVariablesPath? EntityComponentHandler(EntityUid uid, string relativePath)
|
||||
{
|
||||
if (!_entMan.EntityExists(uid)
|
||||
|| !_compFact.TryGetRegistration(relativePath, out var registration, true)
|
||||
|| !_entMan.TryGetComponent(uid, registration.Idx, out var component))
|
||||
return null;
|
||||
|
||||
return new ViewVariablesComponentPath(component, uid);
|
||||
}
|
||||
|
||||
private IEnumerable<string> EntityComponentList(EntityUid uid)
|
||||
{
|
||||
return _entMan.GetComponents(uid)
|
||||
.Select(component => _compFact.GetComponentName(component.GetType()));
|
||||
}
|
||||
}
|
||||
89
Robust.Shared/ViewVariables/ViewVariablesManager.cs
Normal file
89
Robust.Shared/ViewVariables/ViewVariablesManager.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
|
||||
namespace Robust.Shared.ViewVariables;
|
||||
|
||||
internal abstract partial class ViewVariablesManager : IViewVariablesManager
|
||||
{
|
||||
[Dependency] private readonly ISerializationManager _serMan = default!;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly IComponentFactory _compFact = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionMan = default!;
|
||||
[Dependency] private readonly INetManager _netMan = default!;
|
||||
|
||||
private readonly Dictionary<Type, HashSet<object>> _cachedTraits = new();
|
||||
|
||||
private const BindingFlags MembersBindings =
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
|
||||
public virtual void Initialize()
|
||||
{
|
||||
InitializeDomains();
|
||||
InitializeTypeHandlers();
|
||||
InitializeRemote();
|
||||
}
|
||||
|
||||
public object? ReadPath(string path)
|
||||
{
|
||||
return ResolvePath(path)?.Get();
|
||||
}
|
||||
|
||||
public void WritePath(string path, string value)
|
||||
{
|
||||
var resPath = ResolvePath(path);
|
||||
resPath?.Set(DeserializeValue(resPath.Type, value));
|
||||
}
|
||||
|
||||
public object? InvokePath(string path, string arguments)
|
||||
{
|
||||
var resPath = ResolvePath(path);
|
||||
|
||||
if (resPath == null)
|
||||
return null;
|
||||
|
||||
var args = ParseArguments(arguments);
|
||||
|
||||
var desArgs =
|
||||
DeserializeArguments(resPath.InvokeParameterTypes, (int)resPath.InvokeOptionalParameters, args);
|
||||
|
||||
return resPath.Invoke(desArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Figures out which VV traits an object type has. This method is in shared so the client and server agree on this mess.
|
||||
/// </summary>
|
||||
/// <seealso cref="ViewVariablesBlobMetadata.Traits"/>
|
||||
public ICollection<object> TraitIdsFor(Type type)
|
||||
{
|
||||
if (!_cachedTraits.TryGetValue(type, out var traits))
|
||||
{
|
||||
traits = new HashSet<object>();
|
||||
_cachedTraits.Add(type, traits);
|
||||
if (ViewVariablesUtility.TypeHasVisibleMembers(type))
|
||||
{
|
||||
traits.Add(ViewVariablesTraits.Members);
|
||||
}
|
||||
|
||||
if (typeof(IEnumerable).IsAssignableFrom(type))
|
||||
{
|
||||
traits.Add(ViewVariablesTraits.Enumerable);
|
||||
}
|
||||
|
||||
if (typeof(EntityUid).IsAssignableFrom(type))
|
||||
{
|
||||
traits.Add(ViewVariablesTraits.Entity);
|
||||
}
|
||||
}
|
||||
|
||||
return traits;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.ViewVariables
|
||||
{
|
||||
internal abstract class ViewVariablesManagerShared
|
||||
{
|
||||
private readonly Dictionary<Type, HashSet<object>> _cachedTraits = new();
|
||||
|
||||
/// <summary>
|
||||
/// Figures out which VV traits an object type has. This method is in shared so the client and server agree on this mess.
|
||||
/// </summary>
|
||||
/// <seealso cref="ViewVariablesBlobMetadata.Traits"/>
|
||||
public ICollection<object> TraitIdsFor(Type type)
|
||||
{
|
||||
if (!_cachedTraits.TryGetValue(type, out var traits))
|
||||
{
|
||||
traits = new HashSet<object>();
|
||||
_cachedTraits.Add(type, traits);
|
||||
if (ViewVariablesUtility.TypeHasVisibleMembers(type))
|
||||
{
|
||||
traits.Add(ViewVariablesTraits.Members);
|
||||
}
|
||||
|
||||
if (typeof(IEnumerable).IsAssignableFrom(type))
|
||||
{
|
||||
traits.Add(ViewVariablesTraits.Enumerable);
|
||||
}
|
||||
|
||||
if (typeof(EntityUid).IsAssignableFrom(type))
|
||||
{
|
||||
traits.Add(ViewVariablesTraits.Entity);
|
||||
}
|
||||
}
|
||||
|
||||
return traits;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,4 +96,15 @@ namespace Robust.Shared.ViewVariables
|
||||
|
||||
public string TypeName { get; }
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ViewVariablesPathSelector : ViewVariablesObjectSelector
|
||||
{
|
||||
public ViewVariablesPathSelector(string path)
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
}
|
||||
}
|
||||
|
||||
324
Robust.Shared/ViewVariables/ViewVariablesPath.cs
Normal file
324
Robust.Shared/ViewVariables/ViewVariablesPath.cs
Normal file
@@ -0,0 +1,324 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
namespace Robust.Shared.ViewVariables;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a ViewVariables path. Allows you to "Get", "Set" or "Invoke" the path.
|
||||
/// </summary>
|
||||
[Virtual]
|
||||
public abstract class ViewVariablesPath
|
||||
{
|
||||
/// <summary>
|
||||
/// The type that is both returned by the <see cref="Get"/> method and used by the <see cref="Set"/> method.
|
||||
/// </summary>
|
||||
public abstract Type Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the path, if possible.
|
||||
/// </summary>
|
||||
/// <returns>The value of the path, or null. Same type as <see cref="Type"/>.</returns>
|
||||
public abstract object? Get();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the path, if possible.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value to set the path to. Must be of the same type as <see cref="Type"/>.</param>
|
||||
public abstract void Set(object? value);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the path, if possible.
|
||||
/// </summary>
|
||||
/// <param name="parameters">The parameters that the function takes.</param>
|
||||
/// <returns>The object returned by invoking the function, or null.</returns>
|
||||
public abstract object? Invoke(object?[]? parameters);
|
||||
|
||||
/// <summary>
|
||||
/// The types of all parameters in the <see cref="Invoke"/> method.
|
||||
/// </summary>
|
||||
/// <seealso cref="InvokeOptionalParameters"/>
|
||||
public virtual Type[] InvokeParameterTypes { get; } = Array.Empty<Type>();
|
||||
|
||||
/// <summary>
|
||||
/// The number of optional parameters in the <see cref="Invoke"/> method, starting from the end of the array.
|
||||
/// </summary>
|
||||
/// <seealso cref="InvokeParameterTypes"/>
|
||||
public virtual uint InvokeOptionalParameters { get; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The type of the object returned by the <see cref="Invoke"/> method, or <see cref="Void"/> if none.
|
||||
/// </summary>
|
||||
public virtual Type InvokeReturnType { get; } = typeof(void);
|
||||
|
||||
#region Static helper methods
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ViewVariablesFakePath"/> given an object.
|
||||
/// </summary>
|
||||
public static ViewVariablesFakePath FromObject(object obj)
|
||||
=> new(() => obj, null, null, obj.GetType());
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ViewVariablesFakePath"/> given a getter function.
|
||||
/// </summary>
|
||||
public static ViewVariablesFakePath FromGetter(Func<object?> getter, Type type)
|
||||
=> new(getter, null, null, type);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ViewVariablesFakePath"/> given a setter function.
|
||||
/// </summary>
|
||||
public static ViewVariablesFakePath FromSetter(Action<object?> setter, Type type)
|
||||
=> new(null, setter, null, type);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ViewVariablesFakePath"/> given a function to be invoked.
|
||||
/// </summary>
|
||||
public static ViewVariablesFakePath FromInvoker(Func<object?, object?> invoker,
|
||||
Type[]? invokeParameterTypes = null, uint invokeOptionalParameters = 0, Type? invokeReturnType = null)
|
||||
=> new(null, null, invoker, null, invokeParameterTypes, invokeOptionalParameters, invokeReturnType);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ViewVariablesFakePath"/> given a function to be invoked.
|
||||
/// </summary>
|
||||
public static ViewVariablesFakePath FromInvoker(Action<object?> invoker,
|
||||
Type[]? invokeParameterTypes = null, uint invokeOptionalParameters = 0, Type? invokeReturnType = null)
|
||||
=> new(null, null, invoker, null, invokeParameterTypes, invokeOptionalParameters, invokeReturnType);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal sealed class ViewVariablesFieldOrPropertyPath : ViewVariablesPath
|
||||
{
|
||||
internal ViewVariablesFieldOrPropertyPath(object? obj, MemberInfo member)
|
||||
{
|
||||
if (member is not (FieldInfo or PropertyInfo))
|
||||
throw new ArgumentException("Member must be either a field or a property!", nameof(member));
|
||||
|
||||
_object = obj;
|
||||
_member = member;
|
||||
ViewVariablesUtility.TryGetViewVariablesAccess(member, out _access);
|
||||
}
|
||||
|
||||
private readonly object? _object;
|
||||
private readonly MemberInfo _member;
|
||||
private readonly VVAccess? _access;
|
||||
public override Type Type => _member.GetUnderlyingType();
|
||||
|
||||
public override object? Get()
|
||||
{
|
||||
if (_access == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return _object != null
|
||||
? _member.GetValue(_object)
|
||||
: null;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Set(object? value)
|
||||
{
|
||||
if (_access != VVAccess.ReadWrite)
|
||||
return;
|
||||
|
||||
if (_object != null)
|
||||
_member.SetValue(_object, value);
|
||||
}
|
||||
|
||||
public override object? Invoke(object?[]? parameters) => null;
|
||||
}
|
||||
|
||||
internal sealed class ViewVariablesMethodPath : ViewVariablesPath
|
||||
{
|
||||
internal ViewVariablesMethodPath(object? obj, MethodInfo method)
|
||||
{
|
||||
_object = obj;
|
||||
_method = method;
|
||||
ViewVariablesUtility.TryGetViewVariablesAccess(method, out _access);
|
||||
}
|
||||
|
||||
private readonly object? _object;
|
||||
private readonly MethodInfo _method;
|
||||
private readonly VVAccess? _access;
|
||||
public override Type Type => typeof(void);
|
||||
public override Type InvokeReturnType => _method.ReturnType;
|
||||
|
||||
public override object? Get() => null;
|
||||
|
||||
public override void Set(object? value)
|
||||
{
|
||||
}
|
||||
|
||||
public override object? Invoke(object?[]? parameters)
|
||||
{
|
||||
if (_access != VVAccess.ReadWrite)
|
||||
return null;
|
||||
|
||||
return _object != null
|
||||
? _method.Invoke(_object, parameters)
|
||||
: null;
|
||||
}
|
||||
|
||||
public override Type[] InvokeParameterTypes
|
||||
=> _access == VVAccess.ReadWrite
|
||||
? _method.GetParameters().Select(info => info.ParameterType).ToArray()
|
||||
: Array.Empty<Type>();
|
||||
public override uint InvokeOptionalParameters
|
||||
=> _access == VVAccess.ReadWrite
|
||||
? (uint) _method.GetParameters().Count(info => info.IsOptional)
|
||||
: 0;
|
||||
}
|
||||
|
||||
internal sealed class ViewVariablesIndexedPath : ViewVariablesPath
|
||||
{
|
||||
internal ViewVariablesIndexedPath(object? obj, PropertyInfo indexer, object?[] index, VVAccess? parentAccess)
|
||||
{
|
||||
if (indexer.GetIndexParameters().Length == 0)
|
||||
throw new ArgumentException("PropertyInfo is not an indexer!", nameof(indexer));
|
||||
|
||||
_object = obj;
|
||||
_indexer = indexer;
|
||||
_index = index;
|
||||
_access = parentAccess;
|
||||
}
|
||||
|
||||
private readonly object? _object;
|
||||
private readonly PropertyInfo _indexer;
|
||||
private readonly object?[] _index;
|
||||
private readonly VVAccess? _access;
|
||||
public override Type Type => _indexer.GetUnderlyingType();
|
||||
|
||||
public override object? Get()
|
||||
{
|
||||
if (_access == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return _object != null
|
||||
? _indexer.GetValue(_object, _index)
|
||||
: null;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Set(object? value)
|
||||
{
|
||||
if(_access == VVAccess.ReadWrite && _object != null)
|
||||
_indexer.SetValue(_object, value, _index);
|
||||
}
|
||||
|
||||
public override object? Invoke(object?[]? parameters) => null;
|
||||
}
|
||||
|
||||
public sealed class ViewVariablesInstancePath : ViewVariablesPath
|
||||
{
|
||||
public ViewVariablesInstancePath(object? obj)
|
||||
{
|
||||
_object = obj;
|
||||
}
|
||||
|
||||
private readonly object? _object;
|
||||
|
||||
public override Type Type => _object?.GetType() ?? typeof(void);
|
||||
|
||||
public override object? Get() => _object;
|
||||
|
||||
public override void Set(object? value)
|
||||
{
|
||||
}
|
||||
|
||||
public override object? Invoke(object?[]? parameters) => null;
|
||||
}
|
||||
|
||||
public sealed class ViewVariablesComponentPath : ViewVariablesPath
|
||||
{
|
||||
public readonly object? Component;
|
||||
public readonly EntityUid Owner;
|
||||
public override Type Type => Component?.GetType() ?? typeof(void);
|
||||
|
||||
public ViewVariablesComponentPath(object? component, EntityUid owner)
|
||||
{
|
||||
Component = component;
|
||||
Owner = owner;
|
||||
}
|
||||
|
||||
public override object? Get()
|
||||
{
|
||||
return Component;
|
||||
}
|
||||
|
||||
public override void Set(object? value) { }
|
||||
public override object? Invoke(object?[]? parameters) => null;
|
||||
}
|
||||
|
||||
public sealed class ViewVariablesFakePath : ViewVariablesPath
|
||||
{
|
||||
public ViewVariablesFakePath(Func<object?>? getter, Action<object?>? setter, Func<object?, object?>? invoker = null,
|
||||
Type? type = null, Type[]? invokeParameterTypes = null, uint invokeOptionalParameters = 0, Type? invokeReturnType = null)
|
||||
{
|
||||
_getter = getter;
|
||||
_setter = setter;
|
||||
_invoker = invoker;
|
||||
Type = type ?? typeof(void);
|
||||
InvokeParameterTypes = invokeParameterTypes ?? Array.Empty<Type>();
|
||||
InvokeOptionalParameters = invokeOptionalParameters;
|
||||
InvokeReturnType = invokeReturnType ?? typeof(void);
|
||||
}
|
||||
|
||||
public ViewVariablesFakePath(Func<object?>? getter, Action<object?>? setter, Action<object?> invoker,
|
||||
Type? type = null, Type[]? invokeParameterTypes = null, uint invokeOptionalParameters = 0, Type? invokeReturnType = null)
|
||||
: this(getter, setter, null, type, invokeParameterTypes, invokeOptionalParameters, invokeReturnType)
|
||||
{
|
||||
_invoker = p =>
|
||||
{
|
||||
invoker(p);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
private readonly Func<object?>? _getter;
|
||||
private readonly Action<object?>? _setter;
|
||||
private readonly Func<object?, object?>? _invoker;
|
||||
public override Type Type { get; }
|
||||
public override Type[] InvokeParameterTypes { get; }
|
||||
public override uint InvokeOptionalParameters { get; }
|
||||
public override Type InvokeReturnType { get; }
|
||||
|
||||
public override object? Get()
|
||||
{
|
||||
return _getter?.Invoke();
|
||||
}
|
||||
|
||||
public override void Set(object? value)
|
||||
{
|
||||
_setter?.Invoke(value);
|
||||
}
|
||||
|
||||
public override object? Invoke(object?[]? parameters)
|
||||
{
|
||||
return _invoker?.Invoke(parameters);
|
||||
}
|
||||
|
||||
public ViewVariablesFakePath WithGetter(Func<object?> getter, Type? type = null)
|
||||
=> new(getter, _setter, _invoker, type ?? Type, InvokeParameterTypes, InvokeOptionalParameters, InvokeReturnType);
|
||||
|
||||
public ViewVariablesFakePath WithSetter(Action<object?> setter, Type? type = null)
|
||||
=> new(_getter, setter, _invoker, type ?? Type, InvokeParameterTypes, InvokeOptionalParameters, InvokeReturnType);
|
||||
|
||||
public ViewVariablesFakePath WithInvoker(Func<object?, object?> invoker,
|
||||
Type[]? invokeParameterTypes = null, uint invokeOptionalParameters = 0, Type? invokeReturnType = null)
|
||||
=> new(_getter, _setter, invoker, Type, invokeParameterTypes, invokeOptionalParameters,
|
||||
invokeReturnType);
|
||||
}
|
||||
24
Robust.Shared/ViewVariables/ViewVariablesResponseCode.cs
Normal file
24
Robust.Shared/ViewVariables/ViewVariablesResponseCode.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace Robust.Shared.ViewVariables;
|
||||
|
||||
public enum ViewVariablesResponseCode : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// Request went through successfully.
|
||||
/// </summary>
|
||||
Ok = 200,
|
||||
|
||||
/// <summary>
|
||||
/// Request was invalid or something.
|
||||
/// </summary>
|
||||
InvalidRequest = 400,
|
||||
|
||||
/// <summary>
|
||||
/// Come back with admin access.
|
||||
/// </summary>
|
||||
NoAccess = 401,
|
||||
|
||||
/// <summary>
|
||||
/// Object pointing to by the selector does not exist.
|
||||
/// </summary>
|
||||
NoObject = 404,
|
||||
}
|
||||
240
Robust.Shared/ViewVariables/ViewVariablesTypeHandler.cs
Normal file
240
Robust.Shared/ViewVariables/ViewVariablesTypeHandler.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Robust.Shared.ViewVariables;
|
||||
|
||||
public delegate ViewVariablesPath? HandleTypePath(ViewVariablesPath path, string relativePath);
|
||||
public delegate ViewVariablesPath? HandleTypePath<in T>(T? obj, string relativePath);
|
||||
public delegate IEnumerable<string> ListTypeCustomPaths(ViewVariablesPath path);
|
||||
public delegate IEnumerable<string> ListTypeCustomPaths<in T>(T? obj);
|
||||
public delegate ViewVariablesPath? PathHandler(ViewVariablesPath path);
|
||||
public delegate ViewVariablesPath? PathHandler<in T>(T obj);
|
||||
public delegate ViewVariablesPath? PathHandlerNullable<in T>(T? obj);
|
||||
public delegate ViewVariablesPath? PathHandlerComponent<in T>(EntityUid uid, T component);
|
||||
public delegate TValue ComponentPropertyGetter<in TComp, out TValue>(EntityUid uid, TComp comp);
|
||||
public delegate void ComponentPropertySetter<in TComp, in TValue>(EntityUid uid, TValue value, TComp? comp);
|
||||
|
||||
public abstract class ViewVariablesTypeHandler
|
||||
{
|
||||
internal abstract ViewVariablesPath? HandlePath(ViewVariablesPath path, string relativePath);
|
||||
internal abstract IEnumerable<string> ListPath(ViewVariablesPath path);
|
||||
}
|
||||
|
||||
public sealed class ViewVariablesTypeHandler<T> : ViewVariablesTypeHandler
|
||||
{
|
||||
private readonly List<TypeHandlerData> _handlers = new();
|
||||
private readonly Dictionary<string, PathHandler> _paths = new();
|
||||
|
||||
internal ViewVariablesTypeHandler()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adding handler methods allow you to dynamically create and return ViewVariables paths for any sort of path.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The handlers are iterated in the order they were registered in.
|
||||
/// Handlers registered with this method take precedence over handlers registered for specific relative paths.
|
||||
/// </remarks>
|
||||
/// <returns>The same object instance, so you can chain method calls.</returns>
|
||||
public ViewVariablesTypeHandler<T> AddHandler(HandleTypePath<T> handle, ListTypeCustomPaths<T> list)
|
||||
{
|
||||
ViewVariablesPath? HandleWrapper(ViewVariablesPath path, string relativePath)
|
||||
=> handle((T?)path.Get(), relativePath);
|
||||
|
||||
IEnumerable<string> ListWrapper(ViewVariablesPath path)
|
||||
=> list((T?) path.Get());
|
||||
|
||||
_handlers.Add(new TypeHandlerData(HandleWrapper, ListWrapper, handle, list));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AddHandler(Robust.Shared.ViewVariables.HandleTypePath{T},Robust.Shared.ViewVariables.ListTypeCustomPaths{T})"/>
|
||||
public ViewVariablesTypeHandler<T> AddHandler(HandleTypePath handle, ListTypeCustomPaths list)
|
||||
{
|
||||
_handlers.Add(new TypeHandlerData(handle, list));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a specific handler method pair from the type handler.
|
||||
/// </summary>
|
||||
/// <returns>The same object instance, so you can chain method calls.</returns>
|
||||
/// <exception cref="ArgumentException">If the methods specified were not registered.</exception>
|
||||
public ViewVariablesTypeHandler<T> RemoveHandler(HandleTypePath<T> handle, ListTypeCustomPaths<T> list)
|
||||
{
|
||||
for (var i = 0; i < _handlers.Count; i++)
|
||||
{
|
||||
var data = _handlers[i];
|
||||
|
||||
if (data.OriginalHandle != handle || data.OriginalList != list)
|
||||
continue;
|
||||
|
||||
_handlers.RemoveAt(i);
|
||||
return this;
|
||||
}
|
||||
|
||||
throw new ArgumentException("The specified arguments were not found in the list!");
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="RemoveHandler(Robust.Shared.ViewVariables.HandleTypePath{T},Robust.Shared.ViewVariables.ListTypeCustomPaths{T})"/>
|
||||
public ViewVariablesTypeHandler<T> RemoveHandler(HandleTypePath handle, ListTypeCustomPaths list)
|
||||
{
|
||||
for (var i = 0; i < _handlers.Count; i++)
|
||||
{
|
||||
var data = _handlers[i];
|
||||
|
||||
if (data.Handle != handle || data.List != list)
|
||||
continue;
|
||||
|
||||
_handlers.RemoveAt(i);
|
||||
return this;
|
||||
}
|
||||
|
||||
throw new ArgumentException("The specified arguments were not found in the list!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// With this method you can register a handler to handle a specific path relative to the type instance.
|
||||
/// </summary>
|
||||
/// <returns>The same object instance, so you can chain method calls.</returns>
|
||||
public ViewVariablesTypeHandler<T> AddPath(string path, PathHandler<T> handler)
|
||||
{
|
||||
ViewVariablesPath? Wrapper(T? t)
|
||||
=> t != null ? handler(t) : null;
|
||||
|
||||
return AddPathNullable(path, (PathHandlerNullable<T>) Wrapper);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AddPath(string,PathHandler)"/>
|
||||
/// <remarks>As opposed to <see cref="AddPath(string,PathHandler)"/>, here the passed object is nullable.</remarks>
|
||||
/// <!-- The reason this isn't called "AddPath" is because it'd cause many ambiguous invocations.-->
|
||||
public ViewVariablesTypeHandler<T> AddPathNullable(string path, PathHandlerNullable<T> handler)
|
||||
{
|
||||
ViewVariablesPath? Wrapper(ViewVariablesPath p)
|
||||
=> handler((T?) p.Get());
|
||||
|
||||
return AddPath(path, Wrapper);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AddPath(string,PathHandler)"/>
|
||||
/// <remarks>As opposed to the rest of "AddPath" methods, this one is specific to entity components.</remarks>
|
||||
public ViewVariablesTypeHandler<T> AddPath(string path, PathHandlerComponent<T> handler)
|
||||
{
|
||||
ViewVariablesPath? Wrapper(ViewVariablesPath p)
|
||||
{
|
||||
if (p is not ViewVariablesComponentPath pc || pc.Get() is not {} obj)
|
||||
return null;
|
||||
|
||||
return handler(pc.Owner, (T) obj);
|
||||
}
|
||||
|
||||
return AddPath(path, Wrapper);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AddPath(string,PathHandler)"/>
|
||||
public ViewVariablesTypeHandler<T> AddPath<TValue>(string path, ComponentPropertyGetter<T, TValue> getter,
|
||||
ComponentPropertySetter<T, TValue>? setter = null)
|
||||
{
|
||||
// Gee, these wrappers are getting more and more complicated...
|
||||
ViewVariablesPath? Wrapper(ViewVariablesPath p)
|
||||
{
|
||||
if (p is not ViewVariablesComponentPath pc || pc.Get() is not {} obj)
|
||||
return null;
|
||||
|
||||
var comp = (T) obj;
|
||||
|
||||
var newPath = ViewVariablesPath.FromGetter(() => getter(pc.Owner, comp), typeof(TValue));
|
||||
|
||||
if (setter != null)
|
||||
{
|
||||
newPath = newPath.WithSetter(value =>
|
||||
{
|
||||
// In case it explodes with a NRE or something!
|
||||
try
|
||||
{
|
||||
setter(pc.Owner, (TValue) value!, comp);
|
||||
}
|
||||
catch (NullReferenceException e)
|
||||
{
|
||||
Logger.ErrorS(nameof(ViewVariablesManager), e,
|
||||
$"NRE caught in setter for path \"{path}\" for type \"{typeof(T).Name}\"...");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return newPath;
|
||||
}
|
||||
|
||||
return AddPath(path, Wrapper);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AddPath(string,PathHandler)"/>
|
||||
public ViewVariablesTypeHandler<T> AddPath(string path, PathHandler handler)
|
||||
{
|
||||
_paths.Add(path, handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a handler for a specific relative path.
|
||||
/// </summary>
|
||||
/// <returns>The same object instance, so you can chain method calls.</returns>
|
||||
public ViewVariablesTypeHandler<T> RemovePath(string path)
|
||||
{
|
||||
_paths.Remove(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
internal override ViewVariablesPath? HandlePath(ViewVariablesPath path, string relativePath)
|
||||
{
|
||||
// Dynamic handlers take precedence. Iterated by order of registration.
|
||||
foreach (var data in _handlers)
|
||||
{
|
||||
if (data.Handle(path, relativePath) is {} dynPath)
|
||||
return dynPath;
|
||||
}
|
||||
|
||||
// Finally, try to get a static handler.
|
||||
return _paths.TryGetValue(relativePath, out var handler)
|
||||
? handler(path)
|
||||
: null;
|
||||
}
|
||||
|
||||
internal override IEnumerable<string> ListPath(ViewVariablesPath path)
|
||||
{
|
||||
foreach (var data in _handlers)
|
||||
{
|
||||
foreach (var p in data.List(path))
|
||||
{
|
||||
yield return p;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (p, handler) in _paths)
|
||||
{
|
||||
if (handler(path) is {})
|
||||
yield return p;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TypeHandlerData
|
||||
{
|
||||
public readonly HandleTypePath Handle;
|
||||
public readonly ListTypeCustomPaths List;
|
||||
|
||||
public readonly HandleTypePath<T>? OriginalHandle;
|
||||
public readonly ListTypeCustomPaths<T>? OriginalList;
|
||||
|
||||
public TypeHandlerData(HandleTypePath handle, ListTypeCustomPaths list,
|
||||
HandleTypePath<T>? origHandle = null, ListTypeCustomPaths<T>? origList = null)
|
||||
{
|
||||
Handle = handle;
|
||||
List = list;
|
||||
OriginalHandle = origHandle;
|
||||
OriginalList = origList;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user