Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
1343168
docstrings in view variables mvp
Mervill Sep 7, 2024
5e745f2
working with fields and properties
Mervill Sep 8, 2024
457671d
move this code since we'll be expanding it
Mervill Sep 8, 2024
26a7115
Merge branch 'master' into vv-docstring
Mervill Sep 9, 2024
a5a4fdc
boy I sure do love processing xml, yep.
Mervill Sep 10, 2024
349c431
not as much string cleanup as I expected
Mervill Sep 10, 2024
2aa841d
more robustness and cleanup
Mervill Sep 10, 2024
732305a
I met a man without a name in a meadow beyond time and he whispered t…
Mervill Sep 10, 2024
3637c6e
comment
Mervill Sep 10, 2024
a19b647
minor tabs-vs-spaces skirmish, spaces victorious
Mervill Sep 10, 2024
0fda1a7
contemplate the sound of wisdom, recover incremental knowlage about m…
Mervill Sep 11, 2024
7d4cd92
update gitignore
Mervill Sep 11, 2024
cd2e65f
slightly cleaner defensive programming
Mervill Sep 11, 2024
e23bbbd
Add XmlDocTool - a utility program for reducing the size of the gener…
Mervill Sep 11, 2024
2a80d50
Move XmlDocTool into a Robust folder/namespace. use Path.Combine to g…
Mervill Sep 12, 2024
887f63f
add support for the viewvariables custom block in xml comments
Mervill Sep 12, 2024
7a62c0a
Merge branch 'master' into vv-docstring
Mervill Sep 12, 2024
64226ce
error format and return code in xmldoctool
Mervill Sep 13, 2024
63fecf8
xmldoctool output path
Mervill Sep 13, 2024
dc6b30b
run xmldoctool as a post build event
Mervill Sep 13, 2024
e87debc
Merge branch 'master' into vv-docstring
Mervill Sep 13, 2024
0c4376e
better msbuild spellcasting
Mervill Sep 13, 2024
727ecf9
the ritual is complete, the line has folded and become a circle
Mervill Sep 14, 2024
967d33e
update comments
Mervill Sep 14, 2024
77d6a0d
finally add xmldoc tool to the sln
Mervill Sep 14, 2024
d076244
more comments
Mervill Sep 14, 2024
dd940cc
add xmldoc comments to the vv test object
Mervill Sep 14, 2024
4c746b7
make vvdocpath relative so it plays better with linux
Mervill Sep 14, 2024
ac39f18
maybe the linux gotcha has been defeated
Mervill Sep 14, 2024
32a241e
Merge branch 'master' into vv-docstring
Mervill Sep 19, 2024
a346ec0
remove this
Mervill Sep 24, 2024
fb95556
the refresh button in the vv window wont expand verticaly if there is…
Mervill Sep 24, 2024
90790c4
detritus
Mervill Sep 25, 2024
780483a
Merge branch 'master' into vv-docstring
Mervill Apr 24, 2025
5acd325
update cefglue
Mervill Apr 24, 2025
eff08b7
Merge branch 'master' of https://github.com/space-wizards/RobustToolb…
ElectroJr Sep 17, 2025
09d86d1
tools
ElectroJr Sep 17, 2025
60548f7
fix merge error
ElectroJr Sep 17, 2025
a9a719f
Merge branch 'master' into vv-docstring
Mervill Oct 15, 2026
e0b8f03
put all the vv stuff back following the merge
Mervill Oct 15, 2026
f06dcd4
Add GetDocStringForType(Type type)
Mervill Oct 15, 2026
8e21e9e
Turns out we don't need VVRobustProjectDir
Mervill Oct 15, 2026
7f5f768
vv docstring will lazy load instead of direct load
Mervill Oct 16, 2026
8521bfa
xmltool overwrites directily instead of using a temp file, also retru…
Mervill Oct 16, 2026
0a76011
do xmldoc tool in away that appeases certain important djinn (git cli)
Mervill Oct 16, 2026
da51c88
refactor to allow xml docs for server-only code to live exclusively o…
Mervill Oct 20, 2026
029de73
when the pipieline is sus
Mervill Oct 20, 2026
e7c4f12
refactor: use RichTextLabel for word wrapping
Aeshus Oct 28, 2026
29df604
feat: expose collapsible's BoxContainer
Aeshus Oct 28, 2026
a059e3d
feat: make VV prettier
Aeshus Oct 28, 2026
2801d4c
fix: back to labels
Aeshus Oct 28, 2026
3ebc0a8
stealth fix for a common warning that appears because of enabling doc…
Mervill Oct 29, 2026
ed75c57
better flow for the xmldoctool
Mervill Oct 29, 2026
e3b47ad
fix: disable hscroll in VV windows
Aeshus Oct 29, 2026
746714a
style: fix formatting
Aeshus Oct 29, 2026
8e669fa
Merge branch 'vv-docstring' of https://github.com/Mervill/RobustToolb…
Mervill Oct 29, 2026
c9bf109
ViewVariablesDocStrings cleanup p1
Mervill Oct 29, 2026
f9b390e
beep
Mervill Oct 29, 2026
da7ce79
Harden the doc request againt some malicious cases.
Mervill Oct 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,7 @@ Robust.Docfx/api

# C# Dev Kit cache file
*.lscache

# ignore compiler-generated xml docs
Resources/ViewVariables/
Comment thread
Mervill marked this conversation as resolved.
Resolved
Resources/Server/ViewVariables/
3 changes: 3 additions & 0 deletions MSBuild/Robust.Properties.targets
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
<!-- serialization generator -->
<Import Project="Robust.Serialization.Generator.targets" Condition="'$(SkipRobustAnalyzer)' != 'true'" />

<!-- viewvariable xml doc generation -->
<Import Project="Robust.ViewVariables.targets" Condition="'$(ViewVariables)' == 'true'" />

<!-- Robust API system -->
<Import Project="Robust.ProjectReferences.targets" />

Expand Down
73 changes: 73 additions & 0 deletions MSBuild/Robust.ViewVariables.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<!--
This is the MSBuild file that drives the creation of xml docs for use by View Variables.

Normally this is invoked from `Robust.Properties.targets` when a csproj defines `<ViewVariables>`
in its `<PropertyGroup>`.

The workflow is:

0. Ensure the XmlDocTool is built before any project that uses this system
1. Make sure the doc folder exists:
- `..\Resources\ViewVariables\` for client and shared code, which is shipped to clients.
- `..\Resources\Server\ViewVariables\` for server code, which only the server can see.
The server hands these docs to clients that are allowed to VV, on demand.
2. Get MSBuild to generate the xml doc at the path from step 1.
3. Run XmlDocTool on the generated doc

Step two avoids using `<DocumentationFile>` due to a weird quirk of how that facility
works. DocumentationFile always causes two copies of the generated xml file to be
created, one at the path in DocumentationFile and one next to the DLL. This script
uses the somewhat arcane facility `<DocFileItem>` to side-step that problem and create
a single copy of the xml docs in the folder we want.

Project files (.csproj), inside or outside Robust Toolbox, opt in with:

```
<PropertyGroup>
<ViewVariables>true</ViewVariables>
</PropertyGroup>
```

Projects whose name ends in `.Server` are treated as server-only automatically.
Override that with `<ViewVariablesServerOnly>true|false</ViewVariablesServerOnly>`.

see also
github.com/dotnet/msbuild/issues/1559
-->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- Robust Toolbox root, found relative to this file so it works from content projects too -->
<VVRobustDir>$([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)', '..'))</VVRobustDir>
<!-- Doc generation is only enabled to feed VV, we don't require every public member to be documented -->
<NoWarn>$(NoWarn);CS1591;CS1573</NoWarn>
<!-- Server code docs must not ship to clients, they go in the server-only resources folder -->
<ViewVariablesServerOnly Condition="'$(ViewVariablesServerOnly)' == '' And $(MSBuildProjectName.EndsWith('.Server'))">true</ViewVariablesServerOnly>
<VVDocDir Condition="'$(ViewVariablesServerOnly)' == 'true'">..\Resources\Server\ViewVariables\</VVDocDir>
<VVDocDir Condition="'$(ViewVariablesServerOnly)' != 'true'">..\Resources\ViewVariables\</VVDocDir>
<VVDocPath>$(VVDocDir)$(MSBuildProjectName).xml</VVDocPath>
</PropertyGroup>
<ItemGroup>
<!-- Add a reference to XmlDocTool so it always gets built before any project that needs it -->
<ProjectReference Include="$(VVRobustDir)Robust.XmlDocTool\Robust.XmlDocTool.csproj">
<!-- Don't copy the build artifacts from this project to the referencing project's output folder -->
<Private>false</Private>
<!-- Indicate that the referencing project isn't actually meant to reference this project's assembly directly -->
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<!-- Build-time tools run on the host, not the target runtime -->
<GlobalPropertiesToRemove>RuntimeIdentifier;RestoreRuntimeIdentifier</GlobalPropertiesToRemove>
</ProjectReference>
</ItemGroup>
<Target Name="GenerateXmlDoc" BeforeTargets="CoreCompile">
<!-- Ensure the destination folder for the docs exists -->
<MakeDir Directories="$(VVDocDir)"/>
<ItemGroup>
<!-- Slightly arcane invocation of the MSBuild doc generation facility that bypasses the issue explained above -->
<DocFileItem Include="$(VVDocPath)" />
Comment thread
Mervill marked this conversation as resolved.
Resolved
</ItemGroup>
</Target>
<Target Name="TrimXmlDoc" AfterTargets="PostBuildEvent">
<!-- Run XmlDocTool on the generated doc file -->
<!-- Run the dll through the dotnet host rather than relying on a platform-specific apphost existing -->
<Exec Command="&quot;$(DOTNET_HOST_PATH)&quot; &quot;$([MSBuild]::NormalizePath('$(VVRobustDir)', 'bin', 'XmlDocTool', 'Robust.XmlDocTool.dll'))&quot; &quot;$(VVDocPath)&quot;" />
</Target>
</Project>
2 changes: 2 additions & 0 deletions Resources/Locale/en-US/view-variables.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ view-variable-instance-entity-server-components-search-bar-placeholder = Search
view-variable-instance-entity-add-window-server-components = Add Component [S]
view-variable-instance-entity-add-window-client-components = Add Component [C]

view-variables-property-type = Type:
view-variables-property-documentation = Documentation:

## SoundSpecifier
vv-sound-none = None
Expand Down
5 changes: 5 additions & 0 deletions Resources/Server/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/Resources/Server/

Files in this folder are not shipped to the client, and are thus visible only to the server.

See `RobustClientPackaging.ClientIgnoredResources` for refrence.
1 change: 1 addition & 0 deletions Robust.Client/Robust.Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<NoWarn>NU1701;CA1416</NoWarn>
<OutputPath>../bin/Client</OutputPath>
<RobustILLink>true</RobustILLink>
<ViewVariables>true</ViewVariables>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DiscordRichPresence" PrivateAssets="compile" />
Expand Down
10 changes: 6 additions & 4 deletions Robust.Client/UserInterface/Controls/Collapsible.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,16 @@ public string? Title
set => Label.Text = value;
}

public BoxContainer Box { get; }

public CollapsibleHeading()
{
ToggleMode = true;
var box = new BoxContainer();
AddChild(box);
box.AddChild(_chevron);
Box = new BoxContainer();
AddChild(Box);
Box.AddChild(_chevron);
Label = new Label();
box.AddChild(Label);
Box.AddChild(Label);
}

public CollapsibleHeading(string title) : this()
Expand Down
194 changes: 194 additions & 0 deletions Robust.Client/ViewVariables/ClientViewVariablesManager.DocStrings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.ViewVariables;

namespace Robust.Client.ViewVariables
{
internal sealed partial class ClientViewVariablesManager
{
// The client only ever sees the shared docs, server docs are requested from the server.
private ViewVariablesDocStrings? _docStrings = null;

// DocStrings we already asked the server for. null means the server didn't have one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or it means that user asked for them without having proper permission

// Cleared on disconnect because a different server may have different code.
private readonly Dictionary<(ViewVariablesDocStringKind, string), string?> _remoteDocStrings = new();

private readonly Dictionary<uint, TaskCompletionSource<string?[]>> _requestedDocStrings = new();

public void LoadDocStrings()
{
_docStrings = new(Sawmill, ViewVariablesDocStrings.SharedRoot);
_docStrings.EnsureLoaded(_resManager);
}

public string GetDocStringForType(Type type)
{
return GetDocStringForType(DocStringKeyForType(type));
}

public string GetDocStringForType(string key)
{
return GetLocalDocString(ViewVariablesDocStringKind.Type, key);
}

public string GetDocStringForFieldOrProperty(string key)
{
return GetLocalDocString(ViewVariablesDocStringKind.FieldOrProperty, key);
}

public async Task<string[]> GetDocStringsAsync(ViewVariablesDocStringKind kind, IReadOnlyList<string> keys)
{
LoadDocStrings();

var result = new string[keys.Count];
var missingKeys = new List<string>();
var missingIdx = new List<int>();
Comment on lines +47 to +48

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could it be better to use HashSet for those?


for (var i = 0; i < keys.Count; i++)
{
var key = keys[i];

if (_docStrings!.TryGet(kind, key, out var local))
{
result[i] = local;
}
else if (_remoteDocStrings.TryGetValue((kind, key), out var cached))
{
result[i] = cached ?? ViewVariablesDocStrings.Missing(key);
}
else
{
missingKeys.Add(key);
missingIdx.Add(i);
}
}

if (missingKeys.Count == 0 || !_netManager.IsConnected)
{
foreach (var i in missingIdx)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if missingKeys.Count == 0 , then missingIdx is empty, so that loop would effectively is doing nothing (probably separate early return is better?). And i am a bit confused how _netManager.IsConnected flag is working here... why would we need to do any of this if we are not connected?

{
result[i] = ViewVariablesDocStrings.Missing(keys[i]);
}
return result;
}

for (var start = 0; start < missingKeys.Count; start += MsgViewVariablesReqDocStrings.MaxKeys)
{
// We may have disconnected while waiting on the previous chunk.
if (!_netManager.IsConnected)
{
for (var k = start; k < missingKeys.Count; k++)
{
result[missingIdx[k]] = ViewVariablesDocStrings.Missing(missingKeys[k]);
}
break;
}

var count = Math.Min(MsgViewVariablesReqDocStrings.MaxKeys, missingKeys.Count - start);
var chunk = missingKeys.GetRange(start, count);
var remote = await RequestDocStrings(kind, chunk);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would propose naming it promise or task, or something along those lines.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having no parallelism here is a bit odd. Should there be? From what i see that should not really be a problem, and doing parallelism here would be a dirty thing in itself, not trivial. Considering the only thing that currently would ask for batches is server-side components... should not be a problem i presume?


for (var j = 0; j < chunk.Count; j++)
{
// A short answer means we never heard back for this key (e.g. we disconnected and the
// request was finished with an empty array). Don't cache it, or the miss would stick
// around after the cache was cleared and carry over to the next server.
if (j >= remote.Length)
{
result[missingIdx[start + j]] = ViewVariablesDocStrings.Missing(chunk[j]);
continue;
}

var docString = remote[j];
_remoteDocStrings[(kind, chunk[j])] = docString;
result[missingIdx[start + j]] = docString ?? ViewVariablesDocStrings.Missing(chunk[j]);
}
}

return result;
}

public async void ApplyDocStringsAsync(ViewVariablesDocStringKind kind, IReadOnlyList<(string Key, Action<string> Apply)> targets)
{
if (targets.Count == 0)
return;

var keys = new string[targets.Count];
for (var i = 0; i < targets.Count; i++)
{
keys[i] = targets[i].Key;
}

string[] docStrings;
try
{
docStrings = await GetDocStringsAsync(kind, keys);
}
catch (Exception e)
{
Sawmill.Error($"Failed to get DocStrings: {e}");
return;
}

for (var i = 0; i < targets.Count; i++)
{
targets[i].Apply(docStrings[i]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this can contain basically anything - i would propose a bit of redundancy - wrapping whole thing in try-catch and logging any suspicious stuff. Otherwise async void method will fall silently and you will never know whats going on.

}
}

public static string DocStringKeyForType(Type type)
{
return type.AssemblyQualifiedName!.Split(", ")[0];
}

private string GetLocalDocString(ViewVariablesDocStringKind kind, string key)
{
LoadDocStrings();

return _docStrings!.TryGet(kind, key, out var docString)
? docString
: ViewVariablesDocStrings.Missing(key);
}

private Task<string?[]> RequestDocStrings(ViewVariablesDocStringKind kind, List<string> keys)
{
var msg = new MsgViewVariablesReqDocStrings
{
RequestId = _nextReqId++,
Kind = kind,
Keys = keys,
};

var tcs = new TaskCompletionSource<string?[]>();
_requestedDocStrings.Add(msg.RequestId, tcs);
_netManager.ClientSendMessage(msg);
return tcs.Task;
}

private void _netMessageDocStrings(MsgViewVariablesDocStrings message)
{
if (!_requestedDocStrings.Remove(message.RequestId, out var tcs))
{
Sawmill.Error($"Server sent us DocStrings we never asked for, request ID {message.RequestId}.");
return;
}

tcs.SetResult(message.DocStrings);
}

private void OnDisconnectDocStrings(object? sender, NetDisconnectedArgs e)
{
_remoteDocStrings.Clear();

// Nobody is going to answer these anymore, finish them with empty results.
foreach (var tcs in _requestedDocStrings.Values)
{
tcs.TrySetResult(Array.Empty<string?>());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will flood _remoteDocStrings with values after you cleared it. Empty ones, but it will not be any less confusing. Considering async process tho, i am unsure how to properly fix this... Maybe it is just fine TM? but won't this lead to the situation when we falsly assume that values does not have doc string when person re-connects from server where they never had perms for vv to server where they do have?
We could use TrySetCanceled maybe? but that would throw exception, and having exceptions dicate your flow of logic is quite hacky.

}
_requestedDocStrings.Clear();
}
}
}
6 changes: 6 additions & 0 deletions Robust.Client/ViewVariables/ClientViewVariablesManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.ViewVariables.Instances;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
Expand All @@ -25,6 +26,7 @@ internal sealed partial class ClientViewVariablesManager : ViewVariablesManager,
[Dependency] private IRobustSerializer _robustSerializer = default!;
[Dependency] private IEntityManager _entityManager = default!;
[Dependency] private IViewVariableControlFactory _controlFactory = default!;
[Dependency] private IResourceManager _resManager = default!;

private uint _nextReqId = 1;
private readonly Vector2i _defaultWindowSize = (640, 420);
Expand All @@ -45,6 +47,9 @@ public override void Initialize()
{
base.Initialize();
InitializeDomains();
_netManager.RegisterNetMessage<MsgViewVariablesDocStrings>(_netMessageDocStrings);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same about order

_netManager.RegisterNetMessage<MsgViewVariablesReqDocStrings>();
_netManager.Disconnect += OnDisconnectDocStrings;
_netManager.RegisterNetMessage<MsgViewVariablesOpenSession>(_netMessageOpenSession);
_netManager.RegisterNetMessage<MsgViewVariablesRemoteData>(_netMessageRemoteData);
_netManager.RegisterNetMessage<MsgViewVariablesCloseSession>(_netMessageCloseSession);
Expand Down Expand Up @@ -274,6 +279,7 @@ protected override bool TryGetSession(Guid guid, [NotNullWhen(true)] out ICommon
session = null;
return false;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed i think

}

[Virtual]
Expand Down
Loading
Loading