diff --git a/BMapBindings/BMapSharp/BMapSharp/BMapWrapper.cs b/BMapBindings/BMapSharp/BMapSharp/BMapWrapper.cs index ac531ec..a690b78 100644 --- a/BMapBindings/BMapSharp/BMapSharp/BMapWrapper.cs +++ b/BMapBindings/BMapSharp/BMapSharp/BMapWrapper.cs @@ -100,7 +100,10 @@ namespace BMapSharp.BMapWrapper { #endregion } - public abstract class AbstractPointer : SafeHandle { + // TODO: Maybe I need to implement IEquatable, IComparable, 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 { internal AbstractPointer(IntPtr raw_pointer) : base(Utils.INVALID_PTR, true) { this.handle = raw_pointer; } @@ -111,47 +114,42 @@ namespace BMapSharp.BMapWrapper { internal bool isValid() => this.handle != Utils.INVALID_PTR; internal IntPtr getPointer() => this.handle; - // protected AbstractPointer(IntPtr raw_pointer) : base(raw_pointer, true) {} + #region IEquatable - // protected IntPtr GetPointer() => this.handle; - // public override bool IsInvalid { get { return this.handle == Utils.INVALID_PTR; } } + public override bool Equals(object obj) => this.Equals(obj as AbstractPointer); + 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) { - // return m_RawPointer.CompareTo(other.m_RawPointer); - // } + public static bool operator ==(AbstractPointer lhs, AbstractPointer 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 !=(AbstractPointer lhs, AbstractPointer rhs) => !(lhs == rhs); - // #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 + #endregion #region Misc - public override int GetHashCode() => this.handle.GetHashCode(); + public override string ToString() => this.handle.ToString(); + #endregion } - public abstract class AbstractCKObject : SafeHandle { + public abstract class AbstractCKObject : SafeHandle, IEquatable { // Same as AbstractPointer, but not own this handle. internal AbstractCKObject(IntPtr raw_pointer, uint ckid) : base(Utils.INVALID_PTR, false) { this.handle = raw_pointer; @@ -166,44 +164,36 @@ namespace BMapSharp.BMapWrapper { internal IntPtr getPointer() => this.handle; internal uint getCKID() => m_CKID; - // private uint m_CKID; + #region IEquatable - // protected AbstractCKObject(IntPtr raw_pointer, uint ckid) : base(raw_pointer) { - // m_CKID = ckid; - // } + public override bool Equals(object obj) => this.Equals(obj as AbstractCKObject); + 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; - // protected uint GetCKID() => m_CKID; + public override int GetHashCode() => HashCode.Combine(this.handle, 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) { - // 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 + #endregion #region Misc - public override int GetHashCode() => HashCode.Combine(this.handle, m_CKID); public override string ToString() => $"{this.handle}, {m_CKID}"; #endregion diff --git a/BMapBindings/BMapSharp/BMapSharpTestbench/Program.cs b/BMapBindings/BMapSharp/BMapSharpTestbench/Program.cs index 0d8f39f..8c0ca73 100644 --- a/BMapBindings/BMapSharp/BMapSharpTestbench/Program.cs +++ b/BMapBindings/BMapSharp/BMapSharpTestbench/Program.cs @@ -1,8 +1,12 @@ +using BMapSharp.BMapWrapper; using System; using System.Text; +using System.Collections.Generic; +using System.Diagnostics; namespace BMapSharpTestbench { internal class Program { + static void Main(string[] args) { // Check environment Console.OutputEncoding = Encoding.UTF8; @@ -23,83 +27,120 @@ namespace BMapSharpTestbench { string[] encodings = ["cp1252", "gb2312"]; using (var reader = new BMapSharp.BMapWrapper.BMFileReader(file_name, temp_folder, texture_folder, encodings)) { - // Console.WriteLine("===== Groups ====="); - // foreach (var gp in reader.GetGroups()) { - // Console.WriteLine(gp.GetName()); - // foreach (var gp_item in gp.GetObjects()) { - // Console.WriteLine($"\t{gp_item.GetName()}"); - // } - // } - - // Console.WriteLine("===== 3dObjects ====="); - // foreach (var obj in reader.Get3dObjects()) { - // Console.WriteLine(obj.GetName()); - - // var current_mesh = obj.GetCurrentMesh(); - // var mesh_name = current_mesh is null ? "" : current_mesh.GetName(); - // Console.WriteLine($"\tMesh: {mesh_name}"); - // Console.WriteLine($"\tVisibility: {obj.GetVisibility()}"); - // Console.WriteLine($"\tMatrix: {obj.GetWorldMatrix().ToManaged()}"); - // } - - // Console.WriteLine("===== Meshes ====="); - // foreach (var mesh in reader.GetMeshes()) { - // Console.WriteLine(mesh.GetName()); - - // Console.WriteLine($"\tLit Mode: {mesh.GetLitMode()}"); - // Console.WriteLine($"\tVertex Count: {mesh.GetVertexCount()}"); - // Console.WriteLine($"\tFace Count: {mesh.GetFaceCount()}"); - // Console.WriteLine($"\tMaterial Slot Count: {mesh.GetMaterialSlotCount()}"); - // } - - Console.WriteLine("===== Materials ====="); - foreach (var mtl in reader.GetMaterials()) { - Console.WriteLine(mtl.GetName()); - - Console.WriteLine($"\tDiffuse: {mtl.GetDiffuse().ToManagedRGBA()}"); - Console.WriteLine($"\tAmbient: {mtl.GetAmbient().ToManagedRGBA()}"); - Console.WriteLine($"\tSpecular: {mtl.GetSpecular().ToManagedRGBA()}"); - Console.WriteLine($"\tEmissive: {mtl.GetEmissive().ToManagedRGBA()}"); - - Console.WriteLine($"\tSpecular Power: {mtl.GetSpecularPower()}"); - - Console.WriteLine($"\tTexture Border Color: {mtl.GetTextureBorderColor().ToManagedRGBA()}"); - - Console.WriteLine($"\tTexture Blend Mode: {mtl.GetTextureBlendMode()}"); - Console.WriteLine($"\tTexture Min Mode: {mtl.GetTextureMinMode()}"); - Console.WriteLine($"\tTexture Mag Mode: {mtl.GetTextureMagMode()}"); - Console.WriteLine($"\tSource Blend: {mtl.GetSourceBlend()}"); - Console.WriteLine($"\tDest Blend: {mtl.GetDestBlend()}"); - Console.WriteLine($"\tFill Mode: {mtl.GetFillMode()}"); - Console.WriteLine($"\tShade Mode: {mtl.GetShadeMode()}"); - - Console.WriteLine($"\tAlpha Test Enabled: {mtl.GetAlphaTestEnabled()}"); - Console.WriteLine($"\tAlpha Blend Enabled: {mtl.GetAlphaBlendEnabled()}"); - Console.WriteLine($"\tPerspective Correction Enabled: {mtl.GetPerspectiveCorrectionEnabled()}"); - Console.WriteLine($"\tZ Write Enabled: {mtl.GetZWriteEnabled()}"); - Console.WriteLine($"\tTwo Sided Enabled: {mtl.GetTwoSidedEnabled()}"); - - Console.WriteLine($"\tAlpha Ref: {mtl.GetAlphaRef()}"); - - Console.WriteLine($"\tAlpha Func: {mtl.GetAlphaFunc()}"); - Console.WriteLine($"\tZ Func: {mtl.GetZFunc()}"); - } - - // Console.WriteLine("===== Textures ====="); - // foreach (var tex in reader.GetTextures()) { - // Console.WriteLine(tex.GetName()); - - // Console.WriteLine($"\tFile Name: {tex.GetFileName()}"); - // Console.WriteLine($"\tSave Options: {tex.GetSaveOptions()}"); - // Console.WriteLine($"\tVideo Format: {tex.GetVideoFormat()}"); - // } - + TestCommon(reader); + TestIEquatable(reader); } - Console.WriteLine("===== Done ====="); Console.WriteLine("Press any key to quit..."); Console.ReadKey(true); } + + static void TestCommon(BMapSharp.BMapWrapper.BMFileReader reader) { + // Console.WriteLine("===== Groups ====="); + // foreach (var gp in reader.GetGroups()) { + // Console.WriteLine(gp.GetName()); + // foreach (var gp_item in gp.GetObjects()) { + // Console.WriteLine($"\t{gp_item.GetName()}"); + // } + // } + + // Console.WriteLine("===== 3dObjects ====="); + // foreach (var obj in reader.Get3dObjects()) { + // Console.WriteLine(obj.GetName()); + + // var current_mesh = obj.GetCurrentMesh(); + // var mesh_name = current_mesh is null ? "" : current_mesh.GetName(); + // Console.WriteLine($"\tMesh: {mesh_name}"); + // Console.WriteLine($"\tVisibility: {obj.GetVisibility()}"); + // Console.WriteLine($"\tMatrix: {obj.GetWorldMatrix().ToManaged()}"); + // } + + // Console.WriteLine("===== Meshes ====="); + // foreach (var mesh in reader.GetMeshes()) { + // Console.WriteLine(mesh.GetName()); + + // Console.WriteLine($"\tLit Mode: {mesh.GetLitMode()}"); + // Console.WriteLine($"\tVertex Count: {mesh.GetVertexCount()}"); + // Console.WriteLine($"\tFace Count: {mesh.GetFaceCount()}"); + // Console.WriteLine($"\tMaterial Slot Count: {mesh.GetMaterialSlotCount()}"); + // } + + Console.WriteLine("===== Materials ====="); + foreach (var mtl in reader.GetMaterials()) { + Console.WriteLine(mtl.GetName()); + + Console.WriteLine($"\tDiffuse: {mtl.GetDiffuse().ToManagedRGBA()}"); + Console.WriteLine($"\tAmbient: {mtl.GetAmbient().ToManagedRGBA()}"); + Console.WriteLine($"\tSpecular: {mtl.GetSpecular().ToManagedRGBA()}"); + Console.WriteLine($"\tEmissive: {mtl.GetEmissive().ToManagedRGBA()}"); + + Console.WriteLine($"\tSpecular Power: {mtl.GetSpecularPower()}"); + + Console.WriteLine($"\tTexture Border Color: {mtl.GetTextureBorderColor().ToManagedRGBA()}"); + + Console.WriteLine($"\tTexture Blend Mode: {mtl.GetTextureBlendMode()}"); + Console.WriteLine($"\tTexture Min Mode: {mtl.GetTextureMinMode()}"); + Console.WriteLine($"\tTexture Mag Mode: {mtl.GetTextureMagMode()}"); + Console.WriteLine($"\tSource Blend: {mtl.GetSourceBlend()}"); + Console.WriteLine($"\tDest Blend: {mtl.GetDestBlend()}"); + Console.WriteLine($"\tFill Mode: {mtl.GetFillMode()}"); + Console.WriteLine($"\tShade Mode: {mtl.GetShadeMode()}"); + + Console.WriteLine($"\tAlpha Test Enabled: {mtl.GetAlphaTestEnabled()}"); + Console.WriteLine($"\tAlpha Blend Enabled: {mtl.GetAlphaBlendEnabled()}"); + Console.WriteLine($"\tPerspective Correction Enabled: {mtl.GetPerspectiveCorrectionEnabled()}"); + Console.WriteLine($"\tZ Write Enabled: {mtl.GetZWriteEnabled()}"); + Console.WriteLine($"\tTwo Sided Enabled: {mtl.GetTwoSidedEnabled()}"); + + Console.WriteLine($"\tAlpha Ref: {mtl.GetAlphaRef()}"); + + Console.WriteLine($"\tAlpha Func: {mtl.GetAlphaFunc()}"); + Console.WriteLine($"\tZ Func: {mtl.GetZFunc()}"); + } + + // Console.WriteLine("===== Textures ====="); + // foreach (var tex in reader.GetTextures()) { + // Console.WriteLine(tex.GetName()); + + // Console.WriteLine($"\tFile Name: {tex.GetFileName()}"); + // Console.WriteLine($"\tSave Options: {tex.GetSaveOptions()}"); + // Console.WriteLine($"\tVideo Format: {tex.GetVideoFormat()}"); + // } + + Console.WriteLine("===== END ====="); + } + + static void TestIEquatable(BMapSharp.BMapWrapper.BMFileReader reader) { + if (reader.Get3dObjectCount() < 2u) { + 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(reader.Get3dObjects()); + var first_3dobj = all_3dobjects[0]; + var second_3dobj = all_3dobjects[1]; + all_3dobjects = new List(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(); + 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(); + 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())); + + } + } } \ No newline at end of file diff --git a/BMapBindings/PyBMap/testbench.py b/BMapBindings/PyBMap/testbench.py index cd866b4..41e5959 100644 --- a/BMapBindings/PyBMap/testbench.py +++ b/BMapBindings/PyBMap/testbench.py @@ -9,72 +9,124 @@ def main() -> None: texture_folder: str = 'F:\\Ballance\\Ballance\\Textures' encodings: tuple[str, ...] = ('cp1252', ) with bmap.BMFileReader(file_name, temp_folder, texture_folder, encodings) as reader: - # print('===== Groups =====') - # for gp in reader.get_groups(): - # print(gp.get_name()) - # for gp_item in gp.get_objects(): - # print(f'\t{gp_item.get_name()}') + test_common(reader) + test_equatable(reader) - # print('===== 3dObjects =====') - # for obj in reader.get_3dobjects(): - # print(obj.get_name()) +def test_common(reader: bmap.BMFileReader): + # print('===== Groups =====') + # for gp in reader.get_groups(): + # print(gp.get_name()) + # for gp_item in gp.get_objects(): + # print(f'\t{gp_item.get_name()}') - # current_mesh = obj.get_current_mesh() - # mesh_name = '' if current_mesh is None else current_mesh.get_name() - # print(f'\tMesh: {mesh_name}') - # print(f'\tVisibility: {obj.get_visibility()}') - # print(f'\tMatrix: {obj.get_world_matrix().to_const()}') + # print('===== 3dObjects =====') + # for obj in reader.get_3dobjects(): + # print(obj.get_name()) + + # current_mesh = obj.get_current_mesh() + # mesh_name = '' if current_mesh is None else current_mesh.get_name() + # print(f'\tMesh: {mesh_name}') + # print(f'\tVisibility: {obj.get_visibility()}') + # print(f'\tMatrix: {obj.get_world_matrix().to_const()}') + + # print('===== Meshes =====') + # for mesh in reader.get_meshs(): + # print(mesh.get_name()) + + # print(f'\tLit Mode: {mesh.get_lit_mode()}') + # print(f'\tVertex Count: {mesh.get_vertex_count()}') + # print(f'\tFace Count: {mesh.get_face_count()}') + # print(f'\tMaterial Slot Count: {mesh.get_material_slot_count()}') + + print('===== Materials =====') + for mtl in reader.get_materials(): + print(mtl.get_name()) - # print('===== Meshes =====') - # for mesh in reader.get_meshs(): - # print(mesh.get_name()) + print(f'\tDiffuse: {mtl.get_diffuse().to_const_rgba()}') + print(f'\tAmbient: {mtl.get_ambient().to_const_rgba()}') + print(f'\tSpecular: {mtl.get_specular().to_const_rgba()}') + print(f'\tEmissive: {mtl.get_emissive().to_const_rgba()}') - # print(f'\tLit Mode: {mesh.get_lit_mode()}') - # print(f'\tVertex Count: {mesh.get_vertex_count()}') - # print(f'\tFace Count: {mesh.get_face_count()}') - # print(f'\tMaterial Slot Count: {mesh.get_material_slot_count()}') + print(f'\tSpecular Power: {mtl.get_specular_power()}') - print('===== Materials =====') - for mtl in reader.get_materials(): - print(mtl.get_name()) - - print(f'\tDiffuse: {mtl.get_diffuse().to_const_rgba()}') - print(f'\tAmbient: {mtl.get_ambient().to_const_rgba()}') - print(f'\tSpecular: {mtl.get_specular().to_const_rgba()}') - print(f'\tEmissive: {mtl.get_emissive().to_const_rgba()}') + print(f'\tTexture Border Color: {mtl.get_texture_border_color().to_const_rgba()}') - print(f'\tSpecular Power: {mtl.get_specular_power()}') + print(f'\tTexture Blend Mode: {mtl.get_texture_blend_mode()}') + print(f'\tTexture Min Mode: {mtl.get_texture_min_mode()}') + print(f'\tTexture Mag Mode: {mtl.get_texture_mag_mode()}') + print(f'\tSource Blend: {mtl.get_source_blend()}') + print(f'\tDest Blend: {mtl.get_dest_blend()}') + print(f'\tFill Mode: {mtl.get_fill_mode()}') + print(f'\tShade Mode: {mtl.get_shade_mode()}') - print(f'\tTexture Border Color: {mtl.get_texture_border_color().to_const_rgba()}') + print(f'\tAlpha Test Enabled: {mtl.get_alpha_test_enabled()}') + print(f'\tAlpha Blend Enabled: {mtl.get_alpha_blend_enabled()}') + print(f'\tPerspective Correction Enabled: {mtl.get_perspective_correction_enabled()}') + print(f'\tZ Write Enabled: {mtl.get_z_write_enabled()}') + print(f'\tTwo Sided Enabled: {mtl.get_two_sided_enabled()}') - print(f'\tTexture Blend Mode: {mtl.get_texture_blend_mode()}') - print(f'\tTexture Min Mode: {mtl.get_texture_min_mode()}') - print(f'\tTexture Mag Mode: {mtl.get_texture_mag_mode()}') - print(f'\tSource Blend: {mtl.get_source_blend()}') - print(f'\tDest Blend: {mtl.get_dest_blend()}') - print(f'\tFill Mode: {mtl.get_fill_mode()}') - print(f'\tShade Mode: {mtl.get_shade_mode()}') + print(f'\tAlpha Ref: {mtl.get_alpha_ref()}') - print(f'\tAlpha Test Enabled: {mtl.get_alpha_test_enabled()}') - print(f'\tAlpha Blend Enabled: {mtl.get_alpha_blend_enabled()}') - print(f'\tPerspective Correction Enabled: {mtl.get_perspective_correction_enabled()}') - print(f'\tZ Write Enabled: {mtl.get_z_write_enabled()}') - print(f'\tTwo Sided Enabled: {mtl.get_two_sided_enabled()}') + print(f'\tAlpha Func: {mtl.get_alpha_func()}') + print(f'\tZ Func: {mtl.get_z_func()}') - print(f'\tAlpha Ref: {mtl.get_alpha_ref()}') + # print('===== Textures =====') + # for tex in reader.get_textures(): + # print(tex.get_name()) - print(f'\tAlpha Func: {mtl.get_alpha_func()}') - print(f'\tZ Func: {mtl.get_z_func()}') - - print('===== Textures =====') - for tex in reader.get_textures(): - print(tex.get_name()) - - print(f'\tFile Name: {tex.get_file_name()}') - print(f'\tSave Options: {tex.get_save_options()}') - print(f'\tVideo Format: {tex.get_video_format()}') + # print(f'\tFile Name: {tex.get_file_name()}') + # print(f'\tSave Options: {tex.get_save_options()}') + # print(f'\tVideo Format: {tex.get_video_format()}') 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__': main()