elringus / bootsharp

Compile C# solution into single-file ES module with auto-generated JavaScript bindings and type definitions

Home Page:https://bootsharp.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Marshal via arrays

elringus opened this issue · comments

Instead of JSON, explore marshaling via arrays, similar to Embind (https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#value-types), eg:

public record Record (string Str, int Int, bool Bool, Record? Other);

[JSExport]
private static void ReceiveRecord ([JSMarshalAs<JSType.Any>] object raw)
{
    var record = Unmarshal((object?[])raw);
    Console.WriteLine($"Record({record.Str}, {record.Int}, {record.Bool}, Record({record.Other?.Str},
        {record.Other?.Int}, {record.Other?.Bool}))");

    static Record Unmarshal (object?[] raw) => new(
        (string)raw[0]!,
        (int)(double)raw[1]!,
        (bool)raw[2]!,
        raw[3] != null ? Unmarshal(raw[3..7]) : null
    );
}
exports.Program.ReceiveRecord(["foo", 1, true, "bar", 2, false, null]);

And the other way around:

[JSExport] [return: JSMarshalAs<JSType.Any>]
private static object GetRecord ()
{
    return new object?[] { "foo", 5, true, new object?[] { "bar", 6, false, null } };
}

— returns in JavaScript: ["foo",5,true,["bar",6,false,null]].


Should probably be postponed until #138 is solved; otherwise, we'll break interop with tasks of custom data types, that are currently serialized to JSON.
We can actually send arrays as objects:

[JSExport] [return: JSMarshalAs<JSType.Promise<JSType.Any>>]
private static async Task<object> GetRecordAsync ()
{
    await Task.Delay(1);
    return new object?[] { "foo", 7, true, new object?[] { "bar", 8, false, null } };
}

— the task value is transferred to JavaScript as ["foo",7,true,["bar",8,false,null]].

Implemented in https://github.com/elringus/bootsharp/tree/arch/marshal-via-arrays.

After testing, it doesn't seem worth it, as perf and min build size are roughly the same, while it adds more pressure to GC due to array allocations and it's not clear whether it's possible to support recursive types marshaling. It also adds limitations on the init scenarios of the marshaled types, eg it's not possible to use constructor overloads, as we're not storing parameter names.