/******************************************************************************* The content of this file includes portions of the proprietary AUDIOKINETIC Wwise Technology released in source code form as part of the game integration package. The content of this file may not be used without valid licenses to the AUDIOKINETIC Wwise Technology. Note that the use of the game engine is subject to the Unreal(R) Engine End User License Agreement at https://www.unrealengine.com/en-US/eula/unreal License Usage Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use this file in accordance with the end user license agreement provided with the software or, alternatively, in accordance with the terms contained in a written agreement between you and Audiokinetic Inc. Copyright (c) 2023 Audiokinetic Inc. *******************************************************************************/ #include "Wwise/WwiseUnitTests.h" #if WWISE_UNIT_TESTS #include "Wwise/WwiseResourceLoaderImpl.h" #include "Wwise/Mock/WwiseMockExternalSourceManager.h" #include "Wwise/Mock/WwiseMockMediaManager.h" #include "Wwise/Mock/WwiseMockSoundBankManager.h" #include #include #include WWISE_TEST_CASE(ResourceLoader_Smoke, "Wwise::ResourceLoader::ResourceLoader_Smoke", "[ApplicationContextMask][SmokeFilter]") { SECTION("Static") { static_assert(std::is_constructible::value, "Resource Loader must be constructed without parameters"); static_assert(!std::is_copy_constructible::value, "Cannot copy a Resource Loader"); static_assert(!std::is_copy_assignable::value, "Cannot reassign a Resource Loader"); static_assert(!std::is_move_constructible::value, "Cannot move a Resource Loader"); } SECTION("Instantiation") { FWwiseResourceLoaderImpl ResourceLoaderImpl; } SECTION("Sync AuxBus") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseLocalizedAuxBusCookedData CookedData; { FWwiseAuxBusCookedData Data1; Data1.Media.Emplace(FWwiseMediaCookedData{}); Data1.SoundBanks.Emplace(FWwiseSoundBankCookedData{}); CookedData.AuxBusLanguageMap.Emplace(FWwiseLanguageCookedData::Sfx, MoveTemp(Data1)); } // Creating Node auto* Node = ResourceLoaderImpl.CreateAuxBusNode(CookedData, nullptr); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node FWwiseLoadedAuxBusPromise LoadPromise; auto LoadFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadAuxBusAsync(MoveTemp(LoadPromise), MoveTemp(Node)); auto LoadedNode = LoadFuture.Get(); // Synchronously CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { return; } // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; auto UnloadFuture = UnloadPromise.GetFuture(); ResourceLoaderImpl.UnloadAuxBusAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); UnloadFuture.Get(); // Synchronously CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Sync Event") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseLocalizedEventCookedData CookedData; { FWwiseEventCookedData Data1; Data1.Media.Emplace(FWwiseMediaCookedData{}); Data1.SoundBanks.Emplace(FWwiseSoundBankCookedData{}); FWwiseSwitchContainerLeafCookedData Leaf1; FWwiseMediaCookedData Leaf1Media; Leaf1Media.MediaId = 1; Leaf1.Media.Emplace(MoveTemp(Leaf1Media)); FWwiseGroupValueCookedData Leaf1GroupValue; Leaf1GroupValue.Id = 1; Leaf1GroupValue.GroupId = 1; Leaf1GroupValue.Type = EWwiseGroupType::Switch; Leaf1.GroupValueSet.Emplace(MoveTemp(Leaf1GroupValue)); Data1.SwitchContainerLeaves.Emplace(MoveTemp(Leaf1)); CookedData.EventLanguageMap.Emplace(FWwiseLanguageCookedData::Sfx, MoveTemp(Data1)); } // Creating Node auto* Node = ResourceLoaderImpl.CreateEventNode(CookedData, nullptr); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node FWwiseLoadedEventPromise LoadPromise; auto LoadFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadEventAsync(MoveTemp(LoadPromise), MoveTemp(Node)); auto Loaded = LoadFuture.Get(); // Synchronously CHECK(Loaded); if (UNLIKELY(!Loaded)) { return; } // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; auto UnloadFuture = UnloadPromise.GetFuture(); ResourceLoaderImpl.UnloadEventAsync(MoveTemp(UnloadPromise), MoveTemp(Loaded)); UnloadFuture.Get(); // Synchronously CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Sync External Sources") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseExternalSourceCookedData CookedData; // Creating Node auto* Node = ResourceLoaderImpl.CreateExternalSourceNode(CookedData); CHECK(Node) if (UNLIKELY(!Node)) { return;; } // Loading Node FWwiseLoadedExternalSourcePromise LoadPromise; auto LoadFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadExternalSourceAsync(MoveTemp(LoadPromise), MoveTemp(Node)); auto Loaded = LoadFuture.Get(); // Synchronously CHECK(Loaded); if (UNLIKELY(!Loaded)) { return; } // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; auto UnloadFuture = UnloadPromise.GetFuture(); ResourceLoaderImpl.UnloadExternalSourceAsync(MoveTemp(UnloadPromise), MoveTemp(Loaded)); UnloadFuture.Get(); // Synchronously CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Sync GroupValue Switch") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseGroupValueCookedData CookedData; CookedData.Type = EWwiseGroupType::Switch; // Creating Node auto* Node = ResourceLoaderImpl.CreateGroupValueNode(CookedData); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node FWwiseLoadedGroupValuePromise LoadPromise; auto LoadFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadGroupValueAsync(MoveTemp(LoadPromise), MoveTemp(Node)); auto Loaded = LoadFuture.Get(); // Synchronously CHECK(Loaded); if (UNLIKELY(!Loaded)) { return; } // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; auto UnloadFuture = UnloadPromise.GetFuture(); ResourceLoaderImpl.UnloadGroupValueAsync(MoveTemp(UnloadPromise), MoveTemp(Loaded)); UnloadFuture.Get(); // Synchronously CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Sync GroupValue State") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseGroupValueCookedData CookedData; CookedData.Type = EWwiseGroupType::State; // Creating Node auto* Node = ResourceLoaderImpl.CreateGroupValueNode(CookedData); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node FWwiseLoadedGroupValuePromise LoadPromise; auto LoadFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadGroupValueAsync(MoveTemp(LoadPromise), MoveTemp(Node)); auto Loaded = LoadFuture.Get(); // Synchronously CHECK(Loaded); if (UNLIKELY(!Loaded)) { return; } // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; auto UnloadFuture = UnloadPromise.GetFuture(); ResourceLoaderImpl.UnloadGroupValueAsync(MoveTemp(UnloadPromise), MoveTemp(Loaded)); UnloadFuture.Get(); // Synchronously CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Sync InitBank") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseInitBankCookedData CookedData; // Creating Node auto* Node = ResourceLoaderImpl.CreateInitBankNode(CookedData); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node FWwiseLoadedInitBankPromise LoadPromise; auto LoadFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadInitBankAsync(MoveTemp(LoadPromise), MoveTemp(Node)); auto Loaded = LoadFuture.Get(); // Synchronously CHECK(Loaded); if (UNLIKELY(!Loaded)) { return; } // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; auto UnloadFuture = UnloadPromise.GetFuture(); ResourceLoaderImpl.UnloadInitBankAsync(MoveTemp(UnloadPromise), MoveTemp(Loaded)); UnloadFuture.Get(); // Synchronously CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Sync Media") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseMediaCookedData CookedData; // Creating Node auto* Node = ResourceLoaderImpl.CreateMediaNode(CookedData); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node FWwiseLoadedMediaPromise LoadPromise; auto LoadFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadMediaAsync(MoveTemp(LoadPromise), MoveTemp(Node)); auto Loaded = LoadFuture.Get(); // Synchronously CHECK(Loaded); if (UNLIKELY(!Loaded)) { return; } // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; auto UnloadFuture = UnloadPromise.GetFuture(); ResourceLoaderImpl.UnloadMediaAsync(MoveTemp(UnloadPromise), MoveTemp(Loaded)); UnloadFuture.Get(); // Synchronously CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Sync ShareSet") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseLocalizedShareSetCookedData CookedData; { FWwiseShareSetCookedData Data1; Data1.Media.Emplace(FWwiseMediaCookedData{}); Data1.SoundBanks.Emplace(FWwiseSoundBankCookedData{}); CookedData.ShareSetLanguageMap.Emplace(FWwiseLanguageCookedData::Sfx, MoveTemp(Data1)); } // Creating Node auto* Node = ResourceLoaderImpl.CreateShareSetNode(CookedData, &FWwiseLanguageCookedData::Sfx); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node FWwiseLoadedShareSetPromise LoadPromise; auto LoadFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadShareSetAsync(MoveTemp(LoadPromise), MoveTemp(Node)); auto Loaded = LoadFuture.Get(); // Synchronously CHECK(Loaded); if (UNLIKELY(!Loaded)) { return; } // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; auto UnloadFuture = UnloadPromise.GetFuture(); ResourceLoaderImpl.UnloadShareSetAsync(MoveTemp(UnloadPromise), MoveTemp(Loaded)); UnloadFuture.Get(); // Synchronously CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Sync SoundBank") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseLocalizedSoundBankCookedData CookedData; { FWwiseSoundBankCookedData Data1; CookedData.SoundBankLanguageMap.Emplace(FWwiseLanguageCookedData::Sfx, MoveTemp(Data1)); } // Creating Node auto* Node = ResourceLoaderImpl.CreateSoundBankNode(CookedData, nullptr); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node FWwiseLoadedSoundBankPromise LoadPromise; auto LoadFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadSoundBankAsync(MoveTemp(LoadPromise), MoveTemp(Node)); auto Loaded = LoadFuture.Get(); // Synchronously CHECK(Loaded); if (UNLIKELY(!Loaded)) { return; } // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; auto UnloadFuture = UnloadPromise.GetFuture(); ResourceLoaderImpl.UnloadSoundBankAsync(MoveTemp(UnloadPromise), MoveTemp(Loaded)); UnloadFuture.Get(); // Synchronously CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Sync SwitchContainer (Event First)") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseGroupValueCookedData GroupValue; GroupValue.Id = 1; GroupValue.GroupId = 1; GroupValue.Type = EWwiseGroupType::Switch; FWwiseLoadedEventPtr LoadedEvent{nullptr}; FWwiseLoadedGroupValuePtr LoadedGroupValue{nullptr}; // Load Event do { FWwiseLocalizedEventCookedData CookedData; { FWwiseEventCookedData Data1; Data1.Media.Emplace(FWwiseMediaCookedData{}); Data1.SoundBanks.Emplace(FWwiseSoundBankCookedData{}); FWwiseSwitchContainerLeafCookedData Leaf1; FWwiseMediaCookedData Leaf1Media; Leaf1Media.MediaId = 1; Leaf1.Media.Emplace(MoveTemp(Leaf1Media)); Leaf1.GroupValueSet.Emplace(GroupValue); Data1.SwitchContainerLeaves.Emplace(MoveTemp(Leaf1)); CookedData.EventLanguageMap.Emplace(FWwiseLanguageCookedData::Sfx, MoveTemp(Data1)); } // Creating Node auto* Node = ResourceLoaderImpl.CreateEventNode(CookedData, nullptr); CHECK(Node); if (UNLIKELY(!Node)) { break; } // Loading Node FWwiseLoadedEventPromise LoadPromise; auto LoadFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadEventAsync(MoveTemp(LoadPromise), MoveTemp(Node)); LoadedEvent = LoadFuture.Get(); // Synchronously CHECK(LoadedEvent); if (UNLIKELY(!LoadedEvent)) { break; } } while (false); // Load GroupValue do { // Creating Node auto* Node = ResourceLoaderImpl.CreateGroupValueNode(GroupValue); CHECK(Node); if (UNLIKELY(!Node)) { break; } // Loading Node FWwiseLoadedGroupValuePromise LoadPromise; auto LoadFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadGroupValueAsync(MoveTemp(LoadPromise), MoveTemp(Node)); LoadedGroupValue = LoadFuture.Get(); // Synchronously CHECK(LoadedGroupValue); if (UNLIKELY(!LoadedGroupValue)) { break; } } while(false); // Unloading GroupValue if (LIKELY(LoadedGroupValue)) { FWwiseResourceUnloadPromise UnloadPromise; auto UnloadFuture = UnloadPromise.GetFuture(); ResourceLoaderImpl.UnloadGroupValueAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedGroupValue)); UnloadFuture.Get(); // Synchronously } // Unloading Event if (LIKELY(LoadedEvent)) { FWwiseResourceUnloadPromise UnloadPromise; auto UnloadFuture = UnloadPromise.GetFuture(); ResourceLoaderImpl.UnloadEventAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedEvent)); UnloadFuture.Get(); // Synchronously } CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Sync SwitchContainer (Switch First)") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseGroupValueCookedData GroupValue; GroupValue.Id = 1; GroupValue.GroupId = 1; GroupValue.Type = EWwiseGroupType::Switch; FWwiseLoadedEventPtr LoadedEvent{nullptr}; FWwiseLoadedGroupValuePtr LoadedGroupValue{nullptr}; // Load GroupValue do { // Creating Node auto* Node = ResourceLoaderImpl.CreateGroupValueNode(GroupValue); CHECK(Node); if (UNLIKELY(!Node)) { break; } // Loading Node FWwiseLoadedGroupValuePromise LoadPromise; auto LoadFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadGroupValueAsync(MoveTemp(LoadPromise), MoveTemp(Node)); LoadedGroupValue = LoadFuture.Get(); // Synchronously CHECK(LoadedGroupValue); if (UNLIKELY(!LoadedGroupValue)) { break; } } while(false); // Load Event do { FWwiseLocalizedEventCookedData CookedData; { FWwiseEventCookedData Data1; Data1.Media.Emplace(FWwiseMediaCookedData{}); Data1.SoundBanks.Emplace(FWwiseSoundBankCookedData{}); FWwiseSwitchContainerLeafCookedData Leaf1; FWwiseMediaCookedData Leaf1Media; Leaf1Media.MediaId = 1; Leaf1.Media.Emplace(MoveTemp(Leaf1Media)); Leaf1.GroupValueSet.Emplace(GroupValue); Data1.SwitchContainerLeaves.Emplace(MoveTemp(Leaf1)); CookedData.EventLanguageMap.Emplace(FWwiseLanguageCookedData::Sfx, MoveTemp(Data1)); } // Creating Node auto* Node = ResourceLoaderImpl.CreateEventNode(CookedData, nullptr); CHECK(Node); if (UNLIKELY(!Node)) { break; } // Loading Node FWwiseLoadedEventPromise LoadPromise; auto LoadFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadEventAsync(MoveTemp(LoadPromise), MoveTemp(Node)); LoadedEvent = LoadFuture.Get(); // Synchronously CHECK(LoadedEvent); if (UNLIKELY(!LoadedEvent)) { break; } } while (false); // Unloading Event if (LIKELY(LoadedEvent)) { FWwiseResourceUnloadPromise UnloadPromise; auto UnloadFuture = UnloadPromise.GetFuture(); ResourceLoaderImpl.UnloadEventAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedEvent)); UnloadFuture.Get(); // Synchronously } // Unloading GroupValue if (LIKELY(LoadedGroupValue)) { FWwiseResourceUnloadPromise UnloadPromise; auto UnloadFuture = UnloadPromise.GetFuture(); ResourceLoaderImpl.UnloadGroupValueAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedGroupValue)); UnloadFuture.Get(); // Synchronously } CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } } WWISE_TEST_CASE(ResourceLoader_Async, "Wwise::ResourceLoader::ResourceLoader_Async", "[ApplicationContextMask][ProductFilter]") { // Keeping these in prime numbers so we get a higher set of possibilities static constexpr const auto GroupValueCount = 7; static constexpr const auto FileCount = 23; static constexpr const auto CookedDataCount = 53; static constexpr const auto NodeCount = 173; SECTION("Async AuxBus") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseLocalizedAuxBusCookedData CookedDatas[CookedDataCount]; for (auto i = 0; i < CookedDataCount; ++i) { FWwiseAuxBusCookedData Data; Data.AuxBusId = i % CookedDataCount; Data.Media.Emplace(FWwiseMediaCookedData{}); Data.Media[0].MediaId = i % FileCount; Data.SoundBanks.Emplace(FWwiseSoundBankCookedData{}); CookedDatas[i].AuxBusLanguageMap.Emplace(FWwiseLanguageCookedData::Sfx, MoveTemp(Data)); } FWwiseResourceUnloadFuture UnloadFutures[NodeCount]; for (auto i = 0; i < NodeCount; ++i) { // Creating Node auto* Node = ResourceLoaderImpl.CreateAuxBusNode(CookedDatas[i%CookedDataCount], nullptr); CHECK(Node); if (UNLIKELY(!Node)) { continue; } // Loading Node FWwiseLoadedAuxBusPromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadAuxBusAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[i] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedAuxBusPtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadAuxBusAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } for (auto i = 0; i < NodeCount; ++i) { UnloadFutures[i].Get(); } CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Async Event & GroupValue") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseLocalizedEventCookedData EventCookedDatas[CookedDataCount]; for (auto i = 0; i < CookedDataCount; ++i) { FWwiseEventCookedData Data; Data.EventId = i % CookedDataCount; Data.Media.Emplace(FWwiseMediaCookedData{}); Data.Media[0].MediaId = i % FileCount; Data.SoundBanks.Emplace(FWwiseSoundBankCookedData{}); FWwiseSwitchContainerLeafCookedData Leaf; FWwiseMediaCookedData LeafMedia; LeafMedia.MediaId = (i % FileCount) + 2; // Allow overlap while allow for uniques Leaf.Media.Emplace(MoveTemp(LeafMedia)); FWwiseGroupValueCookedData LeafGroupValue; LeafGroupValue.Id = i % GroupValueCount; LeafGroupValue.GroupId = 1; LeafGroupValue.Type = EWwiseGroupType::Switch; Leaf.GroupValueSet.Emplace(MoveTemp(LeafGroupValue)); Data.SwitchContainerLeaves.Emplace(MoveTemp(Leaf)); EventCookedDatas[i].EventLanguageMap.Emplace(FWwiseLanguageCookedData::Sfx, MoveTemp(Data)); } FWwiseGroupValueCookedData GroupValueCookedDatas[CookedDataCount]; for (auto i = 0; i < CookedDataCount; ++i) { GroupValueCookedDatas[i].Id = GroupValueCount - (i % GroupValueCount) - 1; // Make them opposite GroupValueCookedDatas[i].GroupId = 1; GroupValueCookedDatas[i].Type = EWwiseGroupType::Switch; } constexpr const auto FuturesCount = NodeCount * 2; FWwiseResourceUnloadFuture UnloadFutures[FuturesCount]; for (auto i = 0; i < NodeCount; ++i) { auto DoEvent = [&ResourceLoaderImpl, &EventCookedDatas, i, &UnloadFutures]() mutable { // Creating Node auto* Node = ResourceLoaderImpl.CreateEventNode(EventCookedDatas[i%CookedDataCount], nullptr); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node FWwiseLoadedEventPromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadEventAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[i] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedEventPtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadEventAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); }; auto DoGroupValue = [&ResourceLoaderImpl, &GroupValueCookedDatas, i, &UnloadFutures]() mutable { // Creating Node auto* Node = ResourceLoaderImpl.CreateGroupValueNode(GroupValueCookedDatas[i%CookedDataCount]); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node FWwiseLoadedGroupValuePromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadGroupValueAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[NodeCount+i] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedGroupValuePtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadGroupValueAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); }; // Vary which one is done first if (i & 4) { DoEvent(); DoGroupValue(); } else { DoGroupValue(); DoEvent(); } } for (auto i = 0; i < FuturesCount; ++i) { UnloadFutures[i].Get(); } CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Async External Source") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseExternalSourceCookedData CookedDatas[CookedDataCount]; for (auto i = 0; i < CookedDataCount; ++i) { CookedDatas[i].Cookie = i % FileCount; } FWwiseResourceUnloadFuture UnloadFutures[NodeCount]; for (auto i = 0; i < NodeCount; ++i) { auto* Node = ResourceLoaderImpl.CreateExternalSourceNode(CookedDatas[i%CookedDataCount]); CHECK(Node); if (UNLIKELY(!Node)) { continue; } // Loading node FWwiseLoadedExternalSourcePromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadExternalSourceAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[i] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedExternalSourcePtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadExternalSourceAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } for (auto i = 0; i < NodeCount; ++i) { UnloadFutures[i].Get(); } CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Async InitBank") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseInitBankCookedData CookedDatas[CookedDataCount]; for (auto i = 0; i < CookedDataCount; ++i) { CookedDatas[i].Media.Emplace(FWwiseMediaCookedData{}); CookedDatas[i].Media[0].MediaId = i % FileCount; } FWwiseResourceUnloadFuture UnloadFutures[NodeCount]; for (auto i = 0; i < NodeCount; ++i) { // Creating Node auto* Node = ResourceLoaderImpl.CreateInitBankNode(CookedDatas[i%CookedDataCount]); CHECK(Node); if (UNLIKELY(!Node)) { continue; } // Loading Node FWwiseLoadedInitBankPromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadInitBankAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[i] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedInitBankPtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadInitBankAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } for (auto i = 0; i < NodeCount; ++i) { UnloadFutures[i].Get(); } CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Async Media") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseMediaCookedData CookedDatas[CookedDataCount]; for (auto i = 0; i < CookedDataCount; ++i) { CookedDatas[i].MediaId = i % FileCount; CookedDatas[i].bStreaming = !!(i % 2); } FWwiseResourceUnloadFuture UnloadFutures[NodeCount]; for (auto i = 0; i < NodeCount; ++i) { // Creating Node auto* Node = ResourceLoaderImpl.CreateMediaNode(CookedDatas[i%CookedDataCount]); CHECK(Node); if (UNLIKELY(!Node)) { continue; } // Loading Node FWwiseLoadedMediaPromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadMediaAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[i] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedMediaPtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadMediaAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } for (auto i = 0; i < NodeCount; ++i) { UnloadFutures[i].Get(); } CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Async ShareSet") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseLocalizedShareSetCookedData CookedDatas[CookedDataCount]; for (auto i = 0; i < CookedDataCount; ++i) { FWwiseShareSetCookedData Data; Data.ShareSetId = i % CookedDataCount; Data.Media.Emplace(FWwiseMediaCookedData{}); Data.Media[0].MediaId = i % FileCount; Data.SoundBanks.Emplace(FWwiseSoundBankCookedData{}); CookedDatas[i].ShareSetLanguageMap.Emplace(FWwiseLanguageCookedData::Sfx, MoveTemp(Data)); } FWwiseResourceUnloadFuture UnloadFutures[NodeCount]; for (auto i = 0; i < NodeCount; ++i) { // Creating Node auto* Node = ResourceLoaderImpl.CreateShareSetNode(CookedDatas[i%CookedDataCount], &FWwiseLanguageCookedData::Sfx); CHECK(Node); if (UNLIKELY(!Node)) { continue; } // Loading Node FWwiseLoadedShareSetPromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadShareSetAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[i] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedShareSetPtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadShareSetAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } for (auto i = 0; i < NodeCount; ++i) { UnloadFutures[i].Get(); } CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Async SoundBank") { FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseLocalizedSoundBankCookedData CookedDatas[CookedDataCount]; for (auto i = 0; i < CookedDataCount; ++i) { FWwiseSoundBankCookedData Data; Data.SoundBankId = i % FileCount; CookedDatas[i].SoundBankLanguageMap.Emplace(FWwiseLanguageCookedData::Sfx, MoveTemp(Data)); } FWwiseResourceUnloadFuture UnloadFutures[NodeCount]; for (auto i = 0; i < NodeCount; ++i) { // Creating Node auto* Node = ResourceLoaderImpl.CreateSoundBankNode(CookedDatas[i%CookedDataCount], nullptr); CHECK(Node); if (UNLIKELY(!Node)) { continue; } // Loading Node FWwiseLoadedSoundBankPromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadSoundBankAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[i] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedSoundBankPtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadSoundBankAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } for (auto i = 0; i < NodeCount; ++i) { UnloadFutures[i].Get(); } CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } } WWISE_TEST_CASE(ResourceLoader_EdgeCases, "Wwise::ResourceLoader::ResourceLoader_EdgeCases", "[ApplicationContextMask][ProductFilter]") { SECTION("GroupValue PendingKill Reload") { static constexpr const auto Count = 16; const bool bMockSleepOnMediaLoad = FWwiseResourceLoaderImpl::Test::bMockSleepOnMediaLoad; FWwiseResourceLoaderImpl::Test::bMockSleepOnMediaLoad = true; ON_SCOPE_EXIT { FWwiseResourceLoaderImpl::Test::bMockSleepOnMediaLoad = bMockSleepOnMediaLoad; }; FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseLocalizedEventCookedData EventCookedDatas[Count]; FWwiseGroupValueCookedData GroupValueCookedDatas[Count]; for (auto i = 0; i < Count; ++i) { // Event FWwiseEventCookedData Data; Data.EventId = i; Data.Media.Emplace(FWwiseMediaCookedData{}); Data.Media[0].MediaId = i + Count; FWwiseSwitchContainerLeafCookedData Leaf; FWwiseMediaCookedData LeafMedia; LeafMedia.MediaId = i; Leaf.Media.Emplace(MoveTemp(LeafMedia)); FWwiseGroupValueCookedData LeafGroupValue; LeafGroupValue.Id = i; LeafGroupValue.GroupId = 1; LeafGroupValue.Type = EWwiseGroupType::Switch; Leaf.GroupValueSet.Emplace(MoveTemp(LeafGroupValue)); Data.SwitchContainerLeaves.Emplace(MoveTemp(Leaf)); EventCookedDatas[i].EventLanguageMap.Emplace(FWwiseLanguageCookedData::Sfx, MoveTemp(Data)); // GroupValue GroupValueCookedDatas[i].Id = i; GroupValueCookedDatas[i].GroupId = 1; GroupValueCookedDatas[i].Type = EWwiseGroupType::Switch; } constexpr const auto FuturesCount = Count * 2; FWwiseLoadedEventFuture LoadFutures[Count]; FWwiseResourceUnloadFuture UnloadFutures[FuturesCount]; for (auto i = 0; i < Count; ++i) { auto DoEvent = [&LoadFutures, &ResourceLoaderImpl, &EventCookedDatas, i]() mutable { // Creating Node auto* Node = ResourceLoaderImpl.CreateEventNode(EventCookedDatas[i%Count], nullptr); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node 1 FWwiseLoadedEventPromise LoadPromise; LoadFutures[i] = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadEventAsync(MoveTemp(LoadPromise), MoveTemp(Node)); LoadFutures[i].Wait(); }; auto UnloadEvent = [i, &ResourceLoaderImpl, &LoadFutures, &UnloadFutures]() mutable { // Unloading Event FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[i] = UnloadPromise.GetFuture(); ResourceLoaderImpl.UnloadEventAsync(MoveTemp(UnloadPromise), LoadFutures[i].Get()); }; auto DoGroupValue = [&ResourceLoaderImpl, &GroupValueCookedDatas, i, &UnloadFutures]() mutable { // Creating Node auto* Node = ResourceLoaderImpl.CreateGroupValueNode(GroupValueCookedDatas[i%Count]); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node 1 FWwiseLoadedGroupValuePromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadGroupValueAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Future for completion (Unloading Node 2) FWwiseResourceUnloadPromise UnloadPromise2; UnloadFutures[Count+i] = UnloadPromise2.GetFuture(); NodeFuture.Next([i, &GroupValueCookedDatas, &ResourceLoaderImpl, UnloadPromise2 = MoveTemp(UnloadPromise2)](FWwiseLoadedGroupValuePtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise2.EmplaceValue(); return; } // Loading Node 2 auto* Node = ResourceLoaderImpl.CreateGroupValueNode(GroupValueCookedDatas[i%Count]); FWwiseLoadedGroupValuePromise LoadPromise2; auto NodeFuture2 = LoadPromise2.GetFuture(); if (i&8) { ResourceLoaderImpl.LoadGroupValueAsync(MoveTemp(LoadPromise2), MoveTemp(Node)); } // Unload Node 1 FWwiseResourceUnloadPromise UnloadPromise; auto UnloadFuture = UnloadPromise.GetFuture(); ResourceLoaderImpl.UnloadGroupValueAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); if (!(i&8)) { ResourceLoaderImpl.LoadGroupValueAsync(MoveTemp(LoadPromise2), MoveTemp(Node)); } // Wait for Loading Node 2 NodeFuture2.Next([&ResourceLoaderImpl, UnloadFuture = MoveTemp(UnloadFuture), UnloadPromise2 = MoveTemp(UnloadPromise2)](FWwiseLoadedGroupValuePtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise2.EmplaceValue(); return; } // Wait for Unloading Node 1 UnloadFuture.Next([&ResourceLoaderImpl, UnloadPromise2 = MoveTemp(UnloadPromise2), LoadedNode](int) mutable { // Unloading Node 2 and terminate ResourceLoaderImpl.UnloadGroupValueAsync(MoveTemp(UnloadPromise2), MoveTemp(LoadedNode)); }); }); }); }; DoEvent(); DoGroupValue(); UnloadEvent(); } for (auto i = 0; i < FuturesCount; ++i) { UnloadFutures[i].Get(); } CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Event PendingKill Reload") { static constexpr const auto Count = 16; const bool bMockSleepOnMediaLoad = FWwiseResourceLoaderImpl::Test::bMockSleepOnMediaLoad; FWwiseResourceLoaderImpl::Test::bMockSleepOnMediaLoad = true; ON_SCOPE_EXIT { FWwiseResourceLoaderImpl::Test::bMockSleepOnMediaLoad = bMockSleepOnMediaLoad; }; FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); FWwiseLocalizedEventCookedData EventCookedDatas[Count]; FWwiseGroupValueCookedData GroupValueCookedDatas[Count]; for (auto i = 0; i < Count; ++i) { // Event FWwiseEventCookedData Data; Data.EventId = i; Data.Media.Emplace(FWwiseMediaCookedData{}); Data.Media[0].MediaId = i + Count; FWwiseSwitchContainerLeafCookedData Leaf; FWwiseMediaCookedData LeafMedia; LeafMedia.MediaId = i; Leaf.Media.Emplace(MoveTemp(LeafMedia)); FWwiseGroupValueCookedData LeafGroupValue; LeafGroupValue.Id = i; LeafGroupValue.GroupId = 1; LeafGroupValue.Type = EWwiseGroupType::Switch; Leaf.GroupValueSet.Emplace(MoveTemp(LeafGroupValue)); Data.SwitchContainerLeaves.Emplace(MoveTemp(Leaf)); EventCookedDatas[i].EventLanguageMap.Emplace(FWwiseLanguageCookedData::Sfx, MoveTemp(Data)); // GroupValue GroupValueCookedDatas[i].Id = i; GroupValueCookedDatas[i].GroupId = 1; GroupValueCookedDatas[i].Type = EWwiseGroupType::Switch; } constexpr const auto FuturesCount = Count * 2; FWwiseLoadedGroupValueFuture LoadFutures[Count]; FWwiseResourceUnloadFuture UnloadFutures[FuturesCount]; for (auto i = 0; i < Count; ++i) { auto DoEvent = [&ResourceLoaderImpl, &EventCookedDatas, i, &UnloadFutures]() mutable { // Creating Node auto* Node = ResourceLoaderImpl.CreateEventNode(EventCookedDatas[i%Count], nullptr); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node 1 FWwiseLoadedEventPromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadEventAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Future for completion (Unloading Node 2) FWwiseResourceUnloadPromise UnloadPromise2; UnloadFutures[i] = UnloadPromise2.GetFuture(); NodeFuture.Next([i, &EventCookedDatas, &ResourceLoaderImpl, UnloadPromise2 = MoveTemp(UnloadPromise2)](FWwiseLoadedEventPtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise2.EmplaceValue(); return; } // Creating Node 2 auto* Node2 = ResourceLoaderImpl.CreateEventNode(EventCookedDatas[i%Count], nullptr); // Loading Node 2 FWwiseLoadedEventPromise LoadPromise2; auto NodeFuture2 = LoadPromise2.GetFuture(); if (i&4) { ResourceLoaderImpl.LoadEventAsync(MoveTemp(LoadPromise2), MoveTemp(Node2)); } // Unloading Node 1 FWwiseResourceUnloadPromise UnloadPromise; auto UnloadFuture = UnloadPromise.GetFuture(); ResourceLoaderImpl.UnloadEventAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); if (!(i&4)) { ResourceLoaderImpl.LoadEventAsync(MoveTemp(LoadPromise2), MoveTemp(Node2)); } // Wait for Loading Node 2 NodeFuture2.Next([&ResourceLoaderImpl, UnloadFuture = MoveTemp(UnloadFuture), UnloadPromise2 = MoveTemp(UnloadPromise2)](FWwiseLoadedEventPtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise2.EmplaceValue(); return; } // Wait for Unloading Node 1 UnloadFuture.Next([&ResourceLoaderImpl, UnloadPromise2 = MoveTemp(UnloadPromise2), LoadedNode](int) mutable { // Unloading Node 2 and terminate ResourceLoaderImpl.UnloadEventAsync(MoveTemp(UnloadPromise2), MoveTemp(LoadedNode)); }); }); }); }; auto DoGroupValue = [&LoadFutures, &ResourceLoaderImpl, &GroupValueCookedDatas, i]() mutable { // Creating Node auto* Node = ResourceLoaderImpl.CreateGroupValueNode(GroupValueCookedDatas[i%Count]); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node 1 FWwiseLoadedGroupValuePromise LoadPromise; LoadFutures[i] = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadGroupValueAsync(MoveTemp(LoadPromise), MoveTemp(Node)); LoadFutures[i].Wait(); }; auto UnloadGroupValue = [&LoadFutures, &ResourceLoaderImpl, i, &UnloadFutures]() mutable { // Future for completion (Unloading Node 2) FWwiseResourceUnloadPromise UnloadPromise2; UnloadFutures[Count+i] = UnloadPromise2.GetFuture(); ResourceLoaderImpl.UnloadGroupValueAsync(MoveTemp(UnloadPromise2), LoadFutures[i].Get()); }; DoEvent(); DoGroupValue(); UnloadGroupValue(); } for (auto i = 0; i < FuturesCount; ++i) { UnloadFutures[i].Get(); } CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } } WWISE_TEST_CASE(ResourceLoader_Stress, "Wwise::ResourceLoader::ResourceLoader_Stress", "[ApplicationContextMask][StressFilter]") { SECTION("Random All") { static constexpr const auto NumOp = 10000; static constexpr const uint32 NumAuxBusses = 31; static constexpr const uint32 NumEvents = 500; static constexpr const uint32 NumExternalSources = 16; static constexpr const uint32 NumInitBanks = 10; static constexpr const uint32 NumMedia = 2000; static constexpr const uint32 NumShareSets = 15; static constexpr const uint32 NumSoundBanks = 200; static constexpr const uint32 NumStates = 17; static constexpr const uint32 NumSwitches = 14; static constexpr const uint32 MaxLeaves = 10; auto AuxBussesPtr = std::make_unique>(); auto EventsPtr = std::make_unique>(); auto ExternalSourcePtr = std::make_unique>(); auto InitBanksPtr = std::make_unique>(); auto MediaPtr = std::make_unique>(); auto ShareSetsPtr = std::make_unique>(); auto SoundBanksPtr = std::make_unique>(); auto StatesPtr = std::make_unique>(); auto SwitchesPtr = std::make_unique>(); auto& AuxBusses = *AuxBussesPtr; auto& Events = *EventsPtr; auto& ExternalSources = *ExternalSourcePtr; auto& InitBanks = *InitBanksPtr; auto& Media = *MediaPtr; auto& ShareSets = *ShareSetsPtr; auto& SoundBanks = *SoundBanksPtr; auto& States = *StatesPtr; auto& Switches = *SwitchesPtr; // Fill up data { int32 Id = 0; for (auto& Medii : Media) { const auto Rnd = WwiseHashCombineFast(GetTypeHash(Id), 54209201); Medii.MediaId = ++Id; Medii.bStreaming = (Rnd % 4) == 0; } for (auto& SoundBank : SoundBanks) { FWwiseSoundBankCookedData Data; Data.SoundBankId = ++Id; SoundBank.SoundBankLanguageMap.Add(FWwiseLanguageCookedData::Sfx, MoveTemp(Data)); } for (auto& ShareSet : ShareSets) { FWwiseShareSetCookedData Data; Data.ShareSetId = ++Id; auto RequiredMediaCount = WwiseHashCombineFast(GetTypeHash(Id), 139) % (MaxLeaves*2); if (RequiredMediaCount > MaxLeaves) RequiredMediaCount = 0; auto RequiredSoundBankCount = WwiseHashCombineFast(GetTypeHash(Id),295) % (MaxLeaves*3); if (RequiredSoundBankCount > MaxLeaves) RequiredSoundBankCount = 0; for (uint32 i=0; i < RequiredMediaCount; ++i) { const auto MediaToAdd = WwiseHashCombineFast(GetTypeHash(Id), i ^ 9042) % NumMedia; Data.Media.Add(Media[MediaToAdd]); } for (uint32 i=0; i < RequiredSoundBankCount; ++i) { const auto SoundBankToAdd = WwiseHashCombineFast(GetTypeHash(Id), i ^ 5930) % NumSoundBanks; Data.SoundBanks.Add(SoundBanks[SoundBankToAdd].SoundBankLanguageMap[FWwiseLanguageCookedData::Sfx]); } ShareSet.ShareSetLanguageMap.Add(FWwiseLanguageCookedData::Sfx, MoveTemp(Data)); } for (auto& State : States) { State.GroupId = 0; State.Id = ++Id; State.Type = EWwiseGroupType::State; } for (auto& Switch : Switches) { Switch.GroupId = 0; Switch.Id = ++Id; Switch.Type = EWwiseGroupType::Switch; } for (auto& AuxBus : AuxBusses) { FWwiseAuxBusCookedData Data; Data.AuxBusId = ++Id; auto RequiredMediaCount = WwiseHashCombineFast(GetTypeHash(Id), 57381) % (MaxLeaves*2); if (RequiredMediaCount > MaxLeaves) RequiredMediaCount = 0; auto RequiredSoundBankCount = WwiseHashCombineFast(GetTypeHash(Id), 414) % (MaxLeaves*3); if (RequiredSoundBankCount > MaxLeaves) RequiredSoundBankCount = 0; for (uint32 i=0; i < RequiredMediaCount; ++i) { const auto MediaToAdd = WwiseHashCombineFast(GetTypeHash(Id), i ^ 9385938) % NumMedia; Data.Media.Add(Media[MediaToAdd]); } for (uint32 i=0; i < RequiredSoundBankCount; ++i) { const auto SoundBankToAdd = WwiseHashCombineFast(GetTypeHash(Id), i ^ 223982) % NumSoundBanks; Data.SoundBanks.Add(SoundBanks[SoundBankToAdd].SoundBankLanguageMap[FWwiseLanguageCookedData::Sfx]); } AuxBus.AuxBusLanguageMap.Add(FWwiseLanguageCookedData::Sfx, MoveTemp(Data)); } for (auto& InitBank : InitBanks) { InitBank.SoundBankId = ++Id; auto RequiredMediaCount = WwiseHashCombineFast(GetTypeHash(Id), 1758317) % (MaxLeaves*2); if (RequiredMediaCount > MaxLeaves) RequiredMediaCount = 0; for (uint32 i=0; i < RequiredMediaCount; ++i) { const auto MediaToAdd = WwiseHashCombineFast(GetTypeHash(Id), i ^ 190469) % NumMedia; InitBank.Media.Add(Media[MediaToAdd]); } } for (auto& Event : Events) { FWwiseEventCookedData Data; Data.EventId = ++Id; auto RequiredMediaCount = WwiseHashCombineFast(GetTypeHash(Id), 57381) % (MaxLeaves*2); if (RequiredMediaCount > MaxLeaves) RequiredMediaCount = 0; auto RequiredSoundBankCount = WwiseHashCombineFast(GetTypeHash(Id), 414) % (MaxLeaves*3); if (RequiredSoundBankCount > MaxLeaves) RequiredSoundBankCount = 0; auto RequiredGroupValueCount = WwiseHashCombineFast(GetTypeHash(Id), 462058) % (MaxLeaves*4); if (RequiredGroupValueCount > MaxLeaves) RequiredGroupValueCount = 0; auto RequiredSwitchContainerLeafCount = WwiseHashCombineFast(GetTypeHash(Id), 953019401) % (MaxLeaves*2); if (RequiredSwitchContainerLeafCount > MaxLeaves) RequiredSwitchContainerLeafCount = 0; for (uint32 i=0; i < RequiredMediaCount; ++i) { const auto MediaToAdd = WwiseHashCombineFast(GetTypeHash(Id), i ^ 9385938) % NumMedia; Data.Media.Add(Media[MediaToAdd]); } for (uint32 i=0; i < RequiredSoundBankCount; ++i) { const auto SoundBankToAdd = WwiseHashCombineFast(GetTypeHash(Id), i ^ 223982) % NumSoundBanks; Data.SoundBanks.Add(SoundBanks[SoundBankToAdd].SoundBankLanguageMap[FWwiseLanguageCookedData::Sfx]); } for (uint32 i=0; i < RequiredGroupValueCount; ++i) { const bool bIsState = WwiseHashCombineFast(GetTypeHash(Id), i ^ 190101) % 4 == 0; // Mostly switches const auto NumToAdd = WwiseHashCombineFast(GetTypeHash(Id), i ^ 415938) % (bIsState ? NumStates : NumSwitches); Data.RequiredGroupValueSet.Add(bIsState ? States[NumToAdd] : Switches[NumToAdd]); } for (uint32 LeafIter=0; LeafIter < RequiredSwitchContainerLeafCount; ++LeafIter) { FWwiseSwitchContainerLeafCookedData Leaf; auto MediaCount = WwiseHashCombineFast(GetTypeHash(Id), LeafIter ^ 4863419941) % (MaxLeaves * 2); if (MediaCount > MaxLeaves) MediaCount = 0; ++MediaCount; // At least one auto SoundBankCount = WwiseHashCombineFast(GetTypeHash(Id), LeafIter ^ 1065842795) % (MaxLeaves*3); if (SoundBankCount > MaxLeaves) SoundBankCount = 0; auto GroupValueCount = WwiseHashCombineFast(GetTypeHash(Id), LeafIter ^ 2828284104) % MaxLeaves; if (GroupValueCount > MaxLeaves / 2) GroupValueCount = 0; ++GroupValueCount; // At least one for (uint32 i=0; i < MediaCount; ++i) { const auto MediaToAdd = WwiseHashCombineFast(GetTypeHash(Id), i ^ LeafIter ^ 19891919) % NumMedia; Leaf.Media.Add(Media[MediaToAdd]); } for (uint32 i=0; i < SoundBankCount; ++i) { const auto SoundBankToAdd = WwiseHashCombineFast(GetTypeHash(Id), i ^ LeafIter ^ 924606208) % NumSoundBanks; Leaf.SoundBanks.Add(SoundBanks[SoundBankToAdd].SoundBankLanguageMap[FWwiseLanguageCookedData::Sfx]); } for (uint32 i=0; i < GroupValueCount; ++i) { const bool bIsState = WwiseHashCombineFast(GetTypeHash(Id), i ^ LeafIter ^ 8484846414) % 4 == 0; // Mostly switches const auto NumToAdd = WwiseHashCombineFast(GetTypeHash(Id), i ^ LeafIter ^ 10671953) % (bIsState ? NumStates : NumSwitches); Leaf.GroupValueSet.Add(bIsState ? States[NumToAdd] : Switches[NumToAdd]); } Data.SwitchContainerLeaves.Add(MoveTemp(Leaf)); } Event.EventLanguageMap.Add(FWwiseLanguageCookedData::Sfx, MoveTemp(Data)); } for (auto& ExternalSource: ExternalSources) { FWwiseExternalSourceCookedData Data; Data.Cookie = ++Id; } } // Operations FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); auto UnloadFuturesPtr = std::make_unique>(); auto& UnloadFutures = *UnloadFuturesPtr; for (uint32 Op = 0; Op < NumOp; ++Op) { const auto Type = WwiseHashCombineFast(GetTypeHash(Op), 939401805) % 10; switch(Type) { case 0: // AuxBus { const auto AuxBus = WwiseHashCombineFast(GetTypeHash(Op), 2076755927) % NumAuxBusses; // Creating Node auto* Node = ResourceLoaderImpl.CreateAuxBusNode(AuxBusses[AuxBus], nullptr); CHECK(Node); if (UNLIKELY(!Node)) { continue; } // Loading Node FWwiseLoadedAuxBusPromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadAuxBusAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[Op] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedAuxBusPtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadAuxBusAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } break; default: // More Events than anything else! (see Type's Modulo for how many are events) case 1: // Event { const auto Event = WwiseHashCombineFast(GetTypeHash(Op), 549205820) % NumEvents; // Creating Node auto* Node = ResourceLoaderImpl.CreateEventNode(Events[Event], nullptr); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node FWwiseLoadedEventPromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadEventAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[Op] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedEventPtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadEventAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } break; case 2: // External source { const auto ExternalSource = WwiseHashCombineFast(GetTypeHash(Op), 64897712) % NumExternalSources; // Creating Node auto* Node = ResourceLoaderImpl.CreateExternalSourceNode(ExternalSources[ExternalSource]); CHECK(Node); if (UNLIKELY(!Node)) { continue; } // Loading Node FWwiseLoadedExternalSourcePromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadExternalSourceAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[Op] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedExternalSourcePtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadExternalSourceAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } break; case 3: // Init Bank { const auto InitBank = WwiseHashCombineFast(GetTypeHash(Op), 65471010) % NumInitBanks; // Creating Node auto* Node = ResourceLoaderImpl.CreateInitBankNode(InitBanks[InitBank]); CHECK(Node); if (UNLIKELY(!Node)) { continue; } // Loading Node FWwiseLoadedInitBankPromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadInitBankAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[Op] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedInitBankPtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadInitBankAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } break; case 4: // Media { const auto Medii = WwiseHashCombineFast(GetTypeHash(Op), 193119891) % NumMedia; // Creating Node auto* Node = ResourceLoaderImpl.CreateMediaNode(Media[Medii]); CHECK(Node); if (UNLIKELY(!Node)) { continue; } // Loading Node FWwiseLoadedMediaPromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadMediaAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[Op] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedMediaPtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadMediaAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } break; case 5: // ShareSet { const auto ShareSet = WwiseHashCombineFast(GetTypeHash(Op), 331319104) % NumShareSets; // Creating Node auto* Node = ResourceLoaderImpl.CreateShareSetNode(ShareSets[ShareSet], &FWwiseLanguageCookedData::Sfx); CHECK(Node); if (UNLIKELY(!Node)) { continue; } // Loading Node FWwiseLoadedShareSetPromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadShareSetAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[Op] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedShareSetPtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadShareSetAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } break; case 6: // SoundBank { const auto SoundBank = WwiseHashCombineFast(GetTypeHash(Op), 953952742) % NumSoundBanks; // Creating Node auto* Node = ResourceLoaderImpl.CreateSoundBankNode(SoundBanks[SoundBank], nullptr); CHECK(Node); if (UNLIKELY(!Node)) { continue; } // Loading Node FWwiseLoadedSoundBankPromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadSoundBankAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[Op] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedSoundBankPtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadSoundBankAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } break; case 7: // State { const auto State = WwiseHashCombineFast(GetTypeHash(Op), 8198259) % NumStates; // Creating Node auto* Node = ResourceLoaderImpl.CreateGroupValueNode(States[State]); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node FWwiseLoadedGroupValuePromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadGroupValueAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[Op] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedGroupValuePtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadGroupValueAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } break; case 8: // Switch { const auto Switch = WwiseHashCombineFast(GetTypeHash(Op), 24650269) % NumSwitches; // Creating Node auto* Node = ResourceLoaderImpl.CreateGroupValueNode(Switches[Switch]); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node FWwiseLoadedGroupValuePromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadGroupValueAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[Op] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedGroupValuePtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadGroupValueAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } break; } // Leave some leeway to execute some of the operations before starting the next batch if (Op % 100 == 0) { FPlatformProcess::Sleep(0.01f); } } for (auto& UnloadFuture : UnloadFutures) { UnloadFuture.Get(); } CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } SECTION("Sequential Leaf Load") { const bool bMockSleepOnMediaLoad = FWwiseResourceLoaderImpl::Test::bMockSleepOnMediaLoad; FWwiseResourceLoaderImpl::Test::bMockSleepOnMediaLoad = true; ON_SCOPE_EXIT { FWwiseResourceLoaderImpl::Test::bMockSleepOnMediaLoad = bMockSleepOnMediaLoad; }; FWwiseMockExternalSourceManager ExternalSourceManager; FWwiseMockMediaManager MediaManager; FWwiseMockSoundBankManager SoundBankManager; FWwiseResourceLoaderImpl ResourceLoaderImpl(ExternalSourceManager, MediaManager, SoundBankManager); static constexpr const auto MediaOverlap = 1.25f; static constexpr const uint32 NumGroupValues = 1000; static constexpr const auto NumLoops = 2; static constexpr const auto FuturesCount = (NumGroupValues + 2) * NumLoops; static constexpr const auto EventsCount = 2 * NumLoops; FWwiseLocalizedEventCookedData EventCookedDatas[2]; // Fill ordered EventCookedData (0) { FWwiseEventCookedData Data; Data.EventId = 1; FWwiseSwitchContainerLeafCookedData Leaf; for (int i = 0; i < NumGroupValues; ++i) { FWwiseMediaCookedData LeafMedia; LeafMedia.MediaId = 10000 + i / MediaOverlap; Leaf.Media.Emplace(MoveTemp(LeafMedia)); FWwiseGroupValueCookedData LeafGroupValue; LeafGroupValue.Id = 15000 + i; LeafGroupValue.GroupId = 1; LeafGroupValue.Type = EWwiseGroupType::Switch; Leaf.GroupValueSet.Emplace(MoveTemp(LeafGroupValue)); Data.SwitchContainerLeaves.Emplace(MoveTemp(Leaf)); } EventCookedDatas[0].EventLanguageMap.Emplace(FWwiseLanguageCookedData::Sfx, MoveTemp(Data)); } // Fill unordered EventCookedData (1) { FWwiseEventCookedData Data; Data.EventId = 1; FWwiseSwitchContainerLeafCookedData Leaf; for (int i = 0; i < NumGroupValues; ++i) { for (uint32 j = 0; j < 2; ++j) { FWwiseMediaCookedData LeafMedia; LeafMedia.MediaId = 10000 + (WwiseHashCombineFast(GetTypeHash(i), 142401 + j) % NumGroupValues) / MediaOverlap; Leaf.Media.Emplace(MoveTemp(LeafMedia)); } FWwiseGroupValueCookedData LeafGroupValue; LeafGroupValue.Id = 15000 + i; LeafGroupValue.GroupId = 1; LeafGroupValue.Type = EWwiseGroupType::Switch; Leaf.GroupValueSet.Emplace(MoveTemp(LeafGroupValue)); Data.SwitchContainerLeaves.Emplace(MoveTemp(Leaf)); } EventCookedDatas[1].EventLanguageMap.Emplace(FWwiseLanguageCookedData::Sfx, MoveTemp(Data)); } FWwiseGroupValueCookedData GroupValueCookedDatas[NumGroupValues]; for (auto i = 0; i < NumGroupValues; ++i) { GroupValueCookedDatas[i].Id = 15000 + i; GroupValueCookedDatas[i].GroupId = 1; GroupValueCookedDatas[i].Type = EWwiseGroupType::Switch; } FWwiseLoadedEventFuture EventFuturesArray[EventsCount]; FWwiseResourceUnloadFuture UnloadFuturesArray[FuturesCount]; for (auto Loop = 0; Loop < NumLoops; ++Loop) { auto* EventFutures( &EventFuturesArray[2 * Loop] ); auto* UnloadFutures( &UnloadFuturesArray[(NumGroupValues + 2) * Loop] ); for (auto i = 0; i < NumGroupValues; ++i) { if (i == 0) { // Creating Node auto* Node = ResourceLoaderImpl.CreateEventNode(EventCookedDatas[0], nullptr); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node FWwiseLoadedEventPromise LoadPromise; EventFutures[0] = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadEventAsync(MoveTemp(LoadPromise), MoveTemp(Node)); } else if (i == NumGroupValues / 4 * 3) { // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[NumGroupValues] = UnloadPromise.GetFuture(); EventFutures[0].Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedEventPtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadEventAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } if (i == NumGroupValues / 4 * 2) { // Creating Node auto* Node = ResourceLoaderImpl.CreateEventNode(EventCookedDatas[1], nullptr); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node FWwiseLoadedEventPromise LoadPromise; EventFutures[1] = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadEventAsync(MoveTemp(LoadPromise), MoveTemp(Node)); } else if (i == NumGroupValues - 1) { // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[NumGroupValues + 1] = UnloadPromise.GetFuture(); EventFutures[1].Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedEventPtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadEventAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } { // Creating Node auto* Node = ResourceLoaderImpl.CreateGroupValueNode(GroupValueCookedDatas[i]); CHECK(Node); if (UNLIKELY(!Node)) { return; } // Loading Node FWwiseLoadedGroupValuePromise LoadPromise; auto NodeFuture = LoadPromise.GetFuture(); ResourceLoaderImpl.LoadGroupValueAsync(MoveTemp(LoadPromise), MoveTemp(Node)); // Unloading Node FWwiseResourceUnloadPromise UnloadPromise; UnloadFutures[i] = UnloadPromise.GetFuture(); NodeFuture.Next([&ResourceLoaderImpl, UnloadPromise = MoveTemp(UnloadPromise)](FWwiseLoadedGroupValuePtr LoadedNode) mutable { CHECK(LoadedNode); if (UNLIKELY(!LoadedNode)) { UnloadPromise.EmplaceValue(); return; } ResourceLoaderImpl.UnloadGroupValueAsync(MoveTemp(UnloadPromise), MoveTemp(LoadedNode)); }); } // Leave some leeway to execute some of the operations before starting the next batch if (i % 100 == 0) { FPlatformProcess::Sleep(0.01f); } } // Leave some leeway to execute some of the operations before starting the next loop FPlatformProcess::Sleep(0.1f); } for (auto i = 0; i < FuturesCount; ++i) { UnloadFuturesArray[i].Get(); } CHECK(ExternalSourceManager.IsEmpty()); CHECK(MediaManager.IsEmpty()); CHECK(SoundBankManager.IsEmpty()); CHECK(ResourceLoaderImpl.IsEmpty()); CHECK(ResourceLoaderImpl.TrimGroupValueInfo()); } } #endif // WWISE_UNIT_TESTS