Adds ScopedOwner class, that disposes owned values when garbage collected.

This commit is contained in:
Deukhoofd 2021-01-15 16:53:32 +01:00
parent 7e31bba05e
commit 7807ee9676
Signed by: Deukhoofd
GPG Key ID: F63E044490819F6F
18 changed files with 195 additions and 57 deletions

View File

@ -236,7 +236,10 @@ namespace PkmnLibSharp.Battling
public override void Dispose() public override void Dispose()
{ {
Creaturelib.Generated.Battle.ClearBattle(Ptr); if (!IsDeleted)
{
Creaturelib.Generated.Battle.ClearBattle(Ptr);
}
base.Dispose(); base.Dispose();
} }
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using PkmnLibSharp.Utilities;
namespace PkmnLibSharp.Battling namespace PkmnLibSharp.Battling
{ {
@ -34,11 +35,12 @@ namespace PkmnLibSharp.Battling
return this; return this;
} }
public virtual Battle Build() public virtual ScopedOwner<Battle> Build()
{ {
// Use the milliseconds since epoch time as random seed if none is specified. // Use the milliseconds since epoch time as random seed if none is specified.
_seed ??= (ulong) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds; _seed ??= (ulong) (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds;
return new Battle(_library, _parties.ToArray(), _canFlee, _numberOfSides, _pokemonPerSide, _seed.Value); return new ScopedOwner<Battle>(new Battle(_library, _parties.ToArray(), _canFlee, _numberOfSides,
_pokemonPerSide, _seed.Value));
} }
} }
} }

View File

@ -67,6 +67,12 @@ namespace PkmnLibSharp.Battling
private LearnedMove? _move; private LearnedMove? _move;
private Pokemon? _user; private Pokemon? _user;
private ReadOnlyNativePtrArray<Pokemon>? _targets; private ReadOnlyNativePtrArray<Pokemon>? _targets;
public override string ToString()
{
return base.ToString() + $": {Move.Move.Name}";
}
protected override void DeletePtr() protected override void DeletePtr()
{ {
} }

View File

@ -41,7 +41,12 @@ namespace PkmnLibSharp.Battling
(MoveLearnMethod) Creaturelib.Generated.LearnedAttack.GetLearnMethod(Ptr); (MoveLearnMethod) Creaturelib.Generated.LearnedAttack.GetLearnMethod(Ptr);
private MoveData? _move; private MoveData? _move;
public override string ToString()
{
return base.ToString() + $": {Move.Name} PP: {RemainingUses}/{MaxUses}";
}
protected override void DeletePtr() protected override void DeletePtr()
{ {
LearnedAttack.Destruct(Ptr); LearnedAttack.Destruct(Ptr);

View File

@ -487,7 +487,12 @@ namespace PkmnLibSharp.Battling
private Nature? _nature; private Nature? _nature;
private Battle? _battle; private Battle? _battle;
private BattleSide? _battleSide; private BattleSide? _battleSide;
public override string ToString()
{
return base.ToString() + $": {Species.Name}-{Forme.Name} lv. {Level} HP: {CurrentHealth}/{MaxHealth}";
}
protected override void DeletePtr() protected override void DeletePtr()
{ {
Pkmnlib.Generated.Pokemon.Destruct(Ptr); Pkmnlib.Generated.Pokemon.Destruct(Ptr);

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using PkmnLibSharp.Library; using PkmnLibSharp.Library;
using PkmnLibSharp.Library.Items; using PkmnLibSharp.Library.Items;
using PkmnLibSharp.Library.Moves; using PkmnLibSharp.Library.Moves;
using PkmnLibSharp.Utilities;
using Random = PkmnLibSharp.Utilities.Random; using Random = PkmnLibSharp.Utilities.Random;
namespace PkmnLibSharp.Battling namespace PkmnLibSharp.Battling
@ -150,7 +151,7 @@ namespace PkmnLibSharp.Battling
protected abstract TLearnedMove CreateLearnedMove(MoveData move, byte maxUses, MoveLearnMethod learnMethod); protected abstract TLearnedMove CreateLearnedMove(MoveData move, byte maxUses, MoveLearnMethod learnMethod);
public TPokemon Build(Random? random = null) public ScopedOwner<TPokemon> Build(Random? random = null)
{ {
if (!Library.StaticLibrary.SpeciesLibrary.TryGet(Species, out var species)) if (!Library.StaticLibrary.SpeciesLibrary.TryGet(Species, out var species))
{ {
@ -200,7 +201,7 @@ namespace PkmnLibSharp.Battling
Gender = species.GetRandomGender(random); Gender = species.GetRandomGender(random);
} }
return Finalize(species, forme!, heldItem, moves, nature); return new ScopedOwner<TPokemon>(Finalize(species, forme!, heldItem, moves, nature));
} }
} }
} }

View File

@ -106,10 +106,12 @@ namespace PkmnLibSharp.Battling
protected internal override void MarkAsDeleted() protected internal override void MarkAsDeleted()
{ {
base.MarkAsDeleted(); base.MarkAsDeleted();
foreach (var pokemon in Party) for (var index = 0; index < Party.Count; index++)
{ {
var pokemon = Party.GetDontInitialise(index);
pokemon?.MarkAsDeleted(); pokemon?.MarkAsDeleted();
} }
_cache = null; _cache = null;
} }

View File

@ -32,6 +32,11 @@ namespace PkmnLibSharp.Library.Items
Initialize(p); Initialize(p);
} }
public override string ToString()
{
return base.ToString() + $": {Name}";
}
protected override void DeletePtr() protected override void DeletePtr()
{ {
Pkmnlib.Generated.Item.Destruct(Ptr); Pkmnlib.Generated.Item.Destruct(Ptr);

View File

@ -44,6 +44,11 @@ namespace PkmnLibSharp.Library.Moves
Initialize(ptr); Initialize(ptr);
} }
public override string ToString()
{
return base.ToString() + $": {Name}";
}
protected override void DeletePtr() protected override void DeletePtr()
{ {
AttackData.Destruct(Ptr); AttackData.Destruct(Ptr);

View File

@ -143,6 +143,11 @@ namespace PkmnLibSharp.Library
return SpeciesVariant.HasFlag(Ptr, flag.ToPtr()) == 1; return SpeciesVariant.HasFlag(Ptr, flag.ToPtr()) == 1;
} }
public override string ToString()
{
return base.ToString() + $": {Name}";
}
protected internal override void MarkAsDeleted() protected internal override void MarkAsDeleted()
{ {
base.MarkAsDeleted(); base.MarkAsDeleted();

View File

@ -146,6 +146,11 @@ namespace PkmnLibSharp.Library
private ReadOnlyNativePtrArray<Forme>? _formes; private ReadOnlyNativePtrArray<Forme>? _formes;
private string[]? _eggGroups; private string[]? _eggGroups;
public override string ToString()
{
return $"(#{Ptr}) -> Species: {Name}";
}
protected internal override void MarkAsDeleted() protected internal override void MarkAsDeleted()
{ {
base.MarkAsDeleted(); base.MarkAsDeleted();

Binary file not shown.

BIN
PkmnLibSharp/Native/Linux/libpkmnLib.so (Stored with Git LFS)

Binary file not shown.

View File

@ -11,7 +11,7 @@ namespace PkmnLibSharp.Utilities
{ {
get get
{ {
if (_isDeleted) if (IsDeleted)
{ {
throw new Exception( throw new Exception(
"Pointer access after dispose detected. This is not legal, and will cause native exceptions."); "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<IntPtr, WeakReference<PointerWrapper>> Cached = private static readonly ConcurrentDictionary<IntPtr, WeakReference<PointerWrapper>> Cached =
new ConcurrentDictionary<IntPtr, WeakReference<PointerWrapper>>(); new ConcurrentDictionary<IntPtr, WeakReference<PointerWrapper>>();
@ -48,7 +48,7 @@ namespace PkmnLibSharp.Utilities
~PointerWrapper() ~PointerWrapper()
{ {
if (!_isDeleted) if (!IsDeleted)
Cached.TryRemove(Ptr, out _); Cached.TryRemove(Ptr, out _);
} }
@ -80,7 +80,7 @@ namespace PkmnLibSharp.Utilities
public virtual void Dispose() public virtual void Dispose()
{ {
if (_isDeleted) if (IsDeleted)
return; return;
DeletePtr(); DeletePtr();
MarkAsDeleted(); MarkAsDeleted();
@ -88,7 +88,7 @@ namespace PkmnLibSharp.Utilities
protected internal virtual void MarkAsDeleted() protected internal virtual void MarkAsDeleted()
{ {
_isDeleted = true; IsDeleted = true;
Cached.TryRemove(_ptr, out _); Cached.TryRemove(_ptr, out _);
} }

View File

@ -70,6 +70,25 @@ namespace PkmnLibSharp.Utilities
} }
return -1; 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] public T? this[int index]
{ {

View File

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
namespace PkmnLibSharp.Utilities
{
/// <summary>
/// Helper class to ensure an object is disposed when it goes out of scope.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ScopedOwner<T> : 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<T> 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<T> other)
{
return EqualityComparer<T?>.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<T>) obj);
}
public override int GetHashCode()
{
return EqualityComparer<T?>.Default.GetHashCode(Value);
}
public static bool operator ==(ScopedOwner<T>? left, ScopedOwner<T>? right)
{
return Equals(left, right);
}
public static bool operator !=(ScopedOwner<T>? left, ScopedOwner<T>? right)
{
return !Equals(left, right);
}
}
}

View File

@ -16,12 +16,12 @@ namespace PkmnLibSharpTests.Battling.BattleTests
{ {
var lib = BattleLibraryHelper.GetLibrary(); var lib = BattleLibraryHelper.GetLibrary();
var battle = new BattleBuilder(lib, true, 2, 1).Build(); var battle = new BattleBuilder(lib, true, 2, 1).Build();
Assert.AreEqual(lib, battle.Library); Assert.AreEqual(lib, battle.Value.Library);
Assert.AreEqual(true, battle.CanFlee); Assert.AreEqual(true, battle.Value.CanFlee);
Assert.AreEqual(2, battle.SidesCount); Assert.AreEqual(2, battle.Value.SidesCount);
Assert.AreEqual(false, battle.HasEnded); Assert.AreEqual(false, battle.Value.HasEnded);
Assert.AreEqual(0, battle.PartiesCount); Assert.AreEqual(0, battle.Value.PartiesCount);
battle.Dispose(); battle.Value.Dispose();
} }
private static PokemonParty BuildTestParty(BattleLibrary lib) private static PokemonParty BuildTestParty(BattleLibrary lib)
@ -45,10 +45,10 @@ namespace PkmnLibSharpTests.Battling.BattleTests
.WithPartyOnPositions(p1, new BattlePosition(0, 0)) .WithPartyOnPositions(p1, new BattlePosition(0, 0))
.WithPartyOnPositions(p2, new BattlePosition(1, 0)) .WithPartyOnPositions(p2, new BattlePosition(1, 0))
.Build(); .Build();
Assert.AreEqual(2, battle.PartiesCount); Assert.AreEqual(2, battle.Value.PartiesCount);
Assert.AreEqual(p1, battle.Parties[0].Party); Assert.AreEqual(p1, battle.Value.Parties[0].Party);
Assert.AreEqual(p2, battle.Parties[1].Party); Assert.AreEqual(p2, battle.Value.Parties[1].Party);
battle.Dispose(); battle.Value.Dispose();
} }
[Test] [Test]
@ -63,19 +63,19 @@ namespace PkmnLibSharpTests.Battling.BattleTests
.WithPartyOnPositions(p2, new BattlePosition(1, 0)) .WithPartyOnPositions(p2, new BattlePosition(1, 0))
.Build(); .Build();
battle.SwitchPokemon(0, 0, p1.GetAtIndex(0)); battle.Value.SwitchPokemon(0, 0, p1.GetAtIndex(0));
battle.SwitchPokemon(1, 0, p2.GetAtIndex(0)); battle.Value.SwitchPokemon(1, 0, p2.GetAtIndex(0));
Assert.AreEqual(0, battle.CurrentTurn); Assert.AreEqual(0, battle.Value.CurrentTurn);
var moveTurn1 = new MoveTurnChoice(p1.GetAtIndex(0), p1.GetAtIndex(0).Moves[0], 1, 0 ); 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 ); var moveTurn2 = new MoveTurnChoice(p2.GetAtIndex(0), p2.GetAtIndex(0).Moves[0], 0, 0 );
Assert.That(battle.TrySetChoice(moveTurn1)); Assert.That(battle.Value.TrySetChoice(moveTurn1));
Assert.That(battle.TrySetChoice(moveTurn2)); Assert.That(battle.Value.TrySetChoice(moveTurn2));
Assert.AreEqual(battle.CurrentTurn, 1); Assert.AreEqual(battle.Value.CurrentTurn, 1);
battle.Dispose(); battle.Value.Dispose();
} }
[Test] [Test]
@ -97,18 +97,18 @@ namespace PkmnLibSharpTests.Battling.BattleTests
evts.Add(evt); evts.Add(evt);
return Task.CompletedTask; return Task.CompletedTask;
}); });
battle.RegisterEventListener(eventListener); battle.Value.RegisterEventListener(eventListener);
battle.SwitchPokemon(0, 0, p1.GetAtIndex(0)); battle.Value.SwitchPokemon(0, 0, p1.GetAtIndex(0));
battle.SwitchPokemon(1, 0, p2.GetAtIndex(0)); battle.Value.SwitchPokemon(1, 0, p2.GetAtIndex(0));
Assert.AreEqual(0, battle.CurrentTurn); Assert.AreEqual(0, battle.Value.CurrentTurn);
var moveTurn1 = new MoveTurnChoice(p1.GetAtIndex(0), p1.GetAtIndex(0).Moves[0], 1, 0 ); 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 ); var moveTurn2 = new MoveTurnChoice(p2.GetAtIndex(0), p2.GetAtIndex(0).Moves[0], 0, 0 );
Assert.That(battle.TrySetChoice(moveTurn1)); Assert.That(battle.Value.TrySetChoice(moveTurn1));
Assert.That(battle.TrySetChoice(moveTurn2)); Assert.That(battle.Value.TrySetChoice(moveTurn2));
Assert.AreEqual(1, battle.CurrentTurn); Assert.AreEqual(1, battle.Value.CurrentTurn);
eventListener.EnsureFinishedListening(); eventListener.EnsureFinishedListening();
@ -117,7 +117,7 @@ namespace PkmnLibSharpTests.Battling.BattleTests
Assert.AreEqual(damageEvents[0].Pokemon, p2.GetAtIndex(0)); Assert.AreEqual(damageEvents[0].Pokemon, p2.GetAtIndex(0));
Assert.AreEqual(damageEvents[1].Pokemon, p1.GetAtIndex(0)); Assert.AreEqual(damageEvents[1].Pokemon, p1.GetAtIndex(0));
battle.Dispose(); battle.Value.Dispose();
} }
[Test] [Test]
@ -140,21 +140,21 @@ namespace PkmnLibSharpTests.Battling.BattleTests
evts.Add(evt); evts.Add(evt);
return Task.CompletedTask; return Task.CompletedTask;
}); });
battle.RegisterEventListener(eventListener); battle.Value.RegisterEventListener(eventListener);
battle.SwitchPokemon(0, 0, p1.GetAtIndex(0)); battle.Value.SwitchPokemon(0, 0, p1.GetAtIndex(0));
battle.SwitchPokemon(1, 0, p2.GetAtIndex(0)); battle.Value.SwitchPokemon(1, 0, p2.GetAtIndex(0));
Assert.AreEqual(0, battle.CurrentTurn); Assert.AreEqual(0, battle.Value.CurrentTurn);
var moveTurn1 = new MoveTurnChoice(p1.GetAtIndex(0), p1.GetAtIndex(0).Moves[0], 1, 0 ); 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 ); var moveTurn2 = new MoveTurnChoice(p2.GetAtIndex(0), p2.GetAtIndex(0).Moves[0], 0, 0 );
Assert.That(battle.TrySetChoice(moveTurn1)); Assert.That(battle.Value.TrySetChoice(moveTurn1));
Assert.That(battle.TrySetChoice(moveTurn2)); 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); mon1.Heal(1000, true);
mon2.Heal(1000, true); mon2.Heal(1000, true);

View File

@ -13,9 +13,9 @@ namespace PkmnLibSharpTests.Battling
var lib = BattleLibraryHelper.GetLibrary(); var lib = BattleLibraryHelper.GetLibrary();
var pokemon = new PokemonBuilder(lib, "testSpecies", 50) var pokemon = new PokemonBuilder(lib, "testSpecies", 50)
.Build(); .Build();
Assert.AreEqual("testSpecies", pokemon.Species.Name); Assert.AreEqual("testSpecies", pokemon.Value.Species.Name);
Assert.AreEqual(50, pokemon.Level); Assert.AreEqual(50, pokemon.Value.Level);
Assert.AreEqual("default", pokemon.Forme.Name); Assert.AreEqual("default", pokemon.Value.Forme.Name);
pokemon.Dispose(); pokemon.Dispose();
} }
@ -26,7 +26,7 @@ namespace PkmnLibSharpTests.Battling
var pokemon = new PokemonBuilder(lib, "testSpecies", 50) var pokemon = new PokemonBuilder(lib, "testSpecies", 50)
.WithNickname("cuteNickname") .WithNickname("cuteNickname")
.Build(); .Build();
Assert.AreEqual("cuteNickname", pokemon.Nickname); Assert.AreEqual("cuteNickname", pokemon.Value.Nickname);
pokemon.Dispose(); pokemon.Dispose();
} }
@ -37,7 +37,7 @@ namespace PkmnLibSharpTests.Battling
var pokemon = new PokemonBuilder(lib, "testSpecies", 50) var pokemon = new PokemonBuilder(lib, "testSpecies", 50)
.WithGender(Gender.Female) .WithGender(Gender.Female)
.Build(); .Build();
Assert.AreEqual(Gender.Female, pokemon.Gender); Assert.AreEqual(Gender.Female, pokemon.Value.Gender);
pokemon.Dispose(); pokemon.Dispose();
} }
@ -49,8 +49,8 @@ namespace PkmnLibSharpTests.Battling
.LearnMove("testMove", MoveLearnMethod.Unknown) .LearnMove("testMove", MoveLearnMethod.Unknown)
.LearnMove("testMove2", MoveLearnMethod.Level) .LearnMove("testMove2", MoveLearnMethod.Level)
.Build(); .Build();
Assert.AreEqual("testMove", pokemon.Moves[0].Move.Name); Assert.AreEqual("testMove", pokemon.Value.Moves[0].Move.Name);
Assert.AreEqual("testMove2", pokemon.Moves[1].Move.Name); Assert.AreEqual("testMove2", pokemon.Value.Moves[1].Move.Name);
pokemon.Dispose(); pokemon.Dispose();
} }