Im using a pub/sub pattern in a project. If a class is keeping track of a primitive type like a float and other subsystems and classes needs to be able to read it is it possible to use a message to deliver a handle to the float? Not the value but something that allows you them to read it when necessary.
2 Answers
If a class is keeping track of a primitive type like a float and other subsystems and classes needs to be able to read it is it possible to use a message to deliver a handle to the float?
Absolutely! In-fact Unity provides, a surprisingly robust(ish) message system that wasn't all too difficult to implement this. Although their system is, in my personal opinion, not as intuitive as the traditional Producer/Subscriber event based pattern that MSDN uses.
In order to pass a 'handle'(MSDN calls it an alias) to a value, say a float
like in your example, in C# we can use the ref
keyword.
The ref
keyword in C#, when you boil it down to it's basics, is you either defining, or asking for a reference to an object, instead of the object it's self.(This is overgeneralized for sake of brevity).
When you ask for a reference to that object you don't get a copy of that object, you instead get an alias that allows you to get, set, read or anything you want with that variable, as if you controlled it(like in this example)
To use the ref
keyword here's a short example to get you started:
class Alice{
private int RandomInt = 4; // guaranteed to be random
public ref int GetReferenceToPrivateInt()
{
return ref RandomInt;
}
public void LogPrivateInt()
{
Console.WriteLine(RandomInt);
}
}
Alice alice = new Alice();
ref int retrieveReference = alice.GetReferenceToPrivateInt();
retrieveReference = 16;
alice.LogPrivateInt();
// outputs:
16
We can see in this example, even though we never has direct access to the private variable in the Alice
class, when we received a by-reference value from the GetReferenceToPrivateInt()
method. We were able to modify that value, and the changes were reflected by the private Alice
class variable. All without ever having to set the variable manually! How awesome!
If we put the ref
keyword together with the Unity Messaging system we are able to 'pass around' reference variables so we can mutate them without having direct access to them.
Let's start out with creating a IEventSystemHandler
that defines a Subscriber.
public delegate ref T ReferenceAction<T>();
public interface IReferenceMessage<T> : IEventSystemHandler
{
void MutateValue(ref T MessageValue);
void StoreGetter(ReferenceAction<T> GetterForReferencePrimitive);
}
What we did here is create the interface
that defines what methods a subscriber should have to properly 'consume' what we send them.
We created two methods, MutateValue(ref T)
and StoreGetter(ReferenceAction<T>)
. MutateValue()
accepts a by reference generic parameter, and StoreGetter()
accepts this thing called ReferenceAction<T>
.
ReferenceAction<T>
here isn't a method, type, or object that Unity or MSDN has created, we created it in the code above here:
public delegate ref T ReferenceAction<T>();
What this does is create a delegate
, which in a verify simplified sense, is us telling the compiler, "I want to provide a method as a parameter for this function StoreGetter
, but I want that method to have the very specific signature that I need."
Now that we've defined what a subscriber should look like, let's create one.
public class Subcriber: MonoBehaviour, IReferenceMessage<float>
{
// we store the method that we can call to get a reference value here
private ReferenceAction<float> FloatGetter;
// Whenever the producer sends out a message called MutateValue, we're gonna run this automatically because we're subscribed to the producer
public void MutateValue(ref float MessageValue)
{
// change the value of the reference float to prove that value changes on the producer
MessageValue *= 2;
}
// whenever the producer starts, since we're a subscriber, we will have this method called, which will allow us to store the method we can call to get a reference to the hidden float on the producer object
public void StoreGetter(ReferenceAction<float> GetterForReferencePrimitive)
{
// save the method for later so we can get a reference value any time we need it
this.FloatGetter = GetterForReferencePrimitive;
}
}
Let's also create a Producer class as well and bring everything together.
public class Producer : MonoBehaviour
{
// for example purposes we should keep this private, so we know we are changing its value outside this class using it's reference, and not the actual object
private float SecretFloat = 1.0f;
// allow the user to assign a subscriber in the inspector
public GameObject Subcriber;
// create a property that is public, that hides the secret float.
public float Primitive
{
get => SecretFloat;
set
{
SecretFloat = value;
// send message to subscriber that the value of the secret float has changed
ExecuteEvents.Execute<IReferenceMessage<float>>(
Subcriber,
null,
/* pass the secret float as a reference and not a copy */
(x, y) => x.MutateValue(ref SecretFloat)
);
}
}
private void Start()
{
// send message to subscriber so they can get reference float any time using the method we send them
ExecuteEvents.Execute<IReferenceMessage<float>>(Subcriber, null, (x, y) => x.StoreGetter(FloatGetter));
}
// create a method that we send as a message at Start() so other objects can retrieve a reference at any time, not just when the value of secret float has changed
private ref float FloatGetter()
{
// return a reference to secret float, not a copy.
return ref SecretFloat;
}
}
What the very simple(as in not fully-implemented/featured), scripts above is allow you to do a couple really cool things, that I think is what you wanted.
When Start()
is called the subscriber gets a method it saves for later, the method it gets is FloatGetter()
which when it's called - returns a by-reference float
( the SecretFloat
).
When the subscriber gets that message(in StoreGetter()
) it will store the method so it can use it later to get a reference any time.
In addition to that, we also made it so whenever the value of SecretFloat
is changed a second message gets sent to the subscriber, this time instead of a method that returns a reference, we sent the reference directly. That way we don't need to constantly check to see if our reference has been updated, the producer will tell us when, and we can mutate the reference when we get it, if we want. Super cool!
You're probably wondering by now why do we need to store a method to get a reference, couldn't we just store the reference variable in a field or property and use that instead?
Unfortunately ref
values and variables cannot be stored on properties or fields so they can be kept later. Generally due to the way C# works, ref
variables and references can never leave the stack and have limitations for safety reasons.
If you're interested in accomplishing the same effect in pure C# you can use the following to get started, you'll notice it's a lot more streamlined using the traditional EventHandler
approach.
public class Subscriber
{
public void MutateFloat(object caller, ref float primitive)
{
primitive *= 2;
}
}
public class Producer<T>
{
private T SecretPrimitive = default;
public delegate void RefPrimitiveHandler(object caller, ref T primitive);
public event RefPrimitiveHandler OnPrimitiveChange;
public T Primitive
{
get => SecretPrimitive;
set
{
SecretPrimitive = value;
OnPrimitiveChange?.Invoke(this, ref SecretPrimitive);
}
}
}

- 2,224
- 1
- 5
- 19
If I understand you correctly you could probably use a Func<float>
. And simply store a method that returns your float value.
For example
public class System : MonoBehaviour
{
public SubSystem SomeSubSystem;
private float currentFloat;
private float GetFloat()
{
return currentFloat;
}
private void Awake ()
{
SomeSubSystem.Initialize(GetFloat);
}
private void Update()
{
currentFloat += Time.deltaTime;
}
}
and then use it like e.g.
public class SubSystem : MonoBehaviour
{
private Func<float> _getFloat;
public void Initialize (Func<float> getFloat)
{
_getFloat = getFloat;
}
private IEnumerator Start ()
{
while(true)
{
yield return new WaitForSeconds (1);
Debug.Log(_getFloat());
}
}
}
The same thing could of course also be provided in an event like
public event Action<Func<float>> SomeEvent;
So the listener on that event doesn't directly receive the float value but rather the method how to get it and can store it for later.

- 83,094
- 9
- 75
- 115