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..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
@@ -1,16 +1,20 @@
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
{
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,52 +22,109 @@ 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();
- }
-
- // reset all the top-level validations
+ a.segments.ForEach(b => b.ReplayReset()); // This also handles resetting that segments validations
+ a.validations.ForEach(b => b.ReplayReset());
+ });
+
+ // reset all the top-level sequence validations
foreach (var validation in Validations)
{
validation.ReplayReset();
}
+
}
- public BotSegment DequeueBotSegment()
+ /**
+ * Returns the next bot segment and validations to evaluate and also provides the current segmentList level validations
+ */
+ public (BotSegment, List)? DequeueBotSegment()
{
- if (_botSegmentIndex < _botSegments.Count)
+ while (_botSegmentListIndex < _botSegmentLists.Count)
{
- return _botSegments[_botSegmentIndex++];
- }
+ var segmentList = _botSegmentLists[_botSegmentListIndex];
+ if (_botSegmentIndex < segmentList.segments.Count)
+ {
+ var segment = segmentList.segments[_botSegmentIndex++];
+ var segmentListValidations = segmentList.validations;
+ return (segment, segmentListValidations);
+ }
+ else
+ {
+ // move to the next segmentlist starting on the 0th segment in that list
+ _botSegmentIndex = 0;
+ ++_botSegmentListIndex;
+ }
+ }
+
return null;
}
-
- public BotSegment PeekBotSegment()
+
+
+ /**
+ * Collects all of the results from the top-level validations and individual bot segments
+ */
+ public List GetAllValidationResults()
{
- if (_botSegmentIndex < _botSegments.Count)
+
+ // First add all the top level results
+ var results = Validations.Select(validation => validation.data.GetResults()).ToList();
+
+ // Then add the validations from bot segment lists and individual bot segment results
+ foreach (var botSegmentList in _botSegmentLists)
{
- // do not update index
- return _botSegments[_botSegmentIndex];
+ 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 null;
+ 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 631225bb..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
@@ -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
@@ -49,7 +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();
+
+ // 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;
@@ -98,15 +105,50 @@ 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 validations)
+ {
+ validation.ProcessValidation(nextBotSegment.Replay_SegmentNumber);
+ }
+
+ // Finally, run the validations for the sequence 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,
@@ -159,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
@@ -183,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();
+ 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
}
}
@@ -207,13 +249,13 @@ private void ProcessBotSegments()
else
{
// segment list empty.. dequeue another
- var next = _dataPlaybackContainer.DequeueBotSegment();
+ 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
}
}
@@ -282,21 +324,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 +354,7 @@ private void ProcessBotSegmentAction(BotSegment firstActionSegment, Dictionary()?.SetKeyFrameWarningText(null);
- _explorationDriver.StopExploring(firstActionSegment.Replay_SegmentNumber);
- _explorationDriver.ReportPreviouslyCompletedAction(firstActionSegment.botAction.data);
+ _explorationDriver.StopExploring(actionSegment.Replay_SegmentNumber);
+ _explorationDriver.ReportPreviouslyCompletedAction(actionSegment.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);
- }
-
+
}
catch (Exception ex)
{
@@ -393,26 +426,10 @@ public void LateUpdate()
{
if (_dataPlaybackContainer != null)
{
-
- // 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))
- {
- validationsReady = false;
- break;
- }
- }
-
- if (validationsReady)
- {
- ProcessBotSegments();
- }
+ ProcessBotSegments();
}
- if (_nextBotSegments.Count == 0)
+ if (_nextSegmentsAndValidations.Count == 0)
{
MouseEventSender.MoveMouseOffScreen();
@@ -575,16 +592,24 @@ public void Pause()
{
_playState = PlayState.Paused;
- 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(-1);
+ validation.PauseValidation(currentSegmentNumber);
}
}
}
@@ -606,16 +631,23 @@ public void Play()
// resume
_playState = PlayState.Playing;
- foreach (var nextBotSegment in _nextBotSegments)
+ var currentSegmentNumber = _nextSegmentsAndValidations.Count > 0 ? _nextSegmentsAndValidations[0].Item1.Replay_SegmentNumber : -1;
+
+ // 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(-1);
+ validation.UnPauseValidation(currentSegmentNumber);
}
}
}
@@ -638,32 +670,35 @@ 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);
+ }
}
}
-
- // Also wrap up the top-level validations if they exist
- if (_dataPlaybackContainer != null)
- {
- foreach (var validation in _dataPlaybackContainer.Validations)
- {
- validation.StopValidation(-1);
- }
- }
- _nextBotSegments.Clear();
+ var currentSegmentNumber = _nextSegmentsAndValidations.Count > 0 ? _nextSegmentsAndValidations[0].Item1.Replay_SegmentNumber : -1;
+
+ // Wrap up all other validations. This does repeat some of the stopping from above, but is safest
+ _dataPlaybackContainer?.StopAllValidations(currentSegmentNumber);
+
+ _nextSegmentsAndValidations.Clear();
_playState = PlayState.NotLoaded;
BotSequence.ActiveBotSequence = null;
_loopCount = -1;
_replaySuccessful = null;
WaitingForKeyFrameConditions = null;
+ _screenRecorder.validationResults = _dataPlaybackContainer?.GetAllValidationResults() ?? new List();
_screenRecorder.StopRecording();
#if ENABLE_LEGACY_INPUT_MANAGER
RGLegacyInputWrapper.StopSimulation();
@@ -687,23 +722,22 @@ public void UnloadSegmentsAndReset()
public void Stop()
{
-
+
+ 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(-1);
- }
- }
-
- _nextBotSegments.Clear();
+ _dataPlaybackContainer?.StopAllValidations(currentSegmentNumber);
+
+ _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
RGLegacyInputWrapper.StopSimulation();
@@ -728,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();
@@ -784,7 +819,11 @@ public void Update()
#endif
RGUtils.ConfigureInputSettings();
_playState = PlayState.Playing;
- _nextBotSegments.Add(_dataPlaybackContainer.DequeueBotSegment());
+ var nextSegmentAndValidations = _dataPlaybackContainer.DequeueBotSegment();
+ if (nextSegmentAndValidations != null)
+ {
+ _nextSegmentsAndValidations.Add(nextSegmentAndValidations.Value);
+ }
// if starting to play, or on loop 1.. start recording
if (_loopCount < 2)
{
@@ -825,6 +864,7 @@ private void LogPlaybackWarning(string loggedMessage, Exception ex = null)
RGDebug.LogWarning(loggedMessage);
}
}
+
FindObjectOfType()?.SetKeyFrameWarningText(loggedMessage);
if (pauseEditorOnPlaybackWarning)
{
@@ -832,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();
@@ -854,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/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..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;
@@ -89,4 +88,4 @@ public override string ToString()
}
}
-}
\ No newline at end of file
+}
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/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/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
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();