Make Individual Collection lookup thread-safe by locking.

This commit is contained in:
Ottermandias 2023-04-03 12:14:43 +02:00
parent e9ab9a71a8
commit 5a817db069
4 changed files with 134 additions and 116 deletions

View file

@ -459,7 +459,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
if (!id.IsValid) if (!id.IsValid)
return (false, false, _collectionManager.Default.Name); return (false, false, _collectionManager.Default.Name);
if (_collectionManager.Individuals.Individuals.TryGetValue(id, out var collection)) if (_collectionManager.Individuals.TryGetValue(id, out var collection))
return (true, true, collection.Name); return (true, true, collection.Name);
AssociatedCollection(gameObjectIdx, out collection); AssociatedCollection(gameObjectIdx, out collection);
@ -474,7 +474,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
if (!id.IsValid) if (!id.IsValid)
return (PenumbraApiEc.InvalidIdentifier, _collectionManager.Default.Name); return (PenumbraApiEc.InvalidIdentifier, _collectionManager.Default.Name);
var oldCollection = _collectionManager.Individuals.Individuals.TryGetValue(id, out var c) ? c.Name : string.Empty; var oldCollection = _collectionManager.Individuals.TryGetValue(id, out var c) ? c.Name : string.Empty;
if (collectionName.Length == 0) if (collectionName.Length == 0)
{ {
@ -809,8 +809,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi
if (!identifier.IsValid) if (!identifier.IsValid)
return (PenumbraApiEc.InvalidArgument, string.Empty); return (PenumbraApiEc.InvalidArgument, string.Empty);
if (!forceOverwriteCharacter && _collectionManager.Individuals.Individuals.ContainsKey(identifier) if (!forceOverwriteCharacter && _collectionManager.Individuals.ContainsKey(identifier)
|| _tempCollections.Collections.Individuals.ContainsKey(identifier)) || _tempCollections.Collections.ContainsKey(identifier))
return (PenumbraApiEc.CharacterCollectionExists, string.Empty); return (PenumbraApiEc.CharacterCollectionExists, string.Empty);
var name = $"{tag}_{character}"; var name = $"{tag}_{character}";
@ -855,11 +855,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi
if (forceAssignment) if (forceAssignment)
{ {
if (_tempCollections.Collections.Individuals.ContainsKey(identifier) && !_tempCollections.Collections.Delete(identifier)) if (_tempCollections.Collections.ContainsKey(identifier) && !_tempCollections.Collections.Delete(identifier))
return PenumbraApiEc.AssignmentDeletionFailed; return PenumbraApiEc.AssignmentDeletionFailed;
} }
else if (_tempCollections.Collections.Individuals.ContainsKey(identifier) else if (_tempCollections.Collections.ContainsKey(identifier)
|| _collectionManager.Individuals.Individuals.ContainsKey(identifier)) || _collectionManager.Individuals.ContainsKey(identifier))
{ {
return PenumbraApiEc.CharacterCollectionExists; return PenumbraApiEc.CharacterCollectionExists;
} }

View file

@ -114,6 +114,6 @@ public class TempCollectionManager : IDisposable
return false; return false;
var identifier = Penumbra.Actors.CreatePlayer(byteString, worldId); var identifier = Penumbra.Actors.CreatePlayer(byteString, worldId);
return Collections.Individuals.TryGetValue(identifier, out var collection) && RemoveTemporaryCollection(collection.Name); return Collections.TryGetValue(identifier, out var collection) && RemoveTemporaryCollection(collection.Name);
} }
} }

View file

@ -58,7 +58,7 @@ public sealed partial class CollectionManager : ISavable
CollectionType.Default => Default, CollectionType.Default => Default,
CollectionType.Interface => Interface, CollectionType.Interface => Interface,
CollectionType.Current => Current, CollectionType.Current => Current,
CollectionType.Individual => identifier.IsValid && Individuals.Individuals.TryGetValue(identifier, out var c) ? c : null, CollectionType.Individual => identifier.IsValid && Individuals.TryGetValue(identifier, out var c) ? c : null,
_ => null, _ => null,
}; };
} }

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Enums;
using OtterGui.Filesystem; using OtterGui.Filesystem;
@ -18,9 +19,6 @@ public sealed partial class IndividualCollections
public IReadOnlyList<(string DisplayName, IReadOnlyList<ActorIdentifier> Identifiers, ModCollection Collection)> Assignments public IReadOnlyList<(string DisplayName, IReadOnlyList<ActorIdentifier> Identifiers, ModCollection Collection)> Assignments
=> _assignments; => _assignments;
public IReadOnlyDictionary< ActorIdentifier, ModCollection > Individuals
=> _individuals;
// TODO // TODO
public IndividualCollections(ActorService actorManager) public IndividualCollections(ActorService actorManager)
=> _actorManager = actorManager.AwaitedService; => _actorManager = actorManager.AwaitedService;
@ -35,27 +33,41 @@ public sealed partial class IndividualCollections
Invalid, Invalid,
} }
public bool TryGetValue(ActorIdentifier identifier, [NotNullWhen(true)] out ModCollection? collection)
{
lock (_individuals)
{
return _individuals.TryGetValue(identifier, out collection);
}
}
public bool ContainsKey(ActorIdentifier identifier)
{
lock (_individuals)
{
return _individuals.ContainsKey(identifier);
}
}
public AddResult CanAdd(params ActorIdentifier[] identifiers) public AddResult CanAdd(params ActorIdentifier[] identifiers)
{ {
if (identifiers.Length == 0) if (identifiers.Length == 0)
{
return AddResult.Invalid; return AddResult.Invalid;
}
if (identifiers.Any(i => !i.IsValid)) if (identifiers.Any(i => !i.IsValid))
{
return AddResult.Invalid; return AddResult.Invalid;
}
if( identifiers.Any( Individuals.ContainsKey ) ) bool set;
lock (_individuals)
{ {
return AddResult.AlreadySet; set = identifiers.Any(_individuals.ContainsKey);
} }
return AddResult.Valid; return set ? AddResult.AlreadySet : AddResult.Valid;
} }
public AddResult CanAdd( IdentifierType type, string name, ushort homeWorld, ObjectKind kind, IEnumerable< uint > dataIds, out ActorIdentifier[] identifiers ) public AddResult CanAdd(IdentifierType type, string name, ushort homeWorld, ObjectKind kind, IEnumerable<uint> dataIds,
out ActorIdentifier[] identifiers)
{ {
identifiers = Array.Empty<ActorIdentifier>(); identifiers = Array.Empty<ActorIdentifier>();
@ -63,30 +75,31 @@ public sealed partial class IndividualCollections
{ {
case IdentifierType.Player: case IdentifierType.Player:
if (!ByteString.FromString(name, out var playerName)) if (!ByteString.FromString(name, out var playerName))
{
return AddResult.Invalid; return AddResult.Invalid;
}
identifiers = new[] { _actorManager.CreatePlayer( playerName, homeWorld ) }; identifiers = new[]
{
_actorManager.CreatePlayer(playerName, homeWorld),
};
break; break;
case IdentifierType.Retainer: case IdentifierType.Retainer:
if (!ByteString.FromString(name, out var retainerName)) if (!ByteString.FromString(name, out var retainerName))
{
return AddResult.Invalid; return AddResult.Invalid;
}
identifiers = new[] { _actorManager.CreateRetainer( retainerName, 0 ) }; identifiers = new[]
{
_actorManager.CreateRetainer(retainerName, 0),
};
break; break;
case IdentifierType.Owned: case IdentifierType.Owned:
if (!ByteString.FromString(name, out var ownerName)) if (!ByteString.FromString(name, out var ownerName))
{
return AddResult.Invalid; return AddResult.Invalid;
}
identifiers = dataIds.Select(id => _actorManager.CreateOwned(ownerName, homeWorld, kind, id)).ToArray(); identifiers = dataIds.Select(id => _actorManager.CreateOwned(ownerName, homeWorld, kind, id)).ToArray();
break; break;
case IdentifierType.Npc: case IdentifierType.Npc:
identifiers = dataIds.Select( id => _actorManager.CreateIndividual( IdentifierType.Npc, ByteString.Empty, ushort.MaxValue, kind, id ) ).ToArray(); identifiers = dataIds
.Select(id => _actorManager.CreateIndividual(IdentifierType.Npc, ByteString.Empty, ushort.MaxValue, kind, id)).ToArray();
break; break;
default: default:
identifiers = Array.Empty<ActorIdentifier>(); identifiers = Array.Empty<ActorIdentifier>();
@ -99,9 +112,7 @@ public sealed partial class IndividualCollections
public ActorIdentifier[] GetGroup(ActorIdentifier identifier) public ActorIdentifier[] GetGroup(ActorIdentifier identifier)
{ {
if (!identifier.IsValid) if (!identifier.IsValid)
{
return Array.Empty<ActorIdentifier>(); return Array.Empty<ActorIdentifier>();
}
static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier) static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier)
{ {
@ -116,14 +127,24 @@ public sealed partial class IndividualCollections
_ => throw new NotImplementedException(), _ => throw new NotImplementedException(),
}; };
return table.Where(kvp => kvp.Value == name) return table.Where(kvp => kvp.Value == name)
.Select( kvp => manager.CreateIndividualUnchecked( identifier.Type, identifier.PlayerName, identifier.HomeWorld, identifier.Kind, kvp.Key ) ).ToArray(); .Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld, identifier.Kind,
kvp.Key)).ToArray();
} }
return identifier.Type switch return identifier.Type switch
{ {
IdentifierType.Player => new[] { identifier.CreatePermanent() }, IdentifierType.Player => new[]
IdentifierType.Special => new[] { identifier }, {
IdentifierType.Retainer => new[] { identifier.CreatePermanent() }, identifier.CreatePermanent(),
},
IdentifierType.Special => new[]
{
identifier,
},
IdentifierType.Retainer => new[]
{
identifier.CreatePermanent(),
},
IdentifierType.Owned => CreateNpcs(_actorManager, identifier.CreatePermanent()), IdentifierType.Owned => CreateNpcs(_actorManager, identifier.CreatePermanent()),
IdentifierType.Npc => CreateNpcs(_actorManager, identifier), IdentifierType.Npc => CreateNpcs(_actorManager, identifier),
_ => Array.Empty<ActorIdentifier>(), _ => Array.Empty<ActorIdentifier>(),
@ -133,9 +154,7 @@ public sealed partial class IndividualCollections
internal bool Add(ActorIdentifier[] identifiers, ModCollection collection) internal bool Add(ActorIdentifier[] identifiers, ModCollection collection)
{ {
if (identifiers.Length == 0 || !identifiers[0].IsValid) if (identifiers.Length == 0 || !identifiers[0].IsValid)
{
return false; return false;
}
var name = DisplayString(identifiers[0]); var name = DisplayString(identifiers[0]);
return Add(name, identifiers, collection); return Add(name, identifiers, collection);
@ -146,15 +165,16 @@ public sealed partial class IndividualCollections
if (CanAdd(identifiers) != AddResult.Valid if (CanAdd(identifiers) != AddResult.Valid
|| displayName.Length == 0 || displayName.Length == 0
|| _assignments.Any(a => a.DisplayName.Equals(displayName, StringComparison.OrdinalIgnoreCase))) || _assignments.Any(a => a.DisplayName.Equals(displayName, StringComparison.OrdinalIgnoreCase)))
{
return false; return false;
}
for (var i = 0; i < identifiers.Length; ++i) for (var i = 0; i < identifiers.Length; ++i)
{ {
identifiers[i] = identifiers[i].CreatePermanent(); identifiers[i] = identifiers[i].CreatePermanent();
lock (_individuals)
{
_individuals.Add(identifiers[i], collection); _individuals.Add(identifiers[i], collection);
} }
}
_assignments.Add((displayName, identifiers, collection)); _assignments.Add((displayName, identifiers, collection));
@ -170,13 +190,12 @@ public sealed partial class IndividualCollections
internal bool ChangeCollection(int displayIndex, ModCollection newCollection) internal bool ChangeCollection(int displayIndex, ModCollection newCollection)
{ {
if (displayIndex < 0 || displayIndex >= _assignments.Count || _assignments[displayIndex].Collection == newCollection) if (displayIndex < 0 || displayIndex >= _assignments.Count || _assignments[displayIndex].Collection == newCollection)
{
return false; return false;
}
_assignments[displayIndex] = _assignments[displayIndex] with { Collection = newCollection }; _assignments[displayIndex] = _assignments[displayIndex] with { Collection = newCollection };
foreach( var identifier in _assignments[ displayIndex ].Identifiers ) lock (_individuals)
{ {
foreach (var identifier in _assignments[displayIndex].Identifiers)
_individuals[identifier] = newCollection; _individuals[identifier] = newCollection;
} }
@ -192,14 +211,13 @@ public sealed partial class IndividualCollections
internal bool Delete(int displayIndex) internal bool Delete(int displayIndex)
{ {
if (displayIndex < 0 || displayIndex >= _assignments.Count) if (displayIndex < 0 || displayIndex >= _assignments.Count)
{
return false; return false;
}
var (name, identifiers, _) = _assignments[displayIndex]; var (name, identifiers, _) = _assignments[displayIndex];
_assignments.RemoveAt(displayIndex); _assignments.RemoveAt(displayIndex);
foreach( var identifier in identifiers ) lock (_individuals)
{ {
foreach (var identifier in identifiers)
_individuals.Remove(identifier); _individuals.Remove(identifier);
} }