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();