Skip to content

Commit 66e2460

Browse files
committed
refactor: MaaAgentClient
1 parent 491bdd7 commit 66e2460

5 files changed

Lines changed: 246 additions & 39 deletions

File tree

src/MaaFramework.Binding.Native/MaaAgentClient.cs

Lines changed: 84 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,52 +13,71 @@ namespace MaaFramework.Binding;
1313
[DebuggerDisplay("{DebuggerDisplay,nq}")]
1414
public class MaaAgentClient : MaaDisposableHandle<MaaAgentClientHandle>, IMaaAgentClient<MaaAgentClientHandle>
1515
{
16-
private string? _debugSocketId;
16+
private bool _isConnected;
17+
private Process? _agentServerProcess;
1718

1819
[ExcludeFromCodeCoverage(Justification = "Debugger display.")]
1920
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
2021
private string DebuggerDisplay => IsInvalid
2122
? $"Invalid {GetType().Name}"
22-
: $"{GetType().Name} {{ SocketId = {_debugSocketId}, {nameof(DisposeOptions)} = {DisposeOptions} }}";
23+
: $"{GetType().Name} {{ Id = {Id}, IsConnected = {_isConnected} }}";
2324

2425
/// <summary>
2526
/// Creates a <see cref="MaaAgentClient"/> instance.
2627
/// </summary>
28+
/// <param name="identifier">The unique identifier used to communicate with the agent server.</param>
2729
/// <remarks>
2830
/// Wrapper of <see cref="MaaAgentClientCreate"/>.
2931
/// </remarks>
30-
public MaaAgentClient()
32+
public MaaAgentClient(string identifier = "")
3133
: base(invalidHandleValue: MaaAgentClientHandle.Zero)
3234
{
3335
var handle = MaaAgentClientCreate();
3436
SetHandle(handle, needReleased: true);
37+
Id = CreateSocket(identifier).ThrowIfNull();
38+
39+
if (!string.IsNullOrEmpty(identifier))
40+
_ = Id.ThrowIfNotEquals(identifier);
3541
}
3642

43+
/// <param name="identifier">The unique identifier used to communicate with the agent server.</param>
3744
/// <param name="resource">The resource.</param>
38-
/// <param name="disposeOptions">The dispose options.</param>
39-
/// <inheritdoc cref="MaaAgentClient()"/>
45+
/// <inheritdoc cref="MaaAgentClient(string)"/>
4046
[SetsRequiredMembers]
41-
public MaaAgentClient(MaaResource resource, DisposeOptions disposeOptions)
42-
: this()
47+
public MaaAgentClient(string identifier, MaaResource resource)
48+
: this(identifier)
4349
{
4450
Resource = resource;
45-
DisposeOptions = disposeOptions;
4651
}
4752

53+
/// <inheritdoc cref="MaaAgentClient(string, MaaResource)"/>
54+
[SetsRequiredMembers]
55+
public MaaAgentClient(MaaResource resource)
56+
: this("", resource)
57+
{
58+
}
59+
60+
/// <summary>
61+
/// Creates a <see cref="MaaAgentClient"/> instance.
62+
/// </summary>
63+
/// <param name="identifier">The unique identifier used to communicate with the agent server.</param>
64+
/// <param name="resource">The resource.</param>
65+
/// <returns>The <see cref="MaaAgentClient"/> instance.</returns>
66+
public static MaaAgentClient Create(string identifier, MaaResource resource)
67+
=> new(identifier, resource);
68+
69+
/// <inheritdoc cref="Create(string, MaaResource)"/>
70+
public static MaaAgentClient Create(MaaResource resource)
71+
=> new(resource);
72+
4873
/// <inheritdoc/>
49-
public required DisposeOptions DisposeOptions { get; set; }
74+
public string Id { get; }
5075

5176
/// <inheritdoc/>
5277
protected override void Dispose(bool disposing)
5378
{
54-
// Cannot destroy Instance before disposing Resource.
55-
56-
if (DisposeOptions.HasFlag(DisposeOptions.Resource))
57-
{
58-
Resource.Dispose();
59-
}
60-
6179
base.Dispose(disposing);
80+
_agentServerProcess?.Dispose();
6281
}
6382

6483
/// <inheritdoc/>
@@ -90,28 +109,71 @@ public required MaaResource Resource
90109
}
91110
}
92111

93-
/// <inheritdoc/>
112+
/// <summary>
113+
/// Creates a socket connection with the specified identifier.
114+
/// </summary>
115+
/// <param name="identifier">The specified identifier.</param>
116+
/// <returns><see langword="true"/> if the socket was created successfully; otherwise, <see langword="false"/>.</returns>
94117
/// <remarks>
95118
/// Wrapper of <see cref="MaaAgentClientCreateSocket"/>.
96119
/// </remarks>
97-
public string? CreateSocket(string identifier = "")
98-
=> MaaStringBuffer.TryGetValue(out _debugSocketId, handle
120+
protected string? CreateSocket(string identifier = "")
121+
=> MaaStringBuffer.TryGetValue(out var socketId, handle
99122
=> MaaStringBuffer.TrySetValue(handle, identifier)
100123
&& MaaAgentClientCreateSocket(Handle, handle))
101-
? _debugSocketId
124+
? socketId
102125
: null;
103126

104127
/// <inheritdoc/>
105128
/// <remarks>
106129
/// Wrapper of <see cref="MaaAgentClientConnect"/>.
107130
/// </remarks>
108131
public bool LinkStart()
109-
=> MaaAgentClientConnect(Handle);
132+
=> _isConnected = MaaAgentClientConnect(Handle);
133+
134+
/// <inheritdoc/>
135+
/// <remarks>
136+
/// Wrapper of <see cref="MaaAgentClientConnect"/>.
137+
/// </remarks>
138+
public bool LinkStart(ProcessStartInfo info)
139+
{
140+
_agentServerProcess = Process.Start(info);
141+
return LinkStart();
142+
}
143+
144+
/// <inheritdoc/>
145+
/// <remarks>
146+
/// Wrapper of <see cref="MaaAgentClientConnect"/>.
147+
/// </remarks>
148+
public bool LinkStart(IMaaAgentClient.AgentServerStartupMethod method)
149+
{
150+
ArgumentException.ThrowIfNullOrEmpty(Id);
151+
ArgumentException.ThrowIfNullOrEmpty(NativeBindingInfo.NativeAssemblyDirectory);
152+
153+
_agentServerProcess = method.Invoke(Id, NativeBindingInfo.NativeAssemblyDirectory);
154+
return LinkStart();
155+
}
110156

111157
/// <inheritdoc/>
112158
/// <remarks>
113159
/// Wrapper of <see cref="MaaAgentClientDisconnect"/>.
114160
/// </remarks>
115161
public bool LinkStop()
116-
=> MaaAgentClientDisconnect(Handle);
162+
{
163+
if (_isConnected)
164+
{
165+
_isConnected = false;
166+
if (!MaaAgentClientDisconnect(Handle))
167+
{
168+
_isConnected = true;
169+
return false;
170+
}
171+
}
172+
173+
return true;
174+
}
175+
176+
/// <inheritdoc/>
177+
public Process AgentServerProcess => _agentServerProcess
178+
?? throw new InvalidOperationException($"The agent server process is unavailable or not managed by {nameof(MaaAgentClient)}.");
117179
}

src/MaaFramework.Binding.UnitTests/MaaFramework.Binding.UnitTests.csproj

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,20 @@
55
<PackageReference Include="MSTest.TestAdapter" />
66
<PackageReference Include="MSTest.TestFramework" />
77
<PackageReference Include="SixLabors.ImageSharp" />
8-
</ItemGroup>
98

10-
<ItemGroup>
9+
<ProjectReference Include="..\MaaFramework\MaaFramework.csproj" />
10+
<ProjectReference Include="..\MaaFramework.Binding.Extensions\MaaFramework.Binding.Extensions.csproj" />
11+
1112
<None Include="..\Common\TestParam.runsettings" />
1213
<None Include="SampleResource\**">
1314
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
1415
</None>
1516
</ItemGroup>
1617

17-
<ItemGroup>
18-
<ProjectReference Include="..\MaaFramework\MaaFramework.csproj" />
19-
<ProjectReference Include="..\MaaFramework.Binding.Extensions\MaaFramework.Binding.Extensions.csproj" />
20-
</ItemGroup>
21-
2218
<PropertyGroup>
19+
<GenerateProgramFile>false</GenerateProgramFile>
2320
<NoWarn>$(NoWarn);S1121,S1656,S1199,IDE0022,IDE0060,IDE0072</NoWarn>
24-
</PropertyGroup>
2521

26-
<PropertyGroup>
2722
<DefineConstants>$(DefineConstants);MAA_NATIVE;</DefineConstants>
2823
</PropertyGroup>
2924

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using MaaFramework.Binding.Abstractions;
2+
using System.Diagnostics;
3+
4+
namespace MaaFramework.Binding.UnitTests;
5+
6+
/// <summary>
7+
/// Test <see cref="IMaaAgentClient"/> and <see cref="IMaaAgentServer"/>.
8+
/// </summary>
9+
[TestClass]
10+
// ReSharper disable InconsistentNaming
11+
public class Test_IMaaAgentClient
12+
{
13+
public static Dictionary<MaaTypes, object> NewData => new()
14+
{
15+
#if MAA_NATIVE
16+
{ MaaTypes.Native, new MaaAgentClient(new MaaResource()) },
17+
#endif
18+
};
19+
public static Dictionary<MaaTypes, object> Data { get; private set; } = default!;
20+
21+
[ClassInitialize]
22+
public static void InitializeClass(TestContext context)
23+
{
24+
Data = NewData;
25+
foreach (var data in Data.Values.Cast<IMaaAgentClient>())
26+
{
27+
Assert.IsFalse(data.IsInvalid);
28+
}
29+
}
30+
31+
[ClassCleanup]
32+
public static void CleanUpClass()
33+
{
34+
Common.DisposeData(Data.Values.Cast<IMaaDisposable>());
35+
}
36+
37+
[TestMethod]
38+
public void CreateInstances()
39+
{
40+
#if MAA_NATIVE
41+
var newId = Guid.NewGuid().ToString();
42+
using var native1 = new MaaAgentClient(/*identifier = string.Empty*/) { Resource = new MaaResource() };
43+
using var native2 = new MaaAgentClient(/*identifier = string.Empty*/ new MaaResource());
44+
// var native3 = new MaaAgentClient(newId, new MaaResource());
45+
using var native3 = MaaAgentClient.Create(newId, new MaaResource());
46+
using var native4 = MaaAgentClient.Create(new MaaResource());
47+
48+
Assert.AreNotEqual(native1.Id, native2.Id);
49+
Assert.AreEqual(newId, native3.Id);
50+
#endif
51+
}
52+
53+
[TestMethod]
54+
[MaaData(MaaTypes.All, nameof(Data))]
55+
public void Interface_Id_Resource(MaaTypes type, IMaaAgentClient maaAgentClient)
56+
{
57+
Assert.AreNotEqual(string.Empty, maaAgentClient.Id);
58+
Assert.IsNotNull(maaAgentClient.Resource);
59+
}
60+
61+
[TestMethod]
62+
[MaaData(MaaTypes.All, nameof(Data))]
63+
public void Interface_LinkStart_LinkStop_AgentServerProcess(MaaTypes type, IMaaAgentClient maaAgentClient)
64+
{
65+
_ = Assert.ThrowsException<InvalidOperationException>(() =>
66+
maaAgentClient.AgentServerProcess);
67+
var ret = maaAgentClient.LinkStart((socketId, nativeAssemblyDirectory) =>
68+
{
69+
string[] arguments =
70+
[
71+
typeof(Test_IMaaAgentServer).Assembly.Location,
72+
nativeAssemblyDirectory,
73+
Environment.CurrentDirectory,
74+
socketId
75+
];
76+
77+
return Process.Start(new ProcessStartInfo("dotnet", string.Join(' ', arguments))
78+
{
79+
UseShellExecute = false,
80+
});
81+
});
82+
Assert.IsTrue(
83+
ret);
84+
Assert.IsTrue( // double start
85+
maaAgentClient.LinkStart());
86+
Assert.IsFalse(
87+
maaAgentClient.AgentServerProcess.HasExited);
88+
89+
Assert.IsTrue(
90+
maaAgentClient.LinkStop());
91+
Assert.IsTrue( // double stop
92+
maaAgentClient.LinkStop());
93+
Task.Delay(100).Wait(); // wait for process exit
94+
Assert.IsTrue(
95+
maaAgentClient.AgentServerProcess.HasExited);
96+
}
97+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
namespace MaaFramework.Binding.UnitTests;
2+
3+
internal static class Test_IMaaAgentServer
4+
{
5+
public static int Main()
6+
{
7+
var commandLineArgs = Environment.GetCommandLineArgs();
8+
if (commandLineArgs.Length < 4)
9+
{
10+
Console.WriteLine("Call AgentMain.cs instead of this file.");
11+
return 1;
12+
}
13+
14+
var socketId = commandLineArgs[^1];
15+
var userPath = commandLineArgs[^2];
16+
var dllPath = commandLineArgs[^3];
17+
18+
NativeBindingInfo.Set(isAgentServer: true, dllPath); // First step
19+
_ = new MaaToolkit(true, userPath);
20+
var agentServer = new MaaAgentServer();
21+
_ = agentServer.Register(Custom.Recognition);
22+
_ = agentServer.Register(Custom.Action);
23+
_ = agentServer.StartUp(socketId);
24+
agentServer.Join();
25+
agentServer.ShutDown();
26+
return 0;
27+
}
28+
}

src/MaaFramework.Binding/IMaaAgentClient.cs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using MaaFramework.Binding.Abstractions;
2+
using System.Diagnostics;
23

34
namespace MaaFramework.Binding;
45

@@ -14,9 +15,9 @@ public interface IMaaAgentClient<out T> : IMaaAgentClient, IMaaDisposableHandle<
1415
public interface IMaaAgentClient : IMaaDisposable
1516
{
1617
/// <summary>
17-
/// Gets or sets whether disposes the <see cref="Resource"/> when <see cref="IDisposable.Dispose"/> was invoked.
18+
/// Gets the unique identifier used to communicate with the agent server.
1819
/// </summary>
19-
DisposeOptions DisposeOptions { get; set; }
20+
string Id { get; }
2021

2122
/// <summary>
2223
/// Gets or sets a resource that binds to the <see cref="IMaaAgentClient"/>.
@@ -26,21 +27,45 @@ public interface IMaaAgentClient : IMaaDisposable
2627
IMaaResource Resource { get; set; }
2728

2829
/// <summary>
29-
/// Creates a socket connection with the specified identifier.
30+
/// Starts the connection.
3031
/// </summary>
31-
/// <param name="identifier">The connection identifier.</param>
32-
/// <returns><see langword="true"/> if the socket was created successfully; otherwise, <see langword="false"/>.</returns>
33-
string? CreateSocket(string identifier = "");
32+
/// <returns><see langword="true"/> if the connection was started successfully; otherwise, <see langword="false"/>.</returns>
33+
bool LinkStart();
3434

3535
/// <summary>
36-
/// Starts the connection.
36+
/// Starts the agent server process using the specified <see cref="ProcessStartInfo"/> and connects to the agent server.
3737
/// </summary>
38+
/// <param name="info">The process start info.</param>
3839
/// <returns><see langword="true"/> if the connection was started successfully; otherwise, <see langword="false"/>.</returns>
39-
bool LinkStart();
40+
bool LinkStart(ProcessStartInfo info);
41+
42+
/// <summary>
43+
/// Starts the agent server process using the specified method and connects to the agent server.
44+
/// </summary>
45+
/// <param name="method">The delegate method that defines how to start the agent server process.</param>
46+
/// <returns><see langword="true"/> if the connection was started successfully; otherwise, <see langword="false"/>.</returns>
47+
bool LinkStart(AgentServerStartupMethod method);
4048

4149
/// <summary>
4250
/// Stops the connection.
4351
/// </summary>
4452
/// <returns><see langword="true"/> if the connection was stopped successfully; otherwise, <see langword="false"/>.</returns>
4553
bool LinkStop();
54+
55+
/// <summary>
56+
/// Represents a method that starts the agent server process.
57+
/// </summary>
58+
/// <param name="identifier">The unique identifier used to communicate with the agent server.</param>
59+
/// <param name="nativeAssemblyDirectory">The directory path where the <see cref="MaaFramework"/> native assemblies are located.</param>
60+
/// <returns>
61+
/// A <see cref="Process"/> instance representing the started agent server process,
62+
/// or <see langword="null"/> if the method is used to synchronize with unmanaged processes.
63+
/// </returns>
64+
delegate Process? AgentServerStartupMethod(string identifier, string nativeAssemblyDirectory);
65+
66+
/// <summary>
67+
/// Gets the agent server process managed by <see cref="IMaaAgentClient"/> from method LinkStart.
68+
/// </summary>
69+
/// <exception cref="InvalidOperationException">The process is unavailable or not managed by <see cref="IMaaAgentClient"/>.</exception>
70+
Process AgentServerProcess { get; }
4671
}

0 commit comments

Comments
 (0)