mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
* Move RobustXaml to a shared package In a near-future change, I'll make it possible to optionally link to this from Robust.Client, which will allow JIT compiling XAML. Also upgrade it to a version of .NET that supports nullability annotations. * Re-namespace packages * Add a JIT compiler, plus hooks that call into it In Debug, after this change, all XAML will be hot reloaded once every time an assembly is reloaded. The new code is compiled with SRE and is _not_ sandboxed -- this is not suitable to run against prod. In Release, the hot reload path is totally skipped, using the same trick as SmugLeaf used in an earlier attempt to implement this functionality. * Hot reload: watcher This is a bit of a horror, but there's not in-engine support for identifying the source tree or the XAML files in it. * Put everything dangerous behind conditional comp * Code cleanup, docs * Fix a bad comment * Deal a little better with crashes in the watcher * Make reload failures Info, since they're expected They were previously causing the integration tests to flag, even though "a few types fail hot reloading because they're internal" is expected behavior. * Fix an unnecessary null check I removed the ability for CompileCore to return null. * injectors: null! strings, default primitives * Tidy documentation (thanks, PJB!) * Reinstate netstandard2.0, abolish Pidgin * Internal-ize all of Robust.Xaml * Add a cautionary note to Sandbox.yml * Shuffle around where conditional compilation occurs * Privatize fields in XamlImplementationStorage * Internalize XamlJitDelegate * Inline some remarks. No cond. comp in Robust.Xaml * Use file-scoped namespaces They aren't allowed at Language Level 8.0. (which I arbitrarily picked for Robust.Xaml because it's the oldest one that would work) * Bump language level for R.Xaml, file namespaces * Force hot reloading off for integration tests * Fix bizarre comment/behavior in XamlImplementationStorage * Consistently use interfaces, even in generated code * Update Robust.Client/ClientIoC.cs --------- Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
227 lines
11 KiB
C#
227 lines
11 KiB
C#
using System.Linq;
|
|
using XamlX;
|
|
using XamlX.Ast;
|
|
using XamlX.Emit;
|
|
using XamlX.IL;
|
|
using XamlX.Transform;
|
|
using XamlX.TypeSystem;
|
|
|
|
namespace Robust.Xaml
|
|
{
|
|
/// <summary>
|
|
/// Emitters & Transformers based on:
|
|
/// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs
|
|
/// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
|
|
/// - https://github.com/AvaloniaUI/Avalonia/blob/afb8ae6f3c517dae912729511483995b16cb31af/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
|
|
/// </summary>
|
|
internal class RobustXamlILCompiler : XamlILCompiler
|
|
{
|
|
public RobustXamlILCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings, bool fillWithDefaults) : base(configuration, emitMappings, fillWithDefaults)
|
|
{
|
|
Transformers.Insert(0, new IgnoredDirectivesTransformer());
|
|
|
|
Transformers.Add(new AddNameScopeRegistration());
|
|
Transformers.Add(new RobustMarkRootObjectScopeNode());
|
|
|
|
Emitters.Add(new AddNameScopeRegistration.Emitter());
|
|
Emitters.Add(new RobustMarkRootObjectScopeNode.Emitter());
|
|
}
|
|
|
|
class AddNameScopeRegistration : IXamlAstTransformer
|
|
{
|
|
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
|
|
{
|
|
if (node is XamlPropertyAssignmentNode pa)
|
|
{
|
|
if (pa.Property.Name == "Name"
|
|
&& pa.Property.DeclaringType.FullName == "Robust.Client.UserInterface.Control")
|
|
{
|
|
if (context.ParentNodes().FirstOrDefault() is XamlManipulationGroupNode mg
|
|
&& mg.Children.OfType<RobustNameScopeRegistrationXamlIlNode>().Any())
|
|
return node;
|
|
|
|
IXamlAstValueNode? value = null;
|
|
for (var c = 0; c < pa.Values.Count; c++)
|
|
{
|
|
if (pa.Values[c].Type.GetClrType().Equals(context.Configuration.WellKnownTypes.String))
|
|
{
|
|
value = pa.Values[c];
|
|
if (!(value is XamlAstTextNode))
|
|
{
|
|
var local = new XamlAstCompilerLocalNode(value);
|
|
// Wrap original in local initialization
|
|
pa.Values[c] = new XamlAstLocalInitializationNodeEmitter(value, value, local);
|
|
// Use local
|
|
value = local;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (value != null)
|
|
{
|
|
var objectType = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().FirstOrDefault()?.Type.GetClrType();
|
|
return new XamlManipulationGroupNode(pa)
|
|
{
|
|
Children =
|
|
{
|
|
pa,
|
|
new RobustNameScopeRegistrationXamlIlNode(value, objectType)
|
|
}
|
|
};
|
|
}
|
|
}
|
|
/*else if (pa.Property.CustomAttributes.Select(attr => attr.Type).Intersect(context.Configuration.TypeMappings.DeferredContentPropertyAttributes).Any())
|
|
{
|
|
pa.Values[pa.Values.Count - 1] =
|
|
new NestedScopeMetadataNode(pa.Values[pa.Values.Count - 1]);
|
|
}*/
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
class RobustNameScopeRegistrationXamlIlNode : XamlAstNode, IXamlAstManipulationNode
|
|
{
|
|
public IXamlAstValueNode Name { get; set; }
|
|
public IXamlType? TargetType { get; }
|
|
|
|
public RobustNameScopeRegistrationXamlIlNode(IXamlAstValueNode name, IXamlType? targetType) : base(name)
|
|
{
|
|
TargetType = targetType;
|
|
Name = name;
|
|
}
|
|
|
|
public override void VisitChildren(IXamlAstVisitor visitor)
|
|
=> Name = (IXamlAstValueNode)Name.Visit(visitor);
|
|
}
|
|
|
|
internal class Emitter : IXamlAstLocalsNodeEmitter<IXamlILEmitter, XamlILNodeEmitResult>
|
|
{
|
|
public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContextWithLocals<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
|
|
{
|
|
if (node is RobustNameScopeRegistrationXamlIlNode registration)
|
|
{
|
|
|
|
var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
|
|
f.Name == XamlCustomizations.ContextNameScopeFieldName);
|
|
var namescopeRegisterFunction = context.Configuration.TypeSystem
|
|
.FindType("Robust.Client.UserInterface.XAML.NameScope").Methods
|
|
.First(m => m.Name == "Register");
|
|
|
|
using (var targetLoc = context.GetLocalOfType(context.Configuration.TypeSystem.FindType("Robust.Client.UserInterface.Control")))
|
|
{
|
|
|
|
codeGen
|
|
// var target = {pop}
|
|
.Stloc(targetLoc.Local)
|
|
// _context.NameScope.Register(Name, target)
|
|
.Ldloc(context.ContextLocal)
|
|
.Ldfld(scopeField);
|
|
|
|
context.Emit(registration.Name, codeGen, registration.Name.Type.GetClrType());
|
|
|
|
codeGen
|
|
.Ldloc(targetLoc.Local)
|
|
.EmitCall(namescopeRegisterFunction, true);
|
|
}
|
|
|
|
return XamlILNodeEmitResult.Void(1);
|
|
}
|
|
return default!; // PYREX NOTE: This doesn't seem safe! But it's what we were doing before Nullable
|
|
}
|
|
}
|
|
}
|
|
|
|
class RobustMarkRootObjectScopeNode : IXamlAstTransformer
|
|
{
|
|
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
|
|
{
|
|
if (!context.ParentNodes().Any()
|
|
&& node is XamlValueWithManipulationNode mnode)
|
|
{
|
|
mnode.Manipulation = new XamlManipulationGroupNode(mnode,
|
|
new[]
|
|
{
|
|
mnode.Manipulation,
|
|
new HandleRootObjectScopeNode(mnode)
|
|
});
|
|
}
|
|
return node;
|
|
}
|
|
class HandleRootObjectScopeNode : XamlAstNode, IXamlAstManipulationNode
|
|
{
|
|
public HandleRootObjectScopeNode(IXamlLineInfo lineInfo) : base(lineInfo)
|
|
{
|
|
}
|
|
}
|
|
internal class Emitter : IXamlILAstNodeEmitter
|
|
{
|
|
public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
|
|
{
|
|
if (!(node is HandleRootObjectScopeNode))
|
|
{
|
|
return null!; // PYREX NOTE: This doesn't seem safe, but it predates Nullable on this file
|
|
}
|
|
|
|
var controlType = context.Configuration.TypeSystem.FindType("Robust.Client.UserInterface.Control");
|
|
|
|
var next = codeGen.DefineLabel();
|
|
var dontAbsorb = codeGen.DefineLabel();
|
|
var end = codeGen.DefineLabel();
|
|
var contextScopeField = context.RuntimeContext.ContextType.Fields.First(f =>
|
|
f.Name == XamlCustomizations.ContextNameScopeFieldName);
|
|
var controlNameScopeField = controlType.Fields.First(f => f.Name == "NameScope");
|
|
var nameScopeType = context.Configuration.TypeSystem
|
|
.FindType("Robust.Client.UserInterface.XAML.NameScope");
|
|
var nameScopeCompleteMethod = nameScopeType.Methods.First(m => m.Name == "Complete");
|
|
var nameScopeAbsorbMethod = nameScopeType.Methods.First(m => m.Name == "Absorb");
|
|
using (var local = codeGen.LocalsPool.GetLocal(controlType))
|
|
{
|
|
codeGen
|
|
.Isinst(controlType)
|
|
.Dup()
|
|
.Stloc(local.Local) //store control in local field
|
|
.Brfalse(next) //if control is null, move to next (this should never happen but whatev, avalonia does it)
|
|
.Ldloc(context.ContextLocal)
|
|
.Ldfld(contextScopeField)
|
|
.Ldloc(local.Local) //load control from local field
|
|
.Ldfld(controlNameScopeField) //load namescope field from control
|
|
.EmitCall(nameScopeAbsorbMethod, true)
|
|
.Ldloc(local.Local) //load control
|
|
.Ldloc(context.ContextLocal) //load contextObject
|
|
.Ldfld(contextScopeField) //load namescope field from context obj
|
|
.Stfld(controlNameScopeField) //store namescope field in control
|
|
.MarkLabel(next)
|
|
.Ldloc(context.ContextLocal)
|
|
.Ldfld(contextScopeField)
|
|
.EmitCall(nameScopeCompleteMethod, true); //set the namescope as complete
|
|
}
|
|
|
|
return XamlILNodeEmitResult.Void(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
class IgnoredDirectivesTransformer : IXamlAstTransformer
|
|
{
|
|
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
|
|
{
|
|
if (node is XamlAstObjectNode astNode)
|
|
{
|
|
astNode.Children.RemoveAll(n =>
|
|
n is XamlAstXmlDirective dir &&
|
|
dir.Namespace == XamlNamespaces.Xaml2006 &&
|
|
(dir.Name == "Class" ||
|
|
dir.Name == "Precompile" ||
|
|
dir.Name == "FieldModifier" ||
|
|
dir.Name == "ClassModifier"));
|
|
}
|
|
|
|
return node;
|
|
}
|
|
}
|
|
}
|
|
}
|