mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2025-12-13 16:30:24 +00:00
Deserialization fix
This commit is contained in:
@@ -53,10 +53,10 @@ public class AsyncBag<T> : AsyncReply, IAsyncBag
|
||||
//if (!sealedBag && !resultReady)
|
||||
// throw new Exception("Not sealed");
|
||||
|
||||
Timeout(6000, () =>
|
||||
{
|
||||
Console.WriteLine("Timeout " + count + this.Result);
|
||||
});
|
||||
//Timeout(6000, () =>
|
||||
//{
|
||||
//Console.WriteLine("Timeout " + count + this.Result);
|
||||
//});
|
||||
|
||||
base.Then(new Action<object>(o => callback((T[])o)));
|
||||
return this;
|
||||
|
||||
@@ -73,6 +73,8 @@ public class AsyncReply
|
||||
|
||||
public Exception Exception => exception;
|
||||
|
||||
public static AsyncReply<T> FromResult<T>(T result) => new AsyncReply<T>(result);
|
||||
|
||||
public object Wait()
|
||||
{
|
||||
if (resultReady)
|
||||
|
||||
@@ -69,6 +69,7 @@ public class AsyncReply<T> : AsyncReply
|
||||
|
||||
}
|
||||
|
||||
|
||||
public AsyncReply()
|
||||
: base()
|
||||
{
|
||||
|
||||
@@ -466,7 +466,7 @@ public static class DataDeserializer
|
||||
|
||||
}).Error(e =>
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
//Console.WriteLine(e);
|
||||
rt.TriggerError(e);
|
||||
});
|
||||
|
||||
@@ -1081,6 +1081,21 @@ public static class DataDeserializer
|
||||
case TRUIdentifier.UInt16:
|
||||
return GroupUInt16Codec.Decode(tdu.Data.AsSpan(
|
||||
(int)tdu.Offset, (int)tdu.ContentLength));
|
||||
case TRUIdentifier.Enum:
|
||||
|
||||
var enumType = tru.GetRuntimeType(warehouse);
|
||||
|
||||
var enums = Array.CreateInstance(enumType, (int)tdu.ContentLength);
|
||||
var enumTemplate = warehouse.GetTemplateByType(enumType);
|
||||
|
||||
for (var i = 0; i < (int)tdu.ContentLength; i++)
|
||||
{
|
||||
var index = tdu.Data[tdu.Offset + i];
|
||||
enums.SetValue(enumTemplate.Constants[index].Value, i);
|
||||
}
|
||||
|
||||
return enums;
|
||||
|
||||
default:
|
||||
|
||||
|
||||
@@ -1380,6 +1395,22 @@ public static class DataDeserializer
|
||||
case TRUIdentifier.UInt16:
|
||||
return new AsyncReply(GroupUInt16Codec.Decode(tdu.Data.AsSpan(
|
||||
(int)tdu.Offset, (int)tdu.ContentLength)));
|
||||
|
||||
case TRUIdentifier.Enum:
|
||||
var enumType = tru.GetRuntimeType(connection.Instance.Warehouse);
|
||||
|
||||
var rt = Array.CreateInstance(enumType, (int)tdu.ContentLength);
|
||||
var enumTemplate = connection.Instance.Warehouse.GetTemplateByType(enumType);
|
||||
|
||||
for (var i = 0; i < (int)tdu.ContentLength; i++)
|
||||
{
|
||||
var index = tdu.Data[tdu.Offset + i];
|
||||
|
||||
rt.SetValue(Enum.ToObject(enumType, enumTemplate.Constants[index].Value), i);
|
||||
}
|
||||
|
||||
return new AsyncReply(rt);
|
||||
|
||||
default:
|
||||
|
||||
|
||||
@@ -1527,12 +1558,10 @@ public static class DataDeserializer
|
||||
{
|
||||
var rt = new AsyncBag<PropertyValue>();
|
||||
|
||||
Console.WriteLine("PropertyValueArrayParserAsync " + length);
|
||||
|
||||
ListParserAsync(new ParsedTDU() { Data = data, Offset = offset, ContentLength = length }
|
||||
, connection, requestSequence).Then(x =>
|
||||
{
|
||||
Console.WriteLine("PropertyValueArrayParserAsync:Done " + length);
|
||||
|
||||
var ar = (object[])x;
|
||||
var pvs = new List<PropertyValue>();
|
||||
|
||||
@@ -393,6 +393,7 @@ public static class DataSerializer
|
||||
if (value == null)
|
||||
return new TDU(TDUIdentifier.Null, null, 0);
|
||||
|
||||
|
||||
//var warehouse = connection?.Instance?.Warehouse ?? connection?.Server?.Instance?.Warehouse;
|
||||
//if (warehouse == null)
|
||||
// throw new Exception("Warehouse not set.");
|
||||
@@ -557,6 +558,23 @@ public static class DataSerializer
|
||||
{
|
||||
composed = GroupUInt16Codec.Encode((IList<ushort>)value);
|
||||
}
|
||||
else if (tru.Identifier == TRUIdentifier.Enum)
|
||||
{
|
||||
|
||||
var rt = new List<byte>();
|
||||
var template = warehouse.GetTemplateByType(tru.GetRuntimeType(warehouse));
|
||||
|
||||
foreach (var v in value)
|
||||
{
|
||||
var intVal = Convert.ChangeType(v, (v as Enum).GetTypeCode());
|
||||
var ct = template.Constants.FirstOrDefault(x => x.Value.Equals(intVal));
|
||||
if (ct == null)
|
||||
throw new Exception("Unknown Enum.");
|
||||
rt.Add(ct.Index);
|
||||
}
|
||||
|
||||
composed = rt.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
var rt = new List<byte>();
|
||||
|
||||
@@ -1,190 +1,270 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
#nullable enable
|
||||
using System.Globalization;
|
||||
using System.Linq.Expressions;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Esiur.Data;
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.Linq.Expressions;
|
||||
// --- Policies & Options (NET Standard 2.0 compatible) --------------------
|
||||
|
||||
public enum NaNInfinityPolicy
|
||||
{
|
||||
Throw, // Default: throw on NaN/∞ when converting to non-floating types
|
||||
NullIfNullable, // If target is Nullable<decimal>, return null; otherwise throw
|
||||
CoerceZero // Replace NaN/∞ with 0
|
||||
Throw,
|
||||
NullIfNullable,
|
||||
CoerceZero
|
||||
}
|
||||
|
||||
public sealed class RuntimeCastOptions
|
||||
{
|
||||
// Reusable default to avoid per-call allocations
|
||||
public static readonly RuntimeCastOptions Default = new RuntimeCastOptions();
|
||||
|
||||
public bool CheckedNumeric { get; set; } = true;
|
||||
|
||||
// For DateTime/DateTimeOffset parsing
|
||||
public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture;
|
||||
public DateTimeStyles DateTimeStyles { get; set; } = DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal;
|
||||
|
||||
// For enums
|
||||
public DateTimeStyles DateTimeStyles { get; set; } =
|
||||
DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal;
|
||||
|
||||
public bool EnumIgnoreCase { get; set; } = true;
|
||||
public bool EnumMustBeDefined { get; set; } = false;
|
||||
|
||||
// float/double → decimal behavior
|
||||
public NaNInfinityPolicy NaNInfinityPolicy { get; set; } = NaNInfinityPolicy.Throw;
|
||||
}
|
||||
|
||||
// --- Core Caster ----------------------------------------------------------
|
||||
|
||||
public static class RuntimeCaster
|
||||
{
|
||||
// (fromType, toType) -> converter(value, options)
|
||||
private static readonly ConcurrentDictionary<(Type from, Type to),
|
||||
Func<object?, RuntimeCastOptions, object?>> _cache =
|
||||
new ConcurrentDictionary<(Type, Type), Func<object?, RuntimeCastOptions, object?>>();
|
||||
|
||||
public static readonly RuntimeCastOptions Default = new RuntimeCastOptions();
|
||||
// Numeric-only compiled converters
|
||||
private static readonly ConcurrentDictionary<(Type from, Type to, bool @checked),
|
||||
Func<object, object>> _numericCache =
|
||||
new ConcurrentDictionary<(Type, Type, bool), Func<object, object>>();
|
||||
|
||||
// (fromType, toType) -> converter(value, options) (options captured at call time)
|
||||
private static readonly ConcurrentDictionary<(Type from, Type to), Func<object, RuntimeCastOptions, object>> _cache = new();
|
||||
// Per-element converters for collections
|
||||
private static readonly ConcurrentDictionary<(Type from, Type to),
|
||||
Func<object?, RuntimeCastOptions, object?>> _elemConvCache =
|
||||
new ConcurrentDictionary<(Type, Type), Func<object?, RuntimeCastOptions, object?>>();
|
||||
|
||||
// Numeric-only compiled converters (fast path), keyed by checked/unchecked
|
||||
private static readonly ConcurrentDictionary<(Type from, Type to, bool @checked), Func<object, object>> _numericCache = new();
|
||||
// --------- Zero-allocation convenience overloads ---------
|
||||
public static object? Cast(object? value, Type toType)
|
||||
=> Cast(value, toType, RuntimeCastOptions.Default);
|
||||
|
||||
public static object Cast(object value, Type toType, RuntimeCastOptions? options = null)
|
||||
public static object? CastSequence(object? value, Type toType)
|
||||
=> CastSequence(value, toType, RuntimeCastOptions.Default);
|
||||
|
||||
// --------- Main API (options accepted if you want different policies) ---------
|
||||
public static object? Cast(object? value, Type toType, RuntimeCastOptions? options)
|
||||
{
|
||||
options ??= Default;
|
||||
if (toType == null) throw new ArgumentNullException(nameof(toType));
|
||||
var opts = options ?? RuntimeCastOptions.Default;
|
||||
|
||||
if (toType is null) throw new ArgumentNullException(nameof(toType));
|
||||
if (value is null)
|
||||
if (value == null)
|
||||
{
|
||||
if (IsNonNullableValueType(toType))
|
||||
throw new InvalidCastException($"Cannot cast null to non-nullable {toType}.");
|
||||
throw new InvalidCastException("Cannot cast null to non-nullable " + toType + ".");
|
||||
return null;
|
||||
}
|
||||
|
||||
var fromType = value.GetType();
|
||||
if (toType.IsAssignableFrom(fromType)) return value; // already compatible
|
||||
if (toType.IsAssignableFrom(fromType)) return value;
|
||||
|
||||
var fn = _cache.GetOrAdd((fromType, toType), k => BuildConverter(k.from, k.to));
|
||||
return fn(value, options);
|
||||
return fn(value, opts);
|
||||
}
|
||||
|
||||
public static object? CastSequence(object? value, Type toType, RuntimeCastOptions? options)
|
||||
{
|
||||
var opts = options ?? RuntimeCastOptions.Default;
|
||||
if (value == null) return null;
|
||||
|
||||
var fromType = value.GetType();
|
||||
var toUnderlying = Nullable.GetUnderlyingType(toType) ?? toType;
|
||||
|
||||
if (!IsSupportedSeqType(fromType) || !IsSupportedSeqType(toUnderlying))
|
||||
throw new InvalidCastException("Only 1D arrays and List<T> are supported. " + fromType + " → " + toType);
|
||||
|
||||
var fromElem = GetElementType(fromType)!;
|
||||
var toElem = GetElementType(toUnderlying)!;
|
||||
|
||||
// Fast path: same element type
|
||||
if (fromElem == toElem)
|
||||
{
|
||||
if (fromType.IsArray && IsListType(toUnderlying))
|
||||
return ArrayToListDirect((Array)value, toUnderlying);
|
||||
|
||||
if (IsListType(fromType) && toUnderlying.IsArray)
|
||||
return ListToArrayDirect((IList)value, toElem);
|
||||
|
||||
if (fromType.IsArray && toUnderlying.IsArray)
|
||||
return ArrayToArrayDirect((Array)value, toElem);
|
||||
|
||||
if (IsListType(fromType) && IsListType(toUnderlying))
|
||||
return ListToListDirect((IList)value, toUnderlying, toElem);
|
||||
}
|
||||
|
||||
// General path with per-element converter
|
||||
var elemConv = _elemConvCache.GetOrAdd((fromElem, toElem),
|
||||
k => (object? elem, RuntimeCastOptions o) =>
|
||||
{
|
||||
if (elem == null) return null;
|
||||
return Cast(elem, toElem, o);
|
||||
});
|
||||
|
||||
if (fromType.IsArray && IsListType(toUnderlying))
|
||||
return ArrayToListConverted((Array)value, toUnderlying, toElem, elemConv, opts);
|
||||
|
||||
if (IsListType(fromType) && toUnderlying.IsArray)
|
||||
return ListToArrayConverted((IList)value, toElem, elemConv, opts);
|
||||
|
||||
if (fromType.IsArray && toUnderlying.IsArray)
|
||||
return ArrayToArrayConverted((Array)value, toElem, elemConv, opts);
|
||||
|
||||
if (IsListType(fromType) && IsListType(toUnderlying))
|
||||
return ListToListConverted((IList)value, toUnderlying, toElem, elemConv, opts);
|
||||
|
||||
throw new InvalidCastException("Unsupported sequence cast " + fromType + " → " + toType + ".");
|
||||
}
|
||||
|
||||
// ------------------------ Builder ------------------------
|
||||
private static Func<object, RuntimeCastOptions, object> BuildConverter(Type fromType, Type toType)
|
||||
|
||||
private static Func<object?, RuntimeCastOptions, object?> BuildConverter(Type fromType, Type toType)
|
||||
{
|
||||
// Nullable handling is done inside ConvertCore.
|
||||
return (value, opts) => ConvertCore(value, fromType, toType, opts);
|
||||
return (value, opts) => ConvertCore(value!, fromType, toType, opts);
|
||||
}
|
||||
|
||||
// ------------------------ Core Routing ------------------------
|
||||
private static object ConvertCore(object value, Type fromType, Type toType, RuntimeCastOptions opts)
|
||||
|
||||
private static object? ConvertCore(object value, Type fromType, Type toType, RuntimeCastOptions opts)
|
||||
{
|
||||
var toUnderlying = Nullable.GetUnderlyingType(toType) ?? toType;
|
||||
var fromUnderlying = Nullable.GetUnderlyingType(fromType) ?? fromType;
|
||||
|
||||
// If converting nullable source and it's null → result is null if target nullable, else throw
|
||||
if (!fromType.IsValueType && value is null)
|
||||
// Collections early
|
||||
{
|
||||
if (IsNonNullableValueType(toType))
|
||||
throw new InvalidCastException($"Cannot cast null to non-nullable {toType}.");
|
||||
return null;
|
||||
bool handled;
|
||||
var coll = ConvertCollectionsIfAny(value, fromType, toType, opts, out handled);
|
||||
if (handled) return coll;
|
||||
}
|
||||
|
||||
// Special cases first
|
||||
// 1) Enum targets
|
||||
// Enum
|
||||
if (toUnderlying.IsEnum)
|
||||
return ConvertToEnum(value, fromUnderlying, toType, toUnderlying, opts);
|
||||
|
||||
// 2) Guid targets
|
||||
// Guid
|
||||
if (toUnderlying == typeof(Guid))
|
||||
return ConvertToGuid(value, fromUnderlying, toType);
|
||||
|
||||
// 3) DateTime / DateTimeOffset targets
|
||||
// Date/Time
|
||||
if (toUnderlying == typeof(DateTime))
|
||||
return ConvertToDateTime(value, fromUnderlying, toType, opts);
|
||||
|
||||
if (toUnderlying == typeof(DateTimeOffset))
|
||||
return ConvertToDateTimeOffset(value, fromUnderlying, toType, opts);
|
||||
|
||||
// 4) decimal from float/double with NaN/∞ policy
|
||||
if (toUnderlying == typeof(decimal) && (fromUnderlying == typeof(float) || fromUnderlying == typeof(double)))
|
||||
// float/double -> decimal with policy
|
||||
if (toUnderlying == typeof(decimal) &&
|
||||
(fromUnderlying == typeof(float) || fromUnderlying == typeof(double)))
|
||||
{
|
||||
var dec = ConvertFloatDoubleToDecimal(value, fromUnderlying, opts, out bool useNull);
|
||||
if (toType != toUnderlying) // wrap in Nullable<decimal>
|
||||
bool useNull;
|
||||
var dec = ConvertFloatDoubleToDecimal(value, fromUnderlying, opts, out useNull);
|
||||
if (toType != toUnderlying) // Nullable<decimal>
|
||||
return useNull ? null : (decimal?)dec;
|
||||
if (useNull) throw new OverflowException("NaN/Infinity cannot be converted to decimal.");
|
||||
return dec;
|
||||
}
|
||||
|
||||
// 5) General numeric conversions via compiled expression
|
||||
// Numeric -> Numeric
|
||||
if (IsNumeric(fromUnderlying) && IsNumeric(toUnderlying))
|
||||
{
|
||||
var nc = _numericCache.GetOrAdd((fromUnderlying, toUnderlying, opts.CheckedNumeric),
|
||||
k => BuildNumericConverter(k.from, k.to, k.@checked));
|
||||
var result = nc(value);
|
||||
// Wrap into nullable if needed
|
||||
if (toType != toUnderlying) return BoxNullable(result, toUnderlying);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 6) String <-> other basics (Use TypeConverter first; if no path, fall through)
|
||||
// To string
|
||||
if (toUnderlying == typeof(string))
|
||||
return value?.ToString();
|
||||
return value != null ? value.ToString() : null;
|
||||
|
||||
// 7) Last-resort: TypeConverter or ChangeType once, inside this compiled path
|
||||
// Try TypeConverter(target)
|
||||
// TypeConverter(target)
|
||||
var tc = System.ComponentModel.TypeDescriptor.GetConverter(toUnderlying);
|
||||
if (tc.CanConvertFrom(fromUnderlying))
|
||||
{
|
||||
var r = tc.ConvertFrom(null, opts.Culture, value);
|
||||
if (toType != toUnderlying) return BoxNullable(r, toUnderlying);
|
||||
if (toType != toUnderlying) return BoxNullable(r!, toUnderlying);
|
||||
return r!;
|
||||
}
|
||||
|
||||
// Try TypeConverter(source)
|
||||
// TypeConverter(source)
|
||||
var tc2 = System.ComponentModel.TypeDescriptor.GetConverter(fromUnderlying);
|
||||
if (tc2.CanConvertTo(toUnderlying))
|
||||
{
|
||||
var r = tc2.ConvertTo(null, opts.Culture, value, toUnderlying);
|
||||
if (toType != toUnderlying) return BoxNullable(r, toUnderlying);
|
||||
if (toType != toUnderlying) return BoxNullable(r!, toUnderlying);
|
||||
return r!;
|
||||
}
|
||||
|
||||
// Try Convert.ChangeType for IConvertible fallbacks
|
||||
// Convert.ChangeType fallback
|
||||
try
|
||||
{
|
||||
var r = Convert.ChangeType(value, toUnderlying, opts.Culture);
|
||||
if (toType != toUnderlying) return BoxNullable(r, toUnderlying);
|
||||
if (toType != toUnderlying) return BoxNullable(r!, toUnderlying);
|
||||
return r!;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Final attempt: assignable cast via reflection (e.g., interfaces)
|
||||
if (toUnderlying.IsInstanceOfType(value))
|
||||
{
|
||||
if (toType != toUnderlying) return BoxNullable(value, toUnderlying);
|
||||
return value;
|
||||
}
|
||||
throw new InvalidCastException($"Cannot cast {fromType} to {toType}.");
|
||||
throw new InvalidCastException("Cannot cast " + fromType + " to " + toType + ".");
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------ Helpers: Numeric ------------------------
|
||||
private static object? ConvertCollectionsIfAny(object value, Type fromType, Type toType, RuntimeCastOptions opts, out bool handled)
|
||||
{
|
||||
handled = false;
|
||||
var toUnderlying = Nullable.GetUnderlyingType(toType) ?? toType;
|
||||
|
||||
if (!IsSupportedSeqType(fromType) || !IsSupportedSeqType(toUnderlying))
|
||||
return value;
|
||||
|
||||
handled = true;
|
||||
return CastSequence(value, toUnderlying, opts);
|
||||
}
|
||||
|
||||
// ------------------------ Numeric Helpers ------------------------
|
||||
|
||||
private static Func<object, object> BuildNumericConverter(Type from, Type to, bool @checked)
|
||||
{
|
||||
var p = Expression.Parameter(typeof(object), "v");
|
||||
|
||||
Expression val = from.IsValueType
|
||||
? Expression.Unbox(p, from)
|
||||
: Expression.Convert(p, from);
|
||||
Expression val = from.IsValueType ? (Expression)Expression.Unbox(p, from)
|
||||
: (Expression)Expression.Convert(p, from);
|
||||
|
||||
Expression body;
|
||||
try
|
||||
{
|
||||
body = @checked ? Expression.ConvertChecked(val, to) : Expression.Convert(val, to);
|
||||
body = @checked ? (Expression)Expression.ConvertChecked(val, to)
|
||||
: (Expression)Expression.Convert(val, to);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Non-legal numeric convert — should be rare given IsNumeric check.
|
||||
throw new InvalidCastException($"Numeric conversion not supported: {from} -> {to}");
|
||||
throw new InvalidCastException("Numeric conversion not supported: " + from + " -> " + to);
|
||||
}
|
||||
|
||||
// Box result to object
|
||||
Expression boxed = to.IsValueType ? Expression.Convert(body, typeof(object)) : body;
|
||||
Expression boxed = to.IsValueType ? (Expression)Expression.Convert(body, typeof(object)) : body;
|
||||
return Expression.Lambda<Func<object, object>>(boxed, p).Compile();
|
||||
}
|
||||
|
||||
@@ -200,17 +280,19 @@ public static class RuntimeCaster
|
||||
}
|
||||
|
||||
private static bool IsNonNullableValueType(Type t)
|
||||
=> t.IsValueType && Nullable.GetUnderlyingType(t) is null;
|
||||
{
|
||||
return t.IsValueType && Nullable.GetUnderlyingType(t) == null;
|
||||
}
|
||||
|
||||
private static object BoxNullable(object value, Type underlying)
|
||||
{
|
||||
// Create Nullable<T>(value) then box
|
||||
var nt = typeof(Nullable<>).MakeGenericType(underlying);
|
||||
var ctor = nt.GetConstructor(new[] { underlying })!;
|
||||
return ctor.Invoke(new[] { value });
|
||||
var ctor = nt.GetConstructor(new[] { underlying });
|
||||
return ctor!.Invoke(new[] { value });
|
||||
}
|
||||
|
||||
// ------------------------ Helpers: NaN/∞ to decimal ------------------------
|
||||
// ------------------------ NaN/∞ to decimal ------------------------
|
||||
|
||||
private static decimal ConvertFloatDoubleToDecimal(object value, Type fromUnderlying, RuntimeCastOptions opts, out bool useNull)
|
||||
{
|
||||
useNull = false;
|
||||
@@ -220,76 +302,79 @@ public static class RuntimeCaster
|
||||
var f = (float)value;
|
||||
if (float.IsNaN(f) || float.IsInfinity(f))
|
||||
{
|
||||
switch (opts.NaNInfinityPolicy)
|
||||
{
|
||||
case NaNInfinityPolicy.NullIfNullable: useNull = true; return default;
|
||||
case NaNInfinityPolicy.CoerceZero: return 0m;
|
||||
default: throw new OverflowException("Cannot convert NaN/Infinity to decimal.");
|
||||
}
|
||||
if (opts.NaNInfinityPolicy == NaNInfinityPolicy.NullIfNullable) { useNull = true; return 0m; }
|
||||
if (opts.NaNInfinityPolicy == NaNInfinityPolicy.CoerceZero) return 0m;
|
||||
throw new OverflowException("Cannot convert NaN/Infinity to decimal.");
|
||||
}
|
||||
return opts.CheckedNumeric ? checked((decimal)f) : (decimal)f;
|
||||
}
|
||||
else // double
|
||||
else
|
||||
{
|
||||
var d = (double)value;
|
||||
if (double.IsNaN(d) || double.IsInfinity(d))
|
||||
{
|
||||
switch (opts.NaNInfinityPolicy)
|
||||
{
|
||||
case NaNInfinityPolicy.NullIfNullable: useNull = true; return default;
|
||||
case NaNInfinityPolicy.CoerceZero: return 0m;
|
||||
default: throw new OverflowException("Cannot convert NaN/Infinity to decimal.");
|
||||
}
|
||||
if (opts.NaNInfinityPolicy == NaNInfinityPolicy.NullIfNullable) { useNull = true; return 0m; }
|
||||
if (opts.NaNInfinityPolicy == NaNInfinityPolicy.CoerceZero) return 0m;
|
||||
throw new OverflowException("Cannot convert NaN/Infinity to decimal.");
|
||||
}
|
||||
return opts.CheckedNumeric ? checked((decimal)d) : (decimal)d;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------ Helpers: Enum ------------------------
|
||||
private static object ConvertToEnum(object value, Type fromUnderlying, Type toType, Type enumType, RuntimeCastOptions opts)
|
||||
// ------------------------ Enum ------------------------
|
||||
// Note: .NET Standard 2.0 lacks non-generic TryParse(Type, …, ignoreCase).
|
||||
// We use Enum.Parse(Type, string, bool ignoreCase) with try/catch.
|
||||
|
||||
private static object? ConvertToEnum(object value, Type fromUnderlying, Type toType, Type enumType, RuntimeCastOptions opts)
|
||||
{
|
||||
// Nullable<Enum> wrapping
|
||||
bool wrapNullable = toType != enumType;
|
||||
|
||||
// String → Enum
|
||||
if (fromUnderlying == typeof(string))
|
||||
{
|
||||
var parsed = Enum.Parse(enumType, (string)value, opts.EnumIgnoreCase);
|
||||
|
||||
if (opts.EnumMustBeDefined && !Enum.IsDefined(enumType, parsed!))
|
||||
throw new InvalidCastException($"Value '{value}' is not a defined member of {enumType.Name}.");
|
||||
|
||||
return wrapNullable ? BoxNullable(parsed!, enumType) : parsed!;
|
||||
object parsed;
|
||||
try
|
||||
{
|
||||
parsed = Enum.Parse(enumType, (string)value, opts.EnumIgnoreCase);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
throw new InvalidCastException("Cannot parse '" + value + "' to " + enumType.Name + ".");
|
||||
}
|
||||
|
||||
if (opts.EnumMustBeDefined && !Enum.IsDefined(enumType, parsed))
|
||||
throw new InvalidCastException("Value '" + value + "' is not a defined member of " + enumType.Name + ".");
|
||||
|
||||
return wrapNullable ? BoxNullable(parsed, enumType) : parsed;
|
||||
}
|
||||
|
||||
// Numeric → Enum
|
||||
if (IsNumeric(fromUnderlying))
|
||||
{
|
||||
// Convert numeric to enum’s underlying integral type first (checked/unchecked handled by compiled numeric path)
|
||||
var et = Enum.GetUnderlyingType(enumType);
|
||||
var numConv = _numericCache.GetOrAdd((fromUnderlying, et, true), k => BuildNumericConverter(k.from, k.to, k.@checked));
|
||||
var numConv = _numericCache.GetOrAdd((fromUnderlying, et, true),
|
||||
k => BuildNumericConverter(k.from, k.to, k.@checked));
|
||||
var integral = numConv(value);
|
||||
|
||||
var enumObj = Enum.ToObject(enumType, integral);
|
||||
if (opts.EnumMustBeDefined && !Enum.IsDefined(enumType, enumObj))
|
||||
throw new InvalidCastException($"Numeric value {integral} is not a defined member of {enumType.Name}.");
|
||||
throw new InvalidCastException("Numeric value " + integral + " is not a defined member of " + enumType.Name + ".");
|
||||
|
||||
return wrapNullable ? BoxNullable(enumObj, enumType) : enumObj;
|
||||
}
|
||||
|
||||
// Fallback: not supported
|
||||
throw new InvalidCastException($"Cannot cast {fromUnderlying} to enum {enumType.Name}.");
|
||||
throw new InvalidCastException("Cannot cast " + fromUnderlying + " to enum " + enumType.Name + ".");
|
||||
}
|
||||
|
||||
// ------------------------ Helpers: Guid ------------------------
|
||||
private static object ConvertToGuid(object value, Type fromUnderlying, Type toType)
|
||||
// ------------------------ Guid ------------------------
|
||||
|
||||
private static object? ConvertToGuid(object value, Type fromUnderlying, Type toType)
|
||||
{
|
||||
bool wrapNullable = toType != typeof(Guid);
|
||||
|
||||
if (fromUnderlying == typeof(string))
|
||||
{
|
||||
if (!Guid.TryParse((string)value, out var g))
|
||||
throw new InvalidCastException($"Cannot parse '{value}' to Guid.");
|
||||
Guid g;
|
||||
if (!Guid.TryParse((string)value, out g))
|
||||
throw new InvalidCastException("Cannot parse '" + value + "' to Guid.");
|
||||
return wrapNullable ? (Guid?)g : g;
|
||||
}
|
||||
|
||||
@@ -302,31 +387,31 @@ public static class RuntimeCaster
|
||||
return wrapNullable ? (Guid?)g : g;
|
||||
}
|
||||
|
||||
throw new InvalidCastException($"Cannot cast {fromUnderlying} to Guid.");
|
||||
throw new InvalidCastException("Cannot cast " + fromUnderlying + " to Guid.");
|
||||
}
|
||||
|
||||
// ------------------------ Helpers: DateTime / DateTimeOffset ------------------------
|
||||
private static object ConvertToDateTime(object value, Type fromUnderlying, Type toType, RuntimeCastOptions opts)
|
||||
// ------------------------ DateTime / DateTimeOffset ------------------------
|
||||
|
||||
private static object? ConvertToDateTime(object value, Type fromUnderlying, Type toType, RuntimeCastOptions opts)
|
||||
{
|
||||
bool wrapNullable = toType != typeof(DateTime);
|
||||
|
||||
if (fromUnderlying == typeof(string))
|
||||
{
|
||||
if (!DateTime.TryParse((string)value, opts.Culture, opts.DateTimeStyles, out var dt))
|
||||
throw new InvalidCastException($"Cannot parse '{value}' to DateTime.");
|
||||
DateTime dt;
|
||||
if (!DateTime.TryParse((string)value, opts.Culture, opts.DateTimeStyles, out dt))
|
||||
throw new InvalidCastException("Cannot parse '" + value + "' to DateTime.");
|
||||
return wrapNullable ? (DateTime?)dt : dt;
|
||||
}
|
||||
|
||||
if (fromUnderlying == typeof(long))
|
||||
{
|
||||
// Treat as ticks
|
||||
var dt = new DateTime((long)value, DateTimeKind.Unspecified);
|
||||
return wrapNullable ? (DateTime?)dt : dt;
|
||||
}
|
||||
|
||||
if (fromUnderlying == typeof(double))
|
||||
{
|
||||
// Treat as OADate if finite
|
||||
var d = (double)value;
|
||||
if (double.IsNaN(d) || double.IsInfinity(d))
|
||||
throw new InvalidCastException("Cannot convert NaN/Infinity to DateTime.");
|
||||
@@ -334,23 +419,23 @@ public static class RuntimeCaster
|
||||
return wrapNullable ? (DateTime?)dt : dt;
|
||||
}
|
||||
|
||||
throw new InvalidCastException($"Cannot cast {fromUnderlying} to DateTime.");
|
||||
throw new InvalidCastException("Cannot cast " + fromUnderlying + " to DateTime.");
|
||||
}
|
||||
|
||||
private static object ConvertToDateTimeOffset(object value, Type fromUnderlying, Type toType, RuntimeCastOptions opts)
|
||||
private static object? ConvertToDateTimeOffset(object value, Type fromUnderlying, Type toType, RuntimeCastOptions opts)
|
||||
{
|
||||
bool wrapNullable = toType != typeof(DateTimeOffset);
|
||||
|
||||
if (fromUnderlying == typeof(string))
|
||||
{
|
||||
if (!DateTimeOffset.TryParse((string)value, opts.Culture, opts.DateTimeStyles, out var dto))
|
||||
throw new InvalidCastException($"Cannot parse '{value}' to DateTimeOffset.");
|
||||
DateTimeOffset dto;
|
||||
if (!DateTimeOffset.TryParse((string)value, opts.Culture, opts.DateTimeStyles, out dto))
|
||||
throw new InvalidCastException("Cannot parse '" + value + "' to DateTimeOffset.");
|
||||
return wrapNullable ? (DateTimeOffset?)dto : dto;
|
||||
}
|
||||
|
||||
if (fromUnderlying == typeof(long))
|
||||
{
|
||||
// Treat as ticks since 0001-01-01
|
||||
var dto = new DateTimeOffset(new DateTime((long)value, DateTimeKind.Unspecified));
|
||||
return wrapNullable ? (DateTimeOffset?)dto : dto;
|
||||
}
|
||||
@@ -365,6 +450,111 @@ public static class RuntimeCaster
|
||||
return wrapNullable ? (DateTimeOffset?)dto : dto;
|
||||
}
|
||||
|
||||
throw new InvalidCastException($"Cannot cast {fromUnderlying} to DateTimeOffset.");
|
||||
throw new InvalidCastException("Cannot cast " + fromUnderlying + " to DateTimeOffset.");
|
||||
}
|
||||
|
||||
// ------------------------ Collections (arrays/lists) ------------------------
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsListType(Type t)
|
||||
{
|
||||
return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsSupportedSeqType(Type t)
|
||||
{
|
||||
return (t.IsArray && t.GetArrayRank() == 1) || IsListType(t);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Type? GetElementType(Type t)
|
||||
{
|
||||
if (t.IsArray) return t.GetElementType();
|
||||
if (IsListType(t)) return t.GetGenericArguments()[0];
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fast-path (same element types)
|
||||
private static object ArrayToListDirect(Array src, Type listTarget)
|
||||
{
|
||||
var list = (IList)Activator.CreateInstance(listTarget, src.Length)!;
|
||||
foreach (var e in src) list.Add(e);
|
||||
return list;
|
||||
}
|
||||
|
||||
private static object ListToArrayDirect(IList src, Type elemType)
|
||||
{
|
||||
var arr = Array.CreateInstance(elemType, src.Count);
|
||||
for (int i = 0; i < src.Count; i++) arr.SetValue(src[i], i);
|
||||
return arr;
|
||||
}
|
||||
|
||||
private static object ArrayToArrayDirect(Array src, Type elemType)
|
||||
{
|
||||
var dst = Array.CreateInstance(elemType, src.Length);
|
||||
Array.Copy(src, dst, src.Length);
|
||||
return dst;
|
||||
}
|
||||
|
||||
private static object ListToListDirect(IList src, Type listTarget, Type elemType)
|
||||
{
|
||||
var list = (IList)Activator.CreateInstance(listTarget, src.Count)!;
|
||||
for (int i = 0; i < src.Count; i++) list.Add(src[i]);
|
||||
return list;
|
||||
}
|
||||
|
||||
// Converted element paths
|
||||
private static object ArrayToListConverted(
|
||||
Array src, Type listTarget, Type toElem,
|
||||
Func<object?, RuntimeCastOptions, object?> elemConv, RuntimeCastOptions opts)
|
||||
{
|
||||
var list = (IList)Activator.CreateInstance(listTarget, src.Length)!;
|
||||
for (int i = 0; i < src.Length; i++)
|
||||
{
|
||||
var v = src.GetValue(i);
|
||||
list.Add(elemConv(v, opts));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static object ListToArrayConverted(
|
||||
IList src, Type toElem,
|
||||
Func<object?, RuntimeCastOptions, object?> elemConv, RuntimeCastOptions opts)
|
||||
{
|
||||
var arr = Array.CreateInstance(toElem, src.Count);
|
||||
for (int i = 0; i < src.Count; i++)
|
||||
{
|
||||
var v = src[i];
|
||||
arr.SetValue(elemConv(v, opts), i);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
private static object ArrayToArrayConverted(
|
||||
Array src, Type toElem,
|
||||
Func<object?, RuntimeCastOptions, object?> elemConv, RuntimeCastOptions opts)
|
||||
{
|
||||
var dst = Array.CreateInstance(toElem, src.Length);
|
||||
for (int i = 0; i < src.Length; i++)
|
||||
{
|
||||
var v = src.GetValue(i);
|
||||
dst.SetValue(elemConv(v, opts), i);
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
private static object ListToListConverted(
|
||||
IList src, Type listTarget, Type toElem,
|
||||
Func<object?, RuntimeCastOptions, object?> elemConv, RuntimeCastOptions opts)
|
||||
{
|
||||
var dst = (IList)Activator.CreateInstance(listTarget, src.Count)!;
|
||||
for (int i = 0; i < src.Count; i++)
|
||||
{
|
||||
var v = src[i];
|
||||
dst.Add(elemConv(v, opts));
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -169,7 +170,7 @@ namespace Esiur.Data
|
||||
(TRUIdentifier.Record) => typeof(IRecord),
|
||||
(TRUIdentifier.TypedRecord) => warehouse.GetTemplateByClassId((UUID)UUID!, TemplateType.Record)?.DefinedType,
|
||||
(TRUIdentifier.TypedResource) => warehouse.GetTemplateByClassId((UUID)UUID!, TemplateType.Resource)?.DefinedType,
|
||||
(TRUIdentifier.Enum) =>warehouse.GetTemplateByClassId((UUID)UUID!, TemplateType.Enum)?.DefinedType,
|
||||
(TRUIdentifier.Enum) => warehouse.GetTemplateByClassId((UUID)UUID!, TemplateType.Enum)?.DefinedType,
|
||||
|
||||
_ => null
|
||||
};
|
||||
@@ -267,11 +268,16 @@ namespace Esiur.Data
|
||||
// case TRUIdentifier. }
|
||||
//}
|
||||
|
||||
private static Dictionary<Type, TRU> _cache = new Dictionary<Type, TRU>();
|
||||
|
||||
public static TRU? FromType(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return new TRU(TRUIdentifier.Void, true);
|
||||
|
||||
if (_cache.ContainsKey(type))
|
||||
return _cache[type];
|
||||
|
||||
var nullable = false;
|
||||
|
||||
var nullType = System.Nullable.GetUnderlyingType(type);
|
||||
@@ -282,8 +288,12 @@ namespace Esiur.Data
|
||||
nullable = true;
|
||||
}
|
||||
|
||||
TRU? tru = null;
|
||||
|
||||
if (type == typeof(IResource))
|
||||
{
|
||||
return new TRU(TRUIdentifier.Resource, nullable);
|
||||
}
|
||||
else if (type == typeof(IRecord) || type == typeof(Record))
|
||||
return new TRU(TRUIdentifier.Record, nullable);
|
||||
else if (type == typeof(Map<object, object>)
|
||||
@@ -291,19 +301,27 @@ namespace Esiur.Data
|
||||
return new TRU(TRUIdentifier.Map, nullable);
|
||||
else if (Codec.ImplementsInterface(type, typeof(IResource)))
|
||||
{
|
||||
return new TRU(
|
||||
tru = new TRU(
|
||||
TRUIdentifier.TypedResource,
|
||||
nullable,
|
||||
TypeTemplate.GetTypeUUID(type)
|
||||
);
|
||||
|
||||
//_cache.Add(type, tru);
|
||||
|
||||
//return tru;
|
||||
}
|
||||
else if (Codec.ImplementsInterface(type, typeof(IRecord)))
|
||||
{
|
||||
return new TRU(
|
||||
tru = new TRU(
|
||||
TRUIdentifier.TypedRecord,
|
||||
nullable,
|
||||
TypeTemplate.GetTypeUUID(type)
|
||||
);
|
||||
|
||||
//_cache.Add(type, tru);
|
||||
|
||||
//return tru;
|
||||
}
|
||||
else if (type.IsGenericType)
|
||||
{
|
||||
@@ -315,7 +333,7 @@ namespace Esiur.Data
|
||||
var args = type.GetGenericArguments();
|
||||
if (args[0] == typeof(object))
|
||||
{
|
||||
return new TRU(TRUIdentifier.List, nullable);
|
||||
tru = new TRU(TRUIdentifier.List, nullable);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -323,7 +341,8 @@ namespace Esiur.Data
|
||||
if (subType == null) // unrecongnized type
|
||||
return null;
|
||||
|
||||
return new TRU(TRUIdentifier.TypedList, nullable, null,
|
||||
|
||||
tru = new TRU(TRUIdentifier.TypedList, nullable, null,
|
||||
new TRU[] { subType });
|
||||
|
||||
}
|
||||
@@ -334,7 +353,7 @@ namespace Esiur.Data
|
||||
var args = type.GetGenericArguments();
|
||||
if (args[0] == typeof(object) && args[1] == typeof(object))
|
||||
{
|
||||
return new TRU(TRUIdentifier.Map, nullable);
|
||||
tru = new TRU(TRUIdentifier.Map, nullable);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -346,8 +365,9 @@ namespace Esiur.Data
|
||||
if (subType2 == null)
|
||||
return null;
|
||||
|
||||
return new TRU(TRUIdentifier.TypedMap, nullable, null,
|
||||
tru = new TRU(TRUIdentifier.TypedMap, nullable, null,
|
||||
new TRU[] { subType1, subType2 });
|
||||
|
||||
}
|
||||
}
|
||||
else if (genericType == typeof(ValueTuple<,>))
|
||||
@@ -362,7 +382,9 @@ namespace Esiur.Data
|
||||
subTypes[i] = t;
|
||||
}
|
||||
|
||||
return new TRU(TRUIdentifier.Tuple2, nullable, null, subTypes);
|
||||
|
||||
tru = new TRU(TRUIdentifier.Tuple2, nullable, null, subTypes);
|
||||
|
||||
}
|
||||
else if (genericType == typeof(ValueTuple<,,>))
|
||||
{
|
||||
@@ -376,7 +398,8 @@ namespace Esiur.Data
|
||||
subTypes[i] = t;
|
||||
}
|
||||
|
||||
return new TRU(TRUIdentifier.Tuple3, nullable, null, subTypes);
|
||||
tru = new TRU(TRUIdentifier.Tuple3, nullable, null, subTypes);
|
||||
|
||||
}
|
||||
else if (genericType == typeof(ValueTuple<,,,>))
|
||||
{
|
||||
@@ -391,7 +414,7 @@ namespace Esiur.Data
|
||||
subTypes[i] = t;
|
||||
}
|
||||
|
||||
return new TRU(TRUIdentifier.Tuple4, nullable, null, subTypes);
|
||||
tru = new TRU(TRUIdentifier.Tuple4, nullable, null, subTypes);
|
||||
}
|
||||
else if (genericType == typeof(ValueTuple<,,,,>))
|
||||
{
|
||||
@@ -405,7 +428,7 @@ namespace Esiur.Data
|
||||
subTypes[i] = t;
|
||||
}
|
||||
|
||||
return new TRU(TRUIdentifier.Tuple5, nullable, null, subTypes);
|
||||
tru = new TRU(TRUIdentifier.Tuple5, nullable, null, subTypes);
|
||||
}
|
||||
else if (genericType == typeof(ValueTuple<,,,,,>))
|
||||
{
|
||||
@@ -419,7 +442,7 @@ namespace Esiur.Data
|
||||
subTypes[i] = t;
|
||||
}
|
||||
|
||||
return new TRU(TRUIdentifier.Tuple6, nullable, null, subTypes);
|
||||
tru = new TRU(TRUIdentifier.Tuple6, nullable, null, subTypes);
|
||||
}
|
||||
else if (genericType == typeof(ValueTuple<,,,,,,>))
|
||||
{
|
||||
@@ -433,7 +456,7 @@ namespace Esiur.Data
|
||||
subTypes[i] = t;
|
||||
}
|
||||
|
||||
return new TRU(TRUIdentifier.Tuple7, nullable, null, subTypes);
|
||||
tru = new TRU(TRUIdentifier.Tuple7, nullable, null, subTypes);
|
||||
}
|
||||
else
|
||||
return null;
|
||||
@@ -442,7 +465,7 @@ namespace Esiur.Data
|
||||
{
|
||||
var elementType = type.GetElementType();
|
||||
if (elementType == typeof(object))
|
||||
return new TRU(TRUIdentifier.List, nullable);
|
||||
tru = new TRU(TRUIdentifier.List, nullable);
|
||||
else
|
||||
{
|
||||
var subType = FromType(elementType);
|
||||
@@ -450,14 +473,14 @@ namespace Esiur.Data
|
||||
if (subType == null)
|
||||
return null;
|
||||
|
||||
return new TRU(TRUIdentifier.TypedList, nullable, null,
|
||||
tru = new TRU(TRUIdentifier.TypedList, nullable, null,
|
||||
new TRU[] { subType });
|
||||
|
||||
}
|
||||
}
|
||||
else if (type.IsEnum)
|
||||
{
|
||||
return new TRU(TRUIdentifier.Enum, nullable, TypeTemplate.GetTypeUUID(type));
|
||||
tru = new TRU(TRUIdentifier.Enum, nullable, TypeTemplate.GetTypeUUID(type));
|
||||
}
|
||||
else if (type.IsInterface)
|
||||
{
|
||||
@@ -469,6 +492,13 @@ namespace Esiur.Data
|
||||
|
||||
//}
|
||||
|
||||
if (tru != null)
|
||||
{
|
||||
_cache.Add(type, tru);
|
||||
return tru;
|
||||
}
|
||||
|
||||
// last check
|
||||
return type switch
|
||||
{
|
||||
_ when type == typeof(void) => new TRU(TRUIdentifier.Void, nullable),
|
||||
|
||||
@@ -360,8 +360,8 @@ public partial class DistributedConnection : NetworkConnection, IStore
|
||||
}).Error(e =>
|
||||
{
|
||||
// do nothing
|
||||
Console.WriteLine("Queue is empty");
|
||||
//throw e;
|
||||
//Console.WriteLine("Queue is empty");
|
||||
throw e;
|
||||
});
|
||||
|
||||
// set local nonce
|
||||
@@ -434,7 +434,7 @@ public partial class DistributedConnection : NetworkConnection, IStore
|
||||
if (packet.DataType == null)
|
||||
return offset;
|
||||
|
||||
Console.WriteLine("Incoming: " + packet + " " + packet.CallbackId);
|
||||
//Console.WriteLine("Incoming: " + packet + " " + packet.CallbackId);
|
||||
|
||||
if (packet.Method == IIPPacketMethod.Notification)
|
||||
{
|
||||
@@ -1044,7 +1044,7 @@ public partial class DistributedConnection : NetworkConnection, IStore
|
||||
|
||||
SendParams()
|
||||
.AddUInt8((byte)IIPAuthPacketAcknowledge.NoAuthNoAuth)
|
||||
.AddUInt8Array(Codec.Compose(localHeaders,this.Instance.Warehouse, this))
|
||||
.AddUInt8Array(Codec.Compose(localHeaders,Server.Instance.Warehouse, this))
|
||||
.Done();
|
||||
}
|
||||
else
|
||||
@@ -1711,7 +1711,8 @@ public partial class DistributedConnection : NetworkConnection, IStore
|
||||
|
||||
AsyncReply<bool> IStore.Remove(IResource resource)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// @TODO: this is called when no connection is possible
|
||||
return new AsyncReply<bool>(true);
|
||||
}
|
||||
|
||||
public AsyncReply<bool> Remove(string path)
|
||||
|
||||
@@ -35,6 +35,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -180,17 +181,19 @@ partial class DistributedConnection
|
||||
}
|
||||
|
||||
|
||||
public AsyncReply StaticCall(UUID classId, byte index, Map<byte, object> parameters)
|
||||
public AsyncReply StaticCall(UUID classId, byte index, object parameters)
|
||||
{
|
||||
return SendRequest(IIPPacketRequest.StaticCall, classId, index, parameters);
|
||||
}
|
||||
|
||||
public AsyncReply Call(string procedureCall, params object[] parameters)
|
||||
{
|
||||
var args = new Map<byte, object>();
|
||||
for (byte i = 0; i < parameters.Length; i++)
|
||||
args.Add(i, parameters[i]);
|
||||
return Call(procedureCall, args);
|
||||
//var args = new Map<byte, object>();
|
||||
//for (byte i = 0; i < parameters.Length; i++)
|
||||
// args.Add(i, parameters[i]);
|
||||
// return Call(procedureCall, parameters);
|
||||
|
||||
return SendRequest(IIPPacketRequest.ProcedureCall, procedureCall, parameters);
|
||||
}
|
||||
|
||||
public AsyncReply Call(string procedureCall, Map<byte, object> parameters)
|
||||
@@ -198,7 +201,7 @@ partial class DistributedConnection
|
||||
return SendRequest(IIPPacketRequest.ProcedureCall, procedureCall, parameters);
|
||||
}
|
||||
|
||||
internal AsyncReply SendInvoke(uint instanceId, byte index, Map<byte, object> parameters)
|
||||
internal AsyncReply SendInvoke(uint instanceId, byte index, object parameters)
|
||||
{
|
||||
return SendRequest(IIPPacketRequest.InvokeFunction, instanceId, index, parameters);
|
||||
}
|
||||
@@ -266,7 +269,7 @@ partial class DistributedConnection
|
||||
{
|
||||
var req = requests.Take(callbackId);
|
||||
|
||||
Console.WriteLine("Completed " + callbackId);
|
||||
//Console.WriteLine("Completed " + callbackId);
|
||||
|
||||
if (req == null)
|
||||
{
|
||||
@@ -283,7 +286,7 @@ partial class DistributedConnection
|
||||
})
|
||||
.Error(e =>
|
||||
{
|
||||
Console.WriteLine(callbackId + ": failed");
|
||||
//Console.WriteLine(callbackId + ": failed");
|
||||
req.TriggerError(e);
|
||||
});
|
||||
}
|
||||
@@ -1016,7 +1019,7 @@ partial class DistributedConnection
|
||||
{
|
||||
reply.Then(results =>
|
||||
{
|
||||
var arguments = (Map<byte, object>)results;
|
||||
//var arguments = (Map<byte, object>)results;
|
||||
|
||||
// un hold the socket to send data immediately
|
||||
this.Socket.Unhold();
|
||||
@@ -1029,7 +1032,7 @@ partial class DistributedConnection
|
||||
// return;
|
||||
//}
|
||||
|
||||
InvokeFunction(call.Value.Template, callback, arguments, IIPPacketRequest.ProcedureCall, call.Value.Delegate.Target);
|
||||
InvokeFunction(call.Value.Template, callback, results, IIPPacketRequest.ProcedureCall, call.Value.Delegate.Target);
|
||||
|
||||
}).Error(x =>
|
||||
{
|
||||
@@ -1038,13 +1041,13 @@ partial class DistributedConnection
|
||||
}
|
||||
else
|
||||
{
|
||||
var arguments = (Map<byte, object>)parsed;
|
||||
//var arguments = (Map<byte, object>)parsed;
|
||||
|
||||
// un hold the socket to send data immediately
|
||||
this.Socket.Unhold();
|
||||
|
||||
// @TODO: Make managers for procedure calls
|
||||
InvokeFunction(call.Value.Template, callback, arguments, IIPPacketRequest.ProcedureCall, call.Value.Delegate.Target);
|
||||
InvokeFunction(call.Value.Template, callback, parsed, IIPPacketRequest.ProcedureCall, call.Value.Delegate.Target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1090,7 +1093,7 @@ partial class DistributedConnection
|
||||
{
|
||||
reply.Then(results =>
|
||||
{
|
||||
var arguments = (Map<byte, object>)results;
|
||||
//var arguments = (Map<byte, object>)results;
|
||||
|
||||
// un hold the socket to send data immediately
|
||||
this.Socket.Unhold();
|
||||
@@ -1104,7 +1107,7 @@ partial class DistributedConnection
|
||||
// return;
|
||||
//}
|
||||
|
||||
InvokeFunction(ft, callback, arguments, IIPPacketRequest.StaticCall, null);
|
||||
InvokeFunction(ft, callback, results, IIPPacketRequest.StaticCall, null);
|
||||
|
||||
}).Error(x =>
|
||||
{
|
||||
@@ -1113,7 +1116,7 @@ partial class DistributedConnection
|
||||
}
|
||||
else
|
||||
{
|
||||
var arguments = (Map<byte, object>)parsed;
|
||||
//var arguments = (Map<byte, object>)parsed;
|
||||
|
||||
// un hold the socket to send data immediately
|
||||
this.Socket.Unhold();
|
||||
@@ -1121,7 +1124,7 @@ partial class DistributedConnection
|
||||
// @TODO: Make managers for static calls
|
||||
|
||||
|
||||
InvokeFunction(ft, callback, arguments, IIPPacketRequest.StaticCall, null);
|
||||
InvokeFunction(ft, callback, parsed, IIPPacketRequest.StaticCall, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1157,14 +1160,14 @@ partial class DistributedConnection
|
||||
{
|
||||
(parsed as AsyncReply).Then(result =>
|
||||
{
|
||||
var arguments = (Map<byte, object>)result;
|
||||
// var arguments = result;
|
||||
|
||||
// un hold the socket to send data immediately
|
||||
this.Socket.Unhold();
|
||||
|
||||
if (r is DistributedResource)
|
||||
{
|
||||
var rt = (r as DistributedResource)._Invoke(index, arguments);
|
||||
var rt = (r as DistributedResource)._Invoke(index, result);
|
||||
if (rt != null)
|
||||
{
|
||||
rt.Then(res =>
|
||||
@@ -1187,20 +1190,20 @@ partial class DistributedConnection
|
||||
return;
|
||||
}
|
||||
|
||||
InvokeFunction(ft, callback, arguments, IIPPacketRequest.InvokeFunction, r);
|
||||
InvokeFunction(ft, callback, result, IIPPacketRequest.InvokeFunction, r);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var arguments = (Map<byte, object>)parsed;
|
||||
//var arguments = (Map<byte, object>)parsed;
|
||||
|
||||
// un hold the socket to send data immediately
|
||||
this.Socket.Unhold();
|
||||
|
||||
if (r is DistributedResource)
|
||||
{
|
||||
var rt = (r as DistributedResource)._Invoke(index, arguments);
|
||||
var rt = (r as DistributedResource)._Invoke(index, parsed);
|
||||
if (rt != null)
|
||||
{
|
||||
rt.Then(res =>
|
||||
@@ -1223,7 +1226,7 @@ partial class DistributedConnection
|
||||
return;
|
||||
}
|
||||
|
||||
InvokeFunction(ft, callback, arguments, IIPPacketRequest.InvokeFunction, r);
|
||||
InvokeFunction(ft, callback, parsed, IIPPacketRequest.InvokeFunction, r);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1232,7 +1235,7 @@ partial class DistributedConnection
|
||||
|
||||
|
||||
|
||||
void InvokeFunction(FunctionTemplate ft, uint callback, Map<byte, object> arguments, IIPPacketRequest actionType, object target = null)
|
||||
void InvokeFunction(FunctionTemplate ft, uint callback, object arguments, IIPPacketRequest actionType, object target = null)
|
||||
{
|
||||
|
||||
// cast arguments
|
||||
@@ -1245,16 +1248,37 @@ partial class DistributedConnection
|
||||
if (pis.Length > 0)
|
||||
{
|
||||
if (pis.Last().ParameterType == typeof(DistributedConnection))
|
||||
{
|
||||
if (arguments is Map<byte, object> indexedArguments)
|
||||
{
|
||||
for (byte i = 0; i < pis.Length - 1; i++)
|
||||
{
|
||||
if (arguments.ContainsKey(i))
|
||||
args[i] = RuntimeCaster.Cast(arguments[i], pis[i].ParameterType);
|
||||
if (indexedArguments.ContainsKey(i))
|
||||
args[i] = RuntimeCaster.Cast(indexedArguments[i], pis[i].ParameterType);
|
||||
else if (ft.Arguments[i].Type.Nullable)
|
||||
args[i] = null;
|
||||
else
|
||||
args[i] = Type.Missing;
|
||||
}
|
||||
}
|
||||
else if (arguments is object[] arrayArguments)
|
||||
{
|
||||
for (var i = 0; (i < arrayArguments.Length) && (i < pis.Length - 1); i++)
|
||||
{
|
||||
args[i] = RuntimeCaster.Cast(arrayArguments[i], pis[i].ParameterType);
|
||||
}
|
||||
|
||||
for (var i = arrayArguments.Length; i < pis.Length - 1; i++)
|
||||
{
|
||||
args[i] = Type.Missing;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// assume first argument
|
||||
// Note: if object[] is intended, sender should send nest it withing object[] { object[] }
|
||||
if (pis.Length > 1)
|
||||
args[0] = RuntimeCaster.Cast(arguments, pis[0].ParameterType);
|
||||
}
|
||||
|
||||
args[args.Length - 1] = this;
|
||||
@@ -1262,33 +1286,79 @@ partial class DistributedConnection
|
||||
else if (pis.Last().ParameterType == typeof(InvocationContext))
|
||||
{
|
||||
context = new InvocationContext(this, callback);
|
||||
|
||||
if (arguments is Map<byte, object> indexedArguments)
|
||||
{
|
||||
for (byte i = 0; i < pis.Length - 1; i++)
|
||||
{
|
||||
if (arguments.ContainsKey(i))
|
||||
args[i] = RuntimeCaster.Cast(arguments[i], pis[i].ParameterType);
|
||||
if (indexedArguments.ContainsKey(i))
|
||||
args[i] = RuntimeCaster.Cast(indexedArguments[i], pis[i].ParameterType);
|
||||
else if (ft.Arguments[i].Type.Nullable)
|
||||
args[i] = null;
|
||||
else
|
||||
args[i] = Type.Missing;
|
||||
|
||||
}
|
||||
}
|
||||
else if (arguments is object[] arrayArguments)
|
||||
{
|
||||
for (var i = 0; (i < arrayArguments.Length) && (i < pis.Length - 1); i++)
|
||||
{
|
||||
args[i] = RuntimeCaster.Cast(arrayArguments[i], pis[i].ParameterType);
|
||||
}
|
||||
|
||||
for (var i = arrayArguments.Length; i < pis.Length - 1; i++)
|
||||
{
|
||||
args[i] = Type.Missing;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// assume first argument
|
||||
// Note: if object[] is intended, sender should send nest it withing object[] { object[] }
|
||||
if (pis.Length > 1)
|
||||
args[0] = RuntimeCaster.Cast(arguments, pis[0].ParameterType);
|
||||
|
||||
//throw new NotImplementedException("Arguments type not supported.");
|
||||
}
|
||||
|
||||
args[args.Length - 1] = context;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (arguments is Map<byte, object> indexedArguments)
|
||||
{
|
||||
for (byte i = 0; i < pis.Length; i++)
|
||||
{
|
||||
if (arguments.ContainsKey(i))
|
||||
args[i] = RuntimeCaster.Cast(arguments[i], pis[i].ParameterType);
|
||||
if (indexedArguments.ContainsKey(i))
|
||||
args[i] = RuntimeCaster.Cast(indexedArguments[i], pis[i].ParameterType);
|
||||
else if (ft.Arguments[i].Type.Nullable) //Nullable.GetUnderlyingType(pis[i].ParameterType) != null)
|
||||
args[i] = null;
|
||||
else
|
||||
args[i] = Type.Missing;
|
||||
}
|
||||
}
|
||||
else if (arguments is object[] arrayArguments)
|
||||
{
|
||||
for (var i = 0; (i < arrayArguments.Length) && (i < pis.Length); i++)
|
||||
{
|
||||
args[i] = RuntimeCaster.Cast(arrayArguments[i], pis[i].ParameterType);
|
||||
}
|
||||
|
||||
for (var i = arrayArguments.Length; i < pis.Length; i++)
|
||||
{
|
||||
args[i] = Type.Missing;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// assume first argument
|
||||
// Note: if object[] is intended, sender should send nest it withing object[] { object[] }
|
||||
if (pis.Length > 0)
|
||||
args[0] = RuntimeCaster.Cast(arguments, pis[0].ParameterType);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
object rt;
|
||||
@@ -1647,13 +1717,11 @@ partial class DistributedConnection
|
||||
SendRequest(IIPPacketRequest.TemplateFromClassId, classId)
|
||||
.Then((result) =>
|
||||
{
|
||||
Console.WriteLine("Parsing template...");
|
||||
var tt = TypeTemplate.Parse((byte[])result);
|
||||
templateRequests.Remove(classId);
|
||||
templates.Add(tt.ClassId, tt);
|
||||
Instance.Warehouse.PutTemplate(tt);
|
||||
reply.Trigger(tt);
|
||||
Console.WriteLine("Done parsing template...");
|
||||
|
||||
}).Error((ex) =>
|
||||
{
|
||||
@@ -1834,7 +1902,7 @@ partial class DistributedConnection
|
||||
{
|
||||
template = Instance.Warehouse.GetTemplateByClassId(classId, TemplateType.Resource);
|
||||
if (template?.DefinedType != null && template.IsWrapper)
|
||||
dr = Activator.CreateInstance(template.DefinedType, this, id, (ulong)args[1], (string)args[2]) as DistributedResource;
|
||||
dr = Activator.CreateInstance(template.DefinedType, this, id,Convert.ToUInt64( args[1]), (string)args[2]) as DistributedResource;
|
||||
else
|
||||
dr = new DistributedResource(this, id, Convert.ToUInt64(args[1]), (string)args[2]);
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ public class DistributedResource : DynamicObject, IResource, INotifyPropertyChan
|
||||
Instance.EmitResourceEvent(et, args);
|
||||
}
|
||||
|
||||
public AsyncReply _Invoke(byte index, Map<byte, object> args)
|
||||
public AsyncReply _Invoke(byte index, object args)
|
||||
{
|
||||
if (destroyed)
|
||||
throw new Exception("Trying to access a destroyed object.");
|
||||
@@ -312,7 +312,6 @@ public class DistributedResource : DynamicObject, IResource, INotifyPropertyChan
|
||||
|
||||
if (attached && ft != null)
|
||||
{
|
||||
var indexedArgs = new Map<byte, object>();
|
||||
|
||||
if (args.Length == 1)
|
||||
{
|
||||
@@ -322,6 +321,7 @@ public class DistributedResource : DynamicObject, IResource, INotifyPropertyChan
|
||||
|
||||
if (Codec.IsAnonymous(type))
|
||||
{
|
||||
var indexedArgs = new Map<byte, object>();
|
||||
|
||||
var pis = type.GetProperties();
|
||||
|
||||
@@ -334,18 +334,19 @@ public class DistributedResource : DynamicObject, IResource, INotifyPropertyChan
|
||||
|
||||
result = _Invoke(ft.Index, indexedArgs);
|
||||
}
|
||||
else if (args[0] is object[] || args[0] is Map<byte, object>)
|
||||
{
|
||||
result = _Invoke(ft.Index, new object[] { args });
|
||||
}
|
||||
else
|
||||
{
|
||||
indexedArgs.Add((byte)0, args[0]);
|
||||
result = _Invoke(ft.Index, indexedArgs);
|
||||
result = _Invoke(ft.Index, args);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (byte i = 0; i < args.Length; i++)
|
||||
indexedArgs.Add(i, args[i]);
|
||||
|
||||
result = _Invoke(ft.Index, indexedArgs);
|
||||
result = _Invoke(ft.Index, args);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,12 @@ public class TCPSocket : ISocket
|
||||
|
||||
bool held;
|
||||
|
||||
public Socket Socket => sock;
|
||||
|
||||
int bytesSent, bytesReceived;
|
||||
|
||||
public int BytesSent => bytesSent;
|
||||
public int BytesReceived => bytesReceived;
|
||||
//ArraySegment<byte> receiveBufferSegment;
|
||||
|
||||
NetworkBuffer receiveNetworkBuffer = new NetworkBuffer();
|
||||
@@ -117,6 +123,7 @@ public class TCPSocket : ISocket
|
||||
|
||||
if (recCount > 0)
|
||||
{
|
||||
socket.bytesReceived += recCount;
|
||||
socket.receiveNetworkBuffer.Write(socket.receiveBuffer, 0, (uint)recCount);
|
||||
socket.Receiver?.NetworkReceive(socket, socket.receiveNetworkBuffer);
|
||||
|
||||
@@ -287,6 +294,8 @@ public class TCPSocket : ISocket
|
||||
if (state == SocketState.Closed)// || state == SocketState.Terminated)
|
||||
return;
|
||||
|
||||
bytesSent += size;
|
||||
|
||||
var msg = message.Clip((uint)offset, (uint)size);
|
||||
|
||||
lock (sendLock)
|
||||
|
||||
@@ -88,6 +88,10 @@ public class FunctionTemplate : MemberTemplate
|
||||
{
|
||||
rtType = TRU.FromType(mi.ReturnType.GetGenericArguments()[0]);
|
||||
}
|
||||
else if (genericRtType == typeof(Task<>))
|
||||
{
|
||||
rtType = TRU.FromType(mi.ReturnType.GetGenericArguments()[0]);
|
||||
}
|
||||
else if (genericRtType == typeof(IEnumerable<>))// || genericRtType == typeof(IAsyncEnumerable<>))
|
||||
{
|
||||
// get export
|
||||
|
||||
@@ -606,6 +606,7 @@ public class TypeTemplate
|
||||
.Where(x => !(x is FieldInfo c && !c.IsStatic))
|
||||
.Where(x => x.GetCustomAttribute<IgnoreAttribute>() == null)
|
||||
.Where(x => x.Name != "Instance")
|
||||
.Where(x => x.Name != "Trigger")
|
||||
.Where(x => !(x is MethodInfo m && m.IsSpecialName))
|
||||
.Where(x => !(x is EventInfo e &&
|
||||
!(e.EventHandlerType.IsGenericType &&
|
||||
@@ -632,9 +633,9 @@ public class TypeTemplate
|
||||
.Where(x => !(x is FieldInfo c && !c.IsStatic))
|
||||
.Where(x => x.GetCustomAttribute<ExportAttribute>() != null)
|
||||
.Where(x => !(x is MethodInfo m && m.IsSpecialName))
|
||||
.Select(x => new MemberData (
|
||||
info : x,
|
||||
order : order
|
||||
.Select(x => new MemberData(
|
||||
info: x,
|
||||
order: order
|
||||
))
|
||||
.OrderBy(x => x.Name);
|
||||
|
||||
@@ -692,7 +693,7 @@ public class TypeTemplate
|
||||
}
|
||||
|
||||
|
||||
// assign indexies
|
||||
// assign indexes
|
||||
var groups = members.Where(x => x.Parent == null)
|
||||
.OrderBy(x => x.Name).OrderByDescending(x => x.Order)
|
||||
.GroupBy(x => x.Info.MemberType);
|
||||
@@ -924,7 +925,7 @@ public class TypeTemplate
|
||||
public Map<byte, object> CastProperties(Map<string, object> properties)
|
||||
{
|
||||
var rt = new Map<byte, object>();
|
||||
foreach(var kv in properties)
|
||||
foreach (var kv in properties)
|
||||
{
|
||||
var pt = GetPropertyTemplateByName(kv.Key);
|
||||
if (pt == null) continue;
|
||||
|
||||
@@ -83,11 +83,6 @@ public class MemoryStore : IStore
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool Remove(IResource resource)
|
||||
{
|
||||
resources.Remove(resource.Instance.Id);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Modify(IResource resource, string propertyName, object value, ulong? age, DateTime? dateTime)
|
||||
{
|
||||
@@ -153,7 +148,8 @@ public class MemoryStore : IStore
|
||||
|
||||
AsyncReply<bool> IStore.Remove(IResource resource)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
resources.Remove(resource.Instance.Id);
|
||||
return AsyncReply.FromResult(true);
|
||||
}
|
||||
|
||||
public AsyncReply<bool> Remove(string path)
|
||||
|
||||
Reference in New Issue
Block a user