|
1 | 1 | namespace STS2RitsuLib.Audio |
2 | 2 | { |
3 | 3 | /// <summary> |
4 | | - /// Convenience helpers for loading FMOD Studio banks after the game has finished deferred initialization. |
| 4 | + /// Queues FMOD Studio bank and GUID mapping paths until <see cref="DeferredInitializationCompletedEvent" />, |
| 5 | + /// then loads them in one batch with a single <see cref="FmodStudioServer.TryWaitForAllLoads" />. |
5 | 6 | /// </summary> |
6 | 7 | public static class FmodStudioDeferredBankRegistration |
7 | 8 | { |
| 9 | + private static readonly Lock Gate = new(); |
| 10 | + private static readonly HashSet<string> PendingBanks = new(StringComparer.Ordinal); |
| 11 | + private static readonly HashSet<string> PendingGuidFiles = new(StringComparer.Ordinal); |
| 12 | + private static bool _flushHookRegistered; |
| 13 | + |
8 | 14 | /// <summary> |
9 | | - /// Schedules loading one FMOD Studio bank and optional GUID path mappings once, after |
10 | | - /// <see cref="STS2RitsuLib.DeferredInitializationCompletedEvent" /> (or immediately if that milestone already |
11 | | - /// occurred). |
| 15 | + /// Queues a bank path to load after deferred initialization (deduplicated). |
12 | 16 | /// </summary> |
13 | | - /// <param name="bankResourcePath"> |
14 | | - /// Godot resource path to the <c>.bank</c> file (for example <c>res://Mod/audios/x.bank</c> |
15 | | - /// ). |
16 | | - /// </param> |
17 | | - /// <param name="studioGuidMappingsResourcePath"> |
18 | | - /// Optional <c>GUIDs.txt</c>-style resource path; pass <c>null</c> when the bank does not need addon GUID mapping. |
19 | | - /// </param> |
20 | | - /// <param name="waitForAllLoadsAfterBanks"> |
21 | | - /// When true, calls <see cref="FmodStudioServer.TryWaitForAllLoads" /> after all banks in this batch have been |
22 | | - /// submitted. |
23 | | - /// </param> |
24 | | - /// <returns> |
25 | | - /// Subscription token; it is disposed automatically after the deferred load attempt finishes. |
26 | | - /// </returns> |
27 | | - public static IDisposable QueueLoadBankAfterDeferredInitialization( |
28 | | - string bankResourcePath, |
29 | | - string? studioGuidMappingsResourcePath = null, |
30 | | - bool waitForAllLoadsAfterBanks = true) |
| 17 | + public static void RegisterBank(string resourcePath) |
31 | 18 | { |
32 | | - ArgumentException.ThrowIfNullOrWhiteSpace(bankResourcePath); |
| 19 | + if (string.IsNullOrWhiteSpace(resourcePath)) |
| 20 | + return; |
33 | 21 |
|
34 | | - return QueueLoadBanksAfterDeferredInitialization( |
35 | | - [bankResourcePath], |
36 | | - studioGuidMappingsResourcePath, |
37 | | - waitForAllLoadsAfterBanks); |
| 22 | + lock (Gate) |
| 23 | + { |
| 24 | + PendingBanks.Add(resourcePath.Trim()); |
| 25 | + EnsureFlushHookRegisteredLocked(); |
| 26 | + } |
38 | 27 | } |
39 | 28 |
|
40 | 29 | /// <summary> |
41 | | - /// Schedules loading multiple FMOD Studio banks (in order) and optional GUID path mappings once, after |
42 | | - /// <see cref="STS2RitsuLib.DeferredInitializationCompletedEvent" /> (or immediately if that milestone already |
43 | | - /// occurred). |
| 30 | + /// Queues a GUID mapping file for <see cref="FmodStudioServer.TryLoadStudioGuidMappings" /> after deferred |
| 31 | + /// initialization (deduplicated). |
44 | 32 | /// </summary> |
45 | | - /// <param name="bankResourcePaths">Non-empty sequence of Godot resource paths to <c>.bank</c> files.</param> |
46 | | - /// <param name="studioGuidMappingsResourcePath"> |
47 | | - /// Optional <c>GUIDs.txt</c>-style resource path; pass <c>null</c> when no GUID table should be applied. |
48 | | - /// </param> |
49 | | - /// <param name="waitForAllLoadsAfterBanks"> |
50 | | - /// When true, calls <see cref="FmodStudioServer.TryWaitForAllLoads" /> after all banks in this batch have been |
51 | | - /// submitted. |
52 | | - /// </param> |
53 | | - /// <returns> |
54 | | - /// Subscription token; it is disposed automatically after the deferred load attempt finishes. |
55 | | - /// </returns> |
56 | | - public static IDisposable QueueLoadBanksAfterDeferredInitialization( |
57 | | - IEnumerable<string> bankResourcePaths, |
58 | | - string? studioGuidMappingsResourcePath = null, |
59 | | - bool waitForAllLoadsAfterBanks = true) |
| 33 | + public static void RegisterStudioGuidMappings(string guidMapResourcePath) |
| 34 | + { |
| 35 | + if (string.IsNullOrWhiteSpace(guidMapResourcePath)) |
| 36 | + return; |
| 37 | + |
| 38 | + lock (Gate) |
| 39 | + { |
| 40 | + PendingGuidFiles.Add(guidMapResourcePath.Trim()); |
| 41 | + EnsureFlushHookRegisteredLocked(); |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + private static void EnsureFlushHookRegisteredLocked() |
| 46 | + { |
| 47 | + if (_flushHookRegistered) |
| 48 | + return; |
| 49 | + |
| 50 | + _flushHookRegistered = true; |
| 51 | + RitsuLibFramework.SubscribeLifecycle<DeferredInitializationCompletedEvent>(_ => FlushPending()); |
| 52 | + } |
| 53 | + |
| 54 | + private static void FlushPending() |
60 | 55 | { |
61 | | - ArgumentNullException.ThrowIfNull(bankResourcePaths); |
| 56 | + if (FmodStudioServer.TryGet() is null) |
| 57 | + { |
| 58 | + RitsuLibFramework.Logger.Warn( |
| 59 | + "[Audio] deferred FMOD: FmodServer singleton missing; pending banks/GUID files kept for a later flush." |
| 60 | + ); |
| 61 | + return; |
| 62 | + } |
62 | 63 |
|
63 | | - var banks = bankResourcePaths as string[] ?? bankResourcePaths.ToArray(); |
64 | | - if (banks.Length == 0) |
65 | | - throw new ArgumentException("At least one bank path is required.", nameof(bankResourcePaths)); |
| 64 | + List<string> banks; |
| 65 | + List<string> guids; |
66 | 66 |
|
67 | | - return RitsuLibFramework.SubscribeDeferredInitializationOneShot(() => |
| 67 | + lock (Gate) |
68 | 68 | { |
69 | | - if (FmodStudioServer.TryGet() is null) |
70 | | - { |
71 | | - RitsuLibFramework.Logger.Warn( |
72 | | - "[Audio] Deferred FMOD bank load skipped: FmodServer singleton is missing."); |
73 | | - return; |
74 | | - } |
| 69 | + banks = [.. PendingBanks]; |
| 70 | + guids = [.. PendingGuidFiles]; |
| 71 | + PendingBanks.Clear(); |
| 72 | + PendingGuidFiles.Clear(); |
| 73 | + } |
75 | 74 |
|
76 | | - foreach (var path in banks) |
77 | | - { |
78 | | - if (string.IsNullOrWhiteSpace(path)) |
79 | | - continue; |
| 75 | + if (banks.Count == 0 && guids.Count == 0) |
| 76 | + return; |
80 | 77 |
|
81 | | - if (!FmodStudioServer.TryLoadBank(path)) |
82 | | - RitsuLibFramework.Logger.Warn($"[Audio] Deferred FMOD bank load failed: {path}"); |
83 | | - } |
| 78 | + foreach (var path in banks) |
| 79 | + FmodStudioServer.TryLoadBank(path); |
84 | 80 |
|
85 | | - if (waitForAllLoadsAfterBanks) |
86 | | - FmodStudioServer.TryWaitForAllLoads(); |
| 81 | + foreach (var path in guids) |
| 82 | + FmodStudioServer.TryLoadStudioGuidMappings(path); |
87 | 83 |
|
88 | | - if (string.IsNullOrWhiteSpace(studioGuidMappingsResourcePath)) |
89 | | - return; |
| 84 | + FmodStudioServer.TryWaitForAllLoads(); |
90 | 85 |
|
91 | | - if (!FmodStudioServer.TryLoadStudioGuidMappings(studioGuidMappingsResourcePath)) |
92 | | - RitsuLibFramework.Logger.Warn( |
93 | | - $"[Audio] Deferred FMOD guid map failed: {studioGuidMappingsResourcePath}"); |
94 | | - }); |
| 86 | + RitsuLibFramework.Logger.Info( |
| 87 | + $"[Audio] deferred FMOD flush complete (banks={banks.Count}, guid files={guids.Count})." |
| 88 | + ); |
95 | 89 | } |
96 | 90 | } |
97 | 91 | } |
0 commit comments