rdp icon indicating copy to clipboard operation
rdp copied to clipboard

C# Api

Open AiwendilsCode opened this issue 10 months ago • 1 comments

I am trying to implement this Rust implementation into c# so far I created FFIArray which holds data to simplify as well as simplified data returned from Rust.

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct FfiArray
{
  public required nint data;
  public required nuint len;
}

I am converting float[][] array into nint and vice versa using these methods.

public static nint CopyDataFromFloatJaggedArrayToIntPtr(float[][] jaggedArray)
{
    int length = jaggedArray.Length;

    double[] joinedArray = jaggedArray.Select(coords =>
    {
        var x = BitConverter.GetBytes(coords[0]);
        var y = BitConverter.GetBytes(coords[1]);
        return BitConverter.ToDouble([..x, ..y], 0);
    }).ToArray();

    nint arrayPtr = Marshal.AllocHGlobal(jaggedArray.Length * sizeof(double));

    Marshal.Copy(joinedArray, 0, arrayPtr, joinedArray.Length);

    return arrayPtr;
}

public static float[][] CopyDataFromIntPtrToFloatJaggedArray(nint ptr, int length, int innerLength = 2)
{
    float[][] jaggedArray = new float[length][];

    for (int i = 0; i < length; i++)
    {
        byte[] x = new byte[4];
        byte[] y = new byte[4];

        Marshal.Copy(ptr + (i * 8), x, 0, 4);
        Marshal.Copy(ptr + (i * 8 + 4), y, 0, 4);

        jaggedArray[i] = [BitConverter.ToSingle(x), BitConverter.ToSingle(y)];
    }

    return jaggedArray;
}

public static void FreeIntPtr(nint ptr, int length)
{
    for (int i = 0; i < length; i++)
    {
        nint innerPtr = Marshal.ReadIntPtr(ptr, i * nint.Size);
        Marshal.FreeHGlobal(innerPtr);
    }

    Marshal.FreeHGlobal(ptr);
}

Rust bindings from DLL:

internal class RustBindings
{
    [DllImport("RustSimplificationAlgorithms.dll")]
    public static extern FfiArray simplify_rdp_ffi(FfiArray array, double precision);
    [DllImport("RustSimplificationAlgorithms.dll")]
    public static extern FfiArray simplify_rdp_idx_ffi(FfiArray array, double precision);
    [DllImport("RustSimplificationAlgorithms.dll")]
    public static extern FfiArray simplify_visvalingam_ffi(FfiArray array, double precision);
    [DllImport("RustSimplificationAlgorithms.dll")]
    public static extern FfiArray simplify_visvalingam_idx_ffi(FfiArray array, double precision);
    [DllImport("RustSimplificationAlgorithms.dll")]
    public static extern FfiArray simplify_visvalingamp_ffi(FfiArray array, double precision);
    [DllImport("RustSimplificationAlgorithms.dll")]
    public static extern void drop_float_array(FfiArray ptr);
    [DllImport("RustSimplificationAlgorithms.dll")]
    public static extern void drop_usize_array(FfiArray ptr);
}

And Public Api looks like this (for testing purposes I am trying to implement only RDP algorithm).

public static float[][] Simplify(float[][] data, double tolerance)
{
    FfiArray? dataInArray = null;
    FfiArray? simplified = null;

    try
    {
        nint dataForSimplification = MarshalOperations.CopyDataFromFloatJaggedArrayToIntPtr(data);

        dataInArray = new FfiArray()
        {
            data = dataForSimplification,
            len = (nuint)data.Length
        };

        simplified = RustBindings.simplify_rdp_ffi((FfiArray)dataInArray, tolerance);

        float[][] result = MarshalOperations.CopyDataFromIntPtrToFloatJaggedArray(((FfiArray)simplified).data, (int)((FfiArray)simplified).len);

        return result;
    }
    finally
    {

        if (dataInArray is not null)
            MarshalOperations.FreeIntPtr(((FfiArray)dataInArray).data, (int)((FfiArray)dataInArray).len);

        if (simplified is not null)
            RustBindings.drop_float_array((FfiArray)simplified!);

        Console.WriteLine("memory freed");
    }
}

My problem is that the simplification algorithm always returns different values despite the same values being provided. For checking I am using the Python library which is part of this repository. According to that, I am expecting 26 values but I got only 15 at best. Also when I check the returned values only the first two are valid and the others are junk.

I expect this problem is in bad memory handling.

string[] lines = File.ReadAllLines("generatedValuesSin.csv");

float[][] data = new float[lines.Length - 1][];

for (int i = 1; i < lines.Length; i++) // skip first line
{
    data[i - 1] = [(i - 1) * 3600, float.Parse(lines[i], NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture)];
}

float[][] simplified = RdpAlgorithm.Simplify(data, 0.15);

Source code repository: https://github.com/AiwendilsCode/Simplification.NET Python testing code: https://github.com/AiwendilsCode/Simplification.NET/tree/master/Python

As a testing data I am using generated Sin values.

AiwendilsCode avatar Mar 28 '24 16:03 AiwendilsCode

I'm not a C# user, so I can't help you with this.

urschrei avatar Mar 28 '24 17:03 urschrei

I successfully implemented a wrapper for C# here.

AiwendilsCode avatar Jun 05 '24 16:06 AiwendilsCode