View Issue Details

IDProjectCategoryView StatusLast Update
0003970NoesisGUIC# SDKpublic2025-05-23 10:28
ReporterKeldorKatarn Assigned Tosfernandez  
PrioritynormalSeveritymajor 
Status resolvedResolutionfixed 
Product Version3.2 
Target Version3.2.8Fixed in Version3.2.8 
Summary0003970: Accessing child element in decorator allocates memory every frame
Description

My code:

public class DialogHostDecorator : Decorator
{
    private readonly DialogHost _dialogHost = new();

    public DialogHostDecorator()
    {
        AddVisualChild(_dialogHost);
    }

    /// <summary>Gets the <see cref="Visual"/> children count.</summary>
    protected override int VisualChildrenCount
    {
        get
        {
            return Child is null ? 0 : 2;
        }
    }

    /// <inheritdoc/>
    protected override Size ArrangeOverride(Size finalSize)
    {
        base.ArrangeOverride(finalSize);
        var finalSizeRect = new Rect(finalSize);

        _dialogHost.Arrange(finalSizeRect);

        return finalSize;
    }

    /// <inheritdoc/>
    protected override Visual GetVisualChild(int index)
    {
        if (Child == null)
        {
            throw new ArgumentOutOfRangeException(nameof(index));
        }

        switch (index)
        {
            case 0:
                return Child;

            case 1:
                return _dialogHost;

            default:
                throw new ArgumentOutOfRangeException(nameof(index));
        }
    }

    /// <inheritdoc/>
    protected override Size MeasureOverride(Size constraint)
    {
        var desiredSize = base.MeasureOverride(constraint);
        _dialogHost.Measure(constraint);

        desiredSize = new Size(
            Math.Max(desiredSize.Width, _dialogHost.DesiredSize.Width),
            Math.Max(desiredSize.Height, _dialogHost.DesiredSize.Height));

        return desiredSize;
    }
}

Apparently the Child_get property getter is allocating memory every time it is called.

PlatformAny

Activities

KeldorKatarn

KeldorKatarn

2025-02-17 23:36

reporter   ~0010366

Memory.jpg (42,689 bytes)   
Memory.jpg (42,689 bytes)   
sfernandez

sfernandez

2025-02-19 11:35

manager   ~0010374

Last edited: 2025-02-19 11:36

My guess is that the Child is not referenced in the managed world, only in the native instance of the Decorator. So everytime it is requested in the GetVisualChild, a new proxy is created for that child.

Could you store a reference to the Child in your DialogHostDecorator to see if that allocation disappears?

Visual _child;

protected override Visual GetVisualChild(int index)
{
...
case 0:
_child = Child;
return _child;
...
}

KeldorKatarn

KeldorKatarn

2025-02-19 12:16

reporter   ~0010375

Last edited: 2025-02-19 12:22

No, that does not fix it. The allocation happens in Extend.VisualGetChild, outside of my decorator code. Caching the child does nothing, the allocations still happen.

I tried caching by doing

    Visual _child;

    private Visual InternalChild
    {
        get => _child ??= Child;
    }

But the allocation happens before this code even gets called. It allocates 1.2 kB in Extend.VisualGetChild() and another 312 B in Extend.VisualChildrenCount(). My code is not allocating, it's the Extend class that calls my code that's doing the allocation. The allocation happens before or after my code gets called. Every frame, despite the caching.

sfernandez

sfernandez

2025-02-19 15:54

manager   ~0010376

I'll have to reproduce it and investigate, because the only thing that comes to my mind, as I mentioned, is the creation of the proxy for the child. There is no other new in that part of the code.

KeldorKatarn

KeldorKatarn

2025-04-05 22:35

reporter   ~0010475

Last edited: 2025-04-05 22:37

I think this might have less to do with the actual decorator but more with callbacks in the signature of methods in the Extend class.
Because now I'm also seeing it in a place where I override OnRender.

private static void UIElementRender(IntPtr cPtr, IntPtr contextType, IntPtr context,
UIElement.RenderBaseCallback callback)

    private static IntPtr VisualGetChild(IntPtr cPtr, int index,
        Visual.ChildrenCountBaseCallback countCallback, Visual.GetChildBaseCallback childCallback)

All these callbacks I think allocate for a new delegate every frame I think. This seems to be the issue. At least from what I can see.

And this is kind of a big deal because it means you can't override any of these methods without causing a memory leak.

This is kind of a big deal because it means I cannot override any of these methods without causing a memory leak.

The allocation appears whenever I override any of these callbacks that the Extend class handles. Be it GetVisualChild, OnRender... anything that gets called by these Extend methods that have callbacks.
The allocations seem to be these Visual.GetChildBaseCallback, Visual.ChildrenCountBaseCallback,UIElement.RenderBaseCallback, etc delegates that get allocated every frame

sfernandez

sfernandez

2025-05-23 10:28

manager   ~0010735

This solves issues described in posts:
https://www.noesisengine.com/forums/viewtopic.php?t=3588
https://www.noesisengine.com/forums/viewtopic.php?t=3571

Issue History

Date Modified Username Field Change
2025-02-17 23:31 KeldorKatarn New Issue
2025-02-17 23:36 KeldorKatarn Note Added: 0010366
2025-02-17 23:36 KeldorKatarn File Added: Memory.jpg
2025-02-18 18:01 jsantos Assigned To => sfernandez
2025-02-18 18:01 jsantos Status new => assigned
2025-02-18 18:01 jsantos Target Version => 3.2.8
2025-02-19 11:35 sfernandez Note Added: 0010374
2025-02-19 11:36 sfernandez Note Edited: 0010374
2025-02-19 12:16 KeldorKatarn Note Added: 0010375
2025-02-19 12:20 KeldorKatarn Note Edited: 0010375
2025-02-19 12:21 KeldorKatarn Note Edited: 0010375
2025-02-19 12:22 KeldorKatarn Note Edited: 0010375
2025-02-19 15:54 sfernandez Note Added: 0010376
2025-04-05 22:35 KeldorKatarn Note Added: 0010475
2025-04-05 22:36 KeldorKatarn Note Edited: 0010475
2025-04-05 22:37 KeldorKatarn Note Edited: 0010475
2025-05-23 10:28 sfernandez Status assigned => resolved
2025-05-23 10:28 sfernandez Resolution open => fixed
2025-05-23 10:28 sfernandez Fixed in Version => 3.2.8
2025-05-23 10:28 sfernandez Note Added: 0010735