One thing that I repeatedly come up against as a pain point when trying to build larger scale projects in the DOTS ecosystem is the lack of smart pointers, which are C++'s solution to managing memory ownership.
Without GC, we spend a lot of time dealing with manually allocated memory which we must manually dispose of exactly once. This gets tricky if we want to share that memory in multiple places, or build modular systems that don’t know the desired ownership semantics of their inputs.
C++ solves this classic problem with “smart pointers”. These are types that thinly wrap pointers and track their ownership. unique_ptr<T> is a pointer type that restricts copying in such a way as to only ever have one owner, and shared_ptr<T> is a pointer type that does reference counting to allow multiple pieces of code to share ownership of one object and deallocate it when the last reference disappears.
C# does not have these types, and I believe that it is not possible to build good implementations of them due to the lack of C++ features like overridable copy constructors and deterministic struct finalizers.
How are we meant to manage memory resource ownership without smart pointers? What patterns are you using, and what does Unity recommend as the best practice here? I think it would be good to get some input from the Unity staff on this!
I’ll give an example of a problem situation that I’ve hit quite often.
I have some small widely reusable type that needs to be allocated. Let’s say a NativeAnimationCurve type that can be built from the standard managed AnimationCurve and holds its data in a private NativeArray. It’s an exact - but perhaps immutable - AnimationCurve equivalent for use from DOTS code, and many different pieces of code may want to consume it in different ways.
I have some composition type that holds - amongst other things - one or more NativeAnimationCurves. Let’s say that this composed type is ParticleBehaviour. When building this type, I need to pass in the curves. Now who owns them?
If ParticleBehaviour is disposed of, should it dispose the curves? If yes, then that design decision makes it impossible to share that curve instance anywhere else. If no, then I have to somehow track and dispose the curves independently of the ParticleBehaviour, even if I only end up using them in one place.
When I implement the NativeAnimationCurve, I don’t know where and how I will be consuming it. In fact, it’s probably in multiple places in different ways. When I implement the ParticleBehaviour, I don’t know how I’ll be providing the curves or whether they will be shared data. Separation of concerns says that neither should need to know that detail about the other, and this is a requirement for modular re-usability and composition.
The clean solution to this is to use value semantics and allocate copies of the curves going into the composed type. Of course this can be a performance issue for both speed and memory if it’s done either frequently or many times.
Even if I choose one of these options, I then have to find a way of cleanly documenting it on the API of the type, and ideally enforcing correct usage.
This is cumbersome, bad for maintainability, and makes me need to pause and think about a far reaching design decision when doing something that should be as simple as creating a quick composition type.
How are people handling this practically, and what is the Unity staff recommended way to deal with this fundamental challenge introduced by the allocator / Dispose pattern?
I’m marking this as feedback rather than help wanted because I’d like staff attention and because I’m explaining a pain point, but I would also like practical tips. If there’s a good solution available here, then it at least needs better documentation.