diff --git a/core/Extensions/TextExtensions.cs b/core/Extensions/TextExtensions.cs index c5c9a47..816da22 100644 --- a/core/Extensions/TextExtensions.cs +++ b/core/Extensions/TextExtensions.cs @@ -19,6 +19,21 @@ public static long GetLongHashCode(this string text) } } + public static long GetLongHashCode(this Span text) + { + unchecked + { + long hash = 3074457345618258791; + for (int i = 0; i < text.Length; i++) + { + hash += text[i]; + hash *= 3074457345618258799; + } + + return hash; + } + } + public static long GetLongHashCode(this ReadOnlySpan text) { unchecked diff --git a/core/Field.cs b/core/Field.cs new file mode 100644 index 0000000..6790e41 --- /dev/null +++ b/core/Field.cs @@ -0,0 +1,135 @@ +using System; + +namespace Types +{ + /// + /// Describes a field declared in a . + /// + public readonly struct Field : IEquatable + { + internal readonly long typeHash; + internal readonly long nameHash; + + /// + /// The type of the field. + /// + public readonly Type Type => MetadataRegistry.GetType(typeHash); + + /// + /// Name of the field. + /// + public readonly ReadOnlySpan Name => TypeNames.Get(nameHash); + + /// + /// Size of the field in bytes. + /// + public readonly ushort Size => Type.size; + +#if NET + /// + /// Not supported. + /// + [Obsolete("Default constructor not supported", true)] + public Field() + { + } +#endif + + internal Field(long typeHash, long nameHash) + { + this.typeHash = typeHash; + this.nameHash = nameHash; + } + + /// + /// Creates a new field with the given and . + /// + public Field(string fieldName, string fullTypeName) + { + nameHash = TypeNames.Set(fieldName); + typeHash = fullTypeName.GetLongHashCode(); + } + + /// + /// Creates a new field with the given and . + /// + public Field(ReadOnlySpan fieldName, ReadOnlySpan fullTypeName) + { + nameHash = TypeNames.Set(fieldName); + typeHash = fullTypeName.GetLongHashCode(); + } + + /// + /// Creates a new field with the given and . + /// + public Field(string fieldName, int typeHash) + { + nameHash = TypeNames.Set(fieldName); + this.typeHash = typeHash; + } + + /// + public readonly override string ToString() + { + Span buffer = stackalloc char[256]; + int length = ToString(buffer); + return buffer.Slice(0, length).ToString(); + } + + /// + /// Builds a string representation of this field and writes it to . + /// + /// Amount of characters written. + public readonly int ToString(Span buffer) + { + Type type = Type; + type.Name.CopyTo(buffer); + int length = type.Name.Length; + buffer[length++] = '='; + Name.CopyTo(buffer.Slice(length)); + length += Name.Length; + return length; + } + + /// + public readonly override bool Equals(object? obj) + { + return obj is Field field && Equals(field); + } + + /// + public readonly bool Equals(Field other) + { + return nameHash == other.nameHash && typeHash == other.typeHash; + } + + /// + public readonly override int GetHashCode() + { + unchecked + { + int hashCode = 17; + ReadOnlySpan name = Name; + for (int i = 0; i < name.Length; i++) + { + hashCode = hashCode * 31 + name[i]; + } + + hashCode = hashCode * 31 + (int)typeHash; + return hashCode; + } + } + + /// + public static bool operator ==(Field left, Field right) + { + return left.Equals(right); + } + + /// + public static bool operator !=(Field left, Field right) + { + return !(left == right); + } + } +} \ No newline at end of file diff --git a/core/FieldBuffer.cs b/core/FieldBuffer.cs new file mode 100644 index 0000000..832ea8b --- /dev/null +++ b/core/FieldBuffer.cs @@ -0,0 +1,59 @@ +using System; +using System.Diagnostics; + +namespace Types +{ + /// + /// Buffer for storing values. + /// + public unsafe struct FieldBuffer + { + /// + /// Maximum amount that can be stored. + /// + public const int Capacity = 32; + + private fixed long buffer[Capacity]; + + /// + /// Indexer for accessing the value at the given . + /// + public Field this[int index] + { + readonly get + { + long typeHash = buffer[index * 2 + 0]; + long nameHash = buffer[index * 2 + 1]; + return new(typeHash, nameHash); + } + set + { + buffer[index * 2 + 0] = value.typeHash; + buffer[index * 2 + 1] = value.nameHash; + } + } + + /// + /// Creates a new buffer containing the given . + /// + public FieldBuffer(ReadOnlySpan fields) + { + ThrowIfCantFit(fields.Length); + + for (int i = 0; i < fields.Length; i++) + { + buffer[i * 2 + 0] = fields[i].typeHash; + buffer[i * 2 + 1] = fields[i].nameHash; + } + } + + [Conditional("DEBUG")] + private static void ThrowIfCantFit(int length) + { + if (length > Capacity) + { + throw new ArgumentOutOfRangeException(nameof(length), $"Cannot fit {length} types into a buffer of capacity {Capacity}"); + } + } + } +} \ No newline at end of file diff --git a/core/Functions/Register.cs b/core/Functions/Register.cs deleted file mode 100644 index a011288..0000000 --- a/core/Functions/Register.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; - -namespace Types.Functions -{ - /// - /// Function to register a type. - /// - public readonly struct Register - { - private readonly Action action; - - internal Register(Action action) - { - this.action = action; - } - - /// - /// Registers a type with the given . - /// - public unsafe readonly void Invoke(ReadOnlySpan variables) where T : unmanaged - { - TypeLayout type = new(TypeLayout.GetFullName(), (ushort)sizeof(T), variables); - Input input = new(type, RuntimeTypeTable.GetHandle()); - action(input); - TypeInstanceCreator.Initialize(type); - } - - /// - /// Registers a type without variables specified. - /// - public unsafe readonly void Invoke() where T : unmanaged - { - TypeLayout type = new(TypeLayout.GetFullName(), (ushort)sizeof(T)); - Input input = new(type, RuntimeTypeTable.GetHandle()); - action(input); - TypeInstanceCreator.Initialize(type); - } - - /// - /// Input parameter. - /// - public readonly struct Input - { - /// - /// Metadata of the type. - /// - public readonly TypeLayout type; - - private readonly nint handle; - - /// - /// Handle of the registering type. - /// - public readonly RuntimeTypeHandle Handle => RuntimeTypeTable.GetHandle(handle); - - /// - /// Creates the input argument. - /// - public Input(TypeLayout type, RuntimeTypeHandle handle) - { - this.type = type; - this.handle = RuntimeTypeTable.GetAddress(handle); - } - } - } -} \ No newline at end of file diff --git a/core/Functions/RegisterFunction.cs b/core/Functions/RegisterFunction.cs new file mode 100644 index 0000000..afed186 --- /dev/null +++ b/core/Functions/RegisterFunction.cs @@ -0,0 +1,76 @@ +using System; + +namespace Types.Functions +{ + /// + /// Function to register a type. + /// + public readonly struct RegisterFunction + { + /// + /// Registers a type with the given and . + /// + public unsafe readonly void RegisterType(ReadOnlySpan variables, ReadOnlySpan interfaces) where T : unmanaged + { + Type type = new(MetadataRegistry.GetFullName(), (ushort)sizeof(T), variables, interfaces); + MetadataRegistry.RegisterType(type, RuntimeTypeTable.GetHandle()); + TypeInstanceCreator.Initialize(type); + } + + /// + /// Registers a type with the given and . + /// + public unsafe readonly void RegisterType(FieldBuffer variables, byte variableCount, InterfaceTypeBuffer interfaces, byte interfaceCount) where T : unmanaged + { + Type type = new(MetadataRegistry.GetFullName(), (ushort)sizeof(T), variables, variableCount, interfaces, interfaceCount); + MetadataRegistry.RegisterType(type, RuntimeTypeTable.GetHandle()); + TypeInstanceCreator.Initialize(type); + } + + /// + /// Registers a type without variables specified. + /// + public unsafe readonly void RegisterType() where T : unmanaged + { + Type type = new(MetadataRegistry.GetFullName(), (ushort)sizeof(T)); + MetadataRegistry.RegisterType(type, RuntimeTypeTable.GetHandle()); + TypeInstanceCreator.Initialize(type); + } + + /// + /// Registers a type without variables specified. + /// + public readonly void RegisterInterface() + { + Interface interfaceValue = new(MetadataRegistry.GetFullName()); + MetadataRegistry.RegisterInterface(interfaceValue, RuntimeTypeTable.GetHandle()); + } + + /// + /// Input parameter. + /// + public readonly struct Input + { + /// + /// Metadata of the type. + /// + public readonly Type type; + + private readonly nint handle; + + /// + /// Handle of the registering type. + /// + public readonly RuntimeTypeHandle Handle => RuntimeTypeTable.GetHandle(handle); + + /// + /// Creates the input argument. + /// + public Input(Type type, RuntimeTypeHandle handle) + { + this.type = type; + this.handle = RuntimeTypeTable.GetAddress(handle); + } + } + } +} \ No newline at end of file diff --git a/core/ITypeBank.cs b/core/ITypeBank.cs index 6d19e9a..7b086c2 100644 --- a/core/ITypeBank.cs +++ b/core/ITypeBank.cs @@ -8,8 +8,8 @@ namespace Types public interface ITypeBank { /// - /// Loads type metadata into . + /// Loads type metadata into . /// - void Load(Register register); + void Load(RegisterFunction register); } } \ No newline at end of file diff --git a/core/Interface.cs b/core/Interface.cs new file mode 100644 index 0000000..ec6acfc --- /dev/null +++ b/core/Interface.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; + +namespace Types +{ + /// + /// Describes an type that a implements. + /// + public readonly struct Interface : IEquatable + { + /// + /// All registered interfaces. + /// + public static IReadOnlyList All => MetadataRegistry.Interfaces; + + private readonly long hash; + + /// + /// The unique hash for this interface. + /// + public readonly long Hash => hash; + + /// + /// The full name of the interface. + /// + public readonly ReadOnlySpan FullName => TypeNames.Get(hash); + + /// + /// Name of the interface. + /// + public readonly ReadOnlySpan Name + { + get + { + ReadOnlySpan fullName = TypeNames.Get(hash); + int index = fullName.LastIndexOf('.'); + if (index != -1) + { + return fullName.Slice(index + 1); + } + else + { + return fullName; + } + } + } + + /// + /// Retrieves the raw handle for this interface. + /// + public readonly RuntimeTypeHandle TypeHandle => MetadataRegistry.GetRuntimeInterfaceHandle(hash); + + /// + /// Initializes an existing interface. + /// + public Interface(string fullTypeName) + { + hash = TypeNames.Set(fullTypeName); + } + + /// + /// Initializes an existing interface. + /// + public Interface(ReadOnlySpan fullTypeName) + { + hash = TypeNames.Set(fullTypeName); + } + + /// + public readonly override string ToString() + { + return FullName.ToString(); + } + + /// + /// Writes a string representation of this interface to . + /// + public readonly int ToString(Span destination) + { + ReadOnlySpan fullName = FullName; + fullName.CopyTo(destination); + return fullName.Length; + } + + /// + /// Checks if this interface is . + /// + public readonly bool Is() + { + Span buffer = stackalloc char[512]; + int length = MetadataRegistry.GetFullName(typeof(T), buffer); + return hash == buffer.Slice(0, length).GetLongHashCode(); + } + + /// + public readonly override bool Equals(object? obj) + { + return obj is Interface type && Equals(type); + } + + /// + public readonly bool Equals(Interface other) + { + return hash == other.hash; + } + + /// + public readonly override int GetHashCode() + { + unchecked + { + return (int)hash; + } + } + + /// + public static bool operator ==(Interface left, Interface right) + { + return left.Equals(right); + } + + /// + public static bool operator !=(Interface left, Interface right) + { + return !(left == right); + } + } +} \ No newline at end of file diff --git a/core/InterfaceTypeBuffer.cs b/core/InterfaceTypeBuffer.cs new file mode 100644 index 0000000..418d278 --- /dev/null +++ b/core/InterfaceTypeBuffer.cs @@ -0,0 +1,64 @@ +using System; +using System.Diagnostics; + +namespace Types +{ + /// + /// Buffer for storing values. + /// + public unsafe struct InterfaceTypeBuffer + { + /// + /// Maximum amount of values that can be stored. + /// + public const int Capacity = 32; + + private fixed long buffer[Capacity]; + + /// + /// Indexer for accessing a value at the given . + /// + public Interface this[int index] + { + readonly get + { + long hash = buffer[index]; + return MetadataRegistry.GetInterface(hash); + } + set + { + buffer[index] = value.Hash; + } + } + + /// + /// Creates a new buffer containing the given . + /// + public InterfaceTypeBuffer(ReadOnlySpan types) + { + ThrowIfCantFit(types.Length); + + for (int i = 0; i < types.Length; i++) + { + buffer[i] = types[i].Hash; + } + } + + /// + /// Retrieves the raw interface hash at the given . + /// + public readonly long Get(int index) + { + return buffer[index]; + } + + [Conditional("DEBUG")] + private static void ThrowIfCantFit(int length) + { + if (length > Capacity) + { + throw new ArgumentOutOfRangeException(nameof(length), $"Cannot fit {length} types into a buffer of capacity {Capacity}"); + } + } + } +} \ No newline at end of file diff --git a/core/MetadataRegistry.cs b/core/MetadataRegistry.cs new file mode 100644 index 0000000..ab45997 --- /dev/null +++ b/core/MetadataRegistry.cs @@ -0,0 +1,530 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; + +namespace Types +{ + /// + /// Stores metadata about types and interfaces. + /// + public static class MetadataRegistry + { + private static readonly List types = new(); + private static readonly List interfaces = new(); + internal static readonly Dictionary handleToType = new(); + internal static readonly Dictionary handleToInterface = new(); + private static readonly Dictionary typeToHandle = new(); + private static readonly Dictionary interfaceToHandle = new(); + private static readonly Dictionary hashToType = new(); + private static readonly Dictionary hashToInterface = new(); + + /// + /// All registered types. + /// + public static IReadOnlyList Types => types; + + /// + /// All registered interfaces. + /// + public static IReadOnlyList Interfaces => interfaces; + + static unsafe MetadataRegistry() + { + RegisterType(new(GetFullName(), sizeof(byte)), RuntimeTypeTable.GetHandle()); + RegisterType(new(GetFullName(), sizeof(sbyte)), RuntimeTypeTable.GetHandle()); + RegisterType(new(GetFullName(), sizeof(short)), RuntimeTypeTable.GetHandle()); + RegisterType(new(GetFullName(), sizeof(ushort)), RuntimeTypeTable.GetHandle()); + RegisterType(new(GetFullName(), sizeof(int)), RuntimeTypeTable.GetHandle()); + RegisterType(new(GetFullName(), sizeof(uint)), RuntimeTypeTable.GetHandle()); + RegisterType(new(GetFullName(), sizeof(long)), RuntimeTypeTable.GetHandle()); + RegisterType(new(GetFullName(), sizeof(ulong)), RuntimeTypeTable.GetHandle()); + RegisterType(new(GetFullName(), sizeof(float)), RuntimeTypeTable.GetHandle()); + RegisterType(new(GetFullName(), sizeof(double)), RuntimeTypeTable.GetHandle()); + RegisterType(new(GetFullName(), sizeof(char)), RuntimeTypeTable.GetHandle()); + RegisterType(new(GetFullName(), sizeof(bool)), RuntimeTypeTable.GetHandle()); + RegisterType(new(GetFullName(), (ushort)sizeof(nint)), RuntimeTypeTable.GetHandle()); + RegisterType(new(GetFullName(), (ushort)sizeof(nuint)), RuntimeTypeTable.GetHandle()); +#if NET + RegisterType(new(GetFullName(), (ushort)sizeof(Half)), RuntimeTypeTable.GetHandle()); +#endif + + FieldBuffer fields = new(); + InterfaceTypeBuffer interfaces = new(); + fields[0] = new("x", GetFullName()); + fields[1] = new("y", GetFullName()); + fields[2] = new("z", GetFullName()); + fields[3] = new("w", GetFullName()); + RegisterType(new(GetFullName(), (ushort)sizeof(Vector2), fields, 2, interfaces, 0), RuntimeTypeTable.GetHandle()); + RegisterType(new(GetFullName(), (ushort)sizeof(Vector3), fields, 3, interfaces, 0), RuntimeTypeTable.GetHandle()); + RegisterType(new(GetFullName(), (ushort)sizeof(Vector4), fields, 4, interfaces, 0), RuntimeTypeTable.GetHandle()); + RegisterType(new(GetFullName(), (ushort)sizeof(Quaternion), fields, 4, interfaces, 0), RuntimeTypeTable.GetHandle()); + + fields[0] = new("M11", GetFullName()); + fields[1] = new("M12", GetFullName()); + fields[2] = new("M21", GetFullName()); + fields[3] = new("M22", GetFullName()); + fields[4] = new("M31", GetFullName()); + fields[5] = new("M32", GetFullName()); + RegisterType(new(GetFullName(), (ushort)sizeof(Matrix3x2), fields, 6, interfaces, 0), RuntimeTypeTable.GetHandle()); + + fields[0] = new("M11", GetFullName()); + fields[1] = new("M12", GetFullName()); + fields[2] = new("M13", GetFullName()); + fields[3] = new("M14", GetFullName()); + fields[4] = new("M21", GetFullName()); + fields[5] = new("M22", GetFullName()); + fields[6] = new("M23", GetFullName()); + fields[7] = new("M24", GetFullName()); + fields[8] = new("M31", GetFullName()); + fields[9] = new("M32", GetFullName()); + fields[10] = new("M33", GetFullName()); + fields[11] = new("M34", GetFullName()); + fields[12] = new("M41", GetFullName()); + fields[13] = new("M42", GetFullName()); + fields[14] = new("M43", GetFullName()); + fields[15] = new("M44", GetFullName()); + RegisterType(new(GetFullName(), (ushort)sizeof(Matrix4x4), fields, 16, interfaces, 0), RuntimeTypeTable.GetHandle()); + + fields[0] = new("_dateData", GetFullName()); + RegisterType(new(GetFullName(), (ushort)sizeof(DateTime), fields, 1, interfaces, 0), RuntimeTypeTable.GetHandle()); + } + + /// + /// Loads all s from the bank of type . + /// + public static void Load() where T : unmanaged, ITypeBank + { + T bank = default; + bank.Load(new()); + } + + /// + /// Manually registers the given . + /// + public static void RegisterType(Type type, RuntimeTypeHandle handle) + { + ThrowIfAlreadyRegistered(type); + + types.Add(type); + handleToType.Add(handle, type); + typeToHandle.Add(type.Hash, handle); + hashToType.Add(type.Hash, type); + } + + /// + /// Manually registers the given . + /// + public static void RegisterInterface(Interface interfaceValue, RuntimeTypeHandle handle) + { + ThrowIfAlreadyRegistered(interfaceValue); + + interfaces.Add(interfaceValue); + handleToInterface.Add(handle, interfaceValue); + interfaceToHandle.Add(interfaceValue.Hash, handle); + hashToInterface.Add(interfaceValue.Hash, interfaceValue); + } + + /// + /// Tries to manually register the given . + /// + public static bool TryRegisterType(Type type, RuntimeTypeHandle handle) + { + if (types.Contains(type)) + { + return false; + } + + types.Add(type); + handleToType.Add(handle, type); + typeToHandle.Add(type.Hash, handle); + hashToType.Add(type.Hash, type); + return true; + } + + /// + /// Manually registers type without any fields or interfaces. + /// + public unsafe static void RegisterType() where T : unmanaged + { + ushort size = (ushort)sizeof(T); + Type type = new(GetFullName(), size); + RegisterType(type, RuntimeTypeTable.GetHandle()); + } + + /// + /// Manually registers the interface of type . + /// + public static void RegisterInterface() + { + Interface interfaceValue = new(GetFullName()); + RegisterInterface(interfaceValue, RuntimeTypeTable.GetHandle()); + } + + /// + /// Tries to manually register type without any fields or interfaces. + /// + public unsafe static bool TryRegisterType() where T : unmanaged + { + ushort size = (ushort)sizeof(T); + Type type = new(GetFullName(), size); + return TryRegisterType(type, RuntimeTypeTable.GetHandle()); + } + + /// + /// Retrieves the metadata for . + /// + public static Type GetType() where T : unmanaged + { + ThrowIfTypeNotRegistered(); + + return TypeCache.value; + } + + /// + /// Retrieves the metadata for . + /// + public static Interface GetInterface() where T : unmanaged + { + ThrowIfTypeNotRegistered(); + + return InterfaceCache.value; + } + + /// + /// Retrieves the metadata for , or registers + /// it if it's not already registered without any variables. + /// + public static Type GetOrRegisterType() where T : unmanaged + { + return LazyTypeCache.value; + } + + /// + /// Retrieves the metadata for the type with the given . + /// + public static Type GetType(long typeHash) + { + ThrowIfTypeNotRegistered(typeHash); + + return hashToType[typeHash]; + } + + /// + /// Retrieves the metadata for the type with the given . + /// + public static Interface GetInterface(long typeHash) + { + ThrowIfTypeNotRegistered(typeHash); + + return hashToInterface[typeHash]; + } + + /// + /// Tries to get the metadata for the type with the given . + /// + public static bool TryGetType(long typeHash, out Type type) + { + return hashToType.TryGetValue(typeHash, out type); + } + + /// + /// Retrieves the metadata for the of the wanted type. + /// + public static Type GetType(RuntimeTypeHandle handle) + { + ThrowIfNotRegistered(handle); + + return handleToType[handle]; + } + + /// + /// Retrieves the raw handle for the . + /// + public static RuntimeTypeHandle GetRuntimeTypeHandle(long typeHash) + { + ThrowIfTypeNotRegistered(typeHash); + + return typeToHandle[typeHash]; + } + + /// + /// Retrieves the raw handle for the . + /// + public static RuntimeTypeHandle GetRuntimeInterfaceHandle(long typeHash) + { + ThrowIfInterfaceNotRegistered(typeHash); + + return interfaceToHandle[typeHash]; + } + + /// + /// Checks if type is registered. + /// + public static bool IsTypeRegistered() where T : unmanaged + { + return handleToType.ContainsKey(RuntimeTypeTable.GetHandle()); + } + + /// + /// Checks if type is registered. + /// + public static bool IsInterfaceRegistered() + { + return handleToInterface.ContainsKey(RuntimeTypeTable.GetHandle()); + } + + /// + /// Checks if the given is registered. + /// + public static bool IsTypeRegistered(System.Type type) + { + return handleToType.ContainsKey(RuntimeTypeTable.GetHandle(type)); + } + + /// + /// Checks if a type with is registered. + /// + public static bool IsRegistered(ReadOnlySpan fullTypeName) + { + long hash = fullTypeName.GetLongHashCode(); + foreach (Type type in types) + { + if (type.Hash == hash) + { + return true; + } + } + + return false; + } + + /// + /// Checks if a type with is registered. + /// + public static bool IsRegistered(string fullTypeName) + { + long hash = fullTypeName.GetLongHashCode(); + foreach (Type type in types) + { + if (type.Hash == hash) + { + return true; + } + } + + return false; + } + + /// + /// Retrieves the full type name for the given . + /// + public static int GetFullName(System.Type type, Span buffer) + { + int length = 0; + AppendType(buffer, ref length, type); + return length; + + static void Insert(Span buffer, char character, ref int length) + { + buffer.Slice(0, length).CopyTo(buffer.Slice(1)); + buffer[0] = character; + length++; + } + + static void InsertSpan(Span buffer, ReadOnlySpan text, ref int length) + { + buffer.Slice(0, length).CopyTo(buffer.Slice(text.Length)); + text.CopyTo(buffer); + length += text.Length; + } + + static void AppendType(Span fullName, ref int length, System.Type type) + { + //todo: handle case where the type name is System.Collections.Generic.List`1+Enumerator[etc, etc] + System.Type? current = type; + string? currentNameSpace = current.Namespace; + while (current is not null) + { + System.Type[] genericTypes = current.GenericTypeArguments; + string name = current.Name; + if (genericTypes.Length > 0) + { + Insert(fullName, '>', ref length); + for (int i = genericTypes.Length - 1; i >= 0; i--) + { + AppendType(fullName, ref length, genericTypes[i]); + if (i > 0) + { + InsertSpan(fullName, ", ", ref length); + } + } + + Insert(fullName, '<', ref length); + int index = name.IndexOf('`'); + if (index != -1) + { + string trimmedName = name[..index]; + InsertSpan(fullName, trimmedName, ref length); + } + } + else + { + InsertSpan(fullName, name, ref length); + } + + current = current.DeclaringType; + if (current is not null) + { + Insert(fullName, '.', ref length); + } + } + + if (currentNameSpace is not null) + { + Insert(fullName, '.', ref length); + InsertSpan(fullName, currentNameSpace, ref length); + } + } + } + + /// + /// Retrieves the full type name for the given . + /// + public static string GetFullName(System.Type type) + { + Span buffer = stackalloc char[512]; + int length = GetFullName(type, buffer); + return buffer.Slice(0, length).ToString(); + } + + /// + /// Retrieves the full type name for the type . + /// + public static string GetFullName() + { + Span buffer = stackalloc char[512]; + int length = GetFullName(typeof(T), buffer); + return buffer.Slice(0, length).ToString(); + } + + [Conditional("DEBUG")] + private static void ThrowIfTypeNotRegistered() where T : unmanaged + { + if (!IsTypeRegistered()) + { + throw new InvalidOperationException($"Type `{typeof(T)}` is not registered"); + } + } + + [Conditional("DEBUG")] + private static void ThrowIfTypeNotRegistered(long hash) + { + if (!hashToType.ContainsKey(hash)) + { + throw new InvalidOperationException($"Type with hash `{hash}` is not registered"); + } + } + + + [Conditional("DEBUG")] + private static void ThrowIfInterfaceNotRegistered(long hash) + { + if (!hashToInterface.ContainsKey(hash)) + { + throw new InvalidOperationException($"Interface with hash `{hash}` is not registered"); + } + } + + [Conditional("DEBUG")] + private static void ThrowIfNotRegistered(RuntimeTypeHandle handle) + { + if (!handleToType.ContainsKey(handle)) + { + System.Type? type = System.Type.GetTypeFromHandle(handle); + if (type is not null) + { + throw new InvalidOperationException($"Type `{type}` is not registered"); + } + else + { + throw new InvalidOperationException($"Type with handle `{handle}` is not registered"); + } + } + } + + [Conditional("DEBUG")] + private static void ThrowIfAlreadyRegistered(Type type) + { + if (types.Contains(type)) + { + throw new InvalidOperationException($"Type `{type}` is already registered"); + } + } + + [Conditional("DEBUG")] + private static void ThrowIfAlreadyRegistered(Interface interfaceValue) + { + if (interfaces.Contains(interfaceValue)) + { + throw new InvalidOperationException($"Interface `{interfaceValue}` is already registered"); + } + } + + private static class TypeCache where T : unmanaged + { + public static readonly Type value; + + static TypeCache() + { + if (!handleToType.TryGetValue(RuntimeTypeTable.GetHandle(), out value)) + { + throw new InvalidOperationException($"Type `{typeof(T)}` is not registered"); + } + } + } + + private static class InterfaceCache + { + public static readonly Interface value; + + static InterfaceCache() + { + if (!handleToInterface.TryGetValue(RuntimeTypeTable.GetHandle(), out value)) + { + throw new InvalidOperationException($"Interface `{typeof(T)}` is not registered"); + } + } + } + + private unsafe static class LazyTypeCache where T : unmanaged + { + public static readonly Type value; + + static LazyTypeCache() + { + RuntimeTypeHandle key = RuntimeTypeTable.GetHandle(); + if (!handleToType.TryGetValue(key, out value)) + { + value = new(GetFullName(), (ushort)sizeof(T)); + RegisterType(value, key); + } + } + } + + private unsafe static class LazyInterfaceCache where T : unmanaged + { + public static readonly Interface value; + + static LazyInterfaceCache() + { + RuntimeTypeHandle key = RuntimeTypeTable.GetHandle(); + if (!handleToInterface.TryGetValue(key, out value)) + { + value = new(GetFullName()); + RegisterInterface(value, key); + } + } + } + } +} \ No newline at end of file diff --git a/core/Type.cs b/core/Type.cs new file mode 100644 index 0000000..d40c9c9 --- /dev/null +++ b/core/Type.cs @@ -0,0 +1,463 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Types +{ + /// + /// Describes metadata for a type. + /// + [SkipLocalsInit] + public readonly struct Type : IEquatable + { + /// + /// All registered types. + /// + public static IReadOnlyList All => MetadataRegistry.Types; + + /// + /// Size of the type in bytes. + /// + public readonly ushort size; + + private readonly byte fieldCount; + private readonly byte interfaceCount; + private readonly long hash; + private readonly FieldBuffer fields; + private readonly InterfaceTypeBuffer interfaces; + + /// + /// Hash value unique to this type. + /// + public readonly long Hash => hash; + + /// + /// All fields declared in the type. + /// + public unsafe readonly ReadOnlySpan Fields + { + get + { + fixed (void* pointer = &fields) + { + return new ReadOnlySpan(pointer, fieldCount); + } + } + } + + /// + /// All interfaces implemented by this type. + /// + public unsafe readonly ReadOnlySpan Interfaces + { + get + { + fixed (void* pointer = &interfaces) + { + return new ReadOnlySpan(pointer, interfaceCount); + } + } + } + + /// + /// The underlying system type that this represents. + /// + public readonly System.Type SystemType + { + get + { + RuntimeTypeHandle handle = TypeHandle; + return System.Type.GetTypeFromHandle(handle) ?? throw new InvalidOperationException($"System type not found for handle {handle}"); + } + } + + /// + /// Retrieves the raw handle for this type. + /// + public readonly RuntimeTypeHandle TypeHandle => MetadataRegistry.GetRuntimeTypeHandle(hash); + + /// + /// Full name of the type including the namespace. + /// + public readonly ReadOnlySpan FullName => TypeNames.Get(hash); + + /// + /// Name of the type. + /// + public readonly ReadOnlySpan Name + { + get + { + ReadOnlySpan fullName = TypeNames.Get(hash); + int index = fullName.LastIndexOf('.'); + if (index != -1) + { + return fullName.Slice(index + 1); + } + else + { + return fullName; + } + } + } + +#if NET + /// + /// Default constructor not supported. + /// + [Obsolete("Default constructor not supported", true)] + public Type() + { + throw new NotSupportedException(); + } +#endif + + /// + /// Initializes an existing value type. + /// + public Type(ReadOnlySpan fullName, ushort size) + { + this.size = size; + fieldCount = 0; + fields = default; + hash = TypeNames.Set(fullName); + } + + /// + /// Initializes an existing value type. + /// + public Type(string fullName, ushort size) + { + this.size = size; + fieldCount = 0; + fields = default; + hash = TypeNames.Set(fullName); + } + + /// + /// Creates a new type. + /// + public Type(ReadOnlySpan fullName, ushort size, ReadOnlySpan fields, ReadOnlySpan interfaces) + { + this.size = size; + fieldCount = (byte)fields.Length; + this.fields = new(fields); + interfaceCount = (byte)interfaces.Length; + this.interfaces = new(interfaces); + hash = TypeNames.Set(fullName); + } + + /// + /// Creates a new type. + /// + public Type(ReadOnlySpan fullName, ushort size, FieldBuffer fields, byte fieldCount, InterfaceTypeBuffer interfaces, byte interfaceCount) + { + this.size = size; + this.fieldCount = fieldCount; + this.fields = fields; + this.interfaceCount = interfaceCount; + this.interfaces = interfaces; + hash = TypeNames.Set(fullName); + } + + /// + /// Creates a new type. + /// + public Type(string fullName, ushort size, ReadOnlySpan fields, ReadOnlySpan interfaces) + { + this.size = size; + fieldCount = (byte)fields.Length; + this.fields = new(fields); + interfaceCount = (byte)interfaces.Length; + this.interfaces = new(interfaces); + hash = TypeNames.Set(fullName); + } + + /// + public readonly override string ToString() + { + return FullName.ToString(); + } + + /// + /// Writes a string representation of this type to . + /// + public readonly int ToString(Span destination) + { + ReadOnlySpan fullName = FullName; + fullName.CopyTo(destination); + return fullName.Length; + } + + /// + /// Checks if this type metadata represents type . + /// + public readonly bool Is() where T : unmanaged + { + MetadataRegistry.handleToType.TryGetValue(RuntimeTypeTable.GetHandle(), out Type otherType); + return hash == otherType.hash; + } + + /// + /// Checks if the type implements the given + /// . + /// + public readonly bool Implements() + { + Span buffer = stackalloc char[512]; + int length = MetadataRegistry.GetFullName(typeof(T), buffer); + long hash = buffer.Slice(0, length).GetLongHashCode(); + for (int i = 0; i < interfaceCount; i++) + { + if (interfaces.Get(i) == hash) + { + return true; + } + } + + return false; + } + + /// + /// Checks if the type implements the given . + /// + public readonly bool Implements(Interface interfaceValue) + { + long hash = interfaceValue.Hash; + for (int i = 0; i < interfaceCount; i++) + { + if (interfaces.Get(i) == hash) + { + return true; + } + } + + return false; + } + + /// + /// Creates an instance of this type. + /// + public readonly object CreateInstance(ReadOnlySpan bytes) + { + return TypeInstanceCreator.Do(this, bytes); + } + + /// + /// Creates an instance of this type + /// with default state. + /// + public readonly object CreateInstance() + { + Span bytes = stackalloc byte[size]; + bytes.Clear(); + return CreateInstance(bytes); + } + + /// + /// Copies all fields in this type to the . + /// + public readonly byte CopyFieldsTo(Span destination) + { + for (int i = 0; i < fieldCount; i++) + { + destination[i] = fields[i]; + } + + return fieldCount; + } + + /// + /// Checks if this type contains a fields with the given . + /// + public readonly bool ContainsField(string fieldName) + { + ReadOnlySpan nameSpan = fieldName.AsSpan(); + for (int i = 0; i < fieldCount; i++) + { + if (fields[i].Name.SequenceEqual(nameSpan)) + { + return true; + } + } + + return false; + } + + /// + /// Checks if this type contains a fields with the given . + /// + public readonly bool ContainsField(ReadOnlySpan fieldName) + { + ReadOnlySpan nameSpan = fieldName; + for (int i = 0; i < fieldCount; i++) + { + if (fields[i].Name.SequenceEqual(nameSpan)) + { + return true; + } + } + + return false; + } + + /// + /// Retrieves the field in this type with the given . + /// + public readonly Field GetField(string fieldName) + { + ThrowIfFieldIsMissing(fieldName); + + for (int i = 0; i < fieldCount; i++) + { + Field field = fields[i]; + if (field.Name.SequenceEqual(fieldName)) + { + return field; + } + } + + return default; + } + + /// + /// Retrieves the field in this type with the given . + /// + public readonly Field GetField(ReadOnlySpan fieldName) + { + ThrowIfFieldIsMissing(fieldName); + + for (int i = 0; i < fieldCount; i++) + { + Field field = fields[i]; + if (field.Name.SequenceEqual(fieldName)) + { + return field; + } + } + + return default; + } + + /// + /// Retrieves the index of the field with the given . + /// + public readonly int IndexOf(string fieldName) + { + ThrowIfFieldIsMissing(fieldName); + + for (int i = 0; i < fieldCount; i++) + { + Field field = fields[i]; + if (field.Name.SequenceEqual(fieldName)) + { + return i; + } + } + + return -1; + } + + /// + /// Retrieves the index of the field with the given . + /// + public readonly int IndexOf(ReadOnlySpan fieldName) + { + ThrowIfFieldIsMissing(fieldName); + + for (int i = 0; i < fieldCount; i++) + { + Field field = fields[i]; + if (field.Name.SequenceEqual(fieldName)) + { + return i; + } + } + + return -1; + } + + [Conditional("DEBUG")] + private readonly void ThrowIfFieldIsMissing(ReadOnlySpan fieldName) + { + if (!ContainsField(fieldName)) + { + throw new InvalidOperationException($"Field with name `{fieldName.ToString()}` not found in type {FullName.ToString()}"); + } + } + + /// + public readonly override bool Equals(object? obj) + { + return obj is Type type && Equals(type); + } + + /// + public readonly bool Equals(Type other) + { + return hash == other.hash; + } + + /// + public readonly override int GetHashCode() + { + unchecked + { + return (int)hash; + } + } + + /// + /// Retrieves all types that implement the given interface. + /// + public static IEnumerable GetAllThatImplement() + { + Span buffer = stackalloc char[512]; + int length = MetadataRegistry.GetFullName(typeof(T), buffer); + long hash = buffer.Slice(0, length).GetLongHashCode(); + foreach (Type type in All) + { + for (int i = 0; i < type.interfaceCount; i++) + { + if (type.interfaces.Get(i) == hash) + { + yield return type; + break; + } + } + } + } + + /// + /// Retrieves all types that implement the given . + /// + public static IEnumerable GetAllThatImplement(Interface interfaceValue) + { + long hash = interfaceValue.Hash; + foreach (Type type in All) + { + for (int i = 0; i < type.interfaceCount; i++) + { + if (type.interfaces.Get(i) == hash) + { + yield return type; + break; + } + } + } + } + + /// + public static bool operator ==(Type left, Type right) + { + return left.Equals(right); + } + + /// + public static bool operator !=(Type left, Type right) + { + return !(left == right); + } + } +} \ No newline at end of file diff --git a/core/TypeInstanceCreator.cs b/core/TypeInstanceCreator.cs index b2e60bd..76b4d52 100644 --- a/core/TypeInstanceCreator.cs +++ b/core/TypeInstanceCreator.cs @@ -5,9 +5,9 @@ namespace Types { internal static class TypeInstanceCreator { - private static readonly Dictionary functions = new(); + private static readonly Dictionary functions = new(); - public unsafe static void Initialize(TypeLayout type) where T : unmanaged + public unsafe static void Initialize(Type type) where T : unmanaged { functions[type] = static (bytes) => { @@ -19,7 +19,7 @@ public unsafe static void Initialize(TypeLayout type) where T : unmanaged }; } - public static object Do(TypeLayout type, ReadOnlySpan bytes) + public static object Do(Type type, ReadOnlySpan bytes) { Create action = functions[type]; return action(bytes); diff --git a/core/TypeLayout.cs b/core/TypeLayout.cs deleted file mode 100644 index 2b9249e..0000000 --- a/core/TypeLayout.cs +++ /dev/null @@ -1,623 +0,0 @@ -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; - -namespace Types -{ - /// - /// Describes metadata for a type. - /// - [SkipLocalsInit] - public readonly struct TypeLayout : IEquatable - { - /// - /// Maximum amount of variables per type. - /// - public const byte Capacity = 32; - - /// - /// Size of the type in bytes. - /// - public readonly ushort size; - - /// - /// Amount of s the type has. - /// - public readonly byte variableCount; - - private readonly long hash; - private readonly VariablesCollection variables; - - /// - /// Hash value unique to this type. - /// - public readonly long Hash => hash; - - [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] - private readonly Variable[] Variables - { - get - { - Variable[] variables = new Variable[variableCount]; - for (int i = 0; i < variableCount; i++) - { - variables[i] = this.variables[i]; - } - - return variables; - } - } - - /// - /// The underlying system type that this layout represents. - /// - public readonly Type SystemType - { - get - { - RuntimeTypeHandle handle = TypeHandle; - return Type.GetTypeFromHandle(handle) ?? throw new InvalidOperationException($"System type not found for handle {handle}"); - } - } - - /// - /// Retrieves the raw handle for this type. - /// - public readonly RuntimeTypeHandle TypeHandle => TypeRegistry.GetRuntimeTypeHandle(hash); - - /// - /// Full name of the type including the namespace. - /// - public readonly ReadOnlySpan FullName => TypeNames.Get(hash); - - /// - /// Name of the type. - /// - public readonly ReadOnlySpan Name - { - get - { - ReadOnlySpan fullName = TypeNames.Get(hash); - int index = fullName.LastIndexOf('.'); - if (index != -1) - { - return fullName.Slice(index + 1); - } - else - { - return fullName; - } - } - } - - /// - /// Indexer for variables. - /// - public readonly Variable this[int index] => variables[index]; - - /// - /// Indexer for variables. - /// - public readonly Variable this[uint index] => variables[(int)index]; - -#if NET - /// - /// Default constructor not supported. - /// - [Obsolete("Default constructor not supported", true)] - public TypeLayout() - { - throw new NotSupportedException(); - } -#endif - - /// - /// Creates a new type layout without any variables set. - /// - public TypeLayout(ReadOnlySpan fullName, ushort size) - { - this.size = size; - variableCount = 0; - variables = default; - hash = TypeNames.Set(fullName); - } - - /// - /// Creates a new type layout without any variables set. - /// - public TypeLayout(string fullName, ushort size) - { - this.size = size; - variableCount = 0; - variables = default; - hash = TypeNames.Set(fullName); - } - - /// - /// Creates a new type layout. - /// - public TypeLayout(ReadOnlySpan fullName, ushort size, ReadOnlySpan variables) - { - ThrowIfGreaterThanCapacity(variables.Length); - - this.size = size; - variableCount = (byte)variables.Length; - this.variables = new(); - for (int i = 0; i < variableCount; i++) - { - this.variables[i] = variables[i]; - } - - hash = TypeNames.Set(fullName); - } - - /// - /// Creates a new type layout. - /// - public TypeLayout(string fullName, ushort size, ReadOnlySpan variables) - { - ThrowIfGreaterThanCapacity(variables.Length); - - this.size = size; - variableCount = (byte)variables.Length; - this.variables = new(); - for (int i = 0; i < variableCount; i++) - { - this.variables[i] = variables[i]; - } - - hash = TypeNames.Set(fullName); - } - - /// - public readonly override string ToString() - { - return TypeNames.Get(hash).ToString(); - } - - /// - /// Writes a string representation of this type layout to . - /// - public readonly int ToString(Span destination) - { - ReadOnlySpan fullName = TypeNames.Get(hash); - fullName.CopyTo(destination); - return fullName.Length; - } - - /// - /// Checks if this type metadata represents type . - /// - public readonly bool Is() where T : unmanaged - { - TypeRegistry.handleToType.TryGetValue(RuntimeTypeTable.GetHandle(), out TypeLayout otherType); - return hash == otherType.hash; - } - - /// - /// Creates an instance of this type. - /// - public readonly object CreateInstance(ReadOnlySpan bytes) - { - return TypeInstanceCreator.Do(this, bytes); - } - - /// - /// Creates an instance of this type - /// with default state. - /// - public readonly object CreateInstance() - { - Span bytes = stackalloc byte[size]; - bytes.Clear(); - return CreateInstance(bytes); - } - - /// - /// Copies all variables in this type to the . - /// - public readonly byte CopyVariablesTo(Span destination) - { - for (int i = 0; i < variableCount; i++) - { - destination[i] = variables[i]; - } - - return variableCount; - } - - /// - /// Checks if this type contains a variable with the given . - /// - public readonly bool ContainsVariable(string name) - { - ReadOnlySpan nameSpan = name.AsSpan(); - for (int i = 0; i < variableCount; i++) - { - if (variables[i].Name.SequenceEqual(nameSpan)) - { - return true; - } - } - - return false; - } - - /// - /// Checks if this type contains a variable with the given . - /// - public readonly bool ContainsVariable(ReadOnlySpan name) - { - ReadOnlySpan nameSpan = name; - for (int i = 0; i < variableCount; i++) - { - if (variables[i].Name.SequenceEqual(nameSpan)) - { - return true; - } - } - - return false; - } - - /// - /// Retrieves the variable in this type with the given . - /// - public readonly Variable GetVariable(string name) - { - ThrowIfVariableIsMissing(name); - - for (int i = 0; i < variableCount; i++) - { - Variable variable = variables[i]; - if (variable.Name.SequenceEqual(name)) - { - return variable; - } - } - - return default; - } - - /// - /// Retrieves the variable in this type with the given . - /// - public readonly Variable GetVariable(ReadOnlySpan name) - { - ThrowIfVariableIsMissing(name); - - for (int i = 0; i < variableCount; i++) - { - Variable variable = variables[i]; - if (variable.Name.SequenceEqual(name)) - { - return variable; - } - } - - return default; - } - - /// - /// Retrieves the index of the variable with the given . - /// - public readonly int IndexOf(string name) - { - ThrowIfVariableIsMissing(name); - - for (int i = 0; i < variableCount; i++) - { - Variable variable = variables[i]; - if (variable.Name.SequenceEqual(name)) - { - return i; - } - } - - return -1; - } - - /// - /// Retrieves the index of the variable with the given . - /// - public readonly int IndexOf(ReadOnlySpan name) - { - ThrowIfVariableIsMissing(name); - - for (int i = 0; i < variableCount; i++) - { - Variable variable = variables[i]; - if (variable.Name.SequenceEqual(name)) - { - return i; - } - } - - return -1; - } - - /// - /// Retrieves the full type name for the given . - /// - public static int GetFullName(Type type, Span buffer) - { - int length = 0; - AppendType(buffer, ref length, type); - return length; - - static void Insert(Span buffer, char character, ref int length) - { - buffer.Slice(0, length).CopyTo(buffer.Slice(1)); - buffer[0] = character; - length++; - } - - static void InsertSpan(Span buffer, ReadOnlySpan text, ref int length) - { - buffer.Slice(0, length).CopyTo(buffer.Slice(text.Length)); - text.CopyTo(buffer); - length += text.Length; - } - - static void AppendType(Span fullName, ref int length, Type type) - { - //todo: handle case where the type name is System.Collections.Generic.List`1+Enumerator[etc, etc] - Type? current = type; - string? currentNameSpace = current.Namespace; - while (current is not null) - { - Type[] genericTypes = current.GenericTypeArguments; - string name = current.Name; - if (genericTypes.Length > 0) - { - Insert(fullName, '>', ref length); - for (int i = genericTypes.Length - 1; i >= 0; i--) - { - AppendType(fullName, ref length, genericTypes[i]); - if (i > 0) - { - InsertSpan(fullName, ", ", ref length); - } - } - - Insert(fullName, '<', ref length); - int index = name.IndexOf('`'); - if (index != -1) - { - string trimmedName = name[..index]; - InsertSpan(fullName, trimmedName, ref length); - } - } - else - { - InsertSpan(fullName, name, ref length); - } - - current = current.DeclaringType; - if (current is not null) - { - Insert(fullName, '.', ref length); - } - } - - if (currentNameSpace is not null) - { - Insert(fullName, '.', ref length); - InsertSpan(fullName, currentNameSpace, ref length); - } - } - } - - /// - /// Retrieves the full type name for the given . - /// - public static string GetFullName(Type type) - { - Span buffer = stackalloc char[512]; - int length = GetFullName(type, buffer); - return buffer.Slice(0, length).ToString(); - } - - /// - /// Retrieves the full type name for the type . - /// - public static string GetFullName() - { - Span buffer = stackalloc char[512]; - int length = GetFullName(typeof(T), buffer); - return buffer.Slice(0, length).ToString(); - } - - [Conditional("DEBUG")] - private readonly void ThrowIfVariableIsMissing(ReadOnlySpan name) - { - if (!ContainsVariable(name)) - { - throw new InvalidOperationException($"Variable `{name.ToString()}` not found in type {FullName.ToString()}"); - } - } - - [Conditional("DEBUG")] - private static void ThrowIfGreaterThanCapacity(int length) - { - if (length > Capacity) - { - throw new InvalidOperationException($"TypeLayout has reached its capacity of {Capacity} variables"); - } - } - - /// - public readonly override bool Equals(object? obj) - { - return obj is TypeLayout layout && Equals(layout); - } - - /// - public readonly bool Equals(TypeLayout other) - { - return hash == other.hash; - } - - /// - public readonly override int GetHashCode() - { - unchecked - { - return (int)hash; - } - } - - /// - public static bool operator ==(TypeLayout left, TypeLayout right) - { - return left.Equals(right); - } - - /// - public static bool operator !=(TypeLayout left, TypeLayout right) - { - return !(left == right); - } - - /// - /// Describes a variable part of a . - /// - public readonly struct Variable : IEquatable - { - internal readonly long nameHash; - internal readonly long typeHash; - - /// - /// Name of the variable. - /// - public readonly ReadOnlySpan Name => TypeNames.Get(nameHash); - - /// - /// Type layout of the variable. - /// - public readonly TypeLayout Type => TypeRegistry.Get(typeHash); - - /// - /// Size of the variable in bytes. - /// - public readonly ushort Size => Type.size; - - /// - /// Creates a new variable with the given and . - /// - public Variable(string name, string fullTypeName) - { - this.nameHash = TypeNames.Set(name); - typeHash = fullTypeName.GetLongHashCode(); - } - - internal Variable(long typeHash, long nameHash) - { - this.typeHash = typeHash; - this.nameHash = nameHash; - } - - /// - /// Creates a new variable with the given and . - /// - public Variable(ReadOnlySpan name, ReadOnlySpan fullTypeName) - { - this.nameHash = TypeNames.Set(name); - typeHash = fullTypeName.GetLongHashCode(); - } - - /// - /// Creates a new variable with the given and . - /// - public Variable(string name, int typeHash) - { - this.nameHash = TypeNames.Set(name); - this.typeHash = typeHash; - } - - /// - public readonly override string ToString() - { - Span buffer = stackalloc char[256]; - int length = ToString(buffer); - return buffer.Slice(0, length).ToString(); - } - - /// - /// Builds a string representation of this variable and writes it to . - /// - /// Amount of characters written. - public readonly int ToString(Span buffer) - { - TypeLayout typeLayout = Type; - typeLayout.Name.CopyTo(buffer); - int length = typeLayout.Name.Length; - buffer[length++] = '='; - Name.CopyTo(buffer.Slice(length)); - length += Name.Length; - return length; - } - - /// - public readonly override bool Equals(object? obj) - { - return obj is Variable variable && Equals(variable); - } - - /// - public readonly bool Equals(Variable other) - { - return nameHash == other.nameHash && typeHash == other.typeHash; - } - - /// - public readonly override int GetHashCode() - { - unchecked - { - int hashCode = 17; - ReadOnlySpan name = Name; - for (int i = 0; i < name.Length; i++) - { - hashCode = hashCode * 31 + name[i]; - } - - hashCode = hashCode * 31 + (int)typeHash; - return hashCode; - } - } - - /// - public static bool operator ==(Variable left, Variable right) - { - return left.Equals(right); - } - - /// - public static bool operator !=(Variable left, Variable right) - { - return !(left == right); - } - } - - internal unsafe struct VariablesCollection - { - private fixed long variables[TypeLayout.Capacity * 2]; - - public Variable this[int index] - { - readonly get - { - long typeHash = variables[index * 2 + 0]; - long nameHash = variables[index * 2 + 1]; - return new(typeHash, nameHash); - } - set - { - variables[index * 2 + 0] = value.typeHash; - variables[index * 2 + 1] = value.nameHash; - } - } - } - } -} \ No newline at end of file diff --git a/core/TypeRegistry.cs b/core/TypeRegistry.cs deleted file mode 100644 index 539c67a..0000000 --- a/core/TypeRegistry.cs +++ /dev/null @@ -1,334 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Numerics; -using System.Runtime.CompilerServices; -using Types.Functions; - -namespace Types -{ - /// - /// Stores metadata about types. - /// - public static class TypeRegistry - { - private static readonly List types = new(); - internal static readonly Dictionary handleToType = new(); - private static readonly Dictionary typeToHandle = new(); - private static readonly Dictionary hashToType = new(); - - /// - /// All registered type layouts. - /// - public static IReadOnlyCollection All => types; - - [SkipLocalsInit] - static unsafe TypeRegistry() - { - Register(new(TypeLayout.GetFullName(), sizeof(byte)), RuntimeTypeTable.GetHandle()); - Register(new(TypeLayout.GetFullName(), sizeof(sbyte)), RuntimeTypeTable.GetHandle()); - Register(new(TypeLayout.GetFullName(), sizeof(short)), RuntimeTypeTable.GetHandle()); - Register(new(TypeLayout.GetFullName(), sizeof(ushort)), RuntimeTypeTable.GetHandle()); - Register(new(TypeLayout.GetFullName(), sizeof(int)), RuntimeTypeTable.GetHandle()); - Register(new(TypeLayout.GetFullName(), sizeof(uint)), RuntimeTypeTable.GetHandle()); - Register(new(TypeLayout.GetFullName(), sizeof(long)), RuntimeTypeTable.GetHandle()); - Register(new(TypeLayout.GetFullName(), sizeof(ulong)), RuntimeTypeTable.GetHandle()); - Register(new(TypeLayout.GetFullName(), sizeof(float)), RuntimeTypeTable.GetHandle()); - Register(new(TypeLayout.GetFullName(), sizeof(double)), RuntimeTypeTable.GetHandle()); - Register(new(TypeLayout.GetFullName(), sizeof(char)), RuntimeTypeTable.GetHandle()); - Register(new(TypeLayout.GetFullName(), sizeof(bool)), RuntimeTypeTable.GetHandle()); - Register(new(TypeLayout.GetFullName(), (ushort)sizeof(nint)), RuntimeTypeTable.GetHandle()); - Register(new(TypeLayout.GetFullName(), (ushort)sizeof(nuint)), RuntimeTypeTable.GetHandle()); -#if NET - Register(new(TypeLayout.GetFullName(), (ushort)sizeof(Half)), RuntimeTypeTable.GetHandle()); -#endif - - Span buffer = stackalloc TypeLayout.Variable[16]; - - buffer[0] = new("x", TypeLayout.GetFullName()); - buffer[1] = new("y", TypeLayout.GetFullName()); - buffer[2] = new("z", TypeLayout.GetFullName()); - buffer[3] = new("w", TypeLayout.GetFullName()); - Register(new(TypeLayout.GetFullName(), (ushort)sizeof(Vector2), buffer.Slice(0, 2)), RuntimeTypeTable.GetHandle()); - Register(new(TypeLayout.GetFullName(), (ushort)sizeof(Vector3), buffer.Slice(0, 3)), RuntimeTypeTable.GetHandle()); - Register(new(TypeLayout.GetFullName(), (ushort)sizeof(Vector4), buffer.Slice(0, 4)), RuntimeTypeTable.GetHandle()); - Register(new(TypeLayout.GetFullName(), (ushort)sizeof(Quaternion), buffer.Slice(0, 4)), RuntimeTypeTable.GetHandle()); - - buffer[0] = new("M11", TypeLayout.GetFullName()); - buffer[1] = new("M12", TypeLayout.GetFullName()); - buffer[2] = new("M21", TypeLayout.GetFullName()); - buffer[3] = new("M22", TypeLayout.GetFullName()); - buffer[4] = new("M31", TypeLayout.GetFullName()); - buffer[5] = new("M32", TypeLayout.GetFullName()); - Register(new(TypeLayout.GetFullName(), (ushort)sizeof(Matrix3x2), buffer.Slice(0, 6)), RuntimeTypeTable.GetHandle()); - - buffer[0] = new("M11", TypeLayout.GetFullName()); - buffer[1] = new("M12", TypeLayout.GetFullName()); - buffer[2] = new("M13", TypeLayout.GetFullName()); - buffer[3] = new("M14", TypeLayout.GetFullName()); - buffer[4] = new("M21", TypeLayout.GetFullName()); - buffer[5] = new("M22", TypeLayout.GetFullName()); - buffer[6] = new("M23", TypeLayout.GetFullName()); - buffer[7] = new("M24", TypeLayout.GetFullName()); - buffer[8] = new("M31", TypeLayout.GetFullName()); - buffer[9] = new("M32", TypeLayout.GetFullName()); - buffer[10] = new("M33", TypeLayout.GetFullName()); - buffer[11] = new("M34", TypeLayout.GetFullName()); - buffer[12] = new("M41", TypeLayout.GetFullName()); - buffer[13] = new("M42", TypeLayout.GetFullName()); - buffer[14] = new("M43", TypeLayout.GetFullName()); - buffer[15] = new("M44", TypeLayout.GetFullName()); - Register(new(TypeLayout.GetFullName(), (ushort)sizeof(Matrix4x4), buffer.Slice(0, 16)), RuntimeTypeTable.GetHandle()); - - buffer[0] = new("_dateData", TypeLayout.GetFullName()); - Register(new(TypeLayout.GetFullName(), (ushort)sizeof(DateTime), buffer.Slice(0, 1)), RuntimeTypeTable.GetHandle()); - } - - /// - /// Loads all s from the bank of type . - /// - public unsafe static void Load() where T : unmanaged, ITypeBank - { - T bank = default; - bank.Load(new(Register)); - } - - /// - /// Registers a type using the information in the given . - /// - public static void Register(Register.Input input) - { - Register(input.type, input.Handle); - } - - /// - /// Manually registers the given . - /// - public static void Register(TypeLayout type, RuntimeTypeHandle handle) - { - ThrowIfAlreadyRegistered(type); - - types.Add(type); - handleToType.Add(handle, type); - typeToHandle.Add(type.Hash, handle); - hashToType.Add(type.Hash, type); - } - - /// - /// Tries to manually register the given . - /// - public static bool TryRegister(TypeLayout type, RuntimeTypeHandle handle) - { - if (types.Contains(type)) - { - return false; - } - - types.Add(type); - handleToType.Add(handle, type); - typeToHandle.Add(type.Hash, handle); - hashToType.Add(type.Hash, type); - return true; - } - - /// - /// Manually registers type without any variables. - /// - public unsafe static void Register() where T : unmanaged - { - //todo: need to add a warning here when trying to register a type bank itself - ushort size = (ushort)sizeof(T); - TypeLayout type = new(TypeLayout.GetFullName(), size); - Register(type, RuntimeTypeTable.GetHandle()); - } - - /// - /// Tries to manually register type without any variables. - /// - public unsafe static bool TryRegister() where T : unmanaged - { - ushort size = (ushort)sizeof(T); - TypeLayout type = new(TypeLayout.GetFullName(), size); - return TryRegister(type, RuntimeTypeTable.GetHandle()); - } - - /// - /// Retrieves the metadata for . - /// - public static TypeLayout Get() where T : unmanaged - { - ThrowIfNotRegistered(); - - return Cache.value; - } - - /// - /// Retrieves the metadata for , or registers - /// it if it's not already registered without any variables. - /// - public static TypeLayout GetOrRegister() where T : unmanaged - { - return LazyCache.value; - } - - /// - /// Retrieves the metadata for the type with the given . - /// - public static TypeLayout Get(long typeHash) - { - ThrowIfNotRegistered(typeHash); - - return hashToType[typeHash]; - } - - /// - /// Tries to get the metadata for the type with the given . - /// - public static bool TryGet(long typeHash, out TypeLayout type) - { - return hashToType.TryGetValue(typeHash, out type); - } - - /// - /// Retrieves the metadata for the of the wanted type. - /// - public static TypeLayout Get(RuntimeTypeHandle handle) - { - ThrowIfNotRegistered(handle); - - return handleToType[handle]; - } - - /// - /// Retrieves the raw handle for the . - /// - public static RuntimeTypeHandle GetRuntimeTypeHandle(long typeHash) - { - ThrowIfNotRegistered(typeHash); - - return typeToHandle[typeHash]; - } - - /// - /// Checks if type is registered. - /// - public static bool IsRegistered() where T : unmanaged - { - return handleToType.ContainsKey(RuntimeTypeTable.GetHandle()); - } - - /// - /// Checks if the given is registered. - /// - public static bool IsRegistered(Type type) - { - return handleToType.ContainsKey(RuntimeTypeTable.GetHandle(type)); - } - - /// - /// Checks if a type with is registered. - /// - public static bool IsRegistered(ReadOnlySpan fullTypeName) - { - long hash = fullTypeName.GetLongHashCode(); - foreach (TypeLayout type in types) - { - if (type.Hash == hash) - { - return true; - } - } - - return false; - } - - /// - /// Checks if a type with is registered. - /// - public static bool IsRegistered(string fullTypeName) - { - long hash = fullTypeName.GetLongHashCode(); - foreach (TypeLayout type in types) - { - if (type.Hash == hash) - { - return true; - } - } - - return false; - } - - [Conditional("DEBUG")] - private static void ThrowIfNotRegistered() where T : unmanaged - { - if (!IsRegistered()) - { - throw new InvalidOperationException($"Type `{typeof(T)}` is not registered"); - } - } - - [Conditional("DEBUG")] - private static void ThrowIfNotRegistered(long hash) - { - if (!hashToType.ContainsKey(hash)) - { - throw new InvalidOperationException($"Type with hash `{hash}` is not registered"); - } - } - - [Conditional("DEBUG")] - private static void ThrowIfNotRegistered(RuntimeTypeHandle handle) - { - if (!handleToType.ContainsKey(handle)) - { - Type? type = Type.GetTypeFromHandle(handle); - if (type is not null) - { - throw new InvalidOperationException($"Type `{type}` is not registered"); - } - else - { - throw new InvalidOperationException($"Type with handle `{handle}` is not registered"); - } - } - } - - [Conditional("DEBUG")] - private static void ThrowIfAlreadyRegistered(TypeLayout type) - { - if (types.Contains(type)) - { - throw new InvalidOperationException($"Type `{type}` is already registered"); - } - } - - private static class Cache where T : unmanaged - { - public static readonly TypeLayout value; - - static Cache() - { - if (!handleToType.TryGetValue(RuntimeTypeTable.GetHandle(), out value)) - { - throw new InvalidOperationException($"Type `{typeof(T)}` is not registered"); - } - } - } - - private unsafe static class LazyCache where T : unmanaged - { - public static readonly TypeLayout value; - - static LazyCache() - { - RuntimeTypeHandle key = RuntimeTypeTable.GetHandle(); - if (!handleToType.TryGetValue(key, out value)) - { - value = new(TypeLayout.GetFullName(), (ushort)sizeof(T)); - Register(value, key); - } - } - } - } -} \ No newline at end of file diff --git a/generator/Constants.cs b/generator/Constants.cs new file mode 100644 index 0000000..f7a23a0 --- /dev/null +++ b/generator/Constants.cs @@ -0,0 +1,10 @@ +namespace Types.Generator +{ + public static class Constants + { + public const string RegistryTypeName = "MetadataRegistry"; + public const string FieldBufferTypeName = "FieldBuffer"; + public const string InterfaceBufferTypeName = "InterfaceTypeBuffer"; + public const string RegisterFunctionTypeName = "RegisterFunction"; + } +} \ No newline at end of file diff --git a/generator/Generators/TypeBankGenerator.cs b/generator/Generators/TypeBankGenerator.cs index 8416eeb..dbe6963 100644 --- a/generator/Generators/TypeBankGenerator.cs +++ b/generator/Generators/TypeBankGenerator.cs @@ -11,6 +11,8 @@ namespace Types.Generator public class TypeBankGenerator : IIncrementalGenerator { public const string TypeNameFormat = "{0}TypeBank"; + public const string FieldBufferVariableName = "fields"; + public const string InterfaceBufferVariableName = "interfaces"; void IIncrementalGenerator.Initialize(IncrementalGeneratorInitializationContext context) { @@ -110,13 +112,48 @@ public static string Generate(IReadOnlyList types, out string typeN source.BeginGroup(); { - source.AppendLine("readonly void ITypeBank.Load(Register register)"); + source.Append("readonly void ITypeBank.Load("); + source.Append(Constants.RegisterFunctionTypeName); + source.Append(" register)"); + source.AppendLine(); + source.BeginGroup(); { - source.AppendLine("Span buffer = stackalloc TypeLayout.Variable[(int)TypeLayout.Capacity];"); + source.Append(Constants.FieldBufferTypeName); + source.Append(' '); + source.Append(FieldBufferVariableName); + source.Append(" = new();"); + source.AppendLine(); + + source.Append(Constants.InterfaceBufferTypeName); + source.Append(' '); + source.Append(InterfaceBufferVariableName); + source.Append(" = new();"); + source.AppendLine(); + + //register all interfaces first + HashSet interfaceTypes = []; + foreach (ITypeSymbol type in types) + { + foreach (INamedTypeSymbol interfaceType in type.AllInterfaces) + { + if (interfaceType.IsGenericType) + { + continue; + } + + interfaceTypes.Add(interfaceType); + } + } + + foreach (ITypeSymbol interfaceType in interfaceTypes) + { + AppendRegisterInterface(source, interfaceType); + } + foreach (ITypeSymbol type in types) { - AppendRegister(source, type); + AppendRegisterType(source, type); } } source.EndGroup(); @@ -131,7 +168,27 @@ public static string Generate(IReadOnlyList types, out string typeN return source.ToString(); } - private static void AppendRegister(SourceBuilder source, ITypeSymbol type) + private static void AppendRegisterInterface(SourceBuilder source, ITypeSymbol interfaceType) + { + string fullName = interfaceType.GetFullTypeName(); + source.Append("if (!"); + source.Append(Constants.RegistryTypeName); + source.Append(".IsInterfaceRegistered<"); + source.Append(fullName); + source.Append(">())"); + source.AppendLine(); + + source.BeginGroup(); + { + source.Append("register.RegisterInterface<"); + source.Append(fullName); + source.Append(">();"); + source.AppendLine(); + } + source.EndGroup(); + } + + private static void AppendRegisterType(SourceBuilder source, ITypeSymbol type) { string fullName = type.GetFullTypeName(); if (fullName.EndsWith("e__FixedBuffer")) @@ -139,32 +196,62 @@ private static void AppendRegister(SourceBuilder source, ITypeSymbol type) return; } - byte count = 0; - HashSet fieldNames = new(); - foreach (IFieldSymbol field in type.GetFields()) + source.Append("if (!"); + source.Append(Constants.RegistryTypeName); + source.Append(".IsTypeRegistered<"); + source.Append(fullName); + source.Append(">())"); + source.AppendLine(); + + source.BeginGroup(); { - if (fieldNames.Add(field.Name)) + byte variableCount = 0; + byte interfaceCount = 0; + HashSet fieldNames = new(); + foreach (IFieldSymbol field in type.GetFields()) { - AppendVariable(source, field, ref count); + if (fieldNames.Add(field.Name)) + { + AppendVariable(source, field, ref variableCount); + } } - } - source.Append("register.Invoke<"); - source.Append(fullName); - source.Append(">("); - if (count > 0) - { - source.Append("buffer.Slice(0, "); - source.Append(count); - source.Append(')'); - } + foreach (INamedTypeSymbol interfaceType in type.AllInterfaces) + { + if (interfaceType.IsGenericType) + { + continue; + } - source.Append(");"); - source.AppendLine(); + AppendInterface(source, interfaceType, ref interfaceCount); + } + + source.Append("register.RegisterType<"); + source.Append(fullName); + source.Append(">("); + if (variableCount > 0 || interfaceCount > 0) + { + source.Append(FieldBufferVariableName); + source.Append(','); + source.Append(' '); + source.Append(variableCount); + source.Append(','); + source.Append(' '); + source.Append(InterfaceBufferVariableName); + source.Append(','); + source.Append(' '); + source.Append(interfaceCount); + } + + source.Append(");"); + source.AppendLine(); + } + source.EndGroup(); static void AppendVariable(SourceBuilder source, IFieldSymbol field, ref byte count) { - source.Append("buffer["); + source.Append(FieldBufferVariableName); + source.Append('['); source.Append(count); source.Append("] = new(\""); source.Append(field.Name); @@ -186,6 +273,18 @@ static void AppendVariable(SourceBuilder source, IFieldSymbol field, ref byte co source.AppendLine(); count++; } + + static void AppendInterface(SourceBuilder source, INamedTypeSymbol interfaceType, ref byte count) + { + source.Append(InterfaceBufferVariableName); + source.Append('['); + source.Append(count); + source.Append("] = new(\""); + source.Append(interfaceType.GetFullTypeName()); + source.Append("\");"); + source.AppendLine(); + count++; + } } } } \ No newline at end of file diff --git a/generator/Generators/TypeRegistryLoaderGenerator.cs b/generator/Generators/TypeRegistryLoaderGenerator.cs index f60e55b..92720fa 100644 --- a/generator/Generators/TypeRegistryLoaderGenerator.cs +++ b/generator/Generators/TypeRegistryLoaderGenerator.cs @@ -70,7 +70,8 @@ private static string Generate(Compilation compilation) if (type.HasInterface("Types.ITypeBank")) { - builder.Append("TypeRegistry.Load<"); + builder.Append(Constants.RegistryTypeName); + builder.Append(".Load<"); builder.Append(type.GetFullTypeName()); builder.Append(">();"); builder.AppendLine(); diff --git a/tests/BankTests.cs b/tests/BankTests.cs index 98e805a..057d051 100644 --- a/tests/BankTests.cs +++ b/tests/BankTests.cs @@ -1,22 +1,34 @@ -using Types.Functions; +using System; +using Types.Functions; namespace Types.Tests { - public unsafe class BankTests : TypeTests + public unsafe class BankTests : Tests { [Test] public void LoadCustomBank() { - Assert.That(TypeRegistry.IsRegistered(), Is.False); - TypeRegistry.Load(); - Assert.That(TypeRegistry.IsRegistered(), Is.True); + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.False); + MetadataRegistry.Load(); + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.True); } +#if DEBUG + [Test] + public void ThrowWhenRegisteringTwice() + { + Assert.That(MetadataRegistry.IsInterfaceRegistered(), Is.False); + MetadataRegistry.RegisterInterface(); + Assert.That(MetadataRegistry.IsInterfaceRegistered(), Is.True); + Assert.Throws(() => MetadataRegistry.RegisterInterface()); + } +#endif + public readonly struct CustomTypeBank : ITypeBank { - void ITypeBank.Load(Register register) + void ITypeBank.Load(RegisterFunction register) { - register.Invoke(); + register.RegisterType(); } } } diff --git a/tests/LayoutTests.cs b/tests/LayoutTests.cs deleted file mode 100644 index 0dc8eea..0000000 --- a/tests/LayoutTests.cs +++ /dev/null @@ -1,115 +0,0 @@ -using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; -using System.Numerics; - -namespace Types.Tests -{ - public unsafe class LayoutTests : TypeTests - { - [Test] - public void VerifyLayoutOfRegisteredTypes() - { - TypeLayout type = TypeRegistry.Get(); - Assert.That(type.SystemType, Is.EqualTo(typeof(Stress))); - Assert.That(type.Name.ToString(), Is.EqualTo("Stress")); - Assert.That(type.size, Is.EqualTo((uint)sizeof(Stress))); - Assert.That(type.variableCount, Is.EqualTo(5)); - Assert.That(type[0].Size, Is.EqualTo(1)); - Assert.That(type[0].Name.ToString(), Is.EqualTo("first")); - Assert.That(type[1].Size, Is.EqualTo(2)); - Assert.That(type[1].Name.ToString(), Is.EqualTo("second")); - Assert.That(type[2].Size, Is.EqualTo(4)); - Assert.That(type[2].Name.ToString(), Is.EqualTo("third")); - Assert.That(type[3].Size, Is.EqualTo(4)); - Assert.That(type[3].Name.ToString(), Is.EqualTo("fourth")); - Assert.That(type[4].Size, Is.EqualTo((uint)sizeof(Cherry))); - Assert.That(type[4].Name.ToString(), Is.EqualTo("cherry")); - } - - [Test] - public void PrimitiveTypesAreAvailable() - { - Assert.That(TypeRegistry.IsRegistered(), Is.True); - Assert.That(TypeRegistry.IsRegistered(), Is.True); - Assert.That(TypeRegistry.IsRegistered(), Is.True); - Assert.That(TypeRegistry.IsRegistered(), Is.True); - Assert.That(TypeRegistry.IsRegistered(), Is.True); - Assert.That(TypeRegistry.IsRegistered(), Is.True); - Assert.That(TypeRegistry.IsRegistered(), Is.True); - Assert.That(TypeRegistry.IsRegistered(), Is.True); - Assert.That(TypeRegistry.IsRegistered(), Is.True); - Assert.That(TypeRegistry.IsRegistered(), Is.True); - Assert.That(TypeRegistry.IsRegistered(), Is.True); - Assert.That(TypeRegistry.IsRegistered(), Is.True); - - Assert.That(TypeRegistry.IsRegistered(typeof(bool).FullName ?? typeof(bool).Name), Is.True); - Assert.That(TypeRegistry.IsRegistered(typeof(byte).FullName ?? typeof(byte).Name), Is.True); - Assert.That(TypeRegistry.IsRegistered(typeof(sbyte).FullName ?? typeof(sbyte).Name), Is.True); - Assert.That(TypeRegistry.IsRegistered(typeof(short).FullName ?? typeof(short).Name), Is.True); - - Assert.That(TypeRegistry.Get().size, Is.EqualTo((uint)sizeof(Vector3))); - Assert.That(TypeRegistry.Get().variableCount, Is.EqualTo(3)); - } - - [Test] - public void CheckLayouts() - { - Assert.That(TypeRegistry.IsRegistered(), Is.True); - Assert.That(TypeRegistry.IsRegistered(), Is.True); - TypeLayout boolean = TypeRegistry.Get(); - TypeLayout byteType = TypeRegistry.Get(); - Assert.That(boolean.size, Is.EqualTo(1)); - Assert.That(byteType.size, Is.EqualTo(1)); - Assert.That(boolean.GetHashCode(), Is.EqualTo(TypeRegistry.Get().GetHashCode())); - Assert.That(byteType.GetHashCode(), Is.EqualTo(TypeRegistry.Get().GetHashCode())); - } - - [Test] - public void CheckIfLayoutIs() - { - TypeLayout layout = TypeRegistry.Get(); - - Assert.That(layout.Is(), Is.True); - Assert.That(layout.Is(), Is.False); - } - - [Test] - public void CheckNamesOfTypes() - { - Assert.That(TypeRegistry.Get().Name.ToString(), Is.EqualTo("Boolean")); - Assert.That(TypeRegistry.Get().FullName.ToString(), Is.EqualTo("System.Boolean")); - Assert.That(TypeRegistry.Get().Name.ToString(), Is.EqualTo("Cherry")); - Assert.That(TypeRegistry.Get().FullName.ToString(), Is.EqualTo("Types.Tests.Cherry")); - } - - [Test] - public void GetFullNameOfType() - { - string c = TypeLayout.GetFullName(); - Assert.That(c.ToString(), Is.EqualTo("System.Boolean")); - - string a = TypeLayout.GetFullName>(); - Assert.That(a.ToString(), Is.EqualTo("Types.Tests.Dictionary")); - - string b = TypeLayout.GetFullName>>(); - Assert.That(b.ToString(), Is.EqualTo("Types.Tests.Dictionary>")); - } - - [Test] - public void CreateObjectFromTypeLayout() - { - TypeLayout layout = TypeRegistry.Get(); - object instance = layout.CreateInstance(); - Assert.That(instance, Is.InstanceOf()); - Assert.That((Stress)instance, Is.EqualTo(default(Stress))); - } - - [Test] - public void GetOrRegister() - { - Assert.That(TypeRegistry.IsRegistered(), Is.False); - TypeLayout type = TypeRegistry.GetOrRegister(); - Assert.That(TypeRegistry.IsRegistered(), Is.True); - Assert.That(type.Is(), Is.True); - } - } -} \ No newline at end of file diff --git a/tests/RuntimeTypeHandleTests.cs b/tests/RuntimeTypeHandleTests.cs index b480341..a337dfe 100644 --- a/tests/RuntimeTypeHandleTests.cs +++ b/tests/RuntimeTypeHandleTests.cs @@ -8,7 +8,7 @@ public class RuntimeTypeHandleTests public void CastTypeAddressBackToHandle() { nint address = RuntimeTypeTable.GetAddress(); - Type? type = Type.GetTypeFromHandle(RuntimeTypeTable.GetHandle(address)); + System.Type? type = System.Type.GetTypeFromHandle(RuntimeTypeTable.GetHandle(address)); Assert.That(type, Is.EqualTo(typeof(string))); } } diff --git a/tests/Tests.cs b/tests/Tests.cs new file mode 100644 index 0000000..3b88988 --- /dev/null +++ b/tests/Tests.cs @@ -0,0 +1,32 @@ +using System; + +namespace Types.Tests +{ + public abstract class Tests + { + static Tests() + { + MetadataRegistry.Load(); + } + + [SetUp] + protected virtual void SetUp() + { + } + + [TearDown] + protected virtual void TearDown() + { + } + + protected static bool IsRunningRemotely() + { + if (Environment.GetEnvironmentVariable("GITHUB_ACTIONS") is not null) + { + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/tests/TypeTests.cs b/tests/TypeTests.cs index cb0e462..b2b48a7 100644 --- a/tests/TypeTests.cs +++ b/tests/TypeTests.cs @@ -1,32 +1,144 @@ using System; +using System.Collections.Generic; +using System.Numerics; namespace Types.Tests { - public abstract class TypeTests + public unsafe class TypeTests : Tests { - static TypeTests() + [Test] + public void VerifyLayoutOfRegisteredTypes() { - TypeRegistry.Load(); + Type type = MetadataRegistry.GetType(); + Assert.That(type.SystemType, Is.EqualTo(typeof(Stress))); + Assert.That(type.Name.ToString(), Is.EqualTo("Stress")); + Assert.That(type.size, Is.EqualTo((uint)sizeof(Stress))); + + ReadOnlySpan fields = type.Fields; + Assert.That(fields.Length, Is.EqualTo(5)); + Assert.That(fields[0].Size, Is.EqualTo(1)); + Assert.That(fields[0].Name.ToString(), Is.EqualTo("first")); + Assert.That(fields[1].Size, Is.EqualTo(2)); + Assert.That(fields[1].Name.ToString(), Is.EqualTo("second")); + Assert.That(fields[2].Size, Is.EqualTo(4)); + Assert.That(fields[2].Name.ToString(), Is.EqualTo("third")); + Assert.That(fields[3].Size, Is.EqualTo(4)); + Assert.That(fields[3].Name.ToString(), Is.EqualTo("fourth")); + Assert.That(fields[4].Size, Is.EqualTo((uint)sizeof(Cherry))); + Assert.That(fields[4].Name.ToString(), Is.EqualTo("cherry")); + } + + [Test] + public void PrimitiveTypesAreAvailable() + { + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.True); + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.True); + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.True); + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.True); + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.True); + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.True); + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.True); + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.True); + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.True); + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.True); + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.True); + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.True); + + Assert.That(MetadataRegistry.IsRegistered(typeof(bool).FullName ?? typeof(bool).Name), Is.True); + Assert.That(MetadataRegistry.IsRegistered(typeof(byte).FullName ?? typeof(byte).Name), Is.True); + Assert.That(MetadataRegistry.IsRegistered(typeof(sbyte).FullName ?? typeof(sbyte).Name), Is.True); + Assert.That(MetadataRegistry.IsRegistered(typeof(short).FullName ?? typeof(short).Name), Is.True); + + Assert.That(MetadataRegistry.GetType().size, Is.EqualTo((uint)sizeof(Vector3))); + Assert.That(MetadataRegistry.GetType().Fields.Length, Is.EqualTo(3)); + } + + [Test] + public void CheckLayouts() + { + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.True); + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.True); + Type boolean = MetadataRegistry.GetType(); + Type byteType = MetadataRegistry.GetType(); + Assert.That(boolean.size, Is.EqualTo(1)); + Assert.That(byteType.size, Is.EqualTo(1)); + Assert.That(boolean.GetHashCode(), Is.EqualTo(MetadataRegistry.GetType().GetHashCode())); + Assert.That(byteType.GetHashCode(), Is.EqualTo(MetadataRegistry.GetType().GetHashCode())); + } + + [Test] + public void CheckIfLayoutIs() + { + Type layout = MetadataRegistry.GetType(); + + Assert.That(layout.Is(), Is.True); + Assert.That(layout.Is(), Is.False); + } + + [Test] + public void CheckNamesOfTypes() + { + Assert.That(MetadataRegistry.GetType().Name.ToString(), Is.EqualTo("Boolean")); + Assert.That(MetadataRegistry.GetType().FullName.ToString(), Is.EqualTo("System.Boolean")); + Assert.That(MetadataRegistry.GetType().Name.ToString(), Is.EqualTo("Cherry")); + Assert.That(MetadataRegistry.GetType().FullName.ToString(), Is.EqualTo("Types.Tests.Cherry")); + } + + [Test] + public void GetFullNameOfType() + { + string c = MetadataRegistry.GetFullName(); + Assert.That(c.ToString(), Is.EqualTo("System.Boolean")); + + string a = MetadataRegistry.GetFullName>(); + Assert.That(a.ToString(), Is.EqualTo("Types.Tests.Dictionary")); + + string b = MetadataRegistry.GetFullName>>(); + Assert.That(b.ToString(), Is.EqualTo("Types.Tests.Dictionary>")); + } + + [Test] + public void CreateObjectFromTypeLayout() + { + Type layout = MetadataRegistry.GetType(); + object instance = layout.CreateInstance(); + Assert.That(instance, Is.InstanceOf()); + Assert.That((Stress)instance, Is.EqualTo(default(Stress))); } - [SetUp] - protected virtual void SetUp() + [Test] + public void GetOrRegister() { + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.False); + Type type = MetadataRegistry.GetOrRegisterType(); + Assert.That(MetadataRegistry.IsTypeRegistered(), Is.True); + Assert.That(type.Is(), Is.True); } - [TearDown] - protected virtual void TearDown() + [Test] + public void GetImplementedInterfaces() { + Type type = MetadataRegistry.GetType(); + ReadOnlySpan interfaces = type.Interfaces; + Assert.That(interfaces.Length, Is.EqualTo(1)); + Assert.That(interfaces[0].Name.ToString(), Is.EqualTo("IDisposable")); + Assert.That(type.Implements(), Is.True); + Assert.That(type.Implements(), Is.False); + Assert.That(interfaces[0].Is(), Is.True); + Assert.That(interfaces[0].Is(), Is.False); } - protected static bool IsRunningRemotely() + [Test] + public void IterateThroughAllDisposableTypes() { - if (Environment.GetEnvironmentVariable("GITHUB_ACTIONS") is not null) + List types = new(); + foreach (Type type in Type.GetAllThatImplement()) { - return true; + types.Add(type); } - return false; + Assert.That(types.Count, Is.EqualTo(1)); + Assert.That(types[0].Is(), Is.True); } } } \ No newline at end of file diff --git a/tests/Types/Stress.cs b/tests/Types/Stress.cs index 6a3f84d..cca4dd2 100644 --- a/tests/Types/Stress.cs +++ b/tests/Types/Stress.cs @@ -1,6 +1,8 @@ -namespace Types.Tests +using System; + +namespace Types.Tests { - public readonly struct Stress + public readonly struct Stress : IDisposable { public readonly byte first; public readonly ushort second; @@ -12,5 +14,9 @@ public readonly override string ToString() { return $"Stress: {first}, {second}, {third}, {fourth}, {cherry}"; } + + readonly void IDisposable.Dispose() + { + } } } \ No newline at end of file