Skip to content

Performance issue with ShoudRender functionality in WASM #26696

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
KameshRajendran opened this issue Oct 8, 2020 · 4 comments
Closed

Performance issue with ShoudRender functionality in WASM #26696

KameshRajendran opened this issue Oct 8, 2020 · 4 comments
Labels
area-blazor Includes: Blazor, Razor Components feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. Status: Resolved

Comments

@KameshRajendran
Copy link

KameshRajendran commented Oct 8, 2020

Describe the bug

In order to avoid re-rendering of the child components, we have used the ShouldRender method. Even though we use this override method , we could not observe performance improvements.

To Reproduce

Index.razor is supposed to be the sample code. As child content, we have rendered 1000 counts of Node components

 <Canvas @ref="@canvas" Width="1500" Height="700">
    @for (int i = 0; i < 1000; i++)
    {
        var id = "Node" + i.ToString();
        var x = 10 + i * 40;
        var y = (float)(260 + 290 * Math.Sin(Math.PI * i / 22.5));
        <Node Id="@id" X="@x" Y="@y" Width="40" Height="40"></Node>
    }
</Canvas>
 
@code{
    Canvas canvas;
}

 
  1. We've the parent Canvas.Razor component. We have rendered more child components (Node) as a child content of Parent (Canvas) component and we also have some interactive drag events to drag the particular(Node) child component. We've only allowed rendering for the Node component in the drag state, remaining Node components not allowed to re-render. They've disabled ShouldRender once drag end. Please find the below code snippets.

[Canvas.Razor]

@page "/canvas"

@using System.ComponentModel
@using System.Text.Json;
@using System.Text.Json.Serialization;
@using System.Drawing;
@using ShouldRenderWebAppTest.Pages;

@inherits ComponentBase;

<h3>Canvas</h3>
    <div id="canvas" @onmousedown="@OnMouseDown" @onmousemove="@OnMouseMove" @onmouseup="@OnMouseUp" style="width: 100%;height: 700px;position:relative;width:100%; height:700px;position:relative;overflow:hidden;background:#ededed">
        <svg id="canvas_layer" width="@Width" height="@Height" xmlns="http://www.w3.org/2000/svg">
            <g>
                <g id="canvas_layer" style="pointer-events: all;" transform="translate(0,0),scale(1)">
                    <CascadingValue Value="@this">
                        @ChildContent
                    </CascadingValue>
                </g>
            </g>
        </svg>
    </div>

@code
{
    internal Node draggingNode = null;
    internal float diagramStartX;
    internal float diagramStartY;
    internal bool inAction { get; set; }
    internal float previousMousePointX { get; set; }
    internal float previousMousePointY { get; set; }

    [Parameter]
    public float Width { get; set; }

    [Parameter]
    public float Height { get; set; }


    [Inject]
    protected IJSRuntime jsRuntime { get; set; }

    [Parameter]
    [JsonIgnore]
    public RenderFragment ChildContent { get; set; }

    public List<Node> Nodes = new List<Node>();

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();

        CalculateDiagramNodeBounds(null);
    }

    public async void OnMouseDown(MouseEventArgs args)
    {
        if (diagramStartX == 0 && diagramStartY == 0)
        {
            PointF size = await jsRuntime.InvokeAsync<PointF>("getDiagramSize").ConfigureAwait(true);
            diagramStartX = size.X;
            diagramStartY = size.Y;
        }
        float mousePointX = (float)(args.ClientX - diagramStartX);
        float mousePointY = (float)(args.ClientY - diagramStartY);
        for (int i = 0; i < Nodes.Count; i++)
        {
            Node Node = Nodes[i];
            CalculateDiagramNodeBounds(Node);
            bool isInside = Node.bounds.Contains(mousePointX, mousePointY);
            Node._ShouldRender = false;
            if (isInside)
            {
                draggingNode = Node;
                draggingNode._ShouldRender = true;
                inAction = true;
                previousMousePointX = mousePointX; previousMousePointY = mousePointY;
                StateHasChanged();
                break;
            }
        }
    }
    public void OnMouseMove(MouseEventArgs args)
    {
        if (inAction)
        {
            float mousePointX = (float)(args.ClientX - diagramStartX);
            float mousePointY = (float)(args.ClientY - diagramStartY);
            float tx = mousePointX - previousMousePointX;
            float ty = mousePointY - previousMousePointY;
            this.draggingNode.DragNode(draggingNode, tx, ty);
            previousMousePointX = mousePointX; previousMousePointY = mousePointY;
            StateHasChanged();
        }
    }
    public void OnMouseUp(MouseEventArgs args)
    {
        float mousePointX = (float)(args.ClientX - diagramStartX);
        float mousePointY = (float)(args.ClientY - diagramStartY);
        previousMousePointX = mousePointX; previousMousePointY = mousePointY;
        if (inAction)
        {
            inAction = false;
            draggingNode._ShouldRender = false;
            draggingNode = null;
            StateHasChanged();
        }
    }
    public void CalculateDiagramNodeBounds(Node node)
    {
        if (node != null)
        {
            node.bounds = new RectangleF() { X = node.X, Y = node.Y, Width = node.Width, Height = node.Height };
            return;
        }
        for (var i = 0; i < this.Nodes.Count; i++)
        {
            Node Node = this.Nodes[i];
            Node.bounds = new RectangleF() { X = Node.X, Y = Node.Y, Width = Node.Width, Height = Node.Height };
        }
    }
}

  1. We have disabled ShouldRender in the Node component by default and allowed the ShouldRender option on the parent element for a specific child component. The remaining Node components are disabled in the ShouldRender State. So only one Node Component enabled ShouldRender while dragging. Please find the code snippets we have used.

[Node.razor]


@using Microsoft.AspNetCore.Components;
@using System.Drawing;

@inherits ComponentBase;

@{
    <g id="node_element" xmlns="http://www.w3.org/2000/svg">
        <rect id="@Id" x="@X" y="@Y" width="@Width" height="@Height" fill="blue" stroke-width="5"></rect>
    </g>
}

@code {
    private List<string> directParamKeys { get; set; } = new List<string>();
    internal Dictionary<string, object> DirectParameters { get; set; } = new Dictionary<string, object>();

    [CascadingParameter]
    internal Canvas BaseParent { get; set; }
    internal RectangleF bounds { get; set; }
    [Parameter]
    public string Id
    {
        get; set;
    }
    private string _id;
    [Parameter]
    public float X
    {
        get; set;
    }
    private float _x;
    [Parameter]
    public float Y
    {
        get; set;
    }
    private float _y;
    [Parameter]
    public float Width
    {
        get; set;
    }
    private float _width;
    [Parameter]
    public float Height
    {
        get; set;
    }
    private float _height;


    public void DragNode(Node Node, float tx, float ty)
    {
        this._x += tx;
        this._y += ty;
    }
    public bool _ShouldRender = false;
    protected override bool ShouldRender()
    {
        return _ShouldRender;
    }
    protected override void OnParametersSet()
    {
        base.OnParametersSet();
        if (this._x != 0 && this.X != this._x)
            DirectParameters["X"] = this.X = this._x;
        if (this._y != 0 && this.Y != this._y)
            DirectParameters["Y"] = this.Y = this._y;
    }
    public override Task SetParametersAsync(ParameterView parameters)
    {
        parameters.SetParameterProperties(this);
        if (this.directParamKeys.Count == 0)
        {
            foreach (var parameter in parameters)
            {
                if (!parameter.Cascading)
                {
                    directParamKeys.Add(parameter.Name);
                }
            }
        }
        return base.SetParametersAsync(parameters);
    }

    protected override void OnInitialized()
    {
        if (BaseParent != null)
        {
            (this.BaseParent.Nodes as List<Node>).Add(this);
            _x = X;
            _y = Y;
            _width = Width;
            _height = Height;
        }
        StateHasChanged();
    }
}

We could not see the expected dragging performance from the above sample, although only one child component with re-render permission and the remaining child components is not permitted to re-render while mouse drag interaction.

Expected Behavior

We have allowed only one child component to re-render with each mouse move, and other Node components are not allowed to re-render. Therefore, in this case, the dragging should be smooth in performance during interaction.

Further technical details

We have hosted three samples with combinations of 1000 , 5000, 10000 elements. Sample having 5000, 10000 elements shows very performance issues.

Sample link : https://github.com/Naganathan/ShouldRenderWasm/tree/master/ShouldRenderWebAppTest

Hosted Link : https://naganathan.github.io/ShouldRenderWebAppHosted/

Replication Steps :

  1. Click on the hosted link click and drag any rectangle
  2. Click on the 5000Nodes sample , click and drag any rectangle
  3. Click on the 10000Nodes sample , click and drag any rectangle

Note :

  1. We have tried the same approach with Server application too. But we could not able to get reasonable performance in server application too.

Could you please check the sample and hosted link and provide a suggestion to get smooth dragging of child component?

@KameshRajendran KameshRajendran changed the title Facing Performance issue with ShoudRender functionality in WASM Performance issue with ShoudRender functionality in WASM Oct 8, 2020
@mkArtakMSFT mkArtakMSFT added area-blazor Includes: Blazor, Razor Components feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly labels Oct 8, 2020
@mkArtakMSFT
Copy link
Contributor

Thanks for contacting us. There are some definite things in this code which do not follow our perf best practices.
The guidance is currently in PR and will be merged soon, but you can read it here already: https://github.com/dotnet/AspNetCore.Docs/blob/ddc899a36770fca83cff263244df7523cea2aeb6/aspnetcore/blazor/webassembly-performance-best-practices.md

Please follow the guidance provided and if you happen to still face performance issues let us know.

@mkArtakMSFT mkArtakMSFT added the ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. label Oct 8, 2020
@ghost ghost added the Status: Resolved label Oct 8, 2020
@Naganathan
Copy link

Hi @mkArtakMSFT ,

We tried with Virtualize and RenderFragment for our scenarios but still, issues occur if we render more elements like 1000, 5000 and 10000 in the web-assembly sample.

For Virtualize, we have created a separate issue, because we are using <svg> element for rendering our component. While using <Virtualize> tag the component is not rendered.

<Virtualize> issue

For Renderfragment Rendering : The lagging issue occurred if we try to render the more elements like 1000, 5000 and more. The issue will be occurred for both Server and WebAssembly sample for us. Could you please suggest for this issue?

Sample file : Canvas.zip

@ghost
Copy link

ghost commented Oct 10, 2020

This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.

See our Issue Management Policies for more information.

@ghost ghost closed this as completed Oct 10, 2020
@Naganathan
Copy link

Naganathan commented Oct 12, 2020

Task has closed automatically before we get a solution from your end.

As per your Guidance, we used a RenderFragment concept but still the lagging issue occurred while using 1000 for nodes.

please refer to the below attached video and modified sample file.

Video link: Video

Sample file : Sample

In the above sample, we have rendered the node only when dragging and others are not re-rendered. Please refer to the below code example which we have used in a sample.

<div id="canvas" @onmousedown="@OnMouseDown" @onmousemove="@OnMouseMove" @onmouseup="@OnMouseUp" style="width: 100%;height: 700px;position:relative;width:100%; height:700px;position:relative;overflow:hidden;background:#ededed">
    <svg id="canvas_layer" width="@Width" height="@Height" xmlns="http://www.w3.org/2000/svg">
        <g>
            <g id="canvas_layer" style="pointer-events: all;" transform="translate(0,0),scale(1)">

                @if (firstRender)
                {
                    <--render all nodes initially-->
                    @foreach (Node node in Nodes)
                        @RenderNode(node);
                }
                @if (draggingNode != null)
                {
                    @RenderNode(draggingNode); <-- Node object passing as a parameter to render only when dragging--> 
                }
            </g>
        </g>
    </svg>
</div>

@code
{
    RenderFragment<Node> RenderNode = node => __builder =>
    {
        <g id="node_element" xmlns="http://www.w3.org/2000/svg">
            <rect id="@node.Id" x="@node.X" y="@node.Y" width="@node.Width" height="@node.Height" fill="blue" stroke-width="5"></rect>
        </g>
    };
}

@ghost ghost locked as resolved and limited conversation to collaborators Nov 11, 2020
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Includes: Blazor, Razor Components feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. Status: Resolved
Projects
None yet
Development

No branches or pull requests

3 participants