Skip to content

Commit

Permalink
Merge pull request #97 from nowsprinting/feature/scene_crossing_agents
Browse files Browse the repository at this point in the history
Add SceneCrossingAgents (ObserverAgent is obsolete)
  • Loading branch information
get-me-power authored Nov 6, 2024
2 parents 09f5a70 + 89f98d0 commit eee4c84
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 80 deletions.
12 changes: 6 additions & 6 deletions Editor/Localization/ja.po
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ msgstr "フォールバックAgent"
msgid "Agent for when not assigned"
msgstr "割り当てのないSceneのときに使われるAgent"

# observerAgent
msgid "Observer Agent"
msgstr "オブザーバーAgent"
# sceneCrossingAgents
msgid "Scene Crossing Agents"
msgstr "Scene横断Agents"

# observerAgent tooltip
msgid "Parallel running agent using on every scene, for using observer (e.g., EmergencyExitAgent)"
msgstr "Sceneに関わらず並列実行されるAgent。一般的にはEmergencyExitAgentを割り当てます"
# sceneCrossingAgents tooltip
msgid "Agents running by scene crossing. The specified agents will have the same lifespan as Autopilot (i.e., use DontDestroyOnLoad) for specifying, e.g., ErrorHandlerAgent and UGUIEmergencyExitAgent."
msgstr "Sceneを横断して実行されるAgent。指定されたAgentの寿命は Autopilot と同じになります(つまり、DontDestroyOnLoad を使用します)。たとえば、ErrorHandlerAgent や UGUIEmergencyExitAgent などを指定します"

# Header: Autopilot Run Settings
msgid "Autopilot Run Settings"
Expand Down
29 changes: 10 additions & 19 deletions Editor/UI/Settings/AutopilotSettingsEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace DeNA.Anjin.Editor.UI.Settings
[CustomEditor(typeof(AutopilotSettings))]
public class AutopilotSettingsEditor : UnityEditor.Editor
{
// @formatter:off
private static readonly string s_description = L10n.Tr("Description");
private static readonly string s_descriptionTooltip = L10n.Tr("Description about this setting instance");

Expand All @@ -22,17 +23,13 @@ public class AutopilotSettingsEditor : UnityEditor.Editor
private static readonly string s_sceneAgentMapsTooltip = L10n.Tr("Scene to Agent assign mapping");
private static readonly string s_fallbackAgent = L10n.Tr("Fallback Agent");
private static readonly string s_fallbackAgentTooltip = L10n.Tr("Agent for when not assigned");
private static readonly string s_observerAgent = L10n.Tr("Observer Agent");

private static readonly string s_observerAgentTooltip =
L10n.Tr("Parallel running agent using on every scene, for using observer (e.g., EmergencyExitAgent)");
private static readonly string s_sceneCrossingAgents = L10n.Tr("Scene Crossing Agents");
private static readonly string s_sceneCrossingAgentsTooltip = L10n.Tr("Agents running by scene crossing. The specified agents will have the same lifespan as Autopilot (i.e., use DontDestroyOnLoad) for specifying, e.g., ErrorHandlerAgent and UGUIEmergencyExitAgent.");

private static readonly string s_autopilotRunSettingsHeader = L10n.Tr("Autopilot Run Settings");
private static readonly string s_lifespanSec = L10n.Tr("Lifespan Sec");

private static readonly string s_lifespanSecTooltip =
L10n.Tr("Autopilot running lifespan [sec]. When specified zero, so unlimited running");

private static readonly string s_lifespanSecTooltip = L10n.Tr("Autopilot running lifespan [sec]. When specified zero, so unlimited running");
private static readonly string s_randomSeed = L10n.Tr("Random Seed");
private static readonly string s_randomSeedTooltip = L10n.Tr("Random using the specified seed value");
private static readonly string s_timeScale = L10n.Tr("Time Scale");
Expand All @@ -42,15 +39,10 @@ public class AutopilotSettingsEditor : UnityEditor.Editor
private static readonly string s_junitReportPathTooltip = L10n.Tr("JUnit report output path");

private static readonly string s_loggers = L10n.Tr("Loggers");

private static readonly string s_loggersTooltip =
L10n.Tr(
"List of Loggers used for this autopilot settings. If omitted, Debug.unityLogger will be used as default.");
private static readonly string s_loggersTooltip = L10n.Tr("List of Loggers used for this autopilot settings. If omitted, Debug.unityLogger will be used as default.");

private static readonly string s_reporters = L10n.Tr("Reporters");

private static readonly string s_reportersTooltip =
L10n.Tr("List of Reporters to be called on Autopilot terminate.");
private static readonly string s_reportersTooltip = L10n.Tr("List of Reporters to be called on Autopilot terminate.");

private static readonly string s_errorHandlingSettingsHeader = L10n.Tr("Error Handling Settings");
private static readonly string s_handleException = L10n.Tr("Handle Exception");
Expand All @@ -62,14 +54,13 @@ public class AutopilotSettingsEditor : UnityEditor.Editor
private static readonly string s_handleWarning = L10n.Tr("Handle Warning");
private static readonly string s_handleWarningTooltip = L10n.Tr("Notify when Warning detected in log");
private static readonly string s_ignoreMessages = L10n.Tr("Ignore Messages");

private static readonly string s_ignoreMessagesTooltip =
L10n.Tr("Do not send notifications when log messages contain this string");
private static readonly string s_ignoreMessagesTooltip = L10n.Tr("Do not send notifications when log messages contain this string");

private static readonly string s_runButton = L10n.Tr("Run");
private static readonly string s_stopButton = L10n.Tr("Stop");
private const float SpacerPixels = 10f;
private const float SpacerPixelsUnderHeader = 4f;
// @formatter:on

/// <inheritdoc/>
public override void OnInspectorGUI()
Expand All @@ -84,8 +75,8 @@ public override void OnInspectorGUI()
new GUIContent(s_sceneAgentMaps, s_sceneAgentMapsTooltip), true);
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(AutopilotSettings.fallbackAgent)),
new GUIContent(s_fallbackAgent, s_fallbackAgentTooltip));
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(AutopilotSettings.observerAgent)),
new GUIContent(s_observerAgent, s_observerAgentTooltip));
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(AutopilotSettings.sceneCrossingAgents)),
new GUIContent(s_sceneCrossingAgents, s_sceneCrossingAgentsTooltip));

DrawHeader(s_autopilotRunSettingsHeader);
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(AutopilotSettings.lifespanSec)),
Expand Down
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,12 @@ If a Scene does not exist in the list, the Agent set in `Fallback Agent` will be
For example, if you want `UGUIMonkeyAgent` to work for all Scenes, set `Scene Agent Maps` to empty, and set
Set the `Fallback Agent` to an `UGUIMonkeyAgent`'s .asset file.

##### Observer Agent
##### Scene Crossing Agents

Set the Agents to always be launched in parallel, independent of `Scene Agent Maps` and `Fallback Agent`.
As of v1.0.0, `EmergencyExitAgent` is assumed to be used.
If you need to launch multiple Agents in parallel, use `ParallelCompositeAgent`.
Set the Agents to run by scene crossing, independent of `Scene Agent Maps` and `Fallback Agent`.

Note that the Agents set here will be destroyed and created each time a new Scene is loaded.
It is **NOT** set to `DontDestroyOnLoad`.
The specified agents will have the same lifespan as Autopilot (i.e., use `DontDestroyOnLoad`)
for specifying, e.g., `ErrorHandlerAgent` and `UGUIEmergencyExitAgent`.

#### Autopilot Run Settings

Expand Down Expand Up @@ -385,7 +383,7 @@ An Agent that monitors the appearance of the `EmergencyExitAnnotations` componen
This can be used in games that contain behavior that is irregular in the execution of the test scenario, for example, communication errors or "return to title screen" buttons that are triggered by a daybreak.

It should always be started at the same time as other Agents (that actually perform game operations).
This can be accomplished with `ParallelCompositeAgent`, but it is easier to set it up as an `ObserverAgent` in AutopilotSettings.
This can be accomplished with `ParallelCompositeAgent`, but it is easier to set it up as an `Scene Crossing Agents` in AutopilotSettings.



Expand Down
11 changes: 5 additions & 6 deletions README_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,12 @@ Sceneごとに自動実行を行なうAgent設定ファイル(.asset)の対
たとえば全てのSceneで `UGUIMonkeyAgent` が動くようにしたければ、`Scene Agent Maps` は空に、
`Fallback Agent` には `UGUIMonkeyAgent` の.assetファイルを設定します。

##### オブザーバーAgent
##### Scene横断Agents

`Scene Agent Maps``Fallback Agent`とは独立して、常に並列に起動されるAgentを設定します。
v1.0.0時点では `EmergencyExitAgent` の使用を想定しています。
複数のAgentを並列起動する必要があるときは `ParallelCompositeAgent` を使用してください。
`Scene Agent Maps` および `Fallback Agent`とは独立して、Sceneを横断して実行されるAgentを設定します。

なお、ここに設定されたAgentは `DontDestroyOnLoad` **ではなく**、新しいSceneがロードされる度に破棄・生成される点に注意してください。
指定されたAgentの寿命はオートパイロット本体と同じになります(つまり、`DontDestroyOnLoad` を使用します)。
たとえば、`ErrorHandlerAgent``UGUIEmergencyExitAgent` などを指定します。

#### オートパイロット実行設定

Expand Down Expand Up @@ -389,7 +388,7 @@ SerialCompositeAgentと組み合わせることで、一連の操作を何周も
たとえば通信エラーや日またぎで「タイトル画面に戻る」ボタンのような、テストシナリオ遂行上イレギュラーとなる振る舞いが含まれるゲームで利用できます。

常に、他の(実際にゲーム操作を行なう)Agentと同時に起動しておく必要があります。
`ParallelCompositeAgent` でも実現できますが、AutopilotSettingsに `Observer Agent` として設定するほうが簡単です
`ParallelCompositeAgent` でも実現できますが、AutopilotSettingsの `Scene Crossing Agents` に追加するほうが簡単です



Expand Down
34 changes: 24 additions & 10 deletions Runtime/AgentDispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ public interface IAgentDispatcher : IDisposable
/// <param name="fallback">Use fallback agent if true</param>
/// <returns>True: Agent dispatched</returns>
bool DispatchByScene(Scene scene, bool fallback = true);

/// <summary>
/// Dispatch scene crossing Agents.
/// </summary>
void DispatchSceneCrossingAgents();
}

/// <inheritdoc/>
Expand All @@ -44,6 +49,7 @@ public AgentDispatcher(AutopilotSettings settings, ILogger logger, RandomFactory
_settings = settings;
_logger = logger;
_randomFactory = randomFactory;

SceneManager.sceneLoaded += this.DispatchByScene;
}

Expand Down Expand Up @@ -91,28 +97,36 @@ public bool DispatchByScene(Scene scene, bool fallback = true)
}
}

if (agent)
if (!agent)
{
DispatchAgent(agent);
return false;
}

if (_settings.observerAgent != null)
{
DispatchAgent(_settings.observerAgent);
// Note: The ObserverAgent is not made to DontDestroyOnLoad, to start every time.
// Because it is a source of bugs to force the implementation of DontDestroyOnLoad to the descendants of the Composite.
}
DispatchAgent(agent);
return true;
}

return agent != null; // Returns Agent dispatched or not.
/// <inheritdoc/>
public void DispatchSceneCrossingAgents()
{
_settings.sceneCrossingAgents.ForEach(agent =>
{
DispatchAgent(agent, true);
});
}

private void DispatchAgent(AbstractAgent agent)
private void DispatchAgent(AbstractAgent agent, bool dontDestroyOnLoad = false)
{
var agentName = agent.name;
agent.Logger = _logger;
agent.Random = _randomFactory.CreateRandom();

var inspector = new GameObject(agentName).AddComponent<AgentInspector>();
if (dontDestroyOnLoad)
{
Object.DontDestroyOnLoad(inspector.gameObject);
}

var token = inspector.gameObject.GetCancellationTokenOnDestroy();
_logger.Log($"Dispatch agent: {agentName}");
agent.Run(token).Forget(); // Agent also dies when GameObject is destroyed
Expand Down
1 change: 1 addition & 0 deletions Runtime/Autopilot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ private void Start()
_logMessageHandler = new LogMessageHandler(_settings, this);

_dispatcher = new AgentDispatcher(_settings, _logger, _randomFactory);
_dispatcher.DispatchSceneCrossingAgents();
var dispatched = _dispatcher.DispatchByScene(SceneManager.GetActiveScene(), false);
if (!dispatched)
{
Expand Down
23 changes: 23 additions & 0 deletions Runtime/Settings/AutopilotSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,16 @@ public class AutopilotSettings : ScriptableObject
/// <summary>
/// Parallel running agent using on every scene, for using observer (e.g., EmergencyExitAgent)
/// </summary>
[Obsolete("Use crossSceneAgents instead")]
public AbstractAgent observerAgent;

/// <summary>
/// Agents running by scene crossing.
/// The specified agents will have the same lifespan as <c>Autopilot</c> (i.e., use <c>DontDestroyOnLoad</c>)
/// for specifying, e.g., <c>ErrorHandlerAgent</c>, <c>UGUIEmergencyExitAgent</c>.
/// </summary>
public List<AbstractAgent> sceneCrossingAgents = new List<AbstractAgent>();

/// <summary>
/// Autopilot running lifespan [sec]. When specified zero, so unlimited running
/// </summary>
Expand Down Expand Up @@ -345,5 +353,20 @@ private void SaveConvertedObject(Object obj)
AssetDatabase.CreateAsset(obj, Path.Combine(dir, $"New {obj.GetType().Name}.asset"));
#endif
}

[Obsolete("Remove this method when bump major version")]
internal void ConvertSceneCrossingAgentsFromObsoleteObserverAgent(ILogger logger)
{
if (this.observerAgent == null || this.sceneCrossingAgents.Any())
{
return;
}

logger.Log(LogType.Warning, @"ObserverAgent setting in AutopilotSettings has been obsolete.
Please delete the value using Debug Mode in the Inspector window. And using the SceneCrossingAgents.
This time, temporarily converting.");

this.sceneCrossingAgents.Add(this.observerAgent);
}
}
}
55 changes: 23 additions & 32 deletions Tests/Runtime/AgentDispatcherTest.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright (c) 2023-2024 DeNA Co., Ltd.
// This software is released under the MIT License.

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
Expand All @@ -19,7 +17,6 @@
namespace DeNA.Anjin
{
[UnityPlatform(RuntimePlatform.OSXEditor, RuntimePlatform.WindowsEditor, RuntimePlatform.LinuxEditor)]
[SuppressMessage("ApiDesign", "RS0030")]
public class AgentDispatcherTest
{
private const string TestScenePath = "Packages/com.dena.anjin/Tests/TestScenes/Buttons.unity";
Expand All @@ -39,13 +36,6 @@ public void TearDown()
_dispatcher?.Dispose();
}

private static AutopilotSettings CreateAutopilotSettings()
{
var testSettings = ScriptableObject.CreateInstance<AutopilotSettings>();
testSettings.sceneAgentMaps = new List<SceneAgentMap>();
return testSettings;
}

private static SpyAliveCountAgent CreateSpyAliveCountAgent(string name = nameof(SpyAliveCountAgent))
{
var agent = ScriptableObject.CreateInstance<SpyAliveCountAgent>();
Expand All @@ -66,7 +56,7 @@ private void SetUpDispatcher(AutopilotSettings settings)
public async Task DispatchByScene_DispatchAgentBySceneAgentMaps()
{
const string AgentName = "Mapped Agent";
var settings = CreateAutopilotSettings();
var settings = ScriptableObject.CreateInstance<AutopilotSettings>();
settings.sceneAgentMaps.Add(new SceneAgentMap
{
scenePath = TestScenePath, agent = CreateSpyAliveCountAgent(AgentName)
Expand All @@ -85,7 +75,7 @@ public async Task DispatchByScene_DispatchAgentBySceneAgentMaps()
public async Task DispatchByScene_DispatchFallbackAgent()
{
const string AgentName = "Fallback Agent";
var settings = CreateAutopilotSettings();
var settings = ScriptableObject.CreateInstance<AutopilotSettings>();
settings.fallbackAgent = CreateSpyAliveCountAgent(AgentName);
SetUpDispatcher(settings);

Expand All @@ -100,7 +90,7 @@ public async Task DispatchByScene_DispatchFallbackAgent()
[CreateScene]
public async Task DispatchByScene_NoSceneAgentMapsAndFallbackAgent_AgentIsNotDispatch()
{
var settings = CreateAutopilotSettings();
var settings = ScriptableObject.CreateInstance<AutopilotSettings>();
SetUpDispatcher(settings);

await SceneManagerHelper.LoadSceneAsync(TestScenePath);
Expand All @@ -109,28 +99,12 @@ public async Task DispatchByScene_NoSceneAgentMapsAndFallbackAgent_AgentIsNotDis
Assert.That(SpyAliveCountAgent.AliveInstances, Is.EqualTo(0));
}

[Test]
[CreateScene]
public async Task DispatchByScene_DispatchObserverAgent()
{
const string AgentName = "Observer Agent";
var settings = CreateAutopilotSettings();
settings.observerAgent = CreateSpyAliveCountAgent(AgentName);
SetUpDispatcher(settings);

await SceneManagerHelper.LoadSceneAsync(TestScenePath);

var gameObject = GameObject.Find(AgentName);
Assert.That(gameObject, Is.Not.Null);
Assert.That(SpyAliveCountAgent.AliveInstances, Is.EqualTo(1));
}

[Test]
[CreateScene]
public async Task DispatchByScene_ReActivateScene_NotCreateDuplicateAgents()
{
const string AgentName = "Mapped Agent";
var settings = CreateAutopilotSettings();
var settings = ScriptableObject.CreateInstance<AutopilotSettings>();
settings.sceneAgentMaps.Add(new SceneAgentMap
{
scenePath = TestScenePath, agent = CreateSpyAliveCountAgent(AgentName)
Expand All @@ -150,13 +124,30 @@ public async Task DispatchByScene_ReActivateScene_NotCreateDuplicateAgents()
Assert.That(SpyAliveCountAgent.AliveInstances, Is.EqualTo(1)); // Not create duplicate agents
}

[Test]
[CreateScene]
public void DispatchSceneCrossingAgents_DispatchAgent()
{
const string AgentName = "Scene Crossing Agent";
var settings = ScriptableObject.CreateInstance<AutopilotSettings>();
settings.sceneCrossingAgents.Add(CreateSpyAliveCountAgent(AgentName));
SetUpDispatcher(settings);

_dispatcher.DispatchSceneCrossingAgents();

var gameObject = GameObject.Find(AgentName);
Assert.That(gameObject, Is.Not.Null);
Assert.That(SpyAliveCountAgent.AliveInstances, Is.EqualTo(1));
}

[Test]
public async Task Dispose_DestroyAllRunningAgents()
{
var settings = CreateAutopilotSettings();
var settings = ScriptableObject.CreateInstance<AutopilotSettings>();
settings.fallbackAgent = CreateSpyAliveCountAgent("Fallback Agent");
settings.observerAgent = CreateSpyAliveCountAgent("Observer Agent");
settings.sceneCrossingAgents.Add(CreateSpyAliveCountAgent("Scene Crossing Agent"));
SetUpDispatcher(settings);
_dispatcher.DispatchSceneCrossingAgents();
await SceneManagerHelper.LoadSceneAsync(TestScenePath);

_dispatcher.Dispose();
Expand Down
Loading

0 comments on commit eee4c84

Please sign in to comment.