Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/support event (async) #15

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1f0081a
refactor(container-server): adjust middleware behavior.
DingpingZhang Nov 10, 2021
ed82ac8
chore(event): save unfinished work.
DingpingZhang Nov 10, 2021
ab47e36
feat: save uncomplete work - ipc contract interface support the cshar…
DingpingZhang Nov 14, 2021
602d89e
feat: ipc contract interface support the csharp event. (#12)
DingpingZhang Nov 15, 2021
2a052d9
bugfix: support events and add basic test cases. (#12)
DingpingZhang Nov 15, 2021
a8b0c24
bugfix: ensure that execution events are synchronized. (#12)
DingpingZhang Nov 16, 2021
ed292b3
reactor: refine code for supporting event. (#12)
DingpingZhang Nov 16, 2021
8d88c02
refactor: add the serializer-extensions file.
DingpingZhang Nov 17, 2021
0f0ed46
enhance: add more compile-time warning. (#13)
DingpingZhang Nov 17, 2021
16d8a08
chore(version): bump version to 0.6.0
DingpingZhang Nov 17, 2021
9e1f894
fix: incorrect process id.
DingpingZhang Nov 18, 2021
2630c7c
fix: terminate the abnormal link as soon as possible.
DingpingZhang Nov 18, 2021
9409f1c
fix: terminate the abnormal link as soon as possible.
DingpingZhang Nov 18, 2021
ebf2cbb
fix: modify buffer size to 4kb for unknown reason, less than it will …
DingpingZhang Nov 18, 2021
93b8bd4
refactor: improve logger.
DingpingZhang Nov 19, 2021
5812a97
chore(retarget): retarget to .net462.
DingpingZhang Nov 19, 2021
1def7c5
chore: some trivial changes.
DingpingZhang Nov 19, 2021
613d2a6
refactor: some trivial changes.
DingpingZhang Nov 20, 2021
20b38c8
feat: implemented async event calls behavior. (#12)
DingpingZhang Nov 21, 2021
68ce56c
fix: fixed merge issues.
DingpingZhang Nov 23, 2021
cd0f6fc
chore: save uncomplete work.
DingpingZhang Nov 23, 2021
7a6666d
refactor: rebase issues.
DingpingZhang Oct 4, 2022
996e8d5
chore(ci): bump .net of workflows to 7.x.x
DingpingZhang Oct 5, 2022
ca74f69
chore(ci): bump .net of workflows to 6.x.x
DingpingZhang Oct 5, 2022
0c483cc
chore: record bug.
DingpingZhang Apr 16, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
dotnet-version: 6.x.x
- name: Restore dependencies
run: dotnet restore
- name: Build
Expand Down
44 changes: 43 additions & 1 deletion HandyIpc.Generator/ClientProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace HandyIpc.Generator
{
public static class ClientProxy
{
public static string Generate(INamedTypeSymbol @interface, IReadOnlyCollection<IMethodSymbol> methods)
public static string Generate(INamedTypeSymbol @interface, IReadOnlyCollection<IMethodSymbol> methods, IReadOnlyCollection<IEventSymbol> events)
{
var (@namespace, className, typeParameters) = @interface.GenerateNameFromInterface();
string interfaceType = @interface.ToFullDeclaration();
Expand All @@ -30,12 +30,54 @@ public class {nameof(ClientProxy)}{className}{typeParameters} : {interfaceType}
private readonly Sender _sender;
private readonly ISerializer _serializer;
private readonly string _key;
{Text(events.Any() ? @"
private readonly AwaiterManager _awaiterManager;
" : RemoveLineIfEmpty)}

{events.For(item => $@"
private event {item.Type.ToTypeDeclaration()} _{item.Name};
")}
{events.For(item =>
{
IParameterSymbol eSymbol = ((INamedTypeSymbol)item.Type).DelegateInvokeMethod!.Parameters[1];
string eType = eSymbol.Type.ToTypeDeclaration();
return $@"

public event {item.Type.ToTypeDeclaration()} {item.Name}
{{
add
{{
if (_{item.Name} == null)
{{
_awaiterManager.Subscribe(""{item.Name}"", args =>
{{
var e = ({eType})_serializer.Deserialize(args, typeof({eType}));
_{item.Name}?.Invoke(this, e);
}});
}}

_{item.Name} += value;
}}
remove
{{
_{item.Name} -= value;
if (_{item.Name} == null)
{{
_awaiterManager.Unsubscribe(""{item.Name}"");
}}
}}
}}
";
})}

public {nameof(ClientProxy)}{className}(Sender sender, ISerializer serializer, string key)
{{
_sender = sender;
_serializer = serializer;
_key = key;
{Text(events.Any() ? @"
_awaiterManager = new AwaiterManager(key, sender, serializer);
" : RemoveLineIfEmpty)}
}}
{methods.For(method =>
{
Expand Down
33 changes: 12 additions & 21 deletions HandyIpc.Generator/DiagnosticDescriptors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,24 @@ internal static class DiagnosticDescriptors
true,
helpLinkUri: $"{HelpLinkUri}#hi002");

public static readonly DiagnosticDescriptor EventWithoutReturn = new(
public static readonly DiagnosticDescriptor UseStandardEventHandler = new(
"HI003",
"Events are not allowed to have return values",
"The event '{0}' cannot have a return value. Consider using an event handler without a return value.",
"Standard event declarations must be used",
"The event '{0}' does not use the standard event declaration. Consider using an event signature like 'void EventHandler<T>(object this, T eventArgs)'.",
HandyIpc,
DiagnosticSeverity.Error,
true,
helpLinkUri: $"{HelpLinkUri}#hi003");

public static readonly DiagnosticDescriptor ContainsNotSupportedMembers = new(
"HI004",
"This interface contains members that are not supported",
"This interface '{0}' contains members that are not supported. Consider removing non-method or non-event members.",
HandyIpc,
DiagnosticSeverity.Error,
true,
helpLinkUri: $"{HelpLinkUri}#hi004");

public static readonly DiagnosticDescriptor MustContainsMethod = new(
"HI100",
"The contract interface must contains methods",
Expand All @@ -43,24 +52,6 @@ internal static class DiagnosticDescriptors
DiagnosticSeverity.Warning,
true,
helpLinkUri: $"{HelpLinkUri}#hi100");

public static readonly DiagnosticDescriptor UseStandardEventHandler = new(
"HI101",
"Standard event declarations should be used",
"The event '{0}' does not use the standard event declaration. Consider using either System.EventHandler or System.EventHandler<T> to declare the event.",
HandyIpc,
DiagnosticSeverity.Warning,
true,
helpLinkUri: $"{HelpLinkUri}#hi101");

public static readonly DiagnosticDescriptor ContainsNotSupportedMembers = new(
"HI102",
"This interface contains members that are not supported",
"This interface '{0}' contains members that are not supported. Consider removing non-method or non-event members.",
HandyIpc,
DiagnosticSeverity.Warning,
true,
helpLinkUri: $"{HelpLinkUri}#hi102");
}
#pragma warning restore RS2008 // Enable analyzer release tracking
}
13 changes: 9 additions & 4 deletions HandyIpc.Generator/Dispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace HandyIpc.Generator
{
public static class Dispatcher
{
public static string Generate(INamedTypeSymbol @interface, IReadOnlyCollection<IMethodSymbol> methods)
public static string Generate(INamedTypeSymbol @interface, IReadOnlyCollection<IMethodSymbol> methods, IReadOnlyCollection<IEventSymbol> events)
{
var (@namespace, className, typeParameters) = @interface.GenerateNameFromInterface();
string interfaceType = @interface.ToFullDeclaration();
Expand All @@ -26,7 +26,7 @@ namespace {@namespace}
[global::System.Diagnostics.DebuggerNonUserCode]
[global::System.Reflection.Obfuscation(Exclude = true)]
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public class {nameof(Dispatcher)}{className}{typeParameters} : IMethodDispatcher
public class {nameof(Dispatcher)}{className}{typeParameters} : IMethodDispatcher{(events.Any() ? ", INotifiable" : null)}
{@interface.TypeParameters.For(typeParameter => $@"
{typeParameter.ToGenericConstraint()}
")}
Expand All @@ -36,13 +36,20 @@ public class {nameof(Dispatcher)}{className}{typeParameters} : IMethodDispatcher
private readonly Lazy<IReadOnlyDictionary<string, MethodInfo>> _genericMethodMapping;
" : RemoveLineIfEmpty)}

{Text(events.Any() ? @"
public NotifierManager NotifierManager { get; set; }

" : RemoveLineIfEmpty)}
public {nameof(Dispatcher)}{className}({interfaceType} instance)
{{
_instance = instance;
{Text(methods.Any(item => item.TypeParameters.Any()) ? $@"
_genericMethodMapping = new Lazy<IReadOnlyDictionary<string, MethodInfo>>(
() => GeneratorHelper.GetGenericMethodMapping(typeof({interfaceType}), _instance));
" : RemoveLineIfEmpty)}
{events.For(item => $@"
instance.{item.Name} += (_, e) => NotifierManager.Publish(""{item.Name}"", e);
")}
}}

public async Task Dispatch(Context ctx, Func<Task> next)
Expand Down Expand Up @@ -120,8 +127,6 @@ public async Task Dispatch(Context ctx, Func<Task> next)
default:
throw new ArgumentOutOfRangeException(""No matching remote method was found."");
}}

await next();
}}
}}
}}
Expand Down
18 changes: 17 additions & 1 deletion HandyIpc.Generator/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ namespace HandyIpc.Generator
{
internal static class Extensions
{
public static INamedTypeSymbol TaskTypeSymbol { get; set; } = null!;
private static INamedTypeSymbol TaskTypeSymbol = null!;
private static INamedTypeSymbol ObjectTypeSymbol = null!;

public static void Initialize(Compilation compilation)
{
TaskTypeSymbol = compilation.GetTypeByMetadataName("System.Threading.Tasks.Task")!;
ObjectTypeSymbol = compilation.GetTypeByMetadataName("System.Object")!;
}

public static string ToFullDeclaration(this ISymbol symbol)
{
Expand Down Expand Up @@ -122,5 +129,14 @@ public static IEnumerable<ITypeSymbol> EnumerateSelfAndBaseType(this ITypeSymbol
current = current.BaseType;
}
}

public static bool IsStdEventHandler(this IEventSymbol symbol)
{
IMethodSymbol methodSymbol = ((INamedTypeSymbol)symbol.Type).DelegateInvokeMethod!;

return methodSymbol.ReturnsVoid &&
methodSymbol.Parameters.Length == 2 &&
methodSymbol.Parameters[0].Type.Equals(ObjectTypeSymbol, SymbolEqualityComparer.Default);
}
}
}
2 changes: 1 addition & 1 deletion HandyIpc.Generator/HandyIpc.Generator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Version>0.5.0</Version>
<Version>0.6.0</Version>
<LangVersion>latest</LangVersion>
</PropertyGroup>

Expand Down
9 changes: 8 additions & 1 deletion HandyIpc.Generator/ServerProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace HandyIpc.Generator
{
public static class ServerProxy
{
public static string Generate(INamedTypeSymbol @interface, IReadOnlyCollection<IMethodSymbol> methods)
public static string Generate(INamedTypeSymbol @interface, IReadOnlyCollection<IMethodSymbol> methods, IReadOnlyCollection<IEventSymbol> events)
{
var (@namespace, className, typeParameters) = @interface.GenerateNameFromInterface();
string interfaceType = @interface.ToFullDeclaration();
Expand All @@ -26,9 +26,16 @@ public class {nameof(ServerProxy)}{className}{typeParameters} : {interfaceType}
{{
private readonly {interfaceType} _instance;

{events.For(item => $@"
public event {item.Type.ToTypeDeclaration()} {item.Name};
")}

public {nameof(ServerProxy)}{className}({interfaceType} instance)
{{
_instance = instance;
{events.For(item => $@"
instance.{item.Name} += (sender, e) => {item.Name}?.Invoke(sender, e);
")}
}}
{methods.For(method =>
{
Expand Down
50 changes: 39 additions & 11 deletions HandyIpc.Generator/SourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ public class SourceGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
//Debugger.Launch();
//System.Diagnostics.Debugger.Launch();

if (context.SyntaxReceiver is not SyntaxReceiver receiver)
{
return;
}

Compilation compilation = context.Compilation;
Extensions.Initialize(compilation);

INamedTypeSymbol? ipcContractAttributeSymbol = compilation.GetTypeByMetadataName("HandyIpc.IpcContractAttribute");
Extensions.TaskTypeSymbol = compilation.GetTypeByMetadataName("System.Threading.Tasks.Task")!;
if (ipcContractAttributeSymbol is null)
{
context.ReportDiagnostic(Diagnostic.Create(HandyIpcNotReferenced, Location.None));
Expand All @@ -42,14 +42,30 @@ public void Execute(GeneratorExecutionContext context)
// WORKAROUND: The @interface must not be null here.
.Select(@interface => @interface!)
.Where(@interface => ContainsAttribute(@interface, ipcContractAttributeSymbol));
})
.Select(@interface => (
@interface,
methods: @interface.GetMembers().OfType<IMethodSymbol>().ToList().AsReadOnly()));
});

var fileNameCounter = new Dictionary<string, int>();
foreach (var (@interface, methods) in contractInterfaces)
foreach (var @interface in contractInterfaces)
{
ISymbol[] members = @interface.GetMembers().ToArray();
IMethodSymbol[] methods = members.OfType<IMethodSymbol>()
.Where(item => item.MethodKind
is not MethodKind.EventAdd
and not MethodKind.EventRemove
and not MethodKind.EventRaise)
.ToArray();
IEventSymbol[] events = members.OfType<IEventSymbol>().ToArray();

if (members.Length != methods.Length + events.Length * 3)
{
foreach (Location location in @interface.Locations)
{
context.ReportDiagnostic(Diagnostic.Create(ContainsNotSupportedMembers, location, @interface.Name));
}

continue;
}

if (@interface.Interfaces.Length > 0)
{
foreach (Location location in @interface.Locations)
Expand All @@ -60,7 +76,7 @@ public void Execute(GeneratorExecutionContext context)
continue;
}

if (!methods.Any())
if (!methods.Any() && !events.Any())
{
foreach (Location location in @interface.Locations)
{
Expand All @@ -70,9 +86,21 @@ public void Execute(GeneratorExecutionContext context)
continue;
}

string clientProxySource = ClientProxy.Generate(@interface, methods);
string serverProxySource = ServerProxy.Generate(@interface, methods);
string dispatcherSource = Dispatcher.Generate(@interface, methods);
bool hasInvalidEvent = false;
foreach (var location in events.Where(@event => [email protected]()).SelectMany(@event => @event.Locations))
{
hasInvalidEvent = true;
context.ReportDiagnostic(Diagnostic.Create(UseStandardEventHandler, location, @interface.Name));
}

if (hasInvalidEvent)
{
continue;
}

string clientProxySource = ClientProxy.Generate(@interface, methods, events);
string serverProxySource = ServerProxy.Generate(@interface, methods, events);
string dispatcherSource = Dispatcher.Generate(@interface, methods, events);

string fileName = GetUniqueString(@interface.Name, fileNameCounter);

Expand Down
4 changes: 2 additions & 2 deletions HandyIpc.NamedPipe/HandyIpc.NamedPipe.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
<PropertyGroup>
<AssemblyName>HandyIpc.NamedPipe</AssemblyName>
<TargetFramework>netstandard2.0</TargetFramework>
<Version>0.5.0</Version>
<Version>0.6.0</Version>
<GeneratePackageOnBuild Condition="'$(Configuration)'=='Release'">true</GeneratePackageOnBuild>
</PropertyGroup>

<ItemGroup>
<PackageReference Condition="'$(Configuration)'=='Release'" Include="HandyIpc" Version="0.5.*" />
<PackageReference Condition="'$(Configuration)'=='Release'" Include="HandyIpc" Version="0.6.*" />
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 3 additions & 3 deletions HandyIpc.Serializer.Json/HandyIpc.Serializer.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

<PropertyGroup>
<AssemblyName>HandyIpc.Serializer.Json</AssemblyName>
<TargetFramework>netstandard2.0</TargetFramework>
<Version>0.5.0</Version>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<Version>0.6.0</Version>
<GeneratePackageOnBuild Condition="'$(Configuration)'=='Release'">true</GeneratePackageOnBuild>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="[12.0.1,)" />
<PackageReference Condition="'$(Configuration)'=='Release'" Include="HandyIpc" Version="0.5.*" />
<PackageReference Condition="'$(Configuration)'=='Release'" Include="HandyIpc" Version="0.6.*" />
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 3 additions & 3 deletions HandyIpc.Socket/HandyIpc.Socket.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

<PropertyGroup>
<AssemblyName>HandyIpc.Socket</AssemblyName>
<TargetFramework>netstandard2.0</TargetFramework>
<Version>0.5.0</Version>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<Version>0.6.0</Version>
<GeneratePackageOnBuild Condition="'$(Configuration)'=='Release'">true</GeneratePackageOnBuild>
</PropertyGroup>

<ItemGroup>
<PackageReference Condition="'$(Configuration)'=='Release'" Include="HandyIpc" Version="0.5.*" />
<PackageReference Condition="'$(Configuration)'=='Release'" Include="HandyIpc" Version="0.6.*" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion HandyIpc.Tests/BuildInTypeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public void TestBuildInTypesWithSocket()

private static void TestCases(IBuildInType instance)
{
Assert.Throws<TestException>(instance.TestVoidWithoutParams);
Helper.AssertInnerException<TestException>(instance.TestVoidWithoutParams);

instance.TestDoNothing();

Expand Down
Loading