Files
RobustToolbox/Robust.Client/UserInterface/XAML/Proxy/XamlProxyManager.cs
T
83c2a1be11 [Dependency] source generator part 2 (#6550)
* [Dependency] source generator

No more reflection, no more codegen at runtime

Also various changes to Roslyn helpers to make this easier to write.

Requires all types with dependencies to be partial and not have readonly dependency fields. An analyzer enforces this at warning level, the previous injection strategies have remained in the code *for now* as a fallback.

No fallback is available for [field: Dependency] properties, due to a Roslyn bug.

Code Fixes exist. We love Roslyn

* Apply dependencies generator changes to all code

* Release notes

* Preprocessor got hands

* Handle nullable dependencies

These are bad but gotta deal with it.

* Apply suggestions from code review

Co-authored-by: Moony <moony@hellomouse.net>

* Fine, let's not use collection expressions

---------

Co-authored-by: Moony <moony@hellomouse.net>
2026-05-08 12:38:33 +02:00

145 lines
4.9 KiB
C#

#if TOOLS
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Reflection;
using Robust.Xaml;
namespace Robust.Client.UserInterface.XAML.Proxy;
/// <summary>
/// The real implementation of <see cref="IXamlProxyManager"/>.
/// </summary>
public sealed partial class XamlProxyManager : IXamlProxyManager
{
ISawmill _sawmill = null!;
[Dependency] IReflectionManager _reflectionManager = null!;
[Dependency] ILogManager _logManager = null!;
[Dependency] private IConfigurationManager _cfg = null!;
XamlImplementationStorage _xamlImplementationStorage = null!;
List<Assembly> _knownAssemblies = [];
XamlJitCompiler? _xamlJitCompiler;
/// <summary>
/// Initialize this, subscribing to assembly changes.
/// </summary>
public void Initialize()
{
_sawmill = _logManager.GetSawmill("xamlhotreload");
_xamlImplementationStorage = new XamlImplementationStorage(_sawmill, Compile);
var preload = _cfg.GetCVar(CVars.UIXamlJitPreload);
AddAssemblies(reload: preload);
_reflectionManager.OnAssemblyAdded += (_, _) => { AddAssemblies(); };
if (!preload)
{
// Compile any type at all on another thread, so we don't hold up main thread init with loading
// the entire XAML compiler machinery.
// In my testing, it took like 0.5s on debug to run the first XAML compile. Yeah.
ThreadPool.QueueUserWorkItem(_ =>
{
_xamlImplementationStorage.CompileType(UserInterfaceManager.XamlHotReloadWarmupType);
});
}
}
/// <summary>
/// Return true if setting the implementation of <paramref name="fileName" />
/// would not be a no-op.
/// </summary>
/// <param name="fileName">the file name</param>
/// <returns>true or false</returns>
public bool CanSetImplementation(string fileName)
{
return _xamlImplementationStorage.CanSetImplementation(fileName);
}
/// <summary>
/// Replace the implementation of <paramref name="fileName" />, failing
/// silently if the new content does not compile. (but still logging)
/// </summary>
/// <param name="fileName">the file name</param>
/// <param name="fileContent">the new content</param>
public void SetImplementation(string fileName, string fileContent)
{
_xamlImplementationStorage.SetImplementation(fileName, fileContent, false);
}
/// <summary>
/// Add all the types from all known assemblies, then force-JIT everything
/// again.
/// </summary>
private void AddAssemblies(bool reload = true)
{
foreach (var a in _reflectionManager.Assemblies)
{
if (!_knownAssemblies.Contains(a))
{
_knownAssemblies.Add(a);
_xamlImplementationStorage.Add(a);
_xamlJitCompiler = null;
}
}
if (reload)
_xamlImplementationStorage.ForceReloadAll();
}
/// <summary>
/// Populate <paramref name="o" /> using the JIT compiler, if possible.
/// </summary>
/// <param name="t">the static type of <paramref name="o" /></param>
/// <param name="o">a <paramref name="t" /> instance or subclass</param>
/// <returns>true if there was a JITed implementation</returns>
public bool Populate(Type t, object o)
{
return _xamlImplementationStorage.Populate(t, o);
}
/// <summary>
/// Calls <see cref="XamlJitCompiler.Compile"/> using a stored
/// <see cref="XamlJitCompiler"/> instance.
/// </summary>
/// <param name="t">the <see cref="Type"/> that cares about this Xaml</param>
/// <param name="uri">the <see cref="Uri" /> of this xaml (from the type's metadata)</param>
/// <param name="fileName">the filename of this xaml (from the type's metadata)</param>
/// <param name="content">the new content of the xaml file</param>
/// <returns>the MethodInfo for the new JITed implementation</returns>
private MethodInfo? Compile(Type t, Uri uri, string fileName, string content)
{
// initialize XamlJitCompiler lazily because constructing it has
// very high CPU cost
XamlJitCompiler xjit;
lock (this)
{
xjit = _xamlJitCompiler ??= new XamlJitCompiler();
}
var result = xjit.Compile(t, uri, fileName, content);
if (result is XamlJitCompilerResult.Error e)
{
_sawmill.Info($"hot reloading failed: {t.FullName}; {fileName}; {e.Raw.Message} {e.Hint ?? ""}");
return null;
}
if (result is XamlJitCompilerResult.Success s)
{
return s.MethodInfo;
}
throw new InvalidOperationException($"totally unexpected result from compiler operation: {result}");
}
}
#endif