diff --git a/CHANGELOG.md b/CHANGELOG.md index cec0e2d37..bdae88aaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to Stability Matrix will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html). +## v2.11.6 +### Fixed +- Fixed incorrect IPAdapter download links in the HuggingFace model browser +- Fixed potential memory leak of transient controls (Inference Prompt and Output Image Viewer) not being garbage collected due to event subscriptions +- Fixed Batch Count seeds not being recorded properly in Inference projects and image metadata +- Fixed [#795](https://github.com/LykosAI/StabilityMatrix/issues/795) - SwarmUI launch args not working properly +- Fixed [#745](https://github.com/LykosAI/StabilityMatrix/issues/745) - not passing Environment Variables to SwarmUI +### Supporters +#### Visionaries +- Shoutout to our Visionary-tier Patreon supporter, **Scopp Mcdee**! Huge thanks for your continued support! +#### Pioneers +- Many thanks to our Pioneer-tier supporters on Patreon: **tankfox**, **tanangular**, **Mr. Unknown**, and **Szir777**! Your continued support is greatly appreciated! ## v2.11.5 ### Added diff --git a/StabilityMatrix.Avalonia/Assets/hf-packages.json b/StabilityMatrix.Avalonia/Assets/hf-packages.json index 6bbeecbbc..267e36827 100644 --- a/StabilityMatrix.Avalonia/Assets/hf-packages.json +++ b/StabilityMatrix.Avalonia/Assets/hf-packages.json @@ -390,10 +390,8 @@ "ModelName": "SD 1.5 Adapter", "RepositoryPath": "InvokeAI/ip_adapter_sd15", "Files": [ - "image_encoder.txt", - "ip_adapter.bin" + "ip-adapter_sd15.safetensors" ], - "Subfolder": "ip_adapter_sd15", "LicenseType": "Apache 2.0" }, { @@ -401,8 +399,7 @@ "ModelName": "SD 1.5 Light Adapter", "RepositoryPath": "InvokeAI/ip_adapter_sd15_light", "Files": [ - "image_encoder.txt", - "ip_adapter.bin" + "ip-adapter_sd15_light.safetensors" ], "Subfolder": "ip_adapter_sd15_light", "LicenseType": "Apache 2.0" @@ -412,10 +409,8 @@ "ModelName": "SD 1.5 Plus Adapter", "RepositoryPath": "InvokeAI/ip_adapter_plus_sd15", "Files": [ - "image_encoder.txt", - "ip_adapter.bin" + "ip-adapter-plus_sd15.safetensors" ], - "Subfolder": "ip_adapter_plus_sd15", "LicenseType": "Apache 2.0" }, { @@ -423,10 +418,8 @@ "ModelName": "SD 1.5 Face Plus Adapter", "RepositoryPath": "InvokeAI/ip_adapter_plus_face_sd15", "Files": [ - "image_encoder.txt", - "ip_adapter.bin" + "ip-adapter-plus-face_sd15.safetensors" ], - "Subfolder": "ip_adapter_plus_face_sd15", "LicenseType": "Apache 2.0" }, { @@ -434,10 +427,8 @@ "ModelName": "SDXL Adapter", "RepositoryPath": "InvokeAI/ip_adapter_sdxl", "Files": [ - "image_encoder.txt", - "ip_adapter.bin" + "ip-adapter_sdxl.safetensors" ], - "Subfolder": "ip_adapter_sdxl", "LicenseType": "Apache 2.0" }, { @@ -445,10 +436,8 @@ "ModelName": "SDXL Plus Adapter", "RepositoryPath": "InvokeAI/ip-adapter-plus_sdxl_vit-h", "Files": [ - "image_encoder.txt", - "ip_adapter.bin" + "ip-adapter-plus_sdxl_vit-h.safetensors" ], - "Subfolder": "ip_adapter_plus_sdxl_vit-h", "LicenseType": "Apache 2.0" }, { @@ -456,10 +445,8 @@ "ModelName": "SDXL Face Plus Adapter", "RepositoryPath": "InvokeAI/ip-adapter-plus-face_sdxl_vit-h", "Files": [ - "image_encoder.txt", - "ip_adapter.bin" + "ip-adapter-plus-face_sdxl_vit-h.safetensors" ], - "Subfolder": "ip_adapter_plus_face_sdxl_vit-h", "LicenseType": "CreativeML Open RAIL-M" }, { diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/DisposableLoadableViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/DisposableLoadableViewModelBase.cs new file mode 100644 index 000000000..1db67f957 --- /dev/null +++ b/StabilityMatrix.Avalonia/ViewModels/Base/DisposableLoadableViewModelBase.cs @@ -0,0 +1,75 @@ +using System; +using System.Reactive.Disposables; +using JetBrains.Annotations; + +namespace StabilityMatrix.Avalonia.ViewModels.Base; + +public abstract class DisposableLoadableViewModelBase : LoadableViewModelBase, IDisposable +{ + private readonly CompositeDisposable instanceDisposables = new(); + + /// + /// Adds a disposable to be disposed when this view model is disposed. + /// + /// The disposable to add. + protected void AddDisposable([HandlesResourceDisposal] IDisposable disposable) + { + instanceDisposables.Add(disposable); + } + + /// + /// Adds disposables to be disposed when this view model is disposed. + /// + /// The disposables to add. + protected void AddDisposable([HandlesResourceDisposal] params IDisposable[] disposables) + { + foreach (var disposable in disposables) + { + instanceDisposables.Add(disposable); + } + } + + /// + /// Adds a disposable to be disposed when this view model is disposed. + /// + /// The disposable to add. + /// The type of the disposable. + /// The disposable that was added. + protected T AddDisposable([HandlesResourceDisposal] T disposable) + where T : IDisposable + { + instanceDisposables.Add(disposable); + return disposable; + } + + /// + /// Adds disposables to be disposed when this view model is disposed. + /// + /// The disposables to add. + /// The type of the disposables. + /// The disposables that were added. + protected T[] AddDisposable([HandlesResourceDisposal] params T[] disposables) + where T : IDisposable + { + foreach (var disposable in disposables) + { + instanceDisposables.Add(disposable); + } + + return disposables; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + instanceDisposables.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } +} diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/DisposableViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/DisposableViewModelBase.cs new file mode 100644 index 000000000..bfc1c5d9e --- /dev/null +++ b/StabilityMatrix.Avalonia/ViewModels/Base/DisposableViewModelBase.cs @@ -0,0 +1,75 @@ +using System; +using System.Reactive.Disposables; +using JetBrains.Annotations; + +namespace StabilityMatrix.Avalonia.ViewModels.Base; + +public abstract class DisposableViewModelBase : ViewModelBase, IDisposable +{ + private readonly CompositeDisposable instanceDisposables = new(); + + /// + /// Adds a disposable to be disposed when this view model is disposed. + /// + /// The disposable to add. + protected void AddDisposable([HandlesResourceDisposal] IDisposable disposable) + { + instanceDisposables.Add(disposable); + } + + /// + /// Adds disposables to be disposed when this view model is disposed. + /// + /// The disposables to add. + protected void AddDisposable([HandlesResourceDisposal] params IDisposable[] disposables) + { + foreach (var disposable in disposables) + { + instanceDisposables.Add(disposable); + } + } + + /// + /// Adds a disposable to be disposed when this view model is disposed. + /// + /// The disposable to add. + /// The type of the disposable. + /// The disposable that was added. + protected T AddDisposable([HandlesResourceDisposal] T disposable) + where T : IDisposable + { + instanceDisposables.Add(disposable); + return disposable; + } + + /// + /// Adds disposables to be disposed when this view model is disposed. + /// + /// The disposables to add. + /// The type of the disposables. + /// The disposables that were added. + protected T[] AddDisposable([HandlesResourceDisposal] params T[] disposables) + where T : IDisposable + { + foreach (var disposable in disposables) + { + instanceDisposables.Add(disposable); + } + + return disposables; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + instanceDisposables.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } +} diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs index 774e97a70..9c1621d47 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceGenerationViewModelBase.cs @@ -93,7 +93,7 @@ RunningPackageService runningPackageService ClientManager = inferenceClientManager; ImageGalleryCardViewModel = vmFactory.Get(); - ImageFolderCardViewModel = vmFactory.Get(); + ImageFolderCardViewModel = AddDisposable(vmFactory.Get()); GenerateImageCommand.WithConditionalNotificationErrorHandler(notificationService); } diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs index 0cb757bdc..2ae913689 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs @@ -33,8 +33,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Base; public abstract partial class InferenceTabViewModelBase - : LoadableViewModelBase, - IDisposable, + : DisposableLoadableViewModelBase, IPersistentViewProvider, IDropTarget { @@ -158,21 +157,16 @@ private async Task DebugLoadViewState() } } - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { + base.Dispose(disposing); + if (disposing) { ((IPersistentViewProvider)this).AttachedPersistentView = null; } } - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - /// /// Loads image and metadata from a file path /// diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs index 3eb061440..5fdc28ca8 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs @@ -36,7 +36,7 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference; [View(typeof(ImageFolderCard))] [ManagedService] [Transient] -public partial class ImageFolderCardViewModel : ViewModelBase +public partial class ImageFolderCardViewModel : DisposableViewModelBase { private readonly ILogger logger; private readonly IImageIndexService imageIndexService; @@ -83,11 +83,13 @@ INotificationService notificationService .Bind(LocalImages) .Subscribe(); - settingsManager.RelayPropertyFor( - this, - vm => vm.ImageSize, - settings => settings.InferenceImageSize, - delay: TimeSpan.FromMilliseconds(250) + AddDisposable( + settingsManager.RelayPropertyFor( + this, + vm => vm.ImageSize, + settings => settings.InferenceImageSize, + delay: TimeSpan.FromMilliseconds(250) + ) ); } diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs index 04e46f059..4d628edac 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/InferenceTextToImageViewModel.cs @@ -83,7 +83,8 @@ RunningPackageService runningPackageService samplerCard.DenoiseStrength = 1.0d; }); - PromptCardViewModel = vmFactory.Get(); + PromptCardViewModel = AddDisposable(vmFactory.Get()); + BatchSizeCardViewModel = vmFactory.Get(); ModulesCardViewModel = vmFactory.Get(modulesCard => @@ -108,13 +109,15 @@ RunningPackageService runningPackageService ); // When refiner is provided in model card, enable for sampler - ModelCardViewModel - .WhenPropertyChanged(x => x.IsRefinerSelectionEnabled) - .Subscribe(e => - { - SamplerCardViewModel.IsRefinerStepsEnabled = - e.Sender is { IsRefinerSelectionEnabled: true, SelectedRefiner: not null }; - }); + AddDisposable( + ModelCardViewModel + .WhenPropertyChanged(x => x.IsRefinerSelectionEnabled) + .Subscribe(e => + { + SamplerCardViewModel.IsRefinerStepsEnabled = + e.Sender is { IsRefinerSelectionEnabled: true, SelectedRefiner: not null }; + }) + ); } /// @@ -211,13 +214,23 @@ CancellationToken cancellationToken var buildPromptArgs = new BuildPromptEventArgs { Overrides = overrides, SeedOverride = seed }; BuildPrompt(buildPromptArgs); + // update seed in project for batches + var inferenceProject = InferenceProjectDocument.FromLoadable(this); + if (inferenceProject.State?["Seed"]?["Seed"] is not null) + { + inferenceProject = inferenceProject.WithState(x => x["Seed"]["Seed"] = seed); + } + var generationArgs = new ImageGenerationEventArgs { Client = ClientManager.Client, Nodes = buildPromptArgs.Builder.ToNodeDictionary(), OutputNodeNames = buildPromptArgs.Builder.Connections.OutputNodeNames.ToArray(), - Parameters = SaveStateToParameters(new GenerationParameters()), - Project = InferenceProjectDocument.FromLoadable(this), + Parameters = SaveStateToParameters(new GenerationParameters()) with + { + Seed = Convert.ToUInt64(seed) + }, + Project = inferenceProject, FilesToTransfer = buildPromptArgs.FilesToTransfer, BatchIndex = i, // Only clear output images on the first batch diff --git a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs index 919e4e309..a11eba3d3 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Inference/PromptCardViewModel.cs @@ -28,7 +28,10 @@ namespace StabilityMatrix.Avalonia.ViewModels.Inference; [View(typeof(PromptCard))] [ManagedService] [Transient] -public partial class PromptCardViewModel : LoadableViewModelBase, IParametersLoadableState, IComfyStep +public partial class PromptCardViewModel + : DisposableLoadableViewModelBase, + IParametersLoadableState, + IComfyStep { private readonly IModelIndexService modelIndexService; private readonly ISettingsManager settingsManager; @@ -75,11 +78,13 @@ SharedState sharedState vm.AvailableModules = [typeof(PromptExpansionModule)]; }); - settingsManager.RelayPropertyFor( - this, - vm => vm.IsAutoCompletionEnabled, - settings => settings.IsPromptCompletionEnabled, - true + AddDisposable( + settingsManager.RelayPropertyFor( + this, + vm => vm.IsAutoCompletionEnabled, + settings => settings.IsPromptCompletionEnabled, + true + ) ); } diff --git a/StabilityMatrix.Core/Helper/Expressions.cs b/StabilityMatrix.Core/Helper/Expressions.cs deleted file mode 100644 index c830d645a..000000000 --- a/StabilityMatrix.Core/Helper/Expressions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Linq.Expressions; -using System.Reflection; - -namespace StabilityMatrix.Core.Helper; - -public static class Expressions -{ - public static (string propertyName, Expression> assigner) - GetAssigner(Expression> propertyAccessor) - { - if (propertyAccessor.Body is not MemberExpression memberExpression) - { - throw new ArgumentException( - $"Expression must be a member expression, not {propertyAccessor.Body.NodeType}"); - } - - var propertyInfo = memberExpression.Member as PropertyInfo; - if (propertyInfo == null) - { - throw new ArgumentException( - $"Expression member must be a property, not {memberExpression.Member.MemberType}"); - } - - var propertyName = propertyInfo.Name; - var typeParam = Expression.Parameter(typeof(T)); - var valueParam = Expression.Parameter(typeof(TValue)); - var expr = Expression.Lambda>( - Expression.Assign( - Expression.MakeMemberAccess(typeParam, propertyInfo), - valueParam), typeParam, valueParam); - return (propertyName, expr); - } -} diff --git a/StabilityMatrix.Core/Models/Packages/StableSwarm.cs b/StabilityMatrix.Core/Models/Packages/StableSwarm.cs index 32ca92eb2..65f6a75b4 100644 --- a/StabilityMatrix.Core/Models/Packages/StableSwarm.cs +++ b/StabilityMatrix.Core/Models/Packages/StableSwarm.cs @@ -3,6 +3,7 @@ using FreneticUtilities.FreneticDataSyntax; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Exceptions; +using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Helper.Cache; using StabilityMatrix.Core.Models.FDS; @@ -254,6 +255,7 @@ public override async Task RunPackage( ["ASPNETCORE_ENVIRONMENT"] = "Production", ["ASPNETCORE_URLS"] = "http://*:7801" }; + aspEnvVars.Update(settingsManager.Settings.EnvironmentVariables); void HandleConsoleOutput(ProcessOutput s) { @@ -280,7 +282,9 @@ void HandleConsoleOutput(ProcessOutput s) dotnetProcess = await prerequisiteHelper .RunDotnet( - args: [Path.Combine(releaseFolder, dllName), arguments.TrimEnd()], + args: new ProcessArgs(new[] { Path.Combine(releaseFolder, dllName) }).Concat( + arguments.TrimEnd() + ), workingDirectory: installedPackagePath, envVars: aspEnvVars, onProcessOutput: HandleConsoleOutput, diff --git a/StabilityMatrix.Core/Services/ISettingsManager.cs b/StabilityMatrix.Core/Services/ISettingsManager.cs index d0970f06b..1cf8a41b1 100644 --- a/StabilityMatrix.Core/Services/ISettingsManager.cs +++ b/StabilityMatrix.Core/Services/ISettingsManager.cs @@ -73,7 +73,7 @@ public interface ISettingsManager /// /// Register a source observable object and property to be relayed to Settings /// - void RelayPropertyFor( + IDisposable RelayPropertyFor( T source, Expression> sourceProperty, Expression> settingsProperty, diff --git a/StabilityMatrix.Core/Services/SettingsManager.cs b/StabilityMatrix.Core/Services/SettingsManager.cs index 23bd3d9f4..b1770ea3a 100644 --- a/StabilityMatrix.Core/Services/SettingsManager.cs +++ b/StabilityMatrix.Core/Services/SettingsManager.cs @@ -1,9 +1,12 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; +using System.Reactive.Disposables; +using System.Reactive.Linq; using System.Reflection; using System.Text.Json; using AsyncAwaitBestPractices; +using CompiledExpressions; using Microsoft.Extensions.Logging; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Helper; @@ -161,7 +164,7 @@ public void Transaction(Expression> expression, T } /// - public void RelayPropertyFor( + public IDisposable RelayPropertyFor( T source, Expression> sourceProperty, Expression> settingsProperty, @@ -170,49 +173,48 @@ public void RelayPropertyFor( ) where T : INotifyPropertyChanged { - var sourceGetter = sourceProperty.Compile(); - var (propertyName, assigner) = Expressions.GetAssigner(sourceProperty); - var sourceSetter = assigner.Compile(); + var sourceInstanceAccessor = CompiledExpression.CreateAccessor(sourceProperty).WithInstance(source); + var settingsAccessor = CompiledExpression.CreateAccessor(settingsProperty); - var settingsGetter = settingsProperty.Compile(); - var (targetPropertyName, settingsAssigner) = Expressions.GetAssigner(settingsProperty); - var settingsSetter = settingsAssigner.Compile(); + var sourcePropertyPath = sourceInstanceAccessor.FullName; + var settingsPropertyPath = settingsAccessor.FullName; var sourceTypeName = source.GetType().Name; // Update source when settings change - SettingsPropertyChanged += (sender, args) => + void OnSettingsPropertyChanged(object? sender, RelayPropertyChangedEventArgs args) { - if (args.PropertyName != targetPropertyName) + if (args.PropertyName != settingsPropertyPath) return; // Skip if event is relay and the sender is the source, to prevent duplicate if (args.IsRelay && ReferenceEquals(sender, source)) return; + logger.LogTrace( - "[RelayPropertyFor] " + "Settings.{TargetProperty:l} -> {SourceType:l}.{SourceProperty:l}", - targetPropertyName, + "[RelayPropertyFor] " + "Settings.{SettingsProperty:l} -> {SourceType:l}.{SourceProperty:l}", + settingsPropertyPath, sourceTypeName, - propertyName + sourcePropertyPath ); - sourceSetter(source, settingsGetter(Settings)); - }; + sourceInstanceAccessor.Set(source, settingsAccessor.Get(Settings)); + } // Set and Save settings when source changes - source.PropertyChanged += (sender, args) => + void OnSourcePropertyChanged(object? sender, PropertyChangedEventArgs args) { - if (args.PropertyName != propertyName) + if (args.PropertyName != sourcePropertyPath) return; logger.LogTrace( - "[RelayPropertyFor] " + "{SourceType:l}.{SourceProperty:l} -> Settings.{TargetProperty:l}", + "[RelayPropertyFor] {SourceType:l}.{SourceProperty:l} -> Settings.{SettingsProperty:l}", sourceTypeName, - propertyName, - targetPropertyName + sourcePropertyPath, + settingsPropertyPath ); - settingsSetter(Settings, sourceGetter(source)); + settingsAccessor.Set(Settings, sourceInstanceAccessor.Get()); if (IsLibraryDirSet) { @@ -228,25 +230,44 @@ public void RelayPropertyFor( else { logger.LogWarning( - "[RelayPropertyFor] LibraryDir not set when saving ({SourceType:l}.{SourceProperty:l} -> Settings.{TargetProperty:l})", + "[RelayPropertyFor] LibraryDir not set when saving ({SourceType:l}.{SourceProperty:l} -> Settings.{SettingsProperty:l})", sourceTypeName, - propertyName, - targetPropertyName + sourcePropertyPath, + settingsPropertyPath ); } // Invoke property changed event, passing along sender SettingsPropertyChanged?.Invoke( sender, - new RelayPropertyChangedEventArgs(targetPropertyName, true) + new RelayPropertyChangedEventArgs(settingsPropertyPath, true) ); - }; + } + + var subscription = Disposable.Create(() => + { + source.PropertyChanged -= OnSourcePropertyChanged; + SettingsPropertyChanged -= OnSettingsPropertyChanged; + }); - // Set initial value if requested - if (setInitial) + try { - sourceSetter(source, settingsGetter(Settings)); + SettingsPropertyChanged += OnSettingsPropertyChanged; + source.PropertyChanged += OnSourcePropertyChanged; + + // Set initial value if requested + if (setInitial) + { + sourceInstanceAccessor.Set(settingsAccessor.Get(Settings)); + } } + catch + { + subscription.Dispose(); + throw; + } + + return subscription; } /// @@ -255,16 +276,15 @@ public void RegisterPropertyChangedHandler( Action onPropertyChanged ) { - var settingsGetter = settingsProperty.Compile(); - var (propertyName, _) = Expressions.GetAssigner(settingsProperty); + var settingsAccessor = CompiledExpression.CreateAccessor(settingsProperty); // Invoke handler when settings change SettingsPropertyChanged += (_, args) => { - if (args.PropertyName != propertyName) + if (args.PropertyName != settingsAccessor.FullName) return; - onPropertyChanged(settingsGetter(Settings)); + onPropertyChanged(settingsAccessor.Get(Settings)); }; } diff --git a/StabilityMatrix.Core/StabilityMatrix.Core.csproj b/StabilityMatrix.Core/StabilityMatrix.Core.csproj index 00f63c9a4..3b6a7e1cd 100644 --- a/StabilityMatrix.Core/StabilityMatrix.Core.csproj +++ b/StabilityMatrix.Core/StabilityMatrix.Core.csproj @@ -23,6 +23,7 @@ + diff --git a/StabilityMatrix.Tests/Core/ExpressionsTests.cs b/StabilityMatrix.Tests/Core/ExpressionsTests.cs deleted file mode 100644 index 4d6f51a0c..000000000 --- a/StabilityMatrix.Tests/Core/ExpressionsTests.cs +++ /dev/null @@ -1,41 +0,0 @@ -using StabilityMatrix.Core.Helper; - -namespace StabilityMatrix.Tests.Core; - -[TestClass] -public class ExpressionsTests -{ - private class TestClass - { - public int Id { get; set; } - public NestedTestClass? Nested { get; set; } - } - - private class NestedTestClass - { - public string Text { get; set; } = ""; - } - - [TestMethod] - public void GetAssigner_Simple_PropertyName() - { - var (propertyName, _) = - Expressions.GetAssigner(x => x.Id); - - // Check that the property name is correct - Assert.AreEqual("Id", propertyName); - } - - [TestMethod] - public void GetAssigner_Simple_PropertyAssignment() - { - var obj = new TestClass(); - - var (_, assigner) = - Expressions.GetAssigner(x => x.Id); - - assigner.Compile()(obj, 42); - - Assert.AreEqual(42, obj.Id); - } -}