feat: improve BMap bindings.

- Add IEquatable<T> interface for BMapSharp 2 abstract base classes to make they can be used in HashSet or Dictionary.
- Add corresponding testbench for this new added interface to make sure it works.
- Also add set and dict test for PyBMap although it has been proven works.
This commit is contained in:
yyc12345 2024-11-08 14:58:50 +08:00
parent 512729ed05
commit 6f7202a86b
3 changed files with 271 additions and 188 deletions

View File

@ -100,7 +100,10 @@ namespace BMapSharp.BMapWrapper {
#endregion #endregion
} }
public abstract class AbstractPointer : SafeHandle { // TODO: Maybe I need to implement IEquatable, IComparable<T>, and IComparable for AbstractPointer and AbstractCKObject.
// But I give it up. I am lazy. What I have written barely works for me now.
public abstract class AbstractPointer : SafeHandle, IEquatable<AbstractPointer> {
internal AbstractPointer(IntPtr raw_pointer) : base(Utils.INVALID_PTR, true) { internal AbstractPointer(IntPtr raw_pointer) : base(Utils.INVALID_PTR, true) {
this.handle = raw_pointer; this.handle = raw_pointer;
} }
@ -111,47 +114,42 @@ namespace BMapSharp.BMapWrapper {
internal bool isValid() => this.handle != Utils.INVALID_PTR; internal bool isValid() => this.handle != Utils.INVALID_PTR;
internal IntPtr getPointer() => this.handle; internal IntPtr getPointer() => this.handle;
// protected AbstractPointer(IntPtr raw_pointer) : base(raw_pointer, true) {} #region IEquatable
// protected IntPtr GetPointer() => this.handle; public override bool Equals(object obj) => this.Equals(obj as AbstractPointer);
// public override bool IsInvalid { get { return this.handle == Utils.INVALID_PTR; } } public bool Equals(AbstractPointer obj) {
if (obj is null) return false;
// Optimization for a common success case
if (Object.ReferenceEquals(this, obj)) return true;
// If run-time types are not exactly the same, return false.
if (this.GetType() != obj.GetType()) return false;
// Return true if the fields match.
return this.handle == obj.handle;
}
// #region IComparable public override int GetHashCode() => this.handle.GetHashCode();
// public int CompareTo(AbstractPointer other) { public static bool operator ==(AbstractPointer lhs, AbstractPointer rhs) {
// return m_RawPointer.CompareTo(other.m_RawPointer); if (lhs is null) {
// } if (rhs is null) return true;
// Only left side is null.
return false;
}
// Equals handles case of null on right side
return lhs.Equals(rhs);
}
public static bool operator !=(AbstractPointer lhs, AbstractPointer rhs) => !(lhs == rhs);
// #endregion #endregion
// #region IEquatable
// public override bool Equals(object obj) => this.Equals(obj as AbstractPointer);
// public bool Equals(AbstractPointer other) {
// if (other is null) return false;
// if (Object.ReferenceEquals(this, other)) return true;
// // if (this.GetType() != other.GetType()) return false;
// return this.m_RawPointer == other.m_RawPointer;
// }
// public static bool operator ==(AbstractPointer lhs, AbstractPointer rhs) {
// if (lhs is null) {
// if (rhs is null) return true;
// return false;
// }
// return lhs.Equals(rhs);
// }
// public static bool operator !=(AbstractPointer lhs, AbstractPointer rhs) => !(lhs == rhs);
// #endregion
#region Misc #region Misc
public override int GetHashCode() => this.handle.GetHashCode();
public override string ToString() => this.handle.ToString(); public override string ToString() => this.handle.ToString();
#endregion #endregion
} }
public abstract class AbstractCKObject : SafeHandle { public abstract class AbstractCKObject : SafeHandle, IEquatable<AbstractCKObject> {
// Same as AbstractPointer, but not own this handle. // Same as AbstractPointer, but not own this handle.
internal AbstractCKObject(IntPtr raw_pointer, uint ckid) : base(Utils.INVALID_PTR, false) { internal AbstractCKObject(IntPtr raw_pointer, uint ckid) : base(Utils.INVALID_PTR, false) {
this.handle = raw_pointer; this.handle = raw_pointer;
@ -166,44 +164,36 @@ namespace BMapSharp.BMapWrapper {
internal IntPtr getPointer() => this.handle; internal IntPtr getPointer() => this.handle;
internal uint getCKID() => m_CKID; internal uint getCKID() => m_CKID;
// private uint m_CKID; #region IEquatable
// protected AbstractCKObject(IntPtr raw_pointer, uint ckid) : base(raw_pointer) { public override bool Equals(object obj) => this.Equals(obj as AbstractCKObject);
// m_CKID = ckid; public bool Equals(AbstractCKObject obj) {
// } if (obj is null) return false;
// Optimization for a common success case
if (Object.ReferenceEquals(this, obj)) return true;
// If run-time types are not exactly the same, return false.
if (this.GetType() != obj.GetType()) return false;
// Return true if the fields match.
return (this.m_CKID == obj.m_CKID) && (this.handle == obj.handle);
}
// protected override bool IsValid() => base.IsValid() && m_CKID != Utils.INVALID_CKID; public override int GetHashCode() => HashCode.Combine(this.handle, m_CKID);
// protected uint GetCKID() => m_CKID;
// #region IComparable public static bool operator ==(AbstractCKObject lhs, AbstractCKObject rhs) {
if (lhs is null) {
if (rhs is null) return true;
// Only left side is null.
return false;
}
// Equals handles case of null on right side
return lhs.Equals(rhs);
}
public static bool operator !=(AbstractCKObject lhs, AbstractCKObject rhs) => !(lhs == rhs);
// public int CompareTo(AbstractCKObject other) { #endregion
// var ret = base.CompareTo((AbstractPointer)other);
// if (ret != 0) return ret;
// return m_CKID.CompareTo(other.m_CKID);
// }
// #endregion
// #region IEquatable
// public override bool Equals(object obj) => this.Equals(obj as AbstractCKObject);
// public bool Equals(AbstractCKObject other) {
// if (other is null) return false;
// if (Object.ReferenceEquals(this, other)) return true;
// }
// public static bool operator ==(AbstractCKObject left, AbstractCKObject right) =>
// ((AbstractPointer)left == (AbstractPointer)right) && left.m_CKID == right.m_CKID;
// public static bool operator !=(AbstractCKObject left, AbstractCKObject right) =>
// ((AbstractPointer)left != (AbstractPointer)right) || left.m_CKID != right.m_CKID;
// #endregion
#region Misc #region Misc
public override int GetHashCode() => HashCode.Combine(this.handle, m_CKID);
public override string ToString() => $"{this.handle}, {m_CKID}"; public override string ToString() => $"{this.handle}, {m_CKID}";
#endregion #endregion

View File

@ -1,8 +1,12 @@
using BMapSharp.BMapWrapper;
using System; using System;
using System.Text; using System.Text;
using System.Collections.Generic;
using System.Diagnostics;
namespace BMapSharpTestbench { namespace BMapSharpTestbench {
internal class Program { internal class Program {
static void Main(string[] args) { static void Main(string[] args) {
// Check environment // Check environment
Console.OutputEncoding = Encoding.UTF8; Console.OutputEncoding = Encoding.UTF8;
@ -23,6 +27,16 @@ namespace BMapSharpTestbench {
string[] encodings = ["cp1252", "gb2312"]; string[] encodings = ["cp1252", "gb2312"];
using (var reader = new BMapSharp.BMapWrapper.BMFileReader(file_name, temp_folder, texture_folder, encodings)) { using (var reader = new BMapSharp.BMapWrapper.BMFileReader(file_name, temp_folder, texture_folder, encodings)) {
TestCommon(reader);
TestIEquatable(reader);
}
Console.WriteLine("Press any key to quit...");
Console.ReadKey(true);
}
static void TestCommon(BMapSharp.BMapWrapper.BMFileReader reader) {
// Console.WriteLine("===== Groups ====="); // Console.WriteLine("===== Groups =====");
// foreach (var gp in reader.GetGroups()) { // foreach (var gp in reader.GetGroups()) {
// Console.WriteLine(gp.GetName()); // Console.WriteLine(gp.GetName());
@ -94,12 +108,39 @@ namespace BMapSharpTestbench {
// Console.WriteLine($"\tVideo Format: {tex.GetVideoFormat()}"); // Console.WriteLine($"\tVideo Format: {tex.GetVideoFormat()}");
// } // }
Console.WriteLine("===== END =====");
} }
Console.WriteLine("===== Done ====="); static void TestIEquatable(BMapSharp.BMapWrapper.BMFileReader reader) {
Console.WriteLine("Press any key to quit..."); if (reader.Get3dObjectCount() < 2u) {
Console.ReadKey(true); Debug.Fail(
"Invalid file for test IEquatable.",
"We can not perform IEquatable test because the length of 3dObject is too short (must greater than 2). Please choose another file to perform."
);
return;
}
// Prepare test variables
var all_3dobjects = new List<BM3dObject>(reader.Get3dObjects());
var first_3dobj = all_3dobjects[0];
var second_3dobj = all_3dobjects[1];
all_3dobjects = new List<BM3dObject>(reader.Get3dObjects());
var first_3dobj_again = all_3dobjects[0];
Debug.Assert(!Object.ReferenceEquals(first_3dobj, first_3dobj_again));
// Hashtable test
var test_hashset = new HashSet<BM3dObject>();
Debug.Assert(test_hashset.Add(first_3dobj));
Debug.Assert(!test_hashset.Add(first_3dobj_again));
Debug.Assert(test_hashset.Add(second_3dobj));
// Dictionary test
var test_dictionary = new Dictionary<BM3dObject, string>();
Debug.Assert(test_dictionary.TryAdd(first_3dobj, first_3dobj.GetName()));
Debug.Assert(!test_dictionary.TryAdd(first_3dobj_again, first_3dobj_again.GetName()));
Debug.Assert(test_dictionary.TryAdd(second_3dobj, second_3dobj.GetName()));
} }
} }
} }

View File

@ -9,6 +9,10 @@ def main() -> None:
texture_folder: str = 'F:\\Ballance\\Ballance\\Textures' texture_folder: str = 'F:\\Ballance\\Ballance\\Textures'
encodings: tuple[str, ...] = ('cp1252', ) encodings: tuple[str, ...] = ('cp1252', )
with bmap.BMFileReader(file_name, temp_folder, texture_folder, encodings) as reader: with bmap.BMFileReader(file_name, temp_folder, texture_folder, encodings) as reader:
test_common(reader)
test_equatable(reader)
def test_common(reader: bmap.BMFileReader):
# print('===== Groups =====') # print('===== Groups =====')
# for gp in reader.get_groups(): # for gp in reader.get_groups():
# print(gp.get_name()) # print(gp.get_name())
@ -66,15 +70,63 @@ def main() -> None:
print(f'\tAlpha Func: {mtl.get_alpha_func()}') print(f'\tAlpha Func: {mtl.get_alpha_func()}')
print(f'\tZ Func: {mtl.get_z_func()}') print(f'\tZ Func: {mtl.get_z_func()}')
print('===== Textures =====') # print('===== Textures =====')
for tex in reader.get_textures(): # for tex in reader.get_textures():
print(tex.get_name()) # print(tex.get_name())
print(f'\tFile Name: {tex.get_file_name()}') # print(f'\tFile Name: {tex.get_file_name()}')
print(f'\tSave Options: {tex.get_save_options()}') # print(f'\tSave Options: {tex.get_save_options()}')
print(f'\tVideo Format: {tex.get_video_format()}') # print(f'\tVideo Format: {tex.get_video_format()}')
print('===== END =====') print('===== END =====')
def test_equatable(reader: bmap.BMFileReader):
# Check requirements
assert (reader.get_3dobject_count() >= 2), '''
Invalid file for test IEquatable.
We can not perform IEquatable test because the length of 3dObject is too short (must greater than 2). Please choose another file to perform.
'''
# Prepare variables
all_3dobjects: tuple[bmap.BM3dObject, ...] = tuple(reader.get_3dobjects())
first_3dobj: bmap.BM3dObject = all_3dobjects[0]
second_3dobj: bmap.BM3dObject = all_3dobjects[1]
all_3dobjects = tuple(reader.get_3dobjects())
first_3dobj_again: bmap.BM3dObject = all_3dobjects[0]
# Test set
test_set: set[bmap.BM3dObject] = set()
test_set.add(first_3dobj)
assert len(test_set) == 1
assert first_3dobj in test_set
assert first_3dobj_again in test_set
assert second_3dobj not in test_set
test_set.add(first_3dobj_again)
assert len(test_set) == 1
test_set.add(second_3dobj)
assert len(test_set) == 2
assert second_3dobj in test_set
# Test dict
test_dict: dict[bmap.BM3dObject, str | None] = {}
test_dict[first_3dobj] = first_3dobj.get_name()
assert len(test_dict) == 1
assert first_3dobj in test_dict
assert first_3dobj_again in test_dict
assert second_3dobj not in test_dict
test_dict[first_3dobj_again] = first_3dobj_again.get_name()
assert len(test_dict) == 1
test_dict[second_3dobj] = second_3dobj.get_name()
assert len(test_dict) == 2
assert second_3dobj in test_dict
if __name__ == '__main__': if __name__ == '__main__':
main() main()