AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
25.39k stars 2.2k forks source link

XAML hot reload support #3266

Open hez2010 opened 4 years ago

hez2010 commented 4 years ago

All of WPF, UWP, Xamarin and Xamarin.Forms support XAML hot reload, which means that you can edit XAML files in debug session and changes will apply immediately on the running app without needs of restarting the debug session. What's more, all binding states and contexts will not lose during hot reload.

I hope that Avalonia could support XAML hot-reload in the future :)

kekekeks commented 4 years ago

The way it's done in those platforms heavily depends on the separate XAML AST built by IDE services that can produce XAML AST diffs to be applied in the running application. Which we currently don't have.

So probably not this year.

worldbeater commented 4 years ago

Attempted to bring XAML (and C#) hot reload support to Avalonia applications by relying on dotnet watch build tooling. However, the tool is quite dirty and requires heavy testing. And might be comparatively slow.

https://github.com/AvaloniaUI/Live.Avalonia

notanaverageman commented 3 years ago

I have been working on a POC for this feature for a few days. I have managed to change constant and text properties while the app is running. Of course these are baby steps and before going down the rabbit hole I want to get some feedback. Here is a gif for the POC:

Basic Hot Reload

First off, based on my research, the way Visual Studio handles hot reload for WPF is somewhat like this:

My approach is a bit different than Visual Studio's:

Here are some advantages and disadvantages compared to the Visual Studio way:

Advantages:

Disadvantages:

What are your thoughts?

hez2010 commented 3 years ago

@notanaverageman Sounds great. But the approach of watching file system lacks the ability to notify changes when code changed but hasn't been saved into disk yet, which is supported by Visual Studio.

notanaverageman commented 3 years ago

I have managed to create a somehow functional hot reload. Instead of getting diffs from parsed XAML AST I inject some placeholder calls in IL code and parse these calls when XAML file changes. This have a few advantages:

This requires some compiler support. Actually it can be done solely on compiler side if Avalonia specific tasks, such as clearing a property, can be injected as plugins. I have created a draft PR to get early feedback: https://github.com/AvaloniaUI/Avalonia/pull/5363

Here are some details on how the approach works:

While compiling the XAML code following placeholder calls are inserted:

When hot reload is requested, the IL instructions are loaded via XAML compiler and parsed for the diff operation. An object tree is created by parsing the object initialization and property setter markers.

Diffing is done by comparing nodes at the same level and getting a score for each pair. Object scores includes the property scores and child object scores recursively. Different object types result in negative score and do not match. An object is paired with the corresponding object that has the maximum score among other siblings to minimize the edit operations. Old objects that do not match any new object are marked for removal and new objects without a match are marked for addition.

Properties are compared for addition and removal in the same way the objects are compared. Change in the values are detected by comparing the IL codes byte by byte. If there is a difference, the property is marked as changed.

After diffing is done, actions are generated for adding/removing objects and adding/removing/changing properties. Context initialization instructions are given to each action that requires a context. Each action creates a dynamic method on the fly with following content:

Method is compiled and called with every object that is registered for that XAML file. At the end the new populate method is assigned to !XamlIlPopulateOverride field.

Here is the non exhaustive list of things to do:

kekekeks commented 3 years ago

I suspect that emitted MSIL stage might be a bit too late. It might be better to diff the AST nodes between 2 versions of the AST instead.

One issue I know of is that in some instances a child is added to parent collection before initialization and in other instances after.

That's controlled by UsableDuringInitializationAttribute, see https://docs.microsoft.com/en-us/dotnet/api/system.windows.markup.usableduringinitializationattribute?view=net-5.0 for more details

Emit the classes that contain marker methods and weak object storage via compiler.

We can have hot-reload support classes in regular C# code and place them in Avalonia.Markup.Xaml assembly. There already are some runtime helpers.

notanaverageman commented 3 years ago

Thanks for the feedback. I assume that the diff operations would be very similar on AST level. However, I would like to use the compiler generated IL code to apply the changes. Would it be possible to track which AST node corresponds to which IL block? AST changes a lot after transformation, though the general shape seems similar:

Before transformation:

``` XamlX.Ast.XamlAstObjectNode Avalonia.Markup.Xaml.UnitTests:Avalonia.Markup.Xaml.UnitTests.HotReload.TestControl XamlX.Ast.XamlAstXmlDirective XamlX.Ast.XamlAstTextNode xml!!http://schemas.microsoft.com/winfx/2006/xaml:String XamlX.Ast.XamlAstObjectNode xml!!https://github.com/avaloniaui:Border XamlX.Ast.XamlAstObjectNode xml!!https://github.com/avaloniaui:StackPanel ```

After transformation:

``` XamlX.Ast.XamlValueWithManipulationNode XamlX.Ast.XamlAstNewClrObjectNode Avalonia.Markup.Xaml.UnitTests:Avalonia.Markup.Xaml.UnitTests.HotReload.TestControl XamlX.Ast.XamlManipulationGroupNode XamlX.Ast.XamlObjectInitializationNode XamlX.Ast.XamlManipulationGroupNode XamlX.Ast.XamlPropertyAssignmentNode XamlX.Ast.XamlValueNodeWithBeginInit XamlX.Ast.XamlAstLocalInitializationNodeEmitter XamlX.Ast.XamlAstNewClrObjectNode Avalonia.Controls:Avalonia.Controls.Border XamlX.Ast.XamlAstCompilerLocalNode XamlX.Ast.XamlAstManipulationImperativeNode XamlX.Ast.XamlAstImperativeValueManipulation XamlX.Ast.XamlAstCompilerLocalNode XamlX.Ast.XamlObjectInitializationNode XamlX.Ast.XamlManipulationGroupNode XamlX.Ast.XamlPropertyAssignmentNode XamlX.Ast.XamlValueNodeWithBeginInit XamlX.Ast.XamlAstLocalInitializationNodeEmitter XamlX.Ast.XamlAstNewClrObjectNode Avalonia.Controls:Avalonia.Controls.StackPanel XamlX.Ast.XamlAstCompilerLocalNode XamlX.Ast.XamlAstManipulationImperativeNode XamlX.Ast.XamlAstImperativeValueManipulation XamlX.Ast.XamlAstCompilerLocalNode XamlX.Ast.XamlObjectInitializationNode XamlX.Ast.XamlManipulationGroupNode Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers.AvaloniaXamlIlRootObjectScope+HandleRootObjectScopeNode ```

Kir-Antipov commented 1 year ago

I'm currently working on an app, and as a non-front-end developer, I found myself in need of some assistance in that department. The challenge lies in the fact that front-end developers are super-used to having a plethora of excessively reach developer tools at their disposal, many of which they rely on extensively. For instance, the ability to simply hit Ctrl+S and witness the changes being instantly applied to the interface they are building is just mandatory nowadays. Unfortunately, Avalonia falls short in this regard, creating unnecessary barriers to entry, even though many web development skills can be smoothly transitioned to Avalonia development. Currently, the only available options for achieving real-time feedback are:

1) A bloated Windows-only 20+ GB proprietary bloated IDE 2) A paid Java IDE

And neither of these options is particularly appealing, to be quite frankly honest with you, especially for the average front-end developer who doesn't usually possess one of those. While an upcoming Visual Studio Code extension should improve matters somewhat, it still falls short of being an ideal solution since not everyone uses (or wishes to use) VSC. Consequently, when I recommend Avalonia to develop something, I often encounter a lot of pushback because of all that, which is just disheartening because I love Avalonia and have been following its progress for years.

However, I may be no front-end developer, but I'm no coward either. So, if someone requires hot reload functionality for Avalonia to finally stop trying to convince me to rewrite everything with Flutter, then fine, I'll do it myself.

Here are a few examples of what I've managed to accomplish:

Hot Reload: App Hot Reload: User Control
Hot Reload: View Hot Reload: Styles
Hot Reload: Resources Hot Reload: Window

If you'd like to try it out, you're more than welcome to visit my Kir-Antipov/HotAvalonia repo.

I understand that there are other priorities at the moment. Nevertheless, I just want to make a point that hot reload capabilities are crucial for enhancing the developer experience and overall accessibility of a UI framework nowadays. This is essential for making it more appealing to developers of all backgrounds. Therefore, it would be fantastic to one day witness the proper implementation of hot reload functionality within Avalonia itself. Alternatively, it would be greatly appreciated if the core team could share their vision on this topic and outline how they envision such functionality being implemented, thereby enabling the community to contribute to this endeavor.

timunie commented 12 months ago

@Kir-Antipov impressive work. 👏

Thank you for sharing this with us ❤️

patrickklaeren commented 1 month ago

@Kir-Antipov VS is only 20+GB when you install every workload.