Wednesday, November 6, 2024
spot_img

c# – A great way of dealing with MonoBehaviour initialization in Unity?


I assume you do not wish to do that by placing the already-initialized bool/test in an summary base class deriving from MonoBehaviour, as a result of these initializable elements want to make use of a number of completely different inheritance paths. So let’s have a look at how far we will get with the interface…

  1. I have not discovered a option to make the Initialize technique return the implementing object, it will be good to initialize and retailer the article in the identical line.

We will obtain this through the Curiously Recurring Template Sample:

public interface IInitializable
{
    TSelf Initialize();
}


public class TestBehaviour : MonoBehaviour, IInitializable {

    int _internalState;

    TestBehaviour Initialize() {
        _internalState = 1;
        return this;
    }
}

You’ll be able to prolong this to sorts that want an enter for initialization:

public interface IInitializable
{
    TSelf Initialize(TData knowledge);
}

I haven’t got a superb resolution to level 2. It is a bit annoying/redundant to write down out the constructor for a readonly struct, however at the least it is one thing you normally solely do as soon as, in a single place, quite than one thing that calls for cognitive cycles unfold over the entire codebase.

As for 3, avoiding unintentional re-initialization, I do not know if this can be a “higher” means, however you could possibly use a small wrapper struct that can assist you maintain observe of whether or not a reference factors to an initialized or uninitialized part.

First, we identify our precise initialization technique one thing that reminds us we in all probability should not name it immediately (and is straightforward to seek for to validate whether or not we’re breaking that rule):

public interface IInitializable {
    Initialized INTERNAL_Initialize();
}

Then we make a pair of structs to carry a reference and mark whether or not or not it is already initialized. These tiny wrappers find yourself simply the dimensions of the unique reference, so we do not add any additional storage overhead this manner.

public readonly struct Initialized {
    public readonly T content material;
    public Initialized(T toWrap) {content material = toWrap;}

    public static implicit operator T(Initialized wrapper) => wrapper.content material;
}

public struct Uninitialized the place T:IInitializable {
    T _content;

    public Uninitialized(T toWrap) {_content = toWrap;}

    public Initialized Initialize() {
         Assert.IsNotNull(_content, "Try and re-initialize already-initialized behaviour");

         // That is the one place we'll enable a name to the INTERNAL model.
         var initialized = _content.INTERNAL_Initialize();
         _content = null; // Hole-out this wrapper so we're not reused.
         return initialized; 
    }
}

To make the code rather less verbose, we may give ourselves some extension strategies to simply apply these wrappers:

public static class Initialization {
    public static Initialized AsInitialized(this T enter) the place T:IInitializable {
        return new Initialized(enter);
    }

    public static Uninitialized AsUninitialized(this T enter) the place T:IInitializable {
        return new Uninitialized(enter);
    }
}

An initializable behaviour may appear to be this:

public class ToInit : MonoBehaviour, IInitializable
{
    public Initialized INTERNAL_Initialize() {
        return this.AsInitialized();
    }

    public void DoSomethingOnceInitialized() {}
}

And creating/caching an uninitialized reference to it, then later initializing it, may appear to be:

IEnumerator InitTest() {

    GameObject spawn = new GameObject("Check object");

    // We will instantly wrap the reference so we will not by chance
    // use it earlier than initializing it.
    var uninitialized = spawn.AddComponent().AsUninitialized();
    // Sort is Uninitialized however we do not have to write down that right here.

    // Another stuff occurs between creation and initialization...
    yield return null;

    var initialized = DoSomeInitialization(ref uninitialized);
    // Sort is Initialized, and uninitialized now can't be reused.

    // Now we will entry the underlying part's strategies.
    initialized.content material.DoSomethingOnceInitialized();
}

Initialized DoSomeInitialization(ref Uninitialized uninitialized) {
    // Think about there's some extra difficult initialization work right here...
    return uninitialized.Initialize();
}

(This extends to the variations with enter knowledge, omitted right here for brevity)

It is some additional typing right here, nevertheless it helps you to clearly mark whether or not a reference or operate parameter is holding an uninitialized part that wants initialization, or one which has positively already been initialized. You’ll be able to skip the Initialized wrapper and cut back the verbosity in case your codebase helps you to standardize the conference that any bare part reference is at all times already-initialized, and uninitialized ones will at all times be wrapped.

The strategy above will stop you from by chance initializing the identical Uninitialized reference twice, however in case you copy that reference (say, sending the identical uninitialized variable to a number of strategies which may every attempt to initialize it with out telling you), then it will not prevent. You’ll be able to remedy that by selling the struct to a class at the price of an additional heap allocation and pointer hop. However I believe that is simple to keep away from: any operate that takes an uninitialized parameter actually should end in initializing it, in any other case there’s not a lot it may well do with that deal with.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisement -spot_img

Latest Articles