-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Stop Mutagen environment from loading too early.
This regressed in a previous version and was never detected. It was the reason why the app seemed to be taking longer to get to the first pixel, but spent no time at all creating the load order analyzer. The `SettingsViewModel` was depending on the `IGameSettings` which in turn caused the environment to be created. Effectively, this completely ignored any customizations being made to the plugin selections at startup. It's too valuable to have global access to these types to consider moving them all into providers and factories and such, and doesn't really help that much anyway. Using `Lazy<T>` can alleviate the issue somewhat by deferring access to until the load order list is needed - but unfortunately this isn't quite good enough because starting the app in first-time mode will trigger this right away. Instead, we add a "future"-like type, that's a little more intelligent than `Lazy<T>` and uses that under the hood, but requires a bit more wire-up and post-activation logic from the IoC container. Other types can now accept an `IAfterSetup<T>`, which will never actually cause the value to be resolved, instead will throw if the dependency has yet to be created - essentially a "weak reference" version of the Lazy type, with a callback version that is designed to work with property-change notifications. Fixes #113
- Loading branch information
1 parent
f5674a9
commit 1bd05e0
Showing
9 changed files
with
200 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
using Moq; | ||
using System; | ||
using System.Collections.Generic; | ||
using Xunit; | ||
|
||
namespace Focus.Environment.Tests | ||
{ | ||
public class AfterGameSetupTests | ||
{ | ||
private readonly AfterGameSetup<string> afterGameSetup; | ||
private readonly Mock<IGameSetup> gameSetupMock; | ||
private readonly Lazy<string> lazy; | ||
|
||
public AfterGameSetupTests() | ||
{ | ||
gameSetupMock = new Mock<IGameSetup>(); | ||
lazy = new(() => "Dummy Value"); | ||
afterGameSetup = new AfterGameSetup<string>(gameSetupMock.Object, lazy); | ||
} | ||
|
||
[Fact] | ||
public void WhenValueAccessedBeforeSetupConfirmed_Throws() | ||
{ | ||
Assert.Throws<InvalidOperationException>(() => afterGameSetup.Value); | ||
} | ||
|
||
[Fact] | ||
public void WhenValueAccessedAfterSetupConfirmed_Resolves() | ||
{ | ||
gameSetupMock.SetupGet(x => x.IsConfirmed).Returns(true); | ||
|
||
Assert.Equal("Dummy Value", afterGameSetup.Value); | ||
} | ||
|
||
[Fact] | ||
public void WhenCallbacksRegistered_DoesNotInvokeBeforeNotify() | ||
{ | ||
string cbResult1 = null; | ||
string cbResult2 = null; | ||
afterGameSetup.OnValue(x => cbResult1 = x + "1"); | ||
afterGameSetup.OnValue(x => cbResult2 = x + "2"); | ||
|
||
Assert.Null(cbResult1); | ||
Assert.Null(cbResult2); | ||
} | ||
|
||
[Fact] | ||
public void WhenCallbacksRegistered_InvokesAfterExplicitNotify() | ||
{ | ||
string cbResult1 = null; | ||
string cbResult2 = null; | ||
afterGameSetup.OnValue(x => cbResult1 = x + "1"); | ||
afterGameSetup.OnValue(x => cbResult2 = x + "2"); | ||
afterGameSetup.NotifyValue(); | ||
|
||
Assert.Equal("Dummy Value1", cbResult1); | ||
Assert.Equal("Dummy Value2", cbResult2); | ||
} | ||
|
||
[Fact] | ||
public void WhenNotifiedMultipleTimes_InvokesCallbacksOnlyOnce() | ||
{ | ||
var cbResults = new List<string>(); | ||
afterGameSetup.OnValue(x => cbResults.Add(x)); | ||
afterGameSetup.NotifyValue(); | ||
afterGameSetup.NotifyValue(); | ||
afterGameSetup.NotifyValue(); | ||
|
||
Assert.Collection(cbResults, x => Assert.Equal("Dummy Value", x)); | ||
} | ||
|
||
[Fact] | ||
public void WhenCallbacksRegisteredAfterAlreadyNotified_InvokesImmediately() | ||
{ | ||
string cbResult = null; | ||
afterGameSetup.NotifyValue(); | ||
afterGameSetup.OnValue(x => cbResult = x); | ||
|
||
Assert.Equal("Dummy Value", cbResult); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace Focus.Environment | ||
{ | ||
public interface IAfterGameSetup<T> | ||
{ | ||
bool IsReady { get; } | ||
T Value { get; } | ||
|
||
void OnValue(Action<T> callback); | ||
} | ||
|
||
public interface IAfterGameSetupNotifier | ||
{ | ||
void NotifyValue(); | ||
} | ||
|
||
public class AfterGameSetup<T> : IAfterGameSetup<T>, IAfterGameSetupNotifier | ||
{ | ||
public bool IsReady => setup.IsConfirmed; | ||
public T Value => ValueOrThrow(); | ||
|
||
private readonly Lazy<T> lazy; | ||
private readonly IGameSetup setup; | ||
private readonly List<Action<T>> valueCallbacks = new(); | ||
|
||
private bool isNotified; | ||
|
||
public AfterGameSetup(IGameSetup setup, Lazy<T> lazy) | ||
{ | ||
this.lazy = lazy; | ||
this.setup = setup; | ||
} | ||
|
||
public void NotifyValue() | ||
{ | ||
if (isNotified) | ||
return; | ||
foreach (var cb in valueCallbacks) | ||
cb(lazy.Value); | ||
valueCallbacks.Clear(); | ||
isNotified = true; | ||
} | ||
|
||
public void OnValue(Action<T> callback) | ||
{ | ||
if (isNotified) | ||
callback(lazy.Value); | ||
else | ||
valueCallbacks.Add(callback); | ||
} | ||
|
||
private T ValueOrThrow() | ||
{ | ||
if (!setup.IsConfirmed) | ||
throw new InvalidOperationException("Value cannot be accessed until game setup is confirmed."); | ||
return lazy.Value; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters