From 464b354deb3c30f4105969bef7b2696be4ce9d91 Mon Sep 17 00:00:00 2001 From: eanzhao Date: Tue, 14 Jan 2025 11:30:04 +0800 Subject: [PATCH 1/3] feat: add ProxyGAgent for C# code. --- aevatar-framework.sln | 21 +++ .../ProxyGAgent/ProxyGAgentInitialization.cs | 8 ++ .../ProxyGAgent/ProxyGAgentState.cs | 9 ++ .../ProxyGAgent/ProxyStateLogEvent.cs | 7 + .../Aevatar.ProxyGAgent.csproj | 16 +++ .../ProxyCodeLoadContext.cs | 31 +++++ src/Aevatar.ProxyGAgent/ProxyGAgent.cs | 131 ++++++++++++++++++ src/Aevatar.ProxyGAgent/SdkStreamManager.cs | 36 +++++ .../TestGAgents/NaiveTestGAgent.cs | 7 +- ...o.cs => NaiveGAgentInitializationEvent.cs} | 2 +- .../GroupStateLogEvent.cs} | 0 .../MessageStateLogEvent.cs} | 0 .../PublishingStateLogEvent.cs} | 0 .../ReceiveMessageTestStateLogEvent.cs} | 0 .../Aevatar.GAgents.Tests.csproj | 6 + .../GAgentFactoryTests.cs | 12 +- .../Aevatar.Plugins.Test.dll | Bin 0 -> 12288 bytes .../Aevatar.GAgents.Tests/ProxyGAgentTests.cs | 43 ++++++ test/Aevatar.GAgents.Tests/ProxyTestGAgent.cs | 44 ++++++ .../Aevatar.Plugins.Test.csproj | 13 ++ test/Aevatar.Plugins.Test/TestEventHandler.cs | 36 +++++ .../Aevatar.ProxyGAgent.Sdk.csproj | 13 ++ .../IGAgentEventHandler.cs | 15 ++ .../ILogEventConsistency.cs | 9 ++ .../ProxyGAgentEvent.cs | 9 ++ 25 files changed, 458 insertions(+), 10 deletions(-) create mode 100644 src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyGAgentInitialization.cs create mode 100644 src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyGAgentState.cs create mode 100644 src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyStateLogEvent.cs create mode 100644 src/Aevatar.ProxyGAgent/Aevatar.ProxyGAgent.csproj create mode 100644 src/Aevatar.ProxyGAgent/ProxyCodeLoadContext.cs create mode 100644 src/Aevatar.ProxyGAgent/ProxyGAgent.cs create mode 100644 src/Aevatar.ProxyGAgent/SdkStreamManager.cs rename test/Aevatar.Core.Tests/TestInitializeDtos/{NaiveGAgentInitializeDto.cs => NaiveGAgentInitializationEvent.cs} (70%) rename test/Aevatar.Core.Tests/{TestGEvents/GroupGEvent.cs => TestStateLogEvents/GroupStateLogEvent.cs} (100%) rename test/Aevatar.Core.Tests/{TestGEvents/MessageGEvent.cs => TestStateLogEvents/MessageStateLogEvent.cs} (100%) rename test/Aevatar.Core.Tests/{TestGEvents/PublishingGEvent.cs => TestStateLogEvents/PublishingStateLogEvent.cs} (100%) rename test/Aevatar.Core.Tests/{TestGEvents/ReceiveMessageTestGEvent.cs => TestStateLogEvents/ReceiveMessageTestStateLogEvent.cs} (100%) create mode 100644 test/Aevatar.GAgents.Tests/ProxyGAgentPlugins/Aevatar.Plugins.Test.dll create mode 100644 test/Aevatar.GAgents.Tests/ProxyGAgentTests.cs create mode 100644 test/Aevatar.GAgents.Tests/ProxyTestGAgent.cs create mode 100644 test/Aevatar.Plugins.Test/Aevatar.Plugins.Test.csproj create mode 100644 test/Aevatar.Plugins.Test/TestEventHandler.cs create mode 100644 test/Aevatar.ProxyGAgent.Sdk/Aevatar.ProxyGAgent.Sdk.csproj create mode 100644 test/Aevatar.ProxyGAgent.Sdk/IGAgentEventHandler.cs create mode 100644 test/Aevatar.ProxyGAgent.Sdk/ILogEventConsistency.cs create mode 100644 test/Aevatar.ProxyGAgent.Sdk/ProxyGAgentEvent.cs diff --git a/aevatar-framework.sln b/aevatar-framework.sln index f292ad46..9645bb64 100644 --- a/aevatar-framework.sln +++ b/aevatar-framework.sln @@ -20,6 +20,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aevatar.TestBase", "test\Ae EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aevatar.GAgents.Tests", "test\Aevatar.GAgents.Tests\Aevatar.GAgents.Tests.csproj", "{B06AEDD8-DCE4-4F23-B730-C353E627A8E4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aevatar.Plugins.Test", "test\Aevatar.Plugins.Test\Aevatar.Plugins.Test.csproj", "{8D9680A9-0BD0-46EC-AE4C-8FE666900C2E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aevatar.ProxyGAgent.Sdk", "test\Aevatar.ProxyGAgent.Sdk\Aevatar.ProxyGAgent.Sdk.csproj", "{41D14456-6FBB-4432-BBA0-C01DA8DABE2E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aevatar.ProxyGAgent", "src\Aevatar.ProxyGAgent\Aevatar.ProxyGAgent.csproj", "{DA353205-DE92-45CA-995B-D5C492D55528}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -34,6 +40,9 @@ Global {6545F4B2-EB81-4CF6-8FEC-C5FF8E116C7C} = {96D7B45D-F041-44DD-BFCC-E17048911357} {55EF840B-F16D-4D15-9770-B3006ADB6EFB} = {96D7B45D-F041-44DD-BFCC-E17048911357} {B06AEDD8-DCE4-4F23-B730-C353E627A8E4} = {96D7B45D-F041-44DD-BFCC-E17048911357} + {8D9680A9-0BD0-46EC-AE4C-8FE666900C2E} = {96D7B45D-F041-44DD-BFCC-E17048911357} + {41D14456-6FBB-4432-BBA0-C01DA8DABE2E} = {94D0A422-54BA-48E4-9AAA-4C59BFF72F06} + {DA353205-DE92-45CA-995B-D5C492D55528} = {94D0A422-54BA-48E4-9AAA-4C59BFF72F06} EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {70485291-29F6-4FBA-BFE3-3F400184B3C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -68,5 +77,17 @@ Global {B06AEDD8-DCE4-4F23-B730-C353E627A8E4}.Debug|Any CPU.Build.0 = Debug|Any CPU {B06AEDD8-DCE4-4F23-B730-C353E627A8E4}.Release|Any CPU.ActiveCfg = Release|Any CPU {B06AEDD8-DCE4-4F23-B730-C353E627A8E4}.Release|Any CPU.Build.0 = Release|Any CPU + {8D9680A9-0BD0-46EC-AE4C-8FE666900C2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D9680A9-0BD0-46EC-AE4C-8FE666900C2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D9680A9-0BD0-46EC-AE4C-8FE666900C2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D9680A9-0BD0-46EC-AE4C-8FE666900C2E}.Release|Any CPU.Build.0 = Release|Any CPU + {41D14456-6FBB-4432-BBA0-C01DA8DABE2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41D14456-6FBB-4432-BBA0-C01DA8DABE2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41D14456-6FBB-4432-BBA0-C01DA8DABE2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41D14456-6FBB-4432-BBA0-C01DA8DABE2E}.Release|Any CPU.Build.0 = Release|Any CPU + {DA353205-DE92-45CA-995B-D5C492D55528}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA353205-DE92-45CA-995B-D5C492D55528}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA353205-DE92-45CA-995B-D5C492D55528}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA353205-DE92-45CA-995B-D5C492D55528}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyGAgentInitialization.cs b/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyGAgentInitialization.cs new file mode 100644 index 00000000..b30b7379 --- /dev/null +++ b/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyGAgentInitialization.cs @@ -0,0 +1,8 @@ +namespace Aevatar.Core.Abstractions.ProxyGAgent; + +[GenerateSerializer] +public class ProxyGAgentInitialization : InitializationEventBase +{ + [Id(0)] public byte[] EventHandlerCode { get; set; } + [Id(1)] public byte[] TransitionStateCode { get; set; } +} \ No newline at end of file diff --git a/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyGAgentState.cs b/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyGAgentState.cs new file mode 100644 index 00000000..74457bb3 --- /dev/null +++ b/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyGAgentState.cs @@ -0,0 +1,9 @@ +namespace Aevatar.Core.Abstractions.ProxyGAgent; + +[GenerateSerializer] +public class ProxyGAgentState : StateBase +{ + [Id(0)] public byte[] EventHandlerCode { get; set; } + [Id(1)] public byte[] TransitionStateCode { get; set; } + [Id(2)] public Dictionary Database { get; set; } +} \ No newline at end of file diff --git a/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyStateLogEvent.cs b/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyStateLogEvent.cs new file mode 100644 index 00000000..8a20b6dc --- /dev/null +++ b/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyStateLogEvent.cs @@ -0,0 +1,7 @@ +namespace Aevatar.Core.Abstractions.ProxyGAgent; + +[GenerateSerializer] +public class ProxyStateLogEvent : StateLogEventBase +{ + +} \ No newline at end of file diff --git a/src/Aevatar.ProxyGAgent/Aevatar.ProxyGAgent.csproj b/src/Aevatar.ProxyGAgent/Aevatar.ProxyGAgent.csproj new file mode 100644 index 00000000..8d3ceade --- /dev/null +++ b/src/Aevatar.ProxyGAgent/Aevatar.ProxyGAgent.csproj @@ -0,0 +1,16 @@ + + + + net9.0 + enable + enable + Aevatar.ProxyGAgent + + + + + + + + + diff --git a/src/Aevatar.ProxyGAgent/ProxyCodeLoadContext.cs b/src/Aevatar.ProxyGAgent/ProxyCodeLoadContext.cs new file mode 100644 index 00000000..ea376848 --- /dev/null +++ b/src/Aevatar.ProxyGAgent/ProxyCodeLoadContext.cs @@ -0,0 +1,31 @@ +using System.Reflection; +using System.Runtime.Loader; + +namespace Aevatar.ProxyGAgent; + +public class ProxyCodeLoadContext : AssemblyLoadContext +{ + private readonly ISdkStreamManager _sdkStreamManager; + + public ProxyCodeLoadContext(ISdkStreamManager sdkStreamManager) : base(true) + { + _sdkStreamManager = sdkStreamManager; + } + + protected override Assembly Load(AssemblyName assemblyName) + { + return LoadFromFolderOrDefault(assemblyName); + } + + private Assembly LoadFromFolderOrDefault(AssemblyName assemblyName) + { + if (assemblyName.Name.StartsWith("Aevatar.ProxyGAgent.Sdk")) + { + // Sdk assembly should NOT be shared + using var stream = _sdkStreamManager.GetStream(assemblyName); + return LoadFromStream(stream); + } + + return null; + } +} \ No newline at end of file diff --git a/src/Aevatar.ProxyGAgent/ProxyGAgent.cs b/src/Aevatar.ProxyGAgent/ProxyGAgent.cs new file mode 100644 index 00000000..9126d11f --- /dev/null +++ b/src/Aevatar.ProxyGAgent/ProxyGAgent.cs @@ -0,0 +1,131 @@ +using System.Reflection; +using System.Runtime.Loader; +using Aevatar.Core; +using Aevatar.Core.Abstractions; +using Aevatar.Core.Abstractions.ProxyGAgent; +using Aevatar.ProxyGAgent.Sdk; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace Aevatar.ProxyGAgent; + +[GAgent("proxy")] +public class ProxyGAgent : GAgentBase +{ + public ProxyGAgent(ILogger logger) : base(logger) + { + AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve!; + } + + private static Assembly? OnAssemblyResolve(object sender, ResolveEventArgs args) + { + var folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + if (folderPath == null) return null; + var assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll"); + if (!File.Exists(assemblyPath)) return null; + var assembly = Assembly.LoadFrom(assemblyPath); + return assembly; + } + + public override Task GetDescriptionAsync() + { + return Task.FromResult("This is a proxy GAgent for executing C# code."); + } + + public override async Task InitializeAsync(ProxyGAgentInitialization initializeDto) + { + RaiseEvent(new SetEventHandlerCode + { + EventHandlerCode = initializeDto.EventHandlerCode + }); + await ConfirmEvents(); + } + + [GenerateSerializer] + public class SetEventHandlerCode : ProxyStateLogEvent + { + [Id(0)] public byte[] EventHandlerCode { get; set; } + } + + protected override void GAgentTransitionState(ProxyGAgentState state, StateLogEventBase @event) + { + switch (@event) + { + case SetEventHandlerCode setEventHandlerCode: + state.EventHandlerCode = setEventHandlerCode.EventHandlerCode; + break; + } + } + + [AllEventHandler] + public async Task ExecuteEventHandlersAsync(EventWrapperBase eventData) + { + var assembly = Assembly.Load(State.EventHandlerCode); + var handlerTypes = GetHandlerTypes(assembly); + + foreach (var handlerType in handlerTypes) + { + var interfaceType = GetHandlerInterfaceType(handlerType); + var eventType = interfaceType.GetGenericArguments()[0]; + + if (IsMatchingEventType(eventData, eventType)) + { + var handlerInstance = Activator.CreateInstance(handlerType); + var handleMethod = interfaceType.GetMethod(nameof(IGAgentEventHandler.HandleEventAsync)); + + if (handleMethod != null) + { + await InvokeHandleMethodAsync(handleMethod, handlerInstance!, eventData, eventType); + } + } + } + } + + private IEnumerable GetHandlerTypes(Assembly assembly) + { + var handlerInterfaceType = typeof(IGAgentEventHandler<>); + return assembly.GetTypes() + .Where(t => t.GetInterfaces() + .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == handlerInterfaceType) && + t is { IsInterface: false, IsAbstract: false }); + } + + private Type GetHandlerInterfaceType(Type handlerType) + { + var handlerInterfaceType = typeof(IGAgentEventHandler<>); + return handlerType.GetInterfaces() + .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == handlerInterfaceType); + } + + private bool IsMatchingEventType(EventWrapperBase eventData, Type eventType) + { + return eventData.GetType().IsGenericType && + eventData.GetType().GetGenericTypeDefinition() == typeof(EventWrapper<>) && + ((EventWrapper)eventData).Event.GetType().FullName == eventType.FullName; + } + + private async Task InvokeHandleMethodAsync(MethodInfo handleMethod, object handlerInstance, + EventWrapperBase eventData, Type eventType) + { + dynamic eventWrapper = eventData; + var eventJson = JsonConvert.SerializeObject(eventWrapper.Event); + var eventObject = JsonConvert.DeserializeObject(eventJson, eventType); + + var result = await (Task)handleMethod.Invoke(handlerInstance, new object[] { eventObject })!; + if (!result.StateLogEventList.IsNullOrEmpty()) + { + foreach (var stateLogEvent in result.StateLogEventList) + { + RaiseEvent(stateLogEvent); + } + } + + if (!result.GAgentEventBase.IsNullOrEmpty()) + { + foreach (var eventBase in result.GAgentEventBase) + { + await PublishAsync(eventBase); + } + } + } +} \ No newline at end of file diff --git a/src/Aevatar.ProxyGAgent/SdkStreamManager.cs b/src/Aevatar.ProxyGAgent/SdkStreamManager.cs new file mode 100644 index 00000000..4af29408 --- /dev/null +++ b/src/Aevatar.ProxyGAgent/SdkStreamManager.cs @@ -0,0 +1,36 @@ +using System.Collections.Concurrent; +using System.Reflection; + +namespace Aevatar.ProxyGAgent; + +public interface ISdkStreamManager +{ + Stream GetStream(AssemblyName assemblyName); +} + +public class SdkStreamManager(string sdkDir) : ISdkStreamManager +{ + private readonly ConcurrentDictionary _cachedSdkStreams = new(); + + public Stream GetStream(AssemblyName assemblyName) + { + var path = Path.Combine(sdkDir, assemblyName.Name + ".dll"); + if (!File.Exists(path)) + { + var assembly = Assembly.Load(assemblyName); + + path = assembly.Location; + } + + if (!_cachedSdkStreams.TryGetValue(path, out var buffer)) + { + using var fs = new FileStream(path, FileMode.Open, FileAccess.Read); + var length = (int)fs.Length; + buffer = new byte[length]; + fs.ReadExactly(buffer, 0, length); + _cachedSdkStreams.TryAdd(path, buffer); + } + + return new MemoryStream(buffer); + } +} \ No newline at end of file diff --git a/test/Aevatar.Core.Tests/TestGAgents/NaiveTestGAgent.cs b/test/Aevatar.Core.Tests/TestGAgents/NaiveTestGAgent.cs index 133847bd..d838ecbd 100644 --- a/test/Aevatar.Core.Tests/TestGAgents/NaiveTestGAgent.cs +++ b/test/Aevatar.Core.Tests/TestGAgents/NaiveTestGAgent.cs @@ -10,13 +10,14 @@ public class NaiveTestGAgentState : StateBase [Id(0)] public List Content { get; set; } } +[GenerateSerializer] public class NaiveTestStateLogEvent : StateLogEventBase { [Id(0)] public Guid Id { get; set; } } [GAgent("naiveTest")] -public class NaiveTestGAgent : GAgentBase +public class NaiveTestGAgent : GAgentBase { public NaiveTestGAgent(ILogger logger) : base(logger) { @@ -27,13 +28,13 @@ public override Task GetDescriptionAsync() return Task.FromResult("This is a naive test GAgent"); } - public override async Task InitializeAsync(NaiveGAgentInitialize initialize) + public override async Task InitializeAsync(NaiveGAgentInitializationEvent initializationEvent) { if (State.Content.IsNullOrEmpty()) { State.Content = []; } - State.Content.Add(initialize.InitialGreeting); + State.Content.Add(initializationEvent.InitialGreeting); } } \ No newline at end of file diff --git a/test/Aevatar.Core.Tests/TestInitializeDtos/NaiveGAgentInitializeDto.cs b/test/Aevatar.Core.Tests/TestInitializeDtos/NaiveGAgentInitializationEvent.cs similarity index 70% rename from test/Aevatar.Core.Tests/TestInitializeDtos/NaiveGAgentInitializeDto.cs rename to test/Aevatar.Core.Tests/TestInitializeDtos/NaiveGAgentInitializationEvent.cs index 0d416ec6..0000faee 100644 --- a/test/Aevatar.Core.Tests/TestInitializeDtos/NaiveGAgentInitializeDto.cs +++ b/test/Aevatar.Core.Tests/TestInitializeDtos/NaiveGAgentInitializationEvent.cs @@ -3,7 +3,7 @@ namespace Aevatar.Core.Tests.TestInitializeDtos; [GenerateSerializer] -public class NaiveGAgentInitialize : InitializationEventBase +public class NaiveGAgentInitializationEvent : InitializationEventBase { [Id(0)] public string InitialGreeting { get; set; } } \ No newline at end of file diff --git a/test/Aevatar.Core.Tests/TestGEvents/GroupGEvent.cs b/test/Aevatar.Core.Tests/TestStateLogEvents/GroupStateLogEvent.cs similarity index 100% rename from test/Aevatar.Core.Tests/TestGEvents/GroupGEvent.cs rename to test/Aevatar.Core.Tests/TestStateLogEvents/GroupStateLogEvent.cs diff --git a/test/Aevatar.Core.Tests/TestGEvents/MessageGEvent.cs b/test/Aevatar.Core.Tests/TestStateLogEvents/MessageStateLogEvent.cs similarity index 100% rename from test/Aevatar.Core.Tests/TestGEvents/MessageGEvent.cs rename to test/Aevatar.Core.Tests/TestStateLogEvents/MessageStateLogEvent.cs diff --git a/test/Aevatar.Core.Tests/TestGEvents/PublishingGEvent.cs b/test/Aevatar.Core.Tests/TestStateLogEvents/PublishingStateLogEvent.cs similarity index 100% rename from test/Aevatar.Core.Tests/TestGEvents/PublishingGEvent.cs rename to test/Aevatar.Core.Tests/TestStateLogEvents/PublishingStateLogEvent.cs diff --git a/test/Aevatar.Core.Tests/TestGEvents/ReceiveMessageTestGEvent.cs b/test/Aevatar.Core.Tests/TestStateLogEvents/ReceiveMessageTestStateLogEvent.cs similarity index 100% rename from test/Aevatar.Core.Tests/TestGEvents/ReceiveMessageTestGEvent.cs rename to test/Aevatar.Core.Tests/TestStateLogEvents/ReceiveMessageTestStateLogEvent.cs diff --git a/test/Aevatar.GAgents.Tests/Aevatar.GAgents.Tests.csproj b/test/Aevatar.GAgents.Tests/Aevatar.GAgents.Tests.csproj index 46c0e53b..b37f1e44 100644 --- a/test/Aevatar.GAgents.Tests/Aevatar.GAgents.Tests.csproj +++ b/test/Aevatar.GAgents.Tests/Aevatar.GAgents.Tests.csproj @@ -26,7 +26,9 @@ + + @@ -34,6 +36,10 @@ Always + + + Always + diff --git a/test/Aevatar.GAgents.Tests/GAgentFactoryTests.cs b/test/Aevatar.GAgents.Tests/GAgentFactoryTests.cs index 5c55b5a7..6219faaf 100644 --- a/test/Aevatar.GAgents.Tests/GAgentFactoryTests.cs +++ b/test/Aevatar.GAgents.Tests/GAgentFactoryTests.cs @@ -42,7 +42,7 @@ public async Task CreateGAgentByGenericTypeTest() } { - var gAgent = await _gAgentFactory.GetGAgentAsync>(); + var gAgent = await _gAgentFactory.GetGAgentAsync>(); gAgent.ShouldNotBeNull(); Should.NotThrow(() => gAgent.GetPrimaryKey()); gAgent.GetGrainId().ShouldBe(GrainId.Create("aevatar/naiveTest", gAgent.GetPrimaryKey().ToString("N"))); @@ -63,14 +63,14 @@ public async Task CreateGAgentWithInitializeMethodTest() { // Arrange & Act. var guid = Guid.NewGuid(); - var gAgent = await _gAgentFactory.GetGAgentAsync>(guid, - new NaiveGAgentInitialize + var gAgent = await _gAgentFactory.GetGAgentAsync>(guid, + new NaiveGAgentInitializationEvent { InitialGreeting = "Test" }); var initializeDtoType = await gAgent.GetInitializationTypeAsync(); - initializeDtoType.ShouldBe(typeof(NaiveGAgentInitialize)); + initializeDtoType.ShouldBe(typeof(NaiveGAgentInitializationEvent)); await TestHelper.WaitUntilAsync(_ => CheckState(gAgent), TimeSpan.FromSeconds(20)); @@ -94,7 +94,7 @@ public async Task CreateGAgentByAliasTest() } { - var gAgent = await _gAgentFactory.GetGAgentAsync("naiveTest", initializeDto: new NaiveGAgentInitialize + var gAgent = await _gAgentFactory.GetGAgentAsync("naiveTest", initializeDto: new NaiveGAgentInitializationEvent { InitialGreeting = "Test" }); @@ -119,7 +119,7 @@ public async Task GetAvailableGAgentTypesTest() availableGAgents.Count.ShouldBeGreaterThan(20); } - private async Task CheckState(IStateGAgent gAgent) + private async Task CheckState(IStateGAgent gAgent) { var state = await gAgent.GetStateAsync(); return !state.Content.IsNullOrEmpty(); diff --git a/test/Aevatar.GAgents.Tests/ProxyGAgentPlugins/Aevatar.Plugins.Test.dll b/test/Aevatar.GAgents.Tests/ProxyGAgentPlugins/Aevatar.Plugins.Test.dll new file mode 100644 index 0000000000000000000000000000000000000000..579f095db145912390ea5d0ab5bb23de28fe1165 GIT binary patch literal 12288 zcmeHNYj7OZl|Hw-r{^JQERB#~7<+8XX3&_iELp?`+gO%lTYzk12@^1hJep}s6OX3l z=^2A?1ma;TA+SkmO$rv0z>?Zshh+nmY+e+bhf}E}6)2otQ%U8qcp<5fup!y}$g)}R ze&=-e%t*G#qq4t}9^KRTJnp&Y+;i@uyESm*he#(P1Lf>lqR->b*H(dV52nG6F8gAX zJ`;X!`RCO&&n+Js%Q^9hqCHwnkH<6VLcuP@N33|UT!`li@!s8o@o_tAr5YL{t)A*V zeMHwNonAco^N0Q3UZNH8I@L~;0L65v+IHOIC}^uL=c2Z(;k12OxWCaMSRsa~R?*}S*I<3ynvehB!^8a!39ZYco|tp)&3 zeD&-GCRZF*rHW25159i?fC@X>hEj8FMYvK$D{n)PZKY`x)|Eo3xwaB*t3joD2LAKK zwrLcg7humz7ZE9;{^6jxU88&aX1cwEDDnb^RZBo;#9j)p+M}AYsmoXv?#+}HYH&6+ z&a#2?QZeea8roK9iRF-{w!hI{2>ZM#_-AN|70e(_(-XjxQy4R&?IWZe<%(|DxFtzX ztYRsBzF{vwIws&vlG>Wvbt=G5v}>w1fb~h5ABm~AT41+I4v-#G3D}t=+%JKi`p_CJ zd4&lKL5#dJV$Od{HH6nJLVv*oh9qe(G1sgo-3UaD1coJ97ewbN;M!64KY=M0Ax#Gt1H+!KV*=#d zCS2uQ^L>TqO*BtSbV3TMBD#^NgGhaHABbJt6!*(;N4HVa>%+4IKCU8X5qxVl^u#+L z)MO+!a2riW4QZRCCZir*Lq~&XrI6?AKKP1}ZNeDE$c{2Iw8k(SlH2szeqRBN%|?^4 zCSvIJJV-VqFOpvCFuwL?x4)o~fNLfN8$a;HS}cE27F3428Xg+d(16 z-;Gb(V$sUUwSt+PT+rzz-V}^IH@T1nZgO$uJjt~f%Dv=*zy6qe`Q^3wQ{>l$?D`*< zUlk93cYg5@)Z|wa#`SmR7o41(Uoj5$T=^w^%*Zb+H|NiNvtr?Hi&i2lA8-ZYd%LD!ZPOiKSdmcRB1fi9KrDCoy>afJ!@EqD{<#mJf0_xnw z${pAB!ykGHIKoORx8-eKfBC{1tgc`1w{>A`ZS+5?laLhfR5n`fpUZNTi&2miqMJ=Y zq-2Xmd)d%q#psnpi z+xO5{%qZO)VtA9(51}5T6Vmoabxi*peI#O1n|SwbqbnStPXvyIWArus5!k#C6q8Qt zD*z|pdxYwZW8uYg3%vo~pAqhhpqtc51<+U5G5sZtTQ!DmgBLx-_4>dHgzRhd3uV%i z8vFH3Xd|rsn0Rzby!)ZR&k6ig@K|suJrDWCv<*6!(lYf63CIzFl5 z0Xt1M)LnV&t(!x{wZ5zHhke~{cSSddzvsga^;tnLKn^a8;w z!9uiHu)9KI&>5zSNJl$1jIm|Nz&-kt+H(4N=xMN)lK&ytJqynwXEgeG1$$Z3D7GAe>L==6^Hoi!4IXyf z{EZf%&v@7#EuaUfb%iT=t2tkgwF1?bjF5-5z$Unb)!Y zuJZdksxPGx#ynf%G+E&t)0fkO!ed)Lq_@(?E0UOj^bal;`gijI@SgIp*UU!nzEC5% zioRMSxr)A3Be{zHwMKF^{XnpvsIc~szM3vx?OSGIooSzeZ4&H^I$g)SZ+qCUwFhNI zQuL^aA*HRHlJp87PVZQX6t&gVUHZq6PC9kzydK>xtz4P1$S=mCrP0r@qWaptfx1S| z1L`yA|KGj1wz+T&)?)VQI#x%W-i3mLJX!D{hH?yLKFa;r7ZyWeDayN0R-jmzVXIM& zp|qjgk6D|-T=$^gz#9an1s(=$p-DhR zcK}{U9|l}b8`as+By|hy2Q=vhfjPk2=rG_h;oeJkpx#377w%&c-^Uck^$RYA?umSy za`Ywb1ophe+85|Cx)hYh=yQM%p)Zq82t1{ZYG1-Wey{l=y_X!_q%&%d_DfKXn?d!e zS{!OoZ_w+JR>1pJLY+~!nrqZ4bvn|bPS64UN|mE!^}Xs>YNK|IdM~xq-30h`tl_6r zqi&(qN3~XPKd4Qkz7#9T8?+6r?xx3s9|b*B_c*jA^e5CM%B+75+LF+A31<2C0spJ+ zWwjjj*VVP+*M0Q7-l^S3gLRt$n}Szq4+;G^d{GqE*sn*#uScjHw6(ByySiCBjuE&M zl3&!{hgJ=tZgV`VQbq`aWQi78xn*C!K&- z&=$ZRx*G6mp$`h&@4?$a-%lSC_^iP13jCSCvjXR%uMBktddQ(~10JQH2KvQ@j8HN{ zxl8JIN&RuDKQ8r?Qa>s6GxRVW37nzFVgJiQe_7~CFxz5P75B1IK_B1;=vRP$ z4VfOH^a!O#D0fL)OZzl^M|(+QJ)|?-D)4~7M|IZosQwJy0eBMJxbc0!SBwvU{uW)X z%Id4?e^raNN*mI`n1guE!y@%d^Y1Vt8EV+Suq)y;p@Xv+Gf-j71DIV~G1B!oKXqY+ zN&#NMqfLW!Bc+kghv`G~G(AVJ(nnOA_HJ!dGp2ct*Ir{-b8sN4Tn}rfxbqn_qR)y* ztsK#h`RFxE+au@5eL`#EW8EydH?Sto?BikWd(yTZndS0c#Hvcbo)JO=x%fN}zsSRv zdH8Y45NflER-C5P(^Te=owX}7u6)n4tiBB5nfPR zD!iyxrnokVau@YsCyycuQH*{RD;=J%`P#HIUC8FGz9Uwl)a^_bGF!64!|l|+J!j>! zJ$Q?kIk1lUx1}A+M~2eQA<*_(>8w>mMOra&z$%T|*=^-q-eq{LCUPRy-)mVDJ+_bS z=pIFn(yeO&tLooYK6ub7USG_WT-75^shG}`h9)PhL93Wc=X1BZ%DOY9+>vz2=2kn? z&e)(;LhYKI!#A;Xcu&4Onkx)hj@t{9x$|@B6e558M7<>$qRmiwOsfiD{&p0vy zB)WD=oLs`-q*JoSQ$2P*Z;4}0YKK+8z-BOrGt+>&vsvmZIOU>seXeM61cuV1Fe^8X z;ACm2I4NPOSR$Q~(A?^#9YxD3;el(*q2b|e>C7QiUCu#vi*{Hg#PmomV^OZ)l+y4< zlB%Ln#-rb<;(N=5Qf^#qam{5NY}xjrJ?>FF7d=txm+TxYrAyX8Iy06lScFuWfurkM zrk7>c6|7q(Fi2LGJD^_6@$+vMrxG-g8_w{Q5jO?*3?z%^*E*g??hy4AvVySCK)R4S zh&0_(w2$O4b%f#Ou;hk^OO{m$oH{j=$Q3GD@CC+>lt)LQsA%8pq(}0W_)C4+945>x z;-)R@hKFScs6X3X!V~gH87g}0@rhjCDoT1lXAP@DViT(^+)lZ-Ds5LepPxxN$F!>O zEc2?2ZpX34NAiI+c6;ftfGSj;ap~a;h7&>TV+j}d zn?_9wFFf33x-eN~cymzbrQAp^pDWdJu`caP=gSry5m@oAcCJxI28?FT>XbLWSWswp zF>j>{PRegCmZy61c7eG(IsIiw7TXHaZDIRJ7gHF3TPEFklp4$)k`}y~kzLGT`$*^g zzS#}CfLV;S+sad=z3ehhiuajRHlLrvAYMb!RmslSc`>9P3$dqqjwXYa!>J|~`E}n* ztLRha@V_?C&adD13mN2X2D~!%-IB2;IEiH`mUYbCKnP0<)+Xwgg~?y45Fade6>QU% z((v$zdjO!}5v+Ec+3p7EZ&JLBQO#k1Dm%1y4)6`*j(Tn0t=y#uYmz&8to{P_EZ-}C zcF196rev0+jMJv_q9fc5@N`qMj z&k#9xtEbvkX14^nVv##ub1C+@Je%BL&Lw%CV9X_2je_H)xPQKzt3~BNg=f^eixXR%HVd$~}{8*)$J{e_8g2`4CqcDcm20sDxx zi@!S~Sd-p*%`2&Vi*`L_4)Rn^ig+5a@D3q|ucV_y@7M_}kDpC(ItZ>!OiWGj-XaRZ6}`c3Y3MU1u3E( zZ_NL<6(^tEG5z!dTL&JT`Y0K3r9yg~Q~;HxCPt%Njdca$nuGqYE*axTM`>LA}W>(f%l(icA}wOX{xcMsjQab{R4P- z73E?LjS8=F-Fi403I@VWQ<|pv4JM^Q6k1t`vBst;jE;s9Mq4fFk(;KBAleymV$(D& z))niT*QgB5*B$Fx5{m08>@zBzf)NP@VqIFS6@Sr24Gy?URnwFi0ZokMz5~!c{U+4I zr#F|(W6>J3AiI=et*oq7bZH^Dtu1L>${r&w?Fu?ri>8(=34k>H;d+>a@sYm=JTnp+ zf`xt)=vP!Gn30GvU1LoNBdB8Sp_ZoU@1gN?cm|hhGjR{f>#p4m)i+f$g@bwsat z2*@2A#}kO!9bqlHBnVrYsaZu3UXR3aZ>BnzAi{3!Plukma`^V9(~-+V-#*%W_PI-c zc&%>6x=?b430E{Ts+plM;_8|OMk~sih>}+${5C=I_*#MEeuOzCVy!;Lqa15(Y*$82 zd=M23B*<`)04Y!QV(&9I3yM+y1?HUXDp_K0S?F!9$(|o*E)O< z#E+;`QA&_x+Pci@=*n0dI@Wb{Zm_yCBbko&Ojk#?b7Q)rV|`}hx()5wk&zB!Ul#ej zoV)!B+dp`nhrL))i_iAAIXRbN$y8MDv$!R`7Al(OQ~ak^nvb{6J`qp!R(hOIv$RCf z0^h8feMb(iv`x`wug$CzM9r>|db=G3->R?_2jm-9x1vpzu4>P?vyW@ZIo==<0`6WH z;>FWU!-5E;8ypC^V(1kYQ1=}gBi;KqKTB$0kB0i4C`KR3#msRpZo%JDnRE|$_tfxM zLOvUF#k=+n_6|Pr{5u!D@~O?;pM2lG-#mHyXOpaB?R5^G>ek{h;?}XWy_N@WZQ6Z2 zxb&d^n7Fot{dn#9Mr5r$@}9Mlcxww*37(x-Dm9TEp~0Qq9bK1^XT;$)&j>iWd3fyf z-IwIYZ#r3b_+L)L8UK2n>v12Oc|TJlM}(tUu{WO|NaqSP?qrZ$mOPMnZO*QOn%P4# zQ{P{(Ka&yQ@5_XD@-#iuVRtfO%{+(BaywDpd4T9XyxaG$`}Fmw?!$ZgVZc7xi&{V4 zT<-$j54hd^uSOK+O*+pwzT7+i-wIy_3eWS}s~3oZ)-+Ct+i}9=vtmC^jR$e6 za?l`*cR8n3XAz`EZz zPIuB;_%l=2?2)aGYZ7)_bQwwqzF=i=-+sD5%@o( C%R{*U literal 0 HcmV?d00001 diff --git a/test/Aevatar.GAgents.Tests/ProxyGAgentTests.cs b/test/Aevatar.GAgents.Tests/ProxyGAgentTests.cs new file mode 100644 index 00000000..fd4205a9 --- /dev/null +++ b/test/Aevatar.GAgents.Tests/ProxyGAgentTests.cs @@ -0,0 +1,43 @@ +using Aevatar.Core; +using Aevatar.Core.Abstractions; +using Aevatar.Core.Abstractions.ProxyGAgent; +using Aevatar.Core.Tests.TestGAgents; +using Aevatar.Plugins.Test; +using Shouldly; + +namespace Aevatar.GAgents.Tests; + +public class ProxyGAgentTests : AevatarGAgentsTestBase +{ + private readonly IGAgentFactory _gAgentFactory; + + public ProxyGAgentTests() + { + _gAgentFactory = GetRequiredService(); + } + + [Fact] + public async Task ProxyGAgentEventHandlerTest() + { + var code = await File.ReadAllBytesAsync("ProxyGAgentPlugins/Aevatar.Plugins.Test.dll"); + var proxyGAgent = await _gAgentFactory.GetGAgentAsync("proxy", initializeDto: new ProxyGAgentInitialization + { + EventHandlerCode = code + }); + var proxyTestGAgent = await _gAgentFactory.GetGAgentAsync>(); + var publishingGAgent = await _gAgentFactory.GetGAgentAsync(); + await publishingGAgent.RegisterAsync(proxyGAgent); + await publishingGAgent.RegisterAsync(proxyTestGAgent); + await publishingGAgent.PublishEventAsync(new PluginTestEvent1()); + await TestHelper.WaitUntilAsync(_ => CheckCount(proxyTestGAgent, 1), TimeSpan.FromSeconds(30)); + + var state = await proxyTestGAgent.GetStateAsync(); + state.Content.Count.ShouldBe(1); + } + + private async Task CheckCount(IStateGAgent gAgent, int expectedCount) + { + var state = await gAgent.GetStateAsync(); + return state.Content.Count == expectedCount; + } +} diff --git a/test/Aevatar.GAgents.Tests/ProxyTestGAgent.cs b/test/Aevatar.GAgents.Tests/ProxyTestGAgent.cs new file mode 100644 index 00000000..aaa0b88c --- /dev/null +++ b/test/Aevatar.GAgents.Tests/ProxyTestGAgent.cs @@ -0,0 +1,44 @@ +using Aevatar.Core; +using Aevatar.Core.Abstractions; +using Aevatar.Core.Tests.TestInitializeDtos; +using Aevatar.Plugins.Test; +using Aevatar.ProxyGAgent.Sdk; +using Microsoft.Extensions.Logging; + +namespace Aevatar.GAgents.Tests; + +[GenerateSerializer] +public class ProxyTestGAgentState : StateBase +{ + [Id(0)] public List Content { get; set; } +} + +[GenerateSerializer] +public class ProxyTestStateLogEvent : StateLogEventBase +{ + [Id(0)] public Guid Id { get; set; } +} + +[GAgent("proxyTest")] +public class ProxyTestGAgent : GAgentBase +{ + public ProxyTestGAgent(ILogger logger) : base(logger) + { + } + + public override Task GetDescriptionAsync() + { + return Task.FromResult("This is a proxy test GAgent"); + } + + public async Task HandleEventAsync(ProxyGAgentEvent eventData) + { + if (State.Content.IsNullOrEmpty()) + { + State.Content = []; + } + + var data = eventData.EventData["Greeting"].ToString()!; + State.Content.Add(data); + } +} \ No newline at end of file diff --git a/test/Aevatar.Plugins.Test/Aevatar.Plugins.Test.csproj b/test/Aevatar.Plugins.Test/Aevatar.Plugins.Test.csproj new file mode 100644 index 00000000..b81c53e5 --- /dev/null +++ b/test/Aevatar.Plugins.Test/Aevatar.Plugins.Test.csproj @@ -0,0 +1,13 @@ + + + + net9.0 + enable + enable + + + + + + + diff --git a/test/Aevatar.Plugins.Test/TestEventHandler.cs b/test/Aevatar.Plugins.Test/TestEventHandler.cs new file mode 100644 index 00000000..8ab1d8e3 --- /dev/null +++ b/test/Aevatar.Plugins.Test/TestEventHandler.cs @@ -0,0 +1,36 @@ +using Aevatar.Core.Abstractions; +using Aevatar.ProxyGAgent.Sdk; + +namespace Aevatar.Plugins.Test; + +[GenerateSerializer] +public class PluginTestEvent1 : EventBase +{ + [Id(0)] public string Greeting { get; set; } +} + +[GenerateSerializer] +public class PluginTestEvent2 : EventBase +{ + [Id(0)] public string Greeting { get; set; } +} + +public class TestEventHandler : IGAgentEventHandler +{ + public async Task HandleEventAsync(PluginTestEvent1 event1Base) + { + return new EventHandleResult + { + GAgentEventBase = + [ + new ProxyGAgentEvent + { + EventData = new Dictionary + { + ["Greeting"] = "Hello from TestEventHandler" + } + } + ] + }; + } +} \ No newline at end of file diff --git a/test/Aevatar.ProxyGAgent.Sdk/Aevatar.ProxyGAgent.Sdk.csproj b/test/Aevatar.ProxyGAgent.Sdk/Aevatar.ProxyGAgent.Sdk.csproj new file mode 100644 index 00000000..d6385534 --- /dev/null +++ b/test/Aevatar.ProxyGAgent.Sdk/Aevatar.ProxyGAgent.Sdk.csproj @@ -0,0 +1,13 @@ + + + + net9.0 + enable + enable + + + + + + + diff --git a/test/Aevatar.ProxyGAgent.Sdk/IGAgentEventHandler.cs b/test/Aevatar.ProxyGAgent.Sdk/IGAgentEventHandler.cs new file mode 100644 index 00000000..5ca5a603 --- /dev/null +++ b/test/Aevatar.ProxyGAgent.Sdk/IGAgentEventHandler.cs @@ -0,0 +1,15 @@ +using Aevatar.Core.Abstractions; +using Aevatar.Core.Abstractions.ProxyGAgent; + +namespace Aevatar.ProxyGAgent.Sdk; + +public interface IGAgentEventHandler where T : EventBase +{ + Task HandleEventAsync(T eventBase); +} + +public class EventHandleResult +{ + public List StateLogEventList { get; set; } + public List GAgentEventBase { get; set; } +} \ No newline at end of file diff --git a/test/Aevatar.ProxyGAgent.Sdk/ILogEventConsistency.cs b/test/Aevatar.ProxyGAgent.Sdk/ILogEventConsistency.cs new file mode 100644 index 00000000..19f64913 --- /dev/null +++ b/test/Aevatar.ProxyGAgent.Sdk/ILogEventConsistency.cs @@ -0,0 +1,9 @@ +using Aevatar.Core.Abstractions.ProxyGAgent; + +namespace Aevatar.ProxyGAgent.Sdk; + +public interface ILogEventConsistency +{ + ProxyGAgentState State { set; } + Task Apply(ProxyStateLogEvent eventData); +} \ No newline at end of file diff --git a/test/Aevatar.ProxyGAgent.Sdk/ProxyGAgentEvent.cs b/test/Aevatar.ProxyGAgent.Sdk/ProxyGAgentEvent.cs new file mode 100644 index 00000000..f7cd4b36 --- /dev/null +++ b/test/Aevatar.ProxyGAgent.Sdk/ProxyGAgentEvent.cs @@ -0,0 +1,9 @@ +using Aevatar.Core.Abstractions; + +namespace Aevatar.ProxyGAgent.Sdk; + +[GenerateSerializer] +public class ProxyGAgentEvent : EventBase +{ + [Id(0)] public Dictionary EventData { get; set; } +} \ No newline at end of file From 7915c1f0a4d1607ea5cabbfdf6de6e9b157438ec Mon Sep 17 00:00:00 2001 From: eanzhao Date: Tue, 14 Jan 2025 18:13:49 +0800 Subject: [PATCH 2/3] feat: implemented proxy gagent log event consistency part. --- .../ProxyGAgent/ProxyGAgentInitialization.cs | 3 +- .../ProxyGAgent/ProxyGAgentState.cs | 5 +- .../ProxyGAgent/ProxyStateLogEvent.cs | 2 +- src/Aevatar.Core/GAgentBase.Subscribe.cs | 3 +- src/Aevatar.ProxyGAgent/ProxyGAgent.cs | 47 ++++++++++++++---- .../Aevatar.Plugins.Test.dll | Bin 12288 -> 11264 bytes .../Aevatar.GAgents.Tests/ProxyGAgentTests.cs | 25 +++++++--- test/Aevatar.Plugins.Test/TestEventHandler.cs | 23 +++++---- .../TestLogEventConsistency.cs | 15 ++++++ .../ILogEventConsistency.cs | 3 +- 10 files changed, 93 insertions(+), 33 deletions(-) create mode 100644 test/Aevatar.Plugins.Test/TestLogEventConsistency.cs diff --git a/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyGAgentInitialization.cs b/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyGAgentInitialization.cs index b30b7379..60e410fe 100644 --- a/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyGAgentInitialization.cs +++ b/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyGAgentInitialization.cs @@ -3,6 +3,5 @@ namespace Aevatar.Core.Abstractions.ProxyGAgent; [GenerateSerializer] public class ProxyGAgentInitialization : InitializationEventBase { - [Id(0)] public byte[] EventHandlerCode { get; set; } - [Id(1)] public byte[] TransitionStateCode { get; set; } + [Id(0)] public byte[] PluginCode { get; set; } } \ No newline at end of file diff --git a/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyGAgentState.cs b/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyGAgentState.cs index 74457bb3..9855c4e5 100644 --- a/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyGAgentState.cs +++ b/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyGAgentState.cs @@ -3,7 +3,6 @@ namespace Aevatar.Core.Abstractions.ProxyGAgent; [GenerateSerializer] public class ProxyGAgentState : StateBase { - [Id(0)] public byte[] EventHandlerCode { get; set; } - [Id(1)] public byte[] TransitionStateCode { get; set; } - [Id(2)] public Dictionary Database { get; set; } + [Id(0)] public byte[]? PluginCode { get; set; } + [Id(1)] public Dictionary? Database { get; set; } } \ No newline at end of file diff --git a/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyStateLogEvent.cs b/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyStateLogEvent.cs index 8a20b6dc..187fa3f1 100644 --- a/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyStateLogEvent.cs +++ b/src/Aevatar.Core.Abstractions/ProxyGAgent/ProxyStateLogEvent.cs @@ -3,5 +3,5 @@ namespace Aevatar.Core.Abstractions.ProxyGAgent; [GenerateSerializer] public class ProxyStateLogEvent : StateLogEventBase { - + [Id(0)] public Dictionary Data { get; set; } } \ No newline at end of file diff --git a/src/Aevatar.Core/GAgentBase.Subscribe.cs b/src/Aevatar.Core/GAgentBase.Subscribe.cs index 587a1a44..f6625122 100644 --- a/src/Aevatar.Core/GAgentBase.Subscribe.cs +++ b/src/Aevatar.Core/GAgentBase.Subscribe.cs @@ -28,7 +28,8 @@ protected sealed override void TransitionState(TState state, StateLogEventBase @event) diff --git a/src/Aevatar.ProxyGAgent/ProxyGAgent.cs b/src/Aevatar.ProxyGAgent/ProxyGAgent.cs index 9126d11f..d07ca745 100644 --- a/src/Aevatar.ProxyGAgent/ProxyGAgent.cs +++ b/src/Aevatar.ProxyGAgent/ProxyGAgent.cs @@ -34,33 +34,53 @@ public override Task GetDescriptionAsync() public override async Task InitializeAsync(ProxyGAgentInitialization initializeDto) { - RaiseEvent(new SetEventHandlerCode + RaiseEvent(new SetPluginCode { - EventHandlerCode = initializeDto.EventHandlerCode + PluginCode = initializeDto.PluginCode }); await ConfirmEvents(); } [GenerateSerializer] - public class SetEventHandlerCode : ProxyStateLogEvent + public class SetPluginCode : ProxyStateLogEvent { - [Id(0)] public byte[] EventHandlerCode { get; set; } + [Id(0)] public byte[] PluginCode { get; set; } } protected override void GAgentTransitionState(ProxyGAgentState state, StateLogEventBase @event) { - switch (@event) + if (@event is SetPluginCode setPluginCode) { - case SetEventHandlerCode setEventHandlerCode: - state.EventHandlerCode = setEventHandlerCode.EventHandlerCode; - break; + State.PluginCode = setPluginCode.PluginCode; + return; + } + + if (State.PluginCode.IsNullOrEmpty()) + { + return; + } + + var assembly = Assembly.Load(State.PluginCode!); + var logEventConsistencyTypes = GetLogEventConsistencyTypes(assembly); + if (logEventConsistencyTypes == null) return; + foreach (var logEventConsistencyType in logEventConsistencyTypes) + { + var logEventConsistencyInstance = Activator.CreateInstance(logEventConsistencyType)!; + if (logEventConsistencyInstance is not ILogEventConsistency logEventConsistency) continue; + logEventConsistency.State = State; + logEventConsistency.Apply((ProxyStateLogEvent)@event); } } [AllEventHandler] public async Task ExecuteEventHandlersAsync(EventWrapperBase eventData) { - var assembly = Assembly.Load(State.EventHandlerCode); + if (State.PluginCode.IsNullOrEmpty()) + { + return; + } + + var assembly = Assembly.Load(State.PluginCode!); var handlerTypes = GetHandlerTypes(assembly); foreach (var handlerType in handlerTypes) @@ -90,6 +110,15 @@ private IEnumerable GetHandlerTypes(Assembly assembly) t is { IsInterface: false, IsAbstract: false }); } + private IEnumerable? GetLogEventConsistencyTypes(Assembly? assembly) + { + if (assembly == null) return null; + var handlerInterfaceType = typeof(ILogEventConsistency); + return assembly.GetTypes() + .Where(t => handlerInterfaceType.IsAssignableFrom(t) + && t is { IsInterface: false, IsAbstract: false }); + } + private Type GetHandlerInterfaceType(Type handlerType) { var handlerInterfaceType = typeof(IGAgentEventHandler<>); diff --git a/test/Aevatar.GAgents.Tests/ProxyGAgentPlugins/Aevatar.Plugins.Test.dll b/test/Aevatar.GAgents.Tests/ProxyGAgentPlugins/Aevatar.Plugins.Test.dll index 579f095db145912390ea5d0ab5bb23de28fe1165..80488d943db9a3af87c79043834a13b4de639739 100644 GIT binary patch delta 4398 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 diff --git a/test/Aevatar.GAgents.Tests/ProxyGAgentTests.cs b/test/Aevatar.GAgents.Tests/ProxyGAgentTests.cs index fd4205a9..7050b059 100644 --- a/test/Aevatar.GAgents.Tests/ProxyGAgentTests.cs +++ b/test/Aevatar.GAgents.Tests/ProxyGAgentTests.cs @@ -19,25 +19,36 @@ public ProxyGAgentTests() [Fact] public async Task ProxyGAgentEventHandlerTest() { + // Arrange. var code = await File.ReadAllBytesAsync("ProxyGAgentPlugins/Aevatar.Plugins.Test.dll"); var proxyGAgent = await _gAgentFactory.GetGAgentAsync("proxy", initializeDto: new ProxyGAgentInitialization { - EventHandlerCode = code + PluginCode = code }); var proxyTestGAgent = await _gAgentFactory.GetGAgentAsync>(); var publishingGAgent = await _gAgentFactory.GetGAgentAsync(); + + // Act. await publishingGAgent.RegisterAsync(proxyGAgent); await publishingGAgent.RegisterAsync(proxyTestGAgent); - await publishingGAgent.PublishEventAsync(new PluginTestEvent1()); + await publishingGAgent.PublishEventAsync(new PluginTestEvent()); await TestHelper.WaitUntilAsync(_ => CheckCount(proxyTestGAgent, 1), TimeSpan.FromSeconds(30)); - - var state = await proxyTestGAgent.GetStateAsync(); - state.Content.Count.ShouldBe(1); + + // Assert. + var proxyTestGAgentState = await proxyTestGAgent.GetStateAsync(); + proxyTestGAgentState.Content.Count.ShouldBe(1); + proxyTestGAgentState.Content[0].ShouldBe("Hello from TestEventHandler"); + var proxyGAgentWithState = + await _gAgentFactory.GetGAgentAsync>(proxyGAgent.GetPrimaryKey()); + var proxyGAgentState = await proxyGAgentWithState.GetStateAsync(); + proxyGAgentState.Database.ShouldNotBeNull(); + proxyGAgentState.Database.Count.ShouldBe(1); + proxyGAgentState.Database["Test"].ToString().ShouldBe("Raised event from TestEventHandler"); } - + private async Task CheckCount(IStateGAgent gAgent, int expectedCount) { var state = await gAgent.GetStateAsync(); return state.Content.Count == expectedCount; } -} +} \ No newline at end of file diff --git a/test/Aevatar.Plugins.Test/TestEventHandler.cs b/test/Aevatar.Plugins.Test/TestEventHandler.cs index 8ab1d8e3..e77b94c2 100644 --- a/test/Aevatar.Plugins.Test/TestEventHandler.cs +++ b/test/Aevatar.Plugins.Test/TestEventHandler.cs @@ -1,23 +1,18 @@ using Aevatar.Core.Abstractions; +using Aevatar.Core.Abstractions.ProxyGAgent; using Aevatar.ProxyGAgent.Sdk; namespace Aevatar.Plugins.Test; [GenerateSerializer] -public class PluginTestEvent1 : EventBase +public class PluginTestEvent : EventBase { [Id(0)] public string Greeting { get; set; } } -[GenerateSerializer] -public class PluginTestEvent2 : EventBase -{ - [Id(0)] public string Greeting { get; set; } -} - -public class TestEventHandler : IGAgentEventHandler +public class TestEventHandler : IGAgentEventHandler { - public async Task HandleEventAsync(PluginTestEvent1 event1Base) + public async Task HandleEventAsync(PluginTestEvent eventBase) { return new EventHandleResult { @@ -30,6 +25,16 @@ public async Task HandleEventAsync(PluginTestEvent1 event1Bas ["Greeting"] = "Hello from TestEventHandler" } } + ], + StateLogEventList = + [ + new ProxyStateLogEvent + { + Data = new Dictionary + { + ["Test"] = "Raised event from TestEventHandler" + } + } ] }; } diff --git a/test/Aevatar.Plugins.Test/TestLogEventConsistency.cs b/test/Aevatar.Plugins.Test/TestLogEventConsistency.cs new file mode 100644 index 00000000..f97ac814 --- /dev/null +++ b/test/Aevatar.Plugins.Test/TestLogEventConsistency.cs @@ -0,0 +1,15 @@ +using Aevatar.Core.Abstractions; +using Aevatar.Core.Abstractions.ProxyGAgent; +using Aevatar.ProxyGAgent.Sdk; + +namespace Aevatar.Plugins.Test; + +public class TestLogEventConsistency : ILogEventConsistency +{ + public ProxyGAgentState State { get; set; } + public void Apply(ProxyStateLogEvent eventData) + { + State.Database ??= new Dictionary(); + State.Database["Test"] = eventData.Data["Test"]; + } +} \ No newline at end of file diff --git a/test/Aevatar.ProxyGAgent.Sdk/ILogEventConsistency.cs b/test/Aevatar.ProxyGAgent.Sdk/ILogEventConsistency.cs index 19f64913..1bceb864 100644 --- a/test/Aevatar.ProxyGAgent.Sdk/ILogEventConsistency.cs +++ b/test/Aevatar.ProxyGAgent.Sdk/ILogEventConsistency.cs @@ -1,3 +1,4 @@ +using Aevatar.Core.Abstractions; using Aevatar.Core.Abstractions.ProxyGAgent; namespace Aevatar.ProxyGAgent.Sdk; @@ -5,5 +6,5 @@ namespace Aevatar.ProxyGAgent.Sdk; public interface ILogEventConsistency { ProxyGAgentState State { set; } - Task Apply(ProxyStateLogEvent eventData); + void Apply(ProxyStateLogEvent eventData); } \ No newline at end of file From 7a32e64060c8fa831fc59f32e59c281f2ed43808 Mon Sep 17 00:00:00 2001 From: eanzhao Date: Thu, 16 Jan 2025 10:19:08 +0800 Subject: [PATCH 3/3] revert change to execute Apply --- src/Aevatar.Core/GAgentBase.Subscribe.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Aevatar.Core/GAgentBase.Subscribe.cs b/src/Aevatar.Core/GAgentBase.Subscribe.cs index f6625122..587a1a44 100644 --- a/src/Aevatar.Core/GAgentBase.Subscribe.cs +++ b/src/Aevatar.Core/GAgentBase.Subscribe.cs @@ -28,8 +28,7 @@ protected sealed override void TransitionState(TState state, StateLogEventBase @event)