Revisions

This commit is contained in:
2026-08-09 03:54:28 +03:00
parent 2028db671f
commit 3b55d8d2f8
37 changed files with 1743 additions and 230 deletions
+2 -2
View File
@@ -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;
+25
View File
@@ -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;
}
}
+39
View File
@@ -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;
+6 -1
View File
@@ -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;