Skip to content

Commit 98e0e12

Browse files
Fix native AOT crash in ReactorApplication at XAML bootstrap
Reactor-based WinUI apps published with PublishAot=true crashed at Application startup with STATUS_APPLICATION_INTERNAL_EXCEPTION (0xc000027b) inside Microsoft.UI.Xaml.dll. The crash happened when OnLaunched ran `Resources.MergedDictionaries.Add(new XamlControlsResources())` — under AOT the runtime XAML parse path that programmatic construction triggers is not safe, whereas the same type loaded via an App.xaml-compiled XBF (as App.xaml-based projects do) is safe. Fix routes XamlControlsResources through the XAML-compiled path by adding ReactorApplication.xaml as a library-level App-style companion and calling InitializeComponent() in the ReactorApplication constructor, matching the order and code path used by App.xaml-based consumers. GetXamlType now delegates to the XAML-compiler-generated Reactor_XamlTypeInfo.XamlMetaDataProvider (late-bound via Type.GetType with a DynamicDependency to survive trimming) with a hand-rolled ReactorCoreXamlMetaDataProvider as a fallback for primitives, core Microsoft.UI.Xaml types, enums and structs that the Controls-only provider does not carry. Benchmark (ARM64, 7s per cell; previously both Reactor variants crashed under AOT): WinUI.Reactor runs 18.5/8.0/5.2 FPS at 10/50/100% updates; WinUI.ReactorGrid runs 12.6/8.8/7.0 FPS. Both show ~0.1 ms average update cost — the lowest of any WinUI variant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent fa9b6d7 commit 98e0e12

6 files changed

Lines changed: 319 additions & 36 deletions

File tree

src/Reactor/Hosting/ReactorApp.cs

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -638,23 +638,40 @@ private static void RunOnSta(Action action)
638638
/// </summary>
639639
public partial class ReactorApplication : Application, IXamlMetadataProvider
640640
{
641-
private IXamlMetadataProvider? _controlsProvider;
642-
643-
private IXamlMetadataProvider ControlsProvider
641+
// The Reactor library's XAML build pipeline generates
642+
// Microsoft.UI.Reactor.Reactor_XamlTypeInfo.XamlMetaDataProvider — a full provider
643+
// that covers ReactorDefaultResources, XamlControlsResources, ResourceDictionary,
644+
// system primitives, and chains to XamlControlsXamlMetaDataProvider for control
645+
// types. That generated provider is the right primary delegate: it's AOT-safe,
646+
// preserves type registration via compile-time code rather than runtime reflection,
647+
// and correctly handles the schema-only lookups WinUI performs during Application
648+
// startup when theme dictionaries load.
649+
//
650+
// We resolve the generated type at runtime because referencing the generated name
651+
// directly would make the C# pre-compile (run by the XAML compiler itself) fail with
652+
// CS0246 — the generated class doesn't exist yet when that check runs. The
653+
// DynamicDependency keeps the type alive under AOT trimming.
654+
private IXamlMetadataProvider? _reactorProvider;
655+
656+
private IXamlMetadataProvider ReactorProvider => _reactorProvider ??= CreateReactorProvider();
657+
658+
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors,
659+
"Microsoft.UI.Reactor.Reactor_XamlTypeInfo.XamlMetaDataProvider", "Reactor")]
660+
private static IXamlMetadataProvider CreateReactorProvider()
644661
{
645-
get
646-
{
647-
if (_controlsProvider == null)
648-
{
649-
// Activate the WinUI controls' built-in metadata provider.
650-
// This knows about all control types (TextCommandBarFlyout, etc.)
651-
var provider = new Microsoft.UI.Xaml.XamlTypeInfo.XamlControlsXamlMetaDataProvider();
652-
_controlsProvider = provider;
653-
}
654-
return _controlsProvider;
655-
}
662+
var t = global::System.Type.GetType("Microsoft.UI.Reactor.Reactor_XamlTypeInfo.XamlMetaDataProvider, Reactor", throwOnError: false);
663+
return t is null
664+
? new Microsoft.UI.Xaml.XamlTypeInfo.XamlControlsXamlMetaDataProvider()
665+
: (IXamlMetadataProvider)global::System.Activator.CreateInstance(t)!;
656666
}
657667

668+
// Fallback provider covering types WinUI may look up by-string that are not in the
669+
// generated library provider (e.g. user-defined types in the consuming project
670+
// referenced by ResourceDictionary keys). Additive safety net — in the normal path
671+
// the Reactor provider already satisfies queries.
672+
private IXamlMetadataProvider? _coreProvider;
673+
private IXamlMetadataProvider CoreProvider => _coreProvider ??= new Hosting.ReactorCoreXamlMetaDataProvider();
674+
658675
/// <summary>
659676
/// Optional callback for unhandled exceptions. If set, called before deciding whether to handle.
660677
/// Return true to mark the exception as handled; return false (or leave null) to let it crash.
@@ -665,6 +682,13 @@ private IXamlMetadataProvider ControlsProvider
665682

666683
public ReactorApplication()
667684
{
685+
// Loads ReactorApplication.xaml (which references XamlControlsResources) via the
686+
// XAML-compiled, XBF-deserialized path. Under native AOT, constructing
687+
// XamlControlsResources programmatically crashes — putting it in an Application-level
688+
// XAML and letting the XAML runtime activate it through LoadComponent during
689+
// Application construction matches what App.xaml-based projects do and is AOT-safe.
690+
InitializeComponent();
691+
668692
UnhandledException += (_, e) =>
669693
{
670694
_logger.LogError(e.Exception, "UnhandledException: {ExceptionType}: {ExceptionMessage}", e.Exception.GetType().Name, e.Exception.Message);
@@ -677,8 +701,6 @@ public ReactorApplication()
677701

678702
protected override void OnLaunched(LaunchActivatedEventArgs args)
679703
{
680-
// Load WinUI control theme resources programmatically.
681-
Resources.MergedDictionaries.Add(new XamlControlsResources());
682704

683705
var opts = ReactorApp.Options;
684706
var window = new Window { Title = opts.WindowTitle };
@@ -703,9 +725,14 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
703725
window.Activate();
704726
}
705727

706-
// IXamlMetadataProvider — delegates to the WinUI controls' built-in provider
707-
// so custom control types can be resolved from XBF theme resources.
708-
public IXamlType GetXamlType(Type type) => ControlsProvider.GetXamlType(type);
709-
public IXamlType GetXamlType(string fullName) => ControlsProvider.GetXamlType(fullName);
710-
public XmlnsDefinition[] GetXmlnsDefinitions() => ControlsProvider.GetXmlnsDefinitions();
728+
// IXamlMetadataProvider — delegate to the library's generated provider (which already
729+
// chains to XamlControlsXamlMetaDataProvider internally) and fall back to the core
730+
// provider for any schema-only types the generated one doesn't carry. Returning null
731+
// here is the WinUI convention for "unknown type" even though the WinRT interface
732+
// types it as non-nullable.
733+
public IXamlType GetXamlType(Type type)
734+
=> (ReactorProvider.GetXamlType(type) ?? CoreProvider.GetXamlType(type))!;
735+
public IXamlType GetXamlType(string fullName)
736+
=> (ReactorProvider.GetXamlType(fullName) ?? CoreProvider.GetXamlType(fullName))!;
737+
public XmlnsDefinition[] GetXmlnsDefinitions() => ReactorProvider.GetXmlnsDefinitions();
711738
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Application
2+
x:Class="Microsoft.UI.Reactor.ReactorApplication"
3+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
5+
<Application.Resources>
6+
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
7+
</Application.Resources>
8+
</Application>
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
using Microsoft.UI.Xaml;
2+
using Microsoft.UI.Xaml.Controls;
3+
using Microsoft.UI.Xaml.Markup;
4+
using Microsoft.UI.Xaml.Media;
5+
6+
namespace Microsoft.UI.Reactor.Hosting;
7+
8+
/// <summary>
9+
/// Hand-rolled IXamlMetadataProvider that covers system primitives, core Microsoft.UI.Xaml
10+
/// types, common value structs, and the enums referenced by XamlControlsResources' internal
11+
/// XAML. Needed under native AOT because the default XamlControlsXamlMetaDataProvider only
12+
/// covers types in the Microsoft.UI.Xaml.Controls namespace. Under JIT, the XAML parser
13+
/// falls back to reflective type resolution for unknown names; under AOT that path throws,
14+
/// producing a STATUS_APPLICATION_INTERNAL_EXCEPTION crash inside Microsoft.UI.Xaml.dll
15+
/// during Application bootstrap (when XamlControlsResources loads its theme dictionaries).
16+
/// </summary>
17+
internal sealed partial class ReactorCoreXamlMetaDataProvider : IXamlMetadataProvider
18+
{
19+
private static readonly (string Name, Type Type)[] s_entries =
20+
[
21+
// System primitives — WinUI queries these during Setter Value resolution and schema checks.
22+
("Object", typeof(object)),
23+
("Boolean", typeof(bool)),
24+
("Byte", typeof(byte)),
25+
("Int16", typeof(short)),
26+
("Int32", typeof(int)),
27+
("Int64", typeof(long)),
28+
("Single", typeof(float)),
29+
("Double", typeof(double)),
30+
("Char", typeof(char)),
31+
("String", typeof(string)),
32+
("DateTime", typeof(DateTime)),
33+
("TimeSpan", typeof(TimeSpan)),
34+
("Guid", typeof(Guid)),
35+
("Uri", typeof(Uri)),
36+
37+
// Core Microsoft.UI.Xaml — not in XamlControlsXamlMetaDataProvider because
38+
// that one only covers Microsoft.UI.Xaml.Controls.*
39+
("Microsoft.UI.Xaml.DependencyObject", typeof(DependencyObject)),
40+
("Microsoft.UI.Xaml.UIElement", typeof(UIElement)),
41+
("Microsoft.UI.Xaml.FrameworkElement", typeof(FrameworkElement)),
42+
("Microsoft.UI.Xaml.ResourceDictionary", typeof(ResourceDictionary)),
43+
("Microsoft.UI.Xaml.Style", typeof(Style)),
44+
("Microsoft.UI.Xaml.Setter", typeof(Setter)),
45+
("Microsoft.UI.Xaml.SetterBase", typeof(SetterBase)),
46+
("Microsoft.UI.Xaml.DataTemplate", typeof(DataTemplate)),
47+
("Microsoft.UI.Xaml.FrameworkTemplate", typeof(FrameworkTemplate)),
48+
49+
// Enums referenced by Setter values in theme dictionaries.
50+
("Microsoft.UI.Xaml.Visibility", typeof(Visibility)),
51+
("Microsoft.UI.Xaml.HorizontalAlignment", typeof(HorizontalAlignment)),
52+
("Microsoft.UI.Xaml.VerticalAlignment", typeof(VerticalAlignment)),
53+
("Microsoft.UI.Xaml.TextAlignment", typeof(TextAlignment)),
54+
("Microsoft.UI.Xaml.TextWrapping", typeof(TextWrapping)),
55+
("Microsoft.UI.Xaml.TextTrimming", typeof(TextTrimming)),
56+
("Microsoft.UI.Xaml.FlowDirection", typeof(FlowDirection)),
57+
("Microsoft.UI.Xaml.GridUnitType", typeof(GridUnitType)),
58+
("Microsoft.UI.Xaml.Controls.Orientation",typeof(Orientation)),
59+
("Microsoft.UI.Xaml.Controls.ControlTemplate", typeof(ControlTemplate)),
60+
61+
// Structs serialized in XAML attribute form.
62+
("Microsoft.UI.Xaml.Thickness", typeof(Thickness)),
63+
("Microsoft.UI.Xaml.CornerRadius", typeof(CornerRadius)),
64+
("Microsoft.UI.Xaml.GridLength", typeof(GridLength)),
65+
("Microsoft.UI.Xaml.Duration", typeof(Duration)),
66+
67+
// Media primitives.
68+
("Microsoft.UI.Xaml.Media.Brush", typeof(Brush)),
69+
("Microsoft.UI.Xaml.Media.SolidColorBrush", typeof(SolidColorBrush)),
70+
71+
// Windows namespace structs used in XAML.
72+
("Windows.UI.Color", typeof(global::Windows.UI.Color)),
73+
("Windows.Foundation.Size", typeof(global::Windows.Foundation.Size)),
74+
("Windows.Foundation.Point", typeof(global::Windows.Foundation.Point)),
75+
("Windows.Foundation.Rect", typeof(global::Windows.Foundation.Rect)),
76+
];
77+
78+
private static readonly Dictionary<string, Type> s_byName = BuildNameMap();
79+
private static readonly Dictionary<Type, string> s_byType = BuildTypeMap();
80+
81+
private static Dictionary<string, Type> BuildNameMap()
82+
{
83+
var map = new Dictionary<string, Type>(s_entries.Length, StringComparer.Ordinal);
84+
foreach (var (name, type) in s_entries)
85+
map[name] = type;
86+
return map;
87+
}
88+
89+
private static Dictionary<Type, string> BuildTypeMap()
90+
{
91+
var map = new Dictionary<Type, string>(s_entries.Length);
92+
foreach (var (name, type) in s_entries)
93+
map[type] = name;
94+
return map;
95+
}
96+
97+
public IXamlType? GetXamlType(Type type)
98+
=> s_byType.TryGetValue(type, out var name) ? new CoreXamlType(name, type) : null;
99+
100+
public IXamlType? GetXamlType(string fullName)
101+
=> s_byName.TryGetValue(fullName, out var type) ? new CoreXamlType(fullName, type) : null;
102+
103+
public XmlnsDefinition[] GetXmlnsDefinitions() => [];
104+
105+
/// <summary>
106+
/// Minimal IXamlType that satisfies schema-level lookups. WinUI's XAML loader calls
107+
/// GetXamlType during parsing to verify that types referenced in XAML exist; for system
108+
/// and schema-only types, returning a non-null stub with correct FullName + UnderlyingType
109+
/// is sufficient. Activation and member access are unreachable for these types because
110+
/// Reactor apps do not construct them from XAML markup — they only appear as schema
111+
/// references inside the WinUI theme dictionaries.
112+
/// </summary>
113+
private sealed partial class CoreXamlType : IXamlType
114+
{
115+
public CoreXamlType(string fullName, Type underlyingType)
116+
{
117+
FullName = fullName;
118+
UnderlyingType = underlyingType;
119+
}
120+
121+
public string FullName { get; }
122+
public Type UnderlyingType { get; }
123+
public IXamlType? BaseType => null;
124+
public IXamlMember? ContentProperty => null;
125+
public bool IsArray => false;
126+
public bool IsCollection => false;
127+
public bool IsConstructible => false;
128+
public bool IsDictionary => false;
129+
public bool IsMarkupExtension => false;
130+
public bool IsBindable => false;
131+
public bool IsReturnTypeStub => false;
132+
public bool IsLocalType => false;
133+
public IXamlType? ItemType => null;
134+
public IXamlType? KeyType => null;
135+
public IXamlType? BoxedType => null;
136+
public IXamlMember? GetMember(string name) => null;
137+
public object ActivateInstance() => throw new NotSupportedException($"{FullName} is schema-only; cannot activate from XAML.");
138+
public void AddToMap(object instance, object key, object item) => throw new NotSupportedException();
139+
public void AddToVector(object instance, object item) => throw new NotSupportedException();
140+
public void RunInitializer() { }
141+
142+
public object CreateFromString(string input)
143+
{
144+
if (UnderlyingType.IsEnum)
145+
return Enum.Parse(UnderlyingType, input, ignoreCase: true);
146+
throw new NotSupportedException($"Cannot parse '{input}' for schema-only type {FullName}.");
147+
}
148+
}
149+
}
Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
App,Percent,Duration_s,Avg_FPS,Min_FPS,Max_FPS,Avg_Update_ms,Max_Update_ms,Avg_Memory_MB,Peak_Memory_MB
2-
WPF.Direct,10,9.1,17.8,0.9,22.8,1.0,2.8,832.2,1027.0
3-
WinUI.Direct,10,7.9,24.7,3.0,30.3,2.2,4.2,452.5,484.0
4-
WinUI.Bound,10,0,0,0,0,0,0,0,0
5-
WinUI.Duct,10,7.7,20.7,8.9,23.2,0.1,0.5,468.9,481.0
6-
WinUI.DirectX,10,7.4,37.5,23.1,40.7,0.1,0.5,142.0,142.0
7-
WPF.Direct,50,8.5,6.4,1.3,9.9,4.1,26.8,830.2,1078.0
8-
WinUI.Direct,50,7.7,8.1,6.9,8.7,9.1,13.5,462.0,471.0
9-
WinUI.Bound,50,0,0,0,0,0,0,0,0
10-
WinUI.Duct,50,7.6,7.9,6.4,8.7,0.3,1.7,464.7,474.0
11-
WinUI.DirectX,50,7.2,38.0,26.9,40.7,0.1,2.0,135.6,136.0
12-
WPF.Direct,100,9.0,4.0,1.1,4.7,5.7,15.2,677.2,947.0
13-
WinUI.Direct,100,7.7,5.3,4.9,5.9,15.6,20.9,471.6,490.0
14-
WinUI.Bound,100,0,0,0,0,0,0,0,0
15-
WinUI.Duct,100,7.7,5.6,5.3,5.9,0.3,1.9,465.8,476.0
16-
WinUI.DirectX,100,7.2,37.8,29.2,39.8,0.2,2.1,135.8,136.0
2+
WPF.Direct,10,9.1,21.7,1.0,28.9,0.9,2.7,856.2,1006.0
3+
WinUI.Direct,10,8.2,21.6,1.5,25.8,2.4,6.6,452.3,475.0
4+
WinUI.Bound,10,8.8,20.3,0.7,25.0,6.8,12.8,513.8,562.0
5+
WinUI.Reactor,10,7.7,18.6,11.4,21.4,0.1,0.9,475.9,494.0
6+
WinUI.ReactorGrid,10,7.4,14.4,12.1,15.1,0.1,0.6,409.9,422.0
7+
WinUI.DirectX,10,7.4,32.4,19.5,36.7,0.1,0.7,141.5,141.0
8+
WPF.Direct,50,8.8,6.4,1.1,9.8,4.2,11.9,812.7,1051.0
9+
WinUI.Direct,50,7.9,7.4,4.8,8.6,9.7,14.3,462.7,479.0
10+
WinUI.Bound,50,8.8,6.5,0.7,7.5,27.7,54.8,513.1,562.0
11+
WinUI.Reactor,50,7.8,8.2,7.2,10.3,0.3,1.5,475.8,497.0
12+
WinUI.ReactorGrid,50,7.2,9.5,9.0,9.8,0.3,2.2,394.3,409.0
13+
WinUI.DirectX,50,7.3,32.2,24.8,36.1,0.2,1.8,136.2,137.0
14+
WPF.Direct,100,8.9,3.8,1.1,4.3,6.0,15.0,670.2,942.0
15+
WinUI.Direct,100,7.7,5.1,4.4,5.5,16.4,24.3,460.7,473.0
16+
WinUI.Bound,100,8.7,4.3,0.8,5.4,44.1,58.3,506.4,555.0
17+
WinUI.Reactor,100,7.9,5.9,5.2,8.8,0.5,1.6,486.8,508.0
18+
WinUI.ReactorGrid,100,7.5,7.7,6.8,8.5,0.4,1.8,401.6,416.0
19+
WinUI.DirectX,100,7.3,32.4,21.9,34.8,0.2,3.2,135.9,137.0
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
App,Percent,Duration_s,Avg_FPS,Min_FPS,Max_FPS,Avg_Update_ms,Max_Update_ms,Avg_Memory_MB,Peak_Memory_MB
2+
WPF.Direct,10,9.7,19.0,0.7,27.6,1.0,7.1,816.7,992.0
3+
WinUI.Direct,10,7.8,20.6,7.8,23.2,3.0,19.6,418.4,427.0
4+
WinUI.Bound,10,8.9,18.8,0.6,23.1,7.5,10.8,494.3,532.0
5+
WinUI.Reactor,10,7.8,18.5,13.7,20.7,0.1,0.1,436.1,446.0
6+
WinUI.ReactorGrid,10,7.3,12.6,10.8,14.7,0.1,0.1,375.3,383.0
7+
WinUI.DirectX,10,7.3,33.8,29.4,35.5,0.0,0.3,95.4,96.0
8+
WPF.Direct,50,9.0,5.9,1.0,8.7,4.7,18.9,798.3,1056.0
9+
WinUI.Direct,50,7.9,7.3,6.9,7.6,10.3,15.9,433.2,448.0
10+
WinUI.Bound,50,8.9,5.7,0.6,7.7,34.9,64.9,497.0,548.0
11+
WinUI.Reactor,50,7.7,8.0,6.6,13.0,0.1,0.2,451.9,458.0
12+
WinUI.ReactorGrid,50,7.2,8.8,7.7,11.6,0.1,0.2,364.1,378.0
13+
WinUI.DirectX,50,7.3,36.6,31.5,39.5,0.1,0.2,97.2,97.0
14+
WPF.Direct,100,9.1,3.7,1.0,4.3,6.5,15.5,629.5,931.0
15+
WinUI.Direct,100,7.8,5.0,4.2,6.6,19.4,27.6,427.9,440.0
16+
WinUI.Bound,100,9.0,4.4,0.7,6.1,49.0,69.2,500.7,550.0
17+
WinUI.Reactor,100,8.0,5.2,3.7,10.3,0.1,0.3,446.1,465.0
18+
WinUI.ReactorGrid,100,7.4,7.0,5.9,8.7,0.1,0.2,361.2,375.0
19+
WinUI.DirectX,100,7.3,24.9,20.4,29.7,0.1,0.3,95.0,95.0
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/bin/bash
2+
# StressPerf benchmark against published outputs (AOT-compiled for supported variants).
3+
# Runs 10/50/100% update rates, 7s each.
4+
set -e
5+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
7+
8+
DURATION=7
9+
OUTFILE="$SCRIPT_DIR/benchmark_results_aot_publish.csv"
10+
11+
CONFIG="Release"
12+
TFM_WINUI="net9.0-windows10.0.22621.0"
13+
TFM_WPF="net9.0-windows"
14+
PLATFORM="ARM64"
15+
RID="win-arm64"
16+
17+
STRESS_DIR="$REPO_ROOT/tests/stress_perf"
18+
DIRECT_EXE="$STRESS_DIR/StressPerf.Direct/bin/$PLATFORM/$CONFIG/$TFM_WINUI/$RID/publish/StressPerf.Direct.exe"
19+
BOUND_EXE="$STRESS_DIR/StressPerf.Bound/bin/$PLATFORM/$CONFIG/$TFM_WINUI/$RID/publish/StressPerf.Bound.exe"
20+
REACTOR_EXE="$STRESS_DIR/StressPerf.Reactor/bin/$PLATFORM/$CONFIG/$TFM_WINUI/$RID/publish/StressPerf.Reactor.exe"
21+
REACTORGRID_EXE="$STRESS_DIR/StressPerf.ReactorGrid/bin/$PLATFORM/$CONFIG/$TFM_WINUI/$RID/publish/StressPerf.ReactorGrid.exe"
22+
WPF_EXE="$STRESS_DIR/StressPerf.Wpf/bin/$PLATFORM/$CONFIG/$TFM_WPF/$RID/publish/StressPerf.Wpf.exe"
23+
DIRECTX_EXE="$STRESS_DIR/StressPerf.DirectX/bin/$PLATFORM/$CONFIG/$TFM_WINUI/$RID/publish/StressPerf.DirectX.exe"
24+
25+
echo "App,Percent,Duration_s,Avg_FPS,Min_FPS,Max_FPS,Avg_Update_ms,Max_Update_ms,Avg_Memory_MB,Peak_Memory_MB" > "$OUTFILE"
26+
27+
parse_report() {
28+
local file="$1" app="$2" pct="$3"
29+
if [ ! -f "$file" ]; then
30+
echo "$app,$pct,0,0,0,0,0,0,0,0" >> "$OUTFILE"
31+
return
32+
fi
33+
local duration=$(grep "Duration:" "$file" | awk '{print $NF}' | tr -d 's')
34+
local avg_fps=$(grep "Avg FPS:" "$file" | awk '{print $NF}')
35+
local min_fps=$(grep "Min FPS:" "$file" | awk '{print $NF}')
36+
local max_fps=$(grep "Max FPS:" "$file" | awk '{print $NF}')
37+
local avg_update=$(grep "Avg Update:" "$file" | awk '{print $(NF-1)}')
38+
local max_update=$(grep "Max Update:" "$file" | awk '{print $(NF-1)}')
39+
local avg_mem=$(grep "Avg Memory:" "$file" | awk '{print $(NF-1)}')
40+
local peak_mem=$(grep "Peak Memory:" "$file" | awk '{print $(NF-1)}')
41+
echo "$app,$pct,$duration,$avg_fps,$min_fps,$max_fps,$avg_update,$max_update,$avg_mem,$peak_mem" >> "$OUTFILE"
42+
}
43+
44+
run_app() {
45+
local exe="$1" name="$2" pct="$3"
46+
local exe_dir
47+
exe_dir=$(dirname "$exe")
48+
if [ ! -f "$exe" ]; then
49+
echo " SKIP $name (not built: $exe)"
50+
echo "$name,$pct,0,0,0,0,0,0,0,0" >> "$OUTFILE"
51+
return
52+
fi
53+
find "$exe_dir" -maxdepth 1 -iname "*.report.txt" -delete 2>/dev/null || true
54+
echo " Running $name @ ${pct}%..."
55+
"$exe" --headless --percent "$pct" --duration "$DURATION" 2>/dev/null || true
56+
local report_file
57+
report_file=$(find "$exe_dir" -maxdepth 1 -iname "*.report.txt" -type f 2>/dev/null | head -1)
58+
[ -z "$report_file" ] && report_file=$(find "$exe_dir/.." -iname "*.report.txt" -type f 2>/dev/null | head -1)
59+
parse_report "$report_file" "$name" "$pct"
60+
}
61+
62+
echo "=== StressPerf (AOT publish, 10/50/100%) ==="
63+
echo "Duration per run: ${DURATION}s"
64+
echo ""
65+
for pct in 10 50 100; do
66+
echo "--- ${pct}% update rate ---"
67+
run_app "$WPF_EXE" "WPF.Direct" "$pct"
68+
run_app "$DIRECT_EXE" "WinUI.Direct" "$pct"
69+
run_app "$BOUND_EXE" "WinUI.Bound" "$pct"
70+
run_app "$REACTOR_EXE" "WinUI.Reactor" "$pct"
71+
run_app "$REACTORGRID_EXE" "WinUI.ReactorGrid" "$pct"
72+
run_app "$DIRECTX_EXE" "WinUI.DirectX" "$pct"
73+
echo ""
74+
done
75+
76+
echo "=== Done ==="
77+
cat "$OUTFILE"

0 commit comments

Comments
 (0)