EventHorizon.Blazor.TypeScript.Interop.Generator icon indicating copy to clipboard operation
EventHorizon.Blazor.TypeScript.Interop.Generator copied to clipboard

Slow performance setting `VertexData` properties with large arrays

Open limefrogyank opened this issue 4 years ago • 4 comments

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...

limefrogyank avatar Jan 28 '21 19:01 limefrogyank

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.

canhorn avatar Jan 28 '21 23:01 canhorn

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

limefrogyank avatar Jan 28 '21 23:01 limefrogyank

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.

canhorn avatar Jan 28 '21 23:01 canhorn

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);
}

limefrogyank avatar Jan 29 '21 01:01 limefrogyank