Adds ScopedOwner class, that disposes owned values when garbage collected.
This commit is contained in:
parent
7e31bba05e
commit
7807ee9676
|
@ -235,8 +235,11 @@ namespace PkmnLibSharp.Battling
|
|||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (!IsDeleted)
|
||||
{
|
||||
Creaturelib.Generated.Battle.ClearBattle(Ptr);
|
||||
}
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Battle> 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<Battle>(new Battle(_library, _parties.ToArray(), _canFlee, _numberOfSides,
|
||||
_pokemonPerSide, _seed.Value));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -67,6 +67,12 @@ namespace PkmnLibSharp.Battling
|
|||
private LearnedMove? _move;
|
||||
private Pokemon? _user;
|
||||
private ReadOnlyNativePtrArray<Pokemon>? _targets;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return base.ToString() + $": {Move.Move.Name}";
|
||||
}
|
||||
|
||||
protected override void DeletePtr()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -42,6 +42,11 @@ namespace PkmnLibSharp.Battling
|
|||
|
||||
private MoveData? _move;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return base.ToString() + $": {Move.Name} PP: {RemainingUses}/{MaxUses}";
|
||||
}
|
||||
|
||||
protected override void DeletePtr()
|
||||
{
|
||||
LearnedAttack.Destruct(Ptr);
|
||||
|
|
|
@ -488,6 +488,11 @@ namespace PkmnLibSharp.Battling
|
|||
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);
|
||||
|
|
|
@ -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<TPokemon> 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<TPokemon>(Finalize(species, forme!, heldItem, moves, nature));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -146,6 +146,11 @@ namespace PkmnLibSharp.Library
|
|||
private ReadOnlyNativePtrArray<Forme>? _formes;
|
||||
private string[]? _eggGroups;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"(#{Ptr}) -> Species: {Name}";
|
||||
}
|
||||
|
||||
protected internal override void MarkAsDeleted()
|
||||
{
|
||||
base.MarkAsDeleted();
|
||||
|
|
BIN
PkmnLibSharp/Native/Linux/libCreatureLib.so (Stored with Git LFS)
BIN
PkmnLibSharp/Native/Linux/libCreatureLib.so (Stored with Git LFS)
Binary file not shown.
BIN
PkmnLibSharp/Native/Linux/libpkmnLib.so (Stored with Git LFS)
BIN
PkmnLibSharp/Native/Linux/libpkmnLib.so (Stored with Git LFS)
Binary file not shown.
|
@ -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<IntPtr, WeakReference<PointerWrapper>> Cached =
|
||||
new ConcurrentDictionary<IntPtr, WeakReference<PointerWrapper>>();
|
||||
|
@ -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 _);
|
||||
}
|
||||
|
||||
|
|
|
@ -71,6 +71,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]
|
||||
{
|
||||
get
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue