diff --git a/Esiur/Core/AsyncBag.cs b/Esiur/Core/AsyncBag.cs index 0bacc16..df139b3 100644 --- a/Esiur/Core/AsyncBag.cs +++ b/Esiur/Core/AsyncBag.cs @@ -53,10 +53,10 @@ public class AsyncBag : 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(o => callback((T[])o))); return this; diff --git a/Esiur/Core/AsyncReply.cs b/Esiur/Core/AsyncReply.cs index be5fd27..61639c7 100644 --- a/Esiur/Core/AsyncReply.cs +++ b/Esiur/Core/AsyncReply.cs @@ -73,6 +73,8 @@ public class AsyncReply public Exception Exception => exception; + public static AsyncReply FromResult(T result) => new AsyncReply(result); + public object Wait() { if (resultReady) diff --git a/Esiur/Core/AsyncReplyGeneric.cs b/Esiur/Core/AsyncReplyGeneric.cs index 031e6e5..a19c197 100644 --- a/Esiur/Core/AsyncReplyGeneric.cs +++ b/Esiur/Core/AsyncReplyGeneric.cs @@ -69,6 +69,7 @@ public class AsyncReply : AsyncReply } + public AsyncReply() : base() { diff --git a/Esiur/Data/DataDeserializer.cs b/Esiur/Data/DataDeserializer.cs index 739b4f1..1e1ee57 100644 --- a/Esiur/Data/DataDeserializer.cs +++ b/Esiur/Data/DataDeserializer.cs @@ -466,7 +466,7 @@ public static class DataDeserializer }).Error(e => { - Console.WriteLine(e); + //Console.WriteLine(e); rt.TriggerError(e); }); @@ -671,7 +671,7 @@ public static class DataDeserializer var index = tdu.Data[tdu.Offset]; - var template = connection.Instance.Warehouse.GetTemplateByClassId(classId, + var template = connection.Instance.Warehouse.GetTemplateByClassId(classId, TemplateType.Enum); if (template != null) @@ -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: @@ -1165,7 +1180,7 @@ public static class DataDeserializer map.Add(keys.GetValue(i), values.GetValue(i)); return map; - + } public static AsyncReply TupleParserAsync(ParsedTDU tdu, DistributedConnection connection, uint[] requestSequence) @@ -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(); - 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(); diff --git a/Esiur/Data/DataSerializer.cs b/Esiur/Data/DataSerializer.cs index 165c470..6cd8230 100644 --- a/Esiur/Data/DataSerializer.cs +++ b/Esiur/Data/DataSerializer.cs @@ -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)value); } + else if (tru.Identifier == TRUIdentifier.Enum) + { + + var rt = new List(); + 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(); diff --git a/Esiur/Data/RuntimeCaster.cs b/Esiur/Data/RuntimeCaster.cs index 3e1aff0..608ecfe 100644 --- a/Esiur/Data/RuntimeCaster.cs +++ b/Esiur/Data/RuntimeCaster.cs @@ -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, 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> _cache = + new ConcurrentDictionary<(Type, Type), Func>(); - public static readonly RuntimeCastOptions Default = new RuntimeCastOptions(); + // Numeric-only compiled converters + private static readonly ConcurrentDictionary<(Type from, Type to, bool @checked), + Func> _numericCache = + new ConcurrentDictionary<(Type, Type, bool), Func>(); - // (fromType, toType) -> converter(value, options) (options captured at call time) - private static readonly ConcurrentDictionary<(Type from, Type to), Func> _cache = new(); + // Per-element converters for collections + private static readonly ConcurrentDictionary<(Type from, Type to), + Func> _elemConvCache = + new ConcurrentDictionary<(Type, Type), Func>(); - // Numeric-only compiled converters (fast path), keyed by checked/unchecked - private static readonly ConcurrentDictionary<(Type from, Type to, bool @checked), Func> _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 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 BuildConverter(Type fromType, Type toType) + + private static Func 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 + bool useNull; + var dec = ConvertFloatDoubleToDecimal(value, fromUnderlying, opts, out useNull); + if (toType != toUnderlying) // Nullable 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 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>(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(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 wrapping bool wrapNullable = toType != enumType; - // String → Enum if (fromUnderlying == typeof(string)) { - var parsed = Enum.Parse(enumType, (string)value, opts.EnumIgnoreCase); + 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}."); + 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!; + 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 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 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 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 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; } } + diff --git a/Esiur/Data/TRU.cs b/Esiur/Data/TRU.cs index 9d80353..8240a6f 100644 --- a/Esiur/Data/TRU.cs +++ b/Esiur/Data/TRU.cs @@ -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; @@ -64,7 +65,7 @@ namespace Esiur.Data [TDUIdentifier.String] = TRUIdentifier.String, }; - + public void SetNull(List flags) { @@ -145,7 +146,7 @@ namespace Esiur.Data var rt = typeof(Map<,>).MakeGenericType(subs); return rt; } - + return Identifier switch { (TRUIdentifier.Void) => typeof(void), @@ -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 }; @@ -228,7 +229,7 @@ namespace Esiur.Data { switch (Identifier) { - case TRUIdentifier.TypedList: + case TRUIdentifier.TypedList: return (TDUIdentifier.TypedList, SubTypes[0].Compose()); case TRUIdentifier.TypedRecord: return (TDUIdentifier.Record, UUID?.Data); @@ -239,7 +240,7 @@ namespace Esiur.Data return (TDUIdentifier.TypedEnum, UUID?.Data); default: - + throw new NotImplementedException(); } } @@ -267,11 +268,16 @@ namespace Esiur.Data // case TRUIdentifier. } //} + private static Dictionary _cache = new Dictionary(); + 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) @@ -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), diff --git a/Esiur/Net/IIP/DistributedConnection.cs b/Esiur/Net/IIP/DistributedConnection.cs index 759d68b..67d4611 100644 --- a/Esiur/Net/IIP/DistributedConnection.cs +++ b/Esiur/Net/IIP/DistributedConnection.cs @@ -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 IStore.Remove(IResource resource) { - throw new NotImplementedException(); + // @TODO: this is called when no connection is possible + return new AsyncReply(true); } public AsyncReply Remove(string path) diff --git a/Esiur/Net/IIP/DistributedConnectionProtocol.cs b/Esiur/Net/IIP/DistributedConnectionProtocol.cs index 933d512..2391269 100644 --- a/Esiur/Net/IIP/DistributedConnectionProtocol.cs +++ b/Esiur/Net/IIP/DistributedConnectionProtocol.cs @@ -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 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(); - for (byte i = 0; i < parameters.Length; i++) - args.Add(i, parameters[i]); - return Call(procedureCall, args); + //var args = new Map(); + //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 parameters) @@ -198,7 +201,7 @@ partial class DistributedConnection return SendRequest(IIPPacketRequest.ProcedureCall, procedureCall, parameters); } - internal AsyncReply SendInvoke(uint instanceId, byte index, Map 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)results; + //var arguments = (Map)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)parsed; + //var arguments = (Map)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)results; + //var arguments = (Map)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)parsed; + //var arguments = (Map)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)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)parsed; + //var arguments = (Map)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 arguments, IIPPacketRequest actionType, object target = null) + void InvokeFunction(FunctionTemplate ft, uint callback, object arguments, IIPPacketRequest actionType, object target = null) { // cast arguments @@ -1246,15 +1249,36 @@ partial class DistributedConnection { if (pis.Last().ParameterType == typeof(DistributedConnection)) { - for (byte i = 0; i < pis.Length - 1; i++) + if (arguments is Map indexedArguments) { - if (arguments.ContainsKey(i)) - args[i] = RuntimeCaster.Cast(arguments[i], pis[i].ParameterType); - else if (ft.Arguments[i].Type.Nullable) - args[i] = null; - else - args[i] = Type.Missing; + for (byte i = 0; i < pis.Length - 1; i++) + { + 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,16 +1286,39 @@ partial class DistributedConnection else if (pis.Last().ParameterType == typeof(InvocationContext)) { context = new InvocationContext(this, callback); - - for (byte i = 0; i < pis.Length - 1; i++) + if (arguments is Map indexedArguments) { - if (arguments.ContainsKey(i)) - args[i] = RuntimeCaster.Cast(arguments[i], pis[i].ParameterType); - else if (ft.Arguments[i].Type.Nullable) - args[i] = null; - else - args[i] = Type.Missing; + for (byte i = 0; i < pis.Length - 1; i++) + { + 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; @@ -1279,15 +1326,38 @@ partial class DistributedConnection } else { - for (byte i = 0; i < pis.Length; i++) + if (arguments is Map indexedArguments) { - if (arguments.ContainsKey(i)) - args[i] = RuntimeCaster.Cast(arguments[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; + for (byte i = 0; i < pis.Length; i++) + { + 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); + } + } } @@ -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]); } diff --git a/Esiur/Net/IIP/DistributedResource.cs b/Esiur/Net/IIP/DistributedResource.cs index d75c9e9..4836d6b 100644 --- a/Esiur/Net/IIP/DistributedResource.cs +++ b/Esiur/Net/IIP/DistributedResource.cs @@ -177,8 +177,8 @@ public class DistributedResource : DynamicObject, IResource, INotifyPropertyChan var props = new PropertyValue[properties.Length]; for (byte i = 0; i < properties.Length; i++) - props[i] = new PropertyValue(properties[i], - Instance.GetAge(i), + props[i] = new PropertyValue(properties[i], + Instance.GetAge(i), Instance.GetModificationDate(i)); return props; @@ -193,7 +193,7 @@ public class DistributedResource : DynamicObject, IResource, INotifyPropertyChan rt.Add(i, new PropertyValue(properties[i], Instance.GetAge(i), Instance.GetModificationDate(i))); - + return rt; } @@ -238,7 +238,7 @@ public class DistributedResource : DynamicObject, IResource, INotifyPropertyChan Instance.EmitResourceEvent(et, args); } - public AsyncReply _Invoke(byte index, Map 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(); if (args.Length == 1) { @@ -322,6 +321,7 @@ public class DistributedResource : DynamicObject, IResource, INotifyPropertyChan if (Codec.IsAnonymous(type)) { + var indexedArgs = new Map(); 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) + { + 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; } diff --git a/Esiur/Net/Sockets/TCPSocket.cs b/Esiur/Net/Sockets/TCPSocket.cs index 8a96d01..9e2574f 100644 --- a/Esiur/Net/Sockets/TCPSocket.cs +++ b/Esiur/Net/Sockets/TCPSocket.cs @@ -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 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) diff --git a/Esiur/Resource/Template/FunctionTemplate.cs b/Esiur/Resource/Template/FunctionTemplate.cs index 0858265..a0e45ef 100644 --- a/Esiur/Resource/Template/FunctionTemplate.cs +++ b/Esiur/Resource/Template/FunctionTemplate.cs @@ -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 diff --git a/Esiur/Resource/Template/TypeTemplate.cs b/Esiur/Resource/Template/TypeTemplate.cs index df25cf9..bf18674 100644 --- a/Esiur/Resource/Template/TypeTemplate.cs +++ b/Esiur/Resource/Template/TypeTemplate.cs @@ -487,7 +487,7 @@ public class TypeTemplate } // add attributes - var attrs = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + var attrs = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(x => x.GetCustomAttribute() != null); foreach (var attr in attrs) @@ -606,6 +606,7 @@ public class TypeTemplate .Where(x => !(x is FieldInfo c && !c.IsStatic)) .Where(x => x.GetCustomAttribute() == 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() != 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,13 +925,13 @@ public class TypeTemplate public Map CastProperties(Map properties) { var rt = new Map(); - foreach(var kv in properties) + foreach (var kv in properties) { var pt = GetPropertyTemplateByName(kv.Key); if (pt == null) continue; rt.Add(pt.Index, kv.Value); } - + return rt; } } diff --git a/Esiur/Stores/MemoryStore.cs b/Esiur/Stores/MemoryStore.cs index e192360..a65b093 100644 --- a/Esiur/Stores/MemoryStore.cs +++ b/Esiur/Stores/MemoryStore.cs @@ -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 IStore.Remove(IResource resource) { - throw new NotImplementedException(); + resources.Remove(resource.Instance.Id); + return AsyncReply.FromResult(true); } public AsyncReply Remove(string path)