Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CSharpMath.Avalonia/CSharpMath.Avalonia.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<PackageTags>$(PackageTags) avalonia</PackageTags>
<DefineConstants>$(DefineConstants) Avalonia</DefineConstants>
</PropertyGroup>
<ItemGroup><InternalsVisibleTo Include="CSharpMath.Xaml.Tests" /></ItemGroup>

<ItemGroup>
<Compile Include="..\CSharpMath.Xaml\**\*.cs" Link="%(RecursiveDir)%(Filename)%(Extension)" />
Expand Down
5 changes: 3 additions & 2 deletions CSharpMath.Maui/CSharpMath.Maui.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PropertyGroup>
<TargetFrameworks>net10.0;net10.0-android</TargetFrameworks>
<TargetFrameworks Condition="!$([System.OperatingSystem]::IsLinux())">$(TargetFrameworks);net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([System.OperatingSystem]::IsWindows())">$(TargetFrameworks);net10.0-windows10.0.19041.0</TargetFrameworks>
Expand All @@ -18,7 +18,8 @@
<PackageTags>$(PackageTags) maui</PackageTags>
<DefineConstants>$(DefineConstants) Maui</DefineConstants>

</PropertyGroup>
</PropertyGroup>
<ItemGroup><InternalsVisibleTo Include="CSharpMath.Xaml.Tests" /></ItemGroup>

<ItemGroup>
<AdditionalFiles Include="PublicAPI\$(TargetFramework)\PublicAPI.Shipped.txt" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Headless" Version="$(AvaloniaVersion)" />
<ProjectReference Include="../CSharpMath.SkiaSharp/CSharpMath.SkiaSharp.csproj" />
<ProjectReference Include="../CSharpMath.Avalonia/CSharpMath.Avalonia.csproj" />
</ItemGroup>
</Project>
</Project>
75 changes: 74 additions & 1 deletion CSharpMath.Rendering.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using Avalonia;
using Avalonia.Headless;
using BenchmarkDotNet.Attributes;
using CSharpMath.Atom;
using CSharpMath.SkiaSharp;
namespace CSharpMath.Rendering.Benchmarks {
using SkiaSharp;
using Data = Tests.TestRenderingMathData;
Expand Down Expand Up @@ -41,4 +45,73 @@ static void Main(string[] args) {
#endif
}
}
}

[MemoryDiagnoser]
public class ViewUpdateChurnBenchmarks {
const string Sample = @"x_1 = 1.234e50";
[Params(100, 1000)] public int Iterations;
readonly MathPainter painter = new();
readonly MathList content = new MathPainter { LaTeX = Sample }.Content!;

[Benchmark]
public void LaTeXAssignment() {
for (var i = 0; i < Iterations; i++) {
painter.LaTeX = Sample;
painter.Measure(1000);
}
}

[Benchmark]
public void ContentAssignment() {
for (var i = 0; i < Iterations; i++) {
painter.Content = content;
painter.Measure(1000);
}
}

[Benchmark]
public void DirectPainterReuse() {
painter.LaTeX = Sample;
for (var i = 0; i < Iterations; i++) painter.Measure(1000);
}
}

[MemoryDiagnoser]
public class AvaloniaViewUpdateChurnBenchmarks {
const string FirstSample = @"x_1 = 1.234e50";
const string SecondSample = @"x_2 = 5.678e90";
[Params(100, 1000)] public int Iterations;
static AvaloniaViewUpdateChurnBenchmarks() {
_ = CSharpMath.Rendering.BackEnd.Fonts.GlobalTypefaces.ToString();
AppBuilder.Configure<BenchmarkApplication>()
.UseHeadless(new AvaloniaHeadlessPlatformOptions())
.SetupWithoutStarting();
}
sealed class BenchmarkApplication : Application { }
readonly CSharpMath.Avalonia.MathView view = new();
readonly MathList firstContent = new MathPainter { LaTeX = FirstSample }.Content!;
readonly MathList secondContent = new MathPainter { LaTeX = SecondSample }.Content!;

[Benchmark]
public void ViewLaTeXAssignment() {
for (var i = 0; i < Iterations; i++) {
view.LaTeX = i % 2 == 0 ? FirstSample : SecondSample;
view.Measure(new Size(1000, 1000));
}
}

[Benchmark]
public void ViewContentAssignment() {
for (var i = 0; i < Iterations; i++) {
view.Content = i % 2 == 0 ? firstContent : secondContent;
view.Measure(new Size(1000, 1000));
}
}

[Benchmark]
public void DirectPainterReuse() {
view.Painter.LaTeX = FirstSample;
for (var i = 0; i < Iterations; i++) view.Painter.Measure(1000);
}
}
}
4 changes: 2 additions & 2 deletions CSharpMath.Rendering/FrontEnd/Painter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public Painter() {
Atom.LineStyle __style = Atom.LineStyle.Display;
public Atom.LineStyle LineStyle { get => __style; set { __style = value; SetRedisplay(); } }
TContent? __content;
public TContent? Content { get => __content; set { __content = value; SetRedisplay(); } }
public TContent? Content { get => __content; set { __content = value; ErrorMessage = null; SetRedisplay(); } }
public string? LaTeX { get => Content is null ? "" : ContentToLaTeX(Content); set => (Content, ErrorMessage) = LaTeXToContent(value ?? ""); }
#endregion Redisplaying properties

Expand Down Expand Up @@ -132,4 +132,4 @@ GlyphBoxColor is var (glyph, textRun) ? Nullable((WrapColor(glyph), WrapColor(te
public Painter<TCanvas, TContent, TColor> ShallowClone() => (Painter<TCanvas, TContent, TColor>)MemberwiseClone();
#endregion Methods
}
}
}
1 change: 1 addition & 0 deletions CSharpMath.Uno/CSharpMath.Uno.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<DefineConstants>$(DefineConstants) Uno</DefineConstants>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="CSharpMath.Xaml.Tests" />
<AdditionalFiles Include="PublicAPI\$(TargetFramework)\PublicAPI.Shipped.txt" />
<AdditionalFiles Include="PublicAPI\$(TargetFramework)\PublicAPI.Unshipped.txt" />
<Compile Include="..\CSharpMath.Xaml\**\*.cs" Link="%(RecursiveDir)%(Filename)%(Extension)" />
Expand Down
3 changes: 2 additions & 1 deletion CSharpMath.Xaml.Tests/CSharpMath.Xaml.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="$(AvaloniaVersion)" />
<ProjectReference Include="..\CSharpMath.Avalonia\CSharpMath.Avalonia.csproj" />
<ProjectReference Include="..\CSharpMath.Maui\CSharpMath.Maui.csproj" />
<ProjectReference Include="..\CSharpMath.Uno\CSharpMath.Uno.csproj" AdditionalProperties="TargetFramework=net10.0" />
</ItemGroup>
</Project>
</Project>
46 changes: 45 additions & 1 deletion CSharpMath.Xaml.Tests/Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ namespace CSharpMath.Xaml.Tests {
using Rendering.BackEnd;
using Rendering.FrontEnd;
using Rendering.Text;
interface ITestDiagnostics {
void Reset();
int Count(string name);
}
public abstract class Test<TColor, TBindingMode, TProperty, TBaseView, TMathView, TTextView>
where TBindingMode : struct, Enum
where TMathView : TBaseView, ICSharpMathAPI<MathList, TColor>, new()
Expand All @@ -32,8 +36,18 @@ void SetAndRaise<T>(ref T field, T value, [System.Runtime.CompilerServices.Calle
protected abstract TBindingMode OneWayToSource { get; }
protected abstract TBindingMode TwoWay { get; }
protected abstract TView ParseFromXaml<TView>(string xaml) where TView : TBaseView, new();
protected abstract TBaseView CreateInstrumentedMathView();
protected abstract void SetBindingContext(TBaseView view, object viewModel);
protected abstract IDisposable SetBinding(TBaseView view, TProperty property, string viewModelProperty, TBindingMode bindingMode);
static void AssertUpdateCounts(TBaseView view, bool contentSource, Action update) {
var diagnostics = Assert.IsAssignableFrom<ITestDiagnostics>(view);
diagnostics.Reset();
update();
Assert.Equal(contentSource ? 1 : 0, diagnostics.Count("ContentToLaTeX"));
Assert.Equal(contentSource ? 0 : 1, diagnostics.Count("LaTeXToContent"));
Assert.Equal(1, diagnostics.Count("MeasureInvalidation"));
Assert.Equal(1, diagnostics.Count("VisualInvalidation"));
}
IDisposable SetBinding<TView>(TView view, string propertyName, TBindingMode? bindingMode = null) where TView : TBaseView =>
SetBinding(view,
(TProperty)typeof(TView)
Expand All @@ -52,6 +66,9 @@ static void Test<TView, TContent>(TView view, TContent content)
}
Test(new TMathView(), new MathList(new Atom.Atoms.Number("1")));
Test(new TTextView(), (TextAtom)new TextAtom.Text("1"));
var countedView = CreateInstrumentedMathView();
var counted = Assert.IsAssignableFrom<ICSharpMathAPI<MathList, TColor>>(countedView);
AssertUpdateCounts(countedView, false, () => counted.LaTeX = "1");
}
[Fact]
public void ContentUpdatesLaTeX() {
Expand All @@ -64,6 +81,33 @@ static void Test<TView, TContent>(TView view, TContent content)
}
Test(new TMathView(), new MathList(new Atom.Atoms.Number("1")));
Test(new TTextView(), (TextAtom)new TextAtom.Text("1"));
var countedView = CreateInstrumentedMathView();
var counted = Assert.IsAssignableFrom<ICSharpMathAPI<MathList, TColor>>(countedView);
AssertUpdateCounts(countedView, true, () => counted.Content = new MathList(new Atom.Atoms.Number("1")));
}
[Fact]
public void RepeatedContentUpdatesRemainNormalized() {
var view = new TMathView();
for (var i = 0; i < 8; i++) {
view.Content = new MathList(new Atom.Atoms.Number((i + 1).ToString()));
Assert.Equal((i + 1).ToString(), view.LaTeX);
Assert.Null(view.ErrorMessage);
}
var text = new TTextView();
for (var i = 0; i < 8; i++) {
text.LaTeX = "a b";
Assert.NotNull(text.Content);
Assert.Null(text.ErrorMessage);
}
}
[Fact]
public void ValidContentClearsPreviousError() {
var view = new TMathView();
view.LaTeX = @"\missingcommand";
Assert.NotNull(view.ErrorMessage);
view.Content = new MathList(new Atom.Atoms.Number("1"));
Assert.Null(view.ErrorMessage);
Assert.Equal("1", view.LaTeX);
}
[Fact]
public void ContentIsBindable() {
Expand Down Expand Up @@ -259,4 +303,4 @@ public void LaTeXIsContent() {
Assert.Equal(new TextAtom.List(new TextAtom[] { new TextAtom.Text("a"), new TextAtom.ControlSpace(), new TextAtom.Text("b") }), text.Content);
}
}
}
}
9 changes: 8 additions & 1 deletion CSharpMath.Xaml.Tests/TestAvalonia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@

namespace CSharpMath.Xaml.Tests {
using Avalonia;
public class InstrumentedMathView : MathView, ITestDiagnostics {
readonly Dictionary<string, int> counts = new();
internal override void OnDiagnostic(string name) => counts[name] = counts.GetValueOrDefault(name) + 1;
public void Reset() => counts.Clear();
public int Count(string name) => counts.GetValueOrDefault(name);
}
public class TestAvalonia
: Test<Color, BindingMode, AvaloniaProperty, Control, MathView, TextView> {
protected override Display.IDisplay<Rendering.BackEnd.Fonts, Rendering.BackEnd.Glyph> GetDisplay(Control control) {
Expand All @@ -27,10 +33,11 @@ public class TestAvalonia
protected override BindingMode Default => BindingMode.Default;
protected override BindingMode OneWayToSource => BindingMode.OneWayToSource;
protected override BindingMode TwoWay => BindingMode.TwoWay;
protected override Control CreateInstrumentedMathView() => new InstrumentedMathView();
protected override TView ParseFromXaml<TView>(string xaml) => AvaloniaRuntimeXamlLoader.Parse<TView>(xaml);
protected override IDisposable SetBinding(Control view, AvaloniaProperty property, string viewModelProperty, BindingMode bindingMode) =>
view.Bind(property, new Binding(viewModelProperty) { Mode = bindingMode });
protected override void SetBindingContext(Control view, object viewModel) =>
view.DataContext = viewModel;
}
}
}
10 changes: 9 additions & 1 deletion CSharpMath.Xaml.Tests/TestMaui.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ namespace CSharpMath.Xaml.Tests {
using Maui;
using Microsoft.Maui.Controls.Xaml;

public class InstrumentedMauiMathView : MathView, ITestDiagnostics {
readonly Dictionary<string, int> counts = new();
internal override void OnDiagnostic(string name) => counts[name] = counts.GetValueOrDefault(name) + 1;
public void Reset() => counts.Clear();
public int Count(string name) => counts.GetValueOrDefault(name);
}

public class TestMaui
: Test<Color, BindingMode, BindableProperty, GraphicsView, MathView, TextView> {

Expand Down Expand Up @@ -57,6 +64,7 @@ public bool DispatchDelayed(TimeSpan delay, Action action) {
protected override BindingMode Default => BindingMode.Default;
protected override BindingMode OneWayToSource => BindingMode.OneWayToSource;
protected override BindingMode TwoWay => BindingMode.TwoWay;
protected override GraphicsView CreateInstrumentedMathView() => new InstrumentedMauiMathView();
protected override TView ParseFromXaml<TView>(string xaml) {
var view = new TView();
view.LoadFromXaml(xaml);
Expand All @@ -74,4 +82,4 @@ protected override IDisposable SetBinding(GraphicsView view, BindableProperty pr
protected override void SetBindingContext(GraphicsView view, object viewModel) =>
view.BindingContext = viewModel;
}
}
}
15 changes: 15 additions & 0 deletions CSharpMath.Xaml.Tests/TestUno.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Runtime.CompilerServices;
using Microsoft.UI.Xaml.Data;
using Xunit;

namespace CSharpMath.Xaml.Tests {
public class TestUno {
[Fact]
public void BindingExpressionMarkerPreventsCounterpartAssignment() {
var binding = RuntimeHelpers.GetUninitializedObject(typeof(BindingExpression));
Assert.False(Uno.UnoPropertySynchronization.ShouldAssignCounterpart(binding));
Assert.True(Uno.UnoPropertySynchronization.ShouldAssignCounterpart(null));
Assert.True(Uno.UnoPropertySynchronization.ShouldAssignCounterpart(new object()));
}
}
}
Loading
Loading