diff --git a/Editor/Localization/ja.po b/Editor/Localization/ja.po index 225b814..46ad5e3 100644 --- a/Editor/Localization/ja.po +++ b/Editor/Localization/ja.po @@ -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" diff --git a/Editor/UI/Settings/AutopilotSettingsEditor.cs b/Editor/UI/Settings/AutopilotSettingsEditor.cs index ab4c232..629df44 100644 --- a/Editor/UI/Settings/AutopilotSettingsEditor.cs +++ b/Editor/UI/Settings/AutopilotSettingsEditor.cs @@ -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"); @@ -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"); @@ -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"); @@ -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 /// public override void OnInspectorGUI() @@ -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)), diff --git a/README.md b/README.md index e991642..f8a0d9d 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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. diff --git a/README_ja.md b/README_ja.md index 57745e5..bcb780b 100644 --- a/README_ja.md +++ b/README_ja.md @@ -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` などを指定します。 #### オートパイロット実行設定 @@ -389,7 +388,7 @@ SerialCompositeAgentと組み合わせることで、一連の操作を何周も たとえば通信エラーや日またぎで「タイトル画面に戻る」ボタンのような、テストシナリオ遂行上イレギュラーとなる振る舞いが含まれるゲームで利用できます。 常に、他の(実際にゲーム操作を行なう)Agentと同時に起動しておく必要があります。 -`ParallelCompositeAgent` でも実現できますが、AutopilotSettingsに `Observer Agent` として設定するほうが簡単です。 +`ParallelCompositeAgent` でも実現できますが、AutopilotSettingsの `Scene Crossing Agents` に追加するほうが簡単です。 diff --git a/Runtime/AgentDispatcher.cs b/Runtime/AgentDispatcher.cs index 36e4f06..791a9c0 100644 --- a/Runtime/AgentDispatcher.cs +++ b/Runtime/AgentDispatcher.cs @@ -24,6 +24,11 @@ public interface IAgentDispatcher : IDisposable /// Use fallback agent if true /// True: Agent dispatched bool DispatchByScene(Scene scene, bool fallback = true); + + /// + /// Dispatch scene crossing Agents. + /// + void DispatchSceneCrossingAgents(); } /// @@ -44,6 +49,7 @@ public AgentDispatcher(AutopilotSettings settings, ILogger logger, RandomFactory _settings = settings; _logger = logger; _randomFactory = randomFactory; + SceneManager.sceneLoaded += this.DispatchByScene; } @@ -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. + /// + 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(); + 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 diff --git a/Runtime/Autopilot.cs b/Runtime/Autopilot.cs index 027bf08..256aa0c 100644 --- a/Runtime/Autopilot.cs +++ b/Runtime/Autopilot.cs @@ -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) { diff --git a/Runtime/Settings/AutopilotSettings.cs b/Runtime/Settings/AutopilotSettings.cs index b87c81f..a40829e 100644 --- a/Runtime/Settings/AutopilotSettings.cs +++ b/Runtime/Settings/AutopilotSettings.cs @@ -63,8 +63,16 @@ public class AutopilotSettings : ScriptableObject /// /// Parallel running agent using on every scene, for using observer (e.g., EmergencyExitAgent) /// + [Obsolete("Use crossSceneAgents instead")] public AbstractAgent observerAgent; + /// + /// Agents running by scene crossing. + /// The specified agents will have the same lifespan as Autopilot (i.e., use DontDestroyOnLoad) + /// for specifying, e.g., ErrorHandlerAgent, UGUIEmergencyExitAgent. + /// + public List sceneCrossingAgents = new List(); + /// /// Autopilot running lifespan [sec]. When specified zero, so unlimited running /// @@ -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); + } } } diff --git a/Tests/Runtime/AgentDispatcherTest.cs b/Tests/Runtime/AgentDispatcherTest.cs index 369321c..08dbda7 100644 --- a/Tests/Runtime/AgentDispatcherTest.cs +++ b/Tests/Runtime/AgentDispatcherTest.cs @@ -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; @@ -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"; @@ -39,13 +36,6 @@ public void TearDown() _dispatcher?.Dispose(); } - private static AutopilotSettings CreateAutopilotSettings() - { - var testSettings = ScriptableObject.CreateInstance(); - testSettings.sceneAgentMaps = new List(); - return testSettings; - } - private static SpyAliveCountAgent CreateSpyAliveCountAgent(string name = nameof(SpyAliveCountAgent)) { var agent = ScriptableObject.CreateInstance(); @@ -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(); settings.sceneAgentMaps.Add(new SceneAgentMap { scenePath = TestScenePath, agent = CreateSpyAliveCountAgent(AgentName) @@ -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(); settings.fallbackAgent = CreateSpyAliveCountAgent(AgentName); SetUpDispatcher(settings); @@ -100,7 +90,7 @@ public async Task DispatchByScene_DispatchFallbackAgent() [CreateScene] public async Task DispatchByScene_NoSceneAgentMapsAndFallbackAgent_AgentIsNotDispatch() { - var settings = CreateAutopilotSettings(); + var settings = ScriptableObject.CreateInstance(); SetUpDispatcher(settings); await SceneManagerHelper.LoadSceneAsync(TestScenePath); @@ -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(); settings.sceneAgentMaps.Add(new SceneAgentMap { scenePath = TestScenePath, agent = CreateSpyAliveCountAgent(AgentName) @@ -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(); + 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(); 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(); diff --git a/Tests/Runtime/Settings/AutopilotSettingsTest.cs b/Tests/Runtime/Settings/AutopilotSettingsTest.cs index 9f3d547..bd26050 100644 --- a/Tests/Runtime/Settings/AutopilotSettingsTest.cs +++ b/Tests/Runtime/Settings/AutopilotSettingsTest.cs @@ -1,6 +1,7 @@ // Copyright (c) 2023 DeNA Co., Ltd. // This software is released under the MIT License. +using DeNA.Anjin.Agents; using DeNA.Anjin.Loggers; using DeNA.Anjin.Reporters; using DeNA.Anjin.TestDoubles; @@ -229,5 +230,46 @@ public void ConvertSlackReporterFromObsoleteSlackSettings_ExistReporter_NotGener Assert.That(settings.reporters.Count, Is.EqualTo(1)); Assert.That(settings.reporters, Does.Contain(existReporter)); } + + [Test] + public void ConvertSceneCrossingAgentsFromObsoleteObserverAgent_HasObserverAgent_IncludeToCrossSceneAgents() + { + var settings = ScriptableObject.CreateInstance(); + var legacyObserverAgent = ScriptableObject.CreateInstance(); + settings.observerAgent = legacyObserverAgent; + + var spyLogger = ScriptableObject.CreateInstance(); + settings.ConvertSceneCrossingAgentsFromObsoleteObserverAgent(spyLogger.Logger); + Assert.That(settings.sceneCrossingAgents.Count, Is.EqualTo(1)); + Assert.That(settings.sceneCrossingAgents, Has.Member(legacyObserverAgent)); + + Assert.That(spyLogger.Logs, Has.Member((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."))); + } + + [Test] + public void + ConvertSceneCrossingAgentsFromObsoleteObserverAgent_HasNotObserverAgent_NotIncludeToCrossSceneAgents() + { + var settings = ScriptableObject.CreateInstance(); + // Not set observerAgent + + settings.ConvertSceneCrossingAgentsFromObsoleteObserverAgent(Debug.unityLogger); + Assert.That(settings.sceneCrossingAgents, Is.Empty); + } + + [Test] + public void ConvertSceneCrossingAgentsFromObsoleteObserverAgent_ExistAgent_NotIncludeToCrossSceneAgents() + { + var settings = ScriptableObject.CreateInstance(); + settings.sceneCrossingAgents.Add(ScriptableObject.CreateInstance()); // already exists + settings.observerAgent = ScriptableObject.CreateInstance(); + + settings.ConvertSceneCrossingAgentsFromObsoleteObserverAgent(Debug.unityLogger); + Assert.That(settings.sceneCrossingAgents.Count, Is.EqualTo(1)); + Assert.That(settings.sceneCrossingAgents, Has.No.InstanceOf()); + } } }