|
| 1 | +using System.Collections; |
1 | 2 | using System.Collections.Generic; |
2 | 3 | using System.IO; |
3 | 4 | using System.Linq; |
|
12 | 13 | using Content.Shared._Sunrise.SunriseCCVars; |
13 | 14 | using Content.Shared._Sunrise.NetTextures; |
14 | 15 | using Robust.Client.ResourceManagement; |
| 16 | +using Robust.Shared; |
15 | 17 | using Robust.Shared.GameObjects; |
16 | 18 | using Robust.Client.State; |
17 | 19 | using Robust.Shared.ContentPack; |
@@ -274,7 +276,19 @@ await client.WaitPost(() => |
274 | 276 | client.CfgMan.SetCVar(SunriseCCVars.LobbyBackgroundType, "Animation"); |
275 | 277 | }); |
276 | 278 |
|
277 | | - await pair.RunTicksSync(5); |
| 279 | + await WaitUntilClientCondition( |
| 280 | + client, |
| 281 | + () => |
| 282 | + { |
| 283 | + var lobbyState = stateManager.CurrentState as LobbyState; |
| 284 | + return lobbyState?.Lobby != null && |
| 285 | + !lobbyState.Lobby.LoadingAnimationContainer.Visible && |
| 286 | + lobbyState.Lobby.LobbyAnimation.Visible && |
| 287 | + lobbyState.Lobby.LobbyAnimation.DisplayRect.Texture != null && |
| 288 | + client.CfgMan.GetCVar(SunriseCCVars.LobbyAnimation) == invalidAnimationId; |
| 289 | + }, |
| 290 | + timeoutSeconds: 10, |
| 291 | + "network lobby animation transient fallback to finish loading"); |
278 | 292 |
|
279 | 293 | await client.WaitAssertion(() => |
280 | 294 | { |
@@ -797,12 +811,12 @@ private static async Task WaitUntilTextureReady( |
797 | 811 | string resourcePath, |
798 | 812 | int maxTicks) |
799 | 813 | { |
800 | | - await PoolManager.WaitUntil(client, async () => |
801 | | - { |
802 | | - var ready = false; |
803 | | - await client.WaitPost(() => ready = manager.TryGetTexture(resourcePath, out _)); |
804 | | - return ready; |
805 | | - }, maxTicks: maxTicks); |
| 814 | + await WaitUntilClientCondition( |
| 815 | + client, |
| 816 | + () => manager.TryGetTexture(resourcePath, out _), |
| 817 | + ConvertLegacyTickBudgetToSeconds(maxTicks), |
| 818 | + $"texture '{resourcePath}' to become ready", |
| 819 | + () => DescribeTextureLoadState(manager, resourcePath)); |
806 | 820 | } |
807 | 821 |
|
808 | 822 | private static MemoryStream CreateTransferStream(List<(ResPath Relative, byte[] Data)> files) |
@@ -850,12 +864,99 @@ private static async Task WaitUntilAnimationStateReady( |
850 | 864 | string stateId, |
851 | 865 | int maxTicks) |
852 | 866 | { |
853 | | - await PoolManager.WaitUntil(client, async () => |
| 867 | + await WaitUntilClientCondition( |
| 868 | + client, |
| 869 | + () => manager.TryGetAnimationState(resourcePath, stateId, out _), |
| 870 | + ConvertLegacyTickBudgetToSeconds(maxTicks), |
| 871 | + $"animation state '{stateId}' for '{resourcePath}' to become ready", |
| 872 | + () => DescribeAnimationLoadState(manager, resourcePath, stateId)); |
| 873 | + } |
| 874 | + |
| 875 | + private static async Task WaitUntilClientCondition( |
| 876 | + RobustIntegrationTest.ClientIntegrationInstance client, |
| 877 | + Func<bool> predicate, |
| 878 | + int timeoutSeconds, |
| 879 | + string description, |
| 880 | + Func<string> diagnostics = null, |
| 881 | + int tickStep = 5, |
| 882 | + int pollDelayMs = 5) |
| 883 | + { |
| 884 | + var maxTicks = GetTickBudgetForSeconds(client, timeoutSeconds); |
| 885 | + var ticksAwaited = 0; |
| 886 | + |
| 887 | + await client.WaitIdleAsync(); |
| 888 | + |
| 889 | + while (ticksAwaited < maxTicks) |
854 | 890 | { |
855 | 891 | var ready = false; |
856 | | - await client.WaitPost(() => ready = manager.TryGetAnimationState(resourcePath, stateId, out _)); |
857 | | - return ready; |
858 | | - }, maxTicks: maxTicks); |
| 892 | + await client.WaitPost(() => ready = predicate()); |
| 893 | + if (ready) |
| 894 | + return; |
| 895 | + |
| 896 | + var ticksToRun = Math.Min(tickStep, maxTicks - ticksAwaited); |
| 897 | + await client.WaitRunTicks(ticksToRun); |
| 898 | + ticksAwaited += ticksToRun; |
| 899 | + |
| 900 | + if (pollDelayMs > 0) |
| 901 | + await Task.Delay(pollDelayMs); |
| 902 | + } |
| 903 | + |
| 904 | + string debugState = null; |
| 905 | + if (diagnostics != null) |
| 906 | + await client.WaitPost(() => debugState = diagnostics()); |
| 907 | + |
| 908 | + Assert.Fail( |
| 909 | + $"Condition did not pass after {maxTicks} ticks while waiting for {description}." + |
| 910 | + (string.IsNullOrEmpty(debugState) ? string.Empty : $"\n{debugState}")); |
| 911 | + } |
| 912 | + |
| 913 | + private static int ConvertLegacyTickBudgetToSeconds(int maxTicks) |
| 914 | + { |
| 915 | + var defaultTickrate = CVars.NetTickrate.DefaultValue; |
| 916 | + return Math.Max(5, (int) Math.Ceiling((double) maxTicks / defaultTickrate)); |
| 917 | + } |
| 918 | + |
| 919 | + private static int GetTickBudgetForSeconds( |
| 920 | + RobustIntegrationTest.ClientIntegrationInstance client, |
| 921 | + int timeoutSeconds) |
| 922 | + { |
| 923 | + var tickrate = Math.Max(1, client.CfgMan.GetCVar(CVars.NetTickrate)); |
| 924 | + return Math.Max(1, timeoutSeconds * tickrate); |
| 925 | + } |
| 926 | + |
| 927 | + private static string DescribeTextureLoadState(ClientNetTexturesManager manager, string resourcePath) |
| 928 | + { |
| 929 | + var resourceKey = new ResPath(resourcePath).Clean().ToString(); |
| 930 | + var pendingResources = GetPrivateField<Dictionary<string, ResPath>>(manager, "_pendingResources"); |
| 931 | + var preparingResources = GetPrivateField<HashSet<string>>(manager, "_preparingResources"); |
| 932 | + var failedResources = GetPrivateField<HashSet<string>>(manager, "_failedResources"); |
| 933 | + var prepareRequests = GetPrivateField<ICollection>(manager, "_prepareRequests"); |
| 934 | + var preparedUploads = GetPrivateField<ICollection>(manager, "_preparedUploads"); |
| 935 | + var workerRunning = GetPrivateField<bool>(manager, "_prepareWorkerRunning"); |
| 936 | + |
| 937 | + return $"NetTextures state for {resourceKey}: pending={pendingResources.ContainsKey(resourceKey)}, " + |
| 938 | + $"preparing={preparingResources.Contains(resourceKey)}, failed={failedResources.Contains(resourceKey)}, " + |
| 939 | + $"workerRunning={workerRunning}, queuedRequests={prepareRequests.Count}, preparedUploads={preparedUploads.Count}."; |
| 940 | + } |
| 941 | + |
| 942 | + private static string DescribeAnimationLoadState( |
| 943 | + ClientNetTexturesManager manager, |
| 944 | + string resourcePath, |
| 945 | + string stateId) |
| 946 | + { |
| 947 | + var resourceKey = new ResPath(resourcePath).Clean().ToString(); |
| 948 | + var pendingResources = GetPrivateField<Dictionary<string, ResPath>>(manager, "_pendingResources"); |
| 949 | + var preparingResources = GetPrivateField<HashSet<string>>(manager, "_preparingResources"); |
| 950 | + var failedResources = GetPrivateField<HashSet<string>>(manager, "_failedResources"); |
| 951 | + var prepareRequests = GetPrivateField<ICollection>(manager, "_prepareRequests"); |
| 952 | + var preparedUploads = GetPrivateField<ICollection>(manager, "_preparedUploads"); |
| 953 | + var workerRunning = GetPrivateField<bool>(manager, "_prepareWorkerRunning"); |
| 954 | + var hasState = manager.TryGetAnimationState(resourcePath, stateId, out _); |
| 955 | + |
| 956 | + return $"NetTextures state for {resourceKey}#{stateId}: pending={pendingResources.ContainsKey(resourceKey)}, " + |
| 957 | + $"preparing={preparingResources.Contains(resourceKey)}, failed={failedResources.Contains(resourceKey)}, " + |
| 958 | + $"workerRunning={workerRunning}, queuedRequests={prepareRequests.Count}, preparedUploads={preparedUploads.Count}, " + |
| 959 | + $"stateReady={hasState}."; |
859 | 960 | } |
860 | 961 |
|
861 | 962 | private static async Task DispatchFallbackChunk( |
|
0 commit comments