Skip to content

Commit 79cb4e0

Browse files
committed
WIP
1 parent 0a8015c commit 79cb4e0

File tree

5 files changed

+199
-30
lines changed

5 files changed

+199
-30
lines changed

playground/EpoxyHello.Avalonia11/ViewModels/MainWindowViewModel.cs

+17
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,23 @@ public sealed class MainWindowViewModel
4848
public MainWindowViewModel()
4949
{
5050
// A handler for window opened
51+
//this.MainWindowWell.Add(
52+
// //Window.WindowOpenedEvent,
53+
// "Opened",
54+
// () =>
55+
// {
56+
// this.IsEnabled = true;
57+
// return default;
58+
// },
59+
// (window, obj, ptr) => {
60+
// var dlg = new EventHandler(obj, ptr);
61+
// window.Opened += dlg;
62+
// },
63+
// (window, obj, ptr) => {
64+
// var dlg = new EventHandler(obj, ptr);
65+
// window.Opened -= dlg;
66+
// });
67+
5168
this.MainWindowWell.Add(
5269
Window.WindowOpenedEvent,
5370
() =>

playground/EpoxyHello.Wpf/EpoxyHello.Wpf.csproj

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44

55
<PropertyGroup>
66
<OutputType>WinExe</OutputType>
7-
<TargetFrameworks>net461;netcoreapp3.1;net5.0-windows;net6.0-windows;net7.0-windows</TargetFrameworks>
7+
<TargetFrameworks>net461;netcoreapp3.1;net5.0-windows;net6.0-windows;net7.0-windows;net8.0-windows</TargetFrameworks>
88
<UseWPF>true</UseWPF>
9+
<EpoxyBuildDebug>false</EpoxyBuildDebug>
910
</PropertyGroup>
1011

1112
<ItemGroup>

playground/EpoxyHello.Wpf/ViewModels/MainWindowViewModel.cs

+28-7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
using System.IO;
2525
using System.Linq;
2626
using System.Threading.Tasks;
27+
using System.Windows;
2728
using System.Windows.Controls;
2829
using System.Windows.Media;
2930
using System.Windows.Media.Imaging;
@@ -36,7 +37,7 @@ namespace EpoxyHello.Wpf.ViewModels;
3637
[ViewModel]
3738
public sealed class MainWindowViewModel
3839
{
39-
public Command Ready { get; }
40+
public Well<Window> MainWindowWell { get; } = Well.Factory.Create<Window>();
4041

4142
public bool IsEnabled { get; set; }
4243

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

4950
public MainWindowViewModel()
5051
{
51-
// A handler for window loaded
52-
this.Ready = Command.Factory.Create(() =>
53-
{
54-
this.IsEnabled = true;
55-
return default;
56-
});
52+
// A handler for window opened
53+
//this.MainWindowWell.Add(
54+
// //FrameworkElement.LoadedEvent,
55+
// "Loaded",
56+
// () =>
57+
// {
58+
// this.IsEnabled = true;
59+
// return default;
60+
// },
61+
// (window, obj, ptr) => {
62+
// var dlg = new EventHandler(obj, ptr);
63+
// window.Opened += dlg;
64+
// },
65+
// (window, obj, ptr) => {
66+
// var dlg = new EventHandler(obj, ptr);
67+
// window.Opened -= dlg;
68+
// });
69+
70+
this.MainWindowWell.Add(
71+
//FrameworkElement.LoadedEvent,
72+
"Loaded",
73+
() =>
74+
{
75+
this.IsEnabled = true;
76+
return default;
77+
});
5778

5879
// A handler for fetch button
5980
this.Fetch = Command.Factory.Create(async () =>

playground/EpoxyHello.Wpf/Views/MainWindow.xaml

+2-5
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,12 @@
2727
xmlns:viewmodels="clr-namespace:EpoxyHello.Wpf.ViewModels"
2828
xmlns:converters="clr-namespace:EpoxyHello.Wpf.Views.Converters"
2929
mc:Ignorable="d"
30-
Title="EpoxyHello.Wpf" Height="450" Width="800">
30+
Title="EpoxyHello.Wpf" Height="450" Width="800"
31+
epoxy:Fountain.Well="{Binding MainWindowWell}">
3132

3233
<Window.DataContext>
3334
<viewmodels:MainWindowViewModel />
3435
</Window.DataContext>
35-
36-
<epoxy:EventBinder.Events>
37-
<epoxy:Event EventName="Loaded" Command="{Binding Ready}" />
38-
</epoxy:EventBinder.Events>
3936

4037
<DockPanel>
4138
<Button DockPanel.Dock="Top"

src/Epoxy.Build/ViewModelInjector.cs

+150-17
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ public sealed class ViewModelInjector
6060
private readonly TypeReference propertyChangingEventHandlerType;
6161
private readonly TypeReference propertyChangedEventHandlerType;
6262

63+
private readonly TypeDefinition? wellExtensionType;
64+
6365
private readonly MethodDefinition addPropertyChanging;
6466
private readonly MethodDefinition removePropertyChanging;
6567
private readonly MethodDefinition addPropertyChanged;
@@ -81,6 +83,9 @@ public sealed class ViewModelInjector
8183
private readonly MethodDefinition itAddPropertyChanged;
8284
private readonly MethodDefinition itRemovePropertyChanged;
8385

86+
private readonly MethodDefinition? wellAddMethod;
87+
private readonly MethodDefinition? wellAddTEventArgsMethod;
88+
8489
public ViewModelInjector(string[] referencesBasePath, Action<LogLevels, string> message)
8590
{
8691
this.message = message;
@@ -102,6 +107,18 @@ public ViewModelInjector(string[] referencesBasePath, Action<LogLevels, string>
102107
}
103108
);
104109

110+
var epoxyPath = referencesBasePath.
111+
Select(basePath => Path.Combine(basePath, "Epoxy.dll")).
112+
First(File.Exists);
113+
114+
var epoxyAssembly = AssemblyDefinition.ReadAssembly(
115+
epoxyPath,
116+
new ReaderParameters
117+
{
118+
AssemblyResolver = assemblyResolver,
119+
}
120+
);
121+
105122
var fsharpEpoxyPath = referencesBasePath.
106123
Select(basePath => Path.Combine(basePath, "FSharp.Epoxy.dll")).
107124
FirstOrDefault(File.Exists);
@@ -118,6 +135,12 @@ public ViewModelInjector(string[] referencesBasePath, Action<LogLevels, string>
118135
this.message(
119136
LogLevels.Trace,
120137
$"Epoxy.Core.dll is loaded: Path={epoxyCorePath}");
138+
if (epoxyAssembly != null)
139+
{
140+
this.message(
141+
LogLevels.Trace,
142+
$"Epoxy.dll is loaded: Path={epoxyPath}");
143+
}
121144
if (fsharpEpoxyAssembly != null)
122145
{
123146
this.message(
@@ -150,6 +173,9 @@ public ViewModelInjector(string[] referencesBasePath, Action<LogLevels, string>
150173
this.propertyChangedFSharpAsyncDelegateTypeT = fsharpEpoxyAssembly?.MainModule.GetType(
151174
"Epoxy.Internal.PropertyChangedFSharpAsyncDelegate`1")!;
152175

176+
this.wellExtensionType = epoxyAssembly?.MainModule.GetType(
177+
"Epoxy.WellExtension")!;
178+
153179
this.propertyChangingEventHandlerType = internalPropertyBagType.Fields.
154180
First(f => f.Name == "propertyChanging").FieldType;
155181
this.propertyChangedEventHandlerType = internalPropertyBagType.Fields.
@@ -196,6 +222,11 @@ public ViewModelInjector(string[] referencesBasePath, Action<LogLevels, string>
196222
First().AddMethod;
197223
this.itRemovePropertyChanged = itPropertyChangedType.Events.
198224
First().RemoveMethod;
225+
226+
this.wellAddMethod = this.wellExtensionType?.Methods.
227+
First(m => m.IsStatic && m.GenericParameters.Count == 1 && m.Name == "Add" && m.Parameters.Count == 5);
228+
this.wellAddTEventArgsMethod = this.wellExtensionType?.Methods.
229+
First(m => m.IsStatic && m.GenericParameters.Count == 2 && m.Name == "Add" && m.Parameters.Count == 5);
199230
}
200231

201232
private void InjectPropertyChangeEvents(
@@ -715,6 +746,104 @@ private static void ReplaceFieldToAccessor(
715746
}
716747
}
717748

749+
private static void ForEach<T>(
750+
IEnumerable<T> enumerable,
751+
Action<T> action)
752+
{
753+
foreach (var item in enumerable)
754+
{
755+
action(item);
756+
}
757+
}
758+
759+
private static bool ReplaceAddWell(
760+
ModuleDefinition module)
761+
{
762+
var updatingActions = new Queue<Stack<Action>>();
763+
764+
// Enabled parallel processing with delayed updating
765+
// when processes all CIL body streams.
766+
//Parallel.ForEach(
767+
ForEach(
768+
module.GetTypes().
769+
Where(td => td.IsClass),
770+
td =>
771+
{
772+
foreach (var md in td.Methods.
773+
Where(md => !md.IsAbstract && md.HasBody))
774+
{
775+
// (Have to make reverse order)
776+
var actions = new Stack<Action>();
777+
778+
var instructions = md.Body.Instructions;
779+
for (var index = 0; index < instructions.Count; index++)
780+
{
781+
// public static void Add<TEventArgs>(this Well well, string eventName, Func<TEventArgs, ValueTask> action)
782+
// public static void Add(this Well well, string eventName, Func<ValueTask> action)
783+
var inst = instructions[index];
784+
if (inst.OpCode == OpCodes.Call &&
785+
inst.Operand is MethodReference tmr &&
786+
!tmr.HasThis &&
787+
tmr.DeclaringType.FullName == "Epoxy.WellExtension" &&
788+
tmr.Name == "Add" &&
789+
tmr.Parameters.Count == 3 &&
790+
tmr.Parameters[0].ParameterType.FullName == "Epoxy.Well" &&
791+
tmr.Parameters[1].ParameterType.FullName == "System.String" &&
792+
tmr.Parameters[2].ParameterType.Name.StartsWith("Func"))
793+
{
794+
// Makes delayed processing for application.
795+
var capturedIndex = index;
796+
actions.Push(() =>
797+
{
798+
var tmd = tmr.Resolve();
799+
if (tmd.IsPublic && tmd.IsStatic &&
800+
tmd.CustomAttributes.Any(ca => ca.AttributeType.FullName == "System.Runtime.CompilerServices.ExtensionAttribute"))
801+
{
802+
switch (tmd.GenericParameters.Count)
803+
{
804+
case 0:
805+
case 1:
806+
//md.Body.Instructions.Insert(capturedIndex++,
807+
// Instruction.Create(OpCodes.Call,
808+
// module.ImportReference(accessor.Getter)));
809+
break;
810+
}
811+
}
812+
});
813+
}
814+
}
815+
816+
if (actions.Count >= 1)
817+
{
818+
lock (updatingActions)
819+
{
820+
updatingActions.Enqueue(actions);
821+
}
822+
}
823+
}
824+
});
825+
826+
if (updatingActions.Count >= 1)
827+
{
828+
// Updates sequentially (on this thread).
829+
do
830+
{
831+
var actions = updatingActions.Dequeue();
832+
while (actions.Count >= 1)
833+
{
834+
var action = actions.Pop();
835+
action();
836+
}
837+
}
838+
while (updatingActions.Count >= 1);
839+
return true;
840+
}
841+
else
842+
{
843+
return false;
844+
}
845+
}
846+
718847
public bool Inject(string targetAssemblyPath, string? injectedAssemblyPath = null)
719848
{
720849
this.assemblyResolver.AddSearchDirectory(
@@ -749,11 +878,10 @@ public bool Inject(string targetAssemblyPath, string? injectedAssemblyPath = nul
749878
!td.Interfaces.Any(ii => ii.InterfaceType.FullName == this.iViewModelImplementerType.FullName)).
750879
ToArray();
751880

881+
var injected = false;
882+
var removedAccessor = new Dictionary<FieldDefinition, AccessorInformation>();
752883
if (targetTypes.Length >= 1)
753884
{
754-
var injected = false;
755-
var removedAccessor = new Dictionary<FieldDefinition, AccessorInformation>();
756-
757885
foreach (var targetType in targetTypes)
758886
{
759887
if (this.InjectIntoType(targetAssembly.MainModule, targetType, out var rfs))
@@ -776,23 +904,28 @@ public bool Inject(string targetAssemblyPath, string? injectedAssemblyPath = nul
776904
$"InjectProperties: Ignored a type: Assembly={targetAssemblyName}, Type={targetType.FullName}");
777905
}
778906
}
907+
}
779908

780-
if (injected)
781-
{
782-
var module = targetAssembly.MainModule;
783-
ReplaceFieldToAccessor(module, removedAccessor);
909+
var module = targetAssembly.MainModule;
910+
if (ReplaceAddWell(module))
911+
{
912+
injected = true;
913+
}
914+
915+
if (injected)
916+
{
917+
ReplaceFieldToAccessor(module, removedAccessor);
784918

785-
injectedAssemblyPath = injectedAssemblyPath ?? targetAssemblyPath;
919+
injectedAssemblyPath = injectedAssemblyPath ?? targetAssemblyPath;
786920

787-
targetAssembly.Write(
788-
injectedAssemblyPath,
789-
new WriterParameters
790-
{
791-
WriteSymbols = true,
792-
DeterministicMvid = true,
793-
});
794-
return true;
795-
}
921+
targetAssembly.Write(
922+
injectedAssemblyPath,
923+
new WriterParameters
924+
{
925+
WriteSymbols = true,
926+
DeterministicMvid = true,
927+
});
928+
return true;
796929
}
797930
}
798931

0 commit comments

Comments
 (0)