// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Database; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay; namespace osu.Game.Tests.Visual.OnlinePlay { /// /// A base test scene for all online play components and screens. /// public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestSceneDependencies { public Bindable SelectedRoom => OnlinePlayDependencies?.SelectedRoom; public IRoomManager RoomManager => OnlinePlayDependencies?.RoomManager; public OngoingOperationTracker OngoingOperationTracker => OnlinePlayDependencies?.OngoingOperationTracker; public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => OnlinePlayDependencies?.AvailabilityTracker; public TestUserLookupCache UserLookupCache => OnlinePlayDependencies?.UserLookupCache; public BeatmapLookupCache BeatmapLookupCache => OnlinePlayDependencies?.BeatmapLookupCache; /// /// All dependencies required for online play components and screens. /// protected OnlinePlayTestSceneDependencies OnlinePlayDependencies => dependencies?.OnlinePlayDependencies; protected override Container Content => content; private readonly Container content; private readonly Container drawableDependenciesContainer; private DelegatedDependencyContainer dependencies; protected OnlinePlayTestScene() { base.Content.AddRange(new Drawable[] { drawableDependenciesContainer = new Container { RelativeSizeAxes = Axes.Both }, content = new Container { RelativeSizeAxes = Axes.Both }, }); } protected sealed override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { dependencies = new DelegatedDependencyContainer(base.CreateChildDependencies(parent)); return dependencies; } [SetUp] public void Setup() => Schedule(() => { // Reset the room dependencies to a fresh state. drawableDependenciesContainer.Clear(); dependencies.OnlinePlayDependencies = CreateOnlinePlayDependencies(); drawableDependenciesContainer.AddRange(OnlinePlayDependencies.DrawableComponents); var handler = OnlinePlayDependencies.RequestsHandler; // Resolving the BeatmapManager in the test scene will inject the game-wide BeatmapManager, while many test scenes cache their own BeatmapManager instead. // To get around this, the BeatmapManager is looked up from the dependencies provided to the children of the test scene instead. var beatmapManager = dependencies.Get(); ((DummyAPIAccess)API).HandleRequest = request => { TaskCompletionSource tcs = new TaskCompletionSource(); // Because some of the handlers use realm, we need to ensure the game is still alive when firing. // If we don't, a stray `PerformAsync` could hit an `ObjectDisposedException` if running too late. Scheduler.Add(() => { bool result = handler.HandleRequest(request, API.LocalUser.Value, beatmapManager); tcs.SetResult(result); }, false); #pragma warning disable RS0030 // We can't GetResultSafely() here (will fail with "Can't use GetResultSafely from inside an async operation."), but Wait is safe enough due to // the task being a TaskCompletionSource. return tcs.Task.Result; #pragma warning restore RS0030 }; }); /// /// Creates the room dependencies. Called every . /// /// /// Any custom dependencies required for online play sub-classes should be added here. /// protected virtual OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new OnlinePlayTestSceneDependencies(); /// /// A providing a mutable lookup source for online play dependencies. /// private class DelegatedDependencyContainer : IReadOnlyDependencyContainer { /// /// The online play dependencies. /// public OnlinePlayTestSceneDependencies OnlinePlayDependencies { get; set; } private readonly IReadOnlyDependencyContainer parent; private readonly DependencyContainer injectableDependencies; /// /// Creates a new . /// /// The fallback to use when cannot satisfy a dependency. public DelegatedDependencyContainer(IReadOnlyDependencyContainer parent) { this.parent = parent; injectableDependencies = new DependencyContainer(this); } public object Get(Type type) => OnlinePlayDependencies?.Get(type) ?? parent.Get(type); public object Get(Type type, CacheInfo info) => OnlinePlayDependencies?.Get(type, info) ?? parent.Get(type, info); public void Inject(T instance) where T : class => injectableDependencies.Inject(instance); } } }