canhorn / EventHorizon.Blazor.TypeScript.Interop.Generator

This project is a Blazor Interop C# Generator, has a sample against the BabylonJS library.
https://wonderful-pond-05f7b3b10.azurestaticapps.net/
MIT License
134 stars 21 forks source link

Slow performance setting `VertexData` properties with large arrays #34

Open limefrogyank opened 3 years ago

limefrogyank commented 3 years ago

There is a huge bottleneck for me right now when I try setting decimal arrays of length > 100,000 on the VertexData object. It's probably due to the JSInterop layer. I took a look at your other project and it seems you use IJSUnmarshalledRuntime only for certain getter scenarios.

I wonder if it would make sense to include a new set function that has an option to use IJSUnmarshalledRuntime to transfer binary data. Maybe this belongs on your other project...

canhorn commented 3 years ago

I can see having a binary transfer abstraction might work here, I would have to do some testing see for sure.

I will create an issue in the other project to track it.

limefrogyank commented 3 years ago

I'm also a little curious if there's a way to make the "default" number type be a double or float instead of a decimal. I haven't dived too deeply into it but using decimal makes the arrays twice as big when they don't need to be. I'm still exploring some of the options for your generator.

Regardless, this is a great project! Thanks

canhorn commented 3 years ago

It has to be decimal sadly, it was not my first choice. This is a JavaScript issue, since JS has no other number types other everything is a decimal. Moving numbers through the interop, using the UnmarshalledRuntime, causes issues typing and it looses its value.

I have spent many hours on this and it is a headache.

limefrogyank commented 3 years ago

This hack seems to be a decent workaround to loading large arrays into BabylonJS. And it works with float arrays, too :) C#

        [Inject] IJSRuntime JSRuntime { get; set; }
        private IJSUnmarshalledRuntime UnmarshalledRuntime;

protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                UnmarshalledRuntime = (IJSUnmarshalledRuntime)JSRuntime;   
            }
        }

------- functions used like this
var r = UnmarshalledRuntime.InvokeUnmarshalled<string, string, float[], bool>("setFast", vertexData.___guid, "positions", data.Positions);
r = UnmarshalledRuntime.InvokeUnmarshalled<string, string, float[], bool>("setFast", vertexData.___guid, "colors", data.Colors);
r = UnmarshalledRuntime.InvokeUnmarshalled<string, string, float[], bool>("setFast", vertexData.___guid, "indices", data.Indices);
r = UnmarshalledRuntime.InvokeUnmarshalled<string, string, float[], bool>("setFast", vertexData.___guid, "uvs", data.ST);
r = UnmarshalledRuntime.InvokeUnmarshalled<string, string, float[], bool>("setFast", vertexData.___guid, "uvs2", data.RowCol);
r = UnmarshalledRuntime.InvokeUnmarshalled<string, string, float[], bool>("setFast", vertexData.___guid, "uvs3", data.Radius);

Typescript:

declare var BINDING: any; //mono functions
declare var Blazor: Blazor;  //blazor object
declare var Module: any;  //emscripten object
declare var blazorInterop: any;

interface Blazor {
    platform: Platform;
}

interface HeapLock {
    release();
}

interface Platform {
    start(resourceLoader: any): Promise<void>;

    callEntryPoint(assemblyName: string): void;

    toUint8Array(array: any): Uint8Array;

    getArrayLength(array: any): number;
    getArrayEntryPtr<TPtr>(array: TPtr, index: number, itemSize: number): TPtr;

    getObjectFieldsBaseAddress(referenceTypedObject: any): any;
    readInt16Field(baseAddress: any, fieldOffset?: number): number;
    readInt32Field(baseAddress: any, fieldOffset?: number): number;
    readUint64Field(baseAddress: any, fieldOffset?: number): number;
    readFloatField(baseAddress: any, fieldOffset?: number): number;
    readObjectField<T>(baseAddress: any, fieldOffset?: number): T;
    readStringField(baseAddress: any, fieldOffset?: number, readBoolValueAsString?: boolean): string | null;
    readStructField<T extends any>(baseAddress: any, fieldOffset?: number): T;

    beginHeapLock(): HeapLock;
    invokeWhenHeapUnlocked(callback: Function): void;
}

function setFast(root, identifier, value) {
    try {
        let rootName = BINDING.conv_string(root);
        let identifierName = BINDING.conv_string(identifier);
        let arr = toFloat32Array(value);

        blazorInterop.set(rootName, identifierName, arr);

    } catch (ex) {
        console.log("error", ex);
    }
}

function toFloat32Array(array: any): Float32Array {
    const dataPtr = Blazor.platform.getArrayEntryPtr(array, 0, 4);
    const length = Blazor.platform.getArrayLength(array);
    return new Float32Array(Module.HEAPF32.buffer, dataPtr, length);
}