diff --git a/MEOS.NET.Tests/ScalarArrayTests.cs b/MEOS.NET.Tests/ScalarArrayTests.cs new file mode 100644 index 0000000..b80e2e0 --- /dev/null +++ b/MEOS.NET.Tests/ScalarArrayTests.cs @@ -0,0 +1,37 @@ +using MEOS.NET.Types; + +namespace MEOS.NET.Tests +{ + /// + /// An array of scalars is read at the scalar's own width. Reading one as an + /// array of pointers walks it eight bytes at a step, which runs off the end + /// of a bool array and answers the values' own bytes as addresses. + /// + [TestClass] + public class ScalarArrayTests : MeosTest + { + [TestMethod] + public void ATemporalBooleanAnswersItsValuesAsBooleans() + { + TBool temp = (TBool)TBool.In( + "{true@2024-12-06, false@2024-12-07, true@2024-12-08}")!; + + bool[] values = temp.Values(); + + Assert.AreEqual(2, values.Length); + CollectionAssert.AreEquivalent(new[] { false, true }, values); + } + + [TestMethod] + public void ATemporalFloatAnswersItsValuesAsDoubles() + { + TFloat temp = (TFloat)TFloat.In( + "{1.5@2024-12-06, 2.5@2024-12-07}")!; + + double[] values = temp.Values(); + + Assert.AreEqual(2, values.Length); + CollectionAssert.AreEquivalent(new[] { 1.5, 2.5 }, values); + } + } +} diff --git a/MEOS.NET/Functions/Meos.meos.g.cs b/MEOS.NET/Functions/Meos.meos.g.cs index f6ded9e..757a524 100644 --- a/MEOS.NET/Functions/Meos.meos.g.cs +++ b/MEOS.NET/Functions/Meos.meos.g.cs @@ -2905,16 +2905,16 @@ public static bool TboolValueAtTimestamptz(IntPtr temp, long t, bool strict, Int public static bool TboolValueN(IntPtr temp, int n, IntPtr result) => SafeExecution(() => Native.TboolValueN(temp, n, result)); - public static IntPtr[] TboolValues(IntPtr temp) + public static bool[] TboolValues(IntPtr temp) { IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { IntPtr _p = SafeExecution(() => Native.TboolValues(temp, _cnt)); int _n = Marshal.ReadInt32(_cnt); - IntPtr[] _out = new IntPtr[_n]; + bool[] _out = new bool[_n]; for (int _i = 0; _i < _n; _i++) - { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + { _out[_i] = Marshal.ReadByte(_p, _i) != 0; } return _out; } finally { Marshal.FreeHGlobal(_cnt); } diff --git a/MEOS.NET/Functions/Meos.meos_geo.g.cs b/MEOS.NET/Functions/Meos.meos_geo.g.cs index 8a08333..d56ab8a 100644 --- a/MEOS.NET/Functions/Meos.meos_geo.g.cs +++ b/MEOS.NET/Functions/Meos.meos_geo.g.cs @@ -1736,16 +1736,16 @@ public static int[] GeoClusterKmeans(IntPtr geoms, uint ngeoms, uint k) finally { Marshal.FreeHGlobal(_cnt); } } - public static IntPtr[] GeoClusterDbscan(IntPtr geoms, uint ngeoms, double tolerance, int minpoints) + public static uint[] GeoClusterDbscan(IntPtr geoms, uint ngeoms, double tolerance, int minpoints) { IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { IntPtr _p = SafeExecution(() => Native.GeoClusterDbscan(geoms, ngeoms, tolerance, minpoints, _cnt)); int _n = Marshal.ReadInt32(_cnt); - IntPtr[] _out = new IntPtr[_n]; + uint[] _out = new uint[_n]; for (int _i = 0; _i < _n; _i++) - { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + { _out[_i] = (uint) Marshal.ReadInt32(_p, _i * 4); } return _out; } finally { Marshal.FreeHGlobal(_cnt); } diff --git a/MEOS.NET/Functions/Meos.meos_h3.g.cs b/MEOS.NET/Functions/Meos.meos_h3.g.cs index 3d047f9..729cb82 100644 --- a/MEOS.NET/Functions/Meos.meos_h3.g.cs +++ b/MEOS.NET/Functions/Meos.meos_h3.g.cs @@ -135,16 +135,16 @@ public static ulong Th3indexEndValue(IntPtr temp) public static bool Th3indexValueN(IntPtr temp, int n, IntPtr result) => SafeExecution(() => Native.Th3indexValueN(temp, n, result)); - public static IntPtr[] Th3indexValues(IntPtr temp) + public static ulong[] Th3indexValues(IntPtr temp) { IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { IntPtr _p = SafeExecution(() => Native.Th3indexValues(temp, _cnt)); int _n = Marshal.ReadInt32(_cnt); - IntPtr[] _out = new IntPtr[_n]; + ulong[] _out = new ulong[_n]; for (int _i = 0; _i < _n; _i++) - { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + { _out[_i] = (ulong) Marshal.ReadInt64(_p, _i * 8); } return _out; } finally { Marshal.FreeHGlobal(_cnt); } diff --git a/MEOS.NET/Functions/Meos.meos_json.g.cs b/MEOS.NET/Functions/Meos.meos_json.g.cs index 10f7a4c..27542e7 100644 --- a/MEOS.NET/Functions/Meos.meos_json.g.cs +++ b/MEOS.NET/Functions/Meos.meos_json.g.cs @@ -462,16 +462,16 @@ public static IntPtr JsonbsetDelete(IntPtr set, IntPtr key) public static IntPtr JsonbsetDeleteArray(IntPtr set, IntPtr keys, int count) => SafeExecution(() => Native.JsonbsetDeleteArray(set, keys, count)); - public static IntPtr[] JsonbsetExists(IntPtr set, IntPtr key) + public static bool[] JsonbsetExists(IntPtr set, IntPtr key) { IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { IntPtr _p = SafeExecution(() => Native.JsonbsetExists(set, key, _cnt)); int _n = Marshal.ReadInt32(_cnt); - IntPtr[] _out = new IntPtr[_n]; + bool[] _out = new bool[_n]; for (int _i = 0; _i < _n; _i++) - { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + { _out[_i] = Marshal.ReadByte(_p, _i) != 0; } return _out; } finally { Marshal.FreeHGlobal(_cnt); } @@ -513,31 +513,31 @@ public static IntPtr JsonbsetExtractPath(IntPtr set, IntPtr path_elems, int path public static IntPtr JsonbsetInsert(IntPtr set, IntPtr path_elems, int path_len, IntPtr newjb, bool after) => SafeExecution(() => Native.JsonbsetInsert(set, path_elems, path_len, newjb, after)); - public static IntPtr[] JsonbsetPathExists(IntPtr set, IntPtr jp, IntPtr vars, bool silent, bool tz) + public static bool[] JsonbsetPathExists(IntPtr set, IntPtr jp, IntPtr vars, bool silent, bool tz) { IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { IntPtr _p = SafeExecution(() => Native.JsonbsetPathExists(set, jp, vars, silent, tz, _cnt)); int _n = Marshal.ReadInt32(_cnt); - IntPtr[] _out = new IntPtr[_n]; + bool[] _out = new bool[_n]; for (int _i = 0; _i < _n; _i++) - { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + { _out[_i] = Marshal.ReadByte(_p, _i) != 0; } return _out; } finally { Marshal.FreeHGlobal(_cnt); } } - public static IntPtr[] JsonbsetPathMatch(IntPtr set, IntPtr jp, IntPtr vars, bool silent, bool tz) + public static bool[] JsonbsetPathMatch(IntPtr set, IntPtr jp, IntPtr vars, bool silent, bool tz) { IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { IntPtr _p = SafeExecution(() => Native.JsonbsetPathMatch(set, jp, vars, silent, tz, _cnt)); int _n = Marshal.ReadInt32(_cnt); - IntPtr[] _out = new IntPtr[_n]; + bool[] _out = new bool[_n]; for (int _i = 0; _i < _n; _i++) - { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + { _out[_i] = Marshal.ReadByte(_p, _i) != 0; } return _out; } finally { Marshal.FreeHGlobal(_cnt); } diff --git a/MEOS.NET/Functions/Meos.meos_quadbin.g.cs b/MEOS.NET/Functions/Meos.meos_quadbin.g.cs index 22e4723..43c2806 100644 --- a/MEOS.NET/Functions/Meos.meos_quadbin.g.cs +++ b/MEOS.NET/Functions/Meos.meos_quadbin.g.cs @@ -57,16 +57,16 @@ public static uint QuadbinGetResolution(ulong cell) public static ulong QuadbinCellToParent(ulong cell, uint parent_resolution) => SafeExecution(() => Native.QuadbinCellToParent(cell, parent_resolution)); - public static IntPtr[] QuadbinCellToChildren(ulong cell, uint children_resolution) + public static ulong[] QuadbinCellToChildren(ulong cell, uint children_resolution) { IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { IntPtr _p = SafeExecution(() => Native.QuadbinCellToChildren(cell, children_resolution, _cnt)); int _n = Marshal.ReadInt32(_cnt); - IntPtr[] _out = new IntPtr[_n]; + ulong[] _out = new ulong[_n]; for (int _i = 0; _i < _n; _i++) - { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + { _out[_i] = (ulong) Marshal.ReadInt64(_p, _i * 8); } return _out; } finally { Marshal.FreeHGlobal(_cnt); } @@ -75,16 +75,16 @@ public static IntPtr[] QuadbinCellToChildren(ulong cell, uint children_resolutio public static ulong QuadbinCellSibling(ulong cell, string direction) => SafeExecution(() => Native.QuadbinCellSibling(cell, direction)); - public static IntPtr[] QuadbinKRing(ulong cell, int k) + public static ulong[] QuadbinKRing(ulong cell, int k) { IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { IntPtr _p = SafeExecution(() => Native.QuadbinKRing(cell, k, _cnt)); int _n = Marshal.ReadInt32(_cnt); - IntPtr[] _out = new IntPtr[_n]; + ulong[] _out = new ulong[_n]; for (int _i = 0; _i < _n; _i++) - { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + { _out[_i] = (ulong) Marshal.ReadInt64(_p, _i * 8); } return _out; } finally { Marshal.FreeHGlobal(_cnt); } @@ -162,16 +162,16 @@ public static ulong TquadbinEndValue(IntPtr temp) public static bool TquadbinValueN(IntPtr temp, int n, IntPtr result) => SafeExecution(() => Native.TquadbinValueN(temp, n, result)); - public static IntPtr[] TquadbinValues(IntPtr temp) + public static ulong[] TquadbinValues(IntPtr temp) { IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { IntPtr _p = SafeExecution(() => Native.TquadbinValues(temp, _cnt)); int _n = Marshal.ReadInt32(_cnt); - IntPtr[] _out = new IntPtr[_n]; + ulong[] _out = new ulong[_n]; for (int _i = 0; _i < _n; _i++) - { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + { _out[_i] = (ulong) Marshal.ReadInt64(_p, _i * 8); } return _out; } finally { Marshal.FreeHGlobal(_cnt); } diff --git a/MEOS.NET/Functions/Meos.meos_raster.g.cs b/MEOS.NET/Functions/Meos.meos_raster.g.cs index d585ecd..5fdcb90 100644 --- a/MEOS.NET/Functions/Meos.meos_raster.g.cs +++ b/MEOS.NET/Functions/Meos.meos_raster.g.cs @@ -147,16 +147,16 @@ public static IntPtr RasterTileValue(IntPtr traj, IntPtr rq) public static IntPtr RasterTileValueArray(IntPtr traj, IntPtr rqarr, int count) => SafeExecution(() => Native.RasterTileValueArray(traj, rqarr, count)); - public static IntPtr[] TrajectoryQuadbins(IntPtr traj, uint zoom) + public static ulong[] TrajectoryQuadbins(IntPtr traj, uint zoom) { IntPtr _cnt = Marshal.AllocHGlobal(sizeof(int)); try { IntPtr _p = SafeExecution(() => Native.TrajectoryQuadbins(traj, zoom, _cnt)); int _n = Marshal.ReadInt32(_cnt); - IntPtr[] _out = new IntPtr[_n]; + ulong[] _out = new ulong[_n]; for (int _i = 0; _i < _n; _i++) - { _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); } + { _out[_i] = (ulong) Marshal.ReadInt64(_p, _i * 8); } return _out; } finally { Marshal.FreeHGlobal(_cnt); } diff --git a/MEOS.NET/Types/Geo.g.cs b/MEOS.NET/Types/Geo.g.cs index 6993294..266d0e7 100644 --- a/MEOS.NET/Types/Geo.g.cs +++ b/MEOS.NET/Types/Geo.g.cs @@ -114,6 +114,25 @@ public ulong ToS2cellCell(int level) public STBox? TstzspanToStbox(Span s) => MEOSFactory.WrapSTBox(Meos.GeoTstzspanToStbox(this.Ptr, s.Ptr)); + public static uint[] ClusterDbscan(Geo[] geoms, double tolerance, int minpoints) + { + IntPtr[] _geomsValues = new IntPtr[geoms.Length]; + for (int i = 0; i < geoms.Length; i++) + { + _geomsValues[i] = geoms[i].Ptr; + } + + GCHandle _geoms = GCHandle.Alloc(_geomsValues, GCHandleType.Pinned); + try + { + return Meos.GeoClusterDbscan(_geoms.AddrOfPinnedObject(), (uint) geoms.Length, tolerance, minpoints); + } + finally + { + _geoms.Free(); + } + } + public static Geo?[] ClusterIntersecting(Geo[] geoms) { IntPtr[] _geomsValues = new IntPtr[geoms.Length]; diff --git a/MEOS.NET/Types/TBool.g.cs b/MEOS.NET/Types/TBool.g.cs index c75c7db..f1e9e2e 100644 --- a/MEOS.NET/Types/TBool.g.cs +++ b/MEOS.NET/Types/TBool.g.cs @@ -72,6 +72,9 @@ public bool StartValue() } } + public bool[] Values() + => Meos.TboolValues(this.Ptr); + public SpanSet? WhenTrue() => MEOSFactory.WrapSpanSet(Meos.TboolWhenTrue(this.Ptr)); diff --git a/tools/codegen.py b/tools/codegen.py index 39222bb..a954f0c 100755 --- a/tools/codegen.py +++ b/tools/codegen.py @@ -433,6 +433,20 @@ def gen_external_functions(funcs: list[dict]) -> str: "short": "short", } +# How to read one element of a scalar array `Marshal.Copy` has no overload for. +# `{0}` is the array's base pointer and `{1}` the byte offset of the element. +# Reading such an array as an array of POINTERS walks it at eight bytes a step, +# which runs off the end of a `bool` array and answers addresses that are the +# values' own bytes. +_SCALAR_ELEM_READER: dict[str, tuple[int, str]] = { + "bool": (1, "Marshal.ReadByte({0}, {1}) != 0"), + "sbyte": (1, "(sbyte) Marshal.ReadByte({0}, {1})"), + "ushort": (2, "(ushort) Marshal.ReadInt16({0}, {1})"), + "uint": (4, "(uint) Marshal.ReadInt32({0}, {1})"), + "ulong": (8, "(ulong) Marshal.ReadInt64({0}, {1})"), + "float": (4, "BitConverter.Int32BitsToSingle(Marshal.ReadInt32({0}, {1}))"), +} + def _strip_const_stars(c_type: str) -> tuple[str, int]: s = c_type.replace("const ", "").strip() @@ -462,6 +476,8 @@ def _csharp_array_element(c_type: str, canonical: str) -> tuple[str, str]: return (elem, "Marshal.Copy") if base == "uint8_t": return ("byte", "ByteBuffer") + if elem in _SCALAR_ELEM_READER: + return (elem, f"ScalarArray:{elem}") if stars == 1 and base in STRUCTS: # A single pointer to a catalog struct is an array of struct VALUES, not # of pointers: element i sits at the struct's own stride, and reading it @@ -567,6 +583,12 @@ def _emit_outputs_wrapper(f: dict) -> list[str]: stride = ret_strategy.split(":", 1)[1] lines.append(" for (int _i = 0; _i < _n; _i++)") lines.append(f" {{ _resultArr[_i] = IntPtr.Add(_resultPtr, _i * {stride}); }}") + elif ret_strategy.startswith("ScalarArray:"): + size, reader = _SCALAR_ELEM_READER[ret_strategy.split(":", 1)[1]] + offset = "_i" if size == 1 else f"_i * {size}" + lines.append(" for (int _i = 0; _i < _n; _i++)") + lines.append(f" {{ _resultArr[_i] = " + f"{reader.format('_resultPtr', offset)}; }}") else: lines.append(" for (int _i = 0; _i < _n; _i++)") lines.append(" { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); }") @@ -577,6 +599,12 @@ def _emit_outputs_wrapper(f: dict) -> list[str]: stride = strategy.split(":", 1)[1] lines.append(f" for (int _i = 0; _i < _n; _i++)") lines.append(f" {{ _{local}_out[_i] = IntPtr.Add(_{local}_arr, _i * {stride}); }}") + elif strategy.startswith("ScalarArray:"): + size, reader = _SCALAR_ELEM_READER[strategy.split(":", 1)[1]] + offset = "_i" if size == 1 else f"_i * {size}" + lines.append(f" for (int _i = 0; _i < _n; _i++)") + lines.append(f" {{ _{local}_out[_i] = " + f"{reader.format(f'_{local}_arr', offset)}; }}") elif elem == "IntPtr": lines.append(f" for (int _i = 0; _i < _n; _i++)") lines.append(f" {{ _{local}_out[_i] = Marshal.ReadIntPtr(_{local}_arr, _i * IntPtr.Size); }}") @@ -623,6 +651,11 @@ def _copy(indent: str) -> list[str]: stride = strategy.split(":", 1)[1] out.append(f"{indent}for (int _i = 0; _i < _n; _i++)") out.append(f"{indent}{{ _out[_i] = IntPtr.Add(_p, _i * {stride}); }}") + elif strategy.startswith("ScalarArray:"): + size, reader = _SCALAR_ELEM_READER[strategy.split(":", 1)[1]] + offset = "_i" if size == 1 else f"_i * {size}" + out.append(f"{indent}for (int _i = 0; _i < _n; _i++)") + out.append(f"{indent}{{ _out[_i] = {reader.format('_p', offset)}; }}") else: out.append(f"{indent}for (int _i = 0; _i < _n; _i++)") out.append(f"{indent}{{ _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); }}") diff --git a/tools/objectgen.py b/tools/objectgen.py index 1fc3856..2d5ed65 100644 --- a/tools/objectgen.py +++ b/tools/objectgen.py @@ -336,7 +336,9 @@ def map_return(self, f: dict, wrapper_ret: str) -> tuple[str, str] | None: elem = self.m.class_for_ctype(c[:-1] if c.endswith(" **") else c) if elem: return (f"{elem}?[]", f"MEOSFactory.Wrap{elem}Array($)") - if wrapper_ret in ("long[]", "int[]", "double[]", "byte[]"): + # An array of scalars is handed on as it stands, whichever scalar it is + # — the wrapper has already read it at the element's own width. + if wrapper_ret.endswith("[]") and is_scalar(wrapper_ret[:-2]): return (wrapper_ret, "$") if c == "void *" and wrapper_ret == "IntPtr": return ("IntPtr", "$")