Skip to content

Commit

Permalink
Added settings instance cache and cache invalidation
Browse files Browse the repository at this point in the history
Using net6-windows target for the UI project because of windows forms
  • Loading branch information
Aragas committed Mar 2, 2024
1 parent 7cce3dc commit 20bbbfe
Show file tree
Hide file tree
Showing 18 changed files with 189 additions and 51 deletions.
2 changes: 1 addition & 1 deletion build/common.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<PropertyGroup>
<GameVersion>1.0.0</GameVersion>
<GameVersionWithPrefix>v$(GameVersion)</GameVersionWithPrefix>
<Version>5.9.2</Version>
<Version>5.10.0</Version>
<HarmonyVersion>2.2.2</HarmonyVersion>
<ButterLibVersion>2.8.15</ButterLibVersion>
<UIExtenderExVersion>2.8.1</UIExtenderExVersion>
Expand Down
4 changes: 4 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
---------------------------------------------------------------------------------------------------
Version: 5.10.0
Game Versions: v1.0.0,v1.0.1,v1.0.2,v1.0.3,v1.1.0,v1.1.1,v1.1.2,v1.1.3,v1.1.4,v1.1.5,v1.1.6,v1.2.x
* Added Settings instance cache and cache invalidation
---------------------------------------------------------------------------------------------------
Version: 5.9.2
Game Versions: v1.0.0,v1.0.1,v1.0.2,v1.0.3,v1.1.0,v1.1.1,v1.1.2,v1.1.3,v1.1.4,v1.1.5,v1.1.6,v1.2.7
* Switched to .NET 6 for Xbox
Expand Down
29 changes: 21 additions & 8 deletions src/MCM.Abstractions/Base/Global/GlobalSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@ namespace MCM.Abstractions.Base.Global
{
/// <summary>
/// A modder flriendly way to get settings from any place
/// We now cache it. Cache is invalidated on game start/end.
/// </summary>
public static T? Instance
public static T? Instance => (T?) CacheInstance.GetOrAdd(Cache.GetOrAdd(typeof(T), static _ => new T().Id), static id =>
{
get
{
if (!Cache.ContainsKey(typeof(T)))
Cache.TryAdd(typeof(T), new T().Id);
return BaseSettingsProvider.Instance?.GetSettings(Cache[typeof(T)]) as T;
}
}
return BaseSettingsProvider.Instance?.GetSettings(id!);
});

/// <summary>
/// A slower way to get settings from any place.
/// </summary>
public static T? InstanceNotCached => (T?) BaseSettingsProvider.Instance?.GetSettings(Cache.GetOrAdd(typeof(T), static _ =>
{
return new T().Id;
})!);
}

#if !BANNERLORDMCM_INCLUDE_IN_CODE_COVERAGE
Expand All @@ -38,5 +42,14 @@ public static T? Instance
abstract class GlobalSettings : BaseSettings
{
protected static readonly ConcurrentDictionary<Type, string> Cache = new();
// We don't need to clear it since the only case when the settings instance can change is when
// we add a new settings cotainer, which is not possible at runtime
protected static readonly ConcurrentDictionary<string, BaseSettings?> CacheInstance = new();

internal static void InvalidateCache()
{
Cache.Clear();
CacheInstance.Clear();
}
}
}
27 changes: 19 additions & 8 deletions src/MCM.Abstractions/Base/PerCampaign/PerCampaignSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@ namespace MCM.Abstractions.Base.PerCampaign
{
/// <summary>
/// A modder flriendly way to get settings from any place
/// We now cache it. Cache is invalidated on game start/end.
/// </summary>
public static T? Instance
public static T? Instance => (T?) CacheInstance.GetOrAdd(Cache.GetOrAdd(typeof(T), static _ => new T().Id), static id =>
{
get
{
if (!Cache.ContainsKey(typeof(T)))
Cache.TryAdd(typeof(T), new T().Id);
return BaseSettingsProvider.Instance?.GetSettings(Cache[typeof(T)]) as T;
}
}
return BaseSettingsProvider.Instance?.GetSettings(id!);
});

/// <summary>
/// A slower way to get settings from any place.
/// </summary>
public static T? InstanceNotCached => (T?) BaseSettingsProvider.Instance?.GetSettings(Cache.GetOrAdd(typeof(T), static _ =>
{
return new T().Id;
})!);
}

#if !BANNERLORDMCM_INCLUDE_IN_CODE_COVERAGE
Expand All @@ -38,7 +42,14 @@ public static T? Instance
abstract class PerCampaignSettings : BaseSettings
{
protected static readonly ConcurrentDictionary<Type, string> Cache = new();
protected static readonly ConcurrentDictionary<string, BaseSettings?> CacheInstance = new();

internal static void InvalidateCache()
{
Cache.Clear();
CacheInstance.Clear();
}

public sealed override string FormatType { get; } = "json2";
}
}
27 changes: 19 additions & 8 deletions src/MCM.Abstractions/Base/PerSave/PerSaveSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@ namespace MCM.Abstractions.Base.PerSave
{
/// <summary>
/// A modder flriendly way to get settings from any place
/// We now cache it. Cache is invalidated on game start/end.
/// </summary>
public static T? Instance
public static T? Instance => (T?) CacheInstance.GetOrAdd(Cache.GetOrAdd(typeof(T), static _ => new T().Id), static id =>
{
get
{
if (!Cache.ContainsKey(typeof(T)))
Cache.TryAdd(typeof(T), new T().Id);
return BaseSettingsProvider.Instance?.GetSettings(Cache[typeof(T)]) as T;
}
}
return BaseSettingsProvider.Instance?.GetSettings(id!);
});

/// <summary>
/// A slower way to get settings from any place.
/// </summary>
public static T? InstanceNotCached => (T?) BaseSettingsProvider.Instance?.GetSettings(Cache.GetOrAdd(typeof(T), static _ =>
{
return new T().Id;
})!);
}

#if !BANNERLORDMCM_INCLUDE_IN_CODE_COVERAGE
Expand All @@ -38,7 +42,14 @@ public static T? Instance
abstract class PerSaveSettings : BaseSettings
{
protected static readonly ConcurrentDictionary<Type, string> Cache = new();
protected static readonly ConcurrentDictionary<string, BaseSettings?> CacheInstance = new();

internal static void InvalidateCache()
{
Cache.Clear();
CacheInstance.Clear();
}

public sealed override string FormatType { get; } = "json2";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;

namespace MCM.Abstractions
{
#if !BANNERLORDMCM_PUBLIC
internal
#else
public
# endif
interface ISettingsContainerCanInvalidateCache
{
event Action InstanceCacheInvalidated;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface IPerCampaignSettingsContainer :
ISettingsContainerCanReset,
ISettingsContainerPresets,
ISettingsContainerHasUnavailable,
ISettingsContainerHasSettingsPack
ISettingsContainerHasSettingsPack,
ISettingsContainerCanInvalidateCache
{ }
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ interface IPerSaveSettingsContainer :
ISettingsContainerCanReset,
ISettingsContainerPresets,
ISettingsContainerHasUnavailable,
ISettingsContainerHasSettingsPack
ISettingsContainerHasSettingsPack,
ISettingsContainerCanInvalidateCache
{
void LoadSettings();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;

namespace MCM.Abstractions
{
#if !BANNERLORDMCM_PUBLIC
internal
#else
public
# endif
enum ExternalSettingsProviderInvalidateCacheType
{
None,
Global,
PerCampaign,
PerSave,
}

/// <summary>
/// Used to add foreign Options API's that MCM will be able to use.
/// Most likely will be used to ease backwards compatibility ports of older MCM API's so we'll be able to reuse more code.
/// This is a higher level alternative to using <see cref="ISettingsContainer"/>.
/// </summary>
#if !BANNERLORDMCM_PUBLIC
internal
#else
public
# endif
interface IExternalSettingsProviderCanInvalidateCache
{
event Action<ExternalSettingsProviderInvalidateCacheType> InstanceCacheInvalidated;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using MCM.Abstractions.GameFeatures;
using MCM.Abstractions.PerCampaign;

using System;
using System.Collections.Generic;
using System.Linq;

Expand All @@ -16,6 +17,9 @@ namespace MCM.Implementation.PerCampaign
#endif
internal sealed class FluentPerCampaignSettingsContainer : BaseSettingsContainer<FluentPerCampaignSettings>, IFluentPerCampaignSettingsContainer
{
/// <inheritdoc/>
public event Action? InstanceCacheInvalidated;

Check warning on line 21 in src/MCM.Implementation/Containers/PerCampaign/FluentPerCampaignSettingsContainer.cs

View workflow job for this annotation

GitHub Actions / Test Module

The event 'FluentPerCampaignSettingsContainer.InstanceCacheInvalidated' is never used

private readonly IGameEventListener _gameEventListener;

public FluentPerCampaignSettingsContainer(IGameEventListener gameEventListener)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ namespace MCM.Implementation.PerCampaign
#endif
internal sealed class PerCampaignSettingsContainer : BaseSettingsContainer<PerCampaignSettings>, IPerCampaignSettingsContainer
{
/// <inheritdoc/>
public event Action? InstanceCacheInvalidated;

private readonly IBUTRLogger _logger;
private readonly IGameEventListener _gameEventListener;

Expand Down Expand Up @@ -96,6 +99,7 @@ public override bool SaveSettings(BaseSettings settings)
private void GameStarted()
{
_hasGameStarted = true;
InstanceCacheInvalidated?.Invoke();
LoadedSettings.Clear();
}

Expand Down Expand Up @@ -165,6 +169,7 @@ public IEnumerable<UnavailableSetting> GetUnavailableSettings() => !_hasGameStar
private void GameEnded()
{
_hasGameStarted = false;
InstanceCacheInvalidated?.Invoke();
LoadedSettings.Clear();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using MCM.Abstractions.GameFeatures;
using MCM.Abstractions.PerSave;

using System;
using System.Collections.Generic;
using System.Linq;

Expand All @@ -16,6 +17,9 @@ namespace MCM.Implementation.PerSave
#endif
internal sealed class FluentPerSaveSettingsContainer : BaseSettingsContainer<FluentPerSaveSettings>, IFluentPerSaveSettingsContainer
{
/// <inheritdoc/>
public event Action? InstanceCacheInvalidated;

Check warning on line 21 in src/MCM.Implementation/Containers/PerSave/FluentPerSaveSettingsContainer.cs

View workflow job for this annotation

GitHub Actions / Test Module

The event 'FluentPerSaveSettingsContainer.InstanceCacheInvalidated' is never used

private readonly IGameEventListener _gameEventListener;

public FluentPerSaveSettingsContainer(IGameEventListener gameEventListener)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ namespace MCM.Implementation.PerSave
#endif
internal sealed class PerSaveSettingsContainer : BaseSettingsContainer<PerSaveSettings>, IPerSaveSettingsContainer
{
/// <inheritdoc/>
public event Action? InstanceCacheInvalidated;

private readonly IBUTRLogger _logger;
private readonly IGameEventListener _gameEventListener;

Expand Down Expand Up @@ -75,6 +78,7 @@ public override bool SaveSettings(BaseSettings settings)
private void GameStarted()
{
_hasGameStarted = true;
InstanceCacheInvalidated?.Invoke();
LoadedSettings.Clear();
}

Expand Down Expand Up @@ -137,6 +141,7 @@ public IEnumerable<UnavailableSetting> GetUnavailableSettings() => !_hasGameStar
private void GameEnded()
{
_hasGameStarted = false;
InstanceCacheInvalidated?.Invoke();
LoadedSettings.Clear();
}
}
Expand Down
33 changes: 32 additions & 1 deletion src/MCM.Implementation/Providers/DefaultSettingsProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

using MCM.Abstractions;
using MCM.Abstractions.Base;
using MCM.Abstractions.Base.Global;
using MCM.Abstractions.Base.PerCampaign;
using MCM.Abstractions.Base.PerSave;
using MCM.Abstractions.Global;
using MCM.Abstractions.PerCampaign;
using MCM.Abstractions.PerSave;
Expand Down Expand Up @@ -40,18 +43,46 @@ public DefaultSettingsProvider(IBUTRLogger<DefaultSettingsProvider> logger)

foreach (var globalSettingsContainer in globalSettingsContainers)
{
if (globalSettingsContainer is ISettingsContainerCanInvalidateCache canInvalidateCache)
canInvalidateCache.InstanceCacheInvalidated += GlobalSettings.InvalidateCache;

logger.LogInformation($"Found Global container {globalSettingsContainer.GetType()} ({globalSettingsContainer.SettingsDefinitions.Count()})");
}
foreach (var perSaveSettingsContainer in perSaveSettingsContainers)
{
if (perSaveSettingsContainer is ISettingsContainerCanInvalidateCache canInvalidateCache)
canInvalidateCache.InstanceCacheInvalidated += PerSaveSettings.InvalidateCache;

logger.LogInformation($"Found PerSave container {perSaveSettingsContainer.GetType()} ({perSaveSettingsContainer.SettingsDefinitions.Count()})");
}
foreach (var perCampaignSettingsContainer in perCampaignSettingsContainers)
{
if (perCampaignSettingsContainer is ISettingsContainerCanInvalidateCache canInvalidateCache)
canInvalidateCache.InstanceCacheInvalidated += PerCampaignSettings.InvalidateCache;

logger.LogInformation($"Found Campaign container {perCampaignSettingsContainer.GetType()} ({perCampaignSettingsContainer.SettingsDefinitions.Count()})");
}
foreach (var externalSettingsProvider in externalSettingsProviders)
{
if (externalSettingsProvider is IExternalSettingsProviderCanInvalidateCache canInvalidateCache)
{
canInvalidateCache.InstanceCacheInvalidated += (cacheType) =>
{
switch (cacheType)
{
case ExternalSettingsProviderInvalidateCacheType.Global:
GlobalSettings.InvalidateCache();
break;
case ExternalSettingsProviderInvalidateCacheType.PerSave:
PerSaveSettings.InvalidateCache();
break;
case ExternalSettingsProviderInvalidateCacheType.PerCampaign:
PerCampaignSettings.InvalidateCache();
break;
}
};
}

logger.LogInformation($"Found external provider {externalSettingsProvider.GetType()} ({externalSettingsProvider.SettingsDefinitions.Count()})");
}

Expand All @@ -60,7 +91,7 @@ public DefaultSettingsProvider(IBUTRLogger<DefaultSettingsProvider> logger)
.Concat(perSaveSettingsContainers)
.Concat(perCampaignSettingsContainers)
.ToList();

_externalSettingsProviders = externalSettingsProviders.ToList();
}

Expand Down
2 changes: 1 addition & 1 deletion src/MCM.Publish/MCM.Publish.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net472;net6</TargetFrameworks>
<TargetFrameworks>net472;net6-windows</TargetFrameworks>
<Authors>Aragas</Authors>
<ModuleId>Bannerlord.MBOptionScreen</ModuleId>
<ModuleName>Mod Configuration Menu v5</ModuleName>
Expand Down
Loading

0 comments on commit 20bbbfe

Please sign in to comment.