mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-09-08 10:10:49 +00:00
Revisions
This commit is contained in:
@@ -228,13 +228,13 @@ public static class Codec
|
||||
asyncReply.Then(value =>
|
||||
{
|
||||
rt.Trigger(new ParseResult<object>(value, (uint)tdu.TotalLength));
|
||||
});
|
||||
}).Error(rt.TriggerError);
|
||||
}
|
||||
else
|
||||
{
|
||||
rt.Trigger(new ParseResult<object>(result, (uint)tdu.TotalLength));
|
||||
}
|
||||
});
|
||||
}).Error(rt.TriggerError);
|
||||
|
||||
return rt;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Esiur.Core;
|
||||
using Esiur.Data.Types;
|
||||
using Esiur.Protocol;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
@@ -17,4 +18,28 @@ namespace Esiur.Data
|
||||
|
||||
public TypeDef ResourceDefinition { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional server-side invocation hook for local resources whose type
|
||||
/// definition is assembled at runtime. Remote <see cref="EpResource"/>
|
||||
/// proxies remain unchanged; this hook gives their local counterpart the
|
||||
/// same dynamic function semantics.
|
||||
/// </summary>
|
||||
public interface IDynamicResourceFunctionHandler
|
||||
{
|
||||
public AsyncReply InvokeResourceFunctionAsync(
|
||||
byte index,
|
||||
object arguments,
|
||||
InvocationContext context);
|
||||
}
|
||||
|
||||
public delegate void DynamicResourceEventHandler(byte index, object value);
|
||||
|
||||
/// <summary>
|
||||
/// Optional event bridge for a local runtime-defined resource.
|
||||
/// </summary>
|
||||
public interface IDynamicResourceEventSource
|
||||
{
|
||||
public event DynamicResourceEventHandler ResourceEventOccurred;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Esiur.Data;
|
||||
@@ -55,6 +56,9 @@ public static class RuntimeCaster
|
||||
Func<object?, RuntimeCastOptions, object?>> _elemConvCache =
|
||||
new ConcurrentDictionary<(Type, Type), Func<object?, RuntimeCastOptions, object?>>();
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, PropertyInfo[]> _recordPropertyCache =
|
||||
new ConcurrentDictionary<Type, PropertyInfo[]>();
|
||||
|
||||
// --------- Zero-allocation convenience overloads ---------
|
||||
public static object? Cast(object? value, Type toType)
|
||||
=> Cast(value, toType, RuntimeCastOptions.Default);
|
||||
@@ -149,6 +153,13 @@ public static class RuntimeCaster
|
||||
var toUnderlying = Nullable.GetUnderlyingType(toType) ?? toType;
|
||||
var fromUnderlying = Nullable.GetUnderlyingType(fromType) ?? fromType;
|
||||
|
||||
// A peer without a pre-generated CLR proxy represents a remote typed
|
||||
// record as Record. Materialize it into the function's declared local
|
||||
// IRecord type so dynamic resources retain their strongly typed
|
||||
// invocation contract across the wire.
|
||||
if (value is Record record && typeof(IRecord).IsAssignableFrom(toUnderlying))
|
||||
return ConvertRecord(record, toUnderlying, opts);
|
||||
|
||||
// Collections early
|
||||
{
|
||||
bool handled;
|
||||
@@ -233,6 +244,34 @@ public static class RuntimeCaster
|
||||
}
|
||||
}
|
||||
|
||||
private static object ConvertRecord(Record record, Type targetType, RuntimeCastOptions opts)
|
||||
{
|
||||
if (targetType.IsAbstract || targetType.IsInterface)
|
||||
throw new InvalidCastException("Cannot materialize a record as " + targetType + ".");
|
||||
|
||||
var target = Activator.CreateInstance(targetType)
|
||||
?? throw new InvalidCastException("Cannot create " + targetType + ".");
|
||||
var properties = _recordPropertyCache.GetOrAdd(
|
||||
targetType,
|
||||
static type => type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(property =>
|
||||
property.CanWrite &&
|
||||
property.SetMethod?.IsPublic == true &&
|
||||
property.GetIndexParameters().Length == 0)
|
||||
.ToArray());
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if (!record.ContainsKey(property.Name))
|
||||
continue;
|
||||
|
||||
var converted = Cast(record[property.Name], property.PropertyType, opts);
|
||||
property.SetValue(target, converted);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private static object? ConvertCollectionsIfAny(object value, Type fromType, Type toType, RuntimeCastOptions opts, out bool handled)
|
||||
{
|
||||
handled = false;
|
||||
|
||||
@@ -189,7 +189,12 @@ public class RemoteTypeDef:TypeDef
|
||||
definition._content = data.Clip(offset, contentLength);
|
||||
definition._typeId = info.Id;
|
||||
definition._parentTypeId = info.Parent;
|
||||
definition._typeName = string.IsNullOrEmpty(info.Namespace)
|
||||
// Older TypeDefInfo producers put the fully-qualified name in Name
|
||||
// while also filling Namespace. Avoid duplicating the namespace, but
|
||||
// continue accepting producers that send a simple type name.
|
||||
definition._typeName = string.IsNullOrEmpty(info.Namespace) ||
|
||||
info.Name.StartsWith(info.Namespace + ".", StringComparison.Ordinal) ||
|
||||
info.Name.StartsWith(info.Namespace + "+", StringComparison.Ordinal)
|
||||
? info.Name
|
||||
: $"{info.Namespace}.{info.Name}";
|
||||
definition._typeDefKind = info.Kind;
|
||||
|
||||
Reference in New Issue
Block a user