From 49969c025051e815bd96e751aefa040fec5d3374 Mon Sep 17 00:00:00 2001 From: vontell Date: Tue, 17 Dec 2024 23:20:24 -0500 Subject: [PATCH 1/3] Ability to save JSON results and view in the logs --- .../BotSegmentsPlaybackContainer.cs | 22 +++++ .../BotSegmentsPlaybackController.cs | 5 ++ .../SegmentValidationResultContainer.cs | 3 + .../Scripts/StateRecorder/ScreenRecorder.cs | 16 ++++ .../Validation/RGValidateLoggerUtility.cs | 86 +++++++++++++++++++ .../RGValidateLoggerUtility.cs.meta | 3 + 6 files changed, 135 insertions(+) create mode 100644 src/gg.regression.unity.bots/Runtime/Scripts/Validation/RGValidateLoggerUtility.cs create mode 100644 src/gg.regression.unity.bots/Runtime/Scripts/Validation/RGValidateLoggerUtility.cs.meta diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackContainer.cs b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackContainer.cs index 8a3201e4..4695aef2 100644 --- a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackContainer.cs +++ b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackContainer.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Linq; using RegressionGames.StateRecorder.BotSegments.Models; using StateRecorder.BotSegments.Models; +using StateRecorder.BotSegments.Models.SegmentValidations; namespace RegressionGames.StateRecorder.BotSegments { @@ -65,5 +67,25 @@ public BotSegment PeekBotSegment() return null; } + + + /** + * Collects all of the results from the top-level validations and individual bot segments + */ + public List GetAllValidationResults() + { + + // First add all the top level results + var results = Validations.Select(validation => validation.data.GetResults()).ToList(); + + // Then add the individual bot segment results + foreach (var botSegment in _botSegments) + { + results.AddRange(botSegment.validations.Select(v => v.data.GetResults())); + } + + return results; + } + } } diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackController.cs b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackController.cs index 631225bb..bb909e7d 100644 --- a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackController.cs +++ b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackController.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using Newtonsoft.Json; using RegressionGames.StateRecorder.BotSegments.Models; using RegressionGames.StateRecorder.BotSegments.Models.BotActions.KeyMoments; using RegressionGames.StateRecorder.Models; using StateRecorder.BotSegments; using StateRecorder.BotSegments.Models; +using StateRecorder.BotSegments.Models.SegmentValidations; #if UNITY_EDITOR using UnityEditor; #endif @@ -664,6 +666,7 @@ public void UnloadSegmentsAndReset() _replaySuccessful = null; WaitingForKeyFrameConditions = null; + _screenRecorder.validationResults = _dataPlaybackContainer?.GetAllValidationResults() ?? new List(); _screenRecorder.StopRecording(); #if ENABLE_LEGACY_INPUT_MANAGER RGLegacyInputWrapper.StopSimulation(); @@ -704,6 +707,7 @@ public void Stop() WaitingForKeyFrameConditions = null; _lastSegmentPlaybackWarning = null; + _screenRecorder.validationResults = _dataPlaybackContainer?.GetAllValidationResults() ?? new List(); _screenRecorder.StopRecording(); #if ENABLE_LEGACY_INPUT_MANAGER RGLegacyInputWrapper.StopSimulation(); @@ -825,6 +829,7 @@ private void LogPlaybackWarning(string loggedMessage, Exception ex = null) RGDebug.LogWarning(loggedMessage); } } + FindObjectOfType()?.SetKeyFrameWarningText(loggedMessage); if (pauseEditorOnPlaybackWarning) { diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/SegmentValidations/SegmentValidationResultContainer.cs b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/SegmentValidations/SegmentValidationResultContainer.cs index bf91df2a..ffd4b2c0 100644 --- a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/SegmentValidations/SegmentValidationResultContainer.cs +++ b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/SegmentValidations/SegmentValidationResultContainer.cs @@ -1,5 +1,7 @@ using System; using JetBrains.Annotations; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace StateRecorder.BotSegments.Models.SegmentValidations { @@ -28,6 +30,7 @@ public class SegmentValidationResultContainer * The actual state of this validation result * */ + [JsonConverter(typeof(StringEnumConverter))] public SegmentValidationStatus result; public SegmentValidationResultContainer(string name, [CanBeNull] string description, SegmentValidationStatus result) diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/ScreenRecorder.cs b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/ScreenRecorder.cs index 24e6e843..30038c63 100644 --- a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/ScreenRecorder.cs +++ b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/ScreenRecorder.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; +using Newtonsoft.Json; using RegressionGames.ActionManager; using RegressionGames.CodeCoverage; using RegressionGames.RemoteOrchestration; @@ -16,7 +17,9 @@ using RegressionGames.StateRecorder.BotSegments.Models.BotActions; using RegressionGames.StateRecorder.BotSegments.Models.BotCriteria; using RegressionGames.StateRecorder.Models; +using RegressionGames.Validation; using StateRecorder.BotSegments; +using StateRecorder.BotSegments.Models.SegmentValidations; #if UNITY_EDITOR using UnityEditor; #endif @@ -85,6 +88,7 @@ public class ScreenRecorder : MonoBehaviour private string _currentGameplaySessionGameMetadataPath; private string _currentGameplaySessionThumbnailPath; private string _currentGameplaySessionLogsDirectoryPrefix; + private string _currentGameplaySessionValidationsPrefix; private CancellationTokenSource _tokenSource; @@ -112,6 +116,8 @@ public class ScreenRecorder : MonoBehaviour private KeyMomentEvaluator _keyMomentEvaluator = new(); + public List validationResults = new(); + #if UNITY_EDITOR private bool _needToRefreshAssets; #endif @@ -182,6 +188,7 @@ private async Task HandleEndRecording(long tickCount, string thumbnailPath, string logsDirectoryPrefix, string gameMetadataPath, + string validationsPath, bool onDestroy = false) { if (!onDestroy) @@ -265,6 +272,9 @@ private async Task HandleEndRecording(long tickCount, ZipFile.CreateFromDirectory(keyMomentsDirectoryPrefix, keyMomentsDirectoryPrefix + ".zip"); RGDebug.LogInfo($"Finished zipping replay to file: {keyMomentsDirectoryPrefix}.zip"); }); + + // Save the validation results to the validations JSON file, if there are any + await File.WriteAllBytesAsync(_currentGameplaySessionValidationsPrefix, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(validationResults ?? new List()))); // Finally, we also save a thumbnail, by choosing the middle file in the screenshots var screenshotFiles = Directory.GetFiles(screenshotsDirectoryPrefix); @@ -279,6 +289,9 @@ private async Task HandleEndRecording(long tickCount, // wait for the zip tasks to finish Task.WaitAll(zipTask1, zipTask2, zipTask3, zipTask4, zipTask5); + + // print validation results + RGValidateLoggerUtility.LogValidationResults(validationResults); if (!wasReplay) { @@ -575,6 +588,7 @@ private IEnumerator StartRecordingCoroutine(string referenceSessionId) _startTime = DateTime.Now; _tickQueue = new BlockingCollection<(TickDataToWriteToDisk, Action)>(new ConcurrentQueue<(TickDataToWriteToDisk, Action)>()); _tokenSource = new CancellationTokenSource(); + validationResults = new(); Directory.CreateDirectory(stateRecordingsDirectory); @@ -616,6 +630,7 @@ private IEnumerator StartRecordingCoroutine(string referenceSessionId) Directory.CreateDirectory(_currentGameplaySessionMetadataDirectoryPrefix); _currentGameplaySessionThumbnailPath = _currentGameplaySessionDirectoryPrefix + "/thumbnail.jpg"; + _currentGameplaySessionValidationsPrefix = _currentGameplaySessionDirectoryPrefix + "/validations.json"; // run the tick processor in the background, but don't hook it to the token source.. we'll manage cancelling this on our own so we don't miss processing ticks Task.Run(ProcessTicks); @@ -739,6 +754,7 @@ private void StopRecordingCleanupHelper(bool wasRecording, bool wasReplay) _currentGameplaySessionThumbnailPath, _currentGameplaySessionLogsDirectoryPrefix, _currentGameplaySessionGameMetadataPath, + _currentGameplaySessionValidationsPrefix, true); } else diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/Validation/RGValidateLoggerUtility.cs b/src/gg.regression.unity.bots/Runtime/Scripts/Validation/RGValidateLoggerUtility.cs new file mode 100644 index 00000000..79032fbb --- /dev/null +++ b/src/gg.regression.unity.bots/Runtime/Scripts/Validation/RGValidateLoggerUtility.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Text; +using JetBrains.Annotations; +using StateRecorder.BotSegments.Models.SegmentValidations; + +namespace RegressionGames.Validation +{ + + /** + * A set of utilities for logging results related to validations + */ + public class RGValidateLoggerUtility + { + + /** + * Prints out validation results in a clean way in the logs + */ + public static void LogValidationResults([CanBeNull] List results) + { + + if (results == null) + { + // For now, if there are no validations, we just print a small message + RGDebug.LogInfo("No validations were provided as part of this run"); + return; + } + + // Print out results while also collecting total results + var passed = 0; + var failed = 0; + var unknown = 0; + + var logBuilder = new StringBuilder(100_000); + + logBuilder.Append("--------------- VALIDATION RESULTS --------------- (If in the editor, click this to view more)\n\n"); + foreach (var resultSet in results) + { + logBuilder.Append("" + resultSet.name + "\n"); + foreach (var validation in resultSet.validationResults) + { + switch (validation.result) + { + case SegmentValidationStatus.PASSED: + logBuilder.Append(" [PASS] "); + passed++; + break; + case SegmentValidationStatus.FAILED: + logBuilder.Append(" [FAIL] "); + failed++; + break; + case SegmentValidationStatus.UNKNOWN: + logBuilder.Append(" [UNKNOWN] "); + unknown++; + break; + } + logBuilder.Append(validation.name + "\n"); + } + + logBuilder.Append("\n"); + } + + if (failed > 0) + { + logBuilder.Append("VALIDATIONS FAILED - "); + } + else + { + logBuilder.Append("VALIDATIONS PASSED - "); + } + + logBuilder.Append($"{failed} FAILED, {passed} PASSED ({unknown} UNKNOWN)\n\n"); + + // Finally log the results + if (failed > 0) + { + RGDebug.LogError(logBuilder.ToString()); + } + else + { + RGDebug.LogInfo(logBuilder.ToString()); + } + + } + + } +} \ No newline at end of file diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/Validation/RGValidateLoggerUtility.cs.meta b/src/gg.regression.unity.bots/Runtime/Scripts/Validation/RGValidateLoggerUtility.cs.meta new file mode 100644 index 00000000..31a25e82 --- /dev/null +++ b/src/gg.regression.unity.bots/Runtime/Scripts/Validation/RGValidateLoggerUtility.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2c2c3e9500ae4570b6f70b0bbf1935c0 +timeCreated: 1734494119 \ No newline at end of file From 9c649e2a2900a99942c7582dd1642a74122ba37a Mon Sep 17 00:00:00 2001 From: RG-nAmKcAz Date: Wed, 18 Dec 2024 08:58:21 -0500 Subject: [PATCH 2/3] [REG-2230] Operate on BotSegmentLists so we can do validations there --- .../Scripts/RGOverlay/RGSegmentEntry.cs | 11 +-- .../BotSegments/BotSegmentZipParser.cs | 29 +++--- .../BotSegmentsPlaybackContainer.cs | 58 +++++++----- .../BotSegmentsPlaybackController.cs | 89 +++++++++++-------- .../BotSegments/Models/BotSequence.cs | 4 +- .../Models/IRGSegmentValidationData.cs | 12 +-- .../BotSegments/Models/SegmentValidation.cs | 2 +- .../StateRecorder/ReplayToolbarManager.cs | 3 +- .../TestFramework/Scripts/RGTestUtils.cs | 14 ++- 9 files changed, 128 insertions(+), 94 deletions(-) diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/RGOverlay/RGSegmentEntry.cs b/src/gg.regression.unity.bots/Runtime/Scripts/RGOverlay/RGSegmentEntry.cs index 0e4b98b1..cdf60adf 100644 --- a/src/gg.regression.unity.bots/Runtime/Scripts/RGOverlay/RGSegmentEntry.cs +++ b/src/gg.regression.unity.bots/Runtime/Scripts/RGOverlay/RGSegmentEntry.cs @@ -31,7 +31,7 @@ public class RGSegmentEntry : MonoBehaviour * Indicates whether the Segment or Segment List is overridden by a local file. */ public bool isOverride; - + /** * UI component fields */ @@ -49,7 +49,7 @@ public class RGSegmentEntry : MonoBehaviour [SerializeField] public GameObject segmentListIndicatorComponent; - + [SerializeField] public GameObject overrideIndicator; @@ -84,8 +84,8 @@ public void Start() resourcePathComponent.gameObject.SetActive(false); } } - - // set indicator that this Segment is being overriden by a local file, within a build + + // set indicator that this Segment is being overriden by a local file, within a build overrideIndicator.gameObject.SetActive(isOverride); // assign values to the UI components @@ -141,7 +141,8 @@ private void OnPlay() } // play the segment - playbackController.SetDataContainer(new BotSegmentsPlaybackContainer(segmentList.segments, new List(), sessionId)); + playbackController.SetDataContainer(new BotSegmentsPlaybackContainer(new List() {segmentList}, new List(), sessionId)); + playbackController.Play(); } } diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentZipParser.cs b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentZipParser.cs index b8551942..827ca1fa 100644 --- a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentZipParser.cs +++ b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentZipParser.cs @@ -4,9 +4,7 @@ using System.IO.Compression; using System.Linq; using Newtonsoft.Json; -using RegressionGames.StateRecorder.BotSegments.JsonConverters; using RegressionGames.StateRecorder.BotSegments.Models; -using StateRecorder.BotSegments; namespace RegressionGames.StateRecorder.BotSegments { @@ -66,9 +64,9 @@ public static IOrderedEnumerable OrderZipJsonEntries(IEnumerabl return entries; } - public static List ParseBotSegmentZipFromSystemPath(string zipFilePath, out string sessionId) + public static List ParseBotSegmentZipFromSystemPath(string zipFilePath, out string sessionId) { - List results = new(); + List results = new(); sessionId = null; @@ -100,17 +98,7 @@ public static List ParseBotSegmentZipFromSystemPath(string zipFilePa break; } - foreach (var botSegment in botSegmentList.segments) - { - botSegment.Replay_SegmentNumber = replayNumber++; - - if (sessionId == null) - { - sessionId = botSegment.sessionId; - } - - results.Add(botSegment); - } + results.Add(botSegmentList); } catch (Exception) { @@ -132,7 +120,16 @@ public static List ParseBotSegmentZipFromSystemPath(string zipFilePa sessionId = frameData.sessionId; } - results.Add(frameData); + results.Add(new BotSegmentList() + { + segments = new List() + { + frameData + }, + name = "BotSegmentList for BotSegment - " + frameData.name, + description = "BotSegmentList for BotSegment - " + frameData.description, + validations = new() + }); } catch (Exception ex) { diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackContainer.cs b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackContainer.cs index 8a3201e4..7dadb2e0 100644 --- a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackContainer.cs +++ b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackContainer.cs @@ -8,9 +8,11 @@ namespace RegressionGames.StateRecorder.BotSegments public class BotSegmentsPlaybackContainer { - private readonly List _botSegments; + + private readonly List _botSegmentLists; + private int _botSegmentListIndex = 0; private int _botSegmentIndex = 0; - + /** * A top-level set of validations to run for an entire sequence of segments */ @@ -18,51 +20,59 @@ public class BotSegmentsPlaybackContainer public readonly string SessionId; - public BotSegmentsPlaybackContainer(IEnumerable segments, IEnumerable validations, string sessionId = null) + public BotSegmentsPlaybackContainer(IEnumerable segmentLists, IEnumerable validations, string sessionId = null) { var replayNumber = 1; // 1 to align with the actual numbers in the recording - _botSegments = new(segments); + _botSegmentLists = new(segmentLists); + _botSegmentLists.ForEach(a => a.segments.ForEach(b => b.Replay_SegmentNumber = replayNumber++)); Validations = new(validations); - _botSegments.ForEach(a => a.Replay_SegmentNumber = replayNumber++); this.SessionId = sessionId ?? Guid.NewGuid().ToString("n"); } public void Reset() { // sets indexes back to 0 - _botSegmentIndex = 0; + _botSegmentListIndex = 0; - // reset all the tracking flags - foreach (var botSegment in _botSegments) + // reset all the tracking flags in the segmentlists / segments + _botSegmentLists.ForEach(a => { - botSegment.ReplayReset(); - } - + a.segments.ForEach(b => b.ReplayReset()); + a.validations.ForEach(b => b.ReplayReset()); + }); + // reset all the top-level validations foreach (var validation in Validations) { validation.ReplayReset(); } - } - public BotSegment DequeueBotSegment() - { - if (_botSegmentIndex < _botSegments.Count) - { - return _botSegments[_botSegmentIndex++]; - } - - return null; } - public BotSegment PeekBotSegment() + /** + * Returns the next bot segment to evaluate and also provides the current segmentList level validations + */ + public BotSegment DequeueBotSegment(out List segmentListValidations) { - if (_botSegmentIndex < _botSegments.Count) + while (_botSegmentListIndex < _botSegmentLists.Count) { - // do not update index - return _botSegments[_botSegmentIndex]; + var segmentList = _botSegmentLists[_botSegmentListIndex]; + if (_botSegmentIndex < segmentList.segments.Count) + { + var segment = segmentList.segments[_botSegmentIndex++]; + segmentListValidations = segmentList.validations; + return segment; + } + else + { + // move to the next segmentlist starting on the 0th segment in that list + _botSegmentIndex = 0; + ++_botSegmentListIndex; + } + } + segmentListValidations = new List(); return null; } } diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackController.cs b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackController.cs index 631225bb..cb65809a 100644 --- a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackController.cs +++ b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackController.cs @@ -51,6 +51,8 @@ public class BotSegmentsPlaybackController : MonoBehaviour // This is done this way to allow situations like when loading screens (UI) are changing while game objects are loading in the background and the process is not consistent/deterministic between the 2 private readonly List _nextBotSegments = new(); + private List _botSegmentListValidations = new(); + // helps indicate if we made it through the full replay successfully private bool? _replaySuccessful; @@ -108,6 +110,21 @@ private void ProcessBotSegments() ProcessBotSegmentAction(nextBotSegment, transformStatuses, entityStatuses); } + // Run (and potentially end) the validations for this segment + nextBotSegment.ProcessValidation(); + + // Also run the validations for the botsegmentList if they exist + foreach (var validation in _botSegmentListValidations) + { + validation.ProcessValidation(nextBotSegment.Replay_SegmentNumber); + } + + // Also run the validations for the entire list of segments if they exist + foreach (var validation in _dataPlaybackContainer.Validations) + { + validation.ProcessValidation(nextBotSegment.Replay_SegmentNumber); + } + var matched = nextBotSegment.Replay_Matched || nextBotSegment.endCriteria == null || nextBotSegment.endCriteria.Count == 0 || KeyFrameEvaluator.Evaluator.Matched( nextBotSegmentIndex == 0, nextBotSegment.Replay_SegmentNumber, @@ -192,7 +209,7 @@ private void ProcessBotSegments() { if (_nextBotSegments.Count < 2) { - var next = _dataPlaybackContainer.DequeueBotSegment(); + var next = _dataPlaybackContainer.DequeueBotSegment(out _botSegmentListValidations); if (next != null) { _lastTimeLoggedKeyFrameConditions = now; @@ -207,7 +224,7 @@ private void ProcessBotSegments() else { // segment list empty.. dequeue another - var next = _dataPlaybackContainer.DequeueBotSegment(); + var next = _dataPlaybackContainer.DequeueBotSegment(out _botSegmentListValidations); if (next != null) { _lastTimeLoggedKeyFrameConditions = now; @@ -282,21 +299,21 @@ private void ProcessBotSegments() * Thus an exploration action can be invoked in between any 2 passes of the main action when an error occurs. With that concept of update to update interleaving, it should be obvious why actions implementing IKeyMomentExploration must be * written to expect to be interrupted at any point. */ - private void ProcessBotSegmentAction(BotSegment firstActionSegment, Dictionary transformStatuses, Dictionary entityStatuses) + private void ProcessBotSegmentAction(BotSegment actionSegment, Dictionary transformStatuses, Dictionary entityStatuses) { var now = Time.unscaledTime; - string logPrefix = $"({firstActionSegment.Replay_SegmentNumber}) - Bot Segment - "; + string logPrefix = $"({actionSegment.Replay_SegmentNumber}) - Bot Segment - "; try { - if (firstActionSegment.botAction?.IsCompleted == false) + if (actionSegment.botAction?.IsCompleted == false) { // allow the main action to retry between every exploratory action - var didAction = firstActionSegment.ProcessAction(transformStatuses, entityStatuses, out var error); + var didAction = actionSegment.ProcessAction(transformStatuses, entityStatuses, out var error); if (error == null) { // we're going to 'pause' exploring, but not reset the exploration state quite yet until this action fully finishes - _explorationDriver.PauseExploring(firstActionSegment.Replay_SegmentNumber); + _explorationDriver.PauseExploring(actionSegment.Replay_SegmentNumber); if (didAction && _explorationDriver.ExplorationState == ExplorationState.STOPPED) { // for every non error action, reset the timer @@ -312,7 +329,7 @@ private void ProcessBotSegmentAction(BotSegment firstActionSegment, Dictionary()?.SetKeyFrameWarningText(null); - _explorationDriver.StopExploring(firstActionSegment.Replay_SegmentNumber); - _explorationDriver.ReportPreviouslyCompletedAction(firstActionSegment.botAction.data); - } - - // Run (and potentially end) the validations for this segment - firstActionSegment.ProcessValidation(); - - // Also run the validations for the entire list of segments if they exist - foreach (var validation in _dataPlaybackContainer.Validations) - { - validation.ProcessValidation(-1); + _explorationDriver.StopExploring(actionSegment.Replay_SegmentNumber); + _explorationDriver.ReportPreviouslyCompletedAction(actionSegment.botAction.data); } - + } catch (Exception ex) { @@ -393,13 +401,14 @@ public void LateUpdate() { if (_dataPlaybackContainer != null) { - + var currentSegmentNumber = _nextBotSegments.Count > 0 ? _nextBotSegments[0].Replay_SegmentNumber : -1; + // If there are top-level validations running, let's make sure those are prepared before we // process the segments. var validationsReady = true; foreach (var validation in _dataPlaybackContainer.Validations) { - if (!validation.ProcessValidation(-1)) + if (!validation.ProcessValidation(currentSegmentNumber)) { validationsReady = false; break; @@ -575,16 +584,18 @@ public void Pause() { _playState = PlayState.Paused; + var currentSegmentNumber = _nextBotSegments.Count > 0 ? _nextBotSegments[0].Replay_SegmentNumber : -1; + foreach (var nextBotSegment in _nextBotSegments) { nextBotSegment.PauseAction(); nextBotSegment.PauseValidations(); } - + // Also pause the top-level validations foreach (var validation in _dataPlaybackContainer.Validations) { - validation.PauseValidation(-1); + validation.PauseValidation(currentSegmentNumber); } } } @@ -606,16 +617,18 @@ public void Play() // resume _playState = PlayState.Playing; + var currentSegmentNumber = _nextBotSegments.Count > 0 ? _nextBotSegments[0].Replay_SegmentNumber : -1; + foreach (var nextBotSegment in _nextBotSegments) { nextBotSegment.UnPauseAction(); nextBotSegment.UnPauseValidations(); } - + // Also unpause the top-level validations foreach (var validation in _dataPlaybackContainer.Validations) { - validation.UnPauseValidation(-1); + validation.UnPauseValidation(currentSegmentNumber); } } } @@ -647,13 +660,15 @@ public void UnloadSegmentsAndReset() nextBotSegment.StopValidations(); } } - + + var currentSegmentNumber = _nextBotSegments.Count > 0 ? _nextBotSegments[0].Replay_SegmentNumber : -1; + // Also wrap up the top-level validations if they exist if (_dataPlaybackContainer != null) { foreach (var validation in _dataPlaybackContainer.Validations) { - validation.StopValidation(-1); + validation.StopValidation(currentSegmentNumber); } } @@ -687,16 +702,18 @@ public void UnloadSegmentsAndReset() public void Stop() { - + + var currentSegmentNumber = _nextBotSegments.Count > 0 ? _nextBotSegments[0].Replay_SegmentNumber : -1; + // Make sure to stop any running validations if (_dataPlaybackContainer != null) { foreach (var validation in _dataPlaybackContainer.Validations) { - validation.StopValidation(-1); + validation.StopValidation(currentSegmentNumber); } } - + _nextBotSegments.Clear(); _playState = PlayState.Stopped; _loopCount = -1; @@ -784,7 +801,7 @@ public void Update() #endif RGUtils.ConfigureInputSettings(); _playState = PlayState.Playing; - _nextBotSegments.Add(_dataPlaybackContainer.DequeueBotSegment()); + _nextBotSegments.Add(_dataPlaybackContainer.DequeueBotSegment(out _botSegmentListValidations)); // if starting to play, or on loop 1.. start recording if (_loopCount < 2) { diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/BotSequence.cs b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/BotSequence.cs index 11f1b23f..824f7bda 100644 --- a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/BotSequence.cs +++ b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/BotSequence.cs @@ -30,7 +30,7 @@ public class BotSequence * A set of top-level validations to run on a sequence of segments */ public List validations = new(); - + /** * Define the name of this sequence that will be seen in user interfaces and runtime summaries. This SHOULD NOT be null. */ @@ -497,7 +497,7 @@ public void Play() } sessionId ??= Guid.NewGuid().ToString(); - playbackController.SetDataContainer(new BotSegmentsPlaybackContainer(_segmentsToProcess.SelectMany(a => a.segments), validations, sessionId)); + playbackController.SetDataContainer(new BotSegmentsPlaybackContainer(_segmentsToProcess, validations, sessionId)); ActiveBotSequence = this; // SetDataContainer clears this, so set it here before starting playbackController.Play(); } diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/IRGSegmentValidationData.cs b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/IRGSegmentValidationData.cs index 708bfeea..2a6a832c 100644 --- a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/IRGSegmentValidationData.cs +++ b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/IRGSegmentValidationData.cs @@ -5,7 +5,7 @@ namespace StateRecorder.BotSegments.Models { public interface IRGSegmentValidationData { - + /** * Attempts to conduct any preparation needed for validation. Returns true if the validations * are ready to be run, and false otherwise. Implementors should make sure that this method @@ -51,10 +51,10 @@ public interface IRGSegmentValidationData * result. */ public SegmentValidationResultSetContainer GetResults(); - + public void WriteToStringBuilder(StringBuilder stringBuilder); - - public int EffectiveApiVersion(); - + + public int EffectiveApiVersion(); + } -} \ No newline at end of file +} diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/SegmentValidation.cs b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/SegmentValidation.cs index e1983757..b4b9fbf1 100644 --- a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/SegmentValidation.cs +++ b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/SegmentValidation.cs @@ -89,4 +89,4 @@ public override string ToString() } } -} \ No newline at end of file +} diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/ReplayToolbarManager.cs b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/ReplayToolbarManager.cs index e9a666da..e33b1a54 100644 --- a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/ReplayToolbarManager.cs +++ b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/ReplayToolbarManager.cs @@ -237,8 +237,9 @@ private void ProcessDataContainerZipAndSetup(String filePath) { try { + var botSegmentLists = BotSegmentZipParser.ParseBotSegmentZipFromSystemPath(filePath, out var sessionId); // do this on background thread - var dataContainer = new BotSegmentsPlaybackContainer(BotSegmentZipParser.ParseBotSegmentZipFromSystemPath(filePath, out var sessionId), new List(), sessionId); + var dataContainer = new BotSegmentsPlaybackContainer(botSegmentLists, new List(), sessionId); _playbackContainer = dataContainer; } catch (Exception e) diff --git a/src/gg.regression.unity.bots/Tests/TestFramework/Scripts/RGTestUtils.cs b/src/gg.regression.unity.bots/Tests/TestFramework/Scripts/RGTestUtils.cs index 072bbd08..ff603643 100644 --- a/src/gg.regression.unity.bots/Tests/TestFramework/Scripts/RGTestUtils.cs +++ b/src/gg.regression.unity.bots/Tests/TestFramework/Scripts/RGTestUtils.cs @@ -130,7 +130,11 @@ public static IEnumerator StartPlaybackFromDirectory(string recordingPath, Actio { RGDebug.LogInfo("Loading and starting playback recording from " + recordingPath); var playbackController = Object.FindObjectOfType(); - var botSegments = BotSegmentDirectoryParser.ParseBotSegmentSystemDirectory(recordingPath, out var sessionId); + var botSegments = BotSegmentDirectoryParser.ParseBotSegmentSystemDirectory(recordingPath, out var sessionId).Select(a=>new BotSegmentList("BotSegmentList for BotSegment - " + a.name, new List() {a}) + { + description = "BotSegmentList for BotSegment - " + a.description, + validations = new() + }); var replayData = new BotSegmentsPlaybackContainer(botSegments, new List(), sessionId); playbackController.SetDataContainer(replayData); playbackController.Play(); @@ -317,7 +321,11 @@ public static IEnumerator StartBotSegment(BotSegment botSegment, Action(); - playbackController.SetDataContainer(new BotSegmentsPlaybackContainer(new[] { botSegment }, validations ?? new List())); + playbackController.SetDataContainer(new BotSegmentsPlaybackContainer(new List() {new BotSegmentList("BotSegmentList for BotSegment - " + botSegment.name, new List() {botSegment}) + { + description = "BotSegmentList for BotSegment - " + botSegment.description, + validations = new() + }}, validations ?? new List())); playbackController.Play(); @@ -363,7 +371,7 @@ public static IEnumerator StartBotSegmentList(BotSegmentList botSegmentList, Act var playbackController = Object.FindObjectOfType(); - playbackController.SetDataContainer(new BotSegmentsPlaybackContainer(botSegmentList.segments, validations ?? new List())); + playbackController.SetDataContainer(new BotSegmentsPlaybackContainer(new List() {botSegmentList}, validations ?? new List())); playbackController.Play(); From 6cb6b894bf835cb90c93eb5bb90daf7aa23b0aa7 Mon Sep 17 00:00:00 2001 From: vontell Date: Thu, 19 Dec 2024 15:43:38 -0500 Subject: [PATCH 3/3] Fixes rg validations to work properly with segment lists --- .../BotSegmentsPlaybackContainer.cs | 51 ++++-- .../BotSegmentsPlaybackController.cs | 160 ++++++++++-------- .../BotSegments/Models/SegmentValidation.cs | 13 +- 3 files changed, 139 insertions(+), 85 deletions(-) diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackContainer.cs b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackContainer.cs index 6a476fb8..cd852995 100644 --- a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackContainer.cs +++ b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackContainer.cs @@ -39,11 +39,11 @@ public void Reset() // reset all the tracking flags in the segmentlists / segments _botSegmentLists.ForEach(a => { - a.segments.ForEach(b => b.ReplayReset()); + a.segments.ForEach(b => b.ReplayReset()); // This also handles resetting that segments validations a.validations.ForEach(b => b.ReplayReset()); }); - // reset all the top-level validations + // reset all the top-level sequence validations foreach (var validation in Validations) { validation.ReplayReset(); @@ -52,9 +52,9 @@ public void Reset() } /** - * Returns the next bot segment to evaluate and also provides the current segmentList level validations + * Returns the next bot segment and validations to evaluate and also provides the current segmentList level validations */ - public BotSegment DequeueBotSegment(out List segmentListValidations) + public (BotSegment, List)? DequeueBotSegment() { while (_botSegmentListIndex < _botSegmentLists.Count) { @@ -62,8 +62,8 @@ public BotSegment DequeueBotSegment(out List segmentListValid if (_botSegmentIndex < segmentList.segments.Count) { var segment = segmentList.segments[_botSegmentIndex++]; - segmentListValidations = segmentList.validations; - return segment; + var segmentListValidations = segmentList.validations; + return (segment, segmentListValidations); } else { @@ -73,8 +73,7 @@ public BotSegment DequeueBotSegment(out List segmentListValid } } - - segmentListValidations = new List(); + return null; } @@ -88,14 +87,44 @@ public List GetAllValidationResults() // First add all the top level results var results = Validations.Select(validation => validation.data.GetResults()).ToList(); - // Then add the individual bot segment results - foreach (var botSegment in _botSegments) + // Then add the validations from bot segment lists and individual bot segment results + foreach (var botSegmentList in _botSegmentLists) { - results.AddRange(botSegment.validations.Select(v => v.data.GetResults())); + results.AddRange(botSegmentList.validations.Select(v => v.data.GetResults())); + + foreach (var botSegment in botSegmentList.segments) + { + results.AddRange(botSegment.validations.Select(v => v.data.GetResults())); + } } return results; } + + /** + * + * This will request to stop all validations in the container, including sequence validations, bot + * segment list validations, and individual bot segment validations. + * + */ + public void StopAllValidations(int segmentNumber) + { + // First stop the sequence validations + foreach (var validation in Validations) + { + validation.StopValidation(segmentNumber); + } + + // Then stop the segment list validations and bot segment validations + foreach (var botSegmentList in _botSegmentLists) + { + botSegmentList.validations.ForEach(v => v.StopValidation(segmentNumber)); + foreach (var botSegment in botSegmentList.segments) + { + botSegment.validations.ForEach(v => v.StopValidation(segmentNumber)); + } + } + } } } diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackController.cs b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackController.cs index 9c7a372d..74e5942e 100644 --- a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackController.cs +++ b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/BotSegmentsPlaybackController.cs @@ -51,9 +51,12 @@ public class BotSegmentsPlaybackController : MonoBehaviour // We track this as a list instead of a single entry to allow the UI and game object conditions to evaluate separately // We still only unlock the input sequences for a key frame once both UI and game object conditions are met // This is done this way to allow situations like when loading screens (UI) are changing while game objects are loading in the background and the process is not consistent/deterministic between the 2 - private readonly List _nextBotSegments = new(); + private readonly List<(BotSegment, List)> _nextSegmentsAndValidations = new(); - private List _botSegmentListValidations = new(); + // We can only stop bot segment list validations once we are sure that we have moved on to the next + // bot segment list. This variable holds the last bot segment list validations so we can compare it to the + // next if needed and end them if the validations change. + private List _previousBotSegmentListValidations = null; // helps indicate if we made it through the full replay successfully private bool? _replaySuccessful; @@ -102,26 +105,46 @@ private void ProcessBotSegments() // track if we have a new segment to evaluate... so long as we do, keep looping here before releasing from this Update call // thus we process each new segment as soon as possible and don't have any artificial one frame delays before processing var nextBotSegmentIndex = 0; - while (nextBotSegmentIndex < _nextBotSegments.Count) + while (nextBotSegmentIndex < _nextSegmentsAndValidations.Count) { - var nextBotSegment = _nextBotSegments[nextBotSegmentIndex]; - + var (nextBotSegment, validations) = _nextSegmentsAndValidations[nextBotSegmentIndex]; + + // Before moving on, check to see if these bot segment list validations are new. If so, we need to stop + // any previous validations. Note that the check by reference here is intentional because we want + // to see if is actually the same list. + if (_previousBotSegmentListValidations != null && validations != _previousBotSegmentListValidations) + { + _previousBotSegmentListValidations.ForEach(v => v.StopValidation(nextBotSegment.Replay_SegmentNumber - 1)); + _previousBotSegmentListValidations = validations; + } + + // TODO(Q for Zack): I think this check for this individual segment should go in this index = 0 check - is that correct? + // Check that the individual bot segment validations, segment list validations, and sequence validations + // are ready to go. Only continue after that. + if (!EnsureValidationsAreReady(nextBotSegment.validations, nextBotSegment.Replay_SegmentNumber) + || !EnsureValidationsAreReady(validations, nextBotSegment.Replay_SegmentNumber) + || !EnsureValidationsAreReady(_dataPlaybackContainer.Validations, nextBotSegment.Replay_SegmentNumber)) + { + return; + } + // if we're working on the first entry in the list is the only time we do actions if (nextBotSegmentIndex == 0) { + // At this point, we are ready to run the actions! ProcessBotSegmentAction(nextBotSegment, transformStatuses, entityStatuses); } - + // Run (and potentially end) the validations for this segment nextBotSegment.ProcessValidation(); // Also run the validations for the botsegmentList if they exist - foreach (var validation in _botSegmentListValidations) + foreach (var validation in validations) { validation.ProcessValidation(nextBotSegment.Replay_SegmentNumber); } - // Also run the validations for the entire list of segments if they exist + // Finally, run the validations for the sequence if they exist foreach (var validation in _dataPlaybackContainer.Validations) { validation.ProcessValidation(nextBotSegment.Replay_SegmentNumber); @@ -178,7 +201,7 @@ private void ProcessBotSegments() RGDebug.LogInfo($"({nextBotSegment.Replay_SegmentNumber}) - Bot Segment - DONE - Actions and Criteria have been met, but validations have not completed. Forcing them to complete. - {nextBotSegment.name ?? nextBotSegment.resourcePath} - {nextBotSegment.description}"); nextBotSegment.StopValidations(); } - _nextBotSegments.RemoveAt(nextBotSegmentIndex); + _nextSegmentsAndValidations.RemoveAt(nextBotSegmentIndex); // don't update the index since we shortened the list } else @@ -202,22 +225,22 @@ private void ProcessBotSegments() } // we possibly removed from the list above.. need this check - if (_nextBotSegments.Count > 0) + if (_nextSegmentsAndValidations.Count > 0) { // see if the last entry has transient matches.. if so.. dequeue another up to a limit of 2 total segments being evaluated... we may need to come back to this.. but without this look ahead, loading screens like bossroom fail due to background loading // but if you go too far.. you can match segments in the replay that you won't see for another 50 segments when you go back to the menu again.. which is obviously wrong - var lastSegment = _nextBotSegments[^1]; + var (lastSegment, lastValidations) = _nextSegmentsAndValidations[^1]; if (lastSegment.Replay_TransientMatched) { - if (_nextBotSegments.Count < 2) + if (_nextSegmentsAndValidations.Count < 2) { - var next = _dataPlaybackContainer.DequeueBotSegment(out _botSegmentListValidations); + var (next, nextValidations) = _dataPlaybackContainer.DequeueBotSegment() ?? (null, null); if (next != null) { _lastTimeLoggedKeyFrameConditions = now; FindObjectOfType()?.SetKeyFrameWarningText(null); RGDebug.LogInfo($"({next.Replay_SegmentNumber}) - Bot Segment - Added {(next.HasTransientCriteria ? "" : "Non-")}Transient BotSegment for Evaluation after Transient BotSegment - {next.name ?? next.resourcePath} - {next.description}"); - _nextBotSegments.Add(next); + _nextSegmentsAndValidations.Add((next, nextValidations)); //next while loop iteration will get this guy } } @@ -226,13 +249,13 @@ private void ProcessBotSegments() else { // segment list empty.. dequeue another - var next = _dataPlaybackContainer.DequeueBotSegment(out _botSegmentListValidations); + var (next, nextValidations) = _dataPlaybackContainer.DequeueBotSegment() ?? (null, null); if (next != null) { _lastTimeLoggedKeyFrameConditions = now; FindObjectOfType()?.SetKeyFrameWarningText(null); RGDebug.LogInfo($"({next.Replay_SegmentNumber}) - Bot Segment - Added {(next.HasTransientCriteria ? "" : "Non-")}Transient BotSegment for Evaluation - {next.name ?? next.resourcePath} - {next.description}"); - _nextBotSegments.Add(next); + _nextSegmentsAndValidations.Add((next, nextValidations)); //next while loop iteration will get this guy } } @@ -403,27 +426,10 @@ public void LateUpdate() { if (_dataPlaybackContainer != null) { - var currentSegmentNumber = _nextBotSegments.Count > 0 ? _nextBotSegments[0].Replay_SegmentNumber : -1; - - // If there are top-level validations running, let's make sure those are prepared before we - // process the segments. - var validationsReady = true; - foreach (var validation in _dataPlaybackContainer.Validations) - { - if (!validation.ProcessValidation(currentSegmentNumber)) - { - validationsReady = false; - break; - } - } - - if (validationsReady) - { - ProcessBotSegments(); - } + ProcessBotSegments(); } - if (_nextBotSegments.Count == 0) + if (_nextSegmentsAndValidations.Count == 0) { MouseEventSender.MoveMouseOffScreen(); @@ -586,15 +592,21 @@ public void Pause() { _playState = PlayState.Paused; - var currentSegmentNumber = _nextBotSegments.Count > 0 ? _nextBotSegments[0].Replay_SegmentNumber : -1; - - foreach (var nextBotSegment in _nextBotSegments) + var currentSegmentNumber = _nextSegmentsAndValidations.Count > 0 ? _nextSegmentsAndValidations[0].Item1.Replay_SegmentNumber : -1; + + // First, pause all the validations that are part of a segment list or bot segments themselves + foreach (var (nextBotSegment, validations) in _nextSegmentsAndValidations) { nextBotSegment.PauseAction(); nextBotSegment.PauseValidations(); + + foreach (var v in validations) + { + v.PauseValidation(currentSegmentNumber); + } } - // Also pause the top-level validations + // Also pause the top-level sequence validations foreach (var validation in _dataPlaybackContainer.Validations) { validation.PauseValidation(currentSegmentNumber); @@ -619,15 +631,20 @@ public void Play() // resume _playState = PlayState.Playing; - var currentSegmentNumber = _nextBotSegments.Count > 0 ? _nextBotSegments[0].Replay_SegmentNumber : -1; + var currentSegmentNumber = _nextSegmentsAndValidations.Count > 0 ? _nextSegmentsAndValidations[0].Item1.Replay_SegmentNumber : -1; - foreach (var nextBotSegment in _nextBotSegments) + // Unpause all the validations that are part of a segment list or bot segments themselves + foreach (var (nextBotSegment, validations) in _nextSegmentsAndValidations) { nextBotSegment.UnPauseAction(); nextBotSegment.UnPauseValidations(); + foreach (var v in validations) + { + v.UnPauseValidation(currentSegmentNumber); + } } - // Also unpause the top-level validations + // Also unpause the top-level sequence validations foreach (var validation in _dataPlaybackContainer.Validations) { validation.UnPauseValidation(currentSegmentNumber); @@ -653,28 +670,28 @@ public void Loop(Action loopCountCallback) public void UnloadSegmentsAndReset() { - if (_nextBotSegments.Count > 0) + if (_nextSegmentsAndValidations.Count > 0) { - foreach (var nextBotSegment in _nextBotSegments) + // Reset and stop both the validations in the bot segment lists and the bot segments themselves + foreach (var (nextBotSegment, validations) in _nextSegmentsAndValidations) { // stop any action nextBotSegment.AbortAction(); nextBotSegment.StopValidations(); + + foreach (var v in validations) + { + v.StopValidation(nextBotSegment.Replay_SegmentNumber); + } } } - var currentSegmentNumber = _nextBotSegments.Count > 0 ? _nextBotSegments[0].Replay_SegmentNumber : -1; + var currentSegmentNumber = _nextSegmentsAndValidations.Count > 0 ? _nextSegmentsAndValidations[0].Item1.Replay_SegmentNumber : -1; - // Also wrap up the top-level validations if they exist - if (_dataPlaybackContainer != null) - { - foreach (var validation in _dataPlaybackContainer.Validations) - { - validation.StopValidation(currentSegmentNumber); - } - } + // Wrap up all other validations. This does repeat some of the stopping from above, but is safest + _dataPlaybackContainer?.StopAllValidations(currentSegmentNumber); - _nextBotSegments.Clear(); + _nextSegmentsAndValidations.Clear(); _playState = PlayState.NotLoaded; BotSequence.ActiveBotSequence = null; _loopCount = -1; @@ -706,24 +723,20 @@ public void UnloadSegmentsAndReset() public void Stop() { - var currentSegmentNumber = _nextBotSegments.Count > 0 ? _nextBotSegments[0].Replay_SegmentNumber : -1; + var currentSegmentNumber = _nextSegmentsAndValidations.Count > 0 ? _nextSegmentsAndValidations[0].Item1.Replay_SegmentNumber : -1; // Make sure to stop any running validations - if (_dataPlaybackContainer != null) - { - foreach (var validation in _dataPlaybackContainer.Validations) - { - validation.StopValidation(currentSegmentNumber); - } - } + _dataPlaybackContainer?.StopAllValidations(currentSegmentNumber); - _nextBotSegments.Clear(); + _nextSegmentsAndValidations.Clear(); _playState = PlayState.Stopped; _loopCount = -1; _replaySuccessful = null; WaitingForKeyFrameConditions = null; _lastSegmentPlaybackWarning = null; + _previousBotSegmentListValidations = null; + // Note that this retrieves all the validation results from the bot segments, bot segment lists, and sequences _screenRecorder.validationResults = _dataPlaybackContainer?.GetAllValidationResults() ?? new List(); _screenRecorder.StopRecording(); #if ENABLE_LEGACY_INPUT_MANAGER @@ -749,12 +762,13 @@ public void Stop() public void PrepareForNextLoop() { - _nextBotSegments.Clear(); + _nextSegmentsAndValidations.Clear(); _playState = PlayState.Starting; // don't change _loopCount _replaySuccessful = null; WaitingForKeyFrameConditions = null; _lastSegmentPlaybackWarning = null; + _previousBotSegmentListValidations = null; #if ENABLE_LEGACY_INPUT_MANAGER RGLegacyInputWrapper.StopSimulation(); @@ -805,7 +819,11 @@ public void Update() #endif RGUtils.ConfigureInputSettings(); _playState = PlayState.Playing; - _nextBotSegments.Add(_dataPlaybackContainer.DequeueBotSegment(out _botSegmentListValidations)); + var nextSegmentAndValidations = _dataPlaybackContainer.DequeueBotSegment(); + if (nextSegmentAndValidations != null) + { + _nextSegmentsAndValidations.Add(nextSegmentAndValidations.Value); + } // if starting to play, or on loop 1.. start recording if (_loopCount < 2) { @@ -854,12 +872,20 @@ private void LogPlaybackWarning(string loggedMessage, Exception ex = null) } } + /** + * Returns true if the validations are ready to be executed, and false otherwise. + */ + private bool EnsureValidationsAreReady(List validations, int segmentNumber) + { + return validations.All(v => v.data.AttemptPrepareValidation(segmentNumber)); + } + public void OnGUI() { if (_playState == PlayState.Playing || _playState == PlayState.Paused) { // render any GUI things for the first segment action - if (_nextBotSegments.Count > 0) + if (_nextSegmentsAndValidations.Count > 0) { var transformStatuses = new Dictionary(); var entityStatuses = new Dictionary(); @@ -876,7 +902,7 @@ public void OnGUI() entityStatuses = objectFinder.GetObjectStatusForCurrentFrame().Item2; } } - _nextBotSegments[0].OnGUI(transformStatuses, entityStatuses); + _nextSegmentsAndValidations[0].Item1.OnGUI(transformStatuses, entityStatuses); } } } diff --git a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/SegmentValidation.cs b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/SegmentValidation.cs index b4b9fbf1..401c87a3 100644 --- a/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/SegmentValidation.cs +++ b/src/gg.regression.unity.bots/Runtime/Scripts/StateRecorder/BotSegments/Models/SegmentValidation.cs @@ -24,16 +24,15 @@ public class SegmentValidation // be able to wait for the validation to be ready before running the segments. public bool ProcessValidation(int segmentNumber) { - if (_validationIsReady) + if (!_validationIsReady) { - // NOTE: It would be nice to avoid running the validation once they are complete, but there are a lot - // of validations that could be passed but then fail later... so we always just run this until either - // the timeout is hit or the segment criteria and actions are met. We may revisit this. - data.ProcessValidation(segmentNumber); + _validationIsReady = data.AttemptPrepareValidation(segmentNumber); } - else + + // If the validation is now ready, we can start running it + if (_validationIsReady) { - _validationIsReady = data.AttemptPrepareValidation(segmentNumber); + data.ProcessValidation(segmentNumber); } return _validationIsReady;