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);
- }
-}