Last active
January 8, 2024 17:33
-
-
Save abeldantas/8f648a3d383f4ec0794dc9d05938319a to your computer and use it in GitHub Desktop.
Unity Splash Optimization with Priority-based and Thread-Aware Initialization
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <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