Jump to content

Garbage Collected Delegate


photo

Recommended Posts

Hi,

We're getting an error on a garbage collected delegate in our UI. The UI is dynamically created by our UI manager on startup and we hold a copy of all the objects within the UI manager to stop them being garbage collected.

In this case, it's on a WidgetSprite that we've added callbacks for mouse enter and leave and click to. If after about a minute, you move the mouse over the object, you will get the error:

A callback was made on a garbage collected delegate of type 'UnigineSharp_double_x64!Unigine.Widget+Callback0::Invoke'. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.
 

As we are keeping copies of all of the objects, we should be keeping the objects alive and prevent them from being garbage collected. Is there something obvious we're doing wrong, or is there a recommended way of handling UI's within C#.

Thanks,

Robert

Link to comment

I think I've solved this in our widget wrapper class: What we had was this:

public void setCallbacks()
{
	sprite.setCallback0(Gui.CLICKED, OnMouseClicked);
}

void OnMouseClicked()
{
	// Handle event
		'
		'
		'
}

And what we need to do is this:

private Widget.Callback0 mouseClicked;

public void setCallbacks()
{
	mouseClicked = OnMouseClicked;
	sprite.setCallback0(Gui.CLICKED, mouseClicked);
}

void OnMouseClicked()
{
	// Handle event
		'
		'
		'
}

I think we'd have expected the wrapper to keep hold of the delegate.

Thanks,

Robert

 

 

Link to comment

Hi Robert,

You've found a valid workaround. Keeping a reference to a delegate will certainly prevent it from destroying.

Still, it's the responsibility of API to keep it alive.
I'll check what's wrong.

Link to comment

Sorry, but I can't reproduce.

There is a code for each callback, that pins delegate instance.
It prevents delegates not only from destroying but also from moving around a memory (using GCHandle).
It works fine with my tests.

It would help if you provided us with a sample for reproducing.
You can force garbage collection each engine frame if you insert the following code into the WorldLogic.update():

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Link to comment
  • 1 year later...

Hello,

sorry for reviving an old thread. But I had an similar issue with custom console commands in Unigine 2.10 with a dotnetcore project.

Invoking any custom console command except the last one added throws an System.ExecutionEngineException exception, it seems (The stacktrace goes into native code Unigine_x64.dll).

I'm just using this thread because Robert's workaround is also working in this case.

Its quite easy to reproduce

1. Create a 64 bit dotnet core project in Unigine 2.10

2. Modify the world logic:

		public override bool Init()
		{
			// Write here code to be called on world initialization: initialize resources for your world scene during the world start.

			Unigine.Console.AddCommand("my_console_command", "my_console_command", my_console_command);
			Unigine.Console.AddCommand("my_console_command1", "my_console_command1", my_console_command1);

            // This line is needed in order to reproduce the crash consistently
			Engine.GCMode = Engine.GCMODE.EVERY_FRAME;

			return true;
		}

		private void my_console_command(int argc, string[] argv)
		{
			Log.Message("my_console_command(): called\n");
			for (int i = 0; i < argc; i++)
				Log.Message("{0}: {1}\n", i, argv[i]);
		}

		private void my_console_command1(int argc, string[] argv)
		{
			Log.Message("my_console_command1(): called\n");
			for (int i = 0; i < argc; i++)
				Log.Message("{0}: {1}\n", i, argv[i]);
		}

3. load the world and run on the console my_console_command.

Cheers

Helmut

 

Link to comment
  • 6 months later...

The error is still hapening.

I am trying to do this: 

    private float time = 0;
    private void Init()
    {
        node.ObjectBodyRigid.AddContactCallback(onContactWithGround);
    }
    private void Update(){
        time += Game.IFps;
        if(time > 10 && node.ObjectBodyRigid.Immovable){
            node.WorldPosition = new vec3(0,0,10);
            node.ObjectBodyRigid.Immovable = false;
        }
    }
    public void onContactWithGround(Body bodyint num){
        respawnIn10Seconds();
    }
    public void respawnIn10Seconds(){
        time = 0;
        node.ObjectBodyRigid.Immovable = true;
    }

And after a while, a get the crash and the message:

FailFast:
A callback was made on a garbage collected delegate of type 'UnigineSharpCore_x64d!Unigine.Body+ContactDelegate::Invoke'.

 

Link to comment

I am also facing this error. Unfortunately, it is not permanent, sometimes everything works correctly. I noticed a pattern. The error occurs more often if you give the GC a lot of work

Link to comment

Hi,

I am new to Unigene and have experiernced this in verion 2.11 and verrsion 2.12 when using body collision callbacks.

The above mentiond workaround seems to work.

@silentI send u my testproject for debugging.

When using line 67 

 
Process terminated. A callback was made on a garbage collected delegate of type 'UnigineSharpCore_x64d!Unigine.Body+ContactEnterDelegate::Invoke'.
     at Unigine.Engine.Engine_main(IntPtr, System.Runtime.InteropServices.HandleRef, System.Runtime.InteropServices.HandleRef, System.Runtime.InteropServices.HandleRef)
     at Unigine.Engine.Engine_main(IntPtr, System.Runtime.InteropServices.HandleRef, System.Runtime.InteropServices.HandleRef, System.Runtime.InteropServices.HandleRef)
    at Unigine.Engine.Main(Unigine.SystemLogic, Unigine.WorldLogic, Unigine.EditorLogic)
    at UnigineApp.UnigineApp.Main(System.String[])

When using line 69 and 70 in stead of 67 is al seams to work.

public class fractures_systems : Component
{
    //V2.12
    private Body.ContactEnterDelegate cb;
.... etc
    private void Init()
    {
        // Break the mesch in scatter peases on init
        if(fracture_type == frac_type.Scatter_on_init)
        {
            BodyFracture fracture = null;
            ObjectMeshDynamic m = node as ObjectMeshDynamic;
            fracture = new BodyFracture(m);
            fracture.Material = fracture_material.Name;
            fracture.CreateShatterPieces(cracks);
        }
        
        // List to simplify if statement
        List<frac_typefracture_types_for_contactcallback = new List<frac_type>{
            frac_type.Scatter_on_contact,
            frac_type.crack_on_contact,
            frac_type.slice_on_contact
            };
        if(fracture_types_for_contactcallback.Contains(fracture_type))
        //if(fracture_type == frac_type.Scatter_on_contact || fracture_type ==  frac_type.crack_on_contact )
        {
            //V2.11
            //node.ObjectBody.AddContactCallback(ContactCallback);
            //V2.12 ###### garbage collected delegate ERROR ####
            node.ObjectBody.AddContactEnterCallback(ContactCallback);
            //V2.12 
            //cb = ContactCallback;  //### FUNCTIONAL
            //node.ObjectBody.AddContactEnterCallback(cb);  //### FUNCTIONAL
        }
.... etc
    }
Edited by david.cambre
Link to comment

Hi David,

I've found a workaround for your case.
Please change this:

node.ObjectBody.AddContactEnterCallback(ContactCallback);

To this:

// ...
private Body b;

private void Init()
{
	// ...
	b = node.ObjectBody; // save reference to the "ObjectBody"
	b.AddContactEnterCallback(ContactCallback);
	// ...
}

We will try to fix it in next release.

Best regards,
Alexander

Link to comment

Thanks Alexander,

I was stuck for three days until i read this post. So after silent's post i wanted to make sure you had a good reproducible bug.

On 7/27/2020 at 5:48 AM, silent said:

brunoasdfgh

Could you please provide a minimal test scene with reproduction (data and source directories contents will be enough).

Thanks!

 

Link to comment
×
×
  • Create New...