Files
RobustToolbox/Robust.Client/Console/Commands/Debug.cs
Paul Ritter 80f9f24243 Serialization v3 aka constant suffering (#1606)
* oops

* fixes serialization il

* copytest

* typo & misc fixes

* 139 moment

* boxing

* mesa dum

* stuff

* goodbye bad friend

* last commit before the big (4) rewrite

* adds datanodes

* kills yamlobjserializer in favor of the new system

* adds more serializers, actually implements them & removes most of the last of the old system

* changed yamlfieldattribute namespace

* adds back iselfserialize

* refactors consts&flags

* renames everything to data(field/definition)

* adds afterserialization

* help

* dataclassgen

* fuggen help me mannen

* Fix most errors on content

* Fix engine errors except map loader

* maploader & misc fix

* misc fixes

* thing

* help

* refactors datanodes

* help me mannen

* Separate ITypeSerializer into reader and writer

* Convert all type serializers

* priority

* adds alot

* il fixes

* adds robustgen

* argh

* adds array & enum serialization

* fixes dataclasses

* adds vec2i / misc fixes

* fixes inheritance

* a very notcursed todo

* fixes some custom dataclasses

* push dis

* Remove data classes

* boutta box

* yes

* Add angle and regex serializer tests

* Make TypeSerializerTest abstract

* sets up ioc etc

* remove pushinheritance

* fixes

* Merge fixes, fix yaml hot reloading

* General fixes2

* Make enum serialization ignore case

* Fix the tag not being copied in data nodes

* Fix not properly serializing flag enums

* Fix component serialization on startup

* Implement ValueDataNode ToString

* Serialization IL fixes, fix return and string equality

* Remove async from prototype manager

* Make serializing unsupported node as enum exception more descriptive

* Fix serv3 tryread casting to serializer instead of reader

* Add constructor for invalid node type exception

* Temporary fix for SERV3: Turn populate delegate into regular code

* Fix not copying the data of non primitive types

* Fix not using the data definition found in copying

* Make ISerializationHooks require explicit implementations

* Add test for serialization inheritance

* Improve IsOverridenIn method

* Fix error message when a data definition is null

* Add method to cast a read value in Serv3Manager

* Rename IServ3Manager to ISerializationManager

* Rename usages of serv3manager, add generic copy method

* Fix IL copy method lookup

* Rename old usages of serv3manager

* Add ITypeCopier

* resistance is futile

* we will conquer this codebase

* Add copy method to all serializers

* Make primitive mismatch error message more descriptive

* bing bong im going to freacking heck

* oopsie moment

* hello are you interested in my wares

* does generic serializers under new architecture

* Convert every non generic serializer to the new format, general fixes

* Update usgaes of generic serializers, cleanup

* does some pushinheritance logic

* finishes pushinheritance FRAMEWORK

* shed

* Add box2, color and component registry serializer tests

* Create more deserialized types and store prototypes with their deserialized results

* Fixes and serializer updates

* Add serialization manager extensions

* adds pushinheritance

* Update all prototypes to have a parent and have consistent id/parent properties

* Fix grammar component serialization

* Add generic serializer tests

* thonk

* Add array serializer test

* Replace logger warning calls with exceptions

* fixes

* Move redundant methods to serialization manager extensions, cleanup

* Add array serialization

* fixes context

* more fixes

* argh

* inheritance

* this should do it

* fixes

* adds copiers & fixes some stuff

* copiers use context v1

* finishing copy context

* more context fixes

* Test fixes

* funky maps

* Fix server user interface component serialization

* Fix value tuple serialization

* Add copying for value types and arrays. Fix copy internal for primitives, enums and strings

* fixes

* fixes more stuff

* yes

* Make abstract/interface skips debugs instead of warnings

* Fix typo

* Make some dictionaries readonly

* Add checks for the serialization manager initializing and already being initialized

* Add base type required and usage for MeansDataDefinition and ImplicitDataDefinitionForInheritorsAttribute

* copy by ref

* Fix exception wording

* Update data field required summary with the new forbidden docs

* Use extension in map loader

* wanna erp

* Change serializing to not use il temporarily

* Make writing work with nullable types

* pushing

* check

* cuddling slaps HARD

* Add serialization priority test

* important fix

* a serialization thing

* serializer moment

* Add validation for some type serializers

* adds context

* moar context

* fixes

* Do the thing for appearance

* yoo lmao

* push haha pp

* Temporarily make copy delegate regular c# code

* Create deserialized component registry to handle not inheriting conflicting references

* YAML LINTER BABY

* ayes

* Fix sprite component norot not being default true like in latest master

* Remove redundant todos

* Add summary doc to every ISerializationManager method

* icon fixes

* Add skip hook argument to readers and copiers

* Merge fixes

* Fix ordering of arguments in read and copy reflection call

* Fix user interface components deserialization

* pew pew

* i am going to HECK

* Add MustUseReturnValue to copy-over methods

* Make serialization log calls use the same sawmill

* gamin

* Fix doc errors in ISerializationManager.cs

* goodbye brave soldier

* fixes

* WIP merge fixes and entity serialization

* aaaaaaaaaaaaaaa

* aaaaaaaaaaaaaaa

* adds inheritancebehaviour

* test/datafield fixes

* forgot that one

* adds more verbose validation

* This fixes the YAML hot reloading

* Replace yield break with Enumerable.Empty

* adds copiers

* aaaaaaaaaaaaa

* array fix
priority fix
misc fixes

* fix(?)

* fix.

* funny map serialization (wip)

* funny map serialization (wip)

* Add TODO

* adds proper info the validation

* Make yaml linter 5 times faster (~80% less execution time)

* Improves the error message for missing fields in the linter

* Include component name in unknown component type error node

* adds alwaysrelevant usa

* fixes mapsaving

* moved surpressor to analyzers proj

* warning cleanup & moves surpressor

* removes old msbuild targets

* Revert "Make yaml linter 5 times faster (~80% less execution time)"

This reverts commit 2ee4cc2c26.

* Add serialization to RobustServerSimulation and mock reflection methods
Fixes container tests

* Fix nullability warnings

* Improve yaml linter message feedback

* oops moment

* Add IEquatable, IComparable, ToString and operators to DataPosition
Rename it to NodeMark
Make it a readonly struct

* Remove try catch from enum parsing

* Make dependency management in serialization less bad

* Make dependencies an argument instead of a property on the serialization manager

* Clean up type serializers

* Improve validation messages and resourc epath checking

* Fix sprite error message

* reached perfection

Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
Co-authored-by: Vera Aguilera Puerto <zddm@outlook.es>
2021-03-04 15:59:14 -08:00

1113 lines
38 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime;
using System.Text;
using System.Text.RegularExpressions;
using Robust.Client.Input;
using Robust.Client.Debugging;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Asynchronous;
using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client.Console.Commands
{
internal class DumpEntitiesCommand : IConsoleCommand
{
public string Command => "dumpentities";
public string Help => "Dump entity list";
public string Description => "Dumps entity list of UIDs and prototype.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var entityManager = IoCManager.Resolve<IEntityManager>();
foreach (var e in entityManager.GetEntities().OrderBy(e => e.Uid))
{
shell.WriteLine($"entity {e.Uid}, {e.Prototype?.ID}, {e.Transform.Coordinates}.");
}
}
}
internal class GetComponentRegistrationCommand : IConsoleCommand
{
public string Command => "getcomponentregistration";
public string Help => "Usage: getcomponentregistration <componentName>";
public string Description => "Gets component registration information";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length < 1)
{
shell.WriteLine(Help);
return;
}
var componentFactory = IoCManager.Resolve<IComponentFactory>();
try
{
var registration = componentFactory.GetRegistration(args[0]);
var message = new StringBuilder($"'{registration.Name}': (type: {registration.Type}, ");
if (registration.NetID == null)
{
message.Append("no Net ID");
}
else
{
message.Append($"net ID: {registration.NetID}");
}
message.Append($", NSE: {registration.NetworkSynchronizeExistence}, references:");
shell.WriteLine(message.ToString());
foreach (var type in registration.References)
{
shell.WriteLine($" {type}");
}
}
catch (UnknownComponentException)
{
shell.WriteError($"No registration found for '{args[0]}'");
}
}
}
internal class ToggleMonitorCommand : IConsoleCommand
{
public string Command => "monitor";
public string Help =>
"Usage: monitor <name>\nPossible monitors are: fps, net, bandwidth, coord, time, frames, mem, clyde, input";
public string Description => "Toggles a debug monitor in the F3 menu.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var monitor = IoCManager.Resolve<IUserInterfaceManager>().DebugMonitors;
if (args.Length != 1)
{
shell.WriteLine(Help);
return;
}
switch (args[0])
{
case "fps":
monitor.ShowFPS ^= true;
break;
case "net":
monitor.ShowNet ^= true;
break;
case "bandwidth":
monitor.ShowNetBandwidth ^= true;
break;
case "coord":
monitor.ShowCoords ^= true;
break;
case "time":
monitor.ShowTime ^= true;
break;
case "frames":
monitor.ShowFrameGraph ^= true;
break;
case "mem":
monitor.ShowMemory ^= true;
break;
case "clyde":
monitor.ShowClyde ^= true;
break;
case "input":
monitor.ShowInput ^= true;
break;
default:
shell.WriteLine($"Invalid key: {args[0]}");
break;
}
}
}
internal class ExceptionCommand : IConsoleCommand
{
public string Command => "fuck";
public string Help => "Throws an exception";
public string Description => "Throws an exception";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
throw new InvalidOperationException("Fuck");
}
}
internal class ShowBoundingBoxesCommand : IConsoleCommand
{
public string Command => "showbb";
public string Help => "";
public string Description => "Enables debug drawing over all bounding boxes in the game, showing their size.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = IoCManager.Resolve<IDebugDrawing>();
mgr.DebugColliders = !mgr.DebugColliders;
}
}
internal class ShowPositionsCommand : IConsoleCommand
{
public string Command => "showpos";
public string Help => "";
public string Description => "Enables debug drawing over all entity positions in the game.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = IoCManager.Resolve<IDebugDrawing>();
mgr.DebugPositions = !mgr.DebugPositions;
}
}
internal class ShowRayCommand : IConsoleCommand
{
public string Command => "showrays";
public string Help => "Usage: showrays <raylifetime>";
public string Description => "Toggles debug drawing of physics rays. An integer for <raylifetime> must be provided";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteLine(Help);
return;
}
if (!int.TryParse(args[0], out var id))
{
shell.WriteError($"{args[0]} is not a valid integer.");
return;
}
var mgr = IoCManager.Resolve<IDebugDrawingManager>();
mgr.DebugDrawRays = !mgr.DebugDrawRays;
shell.WriteError("Toggled showing rays to:" + mgr.DebugDrawRays.ToString());
mgr.DebugRayLifetime = TimeSpan.FromSeconds((double)int.Parse(args[0], CultureInfo.InvariantCulture));
}
}
internal class DisconnectCommand : IConsoleCommand
{
public string Command => "disconnect";
public string Help => "";
public string Description => "Immediately disconnect from the server and go back to the main menu.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
IoCManager.Resolve<IClientNetManager>().ClientDisconnect("Disconnect command used.");
}
}
internal class EntityInfoCommand : IConsoleCommand
{
public string Command => "entfo";
public string Help =>
"entfo <entityuid>\nThe entity UID can be prefixed with 'c' to convert it to a client entity UID.";
public string Description => "Displays verbose diagnostics for an entity.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteLine(Help);
return;
}
if ((!new Regex(@"^c?[0-9]+$").IsMatch(args[0])))
{
shell.WriteError("Malformed UID");
return;
}
var uid = EntityUid.Parse(args[0]);
var entmgr = IoCManager.Resolve<IEntityManager>();
if (!entmgr.TryGetEntity(uid, out var entity))
{
shell.WriteError("That entity does not exist. Sorry lad.");
return;
}
shell.WriteLine($"{entity.Uid}: {entity.Prototype?.ID}/{entity.Name}");
shell.WriteLine($"init/del/lmt: {entity.Initialized}/{entity.Deleted}/{entity.LastModifiedTick}");
foreach (var component in entity.GetAllComponents())
{
shell.WriteLine(component.ToString() ?? "");
if (component is IComponentDebug debug)
{
foreach (var line in debug.GetDebugString().Split('\n'))
{
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
shell.WriteLine("\t" + line);
}
}
}
}
}
internal class SnapGridGetCell : IConsoleCommand
{
public string Command => "sggcell";
public string Help => "sggcell <gridID> <vector2i> [offset]\nThat vector2i param is in the form x<int>,y<int>.";
public string Description => "Lists entities on a snap grid cell.";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 2 && args.Length != 3)
{
shell.WriteLine(Help);
return;
}
string gridId = args[0];
string indices = args[1];
string offset = args.Length == 3 ? args[2] : "Center";
if (!int.TryParse(args[0], out var id))
{
shell.WriteError($"{args[0]} is not a valid integer.");
return;
}
if (!new Regex(@"^-?[0-9]+,-?[0-9]+$").IsMatch(indices))
{
shell.WriteError("mapIndicies must be of form x<int>,y<int>");
return;
}
SnapGridOffset selectedOffset;
if (Enum.IsDefined(typeof(SnapGridOffset), offset))
{
selectedOffset = (SnapGridOffset)Enum.Parse(typeof(SnapGridOffset), offset);
}
else
{
shell.WriteError("given offset type is not defined");
return;
}
var mapMan = IoCManager.Resolve<IMapManager>();
if (mapMan.GridExists(new GridId(int.Parse(gridId, CultureInfo.InvariantCulture))))
{
foreach (var entity in
mapMan.GetGrid(new GridId(int.Parse(gridId, CultureInfo.InvariantCulture))).GetSnapGridCell(
new Vector2i(
int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture),
int.Parse(indices.Split(',')[1], CultureInfo.InvariantCulture)),
selectedOffset))
{
shell.WriteLine(entity.Owner.Uid.ToString());
}
}
else
{
shell.WriteError("grid does not exist");
}
}
}
internal class SetPlayerName : IConsoleCommand
{
public string Command => "overrideplayername";
public string Description => "Changes the name used when attempting to connect to the server.";
public string Help => Command + " <name>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length < 1)
{
shell.WriteLine(Help);
return;
}
var client = IoCManager.Resolve<IBaseClient>();
client.PlayerNameOverride = args[0];
shell.WriteLine($"Overriding player name to \"{args[0]}\".");
}
}
internal class LoadResource : IConsoleCommand
{
public string Command => "ldrsc";
public string Description => "Pre-caches a resource.";
public string Help => "ldrsc <path> <type>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length < 2)
{
shell.WriteLine(Help);
return;
}
var resourceCache = IoCManager.Resolve<IResourceCache>();
var reflection = IoCManager.Resolve<IReflectionManager>();
Type type;
try
{
type = reflection.LooseGetType(args[1]);
}
catch(ArgumentException)
{
shell.WriteError("Unable to find type");
return;
}
var getResourceMethod =
resourceCache
.GetType()
.GetMethod("GetResource", new[] { typeof(string), typeof(bool) });
DebugTools.Assert(getResourceMethod != null);
var generic = getResourceMethod!.MakeGenericMethod(type);
generic.Invoke(resourceCache, new object[] { args[0], true });
}
}
internal class ReloadResource : IConsoleCommand
{
public string Command => "rldrsc";
public string Description => "Reloads a resource.";
public string Help => "rldrsc <path> <type>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length < 2)
{
shell.WriteLine(Help);
return;
}
var resourceCache = IoCManager.Resolve<IResourceCache>();
var reflection = IoCManager.Resolve<IReflectionManager>();
Type type;
try
{
type = reflection.LooseGetType(args[1]);
}
catch(ArgumentException)
{
shell.WriteError("Unable to find type");
return;
}
var getResourceMethod = resourceCache.GetType().GetMethod("ReloadResource", new[] { typeof(string) });
DebugTools.Assert(getResourceMethod != null);
var generic = getResourceMethod!.MakeGenericMethod(type);
generic.Invoke(resourceCache, new object[] { args[0] });
}
}
internal class GridTileCount : IConsoleCommand
{
public string Command => "gridtc";
public string Description => "Gets the tile count of a grid";
public string Help => "Usage: gridtc <gridId>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteLine(Help);
return;
}
if (!int.TryParse(args[0], out var id))
{
shell.WriteLine($"{args[0]} is not a valid integer.");
return;
}
var gridId = new GridId(int.Parse(args[0]));
var mapManager = IoCManager.Resolve<IMapManager>();
if (mapManager.TryGetGrid(gridId, out var grid))
{
shell.WriteLine(mapManager.GetGrid(gridId).GetAllTiles().Count().ToString());
}
else
{
shell.WriteError($"No grid exists with id {id}");
}
}
}
internal class GuiDumpCommand : IConsoleCommand
{
public string Command => "guidump";
public string Description => "Dump GUI tree to /guidump.txt in user data.";
public string Help => "guidump";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var root = IoCManager.Resolve<IUserInterfaceManager>().RootControl;
var res = IoCManager.Resolve<IResourceManager>();
using (var stream = res.UserData.Create(new ResourcePath("/guidump.txt")))
using (var writer = new StreamWriter(stream, EncodingHelpers.UTF8))
{
_writeNode(root, 0, writer);
}
shell.WriteLine("Saved guidump");
}
private static void _writeNode(Control control, int indents, TextWriter writer)
{
var indentation = new string(' ', indents * 2);
writer.WriteLine("{0}{1}", indentation, control);
foreach (var (key, value) in _propertyValuesFor(control))
{
writer.WriteLine("{2} * {0}: {1}", key, value, indentation);
}
foreach (var child in control.Children)
{
_writeNode(child, indents + 1, writer);
}
}
private static List<(string, string)> _propertyValuesFor(Control control)
{
var members = new List<(string, string)>();
var type = control.GetType();
foreach (var fieldInfo in type.GetAllFields())
{
if (fieldInfo.GetCustomAttribute<ViewVariablesAttribute>() == null)
{
continue;
}
members.Add((fieldInfo.Name, fieldInfo.GetValue(control)?.ToString() ?? "null"));
}
foreach (var propertyInfo in type.GetAllProperties())
{
if (propertyInfo.GetCustomAttribute<ViewVariablesAttribute>() == null)
{
continue;
}
members.Add((propertyInfo.Name, propertyInfo.GetValue(control)?.ToString() ?? "null"));
}
foreach (var (attachedProperty, value) in control.AllAttachedProperties)
{
members.Add(($"{attachedProperty.OwningType.Name}.{attachedProperty.Name}",
value?.ToString() ?? "null"));
}
members.Sort((a, b) => string.Compare(a.Item1, b.Item1, StringComparison.Ordinal));
return members;
}
}
internal class UITestCommand : IConsoleCommand
{
public string Command => "uitest";
public string Description => "Open a dummy UI testing window";
public string Help => "uitest";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var window = new SS14Window { MinSize = (500, 400)};
var tabContainer = new TabContainer();
window.Contents.AddChild(tabContainer);
var scroll = new ScrollContainer();
tabContainer.AddChild(scroll);
//scroll.SetAnchorAndMarginPreset(Control.LayoutPreset.Wide);
var vBox = new VBoxContainer();
scroll.AddChild(vBox);
var progressBar = new ProgressBar { MaxValue = 10, Value = 5 };
vBox.AddChild(progressBar);
var optionButton = new OptionButton();
optionButton.AddItem("Honk");
optionButton.AddItem("Foo");
optionButton.AddItem("Bar");
optionButton.AddItem("Baz");
optionButton.OnItemSelected += eventArgs => optionButton.SelectId(eventArgs.Id);
vBox.AddChild(optionButton);
var tree = new Tree { VerticalExpand = true };
var root = tree.CreateItem();
root.Text = "Honk!";
var child = tree.CreateItem();
child.Text = "Foo";
for (var i = 0; i < 20; i++)
{
child = tree.CreateItem();
child.Text = $"Bar {i}";
}
vBox.AddChild(tree);
var rich = new RichTextLabel();
var message = new FormattedMessage();
message.AddText("Foo\n");
message.PushColor(Color.Red);
message.AddText("Bar");
message.Pop();
rich.SetMessage(message);
vBox.AddChild(rich);
var itemList = new ItemList();
tabContainer.AddChild(itemList);
for (var i = 0; i < 10; i++)
{
itemList.AddItem(i.ToString());
}
var grid = new GridContainer { Columns = 3 };
tabContainer.AddChild(grid);
for (var y = 0; y < 3; y++)
{
for (var x = 0; x < 3; x++)
{
grid.AddChild(new Button
{
MinSize = (50, 50),
Text = $"{x}, {y}"
});
}
}
var group = new ButtonGroup();
var vBoxRadioButtons = new VBoxContainer();
for (var i = 0; i < 10; i++)
{
vBoxRadioButtons.AddChild(new Button
{
Text = i.ToString(),
Group = group
});
// ftftftftftftft
}
tabContainer.AddChild(vBoxRadioButtons);
TabContainer.SetTabTitle(vBoxRadioButtons, "Radio buttons!!");
tabContainer.AddChild(new VBoxContainer
{
Name = "Slider",
Children =
{
new Slider()
}
});
tabContainer.AddChild(new HSplitContainer
{
Children =
{
new PanelContainer
{
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.Red},
Children =
{
new Label{ Text = "FOOBARBAZ"},
}
},
new PanelContainer
{
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.Blue},
Children =
{
new Label{ Text = "FOOBARBAZ"},
}
},
}
});
window.OpenCentered();
}
}
internal class SetClipboardCommand : IConsoleCommand
{
public string Command => "setclipboard";
public string Description => "Sets the system clipboard";
public string Help => "setclipboard <text>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = IoCManager.Resolve<IClipboardManager>();
mgr.SetText(args[0]);
}
}
internal class GetClipboardCommand : IConsoleCommand
{
public string Command => "getclipboard";
public string Description => "Gets the system clipboard";
public string Help => "getclipboard";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = IoCManager.Resolve<IClipboardManager>();
shell.WriteLine(mgr.GetText());
}
}
internal class ToggleLight : IConsoleCommand
{
public string Command => "togglelight";
public string Description => "Toggles light rendering.";
public string Help => "togglelight";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = IoCManager.Resolve<ILightManager>();
if (!mgr.LockConsoleAccess)
mgr.Enabled = !mgr.Enabled;
}
}
internal class ToggleFOV : IConsoleCommand
{
public string Command => "togglefov";
public string Description => "Toggles fov for client.";
public string Help => "togglefov";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = IoCManager.Resolve<IEyeManager>();
if (mgr.CurrentEye != null)
mgr.CurrentEye.DrawFov = !mgr.CurrentEye.DrawFov;
}
}
internal class ToggleHardFOV : IConsoleCommand
{
public string Command => "togglehardfov";
public string Description => "Toggles hard fov for client (for debugging space-station-14#2353).";
public string Help => "togglehardfov";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = IoCManager.Resolve<ILightManager>();
if (!mgr.LockConsoleAccess)
mgr.DrawHardFov = !mgr.DrawHardFov;
}
}
internal class ToggleShadows : IConsoleCommand
{
public string Command => "toggleshadows";
public string Description => "Toggles shadow rendering.";
public string Help => "toggleshadows";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = IoCManager.Resolve<ILightManager>();
if (!mgr.LockConsoleAccess)
mgr.DrawShadows = !mgr.DrawShadows;
}
}
internal class ToggleLightBuf : IConsoleCommand
{
public string Command => "togglelightbuf";
public string Description => "Toggles lighting rendering. This includes shadows but not FOV.";
public string Help => "togglelightbuf";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = IoCManager.Resolve<ILightManager>();
if (!mgr.LockConsoleAccess)
mgr.DrawLighting = !mgr.DrawLighting;
}
}
internal class GcCommand : IConsoleCommand
{
public string Command => "gc";
public string Description => "Run the GC.";
public string Help => "gc [generation]";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length == 0)
{
GC.Collect();
}
else
{
if (int.TryParse(args[0], out int result))
GC.Collect(result);
else
shell.WriteError("Failed to parse argument.");
}
}
}
internal class GcFullCommand : IConsoleCommand
{
public string Command => "gcf";
public string Description => "Run the GC, fully, compacting LOH and everything.";
public string Help => "gcf";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect(2, GCCollectionMode.Forced, true, true);
}
}
internal class GcModeCommand : IConsoleCommand
{
public string Command => "gc_mode";
public string Description => "Change/Read the GC Latency mode.";
public string Help => "gc_mode\nSee current GC Latencymode\ngc_mode [type]\n Change GC Latency mode to [type]";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var prevMode = GCSettings.LatencyMode;
if (args.Length == 0)
{
shell.WriteLine($"current gc latency mode: {(int) prevMode} ({prevMode})");
shell.WriteLine("possible modes:");
foreach (int mode in (int[]) Enum.GetValues(typeof(GCLatencyMode)))
{
shell.WriteLine($" {mode}: {Enum.GetName(typeof(GCLatencyMode), mode)}");
}
}
else
{
GCLatencyMode mode;
if (char.IsDigit(args[0][0]) && int.TryParse(args[0], out var modeNum))
{
mode = (GCLatencyMode) modeNum;
}
else if (!Enum.TryParse(args[0], true, out mode))
{
shell.WriteLine($"unknown gc latency mode: {args[0]}");
return;
}
shell.WriteLine($"attempting gc latency mode change: {(int) prevMode} ({prevMode}) -> {(int) mode} ({mode})");
GCSettings.LatencyMode = mode;
shell.WriteLine($"resulting gc latency mode: {(int) GCSettings.LatencyMode} ({GCSettings.LatencyMode})");
}
}
}
internal class SerializeStatsCommand : IConsoleCommand
{
public string Command => "szr_stats";
public string Description => "Report serializer statistics.";
public string Help => "szr_stats";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
shell.WriteLine($"serialized: {RobustSerializer.BytesSerialized} bytes, {RobustSerializer.ObjectsSerialized} objects");
shell.WriteLine($"largest serialized: {RobustSerializer.LargestObjectSerializedBytes} bytes, {RobustSerializer.LargestObjectSerializedType} objects");
shell.WriteLine($"deserialized: {RobustSerializer.BytesDeserialized} bytes, {RobustSerializer.ObjectsDeserialized} objects");
shell.WriteLine($"largest serialized: {RobustSerializer.LargestObjectDeserializedBytes} bytes, {RobustSerializer.LargestObjectDeserializedType} objects");
}
}
internal class ChunkInfoCommand : IConsoleCommand
{
public string Command => "chunkinfo";
public string Description => "Gets info about a chunk under your mouse cursor.";
public string Help => Command;
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mapMan = IoCManager.Resolve<IMapManager>();
var inputMan = IoCManager.Resolve<IInputManager>();
var eyeMan = IoCManager.Resolve<IEyeManager>();
var mousePos = eyeMan.ScreenToMap(inputMan.MouseScreenPosition);
if (!mapMan.TryFindGridAt(mousePos, out var grid))
{
shell.WriteLine("No grid under your mouse cursor.");
return;
}
var internalGrid = (IMapGridInternal)grid;
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
var chunk = internalGrid.GetChunk(chunkIndex);
shell.WriteLine($"worldBounds: {chunk.CalcWorldBounds()} localBounds: {chunk.CalcLocalBounds()}");
}
}
internal class ReloadShadersCommand : IConsoleCommand
{
public string Command => "rldshader";
public string Description => "Reloads all shaders";
public string Help => "rldshader";
public static Dictionary<string, FileSystemWatcher>? _watchers;
public static ConcurrentDictionary<string, bool>? _reloadShadersQueued = new();
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
IResourceCacheInternal resC;
if (args.Length == 1)
{
if (args[0] == "+watch")
{
if (_watchers != null)
{
shell.WriteLine("Already watching.");
return;
}
resC = IoCManager.Resolve<IResourceCacheInternal>();
_watchers = new Dictionary<string, FileSystemWatcher>();
var stringComparer = PathHelpers.IsFileSystemCaseSensitive()
? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase;
var reversePathResolution = new ConcurrentDictionary<string, HashSet<ResourcePath>>(stringComparer);
var taskManager = IoCManager.Resolve<ITaskManager>();
var shaderCount = 0;
var created = 0;
var dirs = new ConcurrentDictionary<string, SortedSet<string>>(stringComparer);
foreach (var (path, src) in resC.GetAllResources<ShaderSourceResource>())
{
if (!resC.TryGetDiskFilePath(path, out var fullPath))
{
throw new NotImplementedException();
}
reversePathResolution.GetOrAdd(fullPath, _ => new HashSet<ResourcePath>()).Add(path);
var dir = Path.GetDirectoryName(fullPath)!;
var fileName = Path.GetFileName(fullPath);
dirs.GetOrAdd(dir, _ => new SortedSet<string>(stringComparer))
.Add(fileName);
foreach (var inc in src.ParsedShader.Includes)
{
if (!resC.TryGetDiskFilePath(inc, out var incFullPath))
{
throw new NotImplementedException();
}
reversePathResolution.GetOrAdd(incFullPath, _ => new HashSet<ResourcePath>()).Add(path);
var incDir = Path.GetDirectoryName(incFullPath)!;
var incFileName = Path.GetFileName(incFullPath);
dirs.GetOrAdd(incDir, _ => new SortedSet<string>(stringComparer))
.Add(incFileName);
}
++shaderCount;
}
foreach (var (dir, files) in dirs)
{
if (_watchers.TryGetValue(dir, out var watcher))
{
throw new NotImplementedException();
}
watcher = new FileSystemWatcher(dir);
watcher.Changed += (_, ev) =>
{
if (_reloadShadersQueued!.TryAdd(ev.FullPath, true))
{
taskManager.RunOnMainThread(() =>
{
var resPaths = reversePathResolution[ev.FullPath];
foreach (var resPath in resPaths)
{
try
{
IoCManager.Resolve<IResourceCache>()
.ReloadResource<ShaderSourceResource>(resPath);
shell.WriteLine($"Reloaded shader: {resPath}");
}
catch (Exception)
{
shell.WriteLine($"Failed to reload shader: {resPath}");
}
_reloadShadersQueued.TryRemove(ev.FullPath, out var _);
}
});
}
};
foreach (var file in files)
{
watcher.Filters.Add(file);
}
watcher.EnableRaisingEvents = true;
_watchers.Add(dir, watcher);
++created;
}
shell.WriteLine($"Created {created} shader directory watchers for {shaderCount} shaders.");
return;
}
if (args[0] == "-watch")
{
if (_watchers == null)
{
shell.WriteLine("No shader directory watchers active.");
return;
}
var disposed = 0;
foreach (var (_, watcher) in _watchers)
{
++disposed;
watcher.Dispose();
}
_watchers = null;
shell.WriteLine($"Disposed of {disposed} shader directory watchers.");
return;
}
}
if (args.Length > 1)
{
shell.WriteLine("Not implemented.");
return;
}
shell.WriteLine("Reloading content shader resources...");
resC = IoCManager.Resolve<IResourceCacheInternal>();
foreach (var (path, _) in resC.GetAllResources<ShaderSourceResource>())
{
try
{
resC.ReloadResource<ShaderSourceResource>(path);
}
catch (Exception)
{
shell.WriteLine($"Failed to reload shader: {path}");
}
}
shell.WriteLine("Done.");
}
}
internal class ClydeDebugLayerCommand : IConsoleCommand
{
public string Command => "cldbglyr";
public string Description => "Toggle fov and light debug layers";
public string Help => "cldbglyr <layer>: Toggle <layer>\ncldbglyr: Turn all Layers off";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var clyde = IoCManager.Resolve<IClydeInternal>();
if (args.Length < 1)
{
clyde.DebugLayers = ClydeDebugLayers.None;
return;
}
clyde.DebugLayers = args[0] switch
{
"fov" => ClydeDebugLayers.Fov,
"light" => ClydeDebugLayers.Light,
_ => ClydeDebugLayers.None
};
}
}
internal class GetKeyInfoCommand : IConsoleCommand
{
public string Command => "keyinfo";
public string Description => "Keys key info for a key";
public string Help => "keyinfo <Key>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteLine(Help);
return;
}
var clyde = IoCManager.Resolve<IClydeInternal>();
if (Enum.TryParse(typeof(Keyboard.Key), args[0], true, out var parsed))
{
var key = (Keyboard.Key) parsed!;
var name = clyde.GetKeyName(key);
var scanCode = clyde.GetKeyScanCode(key);
var nameScanCode = clyde.GetKeyNameScanCode(scanCode);
shell.WriteLine($"name: '{name}' scan code: '{scanCode}' name via scan code: '{nameScanCode}'");
}
else if (int.TryParse(args[0], out var scanCode))
{
var nameScanCode = clyde.GetKeyNameScanCode(scanCode);
shell.WriteLine($"name via scan code: '{nameScanCode}'");
}
}
}
}