From c8be93f83f033c8deb8b4846d8f5f542922f8b93 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Wed, 21 Sep 2022 19:40:04 +0200 Subject: [PATCH] Implements Form foreign interface --- PkmnLibRSharp/FFI/StaticData/Form.cs | 57 ++++++++++++++ PkmnLibRSharp/PkmnLibRSharp.csproj | 2 +- PkmnLibRSharp/StaticData/Form.cs | 70 +++++++++++++++++ PkmnLibRSharp/StaticData/LearnableMoves.cs | 5 ++ PkmnLibRSharp/StaticData/SecondaryEffect.cs | 21 ++--- .../StaticData/StaticStatisticSet.cs | 4 + PkmnLibRSharp/StaticData/TypeIdentifier.cs | 7 +- PkmnLibRSharp/Utils/CachedExternArray.cs | 77 +++++++++++++++++++ PkmnLibRSharp/Utils/FFIExtensions.cs | 2 +- PkmnLibRSharpTests/StaticData/FormTests.cs | 28 +++++++ 10 files changed, 254 insertions(+), 19 deletions(-) create mode 100644 PkmnLibRSharp/FFI/StaticData/Form.cs create mode 100644 PkmnLibRSharp/StaticData/Form.cs create mode 100644 PkmnLibRSharp/Utils/CachedExternArray.cs create mode 100644 PkmnLibRSharpTests/StaticData/FormTests.cs diff --git a/PkmnLibRSharp/FFI/StaticData/Form.cs b/PkmnLibRSharp/FFI/StaticData/Form.cs new file mode 100644 index 0000000..719b032 --- /dev/null +++ b/PkmnLibRSharp/FFI/StaticData/Form.cs @@ -0,0 +1,57 @@ +using System; +using System.Runtime.InteropServices; +using PkmnLibSharp.StaticData; + +namespace PkmnLibSharp.FFI.StaticData +{ + internal static class Form + { + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr form_new(IntPtr name, float height, float weight, uint baseExperience, + IntPtr types, ulong typesLength, IntPtr baseStats, IntPtr abilities, ulong abilitiesLength, + IntPtr hiddenAbilities, ulong hiddenAbilitiesLength, IntPtr learnableMoves, IntPtr flags, + ulong flagsLength); + + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern void form_drop(IntPtr ptr); + + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr form_name(IntPtr ptr); + + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern float form_height(IntPtr ptr); + + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern float form_weight(IntPtr ptr); + + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint form_base_experience(IntPtr ptr); + + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern ulong form_types_length(IntPtr ptr); + + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern TypeIdentifier form_types_get(IntPtr ptr, ulong index); + + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr form_base_stats(IntPtr ptr); + + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern ulong form_abilities_length(IntPtr ptr); + + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr form_abilities_get(IntPtr ptr, ulong index); + + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern ulong form_hidden_abilities_length(IntPtr ptr); + + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr form_hidden_abilities_get(IntPtr ptr, ulong index); + + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr form_moves(IntPtr ptr); + + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern byte form_has_flag(IntPtr ptr, IntPtr flag); + } +} \ No newline at end of file diff --git a/PkmnLibRSharp/PkmnLibRSharp.csproj b/PkmnLibRSharp/PkmnLibRSharp.csproj index 67b5255..83b4963 100644 --- a/PkmnLibRSharp/PkmnLibRSharp.csproj +++ b/PkmnLibRSharp/PkmnLibRSharp.csproj @@ -3,7 +3,7 @@ netstandard2.1 PkmnLibSharp - 8 + 9 enable diff --git a/PkmnLibRSharp/StaticData/Form.cs b/PkmnLibRSharp/StaticData/Form.cs new file mode 100644 index 0000000..2f58b94 --- /dev/null +++ b/PkmnLibRSharp/StaticData/Form.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Linq; +using PkmnLibSharp.Utils; +using Interface = PkmnLibSharp.FFI.StaticData.Form; + +namespace PkmnLibSharp.StaticData +{ + public class Form : ExternPointer + { + public class CacheData + { + public string? Name { get; internal set; } + public float? Height { get; internal set; } + public float? Weight { get; internal set; } + public uint? BaseExperience { get; internal set; } + public ulong? TypesLength { get; internal set; } + public CachedExternValueArray? Types { get; internal set; } + public StaticStatisticSet? BaseStats { get; internal set; } + public CachedExternArray? Abilities { get; internal set; } + public CachedExternArray? HiddenAbilities { get; internal set; } + public LearnableMoves? LearnableMoves { get; internal set; } + } + + public Form(string name, float height, float weight, uint baseExperience, TypeIdentifier[] types, + StaticStatisticSet baseStats, IReadOnlyCollection abilities, + IReadOnlyCollection hiddenAbilities, LearnableMoves learnableMoves, + IReadOnlyCollection flags) + { + var typesArr = types.ArrayPtr(); + + var abilitiesPtrArray = abilities.Select(x => x.ToPtr()).ToArray(); + var hiddenAbilitiesPtrArray = hiddenAbilities.Select(x => x.ToPtr()).ToArray(); + var flagsPtrArray = flags.Select(x => x.ToPtr()).ToArray(); + + var ptr = Interface.form_new(name.ToPtr(), height, weight, baseExperience, typesArr, (ulong)types.Length, + baseStats.TakeOwnershipAndInvalidate(), abilitiesPtrArray.ArrayPtr(), (ulong)abilities.Count, + hiddenAbilitiesPtrArray.ArrayPtr(), (ulong)hiddenAbilities.Count, + learnableMoves.TakeOwnershipAndInvalidate(), flagsPtrArray.ArrayPtr(), (ulong)flags.Count); + InitializePointer(ptr, true); + } + + public string Name => Cache.Name ??= Interface.form_name(Ptr).PtrString()!; + public float Height => Cache.Height ??= Interface.form_height(Ptr); + public float Weight => Cache.Weight ??= Interface.form_weight(Ptr); + public uint BaseExperience => Cache.BaseExperience ??= Interface.form_base_experience(Ptr); + + public IReadOnlyList Types => + Cache.Types ??= new CachedExternValueArray(Interface.form_types_length(Ptr), + arg => Interface.form_types_get(Ptr, arg)); + + public StaticStatisticSet BaseStats => + Cache.BaseStats ??= new StaticStatisticSet(Interface.form_base_stats(Ptr), false); + + public IReadOnlyList Abilities => + Cache.Abilities ??= new CachedExternArray(Interface.form_abilities_length(Ptr), + arg => Interface.form_abilities_get(Ptr, arg).PtrString()!); + + public IReadOnlyList HiddenAbilities => + Cache.HiddenAbilities ??= new CachedExternArray(Interface.form_hidden_abilities_length(Ptr), + arg => Interface.form_hidden_abilities_get(Ptr, arg).PtrString()!); + + public LearnableMoves LearnableMoves => + Cache.LearnableMoves ??= new LearnableMoves(Interface.form_moves(Ptr), false); + + + protected override CacheData CreateCache() => new CacheData(); + + protected override void Destructor() => Interface.form_drop(Ptr); + } +} \ No newline at end of file diff --git a/PkmnLibRSharp/StaticData/LearnableMoves.cs b/PkmnLibRSharp/StaticData/LearnableMoves.cs index 93bf34e..954d0b6 100644 --- a/PkmnLibRSharp/StaticData/LearnableMoves.cs +++ b/PkmnLibRSharp/StaticData/LearnableMoves.cs @@ -1,3 +1,4 @@ +using System; using PkmnLibSharp.Utils; using Interface = PkmnLibSharp.FFI.StaticData.LearnableMoves; using LevelInt = System.Byte; @@ -6,6 +7,10 @@ namespace PkmnLibSharp.StaticData { public class LearnableMoves : ExternPointer { + internal LearnableMoves(IntPtr ptr, bool isOwner) : base(ptr, isOwner) + { + } + public LearnableMoves() : base(Interface.learnable_moves_new(), true) { } diff --git a/PkmnLibRSharp/StaticData/SecondaryEffect.cs b/PkmnLibRSharp/StaticData/SecondaryEffect.cs index 9bb0199..baf6d3c 100644 --- a/PkmnLibRSharp/StaticData/SecondaryEffect.cs +++ b/PkmnLibRSharp/StaticData/SecondaryEffect.cs @@ -12,8 +12,7 @@ namespace PkmnLibSharp.StaticData { public float? Chance { get; internal set; } public string? Name { get; internal set; } - public ulong? ParameterLength { get; internal set; } - public EffectParameter?[]? Parameters { get; internal set; } + public CachedExternArray? Parameters { get; internal set; } } internal SecondaryEffect(IntPtr ptr, bool isOwner) : base(ptr, isOwner) @@ -32,20 +31,10 @@ namespace PkmnLibSharp.StaticData public float Chance => Cache.Chance ??= Interface.secondary_effect_chance(Ptr); public string Name => Cache.Name ?? (Cache.Name = Interface.secondary_effect_effect_name(Ptr).PtrString()!); - public ulong ParameterLength => - Cache.ParameterLength ?? (Cache.ParameterLength = Interface.secondary_effect_parameter_length(Ptr)).Value; - - public EffectParameter GetParameter(int index) - { - Cache.Parameters ??= new EffectParameter[ParameterLength]; - if (Cache.Parameters[index] == null) - { - var ptr = Interface.secondary_effect_parameter_get(Ptr, (ulong)index); - Cache.Parameters[index] = new EffectParameter(ptr, false); - } - - return Cache.Parameters[index]!; - } + public IReadOnlyList Parameters => + Cache.Parameters ??= new CachedExternArray( + Interface.secondary_effect_parameter_length(Ptr), + arg => new EffectParameter(Interface.secondary_effect_parameter_get(Ptr, arg), false)); protected override CacheData CreateCache() { diff --git a/PkmnLibRSharp/StaticData/StaticStatisticSet.cs b/PkmnLibRSharp/StaticData/StaticStatisticSet.cs index 5abaea8..1bbe96d 100644 --- a/PkmnLibRSharp/StaticData/StaticStatisticSet.cs +++ b/PkmnLibRSharp/StaticData/StaticStatisticSet.cs @@ -17,6 +17,10 @@ namespace PkmnLibSharp.StaticData public T? Speed { get; internal set; } } + internal StaticStatisticSet(IntPtr ptr, bool isOwner) : base(ptr, isOwner) + { + } + public StaticStatisticSet(T hp, T attack, T defense, T specialAttack, T specialDefense, T speed) { var p = typeof(T) switch diff --git a/PkmnLibRSharp/StaticData/TypeIdentifier.cs b/PkmnLibRSharp/StaticData/TypeIdentifier.cs index efd384b..c61cb08 100644 --- a/PkmnLibRSharp/StaticData/TypeIdentifier.cs +++ b/PkmnLibRSharp/StaticData/TypeIdentifier.cs @@ -8,7 +8,7 @@ namespace PkmnLibSharp.StaticData // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable [FieldOffset(0)] private readonly byte _identifier; - internal TypeIdentifier(byte b) + public TypeIdentifier(byte b) { _identifier = b; } @@ -37,5 +37,10 @@ namespace PkmnLibSharp.StaticData { return !left.Equals(right); } + + public override string ToString() + { + return $"Type({_identifier})"; + } } } \ No newline at end of file diff --git a/PkmnLibRSharp/Utils/CachedExternArray.cs b/PkmnLibRSharp/Utils/CachedExternArray.cs new file mode 100644 index 0000000..9476abc --- /dev/null +++ b/PkmnLibRSharp/Utils/CachedExternArray.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace PkmnLibSharp.Utils +{ + public class CachedExternArray : IReadOnlyList + where T: class + { + private readonly T?[] _array; + private readonly Func _getItem; + + public CachedExternArray(ulong size, Func getItem) + { + _array = new T?[(int)size]; + _getItem = getItem; + } + + public IEnumerator GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public int Count => _array.Length; + + public T this[int index] + { + get + { + if (index >= _array.Length) + throw new ArgumentOutOfRangeException( + $"Index {index} was outside of the bounds of the external array with length {Count}"); + return _array[index] ??= _getItem((ulong)index); + } + } + } + + public class CachedExternValueArray : IReadOnlyList + where T: struct + { + private readonly T?[] _array; + private readonly Func _getItem; + + public CachedExternValueArray(ulong size, Func getItem) + { + _array = new T?[(int)size]; + _getItem = getItem; + } + + public IEnumerator GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public int Count => _array.Length; + + public T this[int index] => _array[index] ??= _getItem((ulong)index); + } + +} \ No newline at end of file diff --git a/PkmnLibRSharp/Utils/FFIExtensions.cs b/PkmnLibRSharp/Utils/FFIExtensions.cs index 3679693..e13b372 100644 --- a/PkmnLibRSharp/Utils/FFIExtensions.cs +++ b/PkmnLibRSharp/Utils/FFIExtensions.cs @@ -25,7 +25,7 @@ namespace PkmnLibSharp.Utils { return Marshal.UnsafeAddrOfPinnedArrayElement(a, 0); } - internal static IntPtr ArrayPtr(this T[] a) where T : struct, IConvertible + internal static IntPtr ArrayPtr(this T[] a) where T : struct { return Marshal.UnsafeAddrOfPinnedArrayElement(a, 0); } diff --git a/PkmnLibRSharpTests/StaticData/FormTests.cs b/PkmnLibRSharpTests/StaticData/FormTests.cs new file mode 100644 index 0000000..02263c4 --- /dev/null +++ b/PkmnLibRSharpTests/StaticData/FormTests.cs @@ -0,0 +1,28 @@ +using System; +using NUnit.Framework; +using PkmnLibSharp.StaticData; + +namespace PkmnLibRSharpTests.StaticData +{ + public class FormTests + { + [Test] + public void BasicTests() + { + using var form = new Form("foobar", 0.2f, 5.8f, 300, new TypeIdentifier[] { new(1), new(2) }, + new StaticStatisticSet(5, 10, 30, 20, 2, 0), new[] { "foo", "bar" }, new[] { "set" }, + new LearnableMoves(), Array.Empty()); + Assert.AreEqual("foobar", form.Name); + Assert.AreEqual(0.2f, form.Height, 0.00001f); + Assert.AreEqual(5.8f, form.Weight, 0.00001f); + Assert.AreEqual(300, form.BaseExperience); + Assert.AreEqual(new TypeIdentifier(1), form.Types[0]); + Assert.AreEqual(new TypeIdentifier(2), form.Types[1]); + Assert.AreEqual(10, form.BaseStats.Attack); + Assert.AreEqual("foo", form.Abilities[0]); + Assert.AreEqual("bar", form.Abilities[1]); + Assert.AreEqual("set", form.HiddenAbilities[0]); + + } + } +} \ No newline at end of file