2. Value Providers

  06. Initialization No Comments

What Are Value Providers?

Value providers are a flexible system that can be used to resolve Init arguments dynamically at runtime.

Value providers are any objects that implement IValueProvider<TValue> or IValueByTypeProvider (or their asynchronous counterparts, IValueProviderAsync<TValue> or IValueByTypeProviderAsync).

Use Cases

There are a lot of possible use cases for value providers – here are a few examples to get your imagination running:

  • Addressables – value providers can be used to load addressable assets asynchronously before passing them to clients, all ready to be used. The initializer will automatically make sure that the client remains disabled until the addressable has been loaded.
  • Localization – value providers to localize all texts before passing them to clients. All your various client components don’t need to worry about which localization solution you happen to be using in the project.
  • Randomization – randomize any Init arguments you want (names, color palettes, character customization options…).
  • Databases – Value providers can be used to fetch values from databases at runtime.
  • Per-Client Services – Make sure that different instances of the same type get provided for all clients.
  • Dropdown menu items – The [ValueProviderMenu] attribute can be used to add new items into Init argument dropdown menus for easily assigning value providers into them.

Creating A Value Provider

Value providers are most typically scriptable objects.

To make a new scriptable object value provider, create a class that derives from ScriptableObject and implements IValueProvider<TValue>.

To create an asset from the value provider, also add the [CreateAssetMenu] attribute to it.

At a minimum a value provider has to implement the Value property from the IValueProvider<TValue> interface.

You can also optionally implement the TryGetFor method to support customizing the returned value based on the client requesting it or to support situations where the value provider might not always be able to provide a value.

using Sisus.Init;
using UnityEngine;

[CreateAssetMenu]
class RandomNameProvider : ScriptableObject, IValueProvider<string>
{
    [SerializeField] string[] names = { };

    public string Value => names.Length is 0 ? null : names[Random.Range(0, names.Length)];

    public bool TryGetFor(Component client, out string value)
    {
        if(names.Length is 0)
        {
            value = null;
            return false;
        }

        value = names[Random.Range(0, names.Length)];
        return true;
    }
}

Using Value Providers

To receive a value from a value provider, create a component that derives from MonoBehaviour<T…> and has an Init argument whose type matches the value provided by a value provider.

using Sisus.Init;

class StringClient : MonoBehaviour<string>
{
    protected override void Init(string name) => gameObject.name = name;
}

Then generate an Initializer for the component using the + button in the Init section.

After this simply drag-and-drop a value provider into the Init argument field in the Inspector window.

In addition to using Initializers, you can also assign value providers into Any<TValue> fields.

Value providers can also be combined with the [Service] attribute to create per-client service providers.

Validating Value Providers

If it’s possible for your value provider to be unable to provide values to a client, for example because of invalid configuration, you can implement the INullGuard interface to specify when an error should be shown to the user in the Inspector:

[CreateAssetMenu]
class RandomNameProvider : ScriptableObject, IValueProvider<string>, INullGuard
{
    ...

    public NullGuardResult EvaluateNullGuard(Component client)
    {
        if(names.Length == 0) 
        {
            return NullGuardResult.Error("Names list is empty.");
        }

        foreach(var name in names)
        {
            if(string.IsNullOrEmpty(name))
            {
                return NullGuardResult.Error("Names list contains an empty string.");
            }
        }

        return NullGuardResult.Passed;
    }
}

Init Argument Menu Integration

You can also create a new item in the Init argument dropdown menu for the value provider by adding the [ValueProviderMenu] attribute to it.

[ValueProviderMenu(typeof(string)), CreateAssetMenu]
class RandomNameProvider : ScriptableObject, IValueProvider<string>
{
    public string Value => Random.Range(0, 5) switch
    {
        0 => "Alice",
        1 => "Bob",
        2 => "Charlie",
        3 => "Diana",
        _ => "Eve",
    };
}

Leave a Reply

Your email address will not be published. Required fields are marked *