Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
kekyo committed May 28, 2024
1 parent 6b6c7aa commit 6ea1d33
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ namespace EpoxyHello.Avalonia11.ViewModels;
[ViewModel]
public sealed class MainWindowViewModel
{
//public Command Ready { get; }
public Well<Window> MainWindowWell { get; } = Well.Factory.Create<Window>();

public bool IsEnabled { get; private set; }
Expand All @@ -49,6 +48,23 @@ public sealed class MainWindowViewModel
public MainWindowViewModel()
{
// A handler for window opened
//this.MainWindowWell.Add(
// //Window.WindowOpenedEvent,
// "Opened",
// () =>
// {
// this.IsEnabled = true;
// return default;
// },
// (window, obj, ptr) => {
// var dlg = new EventHandler(obj, ptr);
// window.Opened += dlg;
// },
// (window, obj, ptr) => {
// var dlg = new EventHandler(obj, ptr);
// window.Opened -= dlg;
// });

this.MainWindowWell.Add(
Window.WindowOpenedEvent,
() =>
Expand Down
3 changes: 2 additions & 1 deletion playground/EpoxyHello.Wpf/EpoxyHello.Wpf.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFrameworks>net461;netcoreapp3.1;net5.0-windows;net6.0-windows;net7.0-windows</TargetFrameworks>
<TargetFrameworks>net461;netcoreapp3.1;net5.0-windows;net6.0-windows;net7.0-windows;net8.0-windows</TargetFrameworks>
<UseWPF>true</UseWPF>
<EpoxyBuildDebug>true</EpoxyBuildDebug>
</PropertyGroup>

<ItemGroup>
Expand Down
35 changes: 28 additions & 7 deletions playground/EpoxyHello.Wpf/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
Expand All @@ -36,7 +37,7 @@ namespace EpoxyHello.Wpf.ViewModels;
[ViewModel]
public sealed class MainWindowViewModel
{
public Command Ready { get; }
public Well<Window> MainWindowWell { get; } = Well.Factory.Create<Window>();

public bool IsEnabled { get; set; }

Expand All @@ -48,12 +49,32 @@ public sealed class MainWindowViewModel

public MainWindowViewModel()
{
// A handler for window loaded
this.Ready = Command.Factory.Create(() =>
{
this.IsEnabled = true;
return default;
});
// A handler for window opened
//this.MainWindowWell.Add(
// //FrameworkElement.LoadedEvent,
// "Loaded",
// () =>
// {
// this.IsEnabled = true;
// return default;
// },
// (window, obj, ptr) => {
// var dlg = new EventHandler(obj, ptr);
// window.Opened += dlg;
// },
// (window, obj, ptr) => {
// var dlg = new EventHandler(obj, ptr);
// window.Opened -= dlg;
// });

this.MainWindowWell.Add(
//FrameworkElement.LoadedEvent,
"Loaded",
() =>
{
this.IsEnabled = true;
return default;
});

// A handler for fetch button
this.Fetch = Command.Factory.Create(async () =>
Expand Down
7 changes: 2 additions & 5 deletions playground/EpoxyHello.Wpf/Views/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,12 @@
xmlns:viewmodels="clr-namespace:EpoxyHello.Wpf.ViewModels"
xmlns:converters="clr-namespace:EpoxyHello.Wpf.Views.Converters"
mc:Ignorable="d"
Title="EpoxyHello.Wpf" Height="450" Width="800">
Title="EpoxyHello.Wpf" Height="450" Width="800"
epoxy:Fountain.Well="{Binding MainWindowWell}">

<Window.DataContext>
<viewmodels:MainWindowViewModel />
</Window.DataContext>

<epoxy:EventBinder.Events>
<epoxy:Event EventName="Loaded" Command="{Binding Ready}" />
</epoxy:EventBinder.Events>

<DockPanel>
<Button DockPanel.Dock="Top"
Expand Down
167 changes: 150 additions & 17 deletions src/Epoxy.Build/ViewModelInjector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ public sealed class ViewModelInjector
private readonly TypeReference propertyChangingEventHandlerType;
private readonly TypeReference propertyChangedEventHandlerType;

private readonly TypeDefinition? wellExtensionType;

private readonly MethodDefinition addPropertyChanging;
private readonly MethodDefinition removePropertyChanging;
private readonly MethodDefinition addPropertyChanged;
Expand All @@ -81,6 +83,9 @@ public sealed class ViewModelInjector
private readonly MethodDefinition itAddPropertyChanged;
private readonly MethodDefinition itRemovePropertyChanged;

private readonly MethodDefinition? wellAddMethod;
private readonly MethodDefinition? wellAddTEventArgsMethod;

public ViewModelInjector(string[] referencesBasePath, Action<LogLevels, string> message)
{
this.message = message;
Expand All @@ -102,6 +107,18 @@ public ViewModelInjector(string[] referencesBasePath, Action<LogLevels, string>
}
);

var epoxyPath = referencesBasePath.
Select(basePath => Path.Combine(basePath, "Epoxy.dll")).
First(File.Exists);

var epoxyAssembly = AssemblyDefinition.ReadAssembly(
epoxyPath,
new ReaderParameters
{
AssemblyResolver = assemblyResolver,
}
);

var fsharpEpoxyPath = referencesBasePath.
Select(basePath => Path.Combine(basePath, "FSharp.Epoxy.dll")).
FirstOrDefault(File.Exists);
Expand All @@ -118,6 +135,12 @@ public ViewModelInjector(string[] referencesBasePath, Action<LogLevels, string>
this.message(
LogLevels.Trace,
$"Epoxy.Core.dll is loaded: Path={epoxyCorePath}");
if (epoxyAssembly != null)
{
this.message(
LogLevels.Trace,
$"Epoxy.dll is loaded: Path={epoxyPath}");
}
if (fsharpEpoxyAssembly != null)
{
this.message(
Expand Down Expand Up @@ -150,6 +173,9 @@ public ViewModelInjector(string[] referencesBasePath, Action<LogLevels, string>
this.propertyChangedFSharpAsyncDelegateTypeT = fsharpEpoxyAssembly?.MainModule.GetType(
"Epoxy.Internal.PropertyChangedFSharpAsyncDelegate`1")!;

this.wellExtensionType = epoxyAssembly?.MainModule.GetType(
"Epoxy.WellExtension")!;

this.propertyChangingEventHandlerType = internalPropertyBagType.Fields.
First(f => f.Name == "propertyChanging").FieldType;
this.propertyChangedEventHandlerType = internalPropertyBagType.Fields.
Expand Down Expand Up @@ -196,6 +222,11 @@ public ViewModelInjector(string[] referencesBasePath, Action<LogLevels, string>
First().AddMethod;
this.itRemovePropertyChanged = itPropertyChangedType.Events.
First().RemoveMethod;

this.wellAddMethod = this.wellExtensionType?.Methods.
First(m => m.IsStatic && m.GenericParameters.Count == 1 && m.Name == "Add" && m.Parameters.Count == 5);
this.wellAddTEventArgsMethod = this.wellExtensionType?.Methods.
First(m => m.IsStatic && m.GenericParameters.Count == 2 && m.Name == "Add" && m.Parameters.Count == 5);
}

private void InjectPropertyChangeEvents(
Expand Down Expand Up @@ -715,6 +746,104 @@ private static void ReplaceFieldToAccessor(
}
}

private static void ForEach<T>(
IEnumerable<T> enumerable,
Action<T> action)
{
foreach (var item in enumerable)
{
action(item);
}
}

private static bool ReplaceAddWell(
ModuleDefinition module)
{
var updatingActions = new Queue<Stack<Action>>();

// Enabled parallel processing with delayed updating
// when processes all CIL body streams.
//Parallel.ForEach(
ForEach(
module.GetTypes().
Where(td => td.IsClass),
td =>
{
foreach (var md in td.Methods.
Where(md => !md.IsAbstract && md.HasBody))
{
// (Have to make reverse order)
var actions = new Stack<Action>();
var instructions = md.Body.Instructions;
for (var index = 0; index < instructions.Count; index++)
{
// public static void Add<TEventArgs>(this Well well, string eventName, Func<TEventArgs, ValueTask> action)
// public static void Add(this Well well, string eventName, Func<ValueTask> action)
var inst = instructions[index];
if (inst.OpCode == OpCodes.Call &&
inst.Operand is MethodReference tmr &&
!tmr.HasThis &&
tmr.DeclaringType.FullName == "Epoxy.WellExtension" &&
tmr.Name == "Add" &&
tmr.Parameters.Count == 3 &&
tmr.Parameters[0].ParameterType.FullName == "Epoxy.Well" &&
tmr.Parameters[1].ParameterType.FullName == "System.String" &&
tmr.Parameters[2].ParameterType.Name.StartsWith("Func"))
{
// Makes delayed processing for application.
var capturedIndex = index;
actions.Push(() =>
{
var tmd = tmr.Resolve();
if (tmd.IsPublic && tmd.IsStatic &&
tmd.CustomAttributes.Any(ca => ca.AttributeType.FullName == "System.Runtime.CompilerServices.ExtensionAttribute"))
{
switch (tmd.GenericParameters.Count)
{
case 0:
case 1:
//md.Body.Instructions.Insert(capturedIndex++,
// Instruction.Create(OpCodes.Call,
// module.ImportReference(accessor.Getter)));
break;
}
}
});
}
}
if (actions.Count >= 1)
{
lock (updatingActions)
{
updatingActions.Enqueue(actions);
}
}
}
});

if (updatingActions.Count >= 1)
{
// Updates sequentially (on this thread).
do
{
var actions = updatingActions.Dequeue();
while (actions.Count >= 1)
{
var action = actions.Pop();
action();
}
}
while (updatingActions.Count >= 1);
return true;
}
else
{
return false;
}
}

public bool Inject(string targetAssemblyPath, string? injectedAssemblyPath = null)
{
this.assemblyResolver.AddSearchDirectory(
Expand Down Expand Up @@ -749,11 +878,10 @@ public bool Inject(string targetAssemblyPath, string? injectedAssemblyPath = nul
!td.Interfaces.Any(ii => ii.InterfaceType.FullName == this.iViewModelImplementerType.FullName)).
ToArray();

var injected = false;
var removedAccessor = new Dictionary<FieldDefinition, AccessorInformation>();
if (targetTypes.Length >= 1)
{
var injected = false;
var removedAccessor = new Dictionary<FieldDefinition, AccessorInformation>();

foreach (var targetType in targetTypes)
{
if (this.InjectIntoType(targetAssembly.MainModule, targetType, out var rfs))
Expand All @@ -776,23 +904,28 @@ public bool Inject(string targetAssemblyPath, string? injectedAssemblyPath = nul
$"InjectProperties: Ignored a type: Assembly={targetAssemblyName}, Type={targetType.FullName}");
}
}
}

if (injected)
{
var module = targetAssembly.MainModule;
ReplaceFieldToAccessor(module, removedAccessor);
var module = targetAssembly.MainModule;
if (ReplaceAddWell(module))
{
injected = true;
}

if (injected)
{
ReplaceFieldToAccessor(module, removedAccessor);

injectedAssemblyPath = injectedAssemblyPath ?? targetAssemblyPath;
injectedAssemblyPath = injectedAssemblyPath ?? targetAssemblyPath;

targetAssembly.Write(
injectedAssemblyPath,
new WriterParameters
{
WriteSymbols = true,
DeterministicMvid = true,
});
return true;
}
targetAssembly.Write(
injectedAssemblyPath,
new WriterParameters
{
WriteSymbols = true,
DeterministicMvid = true,
});
return true;
}
}

Expand Down

0 comments on commit 6ea1d33

Please sign in to comment.