A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections; 3using System.Collections.Generic; 4using System.Linq; 5using System.Reflection; 6using UnityEngine; 7 8namespace Unity.VisualScripting 9{ 10 public static class ConversionUtility 11 { 12 public enum ConversionType 13 { 14 Impossible, 15 Identity, 16 Upcast, 17 Downcast, 18 NumericImplicit, 19 NumericExplicit, 20 UserDefinedImplicit, 21 UserDefinedExplicit, 22 UserDefinedThenNumericImplicit, 23 UserDefinedThenNumericExplicit, 24 UnityHierarchy, 25 EnumerableToArray, 26 EnumerableToList, 27 ToString 28 } 29 30 private const BindingFlags UserDefinedBindingFlags = BindingFlags.Static | BindingFlags.Public; 31 32 private static readonly Dictionary<ConversionQuery, ConversionType> conversionTypesCache = new Dictionary<ConversionQuery, ConversionType>(new ConversionQueryComparer()); 33 private static readonly Dictionary<ConversionQuery, MethodInfo[]> userConversionMethodsCache = new Dictionary<ConversionQuery, MethodInfo[]>(new ConversionQueryComparer()); 34 35 private static bool RespectsIdentity(Type source, Type destination) 36 { 37 return source == destination; 38 } 39 40 private static bool IsUpcast(Type source, Type destination) 41 { 42 return destination.IsAssignableFrom(source); 43 } 44 45 private static bool IsDowncast(Type source, Type destination) 46 { 47 return source.IsAssignableFrom(destination); 48 } 49 50 private static bool ExpectsString(Type source, Type destination) 51 { 52 return destination == typeof(string); 53 } 54 55 public static bool HasImplicitNumericConversion(Type source, Type destination) 56 { 57 return implicitNumericConversions.ContainsKey(source) && implicitNumericConversions[source].Contains(destination); 58 } 59 60 public static bool HasExplicitNumericConversion(Type source, Type destination) 61 { 62 return explicitNumericConversions.ContainsKey(source) && explicitNumericConversions[source].Contains(destination); 63 } 64 65 public static bool HasNumericConversion(Type source, Type destination) 66 { 67 return HasImplicitNumericConversion(source, destination) || HasExplicitNumericConversion(source, destination); 68 } 69 70 private static IEnumerable<MethodInfo> FindUserDefinedConversionMethods(ConversionQuery query) 71 { 72 var source = query.source; 73 var destination = query.destination; 74 75 var sourceMethods = source.GetMethods(UserDefinedBindingFlags) 76 .Where(m => m.IsUserDefinedConversion()); 77 78 var destinationMethods = destination.GetMethods(UserDefinedBindingFlags) 79 .Where(m => m.IsUserDefinedConversion()); 80 81 return sourceMethods.Concat(destinationMethods).Where 82 ( 83 m => m.GetParameters()[0].ParameterType.IsAssignableFrom(source) || 84 source.IsAssignableFrom(m.GetParameters()[0].ParameterType) 85 ); 86 } 87 88 // Returning an array directly so that the enumeration in 89 // UserDefinedConversion does not allocate memory 90 private static MethodInfo[] GetUserDefinedConversionMethods(Type source, Type destination) 91 { 92 var query = new ConversionQuery(source, destination); 93 94 if (!userConversionMethodsCache.ContainsKey(query)) 95 { 96 userConversionMethodsCache.Add(query, FindUserDefinedConversionMethods(query).ToArray()); 97 } 98 99 return userConversionMethodsCache[query]; 100 } 101 102 private static ConversionType GetUserDefinedConversionType(Type source, Type destination) 103 { 104 var conversionMethods = GetUserDefinedConversionMethods(source, destination); 105 106 // Duplicate user defined conversions are not allowed, so FirstOrDefault is safe. 107 108 // Look for direct conversions. 109 var conversionMethod = conversionMethods.FirstOrDefault(m => m.ReturnType == destination); 110 111 if (conversionMethod != null) 112 { 113 if (conversionMethod.Name == "op_Implicit") 114 { 115 return ConversionType.UserDefinedImplicit; 116 } 117 else if (conversionMethod.Name == "op_Explicit") 118 { 119 return ConversionType.UserDefinedExplicit; 120 } 121 } 122 // Primitive types can skip the middleman cast, even if it is explicit. 123 else if (destination.IsPrimitive && destination != typeof(IntPtr) && destination != typeof(UIntPtr)) 124 { 125 // Look for implicit conversions. 126 conversionMethod = conversionMethods.FirstOrDefault(m => HasImplicitNumericConversion(m.ReturnType, destination)); 127 128 if (conversionMethod != null) 129 { 130 if (conversionMethod.Name == "op_Implicit") 131 { 132 return ConversionType.UserDefinedThenNumericImplicit; 133 } 134 else if (conversionMethod.Name == "op_Explicit") 135 { 136 return ConversionType.UserDefinedThenNumericExplicit; 137 } 138 } 139 // Look for explicit conversions. 140 else 141 { 142 conversionMethod = conversionMethods.FirstOrDefault(m => HasExplicitNumericConversion(m.ReturnType, destination)); 143 144 if (conversionMethod != null) 145 { 146 return ConversionType.UserDefinedThenNumericExplicit; 147 } 148 } 149 } 150 151 return ConversionType.Impossible; 152 } 153 154 private static bool HasEnumerableToArrayConversion(Type source, Type destination) 155 { 156 return source != typeof(string) && 157 typeof(IEnumerable).IsAssignableFrom(source) && 158 destination.IsArray && 159 destination.GetArrayRank() == 1; 160 } 161 162 private static bool HasEnumerableToListConversion(Type source, Type destination) 163 { 164 return source != typeof(string) && 165 typeof(IEnumerable).IsAssignableFrom(source) && 166 destination.IsGenericType && 167 destination.GetGenericTypeDefinition() == typeof(List<>); 168 } 169 170 private static bool HasUnityHierarchyConversion(Type source, Type destination) 171 { 172 if (destination == typeof(GameObject)) 173 { 174 return typeof(Component).IsAssignableFrom(source); 175 } 176 else if (typeof(Component).IsAssignableFrom(destination) || destination.IsInterface) 177 { 178 return source == typeof(GameObject) || typeof(Component).IsAssignableFrom(source); 179 } 180 181 return false; 182 } 183 184 private static bool IsValidConversion(ConversionType conversionType, bool guaranteed) 185 { 186 if (conversionType == ConversionType.Impossible) 187 { 188 return false; 189 } 190 191 if (guaranteed) 192 { 193 // Downcasts are not guaranteed to succeed. 194 if (conversionType == ConversionType.Downcast) 195 { 196 return false; 197 } 198 } 199 200 return true; 201 } 202 203 public static bool CanConvert(object value, Type type, bool guaranteed) 204 { 205 return IsValidConversion(GetRequiredConversion(value, type), guaranteed); 206 } 207 208 public static bool CanConvert(Type source, Type destination, bool guaranteed) 209 { 210 return IsValidConversion(GetRequiredConversion(source, destination), guaranteed); 211 } 212 213 public static object Convert(object value, Type type) 214 { 215 return Convert(value, type, GetRequiredConversion(value, type)); 216 } 217 218 public static T Convert<T>(object value) 219 { 220 return (T)Convert(value, typeof(T)); 221 } 222 223 public static bool TryConvert(object value, Type type, out object result, bool guaranteed) 224 { 225 var conversionType = GetRequiredConversion(value, type); 226 227 if (IsValidConversion(conversionType, guaranteed)) 228 { 229 result = Convert(value, type, conversionType); 230 return true; 231 } 232 233 result = value; 234 return false; 235 } 236 237 public static bool TryConvert<T>(object value, out T result, bool guaranteed) 238 { 239 if (TryConvert(value, typeof(T), out var res, guaranteed)) 240 { 241 result = (T)res; 242 return true; 243 } 244 245 result = default; 246 return false; 247 } 248 249 public static bool IsConvertibleTo(this Type source, Type destination, bool guaranteed) 250 { 251 return CanConvert(source, destination, guaranteed); 252 } 253 254 public static bool IsConvertibleTo(this object source, Type type, bool guaranteed) 255 { 256 return CanConvert(source, type, guaranteed); 257 } 258 259 public static bool IsConvertibleTo<T>(this object source, bool guaranteed) 260 { 261 return CanConvert(source, typeof(T), guaranteed); 262 } 263 264 public static object ConvertTo(this object source, Type type) 265 { 266 return Convert(source, type); 267 } 268 269 public static T ConvertTo<T>(this object source) 270 { 271 return (T)Convert(source, typeof(T)); 272 } 273 274 public static ConversionType GetRequiredConversion(Type source, Type destination) 275 { 276 var query = new ConversionQuery(source, destination); 277 278 if (!conversionTypesCache.TryGetValue(query, out var conversionType)) 279 { 280 conversionType = DetermineConversionType(query); 281 conversionTypesCache.Add(query, conversionType); 282 } 283 284 return conversionType; 285 } 286 287 private static ConversionType DetermineConversionType(ConversionQuery query) 288 { 289 var source = query.source; 290 var destination = query.destination; 291 292 if (source == null) 293 { 294 if (destination.IsNullable()) 295 { 296 return ConversionType.Identity; 297 } 298 else 299 { 300 return ConversionType.Impossible; 301 } 302 } 303 304 Ensure.That(nameof(destination)).IsNotNull(destination); 305 306 if (RespectsIdentity(source, destination)) 307 { 308 return ConversionType.Identity; 309 } 310 else if (IsUpcast(source, destination)) 311 { 312 return ConversionType.Upcast; 313 } 314 else if (IsDowncast(source, destination)) 315 { 316 return ConversionType.Downcast; 317 } 318 // Disabling *.ToString conversion, because it's more often than otherwise very confusing 319 /*else if (ExpectsString(source, destination)) 320 { 321 return ConversionType.ToString; 322 }*/ 323 else if (HasImplicitNumericConversion(source, destination)) 324 { 325 return ConversionType.NumericImplicit; 326 } 327 else if (HasExplicitNumericConversion(source, destination)) 328 { 329 return ConversionType.NumericExplicit; 330 } 331 else if (HasUnityHierarchyConversion(source, destination)) 332 { 333 return ConversionType.UnityHierarchy; 334 } 335 else if (HasEnumerableToArrayConversion(source, destination)) 336 { 337 return ConversionType.EnumerableToArray; 338 } 339 else if (HasEnumerableToListConversion(source, destination)) 340 { 341 return ConversionType.EnumerableToList; 342 } 343 else 344 { 345 var userDefinedConversionType = GetUserDefinedConversionType(source, destination); 346 347 if (userDefinedConversionType != ConversionType.Impossible) 348 { 349 return userDefinedConversionType; 350 } 351 } 352 353 return ConversionType.Impossible; 354 } 355 356 public static ConversionType GetRequiredConversion(object value, Type type) 357 { 358 Ensure.That(nameof(type)).IsNotNull(type); 359 360 return GetRequiredConversion(value?.GetType(), type); 361 } 362 363 private static object NumericConversion(object value, Type type) 364 { 365 return System.Convert.ChangeType(value, type); 366 } 367 368 private static object UserDefinedConversion(ConversionType conversion, object value, Type type) 369 { 370 var valueType = value.GetType(); 371 var conversionMethods = GetUserDefinedConversionMethods(valueType, type); 372 373 var numeric = conversion == ConversionType.UserDefinedThenNumericImplicit || 374 conversion == ConversionType.UserDefinedThenNumericExplicit; 375 376 MethodInfo conversionMethod = null; 377 378 if (numeric) 379 { 380 foreach (var m in conversionMethods) 381 { 382 if (HasNumericConversion(m.ReturnType, type)) 383 { 384 conversionMethod = m; 385 break; 386 } 387 } 388 } 389 else 390 { 391 foreach (var m in conversionMethods) 392 { 393 if (m.ReturnType == type) 394 { 395 conversionMethod = m; 396 break; 397 } 398 } 399 } 400 401 var result = conversionMethod.InvokeOptimized(null, value); 402 403 if (numeric) 404 { 405 result = NumericConversion(result, type); 406 } 407 408 return result; 409 } 410 411 private static object EnumerableToArrayConversion(object value, Type arrayType) 412 { 413 var elementType = arrayType.GetElementType(); 414 var objectArray = ((IEnumerable)value).Cast<object>().Where(elementType.IsAssignableFrom).ToArray(); // Non-generic OfType 415 var typedArray = Array.CreateInstance(elementType, objectArray.Length); 416 objectArray.CopyTo(typedArray, 0); 417 return typedArray; 418 } 419 420 private static object EnumerableToListConversion(object value, Type listType) 421 { 422 var elementType = listType.GetGenericArguments()[0]; 423 var objectArray = ((IEnumerable)value).Cast<object>().Where(elementType.IsAssignableFrom).ToArray(); // Non-generic OfType 424 var typedList = (IList)Activator.CreateInstance(listType); 425 426 for (var i = 0; i < objectArray.Length; i++) 427 { 428 typedList.Add(objectArray[i]); 429 } 430 431 return typedList; 432 } 433 434 private static object UnityHierarchyConversion(object value, Type type) 435 { 436 if (value.IsUnityNull()) 437 { 438 return null; 439 } 440 441 if (type == typeof(GameObject) && value is Component) 442 { 443 return ((Component)value).gameObject; 444 } 445 else if (typeof(Component).IsAssignableFrom(type) || type.IsInterface) 446 { 447 if (value is Component) 448 { 449 return ((Component)value).GetComponent(type); 450 } 451 else if (value is GameObject) 452 { 453 return ((GameObject)value).GetComponent(type); 454 } 455 } 456 457 throw new InvalidConversionException(); 458 } 459 460 private static object Convert(object value, Type type, ConversionType conversionType) 461 { 462 Ensure.That(nameof(type)).IsNotNull(type); 463 464 if (conversionType == ConversionType.Impossible) 465 { 466 throw new InvalidConversionException($"Cannot convert from '{value?.GetType().ToString() ?? "null"}' to '{type}'."); 467 } 468 469 try 470 { 471 switch (conversionType) 472 { 473 case ConversionType.Identity: 474 case ConversionType.Upcast: 475 case ConversionType.Downcast: 476 return value; 477 478 case ConversionType.ToString: 479 return value.ToString(); 480 481 case ConversionType.NumericImplicit: 482 case ConversionType.NumericExplicit: 483 return NumericConversion(value, type); 484 485 case ConversionType.UserDefinedImplicit: 486 case ConversionType.UserDefinedExplicit: 487 case ConversionType.UserDefinedThenNumericImplicit: 488 case ConversionType.UserDefinedThenNumericExplicit: 489 return UserDefinedConversion(conversionType, value, type); 490 491 case ConversionType.EnumerableToArray: 492 return EnumerableToArrayConversion(value, type); 493 494 case ConversionType.EnumerableToList: 495 return EnumerableToListConversion(value, type); 496 497 case ConversionType.UnityHierarchy: 498 return UnityHierarchyConversion(value, type); 499 500 default: 501 throw new UnexpectedEnumValueException<ConversionType>(conversionType); 502 } 503 } 504 catch (Exception ex) 505 { 506 throw new InvalidConversionException($"Failed to convert from '{value?.GetType().ToString() ?? "null"}' to '{type}' via {conversionType}.", ex); 507 } 508 } 509 510 private struct ConversionQuery : IEquatable<ConversionQuery> 511 { 512 public readonly Type source; 513 public readonly Type destination; 514 515 public ConversionQuery(Type source, Type destination) 516 { 517 this.source = source; 518 this.destination = destination; 519 } 520 521 public bool Equals(ConversionQuery other) 522 { 523 return 524 source == other.source && 525 destination == other.destination; 526 } 527 528 public override bool Equals(object obj) 529 { 530 if (!(obj is ConversionQuery)) 531 { 532 return false; 533 } 534 535 return Equals((ConversionQuery)obj); 536 } 537 538 public override int GetHashCode() 539 { 540 return HashUtility.GetHashCode(source, destination); 541 } 542 } 543 544 // Make sure the equality comparer doesn't use boxing 545 private struct ConversionQueryComparer : IEqualityComparer<ConversionQuery> 546 { 547 public bool Equals(ConversionQuery x, ConversionQuery y) 548 { 549 return x.Equals(y); 550 } 551 552 public int GetHashCode(ConversionQuery obj) 553 { 554 return obj.GetHashCode(); 555 } 556 } 557 558 #region Numeric Conversions 559 560 // https://msdn.microsoft.com/en-us/library/y5b434w4.aspx 561 private static readonly Dictionary<Type, HashSet<Type>> implicitNumericConversions = new Dictionary<Type, HashSet<Type>>() 562 { 563 { 564 typeof(sbyte), 565 new HashSet<Type>() 566 { 567 typeof(byte), 568 typeof(int), 569 typeof(long), 570 typeof(float), 571 typeof(double), 572 typeof(decimal) 573 } 574 }, 575 { 576 typeof(byte), 577 new HashSet<Type>() 578 { 579 typeof(short), 580 typeof(ushort), 581 typeof(int), 582 typeof(uint), 583 typeof(long), 584 typeof(ulong), 585 typeof(float), 586 typeof(double), 587 typeof(decimal) 588 } 589 }, 590 { 591 typeof(short), 592 new HashSet<Type>() 593 { 594 typeof(int), 595 typeof(long), 596 typeof(float), 597 typeof(double), 598 typeof(decimal) 599 } 600 }, 601 { 602 typeof(ushort), 603 new HashSet<Type>() 604 { 605 typeof(int), 606 typeof(uint), 607 typeof(long), 608 typeof(ulong), 609 typeof(float), 610 typeof(double), 611 typeof(decimal), 612 } 613 }, 614 { 615 typeof(int), 616 new HashSet<Type>() 617 { 618 typeof(long), 619 typeof(float), 620 typeof(double), 621 typeof(decimal) 622 } 623 }, 624 { 625 typeof(uint), 626 new HashSet<Type>() 627 { 628 typeof(long), 629 typeof(ulong), 630 typeof(float), 631 typeof(double), 632 typeof(decimal) 633 } 634 }, 635 { 636 typeof(long), 637 new HashSet<Type>() 638 { 639 typeof(float), 640 typeof(double), 641 typeof(decimal) 642 } 643 }, 644 { 645 typeof(char), 646 new HashSet<Type>() 647 { 648 typeof(ushort), 649 typeof(int), 650 typeof(uint), 651 typeof(long), 652 typeof(ulong), 653 typeof(float), 654 typeof(double), 655 typeof(decimal) 656 } 657 }, 658 { 659 typeof(float), 660 new HashSet<Type>() 661 { 662 typeof(double) 663 } 664 }, 665 { 666 typeof(ulong), 667 new HashSet<Type>() 668 { 669 typeof(float), 670 typeof(double), 671 typeof(decimal) 672 } 673 }, 674 }; 675 676 // https://msdn.microsoft.com/en-us/library/yht2cx7b.aspx 677 private static readonly Dictionary<Type, HashSet<Type>> explicitNumericConversions = new Dictionary<Type, HashSet<Type>>() 678 { 679 { 680 typeof(sbyte), 681 new HashSet<Type>() 682 { 683 typeof(byte), 684 typeof(ushort), 685 typeof(uint), 686 typeof(ulong), 687 typeof(char) 688 } 689 }, 690 { 691 typeof(byte), 692 new HashSet<Type>() 693 { 694 typeof(sbyte), 695 typeof(char) 696 } 697 }, 698 { 699 typeof(short), 700 new HashSet<Type>() 701 { 702 typeof(sbyte), 703 typeof(byte), 704 typeof(ushort), 705 typeof(uint), 706 typeof(ulong), 707 typeof(char) 708 } 709 }, 710 { 711 typeof(ushort), 712 new HashSet<Type>() 713 { 714 typeof(sbyte), 715 typeof(byte), 716 typeof(short), 717 typeof(char) 718 } 719 }, 720 { 721 typeof(int), 722 new HashSet<Type>() 723 { 724 typeof(sbyte), 725 typeof(byte), 726 typeof(short), 727 typeof(ushort), 728 typeof(uint), 729 typeof(ulong), 730 typeof(char) 731 } 732 }, 733 { 734 typeof(uint), 735 new HashSet<Type>() 736 { 737 typeof(sbyte), 738 typeof(byte), 739 typeof(short), 740 typeof(ushort), 741 typeof(int), 742 typeof(char) 743 } 744 }, 745 { 746 typeof(long), 747 new HashSet<Type>() 748 { 749 typeof(sbyte), 750 typeof(byte), 751 typeof(short), 752 typeof(ushort), 753 typeof(int), 754 typeof(uint), 755 typeof(ulong), 756 typeof(char) 757 } 758 }, 759 { 760 typeof(ulong), 761 new HashSet<Type>() 762 { 763 typeof(sbyte), 764 typeof(byte), 765 typeof(short), 766 typeof(ushort), 767 typeof(int), 768 typeof(uint), 769 typeof(long), 770 typeof(char) 771 } 772 }, 773 { 774 typeof(char), 775 new HashSet<Type>() 776 { 777 typeof(sbyte), 778 typeof(byte), 779 typeof(short) 780 } 781 }, 782 { 783 typeof(float), 784 new HashSet<Type>() 785 { 786 typeof(sbyte), 787 typeof(byte), 788 typeof(short), 789 typeof(ushort), 790 typeof(int), 791 typeof(uint), 792 typeof(long), 793 typeof(ulong), 794 typeof(char), 795 typeof(decimal) 796 } 797 }, 798 { 799 typeof(double), 800 new HashSet<Type>() 801 { 802 typeof(sbyte), 803 typeof(byte), 804 typeof(short), 805 typeof(ushort), 806 typeof(int), 807 typeof(uint), 808 typeof(long), 809 typeof(ulong), 810 typeof(char), 811 typeof(float), 812 typeof(decimal), 813 } 814 }, 815 { 816 typeof(decimal), 817 new HashSet<Type>() 818 { 819 typeof(sbyte), 820 typeof(byte), 821 typeof(short), 822 typeof(ushort), 823 typeof(int), 824 typeof(uint), 825 typeof(long), 826 typeof(ulong), 827 typeof(char), 828 typeof(float), 829 typeof(double) 830 } 831 } 832 }; 833 834 #endregion 835 } 836}