Skip to content

Instantly share code, notes, and snippets.

@abeldantas
Last active January 8, 2024 17:33
Show Gist options
  • Save abeldantas/8f648a3d383f4ec0794dc9d05938319a to your computer and use it in GitHub Desktop.
Save abeldantas/8f648a3d383f4ec0794dc9d05938319a to your computer and use it in GitHub Desktop.
Unity Splash Optimization with Priority-based and Thread-Aware Initialization
/// <summary>
/// This is the dependency or subsystem that needs to be initialized before the first scene
/// </summary>
public interface IInitializable
{
bool IsInitialized { get; }
bool CanInitializeAsynchronously { get; }
void Initialize();
event Action OnInitialized;
}
/// <summary>
/// Manages the initialization of various dependencies at the start of the game.
/// Dependencies are registered with a priority, and those marked for asynchronous initialization
/// can be initialized in a separate thread.
/// </summary>
public class SplashInitializer : Singleton<SplashInitializer>
{
// We want to be able to specify the order in which dependencies are initialized
// for example analytics before ads
// We don't need two separate collections, but I think it looks better
private SortedList<int, IInitializable> sortedDependencies = new SortedList<int, IInitializable>();
private Queue<IInitializable> initializationQueue = new Queue<IInitializable>();
// Each dependency will call this method to register itself, so the initializer knows about it
public void RegisterDependency(IInitializable dependency, int priority)
{
sortedDependencies.Add(priority, dependency);
dependency.OnInitialized += OnDependencyInitialized;
}
// Lets avoid Awake unless we really need it
void Start()
{
// Take the dependencies in order and add them to the queue
foreach (var pair in sortedDependencies)
{
initializationQueue.Enqueue(pair.Value);
}
StartCoroutine(InitializeDependencies());
}
// By the time this is called, all dependencies are registered
// Chose Coroutines but async/await can be used as well, specially with UniTask
// One very good reason to use UniTask in this case is for implementing the timeouts
IEnumerator InitializeDependencies()
{
while (initializationQueue.Count > 0)
{
var dependency = initializationQueue.Dequeue();
if (!dependency.IsInitialized)
{
if (dependency.CanInitializeAsynchronously)
{
// This depends on how fancy we want to get. Generally it is not a good idea to spin a thread
// without a handle to it. Again, UniTask can help here. Generally I think this should be fine for now though
Task.Run(() =>
dependency.Initialize();
});
}
else
{
dependency.Initialize();
yield return new WaitUntil(() => dependency.IsInitialized); // TODO: Timeout w/ exception for backtrace
}
yield return null; // Wait for one frame. Maybe we need more to ensure we can breathe for ANR signals
}
}
while( !sortedDependencies.Values.All(d => d.IsInitialized) ) // TODO: Timeout w/ exception for backtrace
{
yield return null;
}
}
void OnDependencyInitialized()
{
// Useful for debugging and logging
}
}
// This is an example of a specific dependency. It can be the base class for others, like FacebookInitializer.cs
// Registers itself with the SplashInitializer and specifies whether it can be initialized asynchronously.
public class Dependency : MonoBehaviour, IInitializable
{
public bool IsInitialized { get; private set; } = false;
public bool CanInitializeAsynchronously { get; private set; } = false; // By default, dependencies are initialized synchronously
public event Action OnInitialized;
[SerializedField] int priority = 1;
private void Start()
{
SplashInitializer.Instance.RegisterDependency(this, priority);
}
public void Initialize()
{
// Perform initialization logic here (or on the derived class when overriding this)
// ...
IsInitialized = true;
OnInitialized?.Invoke();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment