I am working on a small scale game engine and have reached a point where I would like to destroy a Game Object (destroy meaning all references to an object become null). This is important because even if I remove the object from the list of objects I want to update, there could be lingering references still in memory from anything that had a reference to the object, preventing it from being collected. However after some research it is clear that there is no way to outright delete an object in C#. Despite this fact the Unity Game Engine has a method that can destroy an object outright.
I found this thread on the Unity forums that explains how Unity does it. It uses a method dubbed as a "smart reference" or a "handle" where there is a wrapper class of sorts around the actual object where only the wrapper references the real object everything references the wrapper. When the object needs to be destroyed, the wrapper removes its reference to the real object, allowing it to be freed when the garbage collector is ready.
This all makes sense to me I am confused about some details. For one, how could I get properties and call functions of the real object class directly from the wrapper? Unity allows you to access properties directly like so gameObject.transform instead of gameObject.obj.transform implying one of four things
If anybody has the answers I need or knows a source I could learn more about these "smart references" please let me know, it would be greatly appreciated
This all makes sense to me I but am confused about some details
Yes! you are confused about this detail:
When the object needs to be destroyed, the wrapper removes its reference to the real object, allowing it to be freed when the garbage collector is ready.
But you're not that confused because you correctly surmised:
Something related to C++ which I wouldn't be able to implement in pure C#
What you're missing is that the garbage collector doesn't get involved at all. The managed object is a thin wrapper around a resource allocated by native code. When "destroy" is called on the managed object, it calls back into the native code to do whatever it does to release those resources.
The managed wrapper hangs around until the GC runs, or forever; that doesn't matter. It's just a tiny little wrapper object, not actually holding on to any expensive resources.
Take a look at the source code:
https://github.com/Unity-Technologies/UnityCsReference/blob/e5f43177f856c5f5bfe8537c9ab6f92425fdcc3b/Runtime/Export/Scripting/UnityEngineObject.bindings.cs
// Removes a gameobject, component or asset.
[NativeMethod(Name = "Scripting::DestroyObjectFromScripting", IsFreeFunction = true, ThrowsException = true)]
public extern static void Destroy(Object obj, [uei.DefaultValue("0.0F")] float t);
The method has no body! Everywhere you see extern, that's a method that is implemented in native code somewhere. Calls to that method dispatch via black magic back into unmanaged code.
You also surmised:
The GameObject wrapper is filled with methods and properties that just call the real object's methods and properties (seems horrible to maintain having to make sure every method has a duplicate)
Look at the source code:
https://github.com/Unity-Technologies/UnityCsReference/blob/e5f43177f856c5f5bfe8537c9ab6f92425fdcc3b/Runtime/Export/Scripting/GameObject.bindings.cs
There are 32 extern methods in this file alone. Those all have to be very carefully kept in sync with the signatures of the native code, otherwise it will be a disaster. Yes this is horrible to maintain, so often this sort of code is machine-generated from a schema so that the build can ensure everything agrees on the signatures.
If anybody knows a source I could learn more about these "smart references" please let me know
Let me caution you that correctly implementing a native code interop system that wraps unmanaged resources is a for-advanced-players-only job. Learning more about it is great, running off pell mell trying to implement your own safe handles is a bad idea. A brief read of the extensive comments in the SafeHandle source code should convey the number of issues the interop team had to consider. (Note in particular the use of constrained execution regions, one of the more obscure features of .NET.)
https://referencesource.microsoft.com/#mscorlib/system/runtime/interopservices/safehandle.cs
I would start by reading the platform invoke documentation.
https://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With