Skip to content

Commit

Permalink
Try to combine AvaloniaResources with the same system path (#15302)
Browse files Browse the repository at this point in the history
* Try to combine AvaloniaResources with the same system path

* Fix test assert
  • Loading branch information
maxkatz6 authored Apr 10, 2024
1 parent 3ea1b2e commit b5db6bb
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 15 deletions.
55 changes: 46 additions & 9 deletions src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Avalonia.Utilities
Expand Down Expand Up @@ -66,20 +67,45 @@ private static void WriteIndex(BinaryWriter writer, List<AvaloniaResourcesIndexE
}
}

[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);
Expand All @@ -94,9 +120,9 @@ public static void WriteResources(Stream output, List<(string Path, int Size, Fu
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);
}
}
Expand All @@ -113,4 +139,15 @@ class AvaloniaResourcesIndexEntry

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 changes: 7 additions & 1 deletion src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,13 @@ private void Pack(Stream output, List<Source> sources)
{
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)
Expand Down
13 changes: 8 additions & 5 deletions src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Platform.Internal;
using Avalonia.Utilities;
using Mono.Cecil;
Expand Down Expand Up @@ -67,11 +68,13 @@ public void Save()

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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit b5db6bb

Please sign in to comment.