Skip to content

Commit

Permalink
feat: Add mixed C#/XAML hot reload support
Browse files Browse the repository at this point in the history
  • Loading branch information
jeromelaban committed Nov 28, 2021
1 parent d42524b commit 6d3ec36
Show file tree
Hide file tree
Showing 21 changed files with 429 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,8 @@
<Message Importance="high"
Condition="'$(UnoUIUseRoslynSourceGenerators)'==''"
Text="Uno.UI is using Uno.SourceGenerators" />

<WriteLinesToFile File="$(IntermediateOutputPath)\build-time-generator.touch" />
</Target>

<Target Name="_RemoveRoslynUnoSourceGeneration" BeforeTargets="CoreCompile" Condition="'$(UnoUIUseRoslynSourceGenerators)'=='false'">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#nullable enable

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace Uno.UI.SourceGenerators.XamlGenerator
{
internal class GenerationRunInfo
{
private ConcurrentDictionary<string, GenerationRunFileInfo> _fileInfo = new ConcurrentDictionary<string, GenerationRunFileInfo>();

internal GenerationRunInfo(GenerationRunInfoManager manager, int index)
{
Index = index;
Manager = manager;
}

internal GenerationRunInfoManager Manager { get; }

internal int Index { get; }

internal GenerationRunFileInfo GetRunFileInfo(string fileId)
=> _fileInfo.GetOrAdd(fileId, f => new GenerationRunFileInfo(this, f));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Uno.Roslyn;

#if NETFRAMEWORK
using Uno.SourceGeneration;
#endif

namespace Uno.UI.SourceGenerators.XamlGenerator
{
internal class GenerationRunInfoManager
{
private List<GenerationRunInfo> _runs = new List<GenerationRunInfo>();
private DateTime _previousWriteTime;

internal GenerationRunInfoManager()
{
}

public IEnumerable<GenerationRunInfo> PreviousRuns
=> _runs.AsEnumerable();

internal GenerationRunInfo CreateRun()
{
var runInfo = new GenerationRunInfo(this, _runs.Count);

_runs.Add(runInfo);

return runInfo;
}

internal void Update(GeneratorExecutionContext context)
{
var intermediateOutputPath = context.GetMSBuildPropertyValue("IntermediateOutputPath");

if (intermediateOutputPath != null)
{
var runFilePath = Path.Combine(intermediateOutputPath, "build-time-generator.touch");

if (File.Exists(runFilePath))
{
var lastWriteTime = new FileInfo(runFilePath).LastWriteTime;

if(lastWriteTime > _previousWriteTime)
{
_previousWriteTime = lastWriteTime;

// Clear the existing runs if a full build has been started
_runs.Clear();
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#nullable enable

using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;

namespace Uno.UI.SourceGenerators.XamlGenerator
{
internal class GenerationRunFileInfo
{
private string _fileId;
private Dictionary<INamedTypeSymbol, int> _appliedTypes = new Dictionary<INamedTypeSymbol, int>();

public GenerationRunFileInfo(GenerationRunInfo runInfo,string fileId)
{
_fileId = fileId;
RunInfo = runInfo;
}

internal GenerationRunInfo RunInfo { get; }

internal string? ComponentCode { get; set; }

internal IReadOnlyDictionary<INamedTypeSymbol, int> AppliedTypes => _appliedTypes;

internal void SetAppliedTypes(Dictionary<INamedTypeSymbol, int> appliedTypes)
{
foreach(var type in appliedTypes)
{
_appliedTypes.Add(type.Key, type.Value);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ private string GetSourceLink(MSBuildItem projectItemInstance)
return link;
}

public KeyValuePair<string, string>[] Generate()
public KeyValuePair<string, string>[] Generate(GenerationRunInfo generationRunInfo)
{
var stopwatch = Stopwatch.StartNew();

Expand Down Expand Up @@ -315,7 +315,8 @@ public KeyValuePair<string, string>[] Generate()
isUnoAssembly: IsUnoAssembly,
isLazyVisualStateManagerEnabled: _isLazyVisualStateManagerEnabled,
generatorContext: _generatorContext,
xamlResourcesTrimming: _xamlResourcesTrimming
xamlResourcesTrimming: _xamlResourcesTrimming,
generationRunFileInfo: generationRunInfo.GetRunFileInfo(file.UniqueID)
)
.GenerateFile()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ namespace Uno.UI.SourceGenerators.XamlGenerator
[Generator]
public class XamlCodeGenerator : ISourceGenerator
{
private readonly GenerationRunInfoManager _generationRunInfoManager = new GenerationRunInfoManager();

public void Initialize(GeneratorInitializationContext context)
{
DependenciesInitializer.Init();
Expand All @@ -36,8 +38,10 @@ public void Execute(GeneratorExecutionContext context)

if (PlatformHelper.IsValidPlatform(context))
{
_generationRunInfoManager.Update(context);

var gen = new XamlCodeGeneration(context);
var genereratedTrees = gen.Generate();
var genereratedTrees = gen.Generate(_generationRunInfoManager.CreateRun());

foreach (var tree in genereratedTrees)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,15 @@ internal partial class XamlFileGenerator
// visual tree. See https://github.com/unoplatform/uno/issues/61
private readonly bool _skipUserControlsInVisualTree;

private readonly List<INamedTypeSymbol> _xamlAppliedTypes = new List<INamedTypeSymbol>();
/// <summary>
/// Holds information about multiple generator runs
/// </summary>
private readonly GenerationRunFileInfo _generationRunFileInfo;

/// <summary>
/// Information about types used in .Apply() scenarios
/// </summary>
private readonly Dictionary<INamedTypeSymbol, int> _xamlAppliedTypes = new Dictionary<INamedTypeSymbol, int>();

private readonly INamedTypeSymbol _elementStubSymbol;
private readonly INamedTypeSymbol _contentPresenterSymbol;
Expand Down Expand Up @@ -218,7 +226,8 @@ public XamlFileGenerator(
bool isUnoAssembly,
bool isLazyVisualStateManagerEnabled,
GeneratorExecutionContext generatorContext,
bool xamlResourcesTrimming)
bool xamlResourcesTrimming,
GenerationRunFileInfo generationRunFileInfo)
{
_fileDefinition = file;
_targetPath = targetPath;
Expand All @@ -239,6 +248,7 @@ public XamlFileGenerator(
_isLazyVisualStateManagerEnabled = isLazyVisualStateManagerEnabled;
_generatorContext = generatorContext;
_xamlResourcesTrimming = xamlResourcesTrimming;
_generationRunFileInfo = generationRunFileInfo;

InitCaches();

Expand Down Expand Up @@ -411,60 +421,49 @@ private string InnerGenerateFile()

using (writer.BlockInvariant("partial class {0} : {1}", _className.className, controlBaseType.ToDisplayString()))
{
var isDirectUserControlChild = _skipUserControlsInVisualTree && IsUserControl(topLevelControl.Type, checkInheritance: false);

using (Scope(_className.ns, _className.className))
{
using (writer.BlockInvariant(BuildControlInitializerDeclaration(topLevelControl)))
if (_generationRunFileInfo.RunInfo.Index == 0)
{
if (IsApplication(topLevelControl.Type))
{
BuildApplicationInitializerBody(writer, topLevelControl);
}
else
var componentBuilder = new IndentedStringBuilder();

using (componentBuilder.Indent(writer.CurrentLevel))
{
BuildGenericControlInitializerBody(writer, topLevelControl, isDirectUserControlChild);
BuildNamedResources(writer, _namedResources);
}
BuildInitializeComponent(componentBuilder, topLevelControl, controlBaseType, false);

BuildCompiledBindingsInitializer(writer, _className.className, controlBaseType);
_generationRunFileInfo.SetAppliedTypes(_xamlAppliedTypes);

if (isDirectUserControlChild)
{
writer.AppendLineInvariant("return content;");
}
}
TryBuildElementStubHolders(componentBuilder);

if (isDirectUserControlChild)
{
using (writer.BlockInvariant("public {0}(bool skipsInitializeComponents)", _className.className))
{
BuildPartials(componentBuilder, isStatic: false);

BuildBackingFields(componentBuilder);

BuildChildSubclasses(componentBuilder);

BuildComponentFields(componentBuilder);

BuildCompiledBindings(componentBuilder, _className.className);

_generationRunFileInfo.ComponentCode = componentBuilder.ToString();
}

using (writer.BlockInvariant("private void InitializeComponent()"))
writer.AppendLineInvariant("{0}", componentBuilder.ToString());
}
else
{
if (_generationRunFileInfo.RunInfo.Manager.PreviousRuns.FirstOrDefault(r => r.GetRunFileInfo(_fileUniqueId)?.ComponentCode != null) is var runFileInfo)
{
writer.AppendLineInvariant("Content = (_View)GetContent();");
var generationRunFileInfo = runFileInfo.GetRunFileInfo(_fileUniqueId);

BuildCompiledBindingsInitializer(writer, _className.className, controlBaseType);
writer.AppendLineInvariant("{0}", generationRunFileInfo.ComponentCode);

if (_isDebug)
foreach(var type in generationRunFileInfo.AppliedTypes)
{
writer.AppendLineInvariant($"global::Uno.UI.FrameworkElementHelper.SetBaseUri(this, \"file:///{_fileDefinition.FilePath.Replace("\\", "/")}\");");
_xamlAppliedTypes.Add(type.Key, type.Value);
}
}
}

TryBuildElementStubHolders(writer);

BuildPartials(writer, isStatic: false);

BuildBackingFields(writer);

BuildChildSubclasses(writer);

BuildComponentFields(writer);

BuildCompiledBindings(writer, _className.className);
}
}
}
Expand All @@ -476,20 +475,55 @@ private string InnerGenerateFile()
return writer.ToString();
}

private void BuildInitializeComponent(IndentedStringBuilder writer, XamlObjectDefinition topLevelControl, INamedTypeSymbol controlBaseType, bool isDirectUserControlChild)
{
using (writer.BlockInvariant($"private void InitializeComponent()"))
{
if (IsApplication(topLevelControl.Type))
{
BuildApplicationInitializerBody(writer, topLevelControl);
}
else
{
if (_isDebug)
{
// Insert hot reload support
writer.AppendLineInvariant($"var __resourceLocator = new global::System.Uri(\"file:///{_fileDefinition.FilePath.Replace("\\", "/")}\");");

using (writer.BlockInvariant($"if(global::Uno.UI.ApplicationHelper.IsLoadableComponent(__resourceLocator))"))
{
writer.AppendLineInvariant($"global::Windows.UI.Xaml.Application.LoadComponent(this, __resourceLocator);");
writer.AppendLineInvariant($"return;");
}
}

BuildGenericControlInitializerBody(writer, topLevelControl, isDirectUserControlChild);
BuildNamedResources(writer, _namedResources);
}

BuildCompiledBindingsInitializer(writer, _className.className, controlBaseType);

if (isDirectUserControlChild)
{
writer.AppendLineInvariant("return content;");
}
}
}

private void BuildXamlApplyBlocks(IndentedStringBuilder writer)
{
TryAnnotateWithGeneratorSource(writer);
using (writer.BlockInvariant("namespace {0}", _defaultNamespace))
{
using (writer.BlockInvariant("static class {0}XamlApplyExtensions", _fileUniqueId))
{
foreach (var typeInfo in _xamlAppliedTypes.Select((type, i) => new { type, Index = i }))
foreach (var typeInfo in _xamlAppliedTypes)
{
writer.AppendLineInvariant($"public delegate void XamlApplyHandler{typeInfo.Index}({GetGlobalizedTypeName(typeInfo.type.ToString())} instance);");
writer.AppendLineInvariant($"public delegate void XamlApplyHandler{typeInfo.Value}({GetGlobalizedTypeName(typeInfo.Key.ToString())} instance);");

writer.AppendLineInvariant($"[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]");
using (writer.BlockInvariant(
$"public static {GetGlobalizedTypeName(typeInfo.type.ToString())} {_fileUniqueId}_XamlApply(this {GetGlobalizedTypeName(typeInfo.type.ToString())} instance, XamlApplyHandler{typeInfo.Index} handler)"
$"public static {GetGlobalizedTypeName(typeInfo.Key.ToString())} {_fileUniqueId}_XamlApply(this {GetGlobalizedTypeName(typeInfo.Key.ToString())} instance, XamlApplyHandler{typeInfo.Value} handler)"
))
{
writer.AppendLineInvariant($"handler(instance);");
Expand Down Expand Up @@ -3672,15 +3706,13 @@ private XamlLazyApplyBlockIIndentedStringBuilder CreateApplyBlock(IIndentedStrin

if (appliedType != null)
{
int typeIndex = _xamlAppliedTypes.IndexOf(appliedType);

if (typeIndex == -1)
if (!_xamlAppliedTypes.TryGetValue(appliedType, out var appliedTypeIndex))
{
_xamlAppliedTypes.Add(appliedType);
typeIndex = _xamlAppliedTypes.Count - 1;
appliedTypeIndex = _xamlAppliedTypes.Count;
_xamlAppliedTypes.Add(appliedType, _xamlAppliedTypes.Count);
}

delegateType = $"{_fileUniqueId}XamlApplyExtensions.XamlApplyHandler{typeIndex}";
delegateType = $"{_fileUniqueId}XamlApplyExtensions.XamlApplyHandler{appliedTypeIndex}";
}

return new XamlLazyApplyBlockIIndentedStringBuilder(writer, closureName, appliedType != null ? _fileUniqueId : null, delegateType);
Expand Down
Loading

0 comments on commit 6d3ec36

Please sign in to comment.