-
Notifications
You must be signed in to change notification settings - Fork 216
dev.DevelopingOperators
Developing basic C# operators — e.g., for math operations or list processing — is straightforward and a lot of fun. .NET handles many of the complicated aspects like automatic hot-reloading, memory management, and cleanup.
However, if you move on to more advanced topics like generating resources on the fly or connecting to 3rd-party libraries, there are a few important things to consider. This chapter collects various lessons we’ve learned during code reviews and real-world development.
Some resources cannot be cleaned up by the .NET garbage collector alone. Examples include:
- Active network connections you opened
- Listeners created via 3rd-party libraries like OSCRug or MIDI
- DirectX resources like textures or buffers
In all of these cases, you need to correctly dispose of the resource when the operator instance’s lifetime ends. Operator instances are disposed in several situations:
-
When closing the Editor or the Player, all instantiated operator instances are disposed. If an instance still holds onto unmanaged resources and does not properly implement
Dispose
, it can prevent a clean application shutdown, resulting in zombie processes that must be killed via Task Manager. This is hard to debug and confusing for users. -
When rebuilding assemblies (e.g., after editing a project or the operator library), all instances are also disposed.
If you're unfamiliar with C#, you might naively try to implement the IDisposable
interface like this:
// ❌ DO NOT DO THIS
public void Dispose()
{
ReleaseCpuData();
}
While this compiles and may seem to work, it has multiple problems:
- It's not thread-safe.
- The
Dispose
method might be called multiple times from different places, which could lead to incorrect or unsafe behavior.
The correct implementation looks like this:
protected override void Dispose(bool isDisposing)
{
if (!isDisposing)
return;
ReleaseCpuData();
}
You do not need to implement the parameterless Dispose()
method — it is already provided by the base class Instance
.
You may occasionally come across code like this:
[Guid("9412d0f4-dab8-4145-9719-10395e154fa7")]
public sealed class NdiOutput : Instance<NdiOutput>, IStatusProvider
{
public NdiOutput()
{
TextureOutput.UpdateAction = Update;
}
// ❌ DO NOT DO THIS
~NdiOutput()
{
Dispose();
}
}
Please avoid implementing a destructor. The base class Instance
already implements it and will correctly call Dispose(bool isDisposing)
when needed.