From 31c37662cfc5d53250f3aa4aa8e93db38f2bc6e9 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Sun, 12 Nov 2023 16:15:15 +0000 Subject: [PATCH] Play game started audio notifications just as the game starts. Previously the StartGameNotification and MusicPlaylist traits used the IWorldLoaded interface to play an audio notification and begin music when the game started. However this interface is used by many traits to perform initial loading whilst the load screen was visible, and this loading can take time. Since the traits could run in any order, then audio notification might fire before another trait with a long loading time. This is not ideal as we want the time between the audio notification occurring and the player being able to interact to be as short and reliable as possible. Now, we introduce a new IPostWorldLoaded which runs after all other loading activity, and we switch StartGameNotification and MusicPlaylist to use it. This allows timing sensitive traits that want to run right at the end of loading to fire reliably and with minimal delay. The player perception of hearing the notification and being able to interact is now much snappier. --- OpenRA.Game/Game.cs | 4 ++++ OpenRA.Game/Traits/TraitsInterfaces.cs | 1 + OpenRA.Game/World.cs | 15 ++++++++++++++- OpenRA.Mods.Common/Traits/World/MusicPlaylist.cs | 4 ++-- .../Traits/World/StartGameNotification.cs | 4 ++-- 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index 3f5c8ddfa3..b8d435db96 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -223,6 +223,10 @@ namespace OpenRA // Much better to clean up now then to drop frames during gameplay for GC pauses. GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; GC.Collect(); + + // PostLoadComplete is designed for anything that should trigger at the very end of loading. + // e.g. audio notifications that the game is starting. + OrderManager.World.PostLoadComplete(worldRenderer); } public static void RestartGame() diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index 6ce8d3842a..25509261b1 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -366,6 +366,7 @@ namespace OpenRA.Traits public interface INotifySelection { void SelectionChanged(); } public interface IWorldLoaded { void WorldLoaded(World w, WorldRenderer wr); } + public interface IPostWorldLoaded { void PostWorldLoaded(World w, WorldRenderer wr); } public interface INotifyGameLoading { void GameLoading(World w); } public interface INotifyGameLoaded { void GameLoaded(World w); } public interface INotifyGameSaved { void GameSaved(World w); } diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index 505fdba1d1..1fda6873e0 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -296,16 +296,29 @@ namespace OpenRA using (new PerfTimer(iwl.GetType().Name + ".WorldLoaded")) iwl.WorldLoaded(this, wr); - gameInfo.StartTimeUtc = DateTime.UtcNow; foreach (var player in Players) gameInfo.AddPlayer(player, OrderManager.LobbyInfo); gameInfo.DisabledSpawnPoints = OrderManager.LobbyInfo.DisabledSpawnPoints; + gameInfo.StartTimeUtc = DateTime.UtcNow; + if (OrderManager.Connection is NetworkConnection nc && nc.Recorder != null) nc.Recorder.Metadata = new ReplayMetadata(gameInfo); } + public void PostLoadComplete(WorldRenderer wr) + { + foreach (var iwl in WorldActor.TraitsImplementing()) + using (new PerfTimer(iwl.GetType().Name + ".PostWorldLoaded")) + iwl.PostWorldLoaded(this, wr); + + foreach (var p in Players) + foreach (var iwl in p.PlayerActor.TraitsImplementing()) + using (new PerfTimer(iwl.GetType().Name + ".PostWorldLoaded")) + iwl.PostWorldLoaded(this, wr); + } + public void SetWorldOwner(Player p) { WorldActor.Owner = p; diff --git a/OpenRA.Mods.Common/Traits/World/MusicPlaylist.cs b/OpenRA.Mods.Common/Traits/World/MusicPlaylist.cs index e0d8a1a368..53f8935f46 100644 --- a/OpenRA.Mods.Common/Traits/World/MusicPlaylist.cs +++ b/OpenRA.Mods.Common/Traits/World/MusicPlaylist.cs @@ -43,7 +43,7 @@ namespace OpenRA.Mods.Common.Traits public override object Create(ActorInitializer init) { return new MusicPlaylist(init.World, this); } } - public class MusicPlaylist : INotifyActorDisposing, IGameOver, IWorldLoaded, INotifyGameLoaded + public class MusicPlaylist : INotifyActorDisposing, IGameOver, IPostWorldLoaded, INotifyGameLoaded { readonly MusicPlaylistInfo info; readonly World world; @@ -98,7 +98,7 @@ namespace OpenRA.Mods.Common.Traits } } - void IWorldLoaded.WorldLoaded(World world, WorldRenderer wr) + void IPostWorldLoaded.PostWorldLoaded(World world, WorldRenderer wr) { // Reset any bogus pre-existing state Game.Sound.DisableWorldSounds = info.DisableWorldSounds; diff --git a/OpenRA.Mods.Common/Traits/World/StartGameNotification.cs b/OpenRA.Mods.Common/Traits/World/StartGameNotification.cs index 0d77342b35..8437892a53 100644 --- a/OpenRA.Mods.Common/Traits/World/StartGameNotification.cs +++ b/OpenRA.Mods.Common/Traits/World/StartGameNotification.cs @@ -38,7 +38,7 @@ namespace OpenRA.Mods.Common.Traits public override object Create(ActorInitializer init) { return new StartGameNotification(this); } } - sealed class StartGameNotification : IWorldLoaded, INotifyGameLoaded, INotifyGameSaved + sealed class StartGameNotification : IPostWorldLoaded, INotifyGameLoaded, INotifyGameSaved { readonly StartGameNotificationInfo info; public StartGameNotification(StartGameNotificationInfo info) @@ -46,7 +46,7 @@ namespace OpenRA.Mods.Common.Traits this.info = info; } - void IWorldLoaded.WorldLoaded(World world, WorldRenderer wr) + void IPostWorldLoaded.PostWorldLoaded(World world, WorldRenderer wr) { if (!world.IsLoadingGameSave) {