From 00beb9a3ee866976a6e21fe10ba772e481dd122a Mon Sep 17 00:00:00 2001 From: Phill Date: Fri, 31 Jan 2025 20:55:30 -0500 Subject: [PATCH 1/3] slow refactor progress --- core/Address.cs | 7 +- core/Components/IsData.cs | 15 --- core/Components/IsDataRequest.cs | 28 +++--- core/DataRequest.cs | 61 +++--------- core/DataSource.cs | 22 ++--- core/EmbeddedAddress.cs | 57 ----------- core/EmbeddedResource.cs | 35 +++++++ core/EmbeddedResourceRegistry.cs | 70 +++++++++++++ core/Extensions/DataExtensions.cs | 10 ++ core/Extensions/NameExtensions.cs | 2 +- core/Functions/Register.cs | 19 ++++ core/Message/HandleDataRequest.cs | 17 ++++ core/RequestStatus.cs | 11 +++ core/RequestedDataNotFoundException.cs | 14 --- core/Types/IDataReference.cs | 7 -- core/Types/IEmbeddedResource.cs | 7 ++ core/Types/IEmbeddedResources.cs | 9 -- core/Types/IEmbeddedResourcesBank.cs | 9 ++ generator/EmbeddedAddressTableGenerator.cs | 97 ------------------- ...EmbeddedResourceRegistryLoaderGenerator.cs | 96 ++++++++++++++++++ tests/AddressTests.cs | 12 --- tests/Assets/data1.txt | 1 + tests/Data.Tests.csproj | 7 +- tests/DataEntityTests.cs | 2 +- tests/EmbeddedResourceBankTests.cs | 33 +++++++ 25 files changed, 361 insertions(+), 287 deletions(-) delete mode 100644 core/Components/IsData.cs delete mode 100644 core/EmbeddedAddress.cs create mode 100644 core/EmbeddedResource.cs create mode 100644 core/EmbeddedResourceRegistry.cs create mode 100644 core/Extensions/DataExtensions.cs create mode 100644 core/Functions/Register.cs create mode 100644 core/Message/HandleDataRequest.cs create mode 100644 core/RequestStatus.cs delete mode 100644 core/RequestedDataNotFoundException.cs delete mode 100644 core/Types/IDataReference.cs create mode 100644 core/Types/IEmbeddedResource.cs delete mode 100644 core/Types/IEmbeddedResources.cs create mode 100644 core/Types/IEmbeddedResourcesBank.cs delete mode 100644 generator/EmbeddedAddressTableGenerator.cs create mode 100644 generator/Generators/EmbeddedResourceRegistryLoaderGenerator.cs create mode 100644 tests/Assets/data1.txt create mode 100644 tests/EmbeddedResourceBankTests.cs diff --git a/core/Address.cs b/core/Address.cs index eed8da7..4a11e45 100644 --- a/core/Address.cs +++ b/core/Address.cs @@ -6,7 +6,7 @@ namespace Data { public struct Address : IEquatable
{ - public FixedString value; + private FixedString value; public Address(FixedString value) { @@ -187,10 +187,5 @@ public static implicit operator Address(string value) { return new(value); } - - public static Address Get() where T : unmanaged, IDataReference - { - return default(T).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..6f0765a 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,36 @@ 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; } } } \ No newline at end of file diff --git a/core/DataRequest.cs b/core/DataRequest.cs index ebb5a33..5a2681e 100644 --- a/core/DataRequest.cs +++ b/core/DataRequest.cs @@ -2,8 +2,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Threading.Tasks; using System.Threading; +using System.Threading.Tasks; using Unmanaged; using Worlds; @@ -28,11 +28,12 @@ public readonly Address Address } } - public readonly USpan Data + public readonly USpan Bytes { get { - ThrowIfDataNotAvailable(); + ThrowIfNotLoaded(); + return entity.GetArray().As(); } } @@ -44,14 +45,8 @@ public readonly bool IsLoaded { get { - if (entity.TryGetComponent(out IsData data)) - { - return data.version == entity.GetComponent().version; - } - else - { - return false; - } + ref IsDataRequest component = ref entity.GetComponent(); + return component.status == RequestStatus.Loaded; } } @@ -71,29 +66,24 @@ public DataRequest() } #endif - public DataRequest(World world, uint existingEntity) + public DataRequest(World world, USpan address, TimeSpan timeout = default) { - this.entity = new(world, existingEntity); + entity = new Entity(world, new IsDataRequest(address, RequestStatus.Submitted, timeout)); } - public DataRequest(World world, USpan address) + public DataRequest(World world, Address address, TimeSpan timeout = default) { - entity = new Entity(world, new IsDataRequest(address)); + entity = new Entity(world, new IsDataRequest(address, RequestStatus.Submitted, timeout)); } - public DataRequest(World world, Address address) + public DataRequest(World world, string address, TimeSpan timeout = default) { - entity = new Entity(world, new IsDataRequest(address)); + entity = new Entity(world, new IsDataRequest(address, RequestStatus.Submitted, timeout)); } - public DataRequest(World world, string address) + public DataRequest(World world, IEnumerable address, TimeSpan timeout = default) { - entity = new Entity(world, new IsDataRequest(address)); - } - - public DataRequest(World world, IEnumerable address) - { - entity = new Entity(world, new IsDataRequest(address)); + entity = new Entity(world, new IsDataRequest(address, RequestStatus.Submitted, timeout)); } public readonly void Dispose() @@ -120,25 +110,6 @@ 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) - { - ThrowIfDataNotAvailable(); - - using BinaryReader reader = new(Data); - return reader.ReadUTF8Span(buffer); - } - public async Task UntilLoaded(Update action, CancellationToken cancellation = default) { World world = entity.GetWorld(); @@ -149,7 +120,7 @@ public async Task UntilLoaded(Update action, CancellationToken cancellation = de } [Conditional("DEBUG")] - public void ThrowIfDataNotAvailable() + public readonly void ThrowIfNotLoaded() { if (!IsLoaded) { @@ -162,4 +133,4 @@ 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..1feebd9 100644 --- a/core/DataSource.cs +++ b/core/DataSource.cs @@ -61,7 +61,7 @@ public DataSource(World world, Address address, USpan text) entity = new(world); entity.AddComponent(new IsDataSource(address)); entity.CreateArray(); - Write(text); + WriteUTF8(text); } /// @@ -72,7 +72,7 @@ public DataSource(World world, Address address, string text) entity = new(world); entity.AddComponent(new IsDataSource(address)); entity.CreateArray(); - Write(text); + WriteUTF8(text); } public readonly void Dispose() @@ -101,31 +101,31 @@ public readonly void Clear() /// /// 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()); } /// 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..8e23614 --- /dev/null +++ b/core/EmbeddedResourceRegistry.cs @@ -0,0 +1,70 @@ +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(); + + public static IReadOnlyList All => all; + + public static void Load() where T : unmanaged, IEmbeddedResourcesBank + { + 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)); + } + + private static bool TryGetMatch(Address address, out int index) + { + for (int i = 0; i < all.Count; i++) + { + if (all[i].address.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]; + } + } +} \ No newline at end of file diff --git a/core/Extensions/DataExtensions.cs b/core/Extensions/DataExtensions.cs new file mode 100644 index 0000000..a7b066d --- /dev/null +++ b/core/Extensions/DataExtensions.cs @@ -0,0 +1,10 @@ +using Data.Components; +using System.Diagnostics; +using Worlds; + +namespace Data +{ + public static class DataExtensions + { + } +} \ No newline at end of file diff --git a/core/Extensions/NameExtensions.cs b/core/Extensions/NameExtensions.cs index 7a47042..6fb0468 100644 --- a/core/Extensions/NameExtensions.cs +++ b/core/Extensions/NameExtensions.cs @@ -11,4 +11,4 @@ public static ref FixedString GetName(this T entity) where T : unmanaged, IEn return ref entity.AsEntity().GetComponent().value; } } -} +} \ No newline at end of file diff --git a/core/Functions/Register.cs b/core/Functions/Register.cs new file mode 100644 index 0000000..ee8130d --- /dev/null +++ b/core/Functions/Register.cs @@ -0,0 +1,19 @@ +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); + } + } +} \ No newline at end of file diff --git a/core/Message/HandleDataRequest.cs b/core/Message/HandleDataRequest.cs new file mode 100644 index 0000000..afa03c6 --- /dev/null +++ b/core/Message/HandleDataRequest.cs @@ -0,0 +1,17 @@ +using Data.Components; +using Worlds; + +namespace Data.Messages +{ + public struct HandleDataRequest + { + public readonly Entity entity; + public IsDataRequest request; + + 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/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/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/core/Types/IEmbeddedResourcesBank.cs b/core/Types/IEmbeddedResourcesBank.cs new file mode 100644 index 0000000..ec9d24d --- /dev/null +++ b/core/Types/IEmbeddedResourcesBank.cs @@ -0,0 +1,9 @@ +using Data.Functions; + +namespace Data +{ + public interface IEmbeddedResourcesBank + { + void Load(Register register); + } +} 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/EmbeddedResourceRegistryLoaderGenerator.cs b/generator/Generators/EmbeddedResourceRegistryLoaderGenerator.cs new file mode 100644 index 0000000..6c8e746 --- /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 Types;"); + 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")) + { + builder.Append("EmbeddedResourceRegistry.Load<"); + builder.Append(type.GetFullTypeName()); + builder.Append(">();"); + builder.AppendLine(); + } + } + } + builder.EndGroup(); + } + builder.EndGroup(); + + if (assemblyName is not null) + { + builder.EndGroup(); + } + + return builder.ToString(); + } + + private static void AppendRegistration(SourceBuilder builder, ITypeSymbol type) + { + builder.AppendLine($"EmbeddedAddress.Register<{GetFullTypeName(type)}>();"); + } + + private static string GetFullTypeName(ITypeSymbol type) + { + return type.ToDisplayString(); + } + } +} \ 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..f7d07af 100644 --- a/tests/DataEntityTests.cs +++ b/tests/DataEntityTests.cs @@ -15,7 +15,7 @@ public void LoadDataFromEntity() using BinaryReader reader = new(data.Bytes); 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..2be4d9b --- /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 : IEmbeddedResourcesBank + { + void IEmbeddedResourcesBank.Load(Register register) + { + register.Invoke("Assets/data1.txt"); + } + } + } +} \ No newline at end of file From 84d3f1bb74dba962793a21f13be76c12b439aedd Mon Sep 17 00:00:00 2001 From: Phill Date: Sat, 1 Feb 2025 16:14:41 -0500 Subject: [PATCH 2/3] further clean up --- core/DataRequest.cs | 63 ++---------------------- core/DataSource.cs | 57 ++-------------------- core/Extensions/DataExtensions.cs | 81 ++++++++++++++++++++++++++++++- core/Types/IData.cs | 8 +++ core/Types/IDataRequest.cs | 6 +++ core/Types/IDataSource.cs | 8 ++- tests/DataEntityTests.cs | 4 +- 7 files changed, 107 insertions(+), 120 deletions(-) create mode 100644 core/Types/IData.cs create mode 100644 core/Types/IDataRequest.cs diff --git a/core/DataRequest.cs b/core/DataRequest.cs index 5a2681e..e7fbc49 100644 --- a/core/DataRequest.cs +++ b/core/DataRequest.cs @@ -1,9 +1,6 @@ using Data.Components; using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; using Unmanaged; using Worlds; @@ -12,44 +9,10 @@ namespace Data /// /// An entity that will contain data loaded from its address. /// - public readonly struct DataRequest : IEntity + public readonly struct DataRequest : IDataRequest { private readonly Entity entity; - /// - /// Address that this data request is looking for. - /// - public readonly Address Address - { - get - { - ref IsDataRequest component = ref entity.GetComponent(); - return component.address; - } - } - - public readonly USpan Bytes - { - get - { - ThrowIfNotLoaded(); - - return entity.GetArray().As(); - } - } - - /// - /// Checks if the data has been loaded. - /// - public readonly bool IsLoaded - { - get - { - ref IsDataRequest component = ref entity.GetComponent(); - return component.status == RequestStatus.Loaded; - } - } - readonly uint IEntity.Value => entity.value; readonly World IEntity.World => entity.world; @@ -93,14 +56,14 @@ public readonly void Dispose() public readonly override string ToString() { - return Address.ToString(); + return this.GetRequestAddress().ToString(); } public readonly bool TryGetData(out USpan data) { - if (IsLoaded) + if (this.IsLoaded()) { - data = entity.GetArray().As(); + data = this.GetBytes(); return true; } else @@ -110,24 +73,6 @@ public readonly bool TryGetData(out USpan data) } } - public async Task UntilLoaded(Update action, CancellationToken cancellation = default) - { - World world = entity.GetWorld(); - while (!IsLoaded) - { - await action(world, cancellation); - } - } - - [Conditional("DEBUG")] - public readonly void ThrowIfNotLoaded() - { - if (!IsLoaded) - { - throw new InvalidOperationException($"Data not yet available on `{entity.GetEntityValue()}`"); - } - } - public static implicit operator Entity(DataRequest request) { return request.entity; diff --git a/core/DataSource.cs b/core/DataSource.cs index 1feebd9..83da913 100644 --- a/core/DataSource.cs +++ b/core/DataSource.cs @@ -13,9 +13,6 @@ namespace Data { 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; @@ -61,7 +58,7 @@ public DataSource(World world, Address address, USpan text) entity = new(world); entity.AddComponent(new IsDataSource(address)); entity.CreateArray(); - WriteUTF8(text); + this.WriteUTF8(text); } /// @@ -72,7 +69,7 @@ public DataSource(World world, Address address, string text) entity = new(world); entity.AddComponent(new IsDataSource(address)); entity.CreateArray(); - WriteUTF8(text); + this.WriteUTF8(text); } public readonly void Dispose() @@ -89,53 +86,7 @@ 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); - } - - /// - /// Appends the given text as UTF8 formatted bytes. - /// - public readonly void WriteUTF8(USpan text) - { - using BinaryWriter writer = new(4); - writer.WriteUTF8(text); - Write(writer.AsSpan()); - } - - /// - /// Appends the given text as UTF8 formatted bytes. - /// - public readonly void WriteUTF8(FixedString text) - { - using BinaryWriter writer = new(4); - writer.WriteUTF8(text); - Write(writer.AsSpan()); - } - - /// - /// Appends the given text as UTF8 formatted bytes. - /// - public readonly void WriteUTF8(string text) - { - using BinaryWriter writer = new(4); - writer.WriteUTF8(text); - Write(writer.AsSpan()); - } - - /// - /// Appends the given bytes. - /// - public readonly void Write(USpan bytes) - { - USpan array = entity.GetArray(); - array = entity.ResizeArray(bytes.Length + array.Length); - bytes.CopyTo(array.As()); + return this.GetSourceAddress().ToString(buffer); } } -} +} \ No newline at end of file diff --git a/core/Extensions/DataExtensions.cs b/core/Extensions/DataExtensions.cs index a7b066d..f65207f 100644 --- a/core/Extensions/DataExtensions.cs +++ b/core/Extensions/DataExtensions.cs @@ -1,10 +1,89 @@ using Data.Components; -using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Unmanaged; using Worlds; namespace Data { public static class DataExtensions { + public static USpan GetBytes(this T data) where T : unmanaged, IData + { + return data.AsEntity().GetArray().As(); + } + + public static BinaryReader CreateBinaryReader(this T data) where T : unmanaged, IData + { + return new(data.GetBytes()); + } + + public static bool IsLoaded(this T data) where T : unmanaged, IDataRequest + { + return data.AsEntity().GetComponent().status == RequestStatus.Loaded; + } + + public static async Task UntilLoaded(this T data, Update action, CancellationToken cancellation = default) where T : unmanaged, IDataRequest + { + while (!IsLoaded(data)) + { + await action(data.GetWorld(), cancellation); + } + } + + public static Address GetSourceAddress(this T data) where T : unmanaged, IDataSource + { + return data.AsEntity().GetComponent().address; + } + + public static Address GetRequestAddress(this T data) where T : unmanaged, IDataRequest + { + return data.AsEntity().GetComponent().address; + } + + public static void Clear(this T data) where T : unmanaged, IData + { + data.AsEntity().ResizeArray(0); + } + + /// + /// Appends the given text as UTF8 formatted bytes. + /// + public static void WriteUTF8(this T data, USpan text) where T : unmanaged, IData + { + using BinaryWriter writer = new(4); + writer.WriteUTF8(text); + Write(data, writer.AsSpan()); + } + + /// + /// Appends the given text as UTF8 formatted bytes. + /// + public static void WriteUTF8(this T data, FixedString text) where T : unmanaged, IData + { + using BinaryWriter writer = new(4); + writer.WriteUTF8(text); + Write(data, writer.AsSpan()); + } + + /// + /// Appends the given text as UTF8 formatted bytes. + /// + public static void WriteUTF8(this T data, string text) where T : unmanaged, IData + { + using BinaryWriter writer = new(4); + writer.WriteUTF8(text); + Write(data, writer.AsSpan()); + } + + /// + /// Appends the given bytes. + /// + public static void Write(this T data, USpan bytes) where T : unmanaged, IData + { + uint length = data.AsEntity().GetArrayLength(); + USpan array = data.AsEntity().ResizeArray(bytes.Length + length); + bytes.CopyTo(array.As()); + } } } \ No newline at end of file diff --git a/core/Types/IData.cs b/core/Types/IData.cs new file mode 100644 index 0000000..bdce355 --- /dev/null +++ b/core/Types/IData.cs @@ -0,0 +1,8 @@ +using Worlds; + +namespace Data +{ + public interface IData : IEntity + { + } +} \ No newline at end of file diff --git a/core/Types/IDataRequest.cs b/core/Types/IDataRequest.cs new file mode 100644 index 0000000..60a7e02 --- /dev/null +++ b/core/Types/IDataRequest.cs @@ -0,0 +1,6 @@ +namespace Data +{ + public interface IDataRequest : IData + { + } +} \ No newline at end of file diff --git a/core/Types/IDataSource.cs b/core/Types/IDataSource.cs index 74f32aa..e606f4d 100644 --- a/core/Types/IDataSource.cs +++ b/core/Types/IDataSource.cs @@ -1,8 +1,6 @@ -using Worlds; - -namespace Data +namespace Data { - public interface IDataSource : IEntity + public interface IDataSource : IData { } -} +} \ No newline at end of file diff --git a/tests/DataEntityTests.cs b/tests/DataEntityTests.cs index f7d07af..d6195cc 100644 --- a/tests/DataEntityTests.cs +++ b/tests/DataEntityTests.cs @@ -11,9 +11,9 @@ public void LoadDataFromEntity() using World world = CreateWorld(); DataSource data = new(world, "hello", "data"); - Assert.That(data.Address.ToString(), Is.EqualTo("hello")); + Assert.That(data.GetSourceAddress().ToString(), Is.EqualTo("hello")); - using BinaryReader reader = new(data.Bytes); + using BinaryReader reader = data.CreateBinaryReader(); USpan buffer = stackalloc char[128]; uint length = reader.ReadUTF8(buffer); From 3b1e7e8fdfda2499ddd56b01b0f11738e1d9c893 Mon Sep 17 00:00:00 2001 From: Phill Date: Mon, 3 Feb 2025 17:29:00 -0500 Subject: [PATCH 3/3] leverage entity inheriting --- core/Address.cs | 37 +++++ core/Components/IsDataRequest.cs | 5 + core/Components/Name.cs | 21 --- core/DataRequest.cs | 64 ++++---- core/DataSource.cs | 99 +++++++----- core/EmbeddedResourceRegistry.cs | 20 ++- core/Extensions/DataExtensions.cs | 89 ----------- core/Extensions/NameExtensions.cs | 14 -- core/Functions/Register.cs | 6 + core/Message/HandleDataRequest.cs | 3 + core/Types/IData.cs | 8 - core/Types/IDataRequest.cs | 6 +- core/Types/IDataSource.cs | 6 +- ...ourcesBank.cs => IEmbeddedResourceBank.cs} | 2 +- generator/Data.Generator.csproj | 4 + .../EmbeddedResourceBankGenerator.cs | 146 ++++++++++++++++++ ...EmbeddedResourceRegistryLoaderGenerator.cs | 12 +- generator/SourceBuilder.cs | 64 -------- tests/DataEntityTests.cs | 2 +- tests/EmbeddedResourceBankTests.cs | 4 +- 20 files changed, 338 insertions(+), 274 deletions(-) delete mode 100644 core/Components/Name.cs delete mode 100644 core/Extensions/DataExtensions.cs delete mode 100644 core/Extensions/NameExtensions.cs delete mode 100644 core/Types/IData.cs rename core/Types/{IEmbeddedResourcesBank.cs => IEmbeddedResourceBank.cs} (68%) create mode 100644 generator/Generators/EmbeddedResourceBankGenerator.cs delete mode 100644 generator/SourceBuilder.cs diff --git a/core/Address.cs b/core/Address.cs index 4a11e45..5cfd3c0 100644 --- a/core/Address.cs +++ b/core/Address.cs @@ -8,6 +8,8 @@ public struct Address : IEquatable
{ private FixedString value; + public readonly byte Length => value.Length; + public Address(FixedString value) { this.value = 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]; @@ -187,5 +219,10 @@ public static implicit operator Address(string value) { return new(value); } + + public static implicit operator FixedString(Address address) + { + return address.value; + } } } \ No newline at end of file diff --git a/core/Components/IsDataRequest.cs b/core/Components/IsDataRequest.cs index 6f0765a..767e163 100644 --- a/core/Components/IsDataRequest.cs +++ b/core/Components/IsDataRequest.cs @@ -39,5 +39,10 @@ public IsDataRequest(IEnumerable address, RequestStatus status, TimeSpan t 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 e7fbc49..047477b 100644 --- a/core/DataRequest.cs +++ b/core/DataRequest.cs @@ -1,6 +1,7 @@ using Data.Components; using System; using System.Collections.Generic; +using System.Diagnostics; using Unmanaged; using Worlds; @@ -9,61 +10,61 @@ namespace Data /// /// An entity that will contain data loaded from its address. /// - public readonly struct DataRequest : IDataRequest + public readonly partial struct DataRequest : IDataRequest { - private readonly Entity entity; + public readonly Address Address => GetComponent().address; + public readonly bool IsLoaded => GetComponent().status == RequestStatus.Loaded; - readonly uint IEntity.Value => entity.value; - readonly World IEntity.World => entity.world; - - readonly void IEntity.Describe(ref Archetype archetype) + public readonly USpan Bytes { - archetype.AddComponentType(); + get + { + ThrowIfNotLoaded(); + + return GetArray().As(); + } } -#if NET - [Obsolete("Default constructor not supported", true)] - public DataRequest() + readonly void IEntity.Describe(ref Archetype archetype) { - throw new NotSupportedException(); + archetype.AddComponentType(); + archetype.AddArrayType(); } -#endif public DataRequest(World world, USpan address, TimeSpan timeout = default) { - entity = new Entity(world, new IsDataRequest(address, RequestStatus.Submitted, timeout)); + this.world = world; + value = world.CreateEntity(new IsDataRequest(address, RequestStatus.Submitted, timeout)); } public DataRequest(World world, Address address, TimeSpan timeout = default) { - entity = new Entity(world, new IsDataRequest(address, RequestStatus.Submitted, timeout)); + this.world = world; + value = world.CreateEntity(new IsDataRequest(address, RequestStatus.Submitted, timeout)); } public DataRequest(World world, string address, TimeSpan timeout = default) { - entity = new Entity(world, new IsDataRequest(address, RequestStatus.Submitted, timeout)); + this.world = world; + value = world.CreateEntity(new IsDataRequest(address, RequestStatus.Submitted, timeout)); } public DataRequest(World world, IEnumerable address, TimeSpan timeout = default) { - entity = new Entity(world, new IsDataRequest(address, RequestStatus.Submitted, timeout)); - } - - public readonly void Dispose() - { - entity.Dispose(); + this.world = world; + value = world.CreateEntity(new IsDataRequest(address, RequestStatus.Submitted, timeout)); } public readonly override string ToString() { - return this.GetRequestAddress().ToString(); + return Address.ToString(); } public readonly bool TryGetData(out USpan data) { - if (this.IsLoaded()) + if (IsLoaded) { - data = this.GetBytes(); + data = Bytes; return true; } else @@ -73,9 +74,20 @@ public readonly bool TryGetData(out USpan data) } } - public static implicit operator Entity(DataRequest request) + public readonly BinaryReader CreateBinaryReader() + { + ThrowIfNotLoaded(); + + return new(Bytes); + } + + [Conditional("DEBUG")] + private readonly void ThrowIfNotLoaded() { - return request.entity; + if (!IsLoaded) + { + throw new InvalidOperationException($"Data entity `{value}` at address `{Address}` has not been loaded"); + } } } } \ No newline at end of file diff --git a/core/DataSource.cs b/core/DataSource.cs index 83da913..ddf272a 100644 --- a/core/DataSource.cs +++ b/core/DataSource.cs @@ -1,5 +1,4 @@ using Data.Components; -using System; using Unmanaged; using Worlds; @@ -9,35 +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; - - 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(); - } - -#if NET - [Obsolete("Default constructor not supported.", true)] - public DataSource() - { - throw new NotSupportedException(); + archetype.AddArrayType(); } -#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); } /// @@ -45,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()); } /// @@ -55,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(); - this.WriteUTF8(text); + this.world = world; + value = world.CreateEntity(new IsDataSource(address)); + world.CreateArray(value); + WriteUTF8(text); } /// @@ -66,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(); - this.WriteUTF8(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() @@ -86,7 +70,52 @@ public readonly override string ToString() public readonly uint ToString(USpan buffer) { - return this.GetSourceAddress().ToString(buffer); + return Address.ToString(buffer); + } + + /// + /// Appends the given text as UTF8 formatted bytes. + /// + public readonly void WriteUTF8(USpan text) + { + using BinaryWriter writer = new(4); + writer.WriteUTF8(text); + Write(writer.AsSpan()); + } + + /// + /// Appends the given text as UTF8 formatted bytes. + /// + public readonly void WriteUTF8(FixedString text) + { + using BinaryWriter writer = new(4); + writer.WriteUTF8(text); + Write(writer.AsSpan()); + } + + /// + /// Appends the given text as UTF8 formatted bytes. + /// + public readonly void WriteUTF8(string text) + { + using BinaryWriter writer = new(4); + writer.WriteUTF8(text); + Write(writer.AsSpan()); + } + + /// + /// Appends the given bytes. + /// + public readonly void Write(USpan bytes) + { + 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/EmbeddedResourceRegistry.cs b/core/EmbeddedResourceRegistry.cs index 8e23614..f6590ff 100644 --- a/core/EmbeddedResourceRegistry.cs +++ b/core/EmbeddedResourceRegistry.cs @@ -9,10 +9,11 @@ namespace Data 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, IEmbeddedResourcesBank + public static void Load() where T : unmanaged, IEmbeddedResourceBank { T template = default; Assembly assembly = typeof(T).Assembly; @@ -27,13 +28,14 @@ void Register(Address 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 < all.Count; i++) + for (int i = 0; i < addresses.Count; i++) { - if (all[i].address.Matches(address)) + if (addresses[i].Matches(address)) { index = i; return true; @@ -66,5 +68,17 @@ 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/DataExtensions.cs b/core/Extensions/DataExtensions.cs deleted file mode 100644 index f65207f..0000000 --- a/core/Extensions/DataExtensions.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Data.Components; -using System.Threading; -using System.Threading.Tasks; -using Unmanaged; -using Worlds; - -namespace Data -{ - public static class DataExtensions - { - public static USpan GetBytes(this T data) where T : unmanaged, IData - { - return data.AsEntity().GetArray().As(); - } - - public static BinaryReader CreateBinaryReader(this T data) where T : unmanaged, IData - { - return new(data.GetBytes()); - } - - public static bool IsLoaded(this T data) where T : unmanaged, IDataRequest - { - return data.AsEntity().GetComponent().status == RequestStatus.Loaded; - } - - public static async Task UntilLoaded(this T data, Update action, CancellationToken cancellation = default) where T : unmanaged, IDataRequest - { - while (!IsLoaded(data)) - { - await action(data.GetWorld(), cancellation); - } - } - - public static Address GetSourceAddress(this T data) where T : unmanaged, IDataSource - { - return data.AsEntity().GetComponent().address; - } - - public static Address GetRequestAddress(this T data) where T : unmanaged, IDataRequest - { - return data.AsEntity().GetComponent().address; - } - - public static void Clear(this T data) where T : unmanaged, IData - { - data.AsEntity().ResizeArray(0); - } - - /// - /// Appends the given text as UTF8 formatted bytes. - /// - public static void WriteUTF8(this T data, USpan text) where T : unmanaged, IData - { - using BinaryWriter writer = new(4); - writer.WriteUTF8(text); - Write(data, writer.AsSpan()); - } - - /// - /// Appends the given text as UTF8 formatted bytes. - /// - public static void WriteUTF8(this T data, FixedString text) where T : unmanaged, IData - { - using BinaryWriter writer = new(4); - writer.WriteUTF8(text); - Write(data, writer.AsSpan()); - } - - /// - /// Appends the given text as UTF8 formatted bytes. - /// - public static void WriteUTF8(this T data, string text) where T : unmanaged, IData - { - using BinaryWriter writer = new(4); - writer.WriteUTF8(text); - Write(data, writer.AsSpan()); - } - - /// - /// Appends the given bytes. - /// - public static void Write(this T data, USpan bytes) where T : unmanaged, IData - { - uint length = data.AsEntity().GetArrayLength(); - USpan array = data.AsEntity().ResizeArray(bytes.Length + length); - bytes.CopyTo(array.As()); - } - } -} \ No newline at end of file diff --git a/core/Extensions/NameExtensions.cs b/core/Extensions/NameExtensions.cs deleted file mode 100644 index 6fb0468..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; - } - } -} \ No newline at end of file diff --git a/core/Functions/Register.cs b/core/Functions/Register.cs index ee8130d..91fe68c 100644 --- a/core/Functions/Register.cs +++ b/core/Functions/Register.cs @@ -15,5 +15,11 @@ 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 index afa03c6..bfa37bd 100644 --- a/core/Message/HandleDataRequest.cs +++ b/core/Message/HandleDataRequest.cs @@ -1,4 +1,5 @@ using Data.Components; +using Unmanaged; using Worlds; namespace Data.Messages @@ -8,6 +9,8 @@ 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; diff --git a/core/Types/IData.cs b/core/Types/IData.cs deleted file mode 100644 index bdce355..0000000 --- a/core/Types/IData.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Worlds; - -namespace Data -{ - public interface IData : IEntity - { - } -} \ No newline at end of file diff --git a/core/Types/IDataRequest.cs b/core/Types/IDataRequest.cs index 60a7e02..d5fd7de 100644 --- a/core/Types/IDataRequest.cs +++ b/core/Types/IDataRequest.cs @@ -1,6 +1,8 @@ -namespace Data +using Worlds; + +namespace Data { - public interface IDataRequest : IData + public interface IDataRequest : IEntity { } } \ No newline at end of file diff --git a/core/Types/IDataSource.cs b/core/Types/IDataSource.cs index e606f4d..f5091e6 100644 --- a/core/Types/IDataSource.cs +++ b/core/Types/IDataSource.cs @@ -1,6 +1,8 @@ -namespace Data +using Worlds; + +namespace Data { - public interface IDataSource : IData + public interface IDataSource : IEntity { } } \ No newline at end of file diff --git a/core/Types/IEmbeddedResourcesBank.cs b/core/Types/IEmbeddedResourceBank.cs similarity index 68% rename from core/Types/IEmbeddedResourcesBank.cs rename to core/Types/IEmbeddedResourceBank.cs index ec9d24d..635fa3f 100644 --- a/core/Types/IEmbeddedResourcesBank.cs +++ b/core/Types/IEmbeddedResourceBank.cs @@ -2,7 +2,7 @@ namespace Data { - public interface IEmbeddedResourcesBank + public interface IEmbeddedResourceBank { void Load(Register register); } 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/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 index 6c8e746..2a1deff 100644 --- a/generator/Generators/EmbeddedResourceRegistryLoaderGenerator.cs +++ b/generator/Generators/EmbeddedResourceRegistryLoaderGenerator.cs @@ -25,7 +25,7 @@ public static string Generate(Compilation compilation) { string? assemblyName = compilation.AssemblyName; SourceBuilder builder = new(); - builder.AppendLine("using Types;"); + builder.AppendLine("using Data;"); builder.AppendLine(); if (assemblyName is not null) @@ -64,10 +64,7 @@ public static string Generate(Compilation compilation) if (type.HasInterface("Data.IEmbeddedResourceBank")) { - builder.Append("EmbeddedResourceRegistry.Load<"); - builder.Append(type.GetFullTypeName()); - builder.Append(">();"); - builder.AppendLine(); + AppendRegistration(builder, type); } } } @@ -85,7 +82,10 @@ public static string Generate(Compilation compilation) private static void AppendRegistration(SourceBuilder builder, ITypeSymbol type) { - builder.AppendLine($"EmbeddedAddress.Register<{GetFullTypeName(type)}>();"); + builder.Append("EmbeddedResourceRegistry.Load<"); + builder.Append(GetFullTypeName(type)); + builder.Append(">();"); + builder.AppendLine(); } private static string GetFullTypeName(ITypeSymbol type) 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/DataEntityTests.cs b/tests/DataEntityTests.cs index d6195cc..6b0cf86 100644 --- a/tests/DataEntityTests.cs +++ b/tests/DataEntityTests.cs @@ -11,7 +11,7 @@ public void LoadDataFromEntity() using World world = CreateWorld(); DataSource data = new(world, "hello", "data"); - Assert.That(data.GetSourceAddress().ToString(), Is.EqualTo("hello")); + Assert.That(data.Address.ToString(), Is.EqualTo("hello")); using BinaryReader reader = data.CreateBinaryReader(); USpan buffer = stackalloc char[128]; diff --git a/tests/EmbeddedResourceBankTests.cs b/tests/EmbeddedResourceBankTests.cs index 2be4d9b..0fd9b40 100644 --- a/tests/EmbeddedResourceBankTests.cs +++ b/tests/EmbeddedResourceBankTests.cs @@ -22,9 +22,9 @@ public void LoadEmbeddedResource() Assert.That(buffer.Slice(0, length).ToString(), Is.EqualTo("this is some data, yo")); } - public readonly struct CustomResourceBank : IEmbeddedResourcesBank + public readonly struct CustomResourceBank : IEmbeddedResourceBank { - void IEmbeddedResourcesBank.Load(Register register) + void IEmbeddedResourceBank.Load(Register register) { register.Invoke("Assets/data1.txt"); }