Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HotAvalonia breaks when a control references a private member #11

Open
jpgarza93 opened this issue Jul 11, 2024 · 14 comments
Open

HotAvalonia breaks when a control references a private member #11

jpgarza93 opened this issue Jul 11, 2024 · 14 comments
Labels
bug Something isn't working

Comments

@jpgarza93
Copy link

jpgarza93 commented Jul 11, 2024

My HotReload works fine on many XAML usercontrols, but it does not work on my MainView.xaml usercontrol that contains the entire app. When I make a change to my MainView.xaml the whole screen turns gray. Is there a way for me to debug?

.NET: 8
Avalonia UI: 11.0.11
Visual Studio: 17.10.4
HotAvalonia: 1.1.1
Configuration: Debug
Platform: Any CPU

MainView.xaml header:

<UserControl
    xmlns="https://github.com/avaloniaui"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="RapidSetupX.Views.MainView"
    xmlns:actipro="http://schemas.actiprosoftware.com/avaloniaui"
    xmlns:behaviors="using:RapidSetupX.Resources.Behaviors"
    xmlns:controls="using:RapidSetupX.Resources.Controls"
    xmlns:data="clr-namespace:Avalonia.Data;assembly=Avalonia.Markup"
    xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
    xmlns:ia="clr-namespace:Avalonia.Xaml.Interactions.Core;assembly=Avalonia.Xaml.Interactions"
    xmlns:icon="using:Material.Icons.Avalonia"
    xmlns:views="using:RapidSetupX.Views"
    xmlns:vms="using:RapidSetupX.ViewModels"
    x:CompileBindings="True"
    x:DataType="vms:MainViewModel"
    Design.Height="1000"
    Design.Width="1382">

UI before changes
image

UI after saving a change
image

@Kir-Antipov
Copy link
Owner

Since your view loses all child controls upon reload, I assume that some error tends to occur during its rehydration. Do you see any exceptions in the debug log? HotAvalonia sends all its logs there.

If you do want to debug HotAvalonia yourself, that's possible, of course. Just take a look at how HotReloadDemo is set up:

<!-- This is what one would use in the actual project. -->
<!--
<ItemGroup>
<PackageReference Condition="$(DefineConstants.Contains(ENABLE_XAML_HOT_RELOAD))" Include="Avalonia.Markup.Xaml.Loader" Version="$(AvaloniaVersion)" />
<PackageReference Condition="$(DefineConstants.Contains(ENABLE_XAML_HOT_RELOAD))" Include="HotAvalonia" Version="1.0.0" />
<PackageReference Include="HotAvalonia.Extensions" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
-->
<ItemGroup>
<PackageReference Condition="$(DefineConstants.Contains(ENABLE_XAML_HOT_RELOAD))" Include="Avalonia.Markup.Xaml.Loader" Version="$(AvaloniaVersion)" />
<ProjectReference Condition="$(DefineConstants.Contains(ENABLE_XAML_HOT_RELOAD))" Include="../../src/HotAvalonia/HotAvalonia.csproj" />
<Compile Include="../../src/HotAvalonia.Extensions/AvaloniaHotReloadExtensions.cs" Link="AvaloniaHotReloadExtensions.cs" Visible="false" />
</ItemGroup>

@jpgarza93
Copy link
Author

Unfortunately, I do not see any errors on the Output > Debug window.

This is my .csproj code:

<!-- HotAvalonia -->
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
  <DefineConstants>$(DefineConstants);ENABLE_XAML_HOT_RELOAD</DefineConstants>
</PropertyGroup>
<ItemGroup>
  <PackageReference Condition="$(DefineConstants.Contains(ENABLE_XAML_HOT_RELOAD))" Include="Avalonia.Markup.Xaml.Loader" Version="$(AvaloniaVersion)" />
  <PackageReference Condition="$(DefineConstants.Contains(ENABLE_XAML_HOT_RELOAD))" Include="HotAvalonia" Version="1.1.1" />
  <PackageReference Include="HotAvalonia.Extensions" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
<!-- End HotAvalonia -->

@jpgarza93
Copy link
Author

jpgarza93 commented Jul 11, 2024

I am seeing after a project rebuild

12:37:18.207 [Error] 25880 <= "Unhandled exception. System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation."
12:37:18.211 [Error] 25880 <= " ---> System.IO.FileNotFoundException: Could not load file or assembly 'System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified."
12:37:18.220 [Error] 25880 <= "File name: 'System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'"
12:37:18.220 [Error] 25880 <= "   at Avalonia.Data.BindingValue`1.get_HasValue()"
12:37:18.221 [Error] 25880 <= "   at Avalonia.Data.BindingValue`1.GetValueOrDefault()"
12:37:18.222 [Error] 25880 <= "   at Avalonia.AvaloniaPropertyChangedExtensions.GetNewValue[T](AvaloniaPropertyChangedEventArgs e)"
12:37:18.222 [Error] 25880 <= "   at Avalonia.Application.OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)"
12:37:18.222 [Error] 25880 <= "   at Avalonia.AvaloniaObject.OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change)"
12:37:18.223 [Error] 25880 <= "   at Avalonia.AvaloniaObject.RaisePropertyChanged[T](AvaloniaProperty`1 property, Optional`1 oldValue, BindingValue`1 newValue, BindingPriority priority, Boolean isEffectiveValue)"
12:37:18.223 [Error] 25880 <= "   at Avalonia.PropertyStore.EffectiveValue`1.SetAndRaiseCore(ValueStore owner, StyledProperty`1 property, T value, BindingPriority priority, Boolean isOverriddenCurrentValue, Boolean isCoercedDefaultValue)"
12:37:18.224 [Error] 25880 <= "   at Avalonia.PropertyStore.EffectiveValue`1.SetLocalValueAndRaise(ValueStore owner, StyledProperty`1 property, T value)"
12:37:18.224 [Error] 25880 <= "   at Avalonia.PropertyStore.ValueStore.SetLocalValue[T](StyledProperty`1 property, T value)"
12:37:18.224 [Error] 25880 <= "   at Avalonia.PropertyStore.ValueStore.SetValue[T](StyledProperty`1 property, T value, BindingPriority priority)"
12:37:18.225 [Error] 25880 <= "   at Avalonia.AvaloniaObject.SetValue[T](StyledProperty`1 property, T value, BindingPriority priority)"
12:37:18.226 [Error] 25880 <= "   at Avalonia.Application.set_RequestedThemeVariant(ThemeVariant value)"
12:37:18.226 [Error] 25880 <= "   at RapidSetupX.App.!XamlIlPopulate(IServiceProvider, App) in C:\repos\jpRapidSoftware\RapidStuff\RapidSetupX\RapidSetupX\App.axaml:line 7"
12:37:18.227 [Error] 25880 <= "   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)"
12:37:18.228 [Error] 25880 <= "   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)"
12:37:18.228 [Error] 25880 <= "   --- End of inner exception stack trace ---"
12:37:18.229 [Error] 25880 <= "   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)"
12:37:18.229 [Error] 25880 <= "   at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)"
12:37:18.230 [Error] 25880 <= "   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)"
12:37:18.230 [Error] 25880 <= "   at HotAvalonia.Helpers.AvaloniaControlHelper.Populate(MethodBase populate, IServiceProvider serviceProvider, Object control) in /_/src/HotAvalonia/Helpers/AvaloniaControlHelper.cs:line 89"
12:37:18.231 [Error] 25880 <= "   at HotAvalonia.AvaloniaControlInfo.Populate(IServiceProvider serviceProvider, Object control, MethodBase populateMethod) in /_/src/HotAvalonia/AvaloniaControlInfo.cs:line 162"
12:37:18.232 [Error] 25880 <= "   at HotAvalonia.AvaloniaControlInfo.Populate(IServiceProvider serviceProvider, Object control) in /_/src/HotAvalonia/AvaloniaControlInfo.cs:line 152"
12:37:18.232 [Error] 25880 <= "   at HotAvalonia.AvaloniaControlManager.<>c__DisplayClass13_0.<TrySubscribeToPopulate>g__PopulateOverride|0(IServiceProvider provider, Object control) in /_/src/HotAvalonia/AvaloniaControlManager.cs:line 141"
12:37:18.232 [Error] 25880 <= "   at HotAvalonia.Helpers.AvaloniaControlHelper.<>c__DisplayClass6_0.<TryOverridePopulate>g__PopulateAndOverride|0(Object control) in /_/src/HotAvalonia/Helpers/AvaloniaControlHelper.cs:line 150"
12:37:18.233 [Error] 25880 <= "   at RapidSetupX.App.!XamlIlPopulateTrampoline(App)"
12:37:18.233 [Error] 25880 <= "   at RapidSetupX.App.Initialize() in C:\repos\jpRapidSoftware\RapidStuff\RapidSetupX\RapidSetupX\App.axaml.cs:line 31"
12:37:18.234 [Error] 25880 <= "   at Avalonia.AppBuilder.SetupUnsafe()"
12:37:18.234 [Error] 25880 <= "   at Avalonia.AppBuilder.Setup()"
12:37:18.235 [Error] 25880 <= "   at Avalonia.AppBuilder.SetupWithoutStarting()"
12:37:18.235 [Error] 25880 <= "   at Avalonia.DesignerSupport.Remote.RemoteDesignerEntryPoint.AppInitializer.ConfigureApp(IAvaloniaRemoteTransportConnection transport, CommandLineArgs args, Object obj)"
12:37:18.236 [Error] 25880 <= "   at Avalonia.DesignerSupport.Remote.RemoteDesignerEntryPoint.Main(String[] cmdline)"
12:37:18.236 [Error] 25880 <= "   at Avalonia.Designer.HostApp.Program.Main(String[] args)"
12:37:20.203 [Error]  Connection error
System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
   at System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult)
   at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
   --- End of inner exception stack trace ---
   at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncTrimPromise`1.Complete(TInstance thisRef, Func`3 endMethod, IAsyncResult asyncResult, Boolean requiresSynchronization)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Avalonia.Remote.Protocol.BsonStreamTransportConnection.<ReadExact>d__15.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Avalonia.Remote.Protocol.BsonStreamTransportConnection.<Reader>d__16.MoveNext()

@Kir-Antipov
Copy link
Owner

I don't think this is relevant to the current issue: at Avalonia.Designer.HostApp.Program.Main(String[] args). This appears to be a problem with Avalonia's designer, which is unrelated to your app's runtime.

@jpgarza93
Copy link
Author

From my project, I can access HotAvalonia's AvaloniaHotReloadContext.cs file. I put a breakpoint on:

catch (Exception e)
{
LoggingHelper.Logger?.Log(this, "Failed to reload {Type} ({Uri}): {Error}", controlManager.Control.ControlType, controlManager.Control.Uri, e);

Now I can see that when I click save I get the following error:

Message

Attempt by method 'Builder_e1baabed55ab465da7fb6f8b612caa12_avares___RapidSetupX_Views_MainView_axaml.__AvaloniaXamlIlPopulate(System.IServiceProvider, RapidSetupX.Views.MainView)' to access method 'RapidSetupX.Views.MainView.RootVisual_PointerWheelChanged(System.Object, Avalonia.Input.PointerWheelEventArgs)' failed.

SerializationStackTraceString

   at Builder_e1baabed55ab465da7fb6f8b612caa12_avares___RapidSetupX_Views_MainView_axaml.__AvaloniaXamlIlPopulate(IServiceProvider, MainView)
   at Avalonia.Markup.Xaml.XamlIl.AvaloniaXamlIlRuntimeCompiler.LoadOrPopulate(Type created, Object rootInstance, IServiceProvider parentServiceProvider)
   at Avalonia.Markup.Xaml.XamlIl.AvaloniaXamlIlRuntimeCompiler.<>c.<LoadGroupSreCore>b__16_2(ValueTuple`2 t)
   at System.Linq.Enumerable.SelectEnumerableIterator`2.ToArray()
   at Avalonia.Markup.Xaml.XamlIl.AvaloniaXamlIlRuntimeCompiler.LoadGroupSreCore(IReadOnlyCollection`1 documents, RuntimeXamlLoaderConfiguration configuration)
   at Avalonia.Markup.Xaml.XamlIl.AvaloniaXamlIlRuntimeCompiler.LoadSreCore(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
   at Avalonia.Markup.Xaml.XamlIl.AvaloniaXamlIlRuntimeCompiler.LoadSre(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
   at Avalonia.Markup.Xaml.XamlIl.AvaloniaXamlIlRuntimeCompiler.Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
   at Avalonia.Markup.Xaml.AvaloniaRuntimeXamlLoader.Load(Stream stream, Assembly localAssembly, Object rootInstance, Uri uri, Boolean designMode)
   at Avalonia.Markup.Xaml.AvaloniaRuntimeXamlLoader.Load(String xaml, Assembly localAssembly, Object rootInstance, Uri uri, Boolean designMode)
   at HotAvalonia.Helpers.AvaloniaControlHelper.Load(String xaml, Uri uri, Object control, MethodInfo& compiledPopulateMethod) in /_/src/HotAvalonia/Helpers/AvaloniaControlHelper.cs:line 48
   at HotAvalonia.AvaloniaControlInfo.Load(String xaml, Object control, MethodInfo& compiledPopulateMethod) in /_/src/HotAvalonia/AvaloniaControlInfo.cs:line 127
   at HotAvalonia.AvaloniaControlManager.ReloadAsync(String xaml, CancellationToken cancellationToken) in /_/src/HotAvalonia/AvaloniaControlManager.cs:line 93
   at HotAvalonia.AvaloniaControlManager.ReloadAsync(CancellationToken cancellationToken) in /_/src/HotAvalonia/AvaloniaControlManager.cs:line 76
   at HotAvalonia.AvaloniaHotReloadContext.OnChanged(Object sender, FileSystemEventArgs args) in /_/src/HotAvalonia/AvaloniaHotReloadContext.cs:line 112

StackTrace

   at Builder_e1baabed55ab465da7fb6f8b612caa12_avares___RapidSetupX_Views_MainView_axaml.__AvaloniaXamlIlPopulate(IServiceProvider , MainView )
   at Avalonia.Markup.Xaml.XamlIl.AvaloniaXamlIlRuntimeCompiler.LoadOrPopulate(Type created, Object rootInstance, IServiceProvider parentServiceProvider) in /_/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs:line 331
   at Avalonia.Markup.Xaml.XamlIl.AvaloniaXamlIlRuntimeCompiler.<>c.<LoadGroupSreCore>b__16_2(ValueTuple`2 t) in /_/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs:line 277
   at System.Linq.Enumerable.SelectEnumerableIterator`2.ToArray() in /_/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs:line 27
   at Avalonia.Markup.Xaml.XamlIl.AvaloniaXamlIlRuntimeCompiler.LoadGroupSreCore(IReadOnlyCollection`1 documents, RuntimeXamlLoaderConfiguration configuration) in /_/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs:line 276
   at Avalonia.Markup.Xaml.XamlIl.AvaloniaXamlIlRuntimeCompiler.LoadSreCore(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) in /_/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs:line 283
   at Avalonia.Markup.Xaml.XamlIl.AvaloniaXamlIlRuntimeCompiler.LoadSre(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) in /_/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs:line 172
   at Avalonia.Markup.Xaml.XamlIl.AvaloniaXamlIlRuntimeCompiler.Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) in /_/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs:line 344
   at Avalonia.Markup.Xaml.AvaloniaRuntimeXamlLoader.Load(Stream stream, Assembly localAssembly, Object rootInstance, Uri uri, Boolean designMode) in /_/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs:line 47
   at Avalonia.Markup.Xaml.AvaloniaRuntimeXamlLoader.Load(String xaml, Assembly localAssembly, Object rootInstance, Uri uri, Boolean designMode) in /_/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs:line 31
   at HotAvalonia.Helpers.AvaloniaControlHelper.Load(String xaml, Uri uri, Object control, MethodInfo& compiledPopulateMethod) in /_/src/HotAvalonia/Helpers/AvaloniaControlHelper.cs:line 48
   at HotAvalonia.AvaloniaControlInfo.Load(String xaml, Object control, MethodInfo& compiledPopulateMethod) in /_/src/HotAvalonia/AvaloniaControlInfo.cs:line 127
   at HotAvalonia.AvaloniaControlManager.<ReloadAsync>d__11.MoveNext() in /_/src/HotAvalonia/AvaloniaControlManager.cs:line 93
   at HotAvalonia.AvaloniaControlManager.<ReloadAsync>d__10.MoveNext() in /_/src/HotAvalonia/AvaloniaControlManager.cs:line 76
   at HotAvalonia.AvaloniaHotReloadContext.<OnChanged>d__10.MoveNext() in /_/src/HotAvalonia/AvaloniaHotReloadContext.cs:line 112

@jpgarza93
Copy link
Author

I do have this code in my MainView.axaml.cs

private void RootVisual_PointerWheelChanged(object? sender, PointerWheelEventArgs e)
{
    if (e.KeyModifiers.HasFlag(KeyModifiers.Control))
    {
        var delta = e.Delta.Y;

        Zoom(delta);

        // Mark the event as handled
        e.Handled = true;
    }
}

@jpgarza93
Copy link
Author

jpgarza93 commented Jul 11, 2024

Looks like I have figured it out.

Events such as the one above need to be public.

Now I can see hot reload changes correctly

@Kir-Antipov
Copy link
Owner

Yup, unfortunately, this is a current limitation of Avalonia.Markup.Xaml.Loader, which I cannot fix on my end. Avalonia compiles the XAML provided to it into a regular method in a dynamic assembly, which is subject to the standard accessibility checks enforced by the Runtime. This means that the method cannot directly access private members of other types, as in your case.

The problem could be circumvented if Avalonia.Markup.Xaml.Loader compiled XAML using a DynamicMethod, which can be made exempt from the visibility checks performed by JIT. However, this requires an upstream change. I'll talk to the Avalonia folks about that.

For now, as you've already figured out, you can temporarily change the access modifiers on your events to public. It's not the prettiest solution in the world, but it will work.

@Kir-Antipov Kir-Antipov changed the title Is there a way to debug HotAvalonia in Visual Studio? My app turns 1 color after saving a XAML change. HotAvalonia breaks when a control references a private member Jul 13, 2024
@Kir-Antipov Kir-Antipov added the bug Something isn't working label Jul 13, 2024
@jpgarza93
Copy link
Author

Thank you very much for all your efforts and time. This library is life-changing

@Kir-Antipov
Copy link
Owner

Thanks a lot for the kind words! :)
I'm glad it's been of help, even though the library still has some limitations. I hope to get rid of most of them by v2.0.0. Multi-project hot reload already works on the development branch, and I also have a local prototype for automagic reload for Images and Icons, and so on.

@alanthinker
Copy link

Yup, unfortunately, this is a current limitation of Avalonia.Markup.Xaml.Loader, which I cannot fix on my end. Avalonia compiles the XAML provided to it into a regular method in a dynamic assembly, which is subject to the standard accessibility checks enforced by the Runtime. This means that the method cannot directly access private members of other types, as in your case.

The problem could be circumvented if Avalonia.Markup.Xaml.Loader compiled XAML using a DynamicMethod, which can be made exempt from the visibility checks performed by JIT. However, this requires an upstream change. I'll talk to the Avalonia folks about that.

For now, as you've already figured out, you can temporarily change the access modifiers on your events to public. It's not the prettiest solution in the world, but it will work.

Maybe user can use a local modified Avalonia.Markup.Xaml.Loader to fix it.
It's not used in release mode. so there is no danger to the actual release product
Can you tell me how to change the Avalonia.Markup.Xaml.Loader code for enable it?
Or is there a fork of Avalonia.Markup.Xaml.Loader that fixed it?
Thank you.

@Kir-Antipov
Copy link
Owner

Maybe user can use a local modified Avalonia.Markup.Xaml.Loader to fix it.

Technically, yes - HotAvalonia has a shadow dependency on Avalonia.Markup.Xaml.Loader, meaning you could replace it with any library that follows the same architecture. However,

Or is there a fork of Avalonia.Markup.Xaml.Loader that fixed it?

No, there is no such fork available. Thus, there's nothing to serve as a substitute for the original library.

Avalonia folks don't seem interested in implementing this fix on their end, so I'll explore the possibility of incorporating it directly into HotAvalonia.

@alanthinker
Copy link

It would be great if future versions could support private members, as there are usually many private event handlers in the project.
Thank you very much.

@Kir-Antipov
Copy link
Owner

Yeah, I know, it's a shame we currently have this limitation. Would love to get rid of it too!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants