IGameConfig: fix load-time race condition

As some public properties of `IGameConfig` are being set on the first
`Framework` tick, there was a short window that those properties were
null, which goes against the interface declaration.

This commit fixes that, by making those properties block for the full
initialization of the class.

A possible side effect is that a plugin that is set to block the game
from loading until it loads will now hang the game if it tries to access
the game configuration from its constructor, instead of throwing a
`NullReferenceException`. As it would mean that the plugin was buggy at
the first place and it would have sometimes failed to load anyway, it
might as well be a non-breaking change.
This commit is contained in:
Soreepeong 2024-02-20 15:37:54 +09:00
parent ac59f73b59
commit c27422384f
3 changed files with 126 additions and 23 deletions

View file

@ -1,14 +1,20 @@
using System;
using System.Diagnostics;
using System.Diagnostics;
using System.Threading.Tasks;
using Dalamud.Game.Config;
using FFXIVClientStructs.FFXIV.Common.Configuration;
using Dalamud.Plugin.Internal.Types;
namespace Dalamud.Plugin.Services;
/// <summary>
/// This class represents the game's configuration.
/// </summary>
/// <remarks>
/// Avoid accessing configuration from your plugin constructor, especially if your plugin sets
/// <see cref="PluginManifest.LoadRequiredState"/> to <c>2</c> and <see cref="PluginManifest.LoadSync"/> to <c>true</c>.
/// If property access from the plugin constructor is desired, do the value retrieval asynchronously via
/// <see cref="IFramework.RunOnFrameworkThread{T}(Func{T})"/>; do not wait for the result right away.
/// </remarks>
public interface IGameConfig
{
/// <summary>
@ -31,6 +37,16 @@ public interface IGameConfig
/// </summary>
public event EventHandler<ConfigChangeEvent> UiControlChanged;
/// <summary>
/// Gets a task representing the initialization state of this instance of <see cref="IGameConfig"/>.
/// </summary>
/// <remarks>
/// Accessing <see cref="GameConfigSection"/>-typed properties such as <see cref="System"/>, directly or indirectly
/// via <see cref="TryGet(Game.Config.SystemConfigOption,out bool)"/>,
/// <see cref="Set(Game.Config.SystemConfigOption,bool)"/>, or alike will block, if this task is incomplete.
/// </remarks>
public Task InitializationTask { get; }
/// <summary>
/// Gets the collection of config options that persist between characters.
/// </summary>