Is OnDestroy reliable in Unity? - c#

In Unity I have a script in which I connect to a socket via TCP and want to use this connection every frame. I need to dispose and clean up after that. My idea was to use Start() to start a connection and OnDestroy()
public class Foo : MonoBehaviour
{
void Start()
{
// start TCP connection
}
void Update()
{
// use connection
}
void OnDestroy()
{
// cleanup
}
}
I need the cleanup to execute whatever happens. Does the OnDestroy() method guarantee to be called before the application stops (in standalone mode and in editor) no matter what happens to the object? If not, how can I guarantee the cleanup?

No it is not!
Even OnApplicationQuit might not get called when your app e.g. crashes for some reason.
And there are other specific cases where neither is called. I know that from my own experience that e.g. on the HoloLens2 apps are not closed but only hibernated. If you then close them via the HoloLens home "menu" then you actually kill them via task manager.
This is pretty dirty and causes neither OnDestroy nor OnApplicationQuit or any other Unity specific messages to be called and we ended up with zomby threads and still occupied TCP ports.
If you really want to go sure (e.g. for giving free the connection, killing threads etc) what I finally did was creating a dedicated class with deconstructor (Finalizer)
The deconstructor is pure c# and does not rely on Unity being shutdown correctly so it is guaranteed to be called even if the app was terminated due to a crash as soon as the Garbage Collector automatically does its job.
A MonoBehaviour itself shouldn't implement any constructor nor destructor but a private "normal" class can:
public class Foo : MonoBehaviour
{
private class FooInternal
{
private bool disposed;
public FooInternal()
{
// create TCP connection
// start thread etc
}
public void Update ()
{
// e.g. forward the Update call in order to handle received messages
// in the Unity main thread
}
public ~FooInternal()
{
Dispose();
}
public void Dispose()
{
if(disposed) return;
disposed = true;
// terminate thread, connection etc
}
}
private FooInternal _internal;
void Start()
{
_internal = new FooInternal ();
}
void Update()
{
_internal.Update();
}
void OnDestroy ()
{
_internal.Dispose();
}
}
if you never pass on the reference to _internal to anything else, the GC should automatically kill it after this instance has been destroyed as well.

You're looking for OnEnable for establishing your connection, and OnDisable for cleaning it up.
The issue with OnDestroy (and OnApplicationQuit fwiw) is it won't be called if the script is disabled, which can happen if:
You call SetActive(false) on the GameObject or any of its parents.
You set enabled = false on the script component.
You disable the GameObject or any of its parents in play mode by unchecking the box in the inspector.
You disable the script component in play mode by unchecking the box in the inspector.
The editor is about to recompile the scripts while play mode is running (an especially irritating event to miss for certain types of cleanup code [cough lookin' at you, Vuforia]).
The issue with Start is it won't be called if the script is re-enabled after having been disabled (it'll be called the first time, but no more), which happens in all of the cases opposite of the above list (as well as when the editor finishes recompilation of scripts in play mode).
On the other hand:
OnDisable will be called when the script transitions to disabled for any of the above reasons, plus all the stuff that OnDestroy and OnApplicationQuit cover.
OnEnable will be called when the script transitions to enabled for any of the above reasons, plus all the stuff that Start covers.
Additionally, you will have a 1:1 correspondence of OnEnable and OnDisable calls by Unity (of course you can call them yourself whenever you want).
You will only almost have a 1:1 correspondence of Start and OnDestroy: The notable exception is if a script component is initially disabled (i.e. you disabled it in edit mode) but its GameObject is enabled, you'll still get OnDestroy called on the script even if Start wasn't called (which may be a bug now that I think about it). Plus, of course, you will miss all the above cases.
OnEnable + OnDisable, however, handle all that cleanly, and generally do exactly what you hope that they do, in a straightforward manner with (theoretically) no weird quirks or gotchas.
For the question itself, yes, OnDestroy is reliable: It is called as documented when the script is destroyed.
Of course it wouldn't be called if the application crashes, but in general you'd have bigger problems and that's not the kind of thing you normally code around. It also probably wouldn't be called if you exploded a grenade next to the machine running the game but, again, that's all in "undefined behavior" territory.

Related

MissingReferenceException when trying to capture a variable in a lambda

I'm trying to access a value of a local variable inside a lambda according to this post, but I get this excepton:
MissingReferenceException: The object of type 'Transform' has been destroyed but you are still trying to access it.
Here is my code:
private static void ValidateComponent<T>(Transform transform, Delegate validationMethod, object[] args) where T : Component
{
#if UNITY_EDITOR // EditorApplication is only available when running in the editor
if (EditorApplication.isPlaying) return;
if (transform == null) return; // in case inspector field is not assigned
var t = transform;
EditorApplication.delayCall += () =>
{
var component = t.GetComponent<T>();
if (component == null)
{
LogAddingComponent<T>(t);
var addedComponent = t.gameObject.AddComponent<T>();
validationMethod.DynamicInvoke(args.Prepend(addedComponent));
}
else
{
validationMethod.DynamicInvoke(args.Prepend(component));
}
};
#endif
}
What am I doing wrong?
You are attempting to pin transform via the variable t only to use it later during your delayCall callback. That's dangerous considering many types in Unity contain unmanaged resources hence the error. The effect is the same if you created a Font in WinForms, Disposed it, then attempted to use the font.
OP:
Is there a better way to approach this then?
From what I understand, Transforms are always attached to the GameObject and it is the GameObject that is being destroyed taking the Transform along with it.
The only way I know to prevent game object destruction is viaDontDestroyOnLoad but I'm not sure if its applicable here since the latter is really designed for protection whilst transitioning to a new scene, but you never know perhaps it prevents things from being destroyed in other scenarios.
Try calling DontDestroyOnLoad at the top of ValidateComponent like so:
private static void ValidateComponent<T>(Transform transform,
Delegate validationMethod,
object[] args)
where T : Component
{
#if UNITY_EDITOR // EditorApplication is only available when running in the editor
if (EditorApplication.isPlaying) return;
if (transform == null) return; // in case inspector field is not assigned
DontDestroyOnLoad (transform.gameObject); // <---- new
var t = transform;
.
.
.
Otherwise, there is this Unity Answer, that suggests simply disabling and re-enabling the object rather than whatever is destroying the object in the first place. Following that approach you shouldn't require any changes to your code above.
Editor-time-only scripts
BTW, regarding the #if UNITY_EDITOR bit, if you want to avoid having to decorate your code with #ifs everywhere (as an ex c/c++ programmer I certainly don't miss having to do that all the time), consider simply moving your script into a child folder called Editor. Unity automatically treats anything placed in such folders as Editor-time-only and won't be incorporated into your final game build. Better yet, no need for the #if stuff.
The best reason for it though is to prevent build errors when Unity is producing your final game due to forgetting to either use #if or Editor folders. I typically run into this one with 3rd party scripts obtained from the Unity store. Instead of having to fix the code I simply move it to a child Editor folder.
See also
Special folder names, Unity

How do you stop a client/host in Unity Netcode for Gameobjects?

I managed to get some kind of multiplayer working with Unity Netcode which I understand is very new, but I'm running into a pretty big issue. I can't seem to be able to disconnect from the server instance. This causes an issue in which I load the game scene and the NetworkManager instance is spawned. Then I return to the main menu scene and load the game scene again, and now there are 2 NetworkManager instances which obviously causes errors.
I read in the docs that you can use this:
public void Disconnect()
{
if (IsHost)
{
NetworkManager.Singleton.StopHost();
}
else if (IsClient)
{
NetworkManager.Singleton.StopClient();
}
else if (IsServer)
{
NetworkManager.Singleton.StopServer();
}
UnityEngine.SceneManagement.SceneManager.LoadScene("MainMenu");
}
However all these stop functions don't seem to exist at all under the singleton. Am I missing a library or something?
For now I am using a workaround which is just disabling "Don't destroy" and then the NetworkManager is destroyed after switching scenes, but what if I wanted to keep it after switching a scene? Is there no way to control when the Server/Client is stopped? I'm assuming this is a bug but I just want to make sure.
void Disconnect()
{
NetworkManager.Singleton.Shutdown();
}
Disconnects clients if connected and stops server if running.
To answer your full question, you also need a Cleanup() function in your non-networked scene that checks for astray NetworkManager singletons and destroys them. This will avoid unnecessary duplication of network managers.
void Cleanup()
{
if (NetworkManager.Singleton != null)
{
Destroy(NetworkManager.Singleton.gameObject);
}
}

Delegate.Combine: How to Check if multicast (combinable) delegates already has a delegate inside of it?

I am using Unity 3D, however, that information should be irrelevant for solving this problem as the core problem is System.Delegate (I wanted to let you know as I'll be linking to some Unity docs for clarification).
I have a custom window that has a custom update function DirectorUpdate. I need this function to run every editor update regardless of what the user/window is doing.
In order for this to be called every editor update, I Combine my method with the Delegate EditorApplication.update:
protected void OnEnable()
{
// If I do the below, base EditorApplication.update won't be called anymore.
// EditorApplication.update = this.DirectorUpdate;
// So I need to do this:
EditorApplication.update = (EditorApplication.CallbackFunction)System.Delegate.Combine(new EditorApplication.CallbackFunction(this.DirectorUpdate), EditorApplication.update);
... // Other stuff
}
Note that this is done inside a window's OnEnable.
The problem is that OnEnable can be called more than once during a single run (for example, when closing the window and then reopening the window during a single editor session) causing
EditorApplication.update = (EditorApplication.CallbackFunction)System.Delegate.Combine(new EditorApplication.CallbackFunction(this.DirectorUpdate), EditorApplication.update);
to be called multiple times, meaning my update method (this.DirectorUpdate) will eventually get called multiple times per update, which is causing some serious bugs.
So, the question is how do I check if EditorApplication.update already has my method "inside" of it. (By inside of it, I of course mean it has already been System.Delegate.Combine(d) to the delegate.)
I am aware that there could be other solutions, for example restoring EditorApplication.update to what it was prior when the window closes, however that won't account for all situations (for example, OnEnable also gets called during window refresh and such) and the bug will persist. (Also, what if another window Concatenates with EditorApplication.update while this window is open?) As such, the best solution would be to check if EditorApplication.update is already callin this method BEFORE Delegate.Combine-ing it.
I think you took the complicated road ;)
Subscribing and unsubscribing events and delegates is as simple as using the operators += and -= like
protected void OnEnable()
{
// You can substract your callback even though it wasn't added so far
// This makes sure it is definitely only added once (for this instance)
EditorApplication.update -= DirectorUpdate;
// This basically internally does such a Combine for you
EditorApplication.update += DirectorUpdate;
... // Other stuff
}
private void OnDisable()
{
// Remove the callback once not needed anymore
EditorApplication.update -= DirectorUpdate;
}
This way you can also have multiple instances of this window open and they all will receive the callback individually.
Btw if this is actually about an EditorWindow then afaik you should not use OnEnabled but you would rather use Awake
Called as the new window is opened.
and OnDestroy.
I am not familiar with what System.Delegate.Combine(d) does, but you can consider instead of enabling/disabling your window, destroying and instantiating it every time, and move your code to the Start or the Awake for it to be called only once per window "activation".
Last but not least, use a mighty boolean in the OnDisable so that you can handle the combine execution if your component was ever disabled. Like so:
bool omgIWasDisabled;
protected void OnEnable()
{
if (!omgIWasDisabled) {
EditorApplication.update = (EditorApplication.CallbackFunction)System.Delegate.Combine(new EditorApplication.CallbackFunction(this.DirectorUpdate), EditorApplication.update);
}
... // Other stuff
}
void OnDisable() {
omgIWasDisabled = true;
}
Hope any of those works out.

C#: Access function of another class when having to use a default constructor and making it static is not an option

I am working on a little app that includes a broadcast receiver.
Broadcast receivers HAVE to HAVE a default constructor. Don't ask me why...
Anyway, This is it:
[BroadcastReceiver]
public class IntentReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
if (AudioManager.ActionAudioBecomingNoisy.Equals(intent.Action))
{
Activity_Player.Instance.PlayOrPauseLogic();
}
}
}
The problem is, that when this function fires (becoming noisy means someone unplugged the headset) I need to be able to stop the current music playing.
In my main class, I have a MediaPlayer object called mediaplayer. I now need to call this mediaplayer object with the ".Stop()" prompt.
This means however, that I cannot just create a new object from my class and then call Stop() on this media player, since this would not affect the currently playing media player.
I can also not hand the media player over into broadcast recevier, since the constructor needs to be the default one.
Lastley, making the mediaplayer object static is also NOT an option for many reasons now.
So what can I do?
What I did now is make a single instance of my activity and THEN call stop on this instance. This is a very bad solution since it causes memory leaks and I need to get rid of it.
Please help me.
Thank you! :)

How to unsubscribe from an event inside of the gamemanager?

In my GameManager script I have a delegate. In my Door script, I subscribe to this delegate. I've tried unsubscribing from it in both the OnDisable and OnDestroy methods in the Door script. In both cases, I get an error when I stop running the game in the editor:
Some objects were not cleaned up when closing the scene. (Did you spawn new GameObjects from OnDestroy?)
Using Debug.Log, I found out that this is because the GameManager will always be destroyed before the Door script. Even if I do a null check inside either the OnDisable or OnDestroy of the Door script to see if the GameManager is null, I get the same error
if (GameManager.Instance)
{
GameManager.Instance.OnAllEnemiesKilled -= OpenDoor;
}
Somebody told me that I don't need to unsubscribe from it, as the delegate will automatically become null when the Door object is destroyed, but that's not true. During runtime, after the Door is destroyed, my update loop inside of the GameManager is still printing that the delegate has one subscriber: the Door.
I suspect you're spawning a new singleton when you call GameManager.Instance after it's been destroyed? If so, do something like this instead:
public class GameManager : MonoBehaviour
{
public static GameManager Instance
{
get
{
if (isDestroyed) return null;
// Your spawn logic...
}
}
static bool isDestroyed;
void OnDestroy()
{
isDestroyed = true;
}
}
As Lece pointed out, I'm spawning a new singleton when I call GameManager.Instance. Rather than creating a static bool to remedy the problem however, I replaced if (_instance == null) with if ((object)_instance == null) inside my Instance getter and it solved the problem. This is because, while the object is destroyed in the native code, it still exists in the managed code. So I'm now referencing it in the managed world. Moreover, Unity makes it so that when an object is destroyed in native code, its value in managed code will still return null.

Categories

Resources