From afd021ec0df718e63ef2052e3a320eaf824e748c Mon Sep 17 00:00:00 2001 From: Luca Carniato Date: Mon, 15 Jul 2024 13:06:37 +0200 Subject: [PATCH 1/2] Add meshkernel state in meshkernelnet --- src/MeshKernelNET/Api/DisposableMesh2D.cs | 136 +++++++++++++++++- .../Api/DisposableNativeObject.cs | 12 +- src/MeshKernelNET/Api/MeshKernelApi.cs | 59 ++++++-- src/MeshKernelNET/Api/MeshKernelState.cs | 62 ++++++++ src/MeshKernelNET/Helpers/IntPtrExtensions.cs | 25 ++++ test/MeshKernelNETTest/Api/MeshKernelTest.cs | 48 ++++--- 6 files changed, 308 insertions(+), 34 deletions(-) create mode 100644 src/MeshKernelNET/Api/MeshKernelState.cs diff --git a/src/MeshKernelNET/Api/DisposableMesh2D.cs b/src/MeshKernelNET/Api/DisposableMesh2D.cs index 8cb1d0a..a13a297 100644 --- a/src/MeshKernelNET/Api/DisposableMesh2D.cs +++ b/src/MeshKernelNET/Api/DisposableMesh2D.cs @@ -1,4 +1,5 @@ -using MeshKernelNET.Native; +using System; +using MeshKernelNET.Native; using ProtoBuf; namespace MeshKernelNET.Api @@ -59,6 +60,32 @@ public sealed class DisposableMesh2D : DisposableNativeObject, IRe public DisposableMesh2D() { + NumNodes = 0; + NumEdges = 0; + NumFaces = 0; + NumFaceNodes = 0; + } + + public DisposableMesh2D(DisposableMesh2D source) + { + NumNodes = source.NumNodes; + NumEdges = source.NumEdges; + NumFaces = source.NumFaces; + NumFaceNodes = source.NumFaceNodes; + + edgeFaces = (int[])source.edgeFaces.Clone(); + edgeNodes = (int[])source.edgeNodes.Clone(); + faceEdges = (int[])source.faceEdges.Clone(); + faceNodes = (int[])source.faceNodes.Clone(); + nodesPerFace = (int[])source.nodesPerFace.Clone(); + nodeX = (double[])source.nodeX.Clone(); + nodeY = (double[])source.nodeY.Clone(); + edgeX = (double[])source.edgeX.Clone(); + edgeY = (double[])source.edgeY.Clone(); + faceX = (double[])source.faceX.Clone(); + faceY = (double[])source.faceY.Clone(); + numValidNodes = source.numValidNodes; + numValidEdges = source.numValidEdges; } public DisposableMesh2D(int nNodes, int nEdges, int nFaces, int nFaceNodes) @@ -81,6 +108,113 @@ public DisposableMesh2D(int nNodes, int nEdges, int nFaces, int nFaceNodes) FaceY = new double[NumFaces]; } + public void UnPinPin() + { + UnPinMemory(); + PinMemory(); + } + + + public void Resize(int nNodes, int nEdges, int nFaces, int nFaceNodes) + { + bool changed = false; + if (nNodes > NumNodes) + { + double[] newNodeX = new double[nNodes]; + double[] newNodeY = new double[nNodes]; + if (NodeX != null) + { + Array.Copy(NodeY, newNodeY, NumNodes); + } + if (NodeY != null) + { + Array.Copy(NodeY, newNodeY, NumNodes); + } + NodeX = newNodeX; + NodeY = newNodeY; + NumNodes = nNodes; + changed = true; + } + + if (nEdges > NumEdges) + { + double[] newEdgeX = new double[nEdges]; + double[] newEdgeY = new double[nEdges]; + int[] newEdgeFaces = new int[nEdges * 2]; + int[] newEdgeNodes = new int[nEdges * 2]; + if (EdgeX != null) + { + Array.Copy(EdgeX, newEdgeX, NumEdges); + } + if (EdgeY != null) + { + Array.Copy(EdgeY, newEdgeY, NumEdges); + } + if (EdgeFaces != null) + { + Array.Copy(EdgeFaces, newEdgeFaces, NumEdges * 2); + } + if (EdgeNodes != null) + { + Array.Copy(EdgeNodes, newEdgeNodes, NumEdges * 2); + } + EdgeX = newEdgeX; + EdgeY = newEdgeY; + EdgeFaces = newEdgeFaces; + EdgeNodes = newEdgeNodes; + NumEdges = nEdges; + changed = true; + } + + if (nFaces > NumFaces) + { + double[] newFaceX = new double[nFaces]; + double[] newFaceY = new double[nFaces]; + int[] newNodesPerFace = new int[nFaces]; + if (FaceX != null) + { + Array.Copy(FaceX, newFaceX, NumFaces); + } + if (FaceY != null) + { + Array.Copy(FaceY, newFaceY, NumFaces); + } + if (NodesPerFace != null) + { + Array.Copy(NodesPerFace, newNodesPerFace, NumFaces); + } + FaceX = newFaceX; + FaceY = newFaceY; + NodesPerFace = newNodesPerFace; + NumFaces = nFaces; + changed = true; + } + + if (nFaceNodes > NumFaceNodes) + { + int[] newFaceEdges = new int[nFaceNodes]; + int[] newFaceNodes = new int[nFaceNodes]; + if (FaceEdges != null) + { + Array.Copy(FaceEdges, newFaceEdges, NumFaceNodes); + } + if (FaceNodes != null) + { + Array.Copy(FaceNodes, newFaceNodes, NumFaceNodes); + } + + FaceEdges = newFaceEdges; + FaceNodes = newFaceNodes; + NumFaceNodes = nFaceNodes; + changed = true; + } + + if (changed) + { + UnPinPin(); + } + } + ~DisposableMesh2D() { Dispose(false); diff --git a/src/MeshKernelNET/Api/DisposableNativeObject.cs b/src/MeshKernelNET/Api/DisposableNativeObject.cs index 112b8ee..34a0d0c 100644 --- a/src/MeshKernelNET/Api/DisposableNativeObject.cs +++ b/src/MeshKernelNET/Api/DisposableNativeObject.cs @@ -67,7 +67,8 @@ public TNative CreateNativeObject() /// protected IntPtr GetPinnedObjectPointer(object objectToLookUp) { - return objectGarbageCollectHandles[objectToLookUp].AddrOfPinnedObject(); + var addressPinnedObject = objectGarbageCollectHandles[objectToLookUp].AddrOfPinnedObject(); + return addressPinnedObject; } /// @@ -92,7 +93,7 @@ protected virtual void Dispose(bool disposing) /// /// Pins the arrays in memory (no garbage collect until unpinned (done in dispose)) /// - private void PinMemory() + public void PinMemory() { IEnumerable arrayProperties = GetType().GetProperties().Where(f => f.PropertyType.IsArray); @@ -126,7 +127,7 @@ private void PinMemory() } } - private void UnPinMemory() + public void UnPinMemory() { foreach (KeyValuePair valuePair in objectGarbageCollectHandles) { @@ -139,7 +140,10 @@ private void UnPinMemory() private void AddObjectToPin(object objectToPin, object lookupObject = null) { object key = lookupObject ?? objectToPin; - objectGarbageCollectHandles.Add(key, GCHandle.Alloc(objectToPin, GCHandleType.Pinned)); + if (!objectGarbageCollectHandles.ContainsKey(key)) + { + objectGarbageCollectHandles.Add(key, GCHandle.Alloc(objectToPin, GCHandleType.Pinned)); + } } private void ReleaseUnmanagedResources() diff --git a/src/MeshKernelNET/Api/MeshKernelApi.cs b/src/MeshKernelNET/Api/MeshKernelApi.cs index a4fe0f0..d9074e8 100644 --- a/src/MeshKernelNET/Api/MeshKernelApi.cs +++ b/src/MeshKernelNET/Api/MeshKernelApi.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using MeshKernelNET.Helpers; using MeshKernelNET.Native; +using System.Collections.Generic; namespace MeshKernelNET.Api { @@ -12,11 +13,16 @@ namespace MeshKernelNET.Api // DotCover on the build server does not work correctly with remoting public sealed class MeshKernelApi : IMeshKernelApi { + + // Static dictionary equivalent to static std::unordered_map meshKernelState in C++ + public static Dictionary MeshKernelStateDictionary = new Dictionary(); + /// public int AllocateState(int projectionType) { var meshKernelId = 0; MeshKernelDll.AllocateState(projectionType, ref meshKernelId); + MeshKernelStateDictionary[meshKernelId] = new MeshKernelState(); return meshKernelId; } @@ -503,7 +509,12 @@ public int CurvilinearSmoothingDirectional(int meshKernelId, public int DeallocateState(int meshKernelId) { - return MeshKernelDll.DeallocateState(meshKernelId); + var errorCode = MeshKernelDll.DeallocateState(meshKernelId); + if (errorCode == 0) + { + MeshKernelStateDictionary.Remove(meshKernelId); + } + return errorCode; } public int GetAveragingMethodClosestPoint(ref int method) @@ -859,25 +870,26 @@ public int Mesh2dGetClosestNode(int meshKernelId, public int Mesh2dGetData(int meshKernelId, out DisposableMesh2D disposableMesh2D) { - var newMesh2D = new Mesh2DNative(); + disposableMesh2D = MeshKernelStateDictionary[meshKernelId].DisposableMesh2D; - int exitCode = MeshKernelDll.Mesh2DGetDimensions(meshKernelId, ref newMesh2D); + var newMesh2DNative = new Mesh2DNative(); + + int exitCode = MeshKernelDll.Mesh2DGetDimensions(meshKernelId, ref newMesh2DNative); if (exitCode != 0) { - disposableMesh2D = new DisposableMesh2D(); return exitCode; } - disposableMesh2D = new DisposableMesh2D(newMesh2D.num_nodes, - newMesh2D.num_edges, - newMesh2D.num_faces, - newMesh2D.num_face_nodes); + disposableMesh2D.Resize(newMesh2DNative.num_nodes, + newMesh2DNative.num_edges, + newMesh2DNative.num_faces, + newMesh2DNative.num_face_nodes); - newMesh2D = disposableMesh2D.CreateNativeObject(); + newMesh2DNative = disposableMesh2D.CreateNativeObject(); + exitCode = MeshKernelDll.Mesh2dGetData(meshKernelId, ref newMesh2DNative); - exitCode = MeshKernelDll.Mesh2dGetData(meshKernelId, ref newMesh2D); - disposableMesh2D = CreateDisposableMesh2D(newMesh2D, true); + CreateDisposableMesh2DCached(newMesh2DNative, ref disposableMesh2D, true); return exitCode; } @@ -1303,6 +1315,7 @@ public int Mesh2dTranslate(int meshKernelId, double translationX, double transla /// public int Mesh2dSet(int meshKernelId, in DisposableMesh2D disposableMesh2D) { + MeshKernelStateDictionary[meshKernelId].DisposableMesh2D = disposableMesh2D; Mesh2DNative mesh2D = disposableMesh2D.CreateNativeObject(); return MeshKernelDll.Mesh2dSet(meshKernelId, ref mesh2D); } @@ -1432,6 +1445,30 @@ private DisposableMesh2D CreateDisposableMesh2D(Mesh2DNative newMesh2DNative, bo return disposableMesh2D; } + private void CreateDisposableMesh2DCached(Mesh2DNative newMesh2DNative, ref DisposableMesh2D disposableMesh2D, bool addCellInformation = false) + { + + newMesh2DNative.node_x.CreateValueArrayCached(newMesh2DNative.num_nodes, disposableMesh2D.NodeX); + newMesh2DNative.node_y.CreateValueArrayCached(newMesh2DNative.num_nodes, disposableMesh2D.NodeY); + newMesh2DNative.edge_nodes.CreateValueArrayCached(newMesh2DNative.num_edges * 2, disposableMesh2D.EdgeNodes); + + disposableMesh2D.NumEdges = newMesh2DNative.num_edges; + disposableMesh2D.NumNodes = newMesh2DNative.num_nodes; + disposableMesh2D.NumValidNodes = newMesh2DNative.num_valid_nodes; + disposableMesh2D.NumValidEdges = newMesh2DNative.num_valid_edges; + + + if (addCellInformation && newMesh2DNative.num_faces > 0) + { + disposableMesh2D.NumFaces = newMesh2DNative.num_faces; + newMesh2DNative.nodes_per_face.CreateValueArrayCached(newMesh2DNative.num_faces, disposableMesh2D.NodesPerFace); + int numFaceNodes = disposableMesh2D.NodesPerFace.Sum(); + newMesh2DNative.face_nodes.CreateValueArrayCached(numFaceNodes, disposableMesh2D.FaceNodes); + newMesh2DNative.face_x.CreateValueArrayCached(newMesh2DNative.num_faces, disposableMesh2D.FaceX); + newMesh2DNative.face_y.CreateValueArrayCached(newMesh2DNative.num_faces, disposableMesh2D.FaceY); + } + } + private DisposableMesh1D CreateDisposableMesh1d(Mesh1DNative newMesh1DNative) { var disposableMesh1D = new DisposableMesh1D diff --git a/src/MeshKernelNET/Api/MeshKernelState.cs b/src/MeshKernelNET/Api/MeshKernelState.cs new file mode 100644 index 0000000..a682882 --- /dev/null +++ b/src/MeshKernelNET/Api/MeshKernelState.cs @@ -0,0 +1,62 @@ +using MeshKernelNET.Api; +using System; + +// Define the MeshKernelState class +public class MeshKernelState : IDisposable +{ + private DisposableMesh2D disposableMesh2D; + private bool disposed = false; // To detect redundant calls + + // Default constructor + public MeshKernelState() + { + disposableMesh2D = new DisposableMesh2D(); + } + + // Example method that returns a reference to the managed DisposableMesh2D + public DisposableMesh2D DisposableMesh2D + { + get { return disposableMesh2D; } + set + { + if (disposableMesh2D != null && !disposed) + { + disposableMesh2D.Dispose(); + } + disposableMesh2D = value; + } + } + + // Public implementation of Dispose pattern callable by consumers. + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + // Protected implementation of Dispose pattern. + protected virtual void Dispose(bool disposing) + { + if (disposed) + return; + + if (disposing) + { + // Free any other managed objects here. + if (disposableMesh2D != null) + { + disposableMesh2D.Dispose(); + disposableMesh2D = null; + } + } + + // Free any unmanaged objects here. + disposed = true; + } + + // Destructor + ~MeshKernelState() + { + Dispose(false); + } +} \ No newline at end of file diff --git a/src/MeshKernelNET/Helpers/IntPtrExtensions.cs b/src/MeshKernelNET/Helpers/IntPtrExtensions.cs index dc0b656..ed6c675 100644 --- a/src/MeshKernelNET/Helpers/IntPtrExtensions.cs +++ b/src/MeshKernelNET/Helpers/IntPtrExtensions.cs @@ -33,5 +33,30 @@ public static T[] CreateValueArray(this IntPtr pointer, int size, int stringS return array; } + + public static void CreateValueArrayCached(this IntPtr pointer, int size, T[] array, int stringSize = 0) + { + if (typeof(T) == typeof(double)) + { + Marshal.Copy(pointer, array as double[], 0, size); + } + else if (typeof(T) == typeof(int)) + { + Marshal.Copy(pointer, array as int[], 0, size); + } + else if (typeof(T) == typeof(string)) + { + int totalByteSize = size * stringSize; + var byteArray = new byte[totalByteSize]; + + Marshal.Copy(pointer, byteArray, 0, totalByteSize); + + byteArray.GetStringArrayFromFlattenedAsciiCodedStringArray(size).CopyTo(array, 0); + } + else + { + throw new NotSupportedException("Currently only double, int and string are supported"); + } + } } } \ No newline at end of file diff --git a/test/MeshKernelNETTest/Api/MeshKernelTest.cs b/test/MeshKernelNETTest/Api/MeshKernelTest.cs index 65cffca..3f4313a 100644 --- a/test/MeshKernelNETTest/Api/MeshKernelTest.cs +++ b/test/MeshKernelNETTest/Api/MeshKernelTest.cs @@ -1067,7 +1067,7 @@ public void Mesh2dDeleteEdgeByIndexThroughAPI() using (var api = new MeshKernelApi()) { var id = 0; - var mesh2D = new DisposableMesh2D(); + var meshApi = new DisposableMesh2D(); try { id = api.AllocateState(0); @@ -1078,19 +1078,19 @@ public void Mesh2dDeleteEdgeByIndexThroughAPI() // Delete a valid edge Assert.AreEqual(0, api.Mesh2dDeleteEdgeByIndex(id, 0)); // Expect the number of edges to decrease by 1 - Assert.AreEqual(0, api.Mesh2dGetData(id, out mesh2D)); - Assert.AreEqual(initNumEdges - 1, mesh2D.NumValidEdges); + Assert.AreEqual(0, api.Mesh2dGetData(id, out meshApi)); + Assert.AreEqual(initNumEdges - 1, meshApi.NumValidEdges); // Delete an invalid edge Assert.AreEqual(0, api.Mesh2dDeleteEdgeByIndex(id, 666)); // Expect the number of edges to remain unchanged - Assert.AreEqual(0, api.Mesh2dGetData(id, out mesh2D)); - Assert.AreEqual(initNumEdges - 1, mesh2D.NumValidEdges); + Assert.AreEqual(0, api.Mesh2dGetData(id, out meshApi)); + Assert.AreEqual(initNumEdges - 1, meshApi.NumValidEdges); } finally { api.DeallocateState(id); - mesh2D.Dispose(); + meshApi.Dispose(); } } } @@ -1803,7 +1803,8 @@ public void Mesh2dConvertProjectionThroughApi() using (var api = new MeshKernelApi()) { var id = 0; - DisposableMesh2D meshInitial = null; + var meshApi = new DisposableMesh2D(); + var meshInitial = new DisposableMesh2D(); var meshFinal = new DisposableMesh2D(); try { @@ -1812,9 +1813,10 @@ public void Mesh2dConvertProjectionThroughApi() Assert.AreEqual(0, api.Mesh2dSet(id, mesh)); // Get data of initial mesh - meshInitial = new DisposableMesh2D(); - Assert.AreEqual(0, api.Mesh2dGetData(id, out meshInitial)); + Assert.AreEqual(0, api.Mesh2dGetData(id, out meshApi)); Assert.IsNotNull(meshInitial); + meshInitial?.Dispose(); + meshInitial = new DisposableMesh2D(meshApi); { // Set the zone string @@ -1827,8 +1829,10 @@ public void Mesh2dConvertProjectionThroughApi() Assert.AreEqual(ProjectionOptions.Spherical, (ProjectionOptions)projection); // Get data of final mesh - Assert.AreEqual(0, api.Mesh2dGetData(id, out meshFinal)); - Assert.IsNotNull(meshFinal); + Assert.AreEqual(0, api.Mesh2dGetData(id, out meshApi)); + Assert.IsNotNull(meshApi); + meshFinal?.Dispose(); + meshFinal = new DisposableMesh2D(meshApi); // It must be different than the initial mesh Assert.AreNotEqual(meshInitial.NodeX, meshFinal.NodeX); @@ -1841,8 +1845,10 @@ public void Mesh2dConvertProjectionThroughApi() } // Get data of final mesh - Assert.AreEqual(0, api.Mesh2dGetData(id, out meshFinal)); - Assert.IsNotNull(meshFinal); + Assert.AreEqual(0, api.Mesh2dGetData(id, out meshApi)); + Assert.IsNotNull(meshApi); + meshFinal?.Dispose(); + meshFinal = new DisposableMesh2D(meshApi); // Compare meshes the initial and final meshes @@ -1874,7 +1880,8 @@ public void Mesh2dConvertProjectionThroughApi() { api.DeallocateState(id); meshInitial?.Dispose(); - meshFinal.Dispose(); + meshFinal?.Dispose(); + meshApi?.Dispose(); } } } @@ -1887,7 +1894,9 @@ public void Mesh2dConvertProjectionThroughApiFails() using (var api = new MeshKernelApi()) { var id = 0; - DisposableMesh2D meshInitial = null; + + var meshApi = new DisposableMesh2D(); + var meshInitial = new DisposableMesh2D(); try { id = api.AllocateState(0); @@ -1896,8 +1905,10 @@ public void Mesh2dConvertProjectionThroughApiFails() // Get data of initial mesh meshInitial = new DisposableMesh2D(); - Assert.AreEqual(0, api.Mesh2dGetData(id, out meshInitial)); - Assert.IsNotNull(meshInitial); + Assert.AreEqual(0, api.Mesh2dGetData(id, out meshApi)); + Assert.IsNotNull(meshApi); + meshInitial?.Dispose(); + meshInitial = new DisposableMesh2D(meshApi); { int expectedExitCode = -1; @@ -1927,7 +1938,8 @@ public void Mesh2dConvertProjectionThroughApiFails() finally { api.DeallocateState(id); - meshInitial.Dispose(); + meshInitial?.Dispose(); + meshApi?.Dispose(); } } } From bc1854f5623cea00024f99eaddab90df575131ef Mon Sep 17 00:00:00 2001 From: Luca Carniato Date: Mon, 15 Jul 2024 18:29:25 +0200 Subject: [PATCH 2/2] Use int dictionary for object addresses --- src/MeshKernelNET/Api/DisposableContacts.cs | 4 +- .../Api/DisposableCurvilinearGrid.cs | 4 +- .../Api/DisposableGeometryList.cs | 6 +- .../Api/DisposableGriddedSamples.cs | 6 +- src/MeshKernelNET/Api/DisposableMesh1D.cs | 6 +- src/MeshKernelNET/Api/DisposableMesh2D.cs | 84 +++++++++++-------- .../Api/DisposableNativeObject.cs | 30 ++++--- src/MeshKernelNET/Api/MeshKernelApi.cs | 10 ++- test/MeshKernelNETTest/Api/MeshKernelTest.cs | 28 +++++-- 9 files changed, 107 insertions(+), 71 deletions(-) diff --git a/src/MeshKernelNET/Api/DisposableContacts.cs b/src/MeshKernelNET/Api/DisposableContacts.cs index ea8af84..9d1f6c7 100644 --- a/src/MeshKernelNET/Api/DisposableContacts.cs +++ b/src/MeshKernelNET/Api/DisposableContacts.cs @@ -51,8 +51,8 @@ public int NumContacts protected override void SetNativeObject(ref ContactsNative nativeObject) { - nativeObject.mesh1d_indices = GetPinnedObjectPointer(Mesh1dIndices); - nativeObject.mesh2d_indices = GetPinnedObjectPointer(Mesh2dIndices); + nativeObject.mesh1d_indices = GetPinnedObjectPointer(0); + nativeObject.mesh2d_indices = GetPinnedObjectPointer(1); nativeObject.num_contacts = NumContacts; } } diff --git a/src/MeshKernelNET/Api/DisposableCurvilinearGrid.cs b/src/MeshKernelNET/Api/DisposableCurvilinearGrid.cs index 46fc289..df9f459 100644 --- a/src/MeshKernelNET/Api/DisposableCurvilinearGrid.cs +++ b/src/MeshKernelNET/Api/DisposableCurvilinearGrid.cs @@ -135,8 +135,8 @@ public double GetNodeY(int nodeIndex) protected override void SetNativeObject(ref CurvilinearGridNative nativeObject) { - nativeObject.node_x = GetPinnedObjectPointer(NodeX); - nativeObject.node_y = GetPinnedObjectPointer(NodeY); + nativeObject.node_x = GetPinnedObjectPointer(0); + nativeObject.node_y = GetPinnedObjectPointer(1); nativeObject.num_m = numM; nativeObject.num_n = numN; } diff --git a/src/MeshKernelNET/Api/DisposableGeometryList.cs b/src/MeshKernelNET/Api/DisposableGeometryList.cs index d14ac97..e11f717 100644 --- a/src/MeshKernelNET/Api/DisposableGeometryList.cs +++ b/src/MeshKernelNET/Api/DisposableGeometryList.cs @@ -91,9 +91,9 @@ public double[] Values protected override void SetNativeObject(ref GeometryListNative nativeObject) { - nativeObject.xCoordinates = GetPinnedObjectPointer(XCoordinates); - nativeObject.yCoordinates = GetPinnedObjectPointer(YCoordinates); - nativeObject.zCoordinates = GetPinnedObjectPointer(Values); + nativeObject.xCoordinates = GetPinnedObjectPointer(0); + nativeObject.yCoordinates = GetPinnedObjectPointer(1); + nativeObject.zCoordinates = GetPinnedObjectPointer(2); nativeObject.numberOfCoordinates = NumberOfCoordinates; nativeObject.geometrySeperator = GeometrySeparator; nativeObject.innerOuterSeperator = InnerOuterSeparator; diff --git a/src/MeshKernelNET/Api/DisposableGriddedSamples.cs b/src/MeshKernelNET/Api/DisposableGriddedSamples.cs index 50bf956..bc2af3b 100644 --- a/src/MeshKernelNET/Api/DisposableGriddedSamples.cs +++ b/src/MeshKernelNET/Api/DisposableGriddedSamples.cs @@ -112,9 +112,9 @@ protected override void SetNativeObject(ref GriddedSamplesNative nativeObject) nativeObject.origin_x = originX; nativeObject.origin_y = originY; nativeObject.cell_size = cellSize; - nativeObject.coordinates_x = GetPinnedObjectPointer(coordinatesX); - nativeObject.coordinates_y = GetPinnedObjectPointer(coordinatesY); - nativeObject.values = GetPinnedObjectPointer(values); + nativeObject.coordinates_x = GetPinnedObjectPointer(0); + nativeObject.coordinates_y = GetPinnedObjectPointer(1); + nativeObject.values = GetPinnedObjectPointer(2); nativeObject.value_type = valueType; } } diff --git a/src/MeshKernelNET/Api/DisposableMesh1D.cs b/src/MeshKernelNET/Api/DisposableMesh1D.cs index 83e0cc5..0904a73 100644 --- a/src/MeshKernelNET/Api/DisposableMesh1D.cs +++ b/src/MeshKernelNET/Api/DisposableMesh1D.cs @@ -90,9 +90,9 @@ public int NumValidEdges protected override void SetNativeObject(ref Mesh1DNative nativeObject) { - nativeObject.edge_nodes = GetPinnedObjectPointer(EdgeNodes); - nativeObject.node_x = GetPinnedObjectPointer(NodeX); - nativeObject.node_y = GetPinnedObjectPointer(NodeY); + nativeObject.edge_nodes = GetPinnedObjectPointer(0); + nativeObject.node_x = GetPinnedObjectPointer(1); + nativeObject.node_y = GetPinnedObjectPointer(2); nativeObject.num_valid_nodes = numValidNodes; nativeObject.num_nodes = NumNodes; nativeObject.num_edges = NumEdges; diff --git a/src/MeshKernelNET/Api/DisposableMesh2D.cs b/src/MeshKernelNET/Api/DisposableMesh2D.cs index a13a297..4b671ef 100644 --- a/src/MeshKernelNET/Api/DisposableMesh2D.cs +++ b/src/MeshKernelNET/Api/DisposableMesh2D.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using MeshKernelNET.Native; using ProtoBuf; @@ -73,17 +76,18 @@ public DisposableMesh2D(DisposableMesh2D source) NumFaces = source.NumFaces; NumFaceNodes = source.NumFaceNodes; - edgeFaces = (int[])source.edgeFaces.Clone(); - edgeNodes = (int[])source.edgeNodes.Clone(); - faceEdges = (int[])source.faceEdges.Clone(); - faceNodes = (int[])source.faceNodes.Clone(); - nodesPerFace = (int[])source.nodesPerFace.Clone(); - nodeX = (double[])source.nodeX.Clone(); - nodeY = (double[])source.nodeY.Clone(); - edgeX = (double[])source.edgeX.Clone(); - edgeY = (double[])source.edgeY.Clone(); - faceX = (double[])source.faceX.Clone(); - faceY = (double[])source.faceY.Clone(); + + edgeFaces = (int[])source.edgeFaces?.Clone(); + edgeNodes = (int[])source.edgeNodes?.Clone(); + faceEdges = (int[])source.faceEdges?.Clone(); + faceNodes = (int[])source.faceNodes?.Clone(); + nodesPerFace = (int[])source.nodesPerFace?.Clone(); + nodeX = (double[])source.nodeX?.Clone(); + nodeY = (double[])source.nodeY?.Clone(); + edgeX = (double[])source.edgeX?.Clone(); + edgeY = (double[])source.edgeY?.Clone(); + faceX = (double[])source.faceX?.Clone(); + faceY = (double[])source.faceY?.Clone(); numValidNodes = source.numValidNodes; numValidEdges = source.numValidEdges; } @@ -108,16 +112,33 @@ public DisposableMesh2D(int nNodes, int nEdges, int nFaces, int nFaceNodes) FaceY = new double[NumFaces]; } - public void UnPinPin() + private bool HasChanged(int nNodes, int nEdges, int nFaces) { - UnPinMemory(); - PinMemory(); + if (nNodes > NumNodes) + { + return true; + } + if (nEdges > NumEdges) + { + return true; + } + if (nFaces > NumFaces) + { + return true; + } + + return false; } public void Resize(int nNodes, int nEdges, int nFaces, int nFaceNodes) { - bool changed = false; + bool hasChanged = HasChanged(nNodes, nEdges, nFaces); + if (hasChanged) + { + UnPinMemory(); + } + if (nNodes > NumNodes) { double[] newNodeX = new double[nNodes]; @@ -133,7 +154,6 @@ public void Resize(int nNodes, int nEdges, int nFaces, int nFaceNodes) NodeX = newNodeX; NodeY = newNodeY; NumNodes = nNodes; - changed = true; } if (nEdges > NumEdges) @@ -163,7 +183,6 @@ public void Resize(int nNodes, int nEdges, int nFaces, int nFaceNodes) EdgeFaces = newEdgeFaces; EdgeNodes = newEdgeNodes; NumEdges = nEdges; - changed = true; } if (nFaces > NumFaces) @@ -187,7 +206,6 @@ public void Resize(int nNodes, int nEdges, int nFaces, int nFaceNodes) FaceY = newFaceY; NodesPerFace = newNodesPerFace; NumFaces = nFaces; - changed = true; } if (nFaceNodes > NumFaceNodes) @@ -206,12 +224,11 @@ public void Resize(int nNodes, int nEdges, int nFaces, int nFaceNodes) FaceEdges = newFaceEdges; FaceNodes = newFaceNodes; NumFaceNodes = nFaceNodes; - changed = true; } - - if (changed) + + if (hasChanged) { - UnPinPin(); + PinMemory(); } } @@ -374,17 +391,18 @@ public double GetNodeY(int nodeIndex) protected override void SetNativeObject(ref Mesh2DNative nativeObject) { - nativeObject.edge_faces = GetPinnedObjectPointer(EdgeFaces); - nativeObject.edge_nodes = GetPinnedObjectPointer(EdgeNodes); - nativeObject.face_edges = GetPinnedObjectPointer(FaceEdges); - nativeObject.face_nodes = GetPinnedObjectPointer(FaceNodes); - nativeObject.nodes_per_face = GetPinnedObjectPointer(NodesPerFace); - nativeObject.node_x = GetPinnedObjectPointer(NodeX); - nativeObject.node_y = GetPinnedObjectPointer(NodeY); - nativeObject.edge_x = GetPinnedObjectPointer(EdgeX); - nativeObject.edge_y = GetPinnedObjectPointer(EdgeY); - nativeObject.face_x = GetPinnedObjectPointer(FaceX); - nativeObject.face_y = GetPinnedObjectPointer(FaceY); + nativeObject.edge_faces = GetPinnedObjectPointer(0); + nativeObject.edge_nodes = GetPinnedObjectPointer(1); + nativeObject.face_edges = GetPinnedObjectPointer(2); + nativeObject.face_nodes = GetPinnedObjectPointer(3); + nativeObject.nodes_per_face = GetPinnedObjectPointer(4); + nativeObject.node_x = GetPinnedObjectPointer(5); + nativeObject.node_y = GetPinnedObjectPointer(6); + nativeObject.edge_x = GetPinnedObjectPointer(7); + nativeObject.edge_y = GetPinnedObjectPointer(8); + nativeObject.face_x = GetPinnedObjectPointer(9); + nativeObject.face_y = GetPinnedObjectPointer(10); + nativeObject.num_nodes = NumNodes; nativeObject.num_valid_nodes = NumValidNodes; nativeObject.num_edges = NumEdges; diff --git a/src/MeshKernelNET/Api/DisposableNativeObject.cs b/src/MeshKernelNET/Api/DisposableNativeObject.cs index 34a0d0c..6b2d1ba 100644 --- a/src/MeshKernelNET/Api/DisposableNativeObject.cs +++ b/src/MeshKernelNET/Api/DisposableNativeObject.cs @@ -12,7 +12,7 @@ namespace MeshKernelNET.Api /// public abstract class DisposableNativeObject : IDisposable where TNative : new() { - private readonly Dictionary objectGarbageCollectHandles = new Dictionary(); + private readonly Dictionary objectGarbageCollectHandles = new Dictionary(); private bool disposed; /// @@ -63,11 +63,11 @@ public TNative CreateNativeObject() /// /// Get the pointer to the pinned object /// - /// Object to get + /// Object to get /// - protected IntPtr GetPinnedObjectPointer(object objectToLookUp) + protected IntPtr GetPinnedObjectPointer(int objectId) { - var addressPinnedObject = objectGarbageCollectHandles[objectToLookUp].AddrOfPinnedObject(); + var addressPinnedObject = objectGarbageCollectHandles[objectId].AddrOfPinnedObject(); return addressPinnedObject; } @@ -98,6 +98,7 @@ public void PinMemory() IEnumerable arrayProperties = GetType().GetProperties().Where(f => f.PropertyType.IsArray); // force initialization + int keyCounter = 0; foreach (PropertyInfo arrayProperty in arrayProperties) { Type elementType = arrayProperty.PropertyType.GetElementType(); @@ -118,18 +119,25 @@ public void PinMemory() } byte[] bytes = ((string[])objectToPin).GetFlattenedAsciiCodedStringArray(bufferSize); - AddObjectToPin(bytes, objectToPin); + AddObjectToPin(bytes, keyCounter); } else { - AddObjectToPin(objectToPin); + AddObjectToPin(objectToPin, keyCounter); } + + keyCounter++; } } public void UnPinMemory() { - foreach (KeyValuePair valuePair in objectGarbageCollectHandles) + if (objectGarbageCollectHandles.Count <= 0) + { + return; + } + + foreach (KeyValuePair valuePair in objectGarbageCollectHandles) { valuePair.Value.Free(); } @@ -137,13 +145,9 @@ public void UnPinMemory() objectGarbageCollectHandles.Clear(); } - private void AddObjectToPin(object objectToPin, object lookupObject = null) + private void AddObjectToPin(object objectToPin, int key) { - object key = lookupObject ?? objectToPin; - if (!objectGarbageCollectHandles.ContainsKey(key)) - { - objectGarbageCollectHandles.Add(key, GCHandle.Alloc(objectToPin, GCHandleType.Pinned)); - } + objectGarbageCollectHandles.Add(key, GCHandle.Alloc(objectToPin, GCHandleType.Pinned)); } private void ReleaseUnmanagedResources() diff --git a/src/MeshKernelNET/Api/MeshKernelApi.cs b/src/MeshKernelNET/Api/MeshKernelApi.cs index d9074e8..14f3a94 100644 --- a/src/MeshKernelNET/Api/MeshKernelApi.cs +++ b/src/MeshKernelNET/Api/MeshKernelApi.cs @@ -889,7 +889,7 @@ public int Mesh2dGetData(int meshKernelId, out DisposableMesh2D disposableMesh2D newMesh2DNative = disposableMesh2D.CreateNativeObject(); exitCode = MeshKernelDll.Mesh2dGetData(meshKernelId, ref newMesh2DNative); - CreateDisposableMesh2DCached(newMesh2DNative, ref disposableMesh2D, true); + disposableMesh2D = CreateDisposableMesh2DCached(newMesh2DNative, disposableMesh2D, true); return exitCode; } @@ -1315,8 +1315,8 @@ public int Mesh2dTranslate(int meshKernelId, double translationX, double transla /// public int Mesh2dSet(int meshKernelId, in DisposableMesh2D disposableMesh2D) { - MeshKernelStateDictionary[meshKernelId].DisposableMesh2D = disposableMesh2D; - Mesh2DNative mesh2D = disposableMesh2D.CreateNativeObject(); + MeshKernelStateDictionary[meshKernelId].DisposableMesh2D = new DisposableMesh2D(disposableMesh2D); + Mesh2DNative mesh2D = MeshKernelStateDictionary[meshKernelId].DisposableMesh2D.CreateNativeObject(); return MeshKernelDll.Mesh2dSet(meshKernelId, ref mesh2D); } @@ -1445,7 +1445,7 @@ private DisposableMesh2D CreateDisposableMesh2D(Mesh2DNative newMesh2DNative, bo return disposableMesh2D; } - private void CreateDisposableMesh2DCached(Mesh2DNative newMesh2DNative, ref DisposableMesh2D disposableMesh2D, bool addCellInformation = false) + private DisposableMesh2D CreateDisposableMesh2DCached(Mesh2DNative newMesh2DNative, DisposableMesh2D disposableMesh2D, bool addCellInformation = false) { newMesh2DNative.node_x.CreateValueArrayCached(newMesh2DNative.num_nodes, disposableMesh2D.NodeX); @@ -1467,6 +1467,8 @@ private void CreateDisposableMesh2DCached(Mesh2DNative newMesh2DNative, ref Disp newMesh2DNative.face_x.CreateValueArrayCached(newMesh2DNative.num_faces, disposableMesh2D.FaceX); newMesh2DNative.face_y.CreateValueArrayCached(newMesh2DNative.num_faces, disposableMesh2D.FaceY); } + + return disposableMesh2D; } private DisposableMesh1D CreateDisposableMesh1d(Mesh1DNative newMesh1DNative) diff --git a/test/MeshKernelNETTest/Api/MeshKernelTest.cs b/test/MeshKernelNETTest/Api/MeshKernelTest.cs index 3f4313a..2a98e1f 100644 --- a/test/MeshKernelNETTest/Api/MeshKernelTest.cs +++ b/test/MeshKernelNETTest/Api/MeshKernelTest.cs @@ -769,6 +769,7 @@ public void Mesh2dRotateThroughAPI() using (var api = new MeshKernelApi()) { var id = 0; + var meshApi = new DisposableMesh2D(); var meshInitial = new DisposableMesh2D(); var meshRotated = new DisposableMesh2D(); try @@ -778,7 +779,9 @@ public void Mesh2dRotateThroughAPI() Assert.AreEqual(0, api.Mesh2dSet(id, mesh)); // Get data of initial mesh - Assert.AreEqual(0, api.Mesh2dGetData(id, out meshInitial)); + Assert.AreEqual(0, api.Mesh2dGetData(id, out meshApi)); + meshInitial?.Dispose(); + meshInitial = new DisposableMesh2D(meshApi); Assert.IsNotNull(meshInitial); var rotationCentre = new[] { 1.0, 5.0 }; @@ -789,7 +792,9 @@ public void Mesh2dRotateThroughAPI() rotationAngle)); // Get data of rotated mesh - Assert.AreEqual(0, api.Mesh2dGetData(id, out meshRotated)); + Assert.AreEqual(0, api.Mesh2dGetData(id, out meshApi)); + meshRotated?.Dispose(); + meshRotated = new DisposableMesh2D(meshApi); Assert.IsNotNull(meshRotated); // Compute expected nodes @@ -845,6 +850,7 @@ public void Mesh2dTranslateThroughAPI() using (var api = new MeshKernelApi()) { var id = 0; + var meshApi = new DisposableMesh2D(); var meshInitial = new DisposableMesh2D(); var meshTranslated = new DisposableMesh2D(); try @@ -854,7 +860,9 @@ public void Mesh2dTranslateThroughAPI() Assert.AreEqual(0, api.Mesh2dSet(id, mesh)); // Get data of initial mesh - Assert.AreEqual(0, api.Mesh2dGetData(id, out meshInitial)); + Assert.AreEqual(0, api.Mesh2dGetData(id, out meshApi)); + meshInitial?.Dispose(); + meshInitial = new DisposableMesh2D(meshApi); Assert.IsNotNull(meshInitial); var translation = new[] { 1.0, 5.0 }; @@ -863,7 +871,9 @@ public void Mesh2dTranslateThroughAPI() translation[1])); // Get data of rotated mesh - Assert.AreEqual(0, api.Mesh2dGetData(id, out meshTranslated)); + Assert.AreEqual(0, api.Mesh2dGetData(id, out meshApi)); + meshTranslated?.Dispose(); + meshTranslated = new DisposableMesh2D(meshApi); Assert.IsNotNull(meshTranslated); // Compute expected nodes @@ -880,8 +890,9 @@ public void Mesh2dTranslateThroughAPI() finally { api.DeallocateState(id); - meshInitial.Dispose(); - meshTranslated.Dispose(); + meshInitial?.Dispose(); + meshTranslated?.Dispose(); + meshApi?.Dispose(); } } } @@ -1072,8 +1083,9 @@ public void Mesh2dDeleteEdgeByIndexThroughAPI() { id = api.AllocateState(0); - Assert.AreEqual(0, api.Mesh2dSet(id, mesh)); int initNumEdges = mesh.NumEdges; + Assert.AreEqual(0, api.Mesh2dSet(id, mesh)); + // Delete a valid edge Assert.AreEqual(0, api.Mesh2dDeleteEdgeByIndex(id, 0)); @@ -1090,7 +1102,7 @@ public void Mesh2dDeleteEdgeByIndexThroughAPI() finally { api.DeallocateState(id); - meshApi.Dispose(); + meshApi?.Dispose(); } } }