diff --git a/core/Address.cs b/core/Address.cs index eed8da7..5cfd3c0 100644 --- a/core/Address.cs +++ b/core/Address.cs @@ -6,7 +6,9 @@ namespace Data { public struct Address : IEquatable
{ - public FixedString value; + private FixedString value; + + public readonly byte Length => value.Length; public Address(FixedString value) { @@ -126,6 +128,36 @@ public readonly bool EndsWith(USpan other) return true; } + public readonly uint IndexOf(char character) + { + return value.IndexOf(character); + } + + public readonly uint LastIndexOf(char character) + { + return value.LastIndexOf(character); + } + + public readonly bool TryIndexOf(char character, out uint index) + { + return value.TryIndexOf(character, out index); + } + + public readonly bool TryLastIndexOf(char character, out uint index) + { + return value.TryLastIndexOf(character, out index); + } + + public readonly Address Slice(uint start, uint length) + { + return new(value.Slice(start, length)); + } + + public readonly Address Slice(uint start) + { + return new(value.Slice(start)); + } + public readonly bool Matches(string other) { USpan buffer = stackalloc char[other.Length]; @@ -188,9 +220,9 @@ public static implicit operator Address(string value) return new(value); } - public static Address Get() where T : unmanaged, IDataReference + public static implicit operator FixedString(Address address) { - return default(T).Value; + return address.value; } } } \ No newline at end of file diff --git a/core/Components/IsData.cs b/core/Components/IsData.cs deleted file mode 100644 index 4e16a53..0000000 --- a/core/Components/IsData.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Worlds; - -namespace Data.Components -{ - [Component] - public struct IsData - { - public uint version; - - public IsData(uint version) - { - this.version = version; - } - } -} \ No newline at end of file diff --git a/core/Components/IsDataRequest.cs b/core/Components/IsDataRequest.cs index 6368870..767e163 100644 --- a/core/Components/IsDataRequest.cs +++ b/core/Components/IsDataRequest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Unmanaged; using Worlds; @@ -7,31 +8,41 @@ namespace Data.Components [Component] public struct IsDataRequest { - public Address address; - public uint version; + public readonly Address address; + public RequestStatus status; + public TimeSpan timeout; - public IsDataRequest(USpan address) + public IsDataRequest(USpan address, RequestStatus status, TimeSpan timeout) { - version = default; this.address = new(address); + this.status = status; + this.timeout = timeout; } - public IsDataRequest(Address address) + public IsDataRequest(Address address, RequestStatus status, TimeSpan timeout) { - version = default; this.address = address; + this.status = status; + this.timeout = timeout; } - public IsDataRequest(string address) + public IsDataRequest(string address, RequestStatus status, TimeSpan timeout) { - version = default; this.address = new(address); + this.status = status; + this.timeout = timeout; } - public IsDataRequest(IEnumerable address) + public IsDataRequest(IEnumerable address, RequestStatus status, TimeSpan timeout) { - version = default; this.address = new(address); + this.status = status; + this.timeout = timeout; + } + + public readonly IsDataRequest BecomeLoaded() + { + return new(address, RequestStatus.Loaded, timeout); } } } \ No newline at end of file diff --git a/core/Components/Name.cs b/core/Components/Name.cs deleted file mode 100644 index 15c6a7e..0000000 --- a/core/Components/Name.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Unmanaged; -using Worlds; - -namespace Data.Components -{ - [Component] - public struct Name - { - public FixedString value; - - public Name(FixedString value) - { - this.value = value; - } - - public Name(USpan value) - { - this.value = new FixedString(value); - } - } -} \ No newline at end of file diff --git a/core/DataRequest.cs b/core/DataRequest.cs index ebb5a33..047477b 100644 --- a/core/DataRequest.cs +++ b/core/DataRequest.cs @@ -2,8 +2,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Threading.Tasks; -using System.Threading; using Unmanaged; using Worlds; @@ -12,93 +10,49 @@ namespace Data /// /// An entity that will contain data loaded from its address. /// - public readonly struct DataRequest : IEntity + public readonly partial struct DataRequest : IDataRequest { - private readonly Entity entity; + public readonly Address Address => GetComponent().address; + public readonly bool IsLoaded => GetComponent().status == RequestStatus.Loaded; - /// - /// Address that this data request is looking for. - /// - public readonly Address Address + public readonly USpan Bytes { get { - ref IsDataRequest component = ref entity.GetComponent(); - return component.address; - } - } - - public readonly USpan Data - { - get - { - ThrowIfDataNotAvailable(); - return entity.GetArray().As(); - } - } + ThrowIfNotLoaded(); - /// - /// Checks if the data has been loaded. - /// - public readonly bool IsLoaded - { - get - { - if (entity.TryGetComponent(out IsData data)) - { - return data.version == entity.GetComponent().version; - } - else - { - return false; - } + return GetArray().As(); } } - readonly uint IEntity.Value => entity.value; - readonly World IEntity.World => entity.world; - readonly void IEntity.Describe(ref Archetype archetype) { archetype.AddComponentType(); + archetype.AddArrayType(); } -#if NET - [Obsolete("Default constructor not supported", true)] - public DataRequest() - { - throw new NotSupportedException(); - } -#endif - - public DataRequest(World world, uint existingEntity) - { - this.entity = new(world, existingEntity); - } - - public DataRequest(World world, USpan address) - { - entity = new Entity(world, new IsDataRequest(address)); - } - - public DataRequest(World world, Address address) + public DataRequest(World world, USpan address, TimeSpan timeout = default) { - entity = new Entity(world, new IsDataRequest(address)); + this.world = world; + value = world.CreateEntity(new IsDataRequest(address, RequestStatus.Submitted, timeout)); } - public DataRequest(World world, string address) + public DataRequest(World world, Address address, TimeSpan timeout = default) { - entity = new Entity(world, new IsDataRequest(address)); + this.world = world; + value = world.CreateEntity(new IsDataRequest(address, RequestStatus.Submitted, timeout)); } - public DataRequest(World world, IEnumerable address) + public DataRequest(World world, string address, TimeSpan timeout = default) { - entity = new Entity(world, new IsDataRequest(address)); + this.world = world; + value = world.CreateEntity(new IsDataRequest(address, RequestStatus.Submitted, timeout)); } - public readonly void Dispose() + public DataRequest(World world, IEnumerable address, TimeSpan timeout = default) { - entity.Dispose(); + this.world = world; + value = world.CreateEntity(new IsDataRequest(address, RequestStatus.Submitted, timeout)); } public readonly override string ToString() @@ -110,7 +64,7 @@ public readonly bool TryGetData(out USpan data) { if (IsLoaded) { - data = entity.GetArray().As(); + data = Bytes; return true; } else @@ -120,46 +74,20 @@ public readonly bool TryGetData(out USpan data) } } - public readonly void SetAddress(Address address) - { - ref IsDataRequest component = ref entity.GetComponent(); - component.address = address; - component.version++; - } - - /// - /// Reads the data as a UTF8 string. - /// - /// Amount of char values copied. - public readonly uint CopyDataAsUTF8To(USpan buffer) + public readonly BinaryReader CreateBinaryReader() { - ThrowIfDataNotAvailable(); + ThrowIfNotLoaded(); - using BinaryReader reader = new(Data); - return reader.ReadUTF8Span(buffer); - } - - public async Task UntilLoaded(Update action, CancellationToken cancellation = default) - { - World world = entity.GetWorld(); - while (!IsLoaded) - { - await action(world, cancellation); - } + return new(Bytes); } [Conditional("DEBUG")] - public void ThrowIfDataNotAvailable() + private readonly void ThrowIfNotLoaded() { if (!IsLoaded) { - throw new InvalidOperationException($"Data not yet available on `{entity.GetEntityValue()}`"); + throw new InvalidOperationException($"Data entity `{value}` at address `{Address}` has not been loaded"); } } - - public static implicit operator Entity(DataRequest request) - { - return request.entity; - } } -} +} \ No newline at end of file diff --git a/core/DataSource.cs b/core/DataSource.cs index c0435e2..ddf272a 100644 --- a/core/DataSource.cs +++ b/core/DataSource.cs @@ -1,5 +1,4 @@ using Data.Components; -using System; using Unmanaged; using Worlds; @@ -9,38 +8,25 @@ namespace Data /// Represents a span of that can be found with /// a . /// - public readonly struct DataSource : IDataSource + public readonly partial struct DataSource : IDataSource { - private readonly Entity entity; - - public readonly USpan Bytes => entity.GetArray().As(); - public readonly Address Address => entity.GetComponent().address; - - readonly uint IEntity.Value => entity.value; - readonly World IEntity.World => entity.world; + public readonly Address Address => GetComponent().address; + public readonly USpan Bytes => GetArray().As(); readonly void IEntity.Describe(ref Archetype archetype) { archetype.AddComponentType(); - archetype.AddArrayElementType(); + archetype.AddArrayType(); } -#if NET - [Obsolete("Default constructor not supported.", true)] - public DataSource() - { - throw new NotSupportedException(); - } -#endif - /// /// Creates an empty data source. /// public DataSource(World world, Address address) { - entity = new(world); - entity.AddComponent(new IsDataSource(address)); - entity.CreateArray(); + this.world = world; + value = world.CreateEntity(new IsDataSource(address)); + world.CreateArray(value); } /// @@ -48,9 +34,9 @@ public DataSource(World world, Address address) /// public DataSource(World world, Address address, USpan bytes) { - entity = new(world); - entity.AddComponent(new IsDataSource(address)); - entity.CreateArray(bytes.As()); + this.world = world; + value = world.CreateEntity(new IsDataSource(address)); + world.CreateArray(value, bytes.As()); } /// @@ -58,10 +44,10 @@ public DataSource(World world, Address address, USpan bytes) /// public DataSource(World world, Address address, USpan text) { - entity = new(world); - entity.AddComponent(new IsDataSource(address)); - entity.CreateArray(); - Write(text); + this.world = world; + value = world.CreateEntity(new IsDataSource(address)); + world.CreateArray(value); + WriteUTF8(text); } /// @@ -69,15 +55,10 @@ public DataSource(World world, Address address, USpan text) /// public DataSource(World world, Address address, string text) { - entity = new(world); - entity.AddComponent(new IsDataSource(address)); - entity.CreateArray(); - Write(text); - } - - public readonly void Dispose() - { - entity.Dispose(); + this.world = world; + value = world.CreateEntity(new IsDataSource(address)); + world.CreateArray(value); + WriteUTF8(text); } public readonly override string ToString() @@ -89,43 +70,37 @@ public readonly override string ToString() public readonly uint ToString(USpan buffer) { - Address name = Address; - return name.ToString(buffer); - } - - public readonly void Clear() - { - entity.ResizeArray(0); + return Address.ToString(buffer); } /// /// Appends the given text as UTF8 formatted bytes. /// - public readonly void Write(USpan text) + public readonly void WriteUTF8(USpan text) { using BinaryWriter writer = new(4); - writer.WriteUTF8Text(text); - Write(writer.GetBytes()); + writer.WriteUTF8(text); + Write(writer.AsSpan()); } /// /// Appends the given text as UTF8 formatted bytes. /// - public readonly void Write(FixedString text) + public readonly void WriteUTF8(FixedString text) { using BinaryWriter writer = new(4); - writer.WriteUTF8Text(text); - Write(writer.GetBytes()); + writer.WriteUTF8(text); + Write(writer.AsSpan()); } /// /// Appends the given text as UTF8 formatted bytes. /// - public readonly void Write(string text) + public readonly void WriteUTF8(string text) { using BinaryWriter writer = new(4); - writer.WriteUTF8Text(text); - Write(writer.GetBytes()); + writer.WriteUTF8(text); + Write(writer.AsSpan()); } /// @@ -133,9 +108,14 @@ public readonly void Write(string text) /// public readonly void Write(USpan bytes) { - USpan array = entity.GetArray(); - array = entity.ResizeArray(bytes.Length + array.Length); + uint length = GetArrayLength(); + USpan array = ResizeArray(bytes.Length + length); bytes.CopyTo(array.As()); } + + public readonly BinaryReader CreateBinaryReader() + { + return new(Bytes); + } } -} +} \ No newline at end of file diff --git a/core/EmbeddedAddress.cs b/core/EmbeddedAddress.cs deleted file mode 100644 index 9aa82fd..0000000 --- a/core/EmbeddedAddress.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace Data -{ - public readonly struct EmbeddedAddress - { - private static readonly List all = new(); - private static readonly HashSet
addresses = new(); - - public static IReadOnlyList All => all; - - public readonly Assembly assembly; - public readonly Address address; - - public EmbeddedAddress(Assembly assembly, Address address) - { - this.assembly = assembly; - this.address = address; - } - - public static void Register() where T : unmanaged - { - T resources = new(); - Assembly assembly = typeof(T).Assembly; - if (resources is IEmbeddedResources embeddedResources) - { - foreach (Address address in embeddedResources.Addresses) - { - Register(assembly, address); - } - } - else if (resources is IDataReference dataReference) - { - Register(assembly, dataReference.Value); - } - else - { - throw new NotImplementedException($"Handling of `{typeof(T).Name}` as an embedded resource is not implemented"); - } - } - - public static void Register(Assembly assembly, string address) - { - Register(assembly, new Address(address)); - } - - public static void Register(Assembly assembly, Address address) - { - if (addresses.Add(address)) - { - all.Add(new EmbeddedAddress(assembly, address)); - } - } - } -} diff --git a/core/EmbeddedResource.cs b/core/EmbeddedResource.cs new file mode 100644 index 0000000..d575deb --- /dev/null +++ b/core/EmbeddedResource.cs @@ -0,0 +1,35 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using Unmanaged; + +namespace Data +{ + public readonly struct EmbeddedResource + { + public readonly Address address; + + private readonly GCHandle assembly; + + public readonly Assembly Assembly => (Assembly)(assembly.Target ?? throw new System.InvalidOperationException("Assembly has been garbage collected, and is no longer available")); + + public EmbeddedResource(Assembly assembly, Address address) + { + this.assembly = GCHandle.Alloc(assembly, GCHandleType.Weak); + this.address = address; + } + + /// + /// Creates a new binary reader with the contents of this embedded resource. + /// + public readonly BinaryReader CreateBinaryReader() + { + Assembly assembly = Assembly; + string[] names = assembly.GetManifestResourceNames(); + string resourcePath = $"{assembly.GetName().Name}.{address.ToString().Replace('/', '.')}"; + System.IO.Stream stream = assembly.GetManifestResourceStream(resourcePath) ?? throw new Exception($"Embedded resource at `{resourcePath}` could not be found"); + stream.Position = 0; + return new(stream); + } + } +} \ No newline at end of file diff --git a/core/EmbeddedResourceRegistry.cs b/core/EmbeddedResourceRegistry.cs new file mode 100644 index 0000000..f6590ff --- /dev/null +++ b/core/EmbeddedResourceRegistry.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using System.Reflection; + +namespace Data +{ + /// + /// Registry containing all known embedded resources. + /// + public static class EmbeddedResourceRegistry + { + private static readonly List all = new(); + private static readonly List
addresses = new(); + + public static IReadOnlyList All => all; + + public static void Load() where T : unmanaged, IEmbeddedResourceBank + { + T template = default; + Assembly assembly = typeof(T).Assembly; + template.Load(new(Register)); + + void Register(Address address) + { + EmbeddedResourceRegistry.Register(assembly, address); + } + } + + public static void Register(Assembly assembly, Address address) + { + all.Add(new EmbeddedResource(assembly, address)); + addresses.Add(address); + } + + private static bool TryGetMatch(Address address, out int index) + { + for (int i = 0; i < addresses.Count; i++) + { + if (addresses[i].Matches(address)) + { + index = i; + return true; + } + } + + index = -1; + return false; + } + + public static bool Contains(Address address) + { + return TryGetMatch(address, out _); + } + + public static bool TryGet(Address address, out EmbeddedResource resource) + { + if (TryGetMatch(address, out int index)) + { + resource = all[index]; + return true; + } + + resource = default; + return false; + } + + public static EmbeddedResource Get(Address address) + { + TryGetMatch(address, out int index); + return all[index]; + } + + public static Address Get() where T : unmanaged, IEmbeddedResource + { + T template = default; + Address address = template.Address; + if (!addresses.Contains(address)) + { + Register(typeof(T).Assembly, address); + } + + return address; + } + } +} \ No newline at end of file diff --git a/core/Extensions/NameExtensions.cs b/core/Extensions/NameExtensions.cs deleted file mode 100644 index 7a47042..0000000 --- a/core/Extensions/NameExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Data.Components; -using Unmanaged; -using Worlds; - -namespace Data -{ - public static class NameExtensions - { - public static ref FixedString GetName(this T entity) where T : unmanaged, IEntity - { - return ref entity.AsEntity().GetComponent().value; - } - } -} diff --git a/core/Functions/Register.cs b/core/Functions/Register.cs new file mode 100644 index 0000000..91fe68c --- /dev/null +++ b/core/Functions/Register.cs @@ -0,0 +1,25 @@ +using System; + +namespace Data.Functions +{ + public readonly struct Register + { + private readonly Action
function; + + public Register(Action
function) + { + this.function = function; + } + + public readonly void Invoke(Address address) + { + function(address); + } + + public readonly void Invoke() where T : unmanaged, IEmbeddedResource + { + T template = default; + function(template.Address); + } + } +} \ No newline at end of file diff --git a/core/Message/HandleDataRequest.cs b/core/Message/HandleDataRequest.cs new file mode 100644 index 0000000..bfa37bd --- /dev/null +++ b/core/Message/HandleDataRequest.cs @@ -0,0 +1,20 @@ +using Data.Components; +using Unmanaged; +using Worlds; + +namespace Data.Messages +{ + public struct HandleDataRequest + { + public readonly Entity entity; + public IsDataRequest request; + + public readonly USpan Bytes => entity.GetArray().As(); + + public HandleDataRequest(Entity entity, IsDataRequest request) + { + this.entity = entity; + this.request = request; + } + } +} \ No newline at end of file diff --git a/core/RequestStatus.cs b/core/RequestStatus.cs new file mode 100644 index 0000000..45e67bc --- /dev/null +++ b/core/RequestStatus.cs @@ -0,0 +1,11 @@ +namespace Data +{ + public enum RequestStatus : byte + { + Unknown = 0, + Submitted = 1, + Loading = 2, + Loaded = 3, + NotFound = 4 + } +} \ No newline at end of file diff --git a/core/RequestedDataNotFoundException.cs b/core/RequestedDataNotFoundException.cs deleted file mode 100644 index 607d17c..0000000 --- a/core/RequestedDataNotFoundException.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Data -{ - /// - /// Thrown when requested data could not be found. - /// - public class RequestedDataNotFoundException : Exception - { - public RequestedDataNotFoundException(string message) : base(message) - { - } - } -} diff --git a/core/Types/IDataReference.cs b/core/Types/IDataReference.cs deleted file mode 100644 index 4703499..0000000 --- a/core/Types/IDataReference.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Data -{ - public interface IDataReference - { - Address Value { get; } - } -} diff --git a/core/Types/IDataRequest.cs b/core/Types/IDataRequest.cs new file mode 100644 index 0000000..d5fd7de --- /dev/null +++ b/core/Types/IDataRequest.cs @@ -0,0 +1,8 @@ +using Worlds; + +namespace Data +{ + public interface IDataRequest : IEntity + { + } +} \ No newline at end of file diff --git a/core/Types/IDataSource.cs b/core/Types/IDataSource.cs index 74f32aa..f5091e6 100644 --- a/core/Types/IDataSource.cs +++ b/core/Types/IDataSource.cs @@ -5,4 +5,4 @@ namespace Data public interface IDataSource : IEntity { } -} +} \ No newline at end of file diff --git a/core/Types/IEmbeddedResource.cs b/core/Types/IEmbeddedResource.cs new file mode 100644 index 0000000..368e60b --- /dev/null +++ b/core/Types/IEmbeddedResource.cs @@ -0,0 +1,7 @@ +namespace Data +{ + public interface IEmbeddedResource + { + Address Address { get; } + } +} \ No newline at end of file diff --git a/core/Types/IEmbeddedResourceBank.cs b/core/Types/IEmbeddedResourceBank.cs new file mode 100644 index 0000000..635fa3f --- /dev/null +++ b/core/Types/IEmbeddedResourceBank.cs @@ -0,0 +1,9 @@ +using Data.Functions; + +namespace Data +{ + public interface IEmbeddedResourceBank + { + void Load(Register register); + } +} diff --git a/core/Types/IEmbeddedResources.cs b/core/Types/IEmbeddedResources.cs deleted file mode 100644 index e1e9d7e..0000000 --- a/core/Types/IEmbeddedResources.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Data -{ - public interface IEmbeddedResources - { - IEnumerable
Addresses { get; } - } -} diff --git a/generator/Data.Generator.csproj b/generator/Data.Generator.csproj index 9b48518..d01c699 100644 --- a/generator/Data.Generator.csproj +++ b/generator/Data.Generator.csproj @@ -22,4 +22,8 @@ + + + + \ No newline at end of file diff --git a/generator/EmbeddedAddressTableGenerator.cs b/generator/EmbeddedAddressTableGenerator.cs deleted file mode 100644 index 78aa1d8..0000000 --- a/generator/EmbeddedAddressTableGenerator.cs +++ /dev/null @@ -1,97 +0,0 @@ -using Microsoft.CodeAnalysis; -using System.Collections.Generic; -using System.Collections.Immutable; - -namespace Data.Generator -{ - [Generator(LanguageNames.CSharp)] - public class EmbeddedAddressTableGenerator : IIncrementalGenerator - { - private static readonly SourceBuilder source = new(); - private static readonly SourceBuilder debug = new(); - private const string TypeName = "EmbeddedAddressTable"; - private const string Namespace = "Data"; - - void IIncrementalGenerator.Initialize(IncrementalGeneratorInitializationContext context) - { - context.RegisterSourceOutput(context.CompilationProvider, Generate); - } - - private static void Generate(SourceProductionContext context, Compilation compilation) - { - context.AddSource($"{TypeName}.generated.cs", Generate(compilation)); - } - - public static string Generate(Compilation compilation) - { - source.Clear(); - source.AppendLine($"namespace {Namespace}"); - source.BeginGroup(); - { - source.AppendLine($"internal static partial class {TypeName}"); - source.BeginGroup(); - { - source.AppendLine($"public static void RegisterAll()"); - source.BeginGroup(); - { - foreach (MetadataReference assemblyReference in compilation.References) - { - if (compilation.GetAssemblyOrModuleSymbol(assemblyReference) is IAssemblySymbol assemblySymbol) - { - Stack stack = new(); - stack.Push(assemblySymbol.GlobalNamespace); - while (stack.Count > 0) - { - ISymbol current = stack.Pop(); - if (current is INamespaceSymbol namespaceSymbol) - { - foreach (ISymbol member in namespaceSymbol.GetNamespaceMembers()) - { - stack.Push(member); - } - - foreach (ISymbol member in namespaceSymbol.GetTypeMembers()) - { - stack.Push(member); - } - } - else if (current is ITypeSymbol typeSymbol) - { - debug.AppendLine($"Type: {typeSymbol.ToDisplayString()}"); - ImmutableArray interfaces = typeSymbol.AllInterfaces; - foreach (INamedTypeSymbol interfaceSymbol in interfaces) - { - if (interfaceSymbol.ToDisplayString() == "Data.IEmbeddedResources") - { - AppendRegistration(typeSymbol); - } - } - - foreach (ISymbol member in typeSymbol.GetMembers()) - { - stack.Push(member); - } - } - } - } - } - } - source.EndGroup(); - } - source.EndGroup(); - } - source.EndGroup(); - return source.ToString(); - } - - private static void AppendRegistration(ITypeSymbol type) - { - source.AppendLine($"EmbeddedAddress.Register<{GetFullTypeName(type)}>();"); - } - - private static string GetFullTypeName(ITypeSymbol type) - { - return type.ToDisplayString(); - } - } -} \ No newline at end of file diff --git a/generator/Generators/EmbeddedResourceBankGenerator.cs b/generator/Generators/EmbeddedResourceBankGenerator.cs new file mode 100644 index 0000000..338eb5c --- /dev/null +++ b/generator/Generators/EmbeddedResourceBankGenerator.cs @@ -0,0 +1,146 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using Types; + +namespace Data.Generator +{ + [Generator(LanguageNames.CSharp)] + public class EmbeddedResourceBankGenerator : IIncrementalGenerator + { + public const string TypeNameFormat = "{0}DataBank"; + + void IIncrementalGenerator.Initialize(IncrementalGeneratorInitializationContext context) + { + IncrementalValuesProvider types = context.SyntaxProvider.CreateSyntaxProvider(Predicate, Transform); + context.RegisterSourceOutput(types.Collect(), Generate); + } + + private void Generate(SourceProductionContext context, ImmutableArray typesArray) + { + List types = new(); + foreach (ITypeSymbol? type in typesArray) + { + if (type is not null) + { + types.Add(type); + } + } + + if (types.Count > 0) + { + string source = Generate(types, out string typeName); + context.AddSource($"{typeName}.generated.cs", source); + } + } + + private static bool Predicate(SyntaxNode node, CancellationToken token) + { + return node.IsKind(SyntaxKind.StructDeclaration); + } + + private static ITypeSymbol? Transform(GeneratorSyntaxContext context, CancellationToken token) + { + StructDeclarationSyntax node = (StructDeclarationSyntax)context.Node; + SemanticModel semanticModel = context.SemanticModel; + ITypeSymbol? type = semanticModel.GetDeclaredSymbol(node); + if (type is null) + { + return null; + } + + if (type is INamedTypeSymbol namedType) + { + if (namedType.IsGenericType) + { + return null; + } + } + + if (type.IsRefLikeType) + { + return null; + } + + if (type.DeclaredAccessibility != Accessibility.Public && type.DeclaredAccessibility != Accessibility.Internal) + { + return null; + } + + if (type.IsUnmanaged()) + { + if (type.HasInterface("Data.IEmbeddedResource")) + { + return type; + } + } + + return null; + } + + public static string Generate(IReadOnlyList types, out string typeName) + { + string? assemblyName = types[0].ContainingAssembly?.Name; + if (assemblyName is not null && assemblyName.EndsWith(".Core")) + { + assemblyName = assemblyName.Substring(0, assemblyName.Length - 5); + } + + SourceBuilder source = new(); + source.AppendLine("using Unmanaged;"); + source.AppendLine("using Worlds;"); + source.AppendLine("using Data;"); + source.AppendLine("using Data.Functions;"); + source.AppendLine(); + + if (assemblyName is not null) + { + source.Append("namespace "); + source.AppendLine(assemblyName); + source.BeginGroup(); + } + + typeName = TypeNameFormat.Replace("{0}", assemblyName ?? ""); + typeName = typeName.Replace(".", ""); + source.Append("public readonly struct "); + source.Append(typeName); + source.Append(" : IEmbeddedResourceBank"); + source.AppendLine(); + + source.BeginGroup(); + { + source.AppendLine("readonly void IEmbeddedResourceBank.Load(Register function)"); + source.BeginGroup(); + { + foreach (ITypeSymbol? type in types) + { + if (type is not null) + { + AppendRegister(source, type); + } + } + } + source.EndGroup(); + } + source.EndGroup(); + + if (assemblyName is not null) + { + source.EndGroup(); + } + + return source.ToString(); + } + + private static void AppendRegister(SourceBuilder source, ITypeSymbol type) + { + source.Append("function.Invoke<"); + source.Append(type.ToDisplayString()); + source.Append(">();"); + source.AppendLine(); + } + } +} \ No newline at end of file diff --git a/generator/Generators/EmbeddedResourceRegistryLoaderGenerator.cs b/generator/Generators/EmbeddedResourceRegistryLoaderGenerator.cs new file mode 100644 index 0000000..2a1deff --- /dev/null +++ b/generator/Generators/EmbeddedResourceRegistryLoaderGenerator.cs @@ -0,0 +1,96 @@ +using Microsoft.CodeAnalysis; +using Types; + +namespace Data.Generator +{ + [Generator(LanguageNames.CSharp)] + public class EmbeddedResourceRegistryLoaderGenerator : IIncrementalGenerator + { + private const string TypeName = "EmbeddedResourceRegistryLoader"; + + void IIncrementalGenerator.Initialize(IncrementalGeneratorInitializationContext context) + { + context.RegisterSourceOutput(context.CompilationProvider, Generate); + } + + private static void Generate(SourceProductionContext context, Compilation compilation) + { + if (compilation.GetEntryPoint(context.CancellationToken) is not null) + { + context.AddSource($"{TypeName}.generated.cs", Generate(compilation)); + } + } + + public static string Generate(Compilation compilation) + { + string? assemblyName = compilation.AssemblyName; + SourceBuilder builder = new(); + builder.AppendLine("using Data;"); + builder.AppendLine(); + + if (assemblyName is not null) + { + builder.Append("namespace "); + builder.Append(assemblyName); + builder.AppendLine(); + builder.BeginGroup(); + } + + builder.AppendLine($"internal static partial class {TypeName}"); + builder.BeginGroup(); + { + builder.AppendLine($"public static void Load()"); + builder.BeginGroup(); + { + foreach (ITypeSymbol type in compilation.GetAllTypes()) + { + if (type.IsRefLikeType) + { + continue; + } + + if (type.DeclaredAccessibility == Accessibility.Private || type.DeclaredAccessibility == Accessibility.ProtectedOrInternal) + { + continue; + } + + if (type is INamedTypeSymbol namedType) + { + if (namedType.IsGenericType) + { + continue; + } + } + + if (type.HasInterface("Data.IEmbeddedResourceBank")) + { + AppendRegistration(builder, type); + } + } + } + builder.EndGroup(); + } + builder.EndGroup(); + + if (assemblyName is not null) + { + builder.EndGroup(); + } + + return builder.ToString(); + } + + private static void AppendRegistration(SourceBuilder builder, ITypeSymbol type) + { + builder.Append("EmbeddedResourceRegistry.Load<"); + builder.Append(GetFullTypeName(type)); + builder.Append(">();"); + builder.AppendLine(); + } + + private static string GetFullTypeName(ITypeSymbol type) + { + return type.ToDisplayString(); + } + } +} \ No newline at end of file diff --git a/generator/SourceBuilder.cs b/generator/SourceBuilder.cs deleted file mode 100644 index 5952512..0000000 --- a/generator/SourceBuilder.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Collections.Generic; -using System.Text; - -namespace Data.Generator -{ - public class SourceBuilder - { - private readonly StringBuilder builder = new(); - private int indentation; - - public IEnumerable Lines - { - get - { - string[] lines = builder.ToString().Split('\n'); - for (int i = 0; i < lines.Length; i++) - { - yield return lines[i].TrimEnd('\r'); - } - } - } - - public override string ToString() - { - return builder.ToString(); - } - - public void Clear() - { - indentation = 0; - builder.Clear(); - } - - public void BeginGroup() - { - AppendIndentation(); - builder.Append('{'); - builder.AppendLine(); - indentation++; - } - - public void EndGroup() - { - indentation--; - AppendIndentation(); - builder.Append('}'); - builder.AppendLine(); - } - - public void AppendIndentation() - { - for (int i = 0; i < indentation; i++) - { - builder.Append(" "); - } - } - - public void AppendLine(object text) - { - AppendIndentation(); - builder.AppendLine(text.ToString()); - } - } -} \ No newline at end of file diff --git a/tests/AddressTests.cs b/tests/AddressTests.cs index 8cb1b9d..702891b 100644 --- a/tests/AddressTests.cs +++ b/tests/AddressTests.cs @@ -2,23 +2,11 @@ { public class AddressTests { - [Test] - public void CheckDataReferenceEquality() - { - Address defaultMaterialAddress = Address.Get(); - Assert.That(defaultMaterialAddress.ToString(), Is.EqualTo("Assets/Materials/unlit.mat")); - } - [Test] public void AddressEquality() { Address a = new("abacus"); Assert.That(a.Matches("*/abacus"), Is.True); } - - public readonly struct DefaultMaterial : IDataReference - { - readonly Address IDataReference.Value => "Assets/Materials/unlit.mat"; - } } } \ No newline at end of file diff --git a/tests/Assets/data1.txt b/tests/Assets/data1.txt new file mode 100644 index 0000000..9c5a87c --- /dev/null +++ b/tests/Assets/data1.txt @@ -0,0 +1 @@ +this is some data, yo \ No newline at end of file diff --git a/tests/Data.Tests.csproj b/tests/Data.Tests.csproj index 97e86aa..9f7efd7 100644 --- a/tests/Data.Tests.csproj +++ b/tests/Data.Tests.csproj @@ -2,7 +2,6 @@ net9.0 - Rendering.Tests disable enable false @@ -10,6 +9,12 @@ true + + + Never + + + all diff --git a/tests/DataEntityTests.cs b/tests/DataEntityTests.cs index 375515e..6b0cf86 100644 --- a/tests/DataEntityTests.cs +++ b/tests/DataEntityTests.cs @@ -13,9 +13,9 @@ public void LoadDataFromEntity() Assert.That(data.Address.ToString(), Is.EqualTo("hello")); - using BinaryReader reader = new(data.Bytes); + using BinaryReader reader = data.CreateBinaryReader(); USpan buffer = stackalloc char[128]; - uint length = reader.ReadUTF8Span(buffer); + uint length = reader.ReadUTF8(buffer); Assert.That(buffer.Slice(0, length).ToString(), Is.EqualTo("data")); } diff --git a/tests/EmbeddedResourceBankTests.cs b/tests/EmbeddedResourceBankTests.cs new file mode 100644 index 0000000..0fd9b40 --- /dev/null +++ b/tests/EmbeddedResourceBankTests.cs @@ -0,0 +1,33 @@ +using Data.Functions; +using Unmanaged; + +namespace Data.Tests +{ + public class EmbeddedResourceBankTests : DataTests + { + [Test] + public void LoadEmbeddedResource() + { + Assert.That(EmbeddedResourceRegistry.Contains("Assets/data1.txt"), Is.False); + + EmbeddedResourceRegistry.Load(); + + Assert.That(EmbeddedResourceRegistry.Contains("Assets/data1.txt"), Is.True); + EmbeddedResource embeddedResource = EmbeddedResourceRegistry.Get("Assets/data1.txt"); + Assert.That(embeddedResource.Assembly, Is.EqualTo(typeof(CustomResourceBank).Assembly)); + Assert.That(embeddedResource.address.ToString(), Is.EqualTo("Assets/data1.txt")); + using BinaryReader data = embeddedResource.CreateBinaryReader(); + USpan buffer = stackalloc char[128]; + uint length = data.ReadUTF8(buffer); + Assert.That(buffer.Slice(0, length).ToString(), Is.EqualTo("this is some data, yo")); + } + + public readonly struct CustomResourceBank : IEmbeddedResourceBank + { + void IEmbeddedResourceBank.Load(Register register) + { + register.Invoke("Assets/data1.txt"); + } + } + } +} \ No newline at end of file