Change everything in collection caches to use IMod and introduce TemporaryMod.

This commit is contained in:
Ottermandias 2022-06-18 16:00:20 +02:00
parent c578bd3a49
commit fc767589a2
18 changed files with 129 additions and 74 deletions

View file

@ -378,7 +378,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
if( group.Type == SelectType.Single )
{
var name = optionNames[ ^1 ];
var optionIdx = group.IndexOf( o => o.Name == name );
var optionIdx = group.IndexOf( o => o.Name == optionNames[^1] );
if( optionIdx < 0 )
{
return PenumbraApiEc.OptionMissing;

View file

@ -65,8 +65,8 @@ public partial class ModCollection
internal IReadOnlyDictionary< Utf8GamePath, ModPath > ResolvedFiles
=> _cache?.ResolvedFiles ?? new Dictionary< Utf8GamePath, ModPath >();
internal IReadOnlyDictionary< string, (SingleArray< Mod >, object?) > ChangedItems
=> _cache?.ChangedItems ?? new Dictionary< string, (SingleArray< Mod >, object?) >();
internal IReadOnlyDictionary< string, (SingleArray< IMod >, object?) > ChangedItems
=> _cache?.ChangedItems ?? new Dictionary< string, (SingleArray< IMod >, object?) >();
internal IEnumerable< SingleArray< ModConflicts > > AllConflicts
=> _cache?.AllConflicts ?? Array.Empty< SingleArray< ModConflicts > >();

View file

@ -11,8 +11,8 @@ using Penumbra.Util;
namespace Penumbra.Collections;
public record struct ModPath( Mod Mod, FullPath Path );
public record ModConflicts( Mod Mod2, List< object > Conflicts, bool HasPriority, bool Solved );
public record struct ModPath( IMod Mod, FullPath Path );
public record ModConflicts( IMod Mod2, List< object > Conflicts, bool HasPriority, bool Solved );
public partial class ModCollection
{
@ -21,15 +21,15 @@ public partial class ModCollection
private class Cache : IDisposable
{
private readonly ModCollection _collection;
private readonly SortedList< string, (SingleArray< Mod >, object?) > _changedItems = new();
private readonly SortedList< string, (SingleArray< IMod >, object?) > _changedItems = new();
public readonly Dictionary< Utf8GamePath, ModPath > ResolvedFiles = new();
public readonly MetaManager MetaManipulations;
private readonly Dictionary< Mod, SingleArray< ModConflicts > > _conflicts = new();
private readonly Dictionary< IMod, SingleArray< ModConflicts > > _conflicts = new();
public IEnumerable< SingleArray< ModConflicts > > AllConflicts
=> _conflicts.Values;
public SingleArray< ModConflicts > Conflicts( Mod mod )
public SingleArray< ModConflicts > Conflicts( IMod mod )
=> _conflicts.TryGetValue( mod, out var c ) ? c : new SingleArray< ModConflicts >();
// Count the number of changes of the effective file list.
@ -38,7 +38,7 @@ public partial class ModCollection
private int _changedItemsSaveCounter = -1;
// Obtain currently changed items. Computes them if they haven't been computed before.
public IReadOnlyDictionary< string, (SingleArray< Mod >, object?) > ChangedItems
public IReadOnlyDictionary< string, (SingleArray< IMod >, object?) > ChangedItems
{
get
{
@ -178,13 +178,13 @@ public partial class ModCollection
}
}
public void ReloadMod( Mod mod, bool addMetaChanges )
public void ReloadMod( IMod mod, bool addMetaChanges )
{
RemoveMod( mod, addMetaChanges );
AddMod( mod, addMetaChanges );
}
public void RemoveMod( Mod mod, bool addMetaChanges )
public void RemoveMod( IMod mod, bool addMetaChanges )
{
var conflicts = Conflicts( mod );
@ -243,37 +243,40 @@ public partial class ModCollection
// Add all files and possibly manipulations of a given mod according to its settings in this collection.
public void AddMod( Mod mod, bool addMetaChanges )
public void AddMod( IMod mod, bool addMetaChanges )
{
var settings = _collection[ mod.Index ].Settings;
if( settings is not { Enabled: true } )
if( mod.Index >= 0 )
{
return;
}
foreach( var (group, groupIndex) in mod.Groups.WithIndex().OrderByDescending( g => g.Item1.Priority ) )
{
if( group.Count == 0 )
var settings = _collection[ mod.Index ].Settings;
if( settings is not { Enabled: true } )
{
continue;
return;
}
var config = settings.Settings[ groupIndex ];
switch( group.Type )
foreach( var (group, groupIndex) in mod.Groups.WithIndex().OrderByDescending( g => g.Item1.Priority ) )
{
case SelectType.Single:
AddSubMod( group[ ( int )config ], mod );
break;
case SelectType.Multi:
if( group.Count == 0 )
{
foreach( var (option, _) in group.WithIndex()
.OrderByDescending( p => group.OptionPriority( p.Item2 ) )
.Where( p => ( ( 1 << p.Item2 ) & config ) != 0 ) )
{
AddSubMod( option, mod );
}
continue;
}
break;
var config = settings.Settings[ groupIndex ];
switch( group.Type )
{
case SelectType.Single:
AddSubMod( group[ ( int )config ], mod );
break;
case SelectType.Multi:
{
foreach( var (option, _) in group.WithIndex()
.OrderByDescending( p => group.OptionPriority( p.Item2 ) )
.Where( p => ( ( 1 << p.Item2 ) & config ) != 0 ) )
{
AddSubMod( option, mod );
}
break;
}
}
}
}
@ -297,7 +300,7 @@ public partial class ModCollection
}
// Add all files and possibly manipulations of a specific submod
private void AddSubMod( ISubMod subMod, Mod parentMod )
private void AddSubMod( ISubMod subMod, IMod parentMod )
{
foreach( var (path, file) in subMod.Files.Concat( subMod.FileSwaps ) )
{
@ -320,7 +323,7 @@ public partial class ModCollection
// For different mods, higher mod priority takes precedence before option group priority,
// which takes precedence before option priority, which takes precedence before ordering.
// Inside the same mod, conflicts are not recorded.
private void AddFile( Utf8GamePath path, FullPath file, Mod mod )
private void AddFile( Utf8GamePath path, FullPath file, IMod mod )
{
if( ResolvedFiles.TryAdd( path, new ModPath( mod, file ) ) )
{
@ -343,7 +346,7 @@ public partial class ModCollection
// Remove all empty conflict sets for a given mod with the given conflicts.
// If transitive is true, also removes the corresponding version of the other mod.
private void RemoveEmptyConflicts( Mod mod, SingleArray< ModConflicts > oldConflicts, bool transitive )
private void RemoveEmptyConflicts( IMod mod, SingleArray< ModConflicts > oldConflicts, bool transitive )
{
var changedConflicts = oldConflicts.Remove( c =>
{
@ -372,10 +375,10 @@ public partial class ModCollection
// Add a new conflict between the added mod and the existing mod.
// Update all other existing conflicts between the existing mod and other mods if necessary.
// Returns if the added mod takes priority before the existing mod.
private bool AddConflict( object data, Mod addedMod, Mod existingMod )
private bool AddConflict( object data, IMod addedMod, IMod existingMod )
{
var addedPriority = addedMod.Index >= 0 ? _collection[ addedMod.Index ].Settings!.Priority : int.MaxValue;
var existingPriority = existingMod.Index >= 0 ? _collection[ existingMod.Index ].Settings!.Priority : int.MaxValue;
var addedPriority = addedMod.Index >= 0 ? _collection[ addedMod.Index ].Settings!.Priority : addedMod.Priority;
var existingPriority = existingMod.Index >= 0 ? _collection[ existingMod.Index ].Settings!.Priority : existingMod.Priority;
if( existingPriority < addedPriority )
{
@ -417,7 +420,7 @@ public partial class ModCollection
// For different mods, higher mod priority takes precedence before option group priority,
// which takes precedence before option priority, which takes precedence before ordering.
// Inside the same mod, conflicts are not recorded.
private void AddManipulation( MetaManipulation manip, Mod mod )
private void AddManipulation( MetaManipulation manip, IMod mod )
{
if( !MetaManipulations.TryGetValue( manip, out var existingMod ) )
{
@ -463,7 +466,7 @@ public partial class ModCollection
{
if( !_changedItems.TryGetValue( name, out var data ) )
{
_changedItems.Add( name, ( new SingleArray< Mod >( modPath.Mod ), obj ) );
_changedItems.Add( name, ( new SingleArray< IMod >( modPath.Mod ), obj ) );
}
else if( !data.Item1.Contains( modPath.Mod ) )
{

View file

@ -14,7 +14,7 @@ public partial class MetaManager
public struct MetaManagerCmp : IDisposable
{
public CmpFile? File = null;
public readonly Dictionary< RspManipulation, Mod > Manipulations = new();
public readonly Dictionary< RspManipulation, IMod > Manipulations = new();
public MetaManagerCmp()
{ }
@ -39,7 +39,7 @@ public partial class MetaManager
Manipulations.Clear();
}
public bool ApplyMod( RspManipulation m, Mod mod )
public bool ApplyMod( RspManipulation m, IMod mod )
{
#if USE_CMP
Manipulations[ m ] = mod;

View file

@ -17,7 +17,7 @@ public partial class MetaManager
{
public readonly ExpandedEqdpFile?[] Files = new ExpandedEqdpFile?[CharacterUtility.NumEqdpFiles - 2]; // TODO: female Hrothgar
public readonly Dictionary< EqdpManipulation, Mod > Manipulations = new();
public readonly Dictionary< EqdpManipulation, IMod > Manipulations = new();
public MetaManagerEqdp()
{ }
@ -51,7 +51,7 @@ public partial class MetaManager
Manipulations.Clear();
}
public bool ApplyMod( EqdpManipulation m, Mod mod )
public bool ApplyMod( EqdpManipulation m, IMod mod )
{
#if USE_EQDP
Manipulations[ m ] = mod;

View file

@ -14,7 +14,7 @@ public partial class MetaManager
public struct MetaManagerEqp : IDisposable
{
public ExpandedEqpFile? File = null;
public readonly Dictionary< EqpManipulation, Mod > Manipulations = new();
public readonly Dictionary< EqpManipulation, IMod > Manipulations = new();
public MetaManagerEqp()
{ }
@ -39,7 +39,7 @@ public partial class MetaManager
Manipulations.Clear();
}
public bool ApplyMod( EqpManipulation m, Mod mod )
public bool ApplyMod( EqpManipulation m, IMod mod )
{
#if USE_EQP
Manipulations[ m ] = mod;

View file

@ -18,7 +18,7 @@ public partial class MetaManager
public EstFile? BodyFile = null;
public EstFile? HeadFile = null;
public readonly Dictionary< EstManipulation, Mod > Manipulations = new();
public readonly Dictionary< EstManipulation, IMod > Manipulations = new();
public MetaManagerEst()
{ }
@ -51,7 +51,7 @@ public partial class MetaManager
Manipulations.Clear();
}
public bool ApplyMod( EstManipulation m, Mod mod )
public bool ApplyMod( EstManipulation m, IMod mod )
{
#if USE_EST
Manipulations[ m ] = mod;

View file

@ -14,7 +14,7 @@ public partial class MetaManager
public struct MetaManagerGmp : IDisposable
{
public ExpandedGmpFile? File = null;
public readonly Dictionary< GmpManipulation, Mod > Manipulations = new();
public readonly Dictionary< GmpManipulation, IMod > Manipulations = new();
public MetaManagerGmp()
{ }
@ -38,7 +38,7 @@ public partial class MetaManager
}
}
public bool ApplyMod( GmpManipulation m, Mod mod )
public bool ApplyMod( GmpManipulation m, IMod mod )
{
#if USE_GMP
Manipulations[ m ] = mod;

View file

@ -18,7 +18,7 @@ public partial class MetaManager
public readonly struct MetaManagerImc : IDisposable
{
public readonly Dictionary< Utf8GamePath, ImcFile > Files = new();
public readonly Dictionary< ImcManipulation, Mod > Manipulations = new();
public readonly Dictionary< ImcManipulation, IMod > Manipulations = new();
private readonly ModCollection _collection;
private static int _imcManagerCount;
@ -65,7 +65,7 @@ public partial class MetaManager
Manipulations.Clear();
}
public bool ApplyMod( ImcManipulation m, Mod mod )
public bool ApplyMod( ImcManipulation m, IMod mod )
{
#if USE_IMC
Manipulations[ m ] = mod;

View file

@ -30,7 +30,7 @@ public partial class MetaManager : IDisposable
}
}
public bool TryGetValue( MetaManipulation manip, [NotNullWhen(true)] out Mod? mod )
public bool TryGetValue( MetaManipulation manip, [NotNullWhen(true)] out IMod? mod )
{
mod = manip.ManipulationType switch
{
@ -86,7 +86,7 @@ public partial class MetaManager : IDisposable
Imc.Dispose();
}
public bool ApplyMod( MetaManipulation m, Mod mod )
public bool ApplyMod( MetaManipulation m, IMod mod )
{
return m.ManipulationType switch
{

View file

@ -0,0 +1,19 @@
using System.Collections.Generic;
using OtterGui.Classes;
namespace Penumbra.Mods;
public interface IMod
{
LowerString Name { get; }
public int Index { get; }
public int Priority { get; }
public int TotalManipulations { get; }
public ISubMod Default { get; }
public IReadOnlyList< IModGroup > Groups { get; }
public IEnumerable< ISubMod > AllSubMods { get; }
}

View file

@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Penumbra.GameData.ByteString;
using Penumbra.Util;
namespace Penumbra.Mods;
public partial class Mod
public partial class Mod : IMod
{
public partial class Editor : IDisposable
{
@ -15,7 +12,7 @@ public partial class Mod
public Editor( Mod mod, int groupIdx, int optionIdx )
{
_mod = mod;
_mod = mod;
SetSubMod( groupIdx, optionIdx );
GroupIdx = groupIdx;
_subMod = _mod._default;

View file

@ -17,6 +17,10 @@ public partial class Mod
public DirectoryInfo ModPath { get; private set; }
public int Index { get; private set; } = -1;
// Unused if Index < 0 but used for special temporary mods.
public int Priority
=> 0;
private Mod( DirectoryInfo modPath )
=> ModPath = modPath;
@ -30,7 +34,7 @@ public partial class Mod
}
var mod = new Mod( modPath );
if( !mod.Reload(out _) )
if( !mod.Reload( out _ ) )
{
// Can not be base path not existing because that is checked before.
PluginLog.Error( $"Mod at {modPath} without name is not supported." );
@ -40,15 +44,17 @@ public partial class Mod
return mod;
}
private bool Reload(out MetaChangeType metaChange)
private bool Reload( out MetaChangeType metaChange )
{
metaChange = MetaChangeType.Deletion;
ModPath.Refresh();
if( !ModPath.Exists )
{
return false;
}
metaChange = LoadMeta();
if( metaChange.HasFlag(MetaChangeType.Deletion) || Name.Length == 0 )
if( metaChange.HasFlag( MetaChangeType.Deletion ) || Name.Length == 0 )
{
return false;
}

View file

@ -24,10 +24,11 @@ public enum MetaChangeType : ushort
public sealed partial class Mod
{
public static readonly Mod ForcedFiles = new(new DirectoryInfo( "." ))
public static readonly TemporaryMod ForcedFiles = new()
{
Name = "Forced Files",
Index = -1,
Name = "Forced Files",
Index = -1,
Priority = int.MaxValue,
};
public const uint CurrentFileVersion = 1;

View file

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using OtterGui.Classes;
namespace Penumbra.Mods;
public sealed partial class Mod
{
public class TemporaryMod : IMod
{
public LowerString Name { get; init; } = LowerString.Empty;
public int Index { get; init; } = -2;
public int Priority { get; init; } = int.MaxValue;
public int TotalManipulations
=> Default.Manipulations.Count;
public ISubMod Default { get; } = new SubMod();
public IReadOnlyList< IModGroup > Groups
=> Array.Empty< IModGroup >();
public IEnumerable< ISubMod > AllSubMods
=> new[] { Default };
}
}

View file

@ -23,13 +23,13 @@ public partial class ConfigWindow
private void DrawChangedItemTab()
{
// Functions in here for less pollution.
bool FilterChangedItem( KeyValuePair< string, (SingleArray< Mod >, object?) > item )
bool FilterChangedItem( KeyValuePair< string, (SingleArray< IMod >, object?) > item )
=> ( _changedItemFilter.IsEmpty
|| ChangedItemName( item.Key, item.Value.Item2 )
.Contains( _changedItemFilter.Lower, StringComparison.InvariantCultureIgnoreCase ) )
&& ( _changedItemModFilter.IsEmpty || item.Value.Item1.Any( m => m.Name.Contains( _changedItemModFilter ) ) );
void DrawChangedItemColumn( KeyValuePair< string, (SingleArray< Mod >, object?) > item )
void DrawChangedItemColumn( KeyValuePair< string, (SingleArray< IMod >, object?) > item )
{
ImGui.TableNextColumn();
DrawChangedItem( item.Key, item.Value.Item2, false );

View file

@ -112,7 +112,7 @@ public partial class ConfigWindow
{
// We can treat all meta manipulations the same,
// we are only really interested in their ToString function here.
static (object, Mod) Convert< T >( KeyValuePair< T, Mod > kvp )
static (object, IMod) Convert< T >( KeyValuePair< T, IMod > kvp )
=> ( kvp.Key!, kvp.Value );
var it = m.Cmp.Manipulations.Select( Convert )
@ -183,7 +183,7 @@ public partial class ConfigWindow
}
// Draw a line for a unfiltered/unconverted manipulation and mod-index pair.
private static void DrawLine( (object, Mod) pair )
private static void DrawLine( (object, IMod) pair )
{
var (manipulation, mod) = pair;
ImGui.TableNextColumn();

View file

@ -128,16 +128,19 @@ public partial class ConfigWindow
foreach( var conflict in Penumbra.CollectionManager.Current.Conflicts( _mod ) )
{
if( ImGui.Selectable( conflict.Mod2.Name ) )
if( ImGui.Selectable( conflict.Mod2.Name ) && conflict.Mod2 is Mod mod )
{
_window._selector.SelectByValue( conflict.Mod2 );
_window._selector.SelectByValue( mod );
}
ImGui.SameLine();
using( var color = ImRaii.PushColor( ImGuiCol.Text,
conflict.HasPriority ? ColorId.HandledConflictMod.Value() : ColorId.ConflictingMod.Value() ) )
{
ImGui.TextUnformatted( $"(Priority {Penumbra.CollectionManager.Current[ conflict.Mod2.Index ].Settings!.Priority})" );
var priority = conflict.Mod2.Index < 0
? conflict.Mod2.Priority
: Penumbra.CollectionManager.Current[conflict.Mod2.Index].Settings!.Priority;
ImGui.TextUnformatted( $"(Priority {priority})" );
}
using var indent = ImRaii.PushIndent( 30f );