Skip to content

Performance issue with ShoudRender functionality in WASM #26696

Closed
@KameshRajendran

Description

@KameshRajendran

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    ✔️ Resolution: AnsweredResolved because the question asked by the original author has been answered.Status: Resolvedarea-blazorIncludes: Blazor, Razor Componentsfeature-blazor-wasmThis issue is related to and / or impacts Blazor WebAssembly

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions