База для нового бэкэнда

This commit is contained in:
2025-03-06 14:10:52 +03:00
parent be6dedaee4
commit c802f337c7
18 changed files with 245 additions and 163 deletions
+18
View File
@@ -0,0 +1,18 @@
using System;
using Example.Shared;
namespace Example.Backend
{
public class CsTest
{
public T FinalCasted<T>(IDataList<T> list, int index)
{
return list.Get(index);
}
public object NonCasted(object list, int index)
{
return FinalCasted(list as IDataList<object>, index);
}
}
}
+24
View File
@@ -0,0 +1,24 @@
using System.Collections.Generic;
using Example.Shared;
namespace Example.Backend
{
public class PagesList : IPagesList
{
public IReadOnlyList<IPage> Collection { get; }
public IPage Get(int index)
{
throw new System.NotImplementedException();
}
public void Add(IPage item)
{
throw new System.NotImplementedException();
}
public void Set(int index, IPage item)
{
throw new System.NotImplementedException();
}
}
}
+2
View File
@@ -1,5 +1,6 @@
using System.Net;
using Example.Backend;
using Example.Shared;
using mROA.Abstract;
using mROA.Cbor;
using mROA.Codegen;
@@ -48,5 +49,6 @@ class Program
var gateway = builder.GetModule<IGatewayModule>();
gateway.Run();
}
}
+1
View File
@@ -11,6 +11,7 @@ namespace Example.Frontend
public string GetName()
{
Console.WriteLine("ClientBasedPrinter called from server!!!!!!!!!!! Vova likes that:)");
return "ClientBasedPrinter from mroa";
}
+3 -1
View File
@@ -17,7 +17,7 @@ class Program
public static void Main(string[] args)
{
var builder = new FullMixBuilder();
new RemoteTypeBinder();
// new RemoteTypeBinder();
// builder.Modules.Add(new JsonSerializationToolkit());
builder.Modules.Add(new CborSerializationToolkit());
@@ -106,5 +106,7 @@ class Program
// Console.WriteLine("X is {0}", x);
// Console.WriteLine("Time : {0}", timer.Elapsed.TotalMilliseconds);
// Console.WriteLine($"Time per call: {timer.Elapsed.TotalMilliseconds / iterations} ms");
}
}
+10 -7
View File
@@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
using mROA.Implementation;
using mROA.Implementation.Attributes;
namespace Example.Shared
{
// [SharedObjectInterface]
// public interface IDataList<T> : IShared
// {
// T Get(int index);
// void Add(T item);
// void Set(int index, T item);
// }
public interface IDataList<T> : IShared, IDisposable
{
IReadOnlyList<T> Collection { get; }
T Get(int index);
void Add(T item);
void Set(int index, T item);
}
}
+9
View File
@@ -0,0 +1,9 @@
using mROA.Implementation.Attributes;
namespace Example.Shared
{
[SharedObjectInterface]
public interface IPagesList : IDataList<IPage>
{
}
}
+4 -1
View File
@@ -52,8 +52,11 @@ namespace mROA.Cbor
return (T)Cast(nonCasted, typeof(T), context);
}
public object Cast(object nonCasted, Type type, IEndPointContext? context)
public object? Cast(object? nonCasted, Type type, IEndPointContext? context)
{
if (nonCasted == null)
return null;
if (nonCasted.GetType() == type)
return nonCasted;
+44 -57
View File
@@ -17,59 +17,27 @@ namespace mROA.Codegen
[Generator]
public class mROASourceGenerator : ISourceGenerator
{
private const string Namespace = "mROA.Implementation";
private const string AttributeName = "SharedObjectInterafceAttribute";
private const string AttributeSourceCode = $@"// <auto-generated/>
namespace {Namespace}
{{
[System.AttributeUsage(System.AttributeTargets.Class)]
public class {AttributeName} : System.Attribute
{{
}}
}}";
/// <summary>
/// Generate code action.
/// It will be executed on specific nodes (ClassDeclarationSyntax annotated with the [Report] attribute) changed by the user.
/// </summary>
/// <param name="context">Source generation context used to add source files.</param>
/// <param name="compilation">Compilation used to provide access to the Semantic Model.</param>
/// <param name="classes">Nodes annotated with the [Report] attribute that trigger the generate action.</param>
private void GenerateCode(GeneratorExecutionContext context, Compilation compilation,
ImmutableArray<InterfaceDeclarationSyntax> classes)
{
var methods = new List<(string, IMethodSymbol)>();
var frontendContextRepo = new List<string>();
// Go through all filtered class declarations.
var declarations = classes.ToList().OrderBy(i => i.Identifier.Text).ToList();
foreach (var classDeclarationSyntax in declarations)
{
// We need to get semantic model of the class to retrieve metadata.
var semanticModel = compilation.GetSemanticModel(classDeclarationSyntax.SyntaxTree);
// Symbols allow us to get the compile-time information.
if (semanticModel.GetDeclaredSymbol(classDeclarationSyntax) is not INamedTypeSymbol classSymbol)
continue;
var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
// 'Identifier' means the token of the node. Get class name from the syntax node.
var className = classDeclarationSyntax.Identifier.Text;
// Go through all class members with a particular type (property) to generate method lines.
var methodBody = classSymbol.GetMembers()
.OfType<IMethodSymbol>().OrderBy(i => i.Name);
var methodBody = CollectMethods(classSymbol);
var originalName = className;
// Build up the source code
className = className.TrimStart('I') + "RemoteEndpoint";
className = className.TrimStart('I') + "RemoteEndpoint";
var methodsText = new List<string>();
@@ -79,12 +47,27 @@ namespace {Namespace}
methods.Add((namespaceName + "." + originalName, method));
var sb = new StringBuilder();
bool isAsync = method.ReturnType.Name == "Task";
bool isVoid = method.ReturnType.Name == "Void" ||
method.ReturnType.ToString() == "System.Threading.Tasks.Task";
bool isParametrized = method.Parameters.Length == 1 && !isAsync ||
method.Parameters.Length == 2 && isAsync;
bool isParametrized;
bool isAsync;
bool isVoid;
switch (method.ReturnType)
{
case INamedTypeSymbol namedType:
isAsync = namedType.Name == "Task";
isVoid = isAsync && namedType.TypeParameters.Length == 0 || namedType.Name == "Void";
isParametrized = method.Parameters.Length == 1 && !isAsync ||
method.Parameters.Length == 2 && isAsync;
break;
case IArrayTypeSymbol:
isAsync = false;
isVoid = false;
isParametrized = method.Parameters.Length != 0;
break;
default:
continue;
}
//Creating signature
sb.AppendLine("public" + (isAsync
@@ -140,7 +123,7 @@ namespace {namespaceName}
// Add the source code to the compilation.
context.AddSource($"{className}.g.cs", SourceText.From(code, Encoding.UTF8));
// context.AddSource($"{className}.g.cs", SourceText.From(code, Encoding.UTF8));
frontendContextRepo.Add(
$"{{ typeof({classSymbol.ToDisplayString()}), typeof({namespaceName}.{className}) }}");
@@ -148,31 +131,27 @@ namespace {namespaceName}
if (methods.Count != 0)
{
// var methodsStringed = methods.Select(i =>
// $"typeof({i.Item1}).GetMethod(\"{i.Item2.Name}\", new Type[] {{{string.Join(", ", i.Item2.Parameters.Select(p => $"typeof({p.Type.ToDisplayString()})"))}}})")
// .ToList();
var methodsStringed = methods.Select(i =>
$"typeof({i.Item1}).GetMethod(\"{i.Item2.Name}\")")
.ToList();
var methodsStringed = Array.Empty<string>();
var coCodegenRepoCode = @$"// <auto-generated/>
using System.Collections.Generic;
using System.Reflection;
using mROA.Abstract;
using mROA.Implementation;
using System;
namespace mROA.Codegen
{{
public class CoCodegenMethodRepository : IMethodRepository
{{
private readonly List<MethodInfo> _methods = new () {{
private readonly List<IMethodInvoker> _methods = new () {{
{string.Join(",\r\n\t\t\t", methodsStringed)}
}};
public MethodInfo GetMethod(int id)
public IMethodInvoker GetMethod(int id)
{{
if (id == -1)
return typeof(IDisposable).GetMethod(""Dispose"");
return mROA.Implementation.MethodInvoker.Dispose;
if (_methods.Count <= id)
return null;
@@ -186,7 +165,7 @@ namespace mROA.Codegen
}}
}}
";
context.AddSource($"CoCodegenMethodRepository.g.cs", SourceText.From(coCodegenRepoCode, Encoding.UTF8));
context.AddSource("CoCodegenMethodRepository.g.cs", SourceText.From(coCodegenRepoCode, Encoding.UTF8));
}
if (frontendContextRepo.Count != 0)
@@ -209,7 +188,7 @@ namespace mROA.Codegen
}}
}}
";
context.AddSource("RemoteTypeBinder.g.cs", SourceText.From(fronendRepoCode, Encoding.UTF8));
// context.AddSource("RemoteTypeBinder.g.cs", SourceText.From(fronendRepoCode, Encoding.UTF8));
}
}
@@ -225,9 +204,7 @@ namespace mROA.Codegen
private string ExtractTaskType(ITypeSymbol taskType)
{
var type = taskType.ToString();
type = type.Substring(type.IndexOf('<') + 1);
return type.Substring(0, type.Length - 1);
return (taskType as INamedTypeSymbol).TypeParameters[0].ToDisplayString();
}
public void Initialize(GeneratorInitializationContext context)
@@ -266,8 +243,7 @@ namespace mROA.Codegen
private bool ContainsSOIAttribute(SyntaxList<AttributeListSyntax> attributes, GeneratorExecutionContext context,
InterfaceDeclarationSyntax interfaceDeclarationSyntax)
{
foreach (AttributeListSyntax attributeListSyntax in attributes)
foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes)
foreach (var attributeSyntax in attributes.SelectMany(attributeListSyntax => attributeListSyntax.Attributes))
{
if (context.Compilation.GetSemanticModel(interfaceDeclarationSyntax.SyntaxTree)
.GetSymbolInfo(attributeSyntax).Symbol is not IMethodSymbol attributeSymbol)
@@ -282,5 +258,16 @@ namespace mROA.Codegen
return false;
}
private List<IMethodSymbol> CollectMethods(INamedTypeSymbol type)
{
var methods = type.GetMembers().OfType<IMethodSymbol>().ToList();
foreach (var inner in type.AllInterfaces)
{
methods.AddRange(inner.GetMembers().OfType<IMethodSymbol>());
}
return methods.OrderBy(i => i.Name).ToList();
}
}
}
+2 -1
View File
@@ -8,6 +8,7 @@ namespace mROA.Abstract
bool IsVoid { get; }
Type[] ParameterTypes { get; }
Type? ReturnType { get; }
object? Invoke(object instance, object?[] parameters, object[] special);
object? Invoke(object instance, object?[]? parameters, object[] special);
Type SuitableType { get; }
}
}
+1 -1
View File
@@ -4,6 +4,6 @@ namespace mROA.Abstract
{
public interface IMethodRepository : IInjectableModule
{
MethodInfo GetMethod(int id);
IMethodInvoker GetMethod(int id);
}
}
+2 -2
View File
@@ -10,8 +10,8 @@ namespace mROA.Abstract
object? Deserialize(byte[] rawData, Type type);
T? Deserialize<T>(Span<byte> rawData);
object? Deserialize(Span<byte> rawData, Type type);
T? Cast<T>(object nonCasted);
object? Cast(object nonCasted, Type type);
T? Cast<T>(object? nonCasted);
object? Cast(object? nonCasted, Type type);
}
}
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
@@ -11,11 +12,22 @@ namespace mROA.Implementation.Backend
{
private IMethodRepository? _methodRepo;
private ICancellationRepository? _cancellationRepo;
private ISerializationToolkit? _serialization;
public void Inject<T>(T dependency)
{
if (dependency is IMethodRepository methodRepo) _methodRepo = methodRepo;
if (dependency is ICancellationRepository cancellationRepo) _cancellationRepo = cancellationRepo;
switch (dependency)
{
case IMethodRepository methodRepo:
_methodRepo = methodRepo;
break;
case ICancellationRepository cancellationRepo:
_cancellationRepo = cancellationRepo;
break;
case ISerializationToolkit serializationToolkit:
_serialization = serializationToolkit;
break;
}
}
public ICommandExecution Execute(ICallRequest command, IContextRepository contextRepository,
@@ -24,50 +36,69 @@ namespace mROA.Implementation.Backend
#if TRACE
Console.WriteLine(command.GetType().Name);
#endif
if (_cancellationRepo is null)
throw new NullReferenceException("Method repository was not defined");
if (_methodRepo is null)
throw new NullReferenceException("Method repository was not defined");
if (contextRepository is null)
throw new NullReferenceException("Context repository was not defined");
if (command is CancelRequest)
{
#if TRACE
Console.WriteLine("Final cancelling request");
#endif
var cts = _cancellationRepo.GetCancellation(command.Id);
cts.Cancel();
_cancellationRepo.FreeCancelation(command.Id);
return new FinalCommandExecution
{
Id = command.Id
};
}
var currentCommand = _methodRepo.GetMethod(command.CommandId);
if (currentCommand == null)
throw new Exception($"Command {command.CommandId} not found");
var context = command.ObjectId != -1
? contextRepository.GetObject<object>(command.ObjectId)
: contextRepository.GetSingleObject(currentCommand.DeclaringType!);
var parameter = command.Parameter;
if (currentCommand.ReturnType.BaseType == typeof(Task) &&
currentCommand.ReturnType.GenericTypeArguments.Length == 1)
return TypedExecuteAsync(currentCommand, context, parameter, command, _cancellationRepo,
representationModule);
if (currentCommand.ReturnType == typeof(Task))
return ExecuteAsync(currentCommand, context, parameter, command, _cancellationRepo,
representationModule);
try
{
var result = Execute(currentCommand, context, parameter, command);
if (_cancellationRepo is null)
throw new NullReferenceException("Method repository was not defined");
if (_methodRepo is null)
throw new NullReferenceException("Method repository was not defined");
if (contextRepository is null)
throw new NullReferenceException("Context repository was not defined");
if (command is CancelRequest)
{
#if TRACE
Console.WriteLine("Final cancelling request");
#endif
var cts = _cancellationRepo.GetCancellation(command.Id);
if (cts == null)
throw new NullReferenceException("Can't find cancellation for this request");
cts.Cancel();
_cancellationRepo.FreeCancelation(command.Id);
return new FinalCommandExecution
{
Id = command.Id
};
}
var invoker = _methodRepo.GetMethod(command.CommandId);
if (invoker == null)
throw new Exception($"Command {command.CommandId} not found");
var context = command.ObjectId != -1
? contextRepository.GetObject<object>(command.ObjectId)
: contextRepository.GetSingleObject(invoker.SuitableType);
if (context == null)
throw new NullReferenceException("Instance can't be null");
object?[]? castedParams = null;
if (invoker.ParameterTypes != Type.EmptyTypes)
{
castedParams = new object[invoker.ParameterTypes.Length];
for (int i = 0; i < castedParams.Length; i++)
{
castedParams[i] = _serialization.Cast(command.Parameters![i], invoker.ParameterTypes[i]);
}
}
var execContext = new RequestContext(command.Id, representationModule.Id);
if (invoker is { IsAsync: true, IsVoid: false })
return TypedExecuteAsync(invoker, context, castedParams, command, _cancellationRepo,
representationModule, execContext);
if (invoker.IsAsync)
return ExecuteAsync(invoker, context, castedParams, command, _cancellationRepo,
representationModule, execContext);
var result = Execute(invoker, context, castedParams, command, execContext);
if (command.CommandId == -1)
{
#if TRACE
@@ -80,23 +111,22 @@ namespace mROA.Implementation.Backend
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
return new ExceptionCommandExecution
{
Id = command.Id,
Exception = e.ToString()
};
}
}
private static ICommandExecution Execute(MethodInfo currentCommand, object context, object? parameter,
ICallRequest command)
private static ICommandExecution Execute(IMethodInvoker invoker, object instance, object?[] parameter,
ICallRequest command, RequestContext executionContext)
{
try
{
var finalParameter = parameter is null
? Array.Empty<object>()
: new[]
{ parameter };
var finalResult = currentCommand.Invoke(context, finalParameter);
var finalResult = invoker.Invoke(instance, parameter, new object[] { executionContext });
if (currentCommand.ReturnType.Name == "Void")
if (invoker.IsVoid)
{
return new FinalCommandExecution
{
@@ -120,9 +150,9 @@ namespace mROA.Implementation.Backend
}
}
private ICommandExecution ExecuteAsync(MethodInfo currentCommand, object context, object? parameter,
private ICommandExecution ExecuteAsync(IMethodInvoker invoker, object instance, object?[]? parameters,
ICallRequest command, ICancellationRepository cancellationRepository,
IRepresentationModule representationModule)
IRepresentationModule representationModule, RequestContext executionContext)
{
var tokenSource = new CancellationTokenSource();
cancellationRepository.RegisterCancellation(command.Id, tokenSource);
@@ -132,11 +162,7 @@ namespace mROA.Implementation.Backend
#endif
try
{
var finalParameter = parameter is null
? new object[] { token }
: new[]
{ parameter, token };
var result = (Task)currentCommand.Invoke(context, finalParameter)!;
var result = (Task)invoker.Invoke(instance, parameters, new object[] { executionContext, token })!;
result.ContinueWith(_ =>
@@ -173,9 +199,9 @@ namespace mROA.Implementation.Backend
}
}
private ICommandExecution TypedExecuteAsync(MethodInfo currentCommand, object context, object? parameter,
private ICommandExecution TypedExecuteAsync(IMethodInvoker invoker, object instance, object?[]? parameters,
ICallRequest command, ICancellationRepository cancellationRepository,
IRepresentationModule representationModule)
IRepresentationModule representationModule, RequestContext executionContext)
{
var tokenSource = new CancellationTokenSource();
cancellationRepository.RegisterCancellation(command.Id, tokenSource);
@@ -183,12 +209,8 @@ namespace mROA.Implementation.Backend
var token = tokenSource.Token;
try
{
var finalParameter = parameter is null
? new object[] { token }
: new[]
{ parameter, token };
var result =
(Task)currentCommand.Invoke(context, finalParameter)!;
(Task)invoker.Invoke(instance, parameters, new object[] { executionContext, token })!;
result.ContinueWith(t =>
{
@@ -198,7 +220,7 @@ namespace mROA.Implementation.Backend
Id = command.Id,
Result = finalResult
};
_cancellationRepo.FreeCancelation(command.Id);
_cancellationRepo!.FreeCancelation(command.Id);
var multiClientOwnershipRepository =
TransmissionConfig.OwnershipRepository as MultiClientOwnershipRepository;
+3 -3
View File
@@ -11,7 +11,7 @@ namespace mROA.Implementation
Guid Id { get; }
int CommandId { get; }
int ObjectId { get; }
object? Parameter { get; }
object?[]? Parameters { get; }
}
public class DefaultCallRequest : ICallRequest
@@ -20,7 +20,7 @@ namespace mROA.Implementation
public int CommandId { get; set; }
public int ObjectId { get; set; } = -1;
public object? Parameter { get; set; }
public object?[]? Parameters { get; set; }
public override string ToString()
{
return $"Call request {{ Id : {Id}, CommandId : {CommandId}, ObjectId : {ObjectId} }}";
@@ -32,7 +32,7 @@ namespace mROA.Implementation
public Guid Id { get; set; }
public int CommandId { get; set; } = -2;
public int ObjectId { get; set; } = -2;
public object? Parameter { get; set; } = null;
public object?[]? Parameters { get; set; } = null;
public override string ToString()
{
return $"Cancel request {{ Id : {Id}, CommandId : {CommandId}, ObjectId : {ObjectId} }}";
@@ -94,15 +94,6 @@ namespace mROA.Implementation.Frontend
tokenSource.Cancel();
var request = defaultRequest.Result;
if (request.Parameter is not null)
{
var method = _methodRepository!.GetMethod(request.CommandId);
var parameterType = method.GetParameters().First()
.ParameterType;
request.Parameter = _serializationToolkit.Cast(request.Parameter, parameterType);
}
var result = _executeModule.Execute(request, _contextRepository, _representationModule);
var resultType = MessageType.Unknown;
+8 -5
View File
@@ -9,23 +9,26 @@ namespace mROA.Implementation
public bool IsVoid { get; set; }
public Type[] ParameterTypes { get; set; } = Type.EmptyTypes;
public Type? ReturnType { get; set; }
public Func<object, object?[], object[], object?> Invoking { get; set; }
public Func<object, object?[]?, object[], object?> Invoking { get; set; } = (_, _, _) => null;
public object? Invoke(object instance, object?[] parameters, object[] special)
public object? Invoke(object instance, object?[]? parameters, object[] special)
{
return Invoking(instance, parameters, special);
}
public static MethodInvoker Dispose = new MethodInvoker
public Type SuitableType { get; set; } = null!;
public static readonly IMethodInvoker Dispose = new MethodInvoker
{
IsAsync = false,
IsVoid = true,
ReturnType = null,
Invoking = ((instance, parameters, special) =>
Invoking = (instance, _, _) =>
{
(instance as IDisposable)?.Dispose();
return null;
})
},
SuitableType = typeof(IDisposable)
};
}
}
+4 -4
View File
@@ -22,11 +22,11 @@ namespace mROA.Implementation
public int Id => _identifier.ContextId;
public int OwnerId => _identifier.OwnerId;
public UniversalObjectIdentifier Identifier => _identifier;
protected async Task<T> GetResultAsync<T>(int methodId, object? parameter = default,
protected async Task<T> GetResultAsync<T>(int methodId, object?[]? parameters = null,
CancellationToken cancellationToken = default)
{
var request = new DefaultCallRequest
{ CommandId = methodId, ObjectId = _identifier.ContextId, Parameter = parameter
{ CommandId = methodId, ObjectId = _identifier.ContextId, Parameters = parameters
};
await _representationModule.PostCallMessageAsync(request.Id, MessageType.CallRequest, request);
@@ -76,11 +76,11 @@ namespace mROA.Implementation
throw errorResponse.Result.GetException();
}
protected async Task CallAsync(int methodId, object? parameter = default,
protected async Task CallAsync(int methodId, object?[]? parameters = null,
CancellationToken cancellationToken = default)
{
var request = new DefaultCallRequest
{ CommandId = methodId, ObjectId = _identifier.ContextId, Parameter = parameter
{ CommandId = methodId, ObjectId = _identifier.ContextId, Parameters = parameters
};
await _representationModule.PostCallMessageAsync(request.Id, MessageType.CallRequest, request);
+16
View File
@@ -0,0 +1,16 @@
using System;
namespace mROA.Implementation
{
public sealed class RequestContext
{
public int OwnerId { get; }
public Guid RequestId { get; }
public RequestContext(Guid requestId, int ownerId)
{
RequestId = requestId;
OwnerId = ownerId;
}
}
}