Object construction with factory method

How to structure your code to produce loads of objects? Learn the Factory Method and how to implement it in Unity3d!

One of the most common things to do in making a game is instantiating objects at runtime. Be it bullets from a gun, enemies rushing forward or instances of software classes, sooner or later you’ll need to do it.

This is one of those classic problems of computer science that have been solved over and over again. The solution to it I’m going to talk about today is the Factory Method (not to be confused with the Abstract Factory, which is way more general in its application).

What’s a Factory Method?

factory-method-pattern

Basically it’s when you use an inherited method (either from a superclass or an interface) to create for you an instance of a Product. The Product will be set up just as you specified with the arguments that are given to the Factory Method.

For instance if you have an¬†EnemyFactory superclass, you could have a OgreFactory implement the FactoryMethod() for it. Then you could have an Ogre orc=datOgreFactory.FactoryMethod(100) to have orc‘s hit points set to 100.
Of course the GoblinFactory is another subclass of EnemyFactory but will still give you a 100 hp hulkling of a goblin if you ask for it with a Goblin hobGoblin=datGoblinFactory.FactoryMethod(100). It’s important to keep in mind that both Ogre and Goblin are one same kind of superclass: Enemy. This means they will share a common usage in their DieForLoot() function so that the kill procedure doesn’t need to know their specifics.

If you want a more technical definition, the internet is filled with it but I’d rather recommend to cut to the source and read it straight from the book written by the Gang of Four.

When to factory?

That’s more of a critical issue worth discussing. My personal answer would be to use it every single time you have more than exactly one thing to instantiate and most of the times you actually instantiate just one thing.

If you want dem clones, make a factory!
If you want dem clones, make a factory!

When you construct an instance of something twice, you are duplicating code and sure as hell it’s better not to write the same thing twice.

As for the reason I’d recommend to write one even if you are doing an instantiation just once:

  • if it’s something that will be a part of gameplay are you really sure you won’t be putting another one somewhere else?
  • Are you really sure you don’t need to test the instantiation itself indipendently?

Chances are, you will, and copy-pasting a bit of boilerplate code upfront is way better than refactoring after.

Also, it helps with respecting the single responsibility principle in the code: if instantiating that object was the only job of the class it’d be a factory, wouldn’t it?

But be careful: in some cases you might need to move even further and go full Abstract Factory. You might ask yourself:

  • Do I need to abstract everything or is it ok to work on concrete instances?
  • Do I need to hide my concrete code (i.e.: with a .dll)?
  • Do I need a factory of factories?

If you answered no to that, there’s no need for too much abstraction and you can just use the good old Factory Method.

blueprints-1837238_1920

How to build one in Unity3d

When working with Unity we have the usual problem of handling the references, which entails serious design decisions over how the factory should be realized. Plus in this case we have an additional issue: interface references won’t show up in the inspector, not even with a SerializeField .

Why? because just as generics (with the exception of the hardcoded List<T> ) interface references don’t get serialized in Unity, unless you write a workaround or pay sixty bucks …or at least five.

So this code:

public class FactoryUser : MonoBehaviour
{
    [SerializeField]
    IFactory thisWontSerialize;
    [SerializeField]
    MonoBehaviour factory;

will be rendered in the inspector like this:

immagine

So how to do a factory? Let’s begin with the interface (of course you can also make a superclass and skip half of the issues).

public interface IFactory 
{
    GameObject FactoryMethod(object[] parameters);
}

Why an array of objects? because we don’t know what we’ll need, so better have the thing that can do everything. A more type-safe approach would be to define a dedicated struct or class.

To have a working example we’ll implement a concrete factory that instantiates a number of childs of a main prefab:

public class ConcreteFactory : MonoBehaviour, IFactory
{
    [SerializeField]
    GameObject mainPrefab;
    [SerializeField]
    GameObject childPrefab;

First of all,¬† we add the references so that they can be seen in the inspector. Of course we’re going to make this class a MonoBehaviour, because to use the inspector is either that or ScriptableObjects and this is not a ScriptableObject tutorial. And I love the inspector.

inspector unity
He obviously prefers a platonic love ūüôĀ
    public GameObject FactoryMethod(object[] parameters)
    {
        GameObject mainObject = Instantiate(mainPrefab);
        int amount = (int)parameters[0];
        for (int i = 0; i < amount; i++)
        {
            GameObject childObject = Instantiate(childPrefab);
            childObject.transform.parent = mainObject.transform;
        }
        return mainObject;
    }

This is the method that implements the IFactory interface. What we now do is:

  1. Instantiate a copy of the main prefab.
  2. Dumbly take for granted that the first parameter really IS an int
  3. proceed to a runtime error to instantiate and reparent his child objects
  4. witness in horror as performance sinks since we didn’t use pooling here

Now that the concrete factory is done let’s see it in action:

public class FactoryUser : MonoBehaviour
{
    [SerializeField]
    MonoBehaviour factory;

Wait, why a MonoBehaviour reference? We don’t want any runtime checks, do we? This should be type-safe and checked at compile-time!

    public void OnValidate()
    {
        if (!(factory is IFactory))
        {
            Debug.LogError("Wrong reference type");
            factory = null;
        }
    }

Well, not really at compile time, we just need it to be done while the game isn’t running. So here’s a check that runs every time the field is modified in the inspector. Of course if you want to inject the factory instance or have it passed some other way, you can use an IFactory variable instead.

This is just a workaround to have both type-safety and serialization after all,¬†here’s another one if you don’t like it this way.

workaround unity interface serialization

But we’ll also avoid having to write a cast every time we use it:

    IFactory Factory { get { return factory as IFactory; } }

So we now can write:

    public void Start()
    {
        object[] parameters = new object[1];
        parameters[0] = 2;
        Factory.FactoryMethod(parameters);
    }

And finally, here it’s at work. Easy as pie. So easy it will be a pleasure to test and spam everywhere (provided you pooled everything).

Where’s the advantage of using this approach? Say your level designer wants to change the enemy type that is spawned in front of the castle gates, he can do it in the inspector simply by changing the factory that is referenced in the castle gate spawn component. No coding required, just a drag&drop.

That’s all folks!

Here’s a Unity project where you can grab al the code and see it in action, plus scroll down for a copy-pasteable version of it.

I hope you liked this tutorial, please tell me if you find this kind of guide interesting or not, either here or on my Twitter. If you don’t want to miss my next thing consider subscribing to my newsletter.

P.S.: I’m currently looking for a job, if you are interested here’s my portfolio.

public class ConcreteFactory : MonoBehaviour, IFactory
{
    [SerializeField]
    GameObject mainPrefab;
    [SerializeField]
    GameObject childPrefab;

    public GameObject FactoryMethod(object[] parameters)
    {
        GameObject mainObject = Instantiate(mainPrefab);
        int amount = (int)parameters[0];
        for (int i = 0; i < amount; i++)
        {
            GameObject childObject = Instantiate(childPrefab);
            childObject.transform.parent = mainObject.transform;
        }
        return mainObject;
    }
}
public interface IFactory 
{
    GameObject FactoryMethod(object[] parameters);
}
public class FactoryUser : MonoBehaviour
{
    [SerializeField]
    MonoBehaviour factory;
    IFactory Factory { get { return factory as IFactory; } }

    public void OnValidate()
    {
        if (!(factory is IFactory))
        {
            Debug.LogError("Wrong reference type");
            factory = null;
        }
    }

    public void Start()
    {
        object[] parameters = new object[1];
        parameters[0] = 2;
        Factory.FactoryMethod(parameters);
    }
}
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

The singleton post – making a Singleton in Unity3d with inheritance

You already know what a singleton is? See how to do it by inheriting from a SingletonBehaviour<T>, skip to “How to singleton”

Sometimes when programming you are just reinventing the wheel. And by sometimes I mean all the time. Basically every problem a programmer faces is an already solved problem from an algorithmic perspective. Either that or you’d have better refused that job unless you are in academy.

So why not to look at all the already found solutions and use the ones matching your problem? The best solutions to the most common problems have been codified a long time ago, both for programming in general (ever heard of the Gang of Four?) and for game programming in particular.

Today I’ll¬†talk about one of these standardized solutions, the most famous one: the Singleton.

What’s “singleton”?

one-way-street-362172_1920

A singleton is a class that you want to make sure is unique and globally accessible.¬†This means that there’s only one in your scene and that it’s accessible from everywhere and everything.

Of course in Unity you won’t have a class floating in nothingness, althought you could do so with a static class. In Unity you want to use that sweet inspector to tweak the singleton’s variables, so you want to make it a component to an object.

When to singleton

gummibarchen-359950_1280

There’s a lot of cases in which a Singleton is just a good idea.¬†For instance since you only have one touch input source on a mobile device, it would be a good idea to have a singleton deal with it.

Also, if you are in need of managing the timing of all the enemy attacks in your musical shump a singleton would be the best candidate to handle the coordination. You need a lot of slowmotions and want to check if there’s overlap? Get a singleton to do that!

You should ask yourself these two questions to check if you need a singleton:

  • is this a thing that must not be duplicated or otherwise there could be problems?
  • is this a thing that I want to have easily accessible from everywhere?

When not to

Since the singleton is very quick to implement (and with this inheritance-based approach¬†you’ll need just a few characters to do so) it’s easy to over-use it and to add useless singletons.

An example is the case where you make a singleton EnemyManager and an Enemy¬†script, where the latter only has some data and the former’s only aim is to extend the Enemy¬†with methods. In that case is better to just add the methods in the Enemy script.

To check if you really need that singleton ask yourself:

  • do I need to hold some information in this class or it’s just methods?
  • can’t I just do the same things by extending something else?
  • will this raise my codebase’s coupling too much?

Also, since this is basically a global variable on steroids it goes without saying that if you have a problem with global variables you should use anything but this (i.e.: if you want to perform unit testing). In general a lot of people have a lot to object to the use of singletons, so inform yourself and make it your call.

singleton unity3d inheritance

How to singleton

[Notice: this part was written differently due to a buggy behaviour that now I can’t reproduce]

Since this is a design pattern you will find a host of different implementations of it and they will all be just as correct as this is, the real question is which way is more suitable to you.

In unity for instance if you use the singleton as a component in a GameObject you can be pretty sure that you won’t need to¬†bother with locks as they did in the unity wiki.

Also, I assume you aren’t trying to implement just one singleton class but that you’ll need a number of them and that you want to do it without duplicating code.

Well, its one kind of singleton inheritance, I guess...
Well, its one kind of singleton inheritance, I guess…

Given these assumptions here’s my implementation of it: we start with a generic class called SingletonBehaviour that inherits from MonoBehaviour, then whenever you want to create a singleton you just mark that class as inheriting¬†SingletonBehaviour.

Let’s look at the code:

public class SingletonBehaviour<T> : MonoBehaviour where T : MonoBehaviour
{

As I said this will be a generic class, that uses T as a parameter to indicate the actual type of the singleton we want to instantiate by inheritance. And that type will only be constrained to be a MonoBehaviour, so that only scripts that can be a component of a gameobject can be used for this.

    public static T instance;

This is where the references of our subclasses will be stored. Since it’s static only one instance per type is allowed.

Now let’s see what we do when an instance is created:

    public virtual void Awake()
    {
        if (instance != null)
        {
            Debug.LogError("Duplicate subclass of type " + typeof(T) + "! eliminating " + name + " while preserving " + instance.name);
            Destroy(gameObject);
        }
        else
        {
            instance = this as T;
        }
    }

The first thing to do is to check if this class has already been instantiated (notice, you can also use¬†if(instance)¬†if you don’t care for readability as you should), if that’s the case the GameObject is to be destroyed. Why the whole GameObject? Because this is a kind of failure so serious you want it to flash everything, add a Debug.Break()¬†too if needed.

For this tutorial’s purpose I’ve also added a (quite verbose) error message. Otherwise, we set the current class’ instance in the instance variable.

    public virtual void OnDestroy()
    {
        if (instance == this)
            instance = null;
    }

That is, only if the instance being destroyed is the same one being stored we go on and clean the contents of the instance variable.

So, now to use this one only needs to inherit from this class like this:

public class SomeSingleton : SingletonBehaviour<SomeSingleton>

Notice that¬†you are not enforced to use the same class as generic parameter by the compiler, which means that you could even have different classes compete for the same slot. I won’t recommend doing that on purpose: if you want to manage different singletons for one “slot” it’s best done explicitly instead of by relying on who gets there first.

That’s all folks!

Once again instead¬†I’ve built for you a test project so that you can download and test it in action, but of course down here comes also the copy-pasteable version.

I hope you enjoyed this tutorial, and that you will consider joining my newsletter. Please also tell me what you think of this and if you think I should cover more design patterns, either here or by hitting me up on twitter.

P.S.: I’m currently looking for a job, give a look to my portfolio if you are interested.

using UnityEngine;

public class SingletonBehaviour<T> : MonoBehaviour where T : MonoBehaviour
{
    public static T instance;

    public virtual void Awake()
    {
        if (instance)
        {
            Debug.LogError("Duplicate subclass of type " + typeof(T) + "! eliminating " + name + " while preserving " + instance.name);
            Destroy(gameObject);
        }
        else
        {
            instance = this as T;
        }
    }

    public virtual void OnDestroy()
    {
        if (instance == this)
            instance = null;
    }
}
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Crossfade gallery, a simple and cool UI element

Last week I’ve released a new UI demo project with some effects made entirely in Unity3D’s UI and it had quite a good reception, in particular at some point I was asked to share.

untitled

Well, I’m not ready to put it on GitHub yet (some parts of the code need more polish before being released without shaming myself), but I do want to share. So I’ll¬†give a downloadable link to¬†the project in my newsletter and start a new tutorial series on the topic here.

Today’s topic is the first panel: the sequential crossfader.

What does crossfade mean?

You have probably seen this transition a gazillion times in your life. It’s when an image fades away and a new one takes its place.

How it’s done is quite simple: for each pixel you lerp its color between the color of the former image and the one of the next.

Luckily for us, we don’t have to even deal with that simple operation, UnityEngine.UI.Graphic has already all that we need.

Let’s wrap Graphic

wrap it tight
wrap it tight

From this class we only need one thing: the CrossFadeAlpha function. But we will need to do 3 things with it.

  1. reset the values to an initial state
  2. fade in
  3. fade out

To do this, we’ll just use a small MonoBehaviour component, but please notice that if you want to do this with whole panels instead of a single graphic, you’ll need to add a canvas group¬†as target and change the functions¬†to manually lerp its alpha value.

So, here’s the code:

using UnityEngine;
using UnityEngine.UI;

public class UICrossFader : MonoBehaviour
{
    [SerializeField]
    Graphic toCrossFade;
    [HideInInspector]
    public float inAlphaLevel = 1f;
    [HideInInspector]
    public float outAlphaLevel = 0f;
    [HideInInspector]
    public float duration = 1f;
    [HideInInspector]
    public bool ignoreTimeScale = false;

    public void FadeIn()
    {
        toCrossFade.CrossFadeAlpha(inAlphaLevel, duration, ignoreTimeScale);
    }

    public void FadeOut()
    {
        toCrossFade.CrossFadeAlpha(outAlphaLevel, duration, ignoreTimeScale);
    }
    
    public void Preset(bool isIn)
    {
        if (isIn)
            toCrossFade.CrossFadeAlpha(inAlphaLevel, 0f, true);
        else
            toCrossFade.CrossFadeAlpha(outAlphaLevel, 0f, true);
    }
}

Why are almost all the variables public but hidden? because if you want to control this behaviour you’ll want to do it¬†by code and you don’t want anyone who doesn’t know what he’s doing to fiddle with it.

As you can see the Preset function can either set something to be visible or invisible (in or out) according to the isIn parameter and does so in just a frame, regardless of the timescale. This is because that function is meant to work behind the scenes, not while the show is going on.

The fade functions are just another wrap for the CrossFadeAlpha with pre-set values. This component is indeed very simple, but keep in mind that not everything in UI is a Graphic, and that also you may want to extend this to do more complex stuff one day.

Managing a sequence with a coroutine Start

flight-1179587_640

    public GameObject listParent;
    [SerializeField]
    float startDelay;
    [SerializeField]
    float interval = 4f;
    [SerializeField]
    bool doLoop = true;

We need just a few variables to decide:

  • what list of elements we’re going to fade (all the children of listParent who have a UICrossFader)
  • after how much time it will start
  • how much time each element should stay visible before we start the fading
  • if this sequence should be looped or not
    IEnumerator Start()
    {
    	bool executeLoop = true;
        UICrossFader[] crossfadeImagesList=null;

        yield return new WaitForSeconds(startDelay);

As you may know (or not, since as of now this seems to be an undocumented feature), the Start function of Monobehaviour can be used as a coroutine. All that you need to do is to change its return type to IEnumerator. I know, it’s crazy, but they did it this way and it IS very comfortable not to write a coroutine call every time you need it.

In the beginning we just declare a couple of variables we’ll need later on and let the execution to wait for the right time to start.

    if (listParent != null)
    {       
        crossfadeImagesList = listParent.GetComponentsInChildren<UICrossFader>();
        if (crossfadeImagesList.Length <= 0)
            Debug.LogError("SequenceFader error, list empity, check that children of listParent do have UICrossFader component");
        else
        {
             //everything else...
        }
    }
    else
    {
        Debug.LogError("SequenceFader error, listParent not assigned!");
    }

The first thing we do is to check that listParent has been set, otherwise an error is due. Then we need¬†to see is if it has stuff to crossfade in it or another error must be raised. And at last, if we have something to crossfade we’ll start doing so.

Now let’s get into that “everything else”:

                foreach (var item in crossfadeImagesList)
                {
                    item.Preset(false);
                }

                System.Collections.IEnumerator crossfadeEnumerator = crossfadeImagesList.GetEnumerator();
                crossfadeEnumerator.MoveNext();
                UICrossFader currentFader = (UICrossFader)crossfadeEnumerator.Current;
                currentFader.Preset(true);
                UICrossFader last = currentFader;
                yield return new WaitForSeconds(interval);

Another bit of initializations is now due: we begin with nothing visible, then we get the first element of the list and set it as visible. We also record it as a last element to have been visible. Then we wait the moment we’ll show the next¬†image.

                while (executeLoop)
                {
                    currentFader = (UICrossFader)crossfadeEnumerator.Current;
                    last.FadeOut();
                    currentFader.FadeIn();
                    last = currentFader;
                    if (!crossfadeEnumerator.MoveNext())
                    {
                        if (doLoop)
                        {
                            crossfadeEnumerator = crossfadeImagesList.GetEnumerator();
                            crossfadeEnumerator.MoveNext();
                        }
                        else executeLoop = false;
                    }
                    yield return new WaitForSeconds(interval);
                }

Usually one would just have the iterator MoveNext be called inside the while condition, but now we may want to stay inside the loop once that all the list is traversed and start from the beginning, so instead we’ll use the executeLoop flag. At each loop we get a new current element to fade in and start the crossfade by calling in the appropriate functions of the UICrossFader, then we update the last and check if¬†this was the last element of the list while at the same time advancing the enumerator with its MoveNext.

to loop or not to loop? this is the question!
to loop or not to loop? this is the question!

If it was the last element, then we have 2 possibilities: if we should not loop, the executeLoop is set to false and we’re out¬†for good; if we should loop then the enumerator is reset to its first element.

After this check we just wait one more interval before continuing the loop.

That’s all folks!

And we’re done, now your images will transition one after another with a nice crossfade. You can use this for all sorts of purposes and even extend it to a “video” fader in which a dialog is played one image after another (I did so for a then killed project back in the day). If you want to get updates on the next tutorials or just download the full project, get on my newsletter, I’ll give a download link for it next week too (maybe with some minor updates since I’m polishing the code to write this tutorial series). For any questions or comments don’t hesitate writing me here¬†or hitting me up on twitter.

P.S.: I’m currently looking¬†for a gamedev job, if you are interested give a look at my portfolio. Also, if you are a fellow Unity3D dev, let’s connect!

using UnityEngine;
using UnityEngine.UI;

public class UICrossFader : MonoBehaviour
{
    [SerializeField]
    Graphic toCrossFade;
    [HideInInspector]
    public float inAlphaLevel = 1f;
    [HideInInspector]
    public float outAlphaLevel = 0f;
    [HideInInspector]
    public float duration = 1f;
    [HideInInspector]
    public bool ignoreTimeScale = false;

    public void FadeIn()
    {
        toCrossFade.CrossFadeAlpha(inAlphaLevel, duration, ignoreTimeScale);
    }

    public void FadeOut()
    {
        toCrossFade.CrossFadeAlpha(outAlphaLevel, duration, ignoreTimeScale);
    }
    
    public void Preset(bool isIn)
    {
        if (isIn)
            toCrossFade.CrossFadeAlpha(inAlphaLevel, 0f, true);
        else
            toCrossFade.CrossFadeAlpha(outAlphaLevel, 0f, true);
    }
}
public class SequenceFader : MonoBehaviour
{
    public GameObject listParent;
    [SerializeField]
    float startDelay;
    [SerializeField]
    float interval = 4f;
    [SerializeField]
    bool doLoop = true;
    bool executeLoop = true;


    IEnumerator Start()
    {
    	bool executeLoop = true;
        UICrossFader[] crossfadeImagesList=null;

        yield return new WaitForSeconds(startDelay);

        if (listParent != null)
        {       
            crossfadeImagesList = listParent.GetComponentsInChildren<UICrossFader>();
            if (crossfadeImagesList.Length <= 0)
                Debug.LogError("SequenceFader error, list empity, check that children of listParent do have UICrossFader component");
            else
            {
                foreach (var item in crossfadeImagesList)
                {
                    item.Preset(false);
                }

                System.Collections.IEnumerator crossfadeEnumerator = crossfadeImagesList.GetEnumerator();
                crossfadeEnumerator.MoveNext();
                UICrossFader currentFader = (UICrossFader)crossfadeEnumerator.Current;
                currentFader.Preset(true);
                UICrossFader last = currentFader;

                yield return new WaitForSeconds(interval);
                while (executeLoop)
                {
                    currentFader = (UICrossFader)crossfadeEnumerator.Current;
                    last.FadeOut();
                    currentFader.FadeIn();
                    last = currentFader;
                    if (!crossfadeEnumerator.MoveNext())
                    {
                        if (doLoop)
                        {
                            crossfadeEnumerator = crossfadeImagesList.GetEnumerator();
                            crossfadeEnumerator.MoveNext();
                        }
                        else executeLoop = false;
                    }
                    yield return new WaitForSeconds(interval);
                }
            }
        }
        else
        {
            Debug.LogError("SequenceFader error, listParent not assigned!");
        }
    }
}

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Input management with Dependency Injection

Object oriented programming has a lot of patterns that can be very useful for making games. One of those patterns is the Dependency Injection, a pattern that helps to decouple classes that would otherwise be tightly connected. So let’s take something that’s really connected and see how dependency injection can help us: the input management.

Wait what’s this Dependency Injection?


Usually if you have a¬†thing (call it client) that uses another thing (call it service), when you change the service, then you have to also change the client. And that’s bad. Let’s say the client is your game logic and you are porting your game from¬†pc to mobile, and that therefore you need to switch from a keyboard + mouse input to a touch one. Since all inputs are changed (perhaps radically since your WASD is now a UI element) you now need to change some input-read line in your game logic even if you used an intermediate class to get those button inputs.

The Dependency Injection way to do it instead is to have the input manager call the game logic functions. Without it knowing whose functions they are. You just set them as callbacks and call them when needed. Who sets the callbacks? The naive option is: the client. But then you still have a direct dependency between the classes. Enter the DIC: Dependency Injection Container. He takes the callbacks from the client and gives them to the service, thus eliminating the dependency between them (and adding another class to your code, that’s not a free lunch).

And what are those de-leee-gates?

Delegate

A delegate is just a way to pass a function as an argument, it can also be stored as a variable and given a type name to be checked so that only the functions that match a certain signature can be stored or passed as a delegate of a specific type.

Let’s read some Input!

    [SerializeField]
    string XbuttonName = "Fire1";
   
// other button names 

    [SerializeField]
    string LeftStickHorizontalName = "Horizontal";
    [SerializeField]
    string LeftStickVerticalName = "Vertical";
//other axis names

First of all we’ll need the names of the input buttons and axis we’re going to read, for this example I’ve used a regular xbox controller. We’ll do this with the old unity input system, not the (currently) experimental one, so we’ll need a string name for it.¬†If you’ve read my other tutorials you know I’ve a personal feud with strings, but this is one of the few cases you really have to use them: if you are building an input manager you don’t want to force whoever uses it to edit code just to rename an input field, so you really want to have that in the inspector, which means a serialized string. Notice that for¬†thumbsticks we’ll need two axis per stick, so two thumbsticks means four¬†axis.

    public static InputManager instance;

    [SerializeField]
    InputManagerDIC inputDIC;

    [SerializeField]
    float triggerSensibility = 0.2f;

As for the other variables, the instance reference will be used to make this class a singleton, the inputDIC is needed to ask for the injection, and the trigger sensibility trashold will be used to get a button behaviour from an axis, because back in my days triggers were fucking buttons and I like it that way.

public delegate void buttonReaction();
public delegate void axisEffect(Vector2 axisVal);

Although we could make this all with predefined System Actions, I’d rather estabilish a more specific interface that reminds whoever writes the game logic code what is supposed to act as a button and what is supposed to act as an axis. It’s just a reminder,¬†nothing more.

good old controller
good old controller
    public static buttonReaction XbuttonPress = delegate () { };
    //other press callbacks ...
    public static buttonReaction XbuttonPressContinuous = delegate () { };
    //other continuous callbacks 
    public static axisEffect leftStickEffect = delegate (Vector2 a) { };
    public static axisEffect rightStickEffect = delegate (Vector2 a) { };
    public static System.Action InputStartRead = delegate () { };

Each callback is initialized to an empty delegate because if for whatever reason we don’t want to use something, we don’t want a nullreference exception to pop out after the change.

Now, we can define a lot of callbacks for each Input since every button has four relevant conditions:

  • just pressed
  • pressed (continuously)
  • just released
  • released (continuously)

In this example I’ll use four¬†buttons and the triggers and read only two condition for the buttons (just pressed and continuous press) and one for the triggers (continuous press), for each of the conditions I want to read I need to define a callback.

The same goes for what to do with thumbsticks, but in that case I just want to read a direction out of them and let the game logic interpret it.

The last callback isn’t really needed but for this tutorial I’ve also built a public repository where you can download a test scene and I need to clean the UI state at the beginning of every frame, so I want a callback for that too.

void Awake()
    {
        if (instance == null)
            instance = this;
        else
            Destroy(gameObject);
        inputDIC.LoadInputManager();
    }

As I said before this is going to be a Singleton. And at the beginning of execution we want the DIC to inject his callbacks in the InputManager, so we’ll call his loading function here.

    void Update()
    {
        InputStartRead();
        if (Input.GetButtonDown(XbuttonName))
        { XbuttonPress(); }
        //read other buttonDowns
        if (Input.GetButton(XbuttonName))
        { XbuttonPressContinuous(); }
        //read other buttons
        if (Input.GetAxis(leftTriggerName) > triggerSensibility)
        { leftTriggerPressContinuous(); }
        if (Input.GetAxis(rightTriggerName) > triggerSensibility)
        { rightTriggerPressContinuous(); }

        leftStickEffect(new Vector2(Input.GetAxis(LeftStickHorizontalName), Input.GetAxis(LeftStickVerticalName)));
        rightStickEffect(new Vector2(Input.GetAxis(RightStickHorizontalName), Input.GetAxis(RightStickVerticalName)));
    }

And at last here’s the action. At first we call the “start reading” callback, then for each button we check the relevant states. Notice that for the trigger we read an axis input and only when it’s over the trashold we’ve set before we call a callback just as if it were a regular button. From the game logic standpoint that trigger will be undistinguishable from a button, it even uses the same delegate type for the callback. For the thumbsticks instead we’ll read the two axis in a single Vector2 variable and use that to call the appropriate axisEffect callback.

How about a UI class for testing this?

a really simple ui
a really simple ui

I’ve made it as basic as it gets, sorry but no fancy stuff here:

    [SerializeField]
    Toggle xButton;
    //other toggles
    [SerializeField]
    Text rStick;
    //other texts

For each button I’ll set a toggle on and off, while for the sticks I’ll show the direction in a text. All the references are passed with serialized fields in the inspector.

    public void LogCallTLCont() { ShowLogButton(lTriggerButton, "TL Cont"); }
    public void LogCallTRCont() { ShowLogButton(rTriggerButton, "TR Cont"); }
    public void LogCallA() { ShowLogButton(aButton, "A "); }
    public void LogCallB() { ShowLogButton(bButton, "B "); }
    public void LogCallX() { ShowLogButton(xButton, "X "); }
    public void LogCallY() { ShowLogButton(yButton, "Y "); }
    public void LogCallACont() { ShowLogButton(aButton, "A Cont"); }
    public void LogCallBCont() { ShowLogButton(bButton, "B Cont"); }
    public void LogCallXCont() { ShowLogButton(xButton, "X Cont"); }
    public void LogCallYCont() { ShowLogButton(yButton, "Y Cont"); }
    public void LogCallL(Vector2 direction) { ShowLogAxis(lStick, "L stick with dir", direction); }
    public void LogCallR(Vector2 direction) { ShowLogAxis(rStick, "R stick with dir", direction); }

    void ShowLogButton(Toggle toggle, string text)
    {
        toggle.isOn = true;
        Debug.Log(text);
    }

    void ShowLogAxis(Text field, string text, Vector2 direction)
    {
        field.text = direction.ToString();
        Debug.Log(text + direction);
    }

All the callbacks are actually using the same couple of functions, logging and setting an UI element each time. But who’s going to reset all those toggles when we didn’t¬†read the¬†button’s release? Our reset function of course:

    public void ResetUI()
    {
        xButton.isOn = false;
        yButton.isOn = false;
        aButton.isOn = false;
        bButton.isOn = false;
        lTriggerButton.isOn = false;
        rTriggerButton.isOn = false;
        rStick.text = Vector2.zero.ToString();
        lStick.text = Vector2.zero.ToString();
    }

¬†It’s Injection time

dependency injection input time
dependency injection input time

Also the DIC is really simple, all it does is to set the callbacks in the InputManager, so it only needs a load function and a field to specify from which class instance it should take the callbacks:

    [SerializeField]
    UserExample target;
    public void LoadInputManager()
    {
        InputManager.XbuttonPress = target.LogCallX;
        InputManager.YbuttonPress = target.LogCallY;
        InputManager.AbuttonPress = target.LogCallA;
        InputManager.BbuttonPress = target.LogCallB;
        InputManager.XbuttonPressContinuous = target.LogCallXCont;
        InputManager.YbuttonPressContinuous = target.LogCallYCont;
        InputManager.AbuttonPressContinuous = target.LogCallACont;
        InputManager.BbuttonPressContinuous = target.LogCallBCont;
        InputManager.leftStickEffect = target.LogCallL;
        InputManager.rightStickEffect = target.LogCallR;
        InputManager.leftTriggerPressContinuous = target.LogCallTLCont;
        InputManager.rightTriggerPressContinuous = target.LogCallTRCont;
        InputManager.InputStartRead = target.ResetUI;

    }

So, as you can see the InputManager has no dependecy towards the client class and the UserExample doesn’t even know that his functions are¬†linked to an input. Any maintenance change on either class will stop here in the DIC and will be as trivial as just changing wich callback is assigned to what variable since that’s all that can happen here.

But what if I just changed Input Settings instead of doing all that?

That’s cool and that’s also the proper way to do it (until you are not porting from pc/console to mobile). Really, until you are not changing between radically different input sources in unity, you’re better off using Unity3d’s input system to remap controls¬†and avoid changing code. I only used the Input management as the easiest-to-explain example, if one thinks this technique is just for that, he’s totally missing the point. This technique can (and according to some people should) be used for absolutely everything.

That’s all folks

Thanks for the read. This time no copy-paste, you get a repository with the whole project already set up and ready to use here. If you have any questions or comments please do express that either in the comments here or just hit me on twitter. And if you don’t want to lose my future stuff, consider my newsletter.

P.S.: I’m currently looking for a job,¬†if you are interested take a look at my portfolio.

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Pooling in unity3d – a simple tutorial

We’ve been through it: instantiations at runtime can be a huge problem. It can cause a spike in the workload of any game, and then you have to find ways to fix that when it happens. Or you can use a technique called pooling, like pros do.

you can have a lot of pools
you can have a lot of pools

What’s pooling in unity3d?

The same it should be anywhere else: instead of instantiating objects wherever and whenever you need them, you create a pool beforehand (while the loading screen is on) and then when it’s needed you grab an object from the pool.

To make a pool you’ll need 3 things:

  • a pool script
  • a pool interface
  • a pool host
    Let’s start with the trivial stuff first.

Pool interface iPoolable

It's objects in a pool, got it?
It’s objects in a pool, got it?

When an object is “in the pool” it’s better if it’s inactive in all conceivable ways. But of course the pool script should not be required to know how to inactivate every single object because that would be a maintenance nightmare. Therefore we’ll need this simple interface to be implemented by one script at the root of the pooled gameobject:

public interface iPoolable
{
  Pool source { get; set; }
  void Initialize();
  void Deactivate();
}

The source property is a reference to the pool script that holds the object’s pool, oviously. Why should the pooled object need to know that? So that it can return to the pool by itself by calling:

source.PutInPool(gameObject);

The Initialize function’s duty is to prep the object to enter the scene in an active state, just as if it was actually instantiated at the very end of the function.

The Deactivate function instead is called when the object would have been removed from the scene weren’t we pooling it. It has to minimize the impact on both memory and cpu/gpu for holding the object in the pool. This usually involves at least a SetActive(false) and a stop on all coroutines on the object if any, but one can get creative and also switch layers and stuff like that.

The pool host

This isn’t a strictly needed element for pooling to work, but if like me you don’t enjoy having a bunch of gameobjects floating around in your game’s hierarchy, you want to do this:

public class PoolHost : MonoBehaviour
{
    public static PoolHost instance;

    void Awake()
    {
        if (instance == null)
            instance = this;
        else
            Destroy(gameObject);
    }

    public static void Hold(GameObject poolable)
    {
        poolable.transform.parent = instance.transform;
    }
}

It’s just a singleton with one static function to hold the pooled gameobject inside the one this script is attached to. You could, of course, separate the pools by type, but why?

but why?

The actual Pool script

This script is designed to be used as a field in another script. This approach enables you to do all sorts of customizations, for instance your weapons can have a pool of bullets, so that you can make more weapons by changing the bullet prefab reference in the pool field.

[System.Serializable]
public class Pool
{
    Stack<GameObject> pool;
    [SerializeField]
    int initialPoolSize;
    [SerializeField]
    int expansionPoolSize;
    [SerializeField]
    GameObject prefabReference;

    public void Populate()
    {
        //stuff..
    }

    void ExpandPoolPopulation(int amount, GameObject prefabReference)
    {
        //stuff..
    }

    public GameObject Allocate()
    {
        //stuff..
    }

    public void PutInPool(GameObject handled)
    {
        //stuff..
    }
}

As you can see it’s entirely serializable, so that we may edit its parameters from the inspector when we use it as a field. The initialPoolSize variable is used to set how many gameobjects we should instantiate in the Populate call, while the expansionPoolSize indicates how many should be instantiated when the pool is empity and an allocation is requested.

Guess what the prefabReference is. Right! It’s where you drag-and-drop your prefab from the project explorer.

drag-and-drop

So, the idea is that the object using this pool needs to Populate it at first, then it can Allocate instances from it and when it’s done it (or the pooled object itself) can PutInPool back the pooled thingy.

Now, let’s see the single functions:

    public void Populate()
    {
        pool = new Stack<GameObject>();
        ExpandPoolPopulation(initialPoolSize, prefabReference);
    }

This one is damn easy. It initializes the pool variable with a brand new Stack (of course you can use my garbageless list instead!), then calls ExpandPoolPopulation to instantiate the very first objects in the pool.

    void ExpandPoolPopulation(int amount, GameObject prefabReference)
    {
        for (int j = 0; j < amount; j++)
        {
            GameObject result = GameObject.Instantiate(prefabReference);
            result.GetComponent<iPoolable>().source = this;
            PutInPool(result);
        }
    }

This one is really straightforward too. It just instantiates the prefab a number of times, sets the source for the object and then puts it in the pool. It’s as obvious as it gets.

    public void PutInPool(GameObject handled)
    {
        handled.GetComponent<iPoolable>().Deactivate();

        handled.SetActive(false);

        handled.transform.position = Vector3.zero;
        handled.transform.rotation = Quaternion.identity;
        handled.transform.localScale = Vector3.one;

        PoolHost.Hold(handled);

        pool.Push(handled);
    }

Here the first thing to do is to call the Deactivate function through the iPoolable interface so that it performs the object specific stuff, then we take care of the operations that can be done on every single gameobject, like moving it in the poolHost so that our hierarchy is clean. Obviously you don’t necessarily have to reset the transform values, but if that’s something that causes you problems, well, you are doing something very wrong.

Last thing we do, is to put the reference to the gameobject inside the pool stack, so that its ready to be used.

    public GameObject Allocate()
    {
        if (pool.Count <= 0)
            ExpandPoolPopulation(expansionPoolSize, prefabReference);

        GameObject result = pool.Pop();

        result.SetActive(true);
        result.GetComponent<iPoolable>().Initialize();
        return result;
    }

Now that we know how the stuff gets inside the pool we can look to how it’s taken out of it. The allocate function first of all checks wether the pool is empity or not. It it’s empity you need more stuff, so you have to expand it.
If this allocation is too much for you, just raise the initialPoolSize and put an error message here, but you are more likely to be better off this way.

Then it recovers the gameobject instance from the pool and initializes it so that it’s prepared just as if it was instantiated right now and returns it to whatever script called this function.

That’s all folks!

Thanks for the attention, copy-pasteable code is just ahead. Remember that the populate function is quite expensive so you’ll probably need to use it behind a loading screen, if you don’t need to then you probably don’t need pooling either.

For any question please don’t hesitate to contact me on my twitter account, and if you don’t want to miss my next tutorial¬†consider subscribing to¬†my Newsletter!

And by the way, I’m currently looking for a job, if you think you may have one for me, take a look at my portfolio!

public interface iPoolable
{
  Pool source { get; set; }
  void Initialize();
  void Deactivate();
}


public class PoolHost : MonoBehaviour
{
    public static PoolHost instance;

    void Awake()
    {
        if (instance == null)
            instance = this;
        else
            Destroy(gameObject);
    }

    public static void Hold(GameObject poolable)
    {
        poolable.transform.parent = instance.transform;
    }
}


[System.Serializable]
public class Pool
{
    Stack<GameObject> pool;
    [SerializeField]
    int initialPoolSize;
    [SerializeField]
    int expansionPoolSize;
    [SerializeField]
    GameObject prefabReference;

    public void Populate()
    {
        pool = new Stack<GameObject>();
        ExpandPoolPopulation(initialPoolSize, prefabReference);
    }

    void ExpandPoolPopulation(int amount, GameObject prefabReference)
    {
        for (int j = 0; j < amount; j++)
        {
            GameObject result = GameObject.Instantiate(prefabReference);
            result.GetComponent<iPoolable>().source = this;
            PutInPool(result);
        }
    }

    public GameObject Allocate()
    {
        if (pool.Count <= 0)
            ExpandPoolPopulation(expansionPoolSize, prefabReference);

        GameObject result = pool.Pop();

        result.SetActive(true);
        result.GetComponent<iPoolable>().Initialize();
        return result;
    }

    public void PutInPool(GameObject handled)
    {
        handled.GetComponent<iPoolable>().Deactivate();

        handled.SetActive(false);

        handled.transform.position = Vector3.zero;
        handled.transform.rotation = Quaternion.identity;
        handled.transform.localScale = Vector3.one;

        PoolHost.Hold(handled);

        pool.Push(handled);
    }
}

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

C# Garbageless List – a 0-garbage optimization for my event dispatcher

  • Part 1: basics event system
  • Part 2: event picker drawer for in-editor usage
  • Part 3: debugging with platform dependant compilation
  • Part 4: multiple dispatchers
  • Part 5: optimization [you are here]

This time you get to see the final form!

final form
Final form, for real.

As we’ve anticipated last week, there’s a problem with my implementation of the event dispatcher. It’s garbage. No, not the dispatcher, the problem is garbage. Lots and lots of garbage generated by hidden “optimizations” the compiler makes and by the c# implementation of the delegate.

A C# garbageless list? proof or GTFO!

The data was extracted sandwitching each call between two profiler BeginSample/EndSample calls, all inside a for loop, adding each time a dinamically-generated delegate so that they were actually different functions (with different hashcodes). Now, the main source of garbage was identified and eliminated thanks to this wonderful piece by somasim, it was the use of enums as keys for my double dictionary indexed by channels/events. Each time a reference was made, the kind compiler tought to box the enum with an object that would immediately be discarded, thus generating garbage.

garbageless list profile info
garbageless list profiler info – humbly tagged as “perfection”

Step 1: Garbageless dictionary use

To avoid that boxing requires a huge change in the implementation: indexing by integers. That would be obtained by casting the enums to int when calling the function, which doesn’t even require an explicit change in the calls, thus preserving retrocompatibility of the class. Man, I love enums.
This is what our data structure initializer becomes:

Dictionary<int, Dictionary<int, GarbagelessList>> initializeDicts()

Step 2: actually implementing the C# garbageless list

The second issue was the usage of the delegate += operator to enqueue delegates one to another. And here’s where it gets absolutely painful: the usual collections in c# always generate garbage when used.¬†I tested them a lot and then decided that I can do better. (Or actually: that I can build something more optimized to my needs)

Now, this will need to go quite low level. Here’re the rules to build a garbageless list:

  1. thou shall not instantiate classes
  2. thou shall log an error each time a slight amount of garbage is generated
  3. thou shall not use default c# implemented data structures
  4. thou shall not stop until you beat the c# dictionary
  5. thou shall never ever free a bit of memory

Wait didn’t you say “garbageless”?

Here’s the problem:¬†allocating an array of elements will always generate garbage in c# (100 int means 440B garbage, 200 goes for 880B and so on), and even creating an empity class can’t be done without wasting 16 bytes. So, in reality, we’ll get garbage both on the instantiation and whenever our default initial capacity is exceeded.

Now, the code this time is awfully long and all needed, so I won’t explain every detail¬†or I’d need a new series just for this. But I’ll run you through the core concepts before giving all the code to copy-paste:

  • encode everything into arrays of structs
  • use linked lists and manage their memory manually
  • use indexing to speed up the search

Also, before we start, be aware of the pros and cons: while this list doesn’t generate any garbage¬†on regular use, it does generate garbage when¬†constructor is called and also when the default capacity limit is passed; also, be aware that when you pass a delegate to it the function will be boxed by the compiler if you didn’t construct a delegate object in advance.¬†And last but not least, the price for the zero garbage generation is… not freeing memory. Which means that you should budget your memory accurately when setting the default capacity variables. If you can muster a few hundred bytes of garbage every now and then, just remove the log error calls and set a reasonably small default value, so that you won’t allocate more than what peak consumption requires. If you absolutely want that 0, use a high default value.

First of all, meet the node struct:

    struct node
    {
        public gameEventHandler content;
        public int nextIndex;
        public int lastIndex;
    }

The next 9 hundreds of lines of code will be used to manage this cutie. It will be hosted into a jagged bidimensional Array of nodes, this means we can instantiate the second lot just by initializing the second field of the array, instead of using the Array.Resize call (which creates a bigger copy of the array and transforms the old one in garbage).

    void addBatch()
    {
        if (capacity > 0)
            UnityEngine.Debug.LogError("Had to extend GarbagelessList, set a higher DEFAULTCAPACITY for GarbagelessList or get garbage");
        ArrayData[capacity / DEFAULTCAPACITY] = new node[DEFAULTCAPACITY];

        for (int i = capacity; i < capacity + DEFAULTCAPACITY; i++)
        {
            setNextIndexPos(i, i + 1);
            setLastIndexAtPos(i, i - 1);
        }
        capacity += DEFAULTCAPACITY;

    }

The variablecapacity¬†will be used to store the amount of nodes currently available, when this function is called by the constructor it has value 0, so the log error won’t trigger. To index the array data we use a int on int division that will return the highest¬†integer smaller¬†than the actual float result. After the instantiation is done, it’s time for the initialization, wich is taken care of inside the for. We’re building here a linked list of free nodes, so each node will link to its successor and the first one’s address will be stored inside the freeTail¬†variable.

Now let’s look at how to get the position of a node inside a¬†bidimensional array:

void setNextIndexPos(int key, int value)
    {
        ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].nextIndex = value;
    }

The first operation is again an int on int division, while the second is the module operator that gives us the remainder of it. So for each integer one and only one node is indexed. I wrapped the access this way for two reasons: first, you can’t just return the node and edit it since it’s a struct, second, I experimented quite a bit before getting to the¬†bidimensional array of structs and didn’t want to rewrite the access everywhere each time. Feel free to substitute the access functions with direct assignments/reads.

    int getFree()
    {
        //if arraydata is full, we need to expand it.
        if (Count >= capacity)
            addBatch();
        int result = freeHead;
        freeHead = getNextIndexAtPos(freeHead);
        return result;
    }

Another important bit of code is here, this is the function I use to get a free node: first it checks if there are any free nodes by comparison between the count and the current capacity of the list, then if needed it adds a new batch, and after that it updates the head of the free nodes list with the successor of the current one.

Access to the data

Now, to grant a fast access to the data I implemented an enumerator-like series of methods

    bool enumeratorJustReset = false;
    int currentIndex;
    public gameEventHandler Current
    {
        get
        {
            return getContentAtPos(currentIndex);
        }
    }


    public bool MoveNext()
    {
        if (Count == 0)
            return false;
        if (enumeratorJustReset)
        {
            enumeratorJustReset = false;
            return true;
        }
        if (currentIndex == tail)
            return false;
        currentIndex = getNextIndexAtPos(currentIndex);
        return true;
    }

    public void Reset()
    {
        currentIndex = head;
        enumeratorJustReset = true;
    }

Notice that I’ve not implemented the¬†IEnumerator¬†interface. Why? because that would require an object instantiation, and that would violate our 1st commandment. Instead I implemented the usual Reset-MoveNext-Current approach as methods of the class, same result, no garbage.

So, if I just stopped with that stuff (and all the accessory code to actually use it, like the add and remove functions) I’d get a zero-garbage list with horrible time consumption. By nature of a linked list, access is necessarily tape-like: you always need to traverse all the list from the head to the element you need to remove it. In my tests that meant a couple of orders of magnitude slower access when compared to standard Dictionary (especially on removal, which is where it’s required to find a specific delegate to erase).

unacceptable

So how can I solve this? Well, I cheated a bit and went on memory lane back to my computer science major years, and remembered a useful tool: indexing. Of course the index needed to allow for addition and removal and had to resist all temptations of garbage generation, so it would need to be a tree structure encoded on an array. And not just any tree structure, but a balanced one. So I opted to implement a Red-Black Tree (since every open source implementation I found relied on classes and other garbage-prone stuff). From scratch.

Fuck.

A tree of knowledge

Now, the indexing would require to identify the content of the original nodes and then use that to get the locations on the regular data array, but since you may very well add multiple identical listeners to a single event there was also the possibility of duplicates, so each index node would need to be able to refer to multiple locations. Thus an array was needed inside each index node. Meet the index’s own node:

struct indexerCouple
    {
        public int[] indexedArrayData;//-1=free slot
        public int hashValue;
        public int leftChildIndex;//default is 0
        public int rightChildIndex;//default is 0
        public int lastIndex;//default is 0
        public bool isRed;

        public int ContentCount;

        public void resize()
        {
            int oldSize = indexedArrayData.Length;
            Array.Resize<int>(ref indexedArrayData, indexedArrayData.Length * 2);
            for (int i = oldSize; i < indexedArrayData.Length; i++)
            {
                indexedArrayData[i] = -1;
            }
            UnityEngine.Debug.LogError("Had to extend indexedArrayData, set a higher DEFAULTCAPACITY for BST indexer indexedArrayData or get garbage");
        }
    }

The indexing parameter will therefore be the result of a GetHashCode function called on the gameEventHandler to be indexed. Addition and removal in an ordered tree are very simple, you just navigate the tree until you find either a leaf or a null pointer and then get one from the free list to add or put it in said list to remove.

    int findHash(int hashvalue, int currentNode)
    {
        if (currentNode == -1) return -1;
        if (getHashAtPos(currentNode) == hashvalue) return currentNode;
        return findHash(hashvalue, (getHashAtPos(currentNode) > hashvalue) ? getLeftChildIndexAtPos(currentNode) : getRightChildIndexAtPos(currentNode));
    }

As you can see the find function is actually quite simple, but it’s recursive, it calls itself for each node it goes through. If you never heard about this stuff here’s a guide to learn about it.

For RB trees insertion and deletion it’s not as¬†simple as in lists or in plain binary search trees. If you want to get to understand what goes on with that part of the code, there’s no short cut. Here are some resources:

Basically what happens in insertion is that first I check if the node already¬†exists, if so, I just add a new index to his array. Otherwise¬†a new node is added to the tree and the violation of the RB properties is dealt with in a series of possible cases. When deleting, again, first I find if the node exists, then if it can just be substituted with a “lower” one I just copy the data and delete the other, otherwise the node is removed and again the RB property violations are dealt with in a series of cases.

I’ve also left a couple of debugging facilities for any problem that may eventually emerge (this optimized version¬†is tested, but has never been used in production unlike its last version), I’d love to hear from anyone who tries to implement this code.

And with the use of this indexer we may finally access the data in our garbageless list by the means of a binary search, which is quite fast and passes the comparison with c# dictionaries.

And that’s all folks!

I thank you for bearing it with me with this ordeal of a tutorial, I know that a lot is missing but we’re already at almost a couple thousand words and probably only the truly desperate or genuinely curious made it this far. For any question on the unclear aspects of the code please don’t hesitate to contact me either through my Newsletter¬†or my twitter account.

And by the way, I’m currently looking for a job, if you think you may have one for me, take a look at my portfolio!

using System;
using System.Collections.Generic;

public class GarbagelessList
{
    #region enumerator
    bool enumeratorJustReset = false;
    int currentIndex;
    public gameEventHandler Current
    {
        get
        {
            return getContentAtPos(currentIndex);
        }
    }


    public bool MoveNext()
    { 
        if (Count == 0)
            return false;
        if (enumeratorJustReset)
        {
            enumeratorJustReset = false;
            return true;
        }
        if (currentIndex == tail)
            return false;
        currentIndex = getNextIndexAtPos(currentIndex);
        return true;
    }

    public void Reset()
    {
        currentIndex = head;
        enumeratorJustReset = true;
    }

    public void Dispose()
    {
        throw new NotImplementedException();
    }
    #endregion


    const int DEFAULTCAPACITY = 1000;
    int capacity;
    public int Count { get; private set; }
    int head;
    int tail;
    int freeHead;
    node[][] ArrayData;
    int newIndexCache;
    struct node
    {
        public gameEventHandler content;
        public int nextIndex;//default is 0
        public int lastIndex;//default is 0
    }
    BST bstIndex;


    #region public functions
    public GarbagelessList()
    {
        ArrayData = new node[DEFAULTCAPACITY][];
        bstIndex = new BST();
        addBatch();
        head = -1;
        tail = -1;
        freeHead = 0;
        Count = 0;
    }

    public void Add(gameEventHandler toAdd)
    {
        //only on the first add we use an ad hoc setup
        if (head == -1)
        {
            head = freeHead;
            tail = freeHead;
            freeHead = getNextIndexAtPos(freeHead);
            setContentAtPos(head, toAdd);
            bstIndex.Add(head, toAdd.GetHashCode());
        }
        else
        {
            newIndexCache = getFree();
            freeHead = getNextIndexAtPos(freeHead);
            setNextIndexPos(tail, newIndexCache);
            setLastIndexAtPos(newIndexCache, tail);
            tail = newIndexCache;
            setContentAtPos(newIndexCache, toAdd);
            bstIndex.Add(newIndexCache, toAdd.GetHashCode());
        }
        ++Count;
    }

    public void Remove(gameEventHandler toRemove)
    {
        int i = bstIndex.getIndexList(toRemove.GetHashCode())[0];

        if (getContentAtPos(i) == toRemove)
        {
            if (head != i)
                setNextIndexPos(getLastIndexAtPos(i), getNextIndexAtPos(i));
            else
            { head = getNextIndexAtPos(i); }
            if (tail != i)
                setLastIndexAtPos(getNextIndexAtPos(i), getLastIndexAtPos(i));
            else
            { tail = getLastIndexAtPos(i); }

            setLastIndexAtPos(freeHead, i);
            setNextIndexPos(i, freeHead);
            freeHead = i;

            --Count;
        }
    }
    /*
    public gameEventHandler this[int key]
    {
        get
        {
            return getContentAtPos(key);
        }
        set
        {
            setContentAtPos(key, value);
        }
    }
    */
    #endregion
    #region arrayWrap
    gameEventHandler getContentAtPos(int key)
    {
        return ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].content;
    }
    void setContentAtPos(int key, gameEventHandler value)
    {
        ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].content = value;
    }
    int getNextIndexAtPos(int key)
    {
        return ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].nextIndex;
    }
    void setNextIndexPos(int key, int value)
    {
        ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].nextIndex = value;
    }
    int getLastIndexAtPos(int key)
    {
        return ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].lastIndex;
    }
    void setLastIndexAtPos(int key, int value)
    {
        ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].lastIndex = value;
    }
    #endregion

    #region memory management
    int getFree()
    {
        //if arraydata is full, we need to expand it.
        if (Count >= capacity)
            addBatch();
        int result = freeHead;
        freeHead = getNextIndexAtPos(freeHead);
        return result;
    }
    void addBatch()
    {
        if (capacity > 0)
            UnityEngine.Debug.LogError("Had to extend GarbagelessList, set a higher DEFAULTCAPACITY for GarbagelessList or get garbage");
        ArrayData[capacity / DEFAULTCAPACITY] = new node[DEFAULTCAPACITY];

        for (int i = capacity; i < capacity + DEFAULTCAPACITY; i++)
        {
            setNextIndexPos(i, i + 1);
            setLastIndexAtPos(i, i - 1);
        }
        capacity += DEFAULTCAPACITY;

    }
    #endregion

}


public class BST
{
    indexerCouple[][] ArrayData;
    const int DEFAULTCAPACITY = 1000;
    public BST()
    {

        ArrayData = new indexerCouple[DEFAULTCAPACITY][];
        indexlistHead = -1;
        poolHead = -1;
        capacity = 0;
        count = 0;
        addBatch();
    }

    #region indexercoupledef

    struct indexerCouple
    {
        public int[] indexedArrayData;//-1=free slot
        public int hashValue;
        public int leftChildIndex;//default is 0
        public int rightChildIndex;//default is 0
        public int lastIndex;//default is 0
        public bool isRed;

        public int ContentCount;

        public void resize()
        {
            int oldSize = indexedArrayData.Length;
            Array.Resize<int>(ref indexedArrayData, indexedArrayData.Length * 2);
            for (int i = oldSize; i < indexedArrayData.Length; i++)
            {
                indexedArrayData[i] = -1;
            }
            UnityEngine.Debug.LogError("Had to extend indexedArrayData, set a higher DEFAULTCAPACITY for BST indexer indexedArrayData or get garbage");
        }
    }

    public bool blackOrNull(int toCeck) { return (toCeck == -1) ? true : (!getIsRedAtPos(toCeck)); }

    public int Sibling(int index)
    {
        int lastIndex = getLastIndexAtPos(index);
        if (lastIndex == -1)
            return -1;
        if (index == getLeftChildIndexAtPos(lastIndex))
            return getRightChildIndexAtPos(lastIndex);
        else
            return getLeftChildIndexAtPos(lastIndex);
    }
    public bool isInternal(int index)
    {
        int lastIndex = getLastIndexAtPos(index);
        if (lastIndex == -1)
            return false;
        if (getLastIndexAtPos(lastIndex) == -1)
            return false;
        if (getRightChildIndexAtPos(lastIndex) == index && getLeftChildIndexAtPos(getLastIndexAtPos(lastIndex)) == lastIndex)
            return true;

        if (getLeftChildIndexAtPos(lastIndex) == index && getRightChildIndexAtPos(getLastIndexAtPos(lastIndex)) == lastIndex)
            return true;
        return false;
    }

    public int InternalChild(int index)
    {
        int lastIndex = getLastIndexAtPos(index);
        if (lastIndex == -1) return -1;
        if (getRightChildIndexAtPos(lastIndex) == index)
            return getLeftChildIndexAtPos(index);
        else
            return getRightChildIndexAtPos(index);

    }

    public int ExternalChild(int index)
    {
        int lastIndex = getLastIndexAtPos(index);
        if (lastIndex == -1) return -1;
        if (getRightChildIndexAtPos(lastIndex) == index)
            return getRightChildIndexAtPos(index);
        else
            return getLeftChildIndexAtPos(index);
    }


    public bool indexerContains(int index, int value)
    {
        for (int i = 0; i < getIndexedArrayDataAtPos(index).Length; i++)
        {
            if (getIndexedArrayDataAtPos(index, i) == value)
                return true;
        }
        return false;
    }
    public void indexerAdd(int index, int value)
    {
        for (int i = 0; i < getIndexedArrayDataAtPos(index).Length; i++)
        {
            if (getIndexedArrayDataAtPos(index, i) == -1)
            {
                setIndexedArrayDataIndexPos(index, i, value);
                incrementIndexedDataCountAtPos(index);
                return;
            }
        }
        resizeIndexedDataCountAtPos(index);
        indexerAdd(index, value);
    }
    public void indexerRemove(int index, int value)
    {
        for (int i = 0; i < getIndexedArrayDataAtPos(index).Length; i++)
        {
            if (getIndexedArrayDataAtPos(index, i) == value)
            {
                setIndexedArrayDataIndexPos(index, i, -1);
                decrementIndexedDataCountAtPos(index);
                return;
            }
        }
    }
    #endregion

    int indexlistHead;
    int poolHead;
    int capacity;
    int count;
    #region query
    public int[] getIndexList(int hashvalue)
    {
        int result = findHash(hashvalue, indexlistHead);
        if (result == -1)
            throw new KeyNotFoundException();
        else
            return getIndexedArrayDataAtPos(result);
    }
    int findHash(int hashvalue, int currentNode)
    {
        if (currentNode == -1) return -1;
        if (getHashAtPos(currentNode) == hashvalue) return currentNode;
        return findHash(hashvalue, (getHashAtPos(currentNode) > hashvalue) ? getLeftChildIndexAtPos(currentNode) : getRightChildIndexAtPos(currentNode));
    }
    int findHashParent(int hashvalue, int currentNode, int parentNode)
    {
        if (currentNode == -1) return parentNode;
        return findHashParent(hashvalue, (getHashAtPos(currentNode) > hashvalue) ? getLeftChildIndexAtPos(currentNode) : getRightChildIndexAtPos(currentNode), currentNode);
    }

    #endregion

    #region pool Management
    void addBatch()
    {
        if (capacity > 0)
            UnityEngine.Debug.LogError("Had to extend BST, set a higher DEFAULTCAPACITY for BST or get garbage");

        ArrayData[capacity / DEFAULTCAPACITY] = new indexerCouple[DEFAULTCAPACITY];

        poolHead = capacity;
        for (int i = capacity; i < capacity + DEFAULTCAPACITY; i++)
        {
            setLastIndexAtPos(i, i + 1);
            setHashIndexPos(i, -1);
            setLeftChildIndexAtPos(i, -1);
            setRightChildIndexAtPos(i, -1);
            int[] toBeSet = new int[DEFAULTCAPACITY];
            for (int j = 0; j < DEFAULTCAPACITY; j++)
            {
                toBeSet[j] = -1;
            }
            setIndexedArrayDataAtPos(i, toBeSet);
        }
        capacity += DEFAULTCAPACITY;

    }

    int makeNew()
    {
        if (poolHead < capacity)
        {
            int result = poolHead;
            poolHead = getLastIndexAtPos(poolHead);
            setLastIndexAtPos(result, -1);
            resetIndexedDataCountAtPos(result);
            return result;
        }
        else
        {
            addBatch();
            return makeNew();
        }
    }
    void dispose(int toDispose)
    {
        setLastIndexAtPos(toDispose, poolHead);
        poolHead = toDispose;
    }
    #endregion
    #region rotation
    void rotateLeft(int parent)
    {
        //x= parent, y=rightchild
        int rightChildIndex = getRightChildIndexAtPos(parent);
        //move rightchild to his new parent
        if (parent != indexlistHead)
        {
            if (getLeftChildIndexAtPos(getLastIndexAtPos(parent)) == parent)
                setLeftChildIndexAtPos(getLastIndexAtPos(parent), rightChildIndex);
            else
                setRightChildIndexAtPos(getLastIndexAtPos(parent), rightChildIndex);
        }
        else
        {
            indexlistHead = rightChildIndex;
        }
        setLastIndexAtPos(rightChildIndex, getLastIndexAtPos(parent));

        int B = getLeftChildIndexAtPos(rightChildIndex);

        //move parent as the new rightchild's leftchild
        setLeftChildIndexAtPos(rightChildIndex, parent);
        setLastIndexAtPos(parent, rightChildIndex);

        //move B to his new parent
        setRightChildIndexAtPos(parent, B);
        if (B != -1)
            setLastIndexAtPos(B, parent);

    }

    void rotateRight(int parent)
    {
        //x= parent, y=leftchild
        int leftChildIndex = getLeftChildIndexAtPos(parent);

        //move leftChild to his new parent
        if (parent != indexlistHead)
        {
            if (getLeftChildIndexAtPos(getLastIndexAtPos(parent)) == parent)
                setLeftChildIndexAtPos(getLastIndexAtPos(parent), leftChildIndex);
            else
                setRightChildIndexAtPos(getLastIndexAtPos(parent), leftChildIndex);
        }
        else
        {
            indexlistHead = leftChildIndex;
        }
        setLastIndexAtPos(leftChildIndex, getLastIndexAtPos(parent));

        int B = getRightChildIndexAtPos(leftChildIndex);

        //move parent as the new leftchild's rightchild
        setRightChildIndexAtPos(leftChildIndex, parent);
        setLastIndexAtPos(parent, leftChildIndex);

        //move B to his new parent
        setLeftChildIndexAtPos(parent, B);
        if (B != -1)
            setLastIndexAtPos(B, parent);

    }
    /// <summary>
    /// revert child-parent relationship through rotation
    /// </summary>
    /// <param name="parent"></param>
    /// <param name="child"></param>
    void rotationSwap(int parent, int child)
    {
        if (getLeftChildIndexAtPos(parent) == child)
            rotateRight(parent);
        else rotateLeft(parent);
    }
    #endregion
    #region insertion
    public void Add(int indexedArrayElement, int hashvalue)
    {
        if (indexlistHead == -1)
        {
            indexlistHead = makeNew();
            setHashIndexPos(indexlistHead, hashvalue);

            indexerAdd(indexlistHead, indexedArrayElement);
            setIsRedAtPos(indexlistHead, false);

        }
        else
        {
            int holder = findHash(hashvalue, indexlistHead);
            if (holder != -1)
            {
                if (!indexerContains(holder, indexedArrayElement))
                {
                    indexerAdd(holder, indexedArrayElement);
                }
            }
            else
            {
                holder = makeNew();
                setHashIndexPos(holder, hashvalue);
                indexerAdd(holder, indexedArrayElement);
                redBlackInsertion(holder);
            }
        }
        ++count;
    }
    /// <summary>
    /// ensure that this is called only when there is no duplicate for toAdd hashvalue in the tree and tree is not empity
    /// </summary>
    /// <param name="toAdd"></param>
    void redBlackInsertion(int toAdd)
    {

        setIsRedAtPos(toAdd, true);

        int parent = findHashParent(getHashAtPos(toAdd), indexlistHead, -1);
        setLastIndexAtPos(toAdd, parent);
        if (getHashAtPos(parent) > getHashAtPos(toAdd))
            setLeftChildIndexAtPos(parent, toAdd);
        else
            setRightChildIndexAtPos(parent, toAdd);
        if (getIsRedAtPos(parent))
            fixRBPropertyViolations(toAdd);
    }
    void fixRBPropertyViolations(int current)
    {
        if (getIsRedAtPos(current))
        {
            if (getLastIndexAtPos(current) == -1)
                setIsRedAtPos(current, false);//if this is root, just recolor it and double reds are solved
            else if (getIsRedAtPos(getLastIndexAtPos(current)))//if parent of current is black there is no double red violation
            {
                //if parent of current is red, then it has at least a grandparent
                int parent = getLastIndexAtPos(current);
                int uncle = Sibling(parent);
                int grandparent = getLastIndexAtPos(parent);

                if (uncle != -1)
                    if (getIsRedAtPos(uncle) && (!getIsRedAtPos(grandparent)))
                    {//case 1, just switch color and recur
                        setIsRedAtPos(parent, false);
                        setIsRedAtPos(uncle, false);
                        setIsRedAtPos(grandparent, true);
                        fixRBPropertyViolations(grandparent);
                        return;
                    }
                if (isInternal(current))
                {
                    //case 2
                    rotationSwap(parent, current);
                    fixRBPropertyViolations(parent);
                    return;
                }
                //case 3
                setIsRedAtPos(grandparent, true);
                setIsRedAtPos(parent, false);
                rotationSwap(grandparent, parent);
            }

        }

    }
    #endregion
    #region deletion
    public void Remove(int indexedArrayElement, int hashvalue)
    {
        if (indexlistHead == -1)
            throw new KeyNotFoundException();
        int holder = findHash(hashvalue, indexlistHead);
        if (holder == -1)
            throw new KeyNotFoundException();
        else
        {
            if (!indexerContains(holder, indexedArrayElement))
                throw new KeyNotFoundException();
            else
            {
                indexerRemove(holder, indexedArrayElement);
                if (getIndexedDataCountAtPos(holder) <= 0)
                {
                    Remove(holder);
                    --count;
                }

            }
        }
    }
    int getInOrderNextInSubTree(int node)
    {
        if (getRightChildIndexAtPos(node) == -1)
            return -1;

        int result = getRightChildIndexAtPos(node);
        while (getLeftChildIndexAtPos(result) != -1)
            result = getLeftChildIndexAtPos(result);
        return result;
    }
    int getInOrderPreviousInSubTree(int node)
    {
        if (getLeftChildIndexAtPos(node) == -1)
            return -1;

        int result = getLeftChildIndexAtPos(node);
        while (getRightChildIndexAtPos(result) != -1)
            result = getRightChildIndexAtPos(result);
        return result;
    }
    void Remove(int toRemove)
    {
        //if has 2 child, switch places with next.
        if (getRightChildIndexAtPos(toRemove) != -1 && getLeftChildIndexAtPos(toRemove) != -1)
        {
            int toSwitchWith = getInOrderNextInSubTree(toRemove);
            setHashIndexPos(toRemove, getHashAtPos(toSwitchWith));
            setIndexedArrayDataAtPos(toRemove, getIndexedArrayDataAtPos(toSwitchWith));
            Remove(toSwitchWith);
            return;
        }
        else
        {

            //if is red leaf, remove
            if (getRightChildIndexAtPos(toRemove) == -1 && getLeftChildIndexAtPos(toRemove) == -1)
            {
                int parent = getLastIndexAtPos(toRemove);
                if (parent != -1)//if isn't root
                {
                    int sibling = Sibling(toRemove);
                    if (getLeftChildIndexAtPos(parent) == toRemove)//if it's a left child
                    {
                        setLeftChildIndexAtPos(parent, -1);
                    }
                    else//if it's a right child
                    {
                        setRightChildIndexAtPos(parent, -1);
                    }

                    if (!getIsRedAtPos(toRemove))
                        solveDoubleBlack(parent, toRemove, sibling);
                }
                else
                {//if is root, and has no children, delete tree
                    indexlistHead = -1;
                }
                dispose(toRemove);

                return;
            }
            //if has one child
            if (getLeftChildIndexAtPos(toRemove) != -1)
                deleteWithChild(getLeftChildIndexAtPos(toRemove));
            else
                deleteWithChild(getRightChildIndexAtPos(toRemove));
        }

    }

    /// <summary>
    /// substitute the toDestroy node with the source node inside the tree, preserving all the content and label data of source, with flags for each tie
    /// </summary>
    /// <param name="source"></param>
    /// <param name="toDestroy"></param>
    void substitute(int source, int toDestroy, bool preserveLast = false, bool preserveLeft = false, bool preserveRight = false)
    {
        if (preserveLast)
        {
            if (getLastIndexAtPos(toDestroy) != -1)
            {
                if (getLeftChildIndexAtPos(getLastIndexAtPos(toDestroy)) == toDestroy)
                    setLeftChildIndexAtPos(getLastIndexAtPos(toDestroy), source);
                else
                    setRightChildIndexAtPos(getLastIndexAtPos(toDestroy), source);
            }
            setLastIndexAtPos(source, getLastIndexAtPos(toDestroy));
        }
        if (preserveRight)
        {
            if (getRightChildIndexAtPos(toDestroy) != -1)
            {
                setLastIndexAtPos(getRightChildIndexAtPos(toDestroy), source);
            }
            setRightChildIndexAtPos(source, getRightChildIndexAtPos(toDestroy));
        }
        if (preserveLeft)
        {
            if (getLeftChildIndexAtPos(toDestroy) != -1)
            {
                setLastIndexAtPos(getLeftChildIndexAtPos(toDestroy), source);
            }
            setLeftChildIndexAtPos(source, getLeftChildIndexAtPos(toDestroy));
        }
        dispose(toDestroy);

    }

    void deleteWithChild(int child)
    {
        if (getIsRedAtPos(child))
        {
            setIsRedAtPos(child, false);
            substitute(child, getLastIndexAtPos(child), preserveLast: true);
            return;
        }
        else
        {
            substitute(child, getLastIndexAtPos(child), preserveLast: true);
            int parent = getLastIndexAtPos(child);
            if (!getIsRedAtPos(parent))
            {
                //case when there's double black
                if (parent != -1)
                    solveDoubleBlack(parent, child, Sibling(child));
            }
        }
    }

    void solveDoubleBlack(int doubleBlackParent, int child, int sibling)
    {
        //if is root, stop it
        if (doubleBlackParent == -1)
            return;
        //if is chain with red parent, just make it black
        if (sibling == -1 && getIsRedAtPos(doubleBlackParent))
        { setIsRedAtPos(doubleBlackParent, false); return; }
        //if is chain with black parent, just recur on him
        if (sibling == -1 && !getIsRedAtPos(doubleBlackParent))
        { solveDoubleBlack(getLastIndexAtPos(doubleBlackParent), doubleBlackParent, Sibling(doubleBlackParent)); return; }

        //otherwise, both parent and sibling exist, deal with doubleblack
        recognizeDoubleBlackCase(doubleBlackParent, child, sibling);

    }

    void recognizeDoubleBlackCase(int Parent, int doubleBlack, int sibling)
    {
        //PrintPretty(Parent, " parent", true, false);
        //PrintPretty(doubleBlack, " doubleblack ", true, false);
        //PrintPretty(sibling, " sibling", true, false);
        if ((!getIsRedAtPos(Parent)) && (!blackOrNull(sibling)))
            solveCaseOne(Parent, doubleBlack, sibling);
        else if ((getIsRedAtPos(Parent)) && blackOrNull(sibling) && (blackOrNull(InternalChild(sibling)) && blackOrNull(ExternalChild(sibling))))
            solveCaseTwo(Parent, doubleBlack, sibling);
        else if ((!getIsRedAtPos(Parent)) && blackOrNull(sibling) && (blackOrNull(InternalChild(sibling)) && blackOrNull(ExternalChild(sibling))))
            solveCaseThree(Parent, doubleBlack, sibling);
        else if (blackOrNull(sibling) && ((!blackOrNull(InternalChild(sibling))) && blackOrNull(ExternalChild(sibling))))
            solveCaseFour(Parent, doubleBlack, sibling);
        else if (blackOrNull(sibling) && (!blackOrNull(ExternalChild(sibling))))
            solveCaseFive(Parent, doubleBlack, sibling);
        else
            throw new InvalidOperationException(" unrecognized case with p red:" + getIsRedAtPos(Parent) + " sibling blackornull:" + blackOrNull(sibling) + " internal blackornull:" + blackOrNull(InternalChild(sibling)) + " external blackornull:" + blackOrNull(ExternalChild(sibling)));
    }
    void solveCaseOne(int Parent, int doubleBlack, int sibling)
    {
        int futureSibling = InternalChild(sibling);
        bool temp = getIsRedAtPos(Parent);
        setIsRedAtPos(Parent, getIsRedAtPos(sibling));
        setIsRedAtPos(sibling, temp);
        rotationSwap(Parent, sibling);
        solveDoubleBlack(Parent, doubleBlack, futureSibling);
    }
    void solveCaseTwo(int Parent, int doubleBlack, int sibling)
    {
        setIsRedAtPos(sibling, true);
        setIsRedAtPos(Parent, false);
    }
    void solveCaseThree(int Parent, int doubleBlack, int sibling)
    {
        setIsRedAtPos(sibling, true);
        solveDoubleBlack(getLastIndexAtPos(Parent), Parent, Sibling(Parent));
    }
    void solveCaseFour(int Parent, int doubleBlack, int sibling)
    {
        int futureSibling = InternalChild(sibling);
        setIsRedAtPos(InternalChild(sibling), false);
        setIsRedAtPos(sibling, true);
        rotationSwap(sibling, InternalChild(sibling));
        printRBTree();
        isRBTree();
        solveCaseFive(Parent, doubleBlack, futureSibling);
    }
    void solveCaseFive(int Parent, int doubleBlack, int sibling)
    {
        setIsRedAtPos(sibling, getIsRedAtPos(Parent));
        setIsRedAtPos(Parent, false);
        setIsRedAtPos(ExternalChild(sibling), false);

        rotationSwap(Parent, sibling);

    }

    #endregion
    #region debug
    public bool isRBTree() { int pathcountvar = 0; return isRBTree(indexlistHead, out pathcountvar); }
    bool isRBTree(int subTreeHead, out int pathCount)
    {

        if (subTreeHead == -1)
        {
            pathCount = 0;
            return true;
        }
        int lpathCount;
        bool left = isRBTree(getLeftChildIndexAtPos(subTreeHead), out lpathCount);
        int rpathCount;
        bool right = isRBTree(getRightChildIndexAtPos(subTreeHead), out rpathCount);
        pathCount = lpathCount;

        if (getLastIndexAtPos(subTreeHead) == -1)
        {
            pathCount = 0;
            if (getIsRedAtPos(subTreeHead))
            {
                UnityEngine.Debug.LogError("Red root detected! on hash " + getHashAtPos(subTreeHead));
                return false;
            }
        }
        else
        {
            if (!getIsRedAtPos(subTreeHead))
            { ++pathCount; }
            else
                if (getIsRedAtPos(getLastIndexAtPos(subTreeHead)))
            {
                UnityEngine.Debug.LogError("Double red detected! on hash " + getHashAtPos(subTreeHead));
                return false;
            }
        }
        if (lpathCount != rpathCount)
            UnityEngine.Debug.LogError("black child count not matching on hash " + getHashAtPos(subTreeHead));

        return (lpathCount == rpathCount && left && right);

    }
    public void printRBTree()
    {
        if (indexlistHead == -1)
            UnityEngine.Debug.Log(" empity ---");
        else
        {
            UnityEngine.Debug.Log(" BST: ");
            PrintPretty(indexlistHead, "", true, true);
        }

    }
    public void PrintPretty(int index, string indent, bool last, bool recursive)
    {
        string result = indent;
        if (last)
        {
            result += "\\-";
            indent += "  ";
        }
        else
        {
            result += "|-";
            indent += "| ";
        }
        if (getLastIndexAtPos(index) != -1)
        {
            result += (getLeftChildIndexAtPos(getLastIndexAtPos(index)) == index) ? "L->" : "R->";
        }
        int arraycount = getIndexedArrayDataAtPos(index).Length;
        result += getHashAtPos(index) + (getIsRedAtPos(index) ? " _R_" : " _B_") + " content size " + arraycount;
        for (int i = 0; i < arraycount; i++)
        {
            if (getIndexedArrayDataAtPos(index, i) != -1)
                result += " [" + i + "]=" + getIndexedArrayDataAtPos(index, i) + "|";
        }
        UnityEngine.Debug.Log(result);
        if (recursive)
        {
            if (getLeftChildIndexAtPos(index) != -1)
                PrintPretty(getLeftChildIndexAtPos(index), indent, getRightChildIndexAtPos(index) == -1, true);
            if (getRightChildIndexAtPos(index) != -1)
                PrintPretty(getRightChildIndexAtPos(index), indent, true, true);
        }

    }
    #endregion

    #region arrayWrap
    int getIndexedDataCountAtPos(int key)
    {
        return ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].ContentCount;
    }
    void resizeIndexedDataCountAtPos(int key)
    {
        ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].resize();
    }
    void resetIndexedDataCountAtPos(int key)
    {
        ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].ContentCount = 0;
    }
    void incrementIndexedDataCountAtPos(int key)
    {
        ++ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].ContentCount;
    }
    void decrementIndexedDataCountAtPos(int key)
    {
        --ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].ContentCount;
    }
    int[] getIndexedArrayDataAtPos(int key)
    {
        return ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].indexedArrayData;
    }
    void setIndexedArrayDataAtPos(int key, int[] indexedArrayData)
    {
        ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].indexedArrayData = indexedArrayData;
    }
    int getIndexedArrayDataAtPos(int key, int subPos)
    {
        return ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].indexedArrayData[subPos];
    }
    void setIndexedArrayDataIndexPos(int key, int subPos, int value)
    {
        ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].indexedArrayData[subPos] = value;
    }
    int getHashAtPos(int key)
    {
        return ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].hashValue;
    }
    void setHashIndexPos(int key, int value)
    {
        ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].hashValue = value;
    }
    int getRightChildIndexAtPos(int key)
    {
        return ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].rightChildIndex;
    }
    void setRightChildIndexAtPos(int key, int value)
    {
        ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].rightChildIndex = value;
    }
    int getLeftChildIndexAtPos(int key)
    {
        return ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].leftChildIndex;
    }
    void setLeftChildIndexAtPos(int key, int value)
    {
        ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].leftChildIndex = value;
    }

    int getLastIndexAtPos(int key)
    {
        return ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].lastIndex;
    }
    void setLastIndexAtPos(int key, int value)
    {
        ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].lastIndex = value;
    }

    bool getIsRedAtPos(int key)
    {
        return ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].isRed;
    }
    void setIsRedAtPos(int key, bool value)
    {
        ArrayData[key / DEFAULTCAPACITY][key % DEFAULTCAPACITY].isRed = value;
    }
    #endregion
}

 

using System;
using System.Collections.Generic;
using UnityEngine;
/*
This eventHandlerManager supports registering/unregistering listeners for specific events and it's usable 
even in Awake functions since its actual initialization happens in Reset time through the InitializeDictionary static function
This class is usable both as a static reference and as a specific instance.
eventHandlerManager has also a debug feature that can be used to check on wether events 
are happening and where (if any) crashes are happening in a series of callbacks for the same event.
To debug the static instance through the inspector an instance of the script needs to be added in the scene but it's not needed for usage.
It's the script's responsibility to remove listeners when the script is destroyed, otherwise there will be an error in the callback chain that will
block the execution of the successive callbacks.
*/
public class eventHandlerManager : MonoBehaviour
{
    //platform-conditional compilation helps to avoid the debug overhead in the final build.
#if UNITY_EDITOR
    //each flag controls a different debug message
    public bool debug;//print debug when any event is broadcasted, unless debugSpecificEvent is true, in which case only that one event is debugged
    public bool debugAdds; //print debug when any listener is added
    public bool debugRemovals;//print debug when any listener is removed
    public bool debugSpecificEvent;//print debug when a specific event is broadcasted, after each callback, 
    public EventPicker picker; //component that gives the UI element to select the event to debug with dropdowns
    //invoked after a debugAll check
    bool shouldDebugEvent(int ev)
    {
        return (!debugSpecificEvent) || (ev == picker.Selected);
    }
    public static eventHandlerManager globalDebugController;
    public bool debugGlobally;//if true activates the debugging for the global eventHandlerManager
    /*
     in reaction to setting the debugGlobally variable, set the instance as 
    globalDebugController so that its debug fields are used for the static
    functions to control debugging, it also ensures that only one debugGlobally 
    variable can be set as true in all the instances of the eventHandlerManager
    */
    void OnValidate()
    {
        if (debugSpecificEvent)
            debug = true;
        if (debugGlobally)
        {
            eventHandlerManager temp = globalDebugController;
            globalDebugController = this;
            if (temp != null)
                if (temp.GetInstanceID() != this.GetInstanceID())
                    temp.debugGlobally = false;
        }
        else
        {
            if (globalDebugController != null)
                if (globalDebugController.GetInstanceID() == this.GetInstanceID())
                    globalDebugController = null;
        }
    }
#endif
    //these dictionaries host the callback delegates, they are initialized by the initializeDicts functions, so that they are already initialized when the first Awake is called.
    static Dictionary<int, Dictionary<int, GarbagelessList>> globalListenerFunctions = initializeDicts();
    Dictionary<int, Dictionary<int, GarbagelessList>> ListenerFunctions = initializeDicts();
    #region broadcast
    public static void globalBroadcast(MonoBehaviour source, eventChannels evType, int ev, object e)
    {
#if UNITY_EDITOR
        if (globalDebugController == null)
            executeBroadcast(false, false, source, evType, ev, e, globalListenerFunctions);
        else
#endif
            executeBroadcast(
#if UNITY_EDITOR
                globalDebugController.debug, globalDebugController.shouldDebugEvent(ev),
#endif
                 source, evType, ev, e, globalListenerFunctions);
    }
    public void Broadcast(MonoBehaviour source, eventChannels evType, int ev, object e)
    {
        executeBroadcast(
#if UNITY_EDITOR
                debug, shouldDebugEvent(ev),
#endif
                source, evType, ev, e, ListenerFunctions);
    }
    static void executeBroadcast(
#if UNITY_EDITOR
        bool debug, bool specific,
#endif
        MonoBehaviour source, eventChannels evType, int ev, object e, Dictionary<int, Dictionary<int, GarbagelessList>> target)
    {
#if UNITY_EDITOR
        //if the flags are true prints a list of what is going to be invoked before execution

        if (debug && specific)
        {
            Debug.Log("EventHandleManager Broadcast" + evType + " - " + ev + " calling " + target[(int)evType][ev].Count + " functions:");
            int i = 0;
            target[(int)evType][ev].Reset();
            while (target[(int)evType][ev].MoveNext())
            {
                Debug.Log(evType + " - " + ev + " [" + i + "](" + target[(int)evType][ev].Current.Method.DeclaringType.ToString() + ">" + target[(int)evType][ev].Current.ToString() + ")");
                ++i;
            }
            Debug.Log("EventHandleManager Broadcast - START");
        }

#endif
        //invoke event delegates
        target[(int)evType][ev].Reset();
        while (target[(int)evType][ev].MoveNext())
        {
            target[(int)evType][ev].Current(e);
        }
#if UNITY_EDITOR
        if (debug && specific)
        {
            Debug.Log("EventHandleManager Broadcast - OVER");
        }
#endif
    }
    #endregion
    #region AddListener
    public static void globalAddListener(eventChannels evType, int ev, gameEventHandler eventListener)
    {
#if UNITY_EDITOR
        if (globalDebugController == null)
            executeAddListener(false, false, false, evType, ev, eventListener, globalListenerFunctions);
        else
#endif
            executeAddListener(
#if UNITY_EDITOR
                globalDebugController.debug, globalDebugController.shouldDebugEvent(ev), globalDebugController.debugAdds,
#endif
                 evType, ev, eventListener, globalListenerFunctions);
    }
    public void AddListener(eventChannels evType, int ev, gameEventHandler eventListener)
    {
        executeAddListener(
#if UNITY_EDITOR
                debug, shouldDebugEvent(ev), debugAdds,
#endif
         evType, ev, eventListener, ListenerFunctions);
    }
    static void executeAddListener(
#if UNITY_EDITOR
                bool debug, bool specific, bool debugAdds,
#endif
         eventChannels evType, int ev, gameEventHandler eventListener, Dictionary<int, Dictionary<int, GarbagelessList>> target)
    {
        target[(int)evType][ev].Add(eventListener);
#if UNITY_EDITOR
        //if this event's execution should be logged, add a debugging delegate before the method
        if (debug && specific)
        {
            target[(int)evType][ev].Add(new gameEventHandler(delegate (object e)
             {
                 Debug.Log("EventHandleManager execution" + evType + " - " + ev +
                     " finished " + eventListener.Method.DeclaringType.ToString() + " >" + eventListener.Method.ToString());
             }));
        }
        if (debugAdds)
            Debug.Log("EventHandleManager Added event " + evType + " - " + ev +
                        " by " + eventListener.Method.DeclaringType.ToString() + " >" + eventListener.Method.ToString());
#endif
    }
    #endregion
    #region RemoveListener
    public static void globalRemoveListener(eventChannels evType, int ev, gameEventHandler eventListener)
    {
#if UNITY_EDITOR
        if (globalDebugController == null)
            executeRemoveListener(false, evType, ev, eventListener, globalListenerFunctions);
        else
#endif
            executeRemoveListener(
#if UNITY_EDITOR
                globalDebugController.debugRemovals,
#endif
                 evType, ev, eventListener, globalListenerFunctions);
    }
    public void RemoveListener(eventChannels evType, int ev, gameEventHandler eventListener)
    {
        executeRemoveListener(
#if UNITY_EDITOR
                debugRemovals,
#endif
                evType, ev, eventListener, ListenerFunctions);
    }
    static void executeRemoveListener(
#if UNITY_EDITOR
                bool debugRemovals,
#endif
        eventChannels evType, int ev, gameEventHandler eventListener, Dictionary<int, Dictionary<int, GarbagelessList>> target)
    {
#if UNITY_EDITOR
        if (debugRemovals)
            Debug.Log("EventHandleManager Removed event " + evType + " - " + ev +
                        " by " + eventListener.Method.DeclaringType.ToString() + " >" + eventListener.Method.ToString());
#endif
        target[(int)evType][ev].Remove(eventListener);
    }
    #endregion

    public void OnDestroy()
    {
        ListenerFunctions = initializeDicts();
    }

    /*
    this method initializes the ListenerFunctions dictionary by doubly indexing first by eventChannel and then by specific event. The initialization consists in an empity gameEventHandler to which new listeners will eventually be added with the AddListener functions
    */
    static Dictionary<int, Dictionary<int, GarbagelessList>> initializeDicts()
    {
        //gets the information on the structure of channels from ChannelEnums
        Dictionary<eventChannels, Array> enumChannelEventList = ChannelEnums.getChannelEnumList();
        Dictionary<int, Dictionary<int, GarbagelessList>> result = new Dictionary<int, Dictionary<int, GarbagelessList>>();
        foreach (var val in (eventChannels[])Enum.GetValues(typeof(eventChannels)))
        {
            result.Add((int)val, new Dictionary<int, GarbagelessList>());
            foreach (var ev in enumChannelEventList[val])
            {
                //adds an empity gameEventHandler for each event
                result[(int)val].Add((int)ev, new GarbagelessList());
            }
        }
        return result;
    }
}
//delegate signature for the callback functions
public delegate void gameEventHandler(object e);

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Localized events for in-object communication

  • Part 1: basics event system
  • Part 2: event picker drawer for in-editor usage
  • Part 3: debugging with platform dependant compilation
  • Part 4: multiple dispatchers [you are here]
  • Part 5: optimization

Now we’ll see how to use the event dispatcher¬†just everywhere. After all it’s not just objects that need to communicate with each other but also components of the same object. Why shouldn’t we apply the same approach inside an object then and use an in-object event system?

internal broadcast systems
internal broadcast systems

It would be good development practice of course to minimize code duplication while keeping separated the stuff that should be, so we naturally will want to avoid rewriting another event dispatcher from scratch (which means modifying the dispatcher we’ve been using all along) but we don’t want that it’s easy to mistakenly use the wrong events on the wrong object, so its best that each type of gameobject (e.g.:each prefab) has its own eventChannel.

Wait, wasn’t it all static references?

Yes, it was. That’s good if you only have one¬†broadcast to rule them all, but as many of you will be thinking that isn’t going to work for a localized event dispatcher. We will need to separate static references and local references, while at the same time¬†avoiding code duplication, because that would hinder code maintenance. And we still want to debug global broadcasts, of course.

Added difficulty: we just don’t want that pesky debug code in our deployment build.

So here’s the plan: the idea is to have one instance “proclaim itself” the controller of global debugging, so that we have an inspector interface to control that debug. Also, every function call will now need to exist in two versions, one global and one local.

The global debug controller

One voice, hear everywhere
One voice, hear everywhere
    public static eventHandlerManager globalDebugController;
    public bool debugGlobally;

    void OnValidate()
    {
        //old stuff

        if (debugGlobally)
        {
            eventHandlerManager temp = globalDebugController;
            globalDebugController = this;
            if (temp != null)
                if (temp.GetInstanceID() != this.GetInstanceID())
                    temp.debugGlobally = false;
        }
        else
        {
            if (globalDebugController != null)
                if (globalDebugController.GetInstanceID() == this.GetInstanceID())
                    globalDebugController = null;
        }
    }

Let’s begin by declaring a new static variable¬†that will hold the¬†reference to the one and only global debug controller, plus a confortable bool to control wich instance will act as a global controller. We could of course write a custom interface or extend the editor to manage this variable, but in a quick and dirty solution we can just use the validation step to check wether the flag is on or of and set or reset the static reference accordingly. Of course we should ensure that there’s only one instance with an active debugGlobally¬†flag, therefore when one is activated we ensure that if there was another instance in charge¬†before its flag gets set to false.

The data and the functions

Obviously we’ll also need to separate local lists of listeners from global ones, so where there was just a static ListenerFunctions¬†we’ll instead have:

    static Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> globalListenerFunctions = initializeDicts();
    Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> ListenerFunctions = initializeDicts();

And once that the data is separated we need to separate also its access, so for every function that we had before, we’ll need 3 now. One to hold the actual procedure in a unified manner so that we don’t duplicate code, one to do¬†the global broadcasts, the last one to make¬†local broadcasts (as an alternative you may consider just passing the dispatcher¬†reference as an argument and reserve a dispatcher to be the “global one”).

Since the change is the same on all the 3 functions I’ll go in detail just for one of them, the broadcast. Let’s start from the actual procedure:

    static void executeBroadcast(
#if UNITY_EDITOR
        bool debug, bool specific,
#endif
        MonoBehaviour source, eventChannels evType, Enum ev, eventArgExtend e, Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> target)
    {
#if UNITY_EDITOR
        //if the flags are true prints a list of what is going to be invoked before execution
        var list = target[evType][ev].GetInvocationList();
        if (list != null && debug && specific)
        {
            Debug.Log("EventHandleManager Broadcast" + evType + " - " + ev + " calling " + list.Length + " functions:");
            for (int i = 0; i < list.Length; i++)
            {
                Debug.Log(evType + " - " + ev + " [" + i + "](" + list[i].Method.DeclaringType.ToString() + ">" + list[i].Method.ToString() + ")");
            }
        }
#endif
        //invoke event delegates
        target[evType][ev](e);
#if UNITY_EDITOR
        if (debug && specific)
        {
            Debug.Log("EventHandleManager Broadcast - OVER");
        }
#endif
    }

I’ll admit, it looks quite the mess. Those¬†#if/#endif in the middle of the function declaration are ugly AF, no way to deny it. But as we’ve said before, we don’t want to waste resources in the deployment build and since this will be a core component (if you are going to use it on every object) we need to optimize it. Actually, this isn’t even optimized enough, as Alessio Greco (to which I’m really grateful) made me realize, combining c# delegates¬†will generate garbage, so we’ll have to fix this with a manual management of the listener references, but we’ll do this next week.

this is getting complicated
this is getting complicated

So, in detail, we’ve got the debug control arguments in the function signature inside a platform dependent if, but we also have a new argument¬†target¬†which can either be the global or the local variable holding the dictionary of listeners, so that this procedure can handle both calls.

    public void Broadcast(MonoBehaviour source, eventChannels evType, Enum ev, eventArgExtend e)
    {
        executeBroadcast(
#if UNITY_EDITOR
                debug, shouldDebugEvent(ev),
#endif
                source, evType, ev, e, ListenerFunctions);
    }

This is the local version of the function. It’s not static, of course, and will automatically pass its own¬†ListenerFunctions¬†to the¬†executeBroadcast¬†function, along with his own debug flags (that, of course, will still be platform dependent).

    public static void globalBroadcast(MonoBehaviour source, eventChannels evType, Enum ev, eventArgExtend e)
    {
#if UNITY_EDITOR
        if (globalDebugController == null)
            executeBroadcast(false, false, source, evType, ev, e, globalListenerFunctions);
        else
#endif
            executeBroadcast(
#if UNITY_EDITOR
                globalDebugController.debug, globalDebugController.shouldDebugEvent(ev),
#endif
                 source, evType, ev, e, globalListenerFunctions);
    }

And at last we’ve got our global¬†call, just for clarity this is what will end up in the deployment build:

    public static void globalBroadcast(MonoBehaviour source, eventChannels evType, Enum ev, eventArgExtend e)
    {
            executeBroadcast( source, evType, ev, e, globalListenerFunctions);
    }

the rest is just a check to see if we’re debugging and to pass the global flags to the execute call, along with the¬†globalListenerFunctions¬†dictionary.

And for the AddListener and RemoveListener functions, just rinse and repeat.

As usual, the copy-pasteable code is down here, join me in the next and last part to delve into the pleasures of optimizaton as¬†we’ll implement our own list of listeners to call. Register to the newsletter if you don’t want to lose it and hit me on twitter for any questions!

public class eventHandlerManager : MonoBehaviour
{
    //platform-conditional compilation helps to avoid the debug overhead in the final build.
#if UNITY_EDITOR
    //each flag controls a different debug message
    public bool debug;//print debug when any event is broadcasted, unless debugSpecificEvent is true, in which case only that one event is debugged
    public bool debugAdds; //print debug when any listener is added
    public bool debugRemovals;//print debug when any listener is removed
    public bool debugSpecificEvent;//print debug when a specific event is broadcasted, after each callback, 
    public EventPicker picker; //component that gives the UI element to select the event to debug with dropdowns
    //invoked after a debugAll check
    bool shouldDebugEvent(Enum ev)
    {
        return (!debugSpecificEvent) || (ev.ToString().Equals(picker.Selected.ToString()));
    }
    public static eventHandlerManager globalDebugController;
    public bool debugGlobally;//if true activates the debugging for the global eventHandlerManager
    /*
     in reaction to setting the debugGlobally variable, set the instance as 
    globalDebugController so that its debug fields are used for the static
    functions to control debugging, it also ensures that only one debugGlobally 
    variable can be set as true in all the instances of the eventHandlerManager
    */
    void OnValidate()
    {
        if (debugSpecificEvent)
            debug = true;
        if (debugGlobally)
        {
            eventHandlerManager temp = globalDebugController;
            globalDebugController = this;
            if (temp != null)
                if (temp.GetInstanceID() != this.GetInstanceID())
                    temp.debugGlobally = false;
        }
        else
        {
            if (globalDebugController != null)
                if (globalDebugController.GetInstanceID() == this.GetInstanceID())
                    globalDebugController = null;
        }
    }
#endif
    //these dictionaries host the callback delegates, they are initialized by the initializeDicts functions, so that they are already initialized when the first Awake is called.
    public static Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> globalListenerFunctions = initializeDicts();
    public Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> ListenerFunctions = initializeDicts();
    #region broadcast
    public static void globalBroadcast(MonoBehaviour source, eventChannels evType, Enum ev, eventArgExtend e)
    {
#if UNITY_EDITOR
        if (globalDebugController == null)
            executeBroadcast(false, false, source, evType, ev, e, globalListenerFunctions);
        else
#endif
            executeBroadcast(
#if UNITY_EDITOR
                globalDebugController.debug, globalDebugController.shouldDebugEvent(ev),
#endif
                 source, evType, ev, e, globalListenerFunctions);
    }
    public void Broadcast(MonoBehaviour source, eventChannels evType, Enum ev, eventArgExtend e)
    {
        executeBroadcast(
#if UNITY_EDITOR
                debug, shouldDebugEvent(ev),
#endif
                source, evType, ev, e, ListenerFunctions);
    }
    static void executeBroadcast(
#if UNITY_EDITOR
        bool debug, bool specific,
#endif
        MonoBehaviour source, eventChannels evType, Enum ev, eventArgExtend e, Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> target)
    {
#if UNITY_EDITOR
        //if the flags are true prints a list of what is going to be invoked before execution
        var list = target[evType][ev].GetInvocationList();
        if (list != null && debug && specific)
        {
            Debug.Log("EventHandleManager Broadcast" + evType + " - " + ev + " calling " + list.Length + " functions:");
            for (int i = 0; i < list.Length; i++)
            {
                Debug.Log(evType + " - " + ev + " [" + i + "](" + list[i].Method.DeclaringType.ToString() + ">" + list[i].Method.ToString() + ")");
            }
        }
#endif
        //invoke event delegates
        target[evType][ev](e);
#if UNITY_EDITOR
        if (debug && specific)
        {
            Debug.Log("EventHandleManager Broadcast - OVER");
        }
#endif
    }
    #endregion
    #region AddListener
    public static void globalAddListener(eventChannels evType, Enum ev, gameEventHandler eventListener)
    {
#if UNITY_EDITOR
        if (globalDebugController == null)
            executeAddListener(false, false, false, evType, ev, eventListener, globalListenerFunctions);
        else
#endif
            executeAddListener(
#if UNITY_EDITOR
                globalDebugController.debug, globalDebugController.shouldDebugEvent(ev), globalDebugController.debugAdds,
#endif
                 evType, ev, eventListener, globalListenerFunctions);
    }
    public void AddListener(eventChannels evType, Enum ev, gameEventHandler eventListener)
    {
        executeAddListener(
#if UNITY_EDITOR
                debug, shouldDebugEvent(ev), debugAdds,
#endif
         evType, ev, eventListener, ListenerFunctions);
    }
    static void executeAddListener(
#if UNITY_EDITOR
                bool debug, bool specific, bool debugAdds,
#endif
         eventChannels evType, Enum ev, gameEventHandler eventListener, Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> target)
    {
#if UNITY_EDITOR
        //if this event's execution should be logged, add a debugging delegate before the method
        if (debug && specific)
            target[evType][ev] += new gameEventHandler(delegate (eventArgExtend e)
            {
                Debug.Log("EventHandleManager execution" + evType + " - " + ev +
                    " finished " + eventListener.Method.DeclaringType.ToString() + " >" + eventListener.Method.ToString());
            });
        if (debugAdds)
            Debug.Log("EventHandleManager Added event " + evType + " - " + ev +
                        " by " + eventListener.Method.DeclaringType.ToString() + " >" + eventListener.Method.ToString());
#endif
        target[evType][ev] += eventListener;
    }
    #endregion
    #region RemoveListener
    public static void globalRemoveListener(eventChannels evType, Enum ev, gameEventHandler eventListener)
    {
#if UNITY_EDITOR
        if (globalDebugController == null)
            executeRemoveListener(false, evType, ev, eventListener, globalListenerFunctions);
        else
#endif
            executeRemoveListener(
#if UNITY_EDITOR
                globalDebugController.debugRemovals,
#endif
                 evType, ev, eventListener, globalListenerFunctions);
    }
    public void RemoveListener(eventChannels evType, Enum ev, gameEventHandler eventListener)
    {
        executeRemoveListener(
#if UNITY_EDITOR
                debugRemovals,
#endif
                evType, ev, eventListener, ListenerFunctions);
    }
    static void executeRemoveListener(
#if UNITY_EDITOR
                bool debugRemovals,
#endif
        eventChannels evType, Enum ev, gameEventHandler eventListener, Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> target)
    {
#if UNITY_EDITOR
        if (debugRemovals)
            Debug.Log("EventHandleManager Removed event " + evType + " - " + ev +
                        " by " + eventListener.Method.DeclaringType.ToString() + " >" + eventListener.Method.ToString());
#endif
        target[evType][ev] -= eventListener;
    }
    #endregion
    public void OnDestroy()
    {
        ListenerFunctions= initializeDicts();
    }
    /*
    this method initializes the ListenerFunctions dictionary by doubly indexing first by eventChannel and then by specific event. The initialization consists in an empity gameEventHandler to which new listeners will eventually be added with the AddListener functions
    */
    static Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> initializeDicts()
    {
        //gets the information on the structure of channels from ChannelEnums
        Dictionary<eventChannels, Array> enumChannelEventList = ChannelEnums.getChannelEnumList();
        Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> result = new Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>>();
        foreach (var val in (eventChannels[])Enum.GetValues(typeof(eventChannels)))
        {
            result.Add(val, new Dictionary<Enum, gameEventHandler>());
            foreach (var ev in enumChannelEventList[val])
            {
                //adds an empity gameEventHandler for each event
                result[val].Add((Enum)ev, new gameEventHandler(delegate (eventArgExtend e) { }));
            }
        }
        return result;
    }
}
//delegate signature for the callback functions
public delegate void gameEventHandler(eventArgExtend e);
//argument class to be extended to add fields to the callbacks it without changing the signature
public class eventArgExtend : System.EventArgs
{
}

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Platform dependent compilation: debug on event dispatcher

  • Part 1: basics event system
  • Part 2: event picker drawer for in-editor usage
  • Part 3: debugging with platform dependant compilation [you are here]
  • Part 4: multiple dispatchers
  • Part 5: optimization

In today’s tutorial I’ll delve in a bit of meta-programming, the obscure art of telling the compiler what to do to generate code, instead of plainly writing it yourself. But don’t worry,¬†at the end of the day, we’ll be using just an if. In this tutorial we’ll be¬†editing my event-system in order to add a debugging feature to it, so that we can log what handlers get invoked on what events and comfortably control that in the inspector.

Platform Dependent Compilation
Platform Dependent Compilation allows you to change code depending on target platform

What’s Platform Dependent Compilation again?

It’s a nice trick you can use in unity to avoid burdening one platform with the code that’s only required for another one. And something that will render your in-editor tests useless when found in some platform-specific plugin, as I learned the painful way. In Unity3d is done by taking one compiler directive to check if some constants are already defined.¬†Unity will define these constants according to build target. If you are developing, for instance, with pc as build target and write¬†#if¬†UNITY_ANDROID¬†all the following code until the¬†#endif¬†will be as commented from the IDE’s point of view.

And when it’s time to build your code, that stuff will just be treated as a comment and not be included in your final build… unless you are building for that platform. In that case that code will be there as if nothing happened.¬†This can be very useful, for instance, when it comes to dealing with input methods in a cross-platform game that’s both on mobile and pc.

Lets debug this
Lets debug this

How do we use Platform dependent compilation to debug an event system?

Basically, the only place where we’ll need it is the editor, so we’ll check for the variable¬†UNITY_EDITOR¬†and that’s it.

And here’s what we’ll need to add to our event script in terms of variables:

#if UNITY_EDITOR
    public bool debug;//print debug when any event is broadcasted, unless debugSpecificEvent is true, in which case only that one event is debugged
    public bool debugAdds; //print debug when any listener is added
    public bool debugRemovals;//print debug when any listener is removed
    public bool debugSpecificEvent;//print debug when a specific event is broadcasted, after each callback, 
    public EventPicker picker; //component that gives the UI element to select the event to debug with dropdowns
    //invoked after a debugAll check
    bool shouldDebugEvent(Enum ev)
    {
        return (!debugSpecificEvent) || (ev.ToString().Equals(picker.Selected.ToString()));
    }
    
    void OnValidate()
    {
        if (debugSpecificEvent)
            debug = true;
    }
#endif

So, inside the platform dependent code block we insert a series of flags and an EventPicker so that we’ll be able to manage what to debug when and even select a specific event to debug in isolation.

There are a couple of lines of code there that aren’t actually variables, let’s explain them:

  • shouldDebugEvent is a function,¬†returns true in two cases, either when¬†we’re not debugging a specific event, or when the event in argument is the one selected in thepicker.
  • OnValidate is a monobehaviour method that is invoked every time the data is changed in the inspector. In this case we want to ensure that when we’re debugging a specific event thedebugflag isn’t forgotten since it’ll need to be true to actually print anything.

Then we’ll need to change quite drastically our Broadcast , AddListener and RemoveListener functions.

This is our new broadcast:

    public static void Broadcast(eventChannels evType, Enum ev, eventArgExtend e)
    {
#if UNITY_EDITOR
        //if the flags are true prints a list of what is going to be invoked before execution
        var list = ListenerFunctions[evType][ev].GetInvocationList();
        if (list != null && debug &&  shouldDebugEvent(ev))
        {
            Debug.Log("EventHandleManager Broadcast" + evType + " - " + ev + " calling " + list.Length + " functions:");
            for (int i = 0; i < list.Length; i++)
            {
                Debug.Log(evType + " - " + ev + " [" + i + "](" + list[i].Method.DeclaringType.ToString() + ">" + list[i].Method.ToString() + ")");
            }
        }
#endif
        //invoke event delegates
        ListenerFunctions[evType][ev](e);
#if UNITY_EDITOR
        if (debug &&  shouldDebugEvent(ev))
        {
            Debug.Log("EventHandleManager Broadcast - OVER");
        }
#endif
    }

The first thing that’s coming to many people’s mind right now is: “wait, what the fuck is that Method.ToString()?!”. Well, it’s C# reflection again. It keeps creeping in, but what we say to the god of the 10’000-words-long tutorials?

t4w9s7

So, just know that that debug code will print the name of the classes and the functions that the event dispatcher will invoke.
In the beginning¬†we recover the list of functions that will be called from the delegate with GetInvocationList, then (provided this event broadcast should be debugged) for each one of them we’ll print its name and¬†source, along with the¬†position number.

Then we invoke the functions, and at last¬†we print a nice “over” after the event reactions are finished.

Registering and unregistering

    static void AddListener(eventChannels evType, Enum ev, gameEventHandler eventListener)
    {
#if UNITY_EDITOR
        //if this event's execution should be logged, add a debugging delegate before the method
        if (debug && shouldDebugEvent(ev))
            ListenerFunctions[evType][ev] += new gameEventHandler(delegate (eventArgExtend e)
            {
                Debug.Log("EventHandleManager execution" + evType + " - " + ev +
                    " finished " + eventListener.Method.DeclaringType.ToString() + " >" + eventListener.Method.ToString());
            });
        if (debugAdds)
            Debug.Log("EventHandleManager Added event " + evType + " - " + ev +
                        " by " + eventListener.Method.DeclaringType.ToString() + " >" + eventListener.Method.ToString());
#endif
        ListenerFunctions[evType][ev] += eventListener;
    }

Here we do two things: in case the event broadcast debugging is active for this event we add a delegate that prints event¬†channel, event name, invoking class and method name. Then, if we’re debugging registration we also log that, again with the same debug message.

The latter will also be done for the unregister function:

    public static void RemoveListener(eventChannels evType, Enum ev, gameEventHandler eventListener, Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> target)
    {
#if UNITY_EDITOR
        if (debugRemovals)
            Debug.Log("EventHandleManager Removed event " + evType + " - " + ev +
                        " by " + eventListener.Method.DeclaringType.ToString() + " >" + eventListener.Method.ToString());
#endif
        ListenerFunctions[evType][ev] -= eventListener;
    }

Here again we log all the same information when the listener is removed from the event.

So, wrapping this up, we’ve added debugging features to our event dispatcher and can now place it in any scene so that we can handle the debug operations in it using¬†the inspector. As usual the full code is down here for copy-paste purposes, we’ll get next time to the semi-final form of the script that will allow not just one single dispatcher in the whole game but multiple independent channels of communication so that for instance a single character can have its own internal event system. Register to the newsletter if you don’t want to lose it and hit me on twitter for any questions!

And by the way, I’m looking for a job right now, check out my portfolio here!

using System;
using System.Collections.Generic;
using UnityEngine;

public class eventHandlerManager : MonoBehaviour
{
#if UNITY_EDITOR
    public bool debug;//print debug when any event is broadcasted, unless debugSpecificEvent is true, in which case only that one event is debugged
    public bool debugAdds; //print debug when any listener is added
    public bool debugRemovals;//print debug when any listener is removed
    public bool debugSpecificEvent;//print debug when a specific event is broadcasted, after each callback, 
    public EventPicker picker; //component that gives the UI element to select the event to debug with dropdowns
    //invoked after a debugAll check
    bool shouldDebugEvent(Enum ev)
    {
        return (!debugSpecificEvent) || (ev.ToString().Equals(picker.Selected.ToString()));
    }
    
    void OnValidate()
    {
        if (debugSpecificEvent)
            debug = true;
    }
#endif
    public static Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> ListenerFunctions = initializeDicts();

    public static void Broadcast(eventChannels evType, Enum ev, eventArgExtend e)
    {
#if UNITY_EDITOR
        //if the flags are true prints a list of what is going to be invoked before execution
        var list = ListenerFunctions[evType][ev].GetInvocationList();
        if (list != null && debug &&  shouldDebugEvent(ev))
        {
            Debug.Log("EventHandleManager Broadcast" + evType + " - " + ev + " calling " + list.Length + " functions:");
            for (int i = 0; i < list.Length; i++)
            {
                Debug.Log(evType + " - " + ev + " [" + i + "](" + list[i].Method.DeclaringType.ToString() + ">" + list[i].Method.ToString() + ")");
            }
        }
#endif
        //invoke event delegates
        ListenerFunctions[evType][ev](e);
#if UNITY_EDITOR
        if (debug &&  shouldDebugEvent(ev))
        {
            Debug.Log("EventHandleManager Broadcast - OVER");
        }
#endif
    }
    static void AddListener(eventChannels evType, Enum ev, gameEventHandler eventListener)
    {
#if UNITY_EDITOR
        //if this event's execution should be logged, add a debugging delegate before the method
        if (debug && shouldDebugEvent(ev))
            ListenerFunctions[evType][ev] += new gameEventHandler(delegate (eventArgExtend e)
            {
                Debug.Log("EventHandleManager execution" + evType + " - " + ev +
                    " finished " + eventListener.Method.DeclaringType.ToString() + " >" + eventListener.Method.ToString());
            });
        if (debugAdds)
            Debug.Log("EventHandleManager Added event " + evType + " - " + ev +
                        " by " + eventListener.Method.DeclaringType.ToString() + " >" + eventListener.Method.ToString());
#endif
        ListenerFunctions[evType][ev] += eventListener;
    }

    public static void RemoveListener(eventChannels evType, Enum ev, gameEventHandler eventListener, Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> target)
    {
#if UNITY_EDITOR
        if (debugRemovals)
            Debug.Log("EventHandleManager Removed event " + evType + " - " + ev +
                        " by " + eventListener.Method.DeclaringType.ToString() + " >" + eventListener.Method.ToString());
#endif
        ListenerFunctions[evType][ev] -= eventListener;
    }
    public void OnDestroy()
    {
        ListenerFunctions = initializeDicts();
    }

    static Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> initializeDicts()
    {
        Dictionary<eventChannels, Array> enumChannelEventList = ChannelEnums.getChannelEnumList();
        Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> result = new Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>>();
        foreach (var val in (eventChannels[])Enum.GetValues(typeof(eventChannels)))
        {
            result.Add(val, new Dictionary<Enum, gameEventHandler>());
            foreach (var ev in enumChannelEventList[val])
            {
                result[val].Add((Enum)ev, new gameEventHandler(delegate (eventArgExtend e) { }));
            }
        }
        return result;
    }
}
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Custom drawer in unity3d to pick an enum – event tutorial followup

  • Part 1: basics event system
  • Part 2: event picker drawer for in-editor usage [you are here]
  • Part 3: debugging with platform dependant compilation
  • Part 4: multiple dispatchers
  • Part 5: optimization

For today’s tutorial we’re going to make something that’s really comfortable to use: a dropdown menu to select an event. We’ll do that by creating anEventPickerclass and a custom drawer by estending thePropertyDrawerclass thatUnityEnginekindly gives us.

First, well’need aEventPickerclass. This class will actually hold the data inside the game, and will not in any way define the editor UI. It’s the data for which we’re going to define a Custom Drawer in Unity3d, not the drawer itself.

custom drawer in unity3d
let’s make a custom drawer

The EventPicker class

To function with the¬†Event system we defined in last tutorial we’ll need for it the ability to read which event channel and which specific event have been selected. It also needs to allow us to identify the specific type of enum to which each channel will be associated with, so that later the custom drawer can use this information to draw the appropriate dropdown menu. Therefore we’ll need at least :

  • one¬†field to store the selected channel
  • one field to store the selected event for each channel
  • one property to give access to the actual currently selected event to any class that will actually use the event picker.
using UnityEngine;

[System.Serializable]
public class EventPicker
{
    public const string channelVarName = "channel";
    public eventChannels channel;

    public inGameChannelEvents inGame;
    public menuChannelEvents menu;

    public System.Enum Selected
    {
        get
        {
            switch (channel)
            {
                case eventChannels.menu: return menu;
                case eventChannels.inGame: return inGame;
                default:
                    Debug.LogError("EventPicker was passed an unimplemented channel!!! " + channel.ToString());
                    break;
            }
            return channel;
        }
    }


}

Sadly, this means that each time a channel is added inChannelEnumsclass, it will need to be added here too. Also, since we’re going to need a Drawer, we don’t have the freedom to choose whatever variable names we want to pick since it relies on getting that name as a string.

String-run-fools
A string! Run, you fools!

We could solve this problem with C# reflection¬†but that’s a deep topic that I’d rather address another time. For now, let’s just use a strong naming convention and have the the event-storage variables use the same name as the channel, while we addchannelVarNameto keep thechannelvariable name as¬†a constant string field¬†near the variable whose name it stores, so that whenever we may need to rename it we can just as easily¬†copy-paste that name inside the string.

TheSelectedproperty is just a switch that returns the value of the field described by the content of thechannelvariable. So that we have a fast way to get the selected event from theEventPicker.

The actual Custom Drawer

The Custom Drawer must have this declaration:

[CustomPropertyDrawer(typeof(EventPicker))]
public class EventPickerDrawer : PropertyDrawer
{

So that unity can assign this drawer to the class that it’s intended to draw.¬†It’s also important to understand that we won’t be writing something from scratch here. We’ll actually be extending aPropertyDrawerclass that already exists in unity. I won’t explain in depth what this class is or does, for that there is the documentation, but please notice we’re going to override¬†two methods:OnGUIandGetPropertyHeight.

Let’s begin with the easy part:

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return base.GetPropertyHeight(property, label) * 3f + 5f;
    }

Now, what we’re doing here is basically telling unity to reserve a height equal to three times a normal variable plus an extra 5¬†pixels. This is because we’re going to put one header and then two dropdowns, and each one is going to use the height of one variable.

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.PrefixLabel(position, label);
        Rect tempPos = position;
        tempPos.height = ROWHEIGHT;
        tempPos.y += ROWHEIGHT;
        tempPos.x += position.width * .4f;
        tempPos.width *= .6f;

TheOnGUIfunction actually tells unity what to draw in the inspector when it encounters anEventPickervariable. Here we start by telling it to draw a label which indicates the kind of objetc to which the data belongs and then declaring aRectwith the appropriate size and coordinates to store the rest of the UI elements.

        SerializedProperty channel = property.FindPropertyRelative(EventPicker.channelVarName);
        EditorGUI.PropertyField(tempPos, channel, GUIContent.none);

This bit of code recovers the data about¬†thechannelvariable in theEventPickervia the serialization information (which means you can’t use this on anything that can’t be serialized), then draws¬†new field with that information. Thechannelvariable is identified by its name, that we recover with the static string insideEventPicker.

        tempPos.y += ROWHEIGHT;
        eventChannels chosen = (eventChannels)channel.enumValueIndex;
        SerializedProperty selection = property.FindPropertyRelative(chosen.ToString());
        EditorGUI.PropertyField(tempPos, selection, GUIContent.none);

Here we increment the starting point of theRectso that Unity won’t draw the fields one over the other.

Then we proceed to get the value of the selected channel converted in itsenumform, so that we can then recover the serialization information for the appropriate variable by using theToStringmethod and benefitting from the strong naming convention used in theEventPickerclass.

That’s all folks

So, let’s recap: first you need to create the data container, but in a way¬†that allows you to access all the names of the fields, then we can write another class that must be¬†linked through aCustomPropertyDrawertag to the data class and that overrides thePropertyDrawerfunctions to draw our class the way we want it to.

Now you have a nice UI element that you can use to pick events for your Event System, without having to write it as a horrible string and then converting it. As usual down here there’s all the code in a copy-paste-friendly format. More on this event system is still coming¬†next week. If you want to be sure not losing it, subscribe to my newsletter. For any feedback comments are below or you can just add me on twitter.

using UnityEngine;

[System.Serializable]
public class EventPicker
{
    public const string channelVarName = "channel";
    public eventChannels channel;

    public inGameChannelEvents inGame;
    public menuChannelEvents menu;

    public System.Enum Selected
    {
        get
        {
            switch (channel)
            {
                case eventChannels.menu: return menu;
                case eventChannels.inGame: return inGame;
                default:
                    Debug.LogError("EventPicker was passed an unimplemented channel!!! " + channel.ToString());
                    break;
            }
            return channel;
        }
    }
}

 

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(EventPicker))]
public class EventPickerDrawer : PropertyDrawer
{
    protected virtual float ROWHEIGHT
    {
        get
        { return 18f; }
    }
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.PrefixLabel(position, label);
        Rect tempPos = position;
        tempPos.height = ROWHEIGHT;
        tempPos.y += ROWHEIGHT;
        tempPos.x += position.width * .4f;
        tempPos.width *= .6f;

        SerializedProperty channel = property.FindPropertyRelative(EventPicker.channelVarName);
        EditorGUI.PropertyField(tempPos, channel, GUIContent.none);
        tempPos.y += ROWHEIGHT;

        eventChannels chosen = (eventChannels)channel.enumValueIndex;
        SerializedProperty selection = property.FindPropertyRelative(chosen.ToString());
        EditorGUI.PropertyField(tempPos, selection, GUIContent.none);

    }
    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return base.GetPropertyHeight(property, label) * 3f + 5f;
    }
}

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Event system: tutorial for Unity3D & C#

  • Part 1: basics event system [you are here]
  • Part 2: event picker drawer for in-editor usage
  • Part 3: debugging with platform dependant compilation
  • Part 4: multiple dispatchers
  • Part 5: optimization

Today I’ll start presenting you the single most useful piece of code that I’ve ever written. Since writing it I used it in every single project I’ve made, however simple. Even in game jams.
I’m speaking of an Event System. A¬†class with no other purpose than to let other objects communicate with each other, with as little overhead as possible. And it’s implemented with delegates.

And it’s way better than SendMessage, if you ask me.

event_driven_programming

What’s an Event System?

Those of you who already know, just skip this paragraph. Still with me? good. The main problem you’ll have with SendMessage is that it needs to use a¬†method’s name as an input. So the object sending the message not only needs to know¬†what method should be invoked by the receiver, but also his name. In a case sensitive way. Want to change that name? too bad, the IDE won’t change the string for you. You’ll have to remember every single one of those and do it by yourself… or never change a function name ever again. Or remove one.

Sounds bad? it is. It’s a maintenance nightmare. Plus, it uses strings. Fuck strings. Strings are evil. They use your memory, trigger your garbage collector and spit on your mother. Never use them unless at gunpoint… and even then think twice about it and instead use an enum.ToString() if you can.

So what’s the solution?¬†we need something more like this: you need to send a message between objects (or scripts) when a “thing” happens. So the scripts in the other object react to the “thing”. The caller script doesn’t need to know who or how will do anything in reaction to the “thing”. It just needs to shout out “Hey! I have a thing here!” and eventually how big the “thing” is. For instance, “Hey! I have a 3.5 Damage here!”. Then the health script subtracts the damage and the particle script spills blood everywhere. But the collider doesn’t need to know that. The “thing” is an Event. Shouting is a Broadcast. The reactions are called Callbacks or Handlers.

But this is nothing I invented, you can just study it here or here, or even here.

What’s a Delegate?

As before, if you know already, just skip ahead. Delegates are something I wish they did teach me in university, because they are just awesome. To put it bluntly: in C# you can treat functions as if they were variables and pass them as an argument to other functions. This means that you can change behaviours of an object at runtime. Or have an object hold and call a function without it even knowing what it does. Absolute decoupling. A maintenance heaven.

Before you can use a delegate, you first need to declare its type. Which means telling the compiler which return type and what arguments will be assigned to it. Not its name. Not its content. Just the signature structure. Of course, if you use as a signature something like:

public delegate object nameFunct(object o);

you can then pass anything and get anything (but say goodbye to compiler type-checks).

After you have declared a delegate type, you can then declare a delegate variable and assign to it a function with a matching signature. Just like you would do with any other variable. If you still need to delve deeper you can go here or here.

Get on with it!

gowi-2

Now, if you have seen my portfolio the event system there can be quite intimidating, but don’t worry, we won’t be doing that version right now. We’ll start with version 0.0.3 while that one is 1.0.1 [edit: this was before optimizing for the last part of the tutorial]. That¬†makes¬†about 100 rows of code and 20 headaches of difference.

Before we get to the main class, the event dispatcher, let’s define some stuff we’ll use there.

public delegate void gameEventHandler(eventArgExtend e);

This will be our event signature. Nothing to return because we don’t want the event sender to have anything to do with the receivers. The event type is this thing here:

public class eventArgExtend : System.EventArgs { }

Why didn’t I use directlySystem.EventArgs?¬†Because in the future I may want to add something between those parentheses and when it happens it will¬†cost me nothing to do so. Had I used directly that type it could require more work to do it.

Now, let’s get to something more interesting: defining the events themselves. To do so I’ll use something infinitely superior to strings: enums.

public enum eventChannels
{
    inGame
}
public enum inGameChannelEvents
{
    thing
}

Now we’ll need a comfortable way to pass this information to the dispatcher.¬†For this¬†I just created a class with a static function:

public class ChannelEnums
{
    public static Dictionary<eventChannels, System.Array> getChannelEnumList()
    {

        Dictionary<eventChannels, System.Array> enumChannelEventList = new Dictionary<eventChannels, System.Array>();
        enumChannelEventList.Add(eventChannels.inGame, System.Enum.GetValues(typeof(inGameChannelEvents)));
        return enumChannelEventList;
    }
}

Notice: adding a channel requires to add a new enum type, and there is no automated way to set a link between an enum value ineventChannelsand an other enumtype. So for each channel you need to add, you’ll have to write a new row of code like the one just before thereturn.

What we did here is to create a static function for the dispatcher. The function will recovery every event channel and every corresponding array of values, so that we can initialize the list of listeners in the dispatcher. All in the nice form of a Dictionary.

Now let’s get to the Event System

Our event system needs to be usable at EVERY stage of game play. This includes the Awake function of the first objects to be present in the first scene. Which means that I can’t use the Awake function to initialize the event system, or I would get in a race condition. That’s fucked up. But years of Unity3D ninjutsu allowed me to discover a precious thing: default values are initialized before Awake, and you can get them with static functions.

public class eventHandlerManager : MonoBehaviour
{
    static Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> ListenerFunctions = initializeDicts();

What’s thatinitializeDicts? we’ll see later. For now just look at the type of ListenerFunctions: it’s a dictionary of dictionaries. It allows us to index¬†gameEventHandler¬†delegates first by channel and then by event.

Now we can write the key functions of the dispatcher: one to raise an event, one to add a delegate as listener, one to remove it.

public static void Broadcast( eventChannels evType,Enum ev,eventArgExtend e) 
	{
		ListenerFunctions[evType][ev](e);
	}
	
	public static void AddListener(eventChannels evType,Enum ev, gameEventHandler eventListener)
	{
		ListenerFunctions[evType][ev]+=eventListener;
	}
	public static void RemoveListener(eventChannels evType,Enum ev, gameEventHandler eventListener)
	{
		ListenerFunctions[evType][ev]-=eventListener;
	}

Why are they all static? because that way you don’t need to get the other classes to find the instance. Yes, this is limiting: you can’t have a damage event that only gets notified to one character. But for something more selective¬†we’ll need to make a lot of changes, for the time being just add an identifier as field in the argument. Then have the receivers check that value. We’ll get back to that in future tutorials.

This is all that’s needed on the outside to use this event handler. Other classes that want to react to an event just need to declare a function that matches thegameEventHandlersignature and then invoke theaddListenerfunction giving that function as the last argument (without parentheses), and do the same for removal like this:

eventHandlerManager.AddListener(eventChannels.inGame, inGameChannelEvents.thing, onThing);

The broadcast use is even simpler:

eventHandlerManager.Broadcast(eventChannels.inGame, inGameChannelEvents.thing, new eventArgExtend());

So, now we’re almost done. There is just one last detail for the magic to fully work: how do we initialize and cleanListenerFunctions?

    public void OnDestroy()
    {
        ListenerFunctions = initializeDicts();
    }

    static Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> initializeDicts()
    {
        Dictionary<eventChannels, Array> enumChannelEventList = ChannelEnums.getChannelEnumList();
        Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> result = new Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>>();
        foreach (var val in (eventChannels[])Enum.GetValues(typeof(eventChannels)))
        {
            result.Add(val, new Dictionary<Enum, gameEventHandler>());
            foreach (var ev in enumChannelEventList[val])
            {
                result[val].Add((Enum)ev, new gameEventHandler(delegate (eventArgExtend e) { }));
            }
        }
        return result;
    }

Wait a minute: did I initialize ListenerFunctions on destroy? Yes, because that is a static field. It will survive to the existence of the eventHandlerManager instance. And since that should only happen on a scene load, I can be sure that nothing should stay in the dictionary and that the new scene has a ready new clean slate to work on when the first Awake is called.

The initialization works by first getting the enum data from the static function we defined before in ChannelEnums, then using that data to initialize every field of the dictionary with an empitygameEventHandler; this way we can later add the Handlers with a+=instead of checking for a null field every time.

That’s all folks!

Not really. Actually there is a lot more that we can add to this class, mainly for debugging purposes, plus the “local” version of the event dispatcher that I mentioned before. But this tutorial is already huge, so maybe it’s better to deal with that stuff¬†next week. If you want to be sure not losing it, subscribe to my newsletter. For any feedback comments are below or you can just add me on twitter.

using System;
using System.Collections.Generic;
using UnityEngine;

public class eventHandlerManager : MonoBehaviour
{
    public static Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> ListenerFunctions = initializeDicts();

    public static void Broadcast(eventChannels evType, Enum ev, eventArgExtend e)
    {
        ListenerFunctions[evType][ev](e);
    }

    public static void AddListener(eventChannels evType, Enum ev, gameEventHandler eventListener)
    {
        ListenerFunctions[evType][ev] += eventListener;
    }
    public static void RemoveListener(eventChannels evType, Enum ev, gameEventHandler eventListener)
    {
        ListenerFunctions[evType][ev] -= eventListener;
    }

    public void OnDestroy()
    {
        ListenerFunctions = initializeDicts();
    }

    static Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> initializeDicts()
    {
        Dictionary<eventChannels, Array> enumChannelEventList = ChannelEnums.getChannelEnumList();
        Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>> result = new Dictionary<eventChannels, Dictionary<Enum, gameEventHandler>>();
        foreach (var val in (eventChannels[])Enum.GetValues(typeof(eventChannels)))
        {
            result.Add(val, new Dictionary<Enum, gameEventHandler>());
            foreach (var ev in enumChannelEventList[val])
            {
                result[val].Add((Enum)ev, new gameEventHandler(delegate (eventArgExtend e) { }));
            }
        }
        return result;
    }
}

public enum eventChannels
{
    inGame
}

public enum inGameChannelEvents
{
    thing
}

public class eventArgExtend : System.EventArgs { }

public delegate void gameEventHandler(eventArgExtend e);

public class ChannelEnums
{
    public static Dictionary<eventChannels, System.Array> getChannelEnumList()
    {

        Dictionary<eventChannels, System.Array> enumChannelEventList = new Dictionary<eventChannels, System.Array>();
        enumChannelEventList.Add(eventChannels.inGame, System.Enum.GetValues(typeof(inGameChannelEvents)));
        return enumChannelEventList;
    }
}

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •