-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
af3e0e0
commit d3c7c0a
Showing
14 changed files
with
1,910 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// Copyright (c) 2023-2024 DeNA Co., Ltd. | ||
// This software is released under the MIT License. | ||
|
||
using DeNA.Anjin.Agents; | ||
using UnityEditor; | ||
using UnityEngine; | ||
|
||
namespace DeNA.Anjin.Editor.UI.Agents | ||
{ | ||
/// <summary> | ||
/// Editor GUI for TimeBombAgent | ||
/// </summary> | ||
[CustomEditor(typeof(TimeBombAgent))] | ||
public class TimeBombAgentEditor : UnityEditor.Editor | ||
{ | ||
private static readonly string s_description = L10n.Tr("Description"); | ||
private static readonly string s_descriptionTooltip = L10n.Tr("Description about this agent instance"); | ||
private static readonly string s_agent = L10n.Tr("Agent"); | ||
|
||
private static readonly string s_agentTooltip = | ||
L10n.Tr("Working Agent. If this Agent exits first, the TimeBombAgent will fail."); | ||
|
||
private static readonly string s_defuseMessage = L10n.Tr("Defuse Message"); | ||
|
||
private static readonly string s_defuseMessageTooltip = | ||
L10n.Tr("Defuse the time bomb when this message comes to the log first. Can specify regex."); | ||
|
||
/// <inheritdoc/> | ||
public override void OnInspectorGUI() | ||
{ | ||
serializedObject.Update(); | ||
|
||
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(TimeBombAgent.description)), | ||
new GUIContent(s_description, s_descriptionTooltip)); | ||
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(TimeBombAgent.agent)), | ||
new GUIContent(s_agent, s_agentTooltip)); | ||
EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(TimeBombAgent.defuseMessage)), | ||
new GUIContent(s_defuseMessage, s_defuseMessageTooltip)); | ||
|
||
serializedObject.ApplyModifiedProperties(); | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// Copyright (c) 2023-2024 DeNA Co., Ltd. | ||
// This software is released under the MIT License. | ||
|
||
using System; | ||
using System.Text.RegularExpressions; | ||
using System.Threading; | ||
using Cysharp.Threading.Tasks; | ||
using UnityEngine; | ||
|
||
namespace DeNA.Anjin.Agents | ||
{ | ||
/// <summary> | ||
/// An Agent that will fail if a defuse message is not received before the inner agent exits. | ||
/// | ||
/// For example, pass the out-game tutorial (Things that can be completed with tap operations on uGUI, such as smartphone games). | ||
/// 1. Set `UGUIMonkeyAgent` as the `Agent`. It should not be able to operate except for the advancing button. Set the `Lifespan Sec` with a little margin. | ||
/// 2. Set the log message to be output when the tutorial is completed as the `Defuse Message`. | ||
/// </summary> | ||
[CreateAssetMenu(fileName = "New TimeBombAgent", menuName = "Anjin/Time Bomb Agent", order = 17)] | ||
public class TimeBombAgent : AbstractAgent | ||
{ | ||
/// <summary> | ||
/// Working Agent. If this Agent exits first, the TimeBombAgent will fail. | ||
/// </summary> | ||
public AbstractAgent agent; | ||
|
||
/// <summary> | ||
/// Defuse the time bomb when this message comes to the log first. | ||
/// Can specify regex. | ||
/// </summary> | ||
public string defuseMessage; | ||
|
||
private Regex DefuseMessageRegex => new Regex(defuseMessage); | ||
private CancellationTokenSource _cts; | ||
|
||
private void OnEnable() | ||
{ | ||
Application.logMessageReceived += HandleDefuseMessage; | ||
} | ||
|
||
private void OnDisable() | ||
{ | ||
Application.logMessageReceived -= HandleDefuseMessage; | ||
} | ||
|
||
private void HandleDefuseMessage(string logString, string stackTrace, LogType type) | ||
{ | ||
if (DefuseMessageRegex.IsMatch(logString)) | ||
{ | ||
try | ||
{ | ||
_cts?.Cancel(); | ||
} | ||
catch (ObjectDisposedException e) | ||
{ | ||
// ignored | ||
} | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override async UniTask Run(CancellationToken token) | ||
{ | ||
Logger.Log($"Enter {this.name}.Run()"); | ||
|
||
using (var agentCts = new CancellationTokenSource()) // To cancel only the Working Agent. | ||
{ | ||
_cts = agentCts; | ||
|
||
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(token, agentCts.Token)) | ||
{ | ||
try | ||
{ | ||
agent.Logger = Logger; | ||
agent.Random = Random; // This Agent does not consume pseudo-random numbers, so passed on as is. | ||
await agent.Run(linkedCts.Token); | ||
|
||
throw new TimeoutException( | ||
$"Could not receive defuse message `{defuseMessage}` before the agent terminated."); | ||
} | ||
catch (OperationCanceledException e) | ||
{ | ||
if (token.IsCancellationRequested) // The parent was cancelled. | ||
{ | ||
throw; | ||
} | ||
|
||
Logger.Log($"Working agent {agent.name} was cancelled."); | ||
} | ||
} | ||
} | ||
|
||
Logger.Log($"Exit {this.name}.Run()"); | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
// Copyright (c) 2023-2024 DeNA Co., Ltd. | ||
// This software is released under the MIT License. | ||
|
||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Cysharp.Threading.Tasks; | ||
using DeNA.Anjin.Utilities; | ||
using NUnit.Framework; | ||
using TestHelper.Attributes; | ||
using UnityEngine; | ||
using UnityEngine.TestTools; | ||
|
||
namespace DeNA.Anjin.Agents | ||
{ | ||
[TestFixture] | ||
[UnityPlatform(RuntimePlatform.OSXEditor, RuntimePlatform.WindowsEditor, RuntimePlatform.LinuxEditor)] | ||
public class TimeBombAgentTest | ||
{ | ||
private static UGUIMonkeyAgent CreateMonkeyAgent(long lifespanSec) | ||
{ | ||
var agent = ScriptableObject.CreateInstance<UGUIMonkeyAgent>(); | ||
agent.name = nameof(UGUIMonkeyAgent); | ||
agent.lifespanSec = lifespanSec; | ||
agent.delayMillis = 100; | ||
agent.secondsToErrorForNoInteractiveComponent = 0; // Disable check | ||
agent.touchAndHoldDelayMillis = 200; | ||
return agent; | ||
} | ||
|
||
private static TimeBombAgent CreateTimeBombAgent(AbstractAgent workingAgent, string defuseMessage) | ||
{ | ||
var agent = ScriptableObject.CreateInstance<TimeBombAgent>(); | ||
agent.name = nameof(TimeBombAgent); | ||
agent.agent = workingAgent; | ||
agent.defuseMessage = defuseMessage; | ||
return agent; | ||
} | ||
|
||
[Test] | ||
public async Task Run_CancelTask_StopAgent() | ||
{ | ||
var monkeyAgent = CreateMonkeyAgent(5); | ||
var agent = CreateTimeBombAgent(monkeyAgent, "^Never match!$"); | ||
agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler); | ||
agent.Random = new RandomFactory(0).CreateRandom(); | ||
|
||
using (var cts = new CancellationTokenSource()) | ||
{ | ||
var task = agent.Run(cts.Token); | ||
await UniTask.NextFrame(); | ||
|
||
cts.Cancel(); | ||
await UniTask.NextFrame(); | ||
|
||
Assert.That(task.Status, Is.EqualTo(UniTaskStatus.Canceled)); | ||
} | ||
} | ||
|
||
[Test] | ||
[LoadScene("Packages/com.dena.anjin/Tests/TestScenes/OutGameTutorial.unity")] | ||
public async Task Run_Defuse_StopAgent() | ||
{ | ||
var monkeyAgent = CreateMonkeyAgent(5); | ||
var agent = CreateTimeBombAgent(monkeyAgent, "^Tutorial Completed!$"); | ||
agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler); | ||
agent.Random = new RandomFactory(0).CreateRandom(); | ||
|
||
LogAssert.Expect(LogType.Log, "Working agent UGUIMonkeyAgent was cancelled."); | ||
LogAssert.Expect(LogType.Log, "Exit TimeBombAgent.Run()"); | ||
|
||
using (var cts = new CancellationTokenSource()) | ||
{ | ||
await agent.Run(cts.Token); | ||
} | ||
} | ||
|
||
[Test] | ||
[LoadScene("Packages/com.dena.anjin/Tests/TestScenes/OutGameTutorial.unity")] | ||
public async Task Run_NotDefuse_ThrowsTimeoutException() | ||
{ | ||
var monkeyAgent = CreateMonkeyAgent(1); | ||
var agent = CreateTimeBombAgent(monkeyAgent, "^Never match!$"); | ||
agent.Logger = new ConsoleLogger(Debug.unityLogger.logHandler); | ||
agent.Random = new RandomFactory(0).CreateRandom(); | ||
|
||
using (var cts = new CancellationTokenSource()) | ||
{ | ||
try | ||
{ | ||
await agent.Run(cts.Token); | ||
Assert.Fail("Should throw TimeoutException"); | ||
} | ||
catch (TimeoutException e) | ||
{ | ||
Assert.That(e.Message, Is.EqualTo( | ||
"Could not receive defuse message `^Never match!$` before the agent terminated.")); | ||
} | ||
} | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.