Browse Source

Try to combine AvaloniaResources with the same system path (#15302)

* Try to combine AvaloniaResources with the same system path

* Fix test assert
release/11.1.0-beta2
Max Katz 2 years ago
parent
commit
a05c68f1df
  1. 55
      src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
  2. 8
      src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
  3. 13
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
  4. 91
      tests/Avalonia.Base.UnitTests/Utilities/AvaloniaResourcesIndexTests.cs

55
src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Avalonia.Utilities
@ -66,20 +67,45 @@ namespace Avalonia.Utilities
}
}
[Obsolete]
public static void WriteResources(Stream output, List<(string Path, int Size, Func<Stream> Open)> resources)
{
var entries = new List<AvaloniaResourcesIndexEntry>(resources.Count);
WriteResources(output,
resources.Select(r => new AvaloniaResourcesEntry { Path = r.Path, Open = r.Open, Size = r.Size })
.ToList());
}
public static void WriteResources(Stream output, IReadOnlyList<AvaloniaResourcesEntry> resources)
{
var entries = new List<AvaloniaResourcesIndexEntry>();
var index = new Dictionary<string, (AvaloniaResourcesIndexEntry entry, Func<Stream> open)>();
var offset = 0;
foreach (var resource in resources)
{
entries.Add(new AvaloniaResourcesIndexEntry
// Try to combine resources with the same system path, if present.
if (!string.IsNullOrEmpty(resource.SystemPath)
&& index.TryGetValue(resource.SystemPath, out var existingResource))
{
Path = resource.Path,
Offset = offset,
Size = resource.Size
});
offset += resource.Size;
entries.Add(new AvaloniaResourcesIndexEntry
{
Path = resource.Path,
Offset = existingResource.entry.Offset,
Size = existingResource.entry.Size
});
}
else
{
var entry = new AvaloniaResourcesIndexEntry
{
Path = resource.Path,
Offset = offset,
Size = resource.Size
};
index[resource.SystemPath ?? offset.ToString()] = (entry, resource.Open!);
entries.Add(entry);
offset += resource.Size;
}
}
using var writer = new BinaryWriter(output, Encoding.UTF8, leaveOpen: true);
@ -94,9 +120,9 @@ namespace Avalonia.Utilities
writer.Write(indexSize);
output.Position = posAfterEntries;
foreach (var resource in resources)
foreach (var pair in index)
{
using var resourceStream = resource.Open();
using var resourceStream = pair.Value.open();
resourceStream.CopyTo(output);
}
}
@ -113,4 +139,15 @@ namespace Avalonia.Utilities
public int Size { get; set; }
}
#if !BUILDTASK
public
#endif
class AvaloniaResourcesEntry
{
public string? Path { get; init; }
public Func<Stream>? Open { get; init; }
public int Size { get; init; }
public string? SystemPath { get; init; }
}
}

8
src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs

@ -80,7 +80,13 @@ namespace Avalonia.Build.Tasks
{
AvaloniaResourcesIndexReaderWriter.WriteResources(
output,
sources.Select(source => (source.Path, source.Size, (Func<Stream>) source.Open)).ToList());
sources.Select(source => new AvaloniaResourcesEntry
{
Path = source.Path,
Size = source.Size,
SystemPath = source.SystemPath,
Open = source.Open
}).ToList());
}
private bool PreProcessXamlFiles(List<Source> sources)

13
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Platform.Internal;
using Avalonia.Utilities;
using Mono.Cecil;
@ -67,11 +68,13 @@ namespace Avalonia.Build.Tasks
AvaloniaResourcesIndexReaderWriter.WriteResources(
output,
_resources.Select(x => (
Path: x.Key,
Size: x.Value.FileContents.Length,
Open: (Func<Stream>) (() => new MemoryStream(x.Value.FileContents))
)).ToList());
_resources.Select(x => new AvaloniaResourcesEntry
{
Path = x.Key,
Size = x.Value.FileContents.Length,
SystemPath = x.Value.FilePath,
Open = () => new MemoryStream(x.Value.FileContents)
}).ToList());
output.Position = 0L;
_embedded = new EmbeddedResource(Constants.AvaloniaResourceName, ManifestResourceAttributes.Public, output);

91
tests/Avalonia.Base.UnitTests/Utilities/AvaloniaResourcesIndexTests.cs

@ -0,0 +1,91 @@
using System;
using System.IO;
using System.Text;
using Avalonia.Utilities;
using Xunit;
namespace Avalonia.Base.UnitTests;
public class AvaloniaResourcesIndexTests
{
[Fact]
public void Should_Write_And_Read_The_Same_Resources()
{
using var memoryStream = new MemoryStream();
var fooBytes = Encoding.UTF8.GetBytes("foo");
var booBytes = Encoding.UTF8.GetBytes("boo");
AvaloniaResourcesIndexReaderWriter.WriteResources(memoryStream,
new[]
{
new AvaloniaResourcesEntry
{
Path = "foo.xaml", Size = fooBytes.Length, Open = () => new MemoryStream(fooBytes)
},
new AvaloniaResourcesEntry
{
Path = "boo.xaml", Size = booBytes.Length, Open = () => new MemoryStream(booBytes)
}
});
memoryStream.Seek(4, SeekOrigin.Begin); // skip 4 bytes for "index size" field.
var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(memoryStream);
var resourcesBasePosition = memoryStream.Position;
Span<byte> buffer = stackalloc byte[index[0].Size];
Assert.Equal("foo.xaml", index[0].Path);
Assert.Equal(0, index[0].Offset);
Assert.Equal(fooBytes.Length, index[0].Size);
memoryStream.Seek(resourcesBasePosition + index[0].Offset, SeekOrigin.Begin);
memoryStream.ReadExactly(buffer);
Assert.Equal(fooBytes, buffer.ToArray());
Assert.Equal("boo.xaml", index[1].Path);
Assert.Equal(fooBytes.Length, index[1].Offset);
Assert.Equal(booBytes.Length, index[1].Size);
memoryStream.Seek(resourcesBasePosition + index[1].Offset, SeekOrigin.Begin);
memoryStream.ReadExactly(buffer);
Assert.Equal(booBytes, buffer.ToArray());
}
[Fact]
public void Should_Combined_Same_Physical_Path_Resources()
{
using var memoryStream = new MemoryStream();
var resourceBytes = Encoding.UTF8.GetBytes("resource-data");
AvaloniaResourcesIndexReaderWriter.WriteResources(memoryStream, new[]
{
new AvaloniaResourcesEntry
{
Path = "app.xaml",
SystemPath = "app.ico",
Size = resourceBytes.Length,
Open = () => new MemoryStream(resourceBytes)
},
new AvaloniaResourcesEntry
{
Path = "!__AvaloniaDefaultWindowIcon",
SystemPath = "app.ico",
Size = resourceBytes.Length,
Open = () => new MemoryStream(resourceBytes)
}
});
memoryStream.Seek(4, SeekOrigin.Begin); // skip 4 bytes for "index size" field.
var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(memoryStream);
Assert.Equal("app.xaml", index[0].Path);
Assert.Equal(0, index[0].Offset);
Assert.Equal(resourceBytes.Length, index[0].Size);
Assert.Equal("!__AvaloniaDefaultWindowIcon", index[1].Path);
Assert.Equal(0, index[1].Offset);
Assert.Equal(resourceBytes.Length, index[1].Size);
}
}
Loading…
Cancel
Save