devkit

DevKit - Super Repo for All Plugins

View on GitHub

Pub/Sub Messaging in Unity

Pub-Sub Messenger is a Lightweight, Open Source Library for Unity. This library will provide a pub-sub mechanism that is a part of SOLID principles in programming.

Contents

Youtube Video with Instructions:

Video-Instructions


The Problem

Example of common usages of events in C#:

public class Human
{
    // event that passes instance of Stick when it is invoked
    public event Action<Stick> FetchStick;
    
    // static event that passes instance of Ball when it is invoked
    public static event Action<Ball> FetchBall;
}

public class Dog : Animal
{
    // event handler - method that is invoked by event and receives Stick instance
    public void OnFetchStick(Stick stick) { /*TODO*/ }
}

public class Cat : Animal
{
    // event handler - method that is invoked by event and receives Ball instance
    public void OnFetchBall(Ball ball) { /*TODO*/ }
}

public class Playground
{
    public Human David { get; set; }
    private Dog Rex { get; set; }
    private Cat Max { get; set; }

    public void RegisterEvents()
    {
        David.FetchStick += Rex.OnFetchStick;
        Human.FetchBall += Max.OnFetchBall;
    }
}

david -> rex

Rex.OnFetchStick is attached to David.FetchStick and David instance has reference to Rex instance. Rex instance will not be removed from the memory until it’s handler OnFetchStick will be detached from FetchStick event or until David will be removed from the memory.

human -> rex

Max.OnFetchBall is attached to Human.FetchBall and Max instance is referenced by static pointer Human.FetchBall. Max instance will not be removed from the memory until it’s handler OnFetchBall will be detached from static event FetchBall. Static references are worse case of memory leaks.


The Solution

Usage of Messenger as Pub/Sub mechanism:

public class FetchStickPayload
{
     // stick type for filtering
     public StickTypes StickType { get; set; }
     
     // the position of stick in the space
     public Vector3 Position { get; set; }
}

// publisher
public class Human
{
     public void PublishFetchStickPayload()
     {
         // publish new payload with specific data
         Messenger.Default.Publish(
              new FetchStickPayload 
                       { 
                              StickType = StickTypes.PlasticStick, 
                              Position = new Vector3(1, 1, 0) 
                       });
     }
}

// subscriber
public class Dog : Animal
{
     // callback - method that is invoked by Messenger and receives payload instance
     public void OnFetchStick(FetchStickPayload payload) { /* TODO handle stick fetching */ }
}

public class Playground
{
     public Human David { get; set; }
     public Dog Billy { get; set; }
     public Dog Mika { get; set; }

     public void Subscribe()
     { 
         // subscribe callback Billy.OnFetchStick to FetchStickPayload
         Messenger.Default.Subscribe<FetchStickPayload>(Billy.OnFetchStick);
        
         // subscribe callback Mika.OnFetchStick to FetchStickPayload with filter/predicate
         Messenger.Default.Subscribe<FetchStickPayload>(Mika.OnFetchStick, CanFetchStick);
     }

     private bool CanFetchStick(FetchStickPayload payload) { /* TODO filter unwanted stick types */ }
}

billi -> mika


Message Routing

Messenger


Messenger API

Messenger implements this interface:

// Messenger Interface
public interface IMessenger
{
    // Subscribe callback to receive a payload
    // Predicate to filter the payload (optional)
    IMessenger Subscribe<T>(Action<T> callback, Predicate<T> predicate = null);

    // Unsubscribe the callback from receiving the payload 
    IMessenger Unsubscribe<T>(Action<T> callback);

    // Publish the payload to its subscribers
    IMessenger Publish<T>(T payload);
}

Access to default Messenger instance via:

SuperMaxim.Messaging.Messenger.Default.[function]

Publish

// Generic Parameter <T> - here is a <Payload> that will be published to subscribers of this type
Messenger.Default.Publish<Payload>(new Payload{ /* payload params */ });

// In most cases there is no need in specifying Generic Parameter <T>
Messenger.Default.Publish(new Payload{ /* payload params */ });

// Generic Parameter <T> - here is a <IPayload> that will be published to subscribers of this type
Messenger.Default.Publish<IPayload>(new Payload{ /* payload params */ });

class Payload : IPayload
{

}

Subscribe

// Payload – the type of Callback parameter
// Callback – delegate (Action<T>) that will receive the payload
Messenger.Default.Subscribe<Payload>(Callback);

private static void Callback(Payload payload)
{
  // Callback logic
}

Subscribe with Predicate

// Predicate – delegate (Predicate<T>) that will receive payload to filter
Messenger.Default.Subscribe<Payload>(Callback, Predicate);

private static bool Predicate(Payload payload)
{
  // Predicate filter logic
  // if function will return ‘false’ value, the Callback will not be invoked
  return accepted;
}

Unsubscribe - Variant #1

// Payload – the type of Callback parameter that was subscribed
// Callback – delegate (Action<Payload>) that was subscribed
Messenger.Default.Unsubscribe<Payload>(Callback);

private static void Callback(Payload payload)
{
  // Callback logic
}

Unsubscribe - Variant #2

// IPayload – the type of Callback parameter that was subscribed
// Callback – delegate (Action<Payload>) that was subscribed
Messenger.Default.Unsubscribe<IPayload>(Callback);

// Payload class implements IPayload interface
private static void Callback(Payload payload)
{
  // Callback logic
}

Use Cases


Correct Usage


MainThreadDispatcher API

Main Thread dispatcher is responsible for the synchronisation of callbacks between Main and other threads.

MainThreadDispatcher implements this interface:

public interface IThreadDispatcher
{
    // managed Thread ID
    int ThreadId { get; }

    // dispatch - adds callback delegate into dispatcher’s queue
    // action - delegate reference to method that should be invoked on main thread
    // payload - the data that should be passed to the method/callback
    void Dispatch(Delegate action, object[] payload);
}

Dispatch Method - example

MainThreadDispatcher.Default.Dispatch(Callback, new object[] { payload, state });

Package Structure

Package Folders

folders

Folders with name “Core” contain base classes and scripts that are shared across different modules. Folders with name “Messaging” contain classes and scripts that are specific for the Messenger.

Plugins / Core

core Extensions – extensions classes Objects – core base classes Threading – multithreading related classes/scripts WeakRef – weak reference handling classes

Plugins / Messaging

messaging Components – useful unity components Monitor – debugging and monitoring tools


Unit Tests

Coverage:

Messenger Tests

Weak Reference Tests

Tests Folder

tests_folder

Playmode Tests

playmode_tests


DEMO Project

Goals:

[WebGL Demo]

Demo Structure

Folders / Files demo_folders

Sample Scene demo_UI


Project License Discord Asset Store