From 7807ee9676a51a6a7f1e8cbd30bb6d09a12ec28a Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Fri, 15 Jan 2021 16:53:32 +0100 Subject: [PATCH] Adds ScopedOwner class, that disposes owned values when garbage collected. --- PkmnLibSharp/Battling/Battle/Battle.cs | 5 +- PkmnLibSharp/Battling/Battle/BattleBuilder.cs | 6 +- PkmnLibSharp/Battling/Battle/ExecutingMove.cs | 6 ++ PkmnLibSharp/Battling/LearnedMove.cs | 7 +- PkmnLibSharp/Battling/Pokemon.cs | 7 +- PkmnLibSharp/Battling/PokemonBuilder.cs | 5 +- PkmnLibSharp/Battling/PokemonParty.cs | 4 +- PkmnLibSharp/Library/Items/Item.cs | 5 ++ PkmnLibSharp/Library/Moves/MoveData.cs | 5 ++ PkmnLibSharp/Library/Species/Forme.cs | 5 ++ PkmnLibSharp/Library/Species/Species.cs | 5 ++ PkmnLibSharp/Native/Linux/libCreatureLib.so | 4 +- PkmnLibSharp/Native/Linux/libpkmnLib.so | 4 +- PkmnLibSharp/Utilities/PointerWrapper.cs | 10 +-- .../Utilities/ReadOnlyNativePtrArray.cs | 19 +++++ PkmnLibSharp/Utilities/ScopedOwner.cs | 75 +++++++++++++++++++ .../Battling/BattleTests/BasicBattleTests.cs | 66 ++++++++-------- .../Battling/PokemonBuilderTests.cs | 14 ++-- 18 files changed, 195 insertions(+), 57 deletions(-) create mode 100644 PkmnLibSharp/Utilities/ScopedOwner.cs diff --git a/PkmnLibSharp/Battling/Battle/Battle.cs b/PkmnLibSharp/Battling/Battle/Battle.cs index 7d42dac..1f90d8e 100644 --- a/PkmnLibSharp/Battling/Battle/Battle.cs +++ b/PkmnLibSharp/Battling/Battle/Battle.cs @@ -236,7 +236,10 @@ namespace PkmnLibSharp.Battling public override void Dispose() { - Creaturelib.Generated.Battle.ClearBattle(Ptr); + if (!IsDeleted) + { + Creaturelib.Generated.Battle.ClearBattle(Ptr); + } base.Dispose(); } } diff --git a/PkmnLibSharp/Battling/Battle/BattleBuilder.cs b/PkmnLibSharp/Battling/Battle/BattleBuilder.cs index f4b5dd4..6f41c8f 100644 --- a/PkmnLibSharp/Battling/Battle/BattleBuilder.cs +++ b/PkmnLibSharp/Battling/Battle/BattleBuilder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using PkmnLibSharp.Utilities; namespace PkmnLibSharp.Battling { @@ -34,11 +35,12 @@ namespace PkmnLibSharp.Battling return this; } - public virtual Battle Build() + public virtual ScopedOwner Build() { // Use the milliseconds since epoch time as random seed if none is specified. _seed ??= (ulong) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds; - return new Battle(_library, _parties.ToArray(), _canFlee, _numberOfSides, _pokemonPerSide, _seed.Value); + return new ScopedOwner(new Battle(_library, _parties.ToArray(), _canFlee, _numberOfSides, + _pokemonPerSide, _seed.Value)); } } } \ No newline at end of file diff --git a/PkmnLibSharp/Battling/Battle/ExecutingMove.cs b/PkmnLibSharp/Battling/Battle/ExecutingMove.cs index 3dba295..590d023 100644 --- a/PkmnLibSharp/Battling/Battle/ExecutingMove.cs +++ b/PkmnLibSharp/Battling/Battle/ExecutingMove.cs @@ -67,6 +67,12 @@ namespace PkmnLibSharp.Battling private LearnedMove? _move; private Pokemon? _user; private ReadOnlyNativePtrArray? _targets; + + public override string ToString() + { + return base.ToString() + $": {Move.Move.Name}"; + } + protected override void DeletePtr() { } diff --git a/PkmnLibSharp/Battling/LearnedMove.cs b/PkmnLibSharp/Battling/LearnedMove.cs index 4f57238..45ac543 100644 --- a/PkmnLibSharp/Battling/LearnedMove.cs +++ b/PkmnLibSharp/Battling/LearnedMove.cs @@ -41,7 +41,12 @@ namespace PkmnLibSharp.Battling (MoveLearnMethod) Creaturelib.Generated.LearnedAttack.GetLearnMethod(Ptr); private MoveData? _move; - + + public override string ToString() + { + return base.ToString() + $": {Move.Name} PP: {RemainingUses}/{MaxUses}"; + } + protected override void DeletePtr() { LearnedAttack.Destruct(Ptr); diff --git a/PkmnLibSharp/Battling/Pokemon.cs b/PkmnLibSharp/Battling/Pokemon.cs index e0bec39..0c01a57 100644 --- a/PkmnLibSharp/Battling/Pokemon.cs +++ b/PkmnLibSharp/Battling/Pokemon.cs @@ -487,7 +487,12 @@ namespace PkmnLibSharp.Battling private Nature? _nature; private Battle? _battle; private BattleSide? _battleSide; - + + public override string ToString() + { + return base.ToString() + $": {Species.Name}-{Forme.Name} lv. {Level} HP: {CurrentHealth}/{MaxHealth}"; + } + protected override void DeletePtr() { Pkmnlib.Generated.Pokemon.Destruct(Ptr); diff --git a/PkmnLibSharp/Battling/PokemonBuilder.cs b/PkmnLibSharp/Battling/PokemonBuilder.cs index d4e70f7..d7cc42f 100644 --- a/PkmnLibSharp/Battling/PokemonBuilder.cs +++ b/PkmnLibSharp/Battling/PokemonBuilder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using PkmnLibSharp.Library; using PkmnLibSharp.Library.Items; using PkmnLibSharp.Library.Moves; +using PkmnLibSharp.Utilities; using Random = PkmnLibSharp.Utilities.Random; namespace PkmnLibSharp.Battling @@ -150,7 +151,7 @@ namespace PkmnLibSharp.Battling protected abstract TLearnedMove CreateLearnedMove(MoveData move, byte maxUses, MoveLearnMethod learnMethod); - public TPokemon Build(Random? random = null) + public ScopedOwner Build(Random? random = null) { if (!Library.StaticLibrary.SpeciesLibrary.TryGet(Species, out var species)) { @@ -200,7 +201,7 @@ namespace PkmnLibSharp.Battling Gender = species.GetRandomGender(random); } - return Finalize(species, forme!, heldItem, moves, nature); + return new ScopedOwner(Finalize(species, forme!, heldItem, moves, nature)); } } } \ No newline at end of file diff --git a/PkmnLibSharp/Battling/PokemonParty.cs b/PkmnLibSharp/Battling/PokemonParty.cs index c924ef4..96901e0 100644 --- a/PkmnLibSharp/Battling/PokemonParty.cs +++ b/PkmnLibSharp/Battling/PokemonParty.cs @@ -106,10 +106,12 @@ namespace PkmnLibSharp.Battling protected internal override void MarkAsDeleted() { base.MarkAsDeleted(); - foreach (var pokemon in Party) + for (var index = 0; index < Party.Count; index++) { + var pokemon = Party.GetDontInitialise(index); pokemon?.MarkAsDeleted(); } + _cache = null; } diff --git a/PkmnLibSharp/Library/Items/Item.cs b/PkmnLibSharp/Library/Items/Item.cs index 1fe7c13..d5d2f08 100644 --- a/PkmnLibSharp/Library/Items/Item.cs +++ b/PkmnLibSharp/Library/Items/Item.cs @@ -32,6 +32,11 @@ namespace PkmnLibSharp.Library.Items Initialize(p); } + public override string ToString() + { + return base.ToString() + $": {Name}"; + } + protected override void DeletePtr() { Pkmnlib.Generated.Item.Destruct(Ptr); diff --git a/PkmnLibSharp/Library/Moves/MoveData.cs b/PkmnLibSharp/Library/Moves/MoveData.cs index e95286c..c4c9527 100644 --- a/PkmnLibSharp/Library/Moves/MoveData.cs +++ b/PkmnLibSharp/Library/Moves/MoveData.cs @@ -44,6 +44,11 @@ namespace PkmnLibSharp.Library.Moves Initialize(ptr); } + public override string ToString() + { + return base.ToString() + $": {Name}"; + } + protected override void DeletePtr() { AttackData.Destruct(Ptr); diff --git a/PkmnLibSharp/Library/Species/Forme.cs b/PkmnLibSharp/Library/Species/Forme.cs index 7804918..a38787d 100644 --- a/PkmnLibSharp/Library/Species/Forme.cs +++ b/PkmnLibSharp/Library/Species/Forme.cs @@ -143,6 +143,11 @@ namespace PkmnLibSharp.Library return SpeciesVariant.HasFlag(Ptr, flag.ToPtr()) == 1; } + public override string ToString() + { + return base.ToString() + $": {Name}"; + } + protected internal override void MarkAsDeleted() { base.MarkAsDeleted(); diff --git a/PkmnLibSharp/Library/Species/Species.cs b/PkmnLibSharp/Library/Species/Species.cs index a3f2343..fe8b3aa 100644 --- a/PkmnLibSharp/Library/Species/Species.cs +++ b/PkmnLibSharp/Library/Species/Species.cs @@ -146,6 +146,11 @@ namespace PkmnLibSharp.Library private ReadOnlyNativePtrArray? _formes; private string[]? _eggGroups; + public override string ToString() + { + return $"(#{Ptr}) -> Species: {Name}"; + } + protected internal override void MarkAsDeleted() { base.MarkAsDeleted(); diff --git a/PkmnLibSharp/Native/Linux/libCreatureLib.so b/PkmnLibSharp/Native/Linux/libCreatureLib.so index 4011926..3e4c11b 100755 --- a/PkmnLibSharp/Native/Linux/libCreatureLib.so +++ b/PkmnLibSharp/Native/Linux/libCreatureLib.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f61261ce8e6fc703322705cfda92cd000329cbce59fc76882340dc1872f379b5 -size 4300192 +oid sha256:27621bdf10c2fe2da54ea3f314e2662b68e87f5f157a2482d4ea235d4bb0e5e8 +size 2427480 diff --git a/PkmnLibSharp/Native/Linux/libpkmnLib.so b/PkmnLibSharp/Native/Linux/libpkmnLib.so index 24b2f6a..a5f8ce1 100755 --- a/PkmnLibSharp/Native/Linux/libpkmnLib.so +++ b/PkmnLibSharp/Native/Linux/libpkmnLib.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d09d6d7a48b9613935c0219e45b350d6cf254a41b8a2b3edfaeef48d9b859462 -size 3409000 +oid sha256:3b107711d0b729d150cae0f584442241bc150d6d0eb56acadeeb33137f72c704 +size 3410088 diff --git a/PkmnLibSharp/Utilities/PointerWrapper.cs b/PkmnLibSharp/Utilities/PointerWrapper.cs index 1656c28..b4fd405 100644 --- a/PkmnLibSharp/Utilities/PointerWrapper.cs +++ b/PkmnLibSharp/Utilities/PointerWrapper.cs @@ -11,7 +11,7 @@ namespace PkmnLibSharp.Utilities { get { - if (_isDeleted) + if (IsDeleted) { throw new Exception( "Pointer access after dispose detected. This is not legal, and will cause native exceptions."); @@ -21,7 +21,7 @@ namespace PkmnLibSharp.Utilities } } - private bool _isDeleted = false; + protected bool IsDeleted { get; private set; } = false; private static readonly ConcurrentDictionary> Cached = new ConcurrentDictionary>(); @@ -48,7 +48,7 @@ namespace PkmnLibSharp.Utilities ~PointerWrapper() { - if (!_isDeleted) + if (!IsDeleted) Cached.TryRemove(Ptr, out _); } @@ -80,7 +80,7 @@ namespace PkmnLibSharp.Utilities public virtual void Dispose() { - if (_isDeleted) + if (IsDeleted) return; DeletePtr(); MarkAsDeleted(); @@ -88,7 +88,7 @@ namespace PkmnLibSharp.Utilities protected internal virtual void MarkAsDeleted() { - _isDeleted = true; + IsDeleted = true; Cached.TryRemove(_ptr, out _); } diff --git a/PkmnLibSharp/Utilities/ReadOnlyNativePtrArray.cs b/PkmnLibSharp/Utilities/ReadOnlyNativePtrArray.cs index 8a65932..eadb8bc 100644 --- a/PkmnLibSharp/Utilities/ReadOnlyNativePtrArray.cs +++ b/PkmnLibSharp/Utilities/ReadOnlyNativePtrArray.cs @@ -70,6 +70,25 @@ namespace PkmnLibSharp.Utilities } return -1; } + + internal T? GetDontInitialise(int index) + { + unsafe + { + if (index >= Count) + throw new IndexOutOfRangeException(); + // Where's your god now? + // (We add the offset of the index to the pointer, then dereference the pointer pointer to get the actual pointer to the object we want.) + var p = new IntPtr(*(void**)IntPtr.Add(_ptr, index * IntPtr.Size).ToPointer()); + if (p == IntPtr.Zero) + return null; + if (_cache[index]?.Ptr == p) + return _cache[index]!; + if (PointerWrapper.TryResolvePointer(p, out T? t)) + return t!; + return null; + } + } public T? this[int index] { diff --git a/PkmnLibSharp/Utilities/ScopedOwner.cs b/PkmnLibSharp/Utilities/ScopedOwner.cs new file mode 100644 index 0000000..bb3f640 --- /dev/null +++ b/PkmnLibSharp/Utilities/ScopedOwner.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; + +namespace PkmnLibSharp.Utilities +{ + /// + /// Helper class to ensure an object is disposed when it goes out of scope. + /// + /// + public class ScopedOwner : IDisposable where T : PointerWrapper + { + public T? Value { get; private set; } + + public ScopedOwner(T value) + { + Value = value; + } + + public T? TakeOwnership() + { + var val = Value; + Value = null; + return val; + } + + ~ScopedOwner() + { + if (Value != null) + Value.Dispose(); + } + + public static implicit operator T?(ScopedOwner val) => val.Value; + + public override string ToString() + { + if (Value == null) return "null"; + return Value.ToString(); + } + + public void Dispose() + { + if (Value != null) + Value.Dispose(); + Value = null; + } + + protected bool Equals(ScopedOwner other) + { + return EqualityComparer.Default.Equals(Value, other.Value); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ScopedOwner) obj); + } + + public override int GetHashCode() + { + return EqualityComparer.Default.GetHashCode(Value); + } + + public static bool operator ==(ScopedOwner? left, ScopedOwner? right) + { + return Equals(left, right); + } + + public static bool operator !=(ScopedOwner? left, ScopedOwner? right) + { + return !Equals(left, right); + } + } +} \ No newline at end of file diff --git a/PkmnLibSharpTests/Battling/BattleTests/BasicBattleTests.cs b/PkmnLibSharpTests/Battling/BattleTests/BasicBattleTests.cs index ed8618f..5745573 100644 --- a/PkmnLibSharpTests/Battling/BattleTests/BasicBattleTests.cs +++ b/PkmnLibSharpTests/Battling/BattleTests/BasicBattleTests.cs @@ -16,12 +16,12 @@ namespace PkmnLibSharpTests.Battling.BattleTests { var lib = BattleLibraryHelper.GetLibrary(); var battle = new BattleBuilder(lib, true, 2, 1).Build(); - Assert.AreEqual(lib, battle.Library); - Assert.AreEqual(true, battle.CanFlee); - Assert.AreEqual(2, battle.SidesCount); - Assert.AreEqual(false, battle.HasEnded); - Assert.AreEqual(0, battle.PartiesCount); - battle.Dispose(); + Assert.AreEqual(lib, battle.Value.Library); + Assert.AreEqual(true, battle.Value.CanFlee); + Assert.AreEqual(2, battle.Value.SidesCount); + Assert.AreEqual(false, battle.Value.HasEnded); + Assert.AreEqual(0, battle.Value.PartiesCount); + battle.Value.Dispose(); } private static PokemonParty BuildTestParty(BattleLibrary lib) @@ -45,10 +45,10 @@ namespace PkmnLibSharpTests.Battling.BattleTests .WithPartyOnPositions(p1, new BattlePosition(0, 0)) .WithPartyOnPositions(p2, new BattlePosition(1, 0)) .Build(); - Assert.AreEqual(2, battle.PartiesCount); - Assert.AreEqual(p1, battle.Parties[0].Party); - Assert.AreEqual(p2, battle.Parties[1].Party); - battle.Dispose(); + Assert.AreEqual(2, battle.Value.PartiesCount); + Assert.AreEqual(p1, battle.Value.Parties[0].Party); + Assert.AreEqual(p2, battle.Value.Parties[1].Party); + battle.Value.Dispose(); } [Test] @@ -63,19 +63,19 @@ namespace PkmnLibSharpTests.Battling.BattleTests .WithPartyOnPositions(p2, new BattlePosition(1, 0)) .Build(); - battle.SwitchPokemon(0, 0, p1.GetAtIndex(0)); - battle.SwitchPokemon(1, 0, p2.GetAtIndex(0)); - Assert.AreEqual(0, battle.CurrentTurn); + battle.Value.SwitchPokemon(0, 0, p1.GetAtIndex(0)); + battle.Value.SwitchPokemon(1, 0, p2.GetAtIndex(0)); + Assert.AreEqual(0, battle.Value.CurrentTurn); var moveTurn1 = new MoveTurnChoice(p1.GetAtIndex(0), p1.GetAtIndex(0).Moves[0], 1, 0 ); var moveTurn2 = new MoveTurnChoice(p2.GetAtIndex(0), p2.GetAtIndex(0).Moves[0], 0, 0 ); - Assert.That(battle.TrySetChoice(moveTurn1)); - Assert.That(battle.TrySetChoice(moveTurn2)); + Assert.That(battle.Value.TrySetChoice(moveTurn1)); + Assert.That(battle.Value.TrySetChoice(moveTurn2)); - Assert.AreEqual(battle.CurrentTurn, 1); + Assert.AreEqual(battle.Value.CurrentTurn, 1); - battle.Dispose(); + battle.Value.Dispose(); } [Test] @@ -97,18 +97,18 @@ namespace PkmnLibSharpTests.Battling.BattleTests evts.Add(evt); return Task.CompletedTask; }); - battle.RegisterEventListener(eventListener); + battle.Value.RegisterEventListener(eventListener); - battle.SwitchPokemon(0, 0, p1.GetAtIndex(0)); - battle.SwitchPokemon(1, 0, p2.GetAtIndex(0)); - Assert.AreEqual(0, battle.CurrentTurn); + battle.Value.SwitchPokemon(0, 0, p1.GetAtIndex(0)); + battle.Value.SwitchPokemon(1, 0, p2.GetAtIndex(0)); + Assert.AreEqual(0, battle.Value.CurrentTurn); var moveTurn1 = new MoveTurnChoice(p1.GetAtIndex(0), p1.GetAtIndex(0).Moves[0], 1, 0 ); var moveTurn2 = new MoveTurnChoice(p2.GetAtIndex(0), p2.GetAtIndex(0).Moves[0], 0, 0 ); - Assert.That(battle.TrySetChoice(moveTurn1)); - Assert.That(battle.TrySetChoice(moveTurn2)); - Assert.AreEqual(1, battle.CurrentTurn); + Assert.That(battle.Value.TrySetChoice(moveTurn1)); + Assert.That(battle.Value.TrySetChoice(moveTurn2)); + Assert.AreEqual(1, battle.Value.CurrentTurn); eventListener.EnsureFinishedListening(); @@ -117,7 +117,7 @@ namespace PkmnLibSharpTests.Battling.BattleTests Assert.AreEqual(damageEvents[0].Pokemon, p2.GetAtIndex(0)); Assert.AreEqual(damageEvents[1].Pokemon, p1.GetAtIndex(0)); - battle.Dispose(); + battle.Value.Dispose(); } [Test] @@ -140,21 +140,21 @@ namespace PkmnLibSharpTests.Battling.BattleTests evts.Add(evt); return Task.CompletedTask; }); - battle.RegisterEventListener(eventListener); + battle.Value.RegisterEventListener(eventListener); - battle.SwitchPokemon(0, 0, p1.GetAtIndex(0)); - battle.SwitchPokemon(1, 0, p2.GetAtIndex(0)); - Assert.AreEqual(0, battle.CurrentTurn); + battle.Value.SwitchPokemon(0, 0, p1.GetAtIndex(0)); + battle.Value.SwitchPokemon(1, 0, p2.GetAtIndex(0)); + Assert.AreEqual(0, battle.Value.CurrentTurn); var moveTurn1 = new MoveTurnChoice(p1.GetAtIndex(0), p1.GetAtIndex(0).Moves[0], 1, 0 ); var moveTurn2 = new MoveTurnChoice(p2.GetAtIndex(0), p2.GetAtIndex(0).Moves[0], 0, 0 ); - Assert.That(battle.TrySetChoice(moveTurn1)); - Assert.That(battle.TrySetChoice(moveTurn2)); + Assert.That(battle.Value.TrySetChoice(moveTurn1)); + Assert.That(battle.Value.TrySetChoice(moveTurn2)); - Assert.AreEqual(battle.CurrentTurn, 1); + Assert.AreEqual(battle.Value.CurrentTurn, 1); - battle.Dispose(); + battle.Value.Dispose(); mon1.Heal(1000, true); mon2.Heal(1000, true); diff --git a/PkmnLibSharpTests/Battling/PokemonBuilderTests.cs b/PkmnLibSharpTests/Battling/PokemonBuilderTests.cs index 0623d80..1e9b524 100644 --- a/PkmnLibSharpTests/Battling/PokemonBuilderTests.cs +++ b/PkmnLibSharpTests/Battling/PokemonBuilderTests.cs @@ -13,9 +13,9 @@ namespace PkmnLibSharpTests.Battling var lib = BattleLibraryHelper.GetLibrary(); var pokemon = new PokemonBuilder(lib, "testSpecies", 50) .Build(); - Assert.AreEqual("testSpecies", pokemon.Species.Name); - Assert.AreEqual(50, pokemon.Level); - Assert.AreEqual("default", pokemon.Forme.Name); + Assert.AreEqual("testSpecies", pokemon.Value.Species.Name); + Assert.AreEqual(50, pokemon.Value.Level); + Assert.AreEqual("default", pokemon.Value.Forme.Name); pokemon.Dispose(); } @@ -26,7 +26,7 @@ namespace PkmnLibSharpTests.Battling var pokemon = new PokemonBuilder(lib, "testSpecies", 50) .WithNickname("cuteNickname") .Build(); - Assert.AreEqual("cuteNickname", pokemon.Nickname); + Assert.AreEqual("cuteNickname", pokemon.Value.Nickname); pokemon.Dispose(); } @@ -37,7 +37,7 @@ namespace PkmnLibSharpTests.Battling var pokemon = new PokemonBuilder(lib, "testSpecies", 50) .WithGender(Gender.Female) .Build(); - Assert.AreEqual(Gender.Female, pokemon.Gender); + Assert.AreEqual(Gender.Female, pokemon.Value.Gender); pokemon.Dispose(); } @@ -49,8 +49,8 @@ namespace PkmnLibSharpTests.Battling .LearnMove("testMove", MoveLearnMethod.Unknown) .LearnMove("testMove2", MoveLearnMethod.Level) .Build(); - Assert.AreEqual("testMove", pokemon.Moves[0].Move.Name); - Assert.AreEqual("testMove2", pokemon.Moves[1].Move.Name); + Assert.AreEqual("testMove", pokemon.Value.Moves[0].Move.Name); + Assert.AreEqual("testMove2", pokemon.Value.Moves[1].Move.Name); pokemon.Dispose(); }