Skip to content

Commit 11c4fe7

Browse files
authored
Merge pull request #9949 from MrJul/binary-resources-index
Made resources index binary
2 parents 876e46d + b8a4de9 commit 11c4fe7

File tree

5 files changed

+103
-98
lines changed

5 files changed

+103
-98
lines changed

src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public AssemblyDescriptor(Assembly assembly)
3232
Resources.Remove(Constants.AvaloniaResourceName);
3333

3434
var indexLength = new BinaryReader(resources).ReadInt32();
35-
var index = AvaloniaResourcesIndexReaderWriter.Read(new SlicedStream(resources, 4, indexLength));
35+
var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength));
3636
var baseOffset = indexLength + 4;
3737
AvaloniaResources = index.ToDictionary(r => GetPathRooted(r), r => (IAssetDescriptor)
3838
new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size));
Lines changed: 80 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,116 @@
11
using System;
22
using System.Collections.Generic;
33
using System.IO;
4-
using System.Runtime.Serialization;
5-
using System.Xml.Linq;
6-
using System.Linq;
7-
using System.Diagnostics.CodeAnalysis;
4+
using System.Text;
85

96
namespace Avalonia.Utilities
107
{
11-
#if !BUILDTASK
8+
#if !BUILDTASK
129
public
13-
#endif
10+
#endif
1411
static class AvaloniaResourcesIndexReaderWriter
1512
{
16-
private const int LastKnownVersion = 1;
17-
public static List<AvaloniaResourcesIndexEntry> Read(Stream stream)
13+
private const int XmlLegacyVersion = 1;
14+
private const int BinaryCurrentVersion = 2;
15+
16+
public static List<AvaloniaResourcesIndexEntry> ReadIndex(Stream stream)
1817
{
19-
var ver = new BinaryReader(stream).ReadInt32();
20-
if (ver > LastKnownVersion)
21-
throw new Exception("Resources index format version is not known");
22-
23-
var assetDoc = XDocument.Load(stream);
24-
XNamespace assetNs = assetDoc.Root!.Attribute("xmlns")!.Value;
25-
List<AvaloniaResourcesIndexEntry> entries =
26-
(from entry in assetDoc.Root.Element(assetNs + "Entries")!.Elements(assetNs + "AvaloniaResourcesIndexEntry")
27-
select new AvaloniaResourcesIndexEntry
28-
{
29-
Path = entry.Element(assetNs + "Path")!.Value,
30-
Offset = int.Parse(entry.Element(assetNs + "Offset")!.Value),
31-
Size = int.Parse(entry.Element(assetNs + "Size")!.Value)
32-
}).ToList();
18+
using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
3319

34-
return entries;
20+
var version = reader.ReadInt32();
21+
return version switch
22+
{
23+
XmlLegacyVersion => ReadXmlIndex(),
24+
BinaryCurrentVersion => ReadBinaryIndex(reader),
25+
_ => throw new Exception($"Unknown resources index format version {version}")
26+
};
3527
}
3628

37-
[RequiresUnreferencedCode("AvaloniaResources uses Data Contract Serialization, which might require unreferenced code")]
38-
public static void Write(Stream stream, List<AvaloniaResourcesIndexEntry> entries)
29+
private static List<AvaloniaResourcesIndexEntry> ReadXmlIndex()
30+
=> throw new NotSupportedException("Found legacy resources index format: please recompile your XAML files");
31+
32+
private static List<AvaloniaResourcesIndexEntry> ReadBinaryIndex(BinaryReader reader)
3933
{
40-
new BinaryWriter(stream).Write(LastKnownVersion);
41-
new DataContractSerializer(typeof(AvaloniaResourcesIndex)).WriteObject(stream,
42-
new AvaloniaResourcesIndex()
43-
{
44-
Entries = entries
34+
var entryCount = reader.ReadInt32();
35+
var entries = new List<AvaloniaResourcesIndexEntry>(entryCount);
36+
37+
for (var i = 0; i < entryCount; ++i)
38+
{
39+
entries.Add(new AvaloniaResourcesIndexEntry {
40+
Path = reader.ReadString(),
41+
Offset = reader.ReadInt32(),
42+
Size = reader.ReadInt32()
4543
});
44+
}
45+
46+
return entries;
4647
}
4748

48-
[RequiresUnreferencedCode("AvaloniaResources uses Data Contract Serialization, which might require unreferenced code")]
49-
public static byte[] Create(Dictionary<string, byte[]> data)
49+
public static void WriteIndex(Stream output, List<AvaloniaResourcesIndexEntry> entries)
5050
{
51-
var sources = data.ToList();
52-
var offsets = new Dictionary<string, int>();
53-
var coffset = 0;
54-
foreach (var s in sources)
51+
using var writer = new BinaryWriter(output, Encoding.UTF8, leaveOpen: true);
52+
53+
WriteIndex(writer, entries);
54+
}
55+
56+
private static void WriteIndex(BinaryWriter writer, List<AvaloniaResourcesIndexEntry> entries)
57+
{
58+
writer.Write(BinaryCurrentVersion);
59+
writer.Write(entries.Count);
60+
61+
foreach (var entry in entries)
5562
{
56-
offsets[s.Key] = coffset;
57-
coffset += s.Value.Length;
63+
writer.Write(entry.Path ?? string.Empty);
64+
writer.Write(entry.Offset);
65+
writer.Write(entry.Size);
5866
}
59-
var index = sources.Select(s => new AvaloniaResourcesIndexEntry
60-
{
61-
Path = s.Key,
62-
Size = s.Value.Length,
63-
Offset = offsets[s.Key]
64-
}).ToList();
65-
var output = new MemoryStream();
66-
var ms = new MemoryStream();
67-
AvaloniaResourcesIndexReaderWriter.Write(ms, index);
68-
new BinaryWriter(output).Write((int)ms.Length);
69-
ms.Position = 0;
70-
ms.CopyTo(output);
71-
foreach (var s in sources)
67+
}
68+
69+
public static void WriteResources(Stream output, List<(string Path, int Size, Func<Stream> Open)> resources)
70+
{
71+
var entries = new List<AvaloniaResourcesIndexEntry>(resources.Count);
72+
var offset = 0;
73+
74+
foreach (var resource in resources)
7275
{
73-
output.Write(s.Value,0,s.Value.Length);
76+
entries.Add(new AvaloniaResourcesIndexEntry
77+
{
78+
Path = resource.Path,
79+
Offset = offset,
80+
Size = resource.Size
81+
});
82+
offset += resource.Size;
7483
}
7584

76-
return output.ToArray();
77-
}
78-
}
85+
using var writer = new BinaryWriter(output, Encoding.UTF8, leaveOpen: true);
86+
writer.Write(0); // index size placeholder, overwritten below
7987

80-
[DataContract]
81-
#if !BUILDTASK
82-
public
83-
#endif
84-
class AvaloniaResourcesIndex
85-
{
86-
[DataMember]
87-
public List<AvaloniaResourcesIndexEntry> Entries { get; set; } = new List<AvaloniaResourcesIndexEntry>();
88+
var posBeforeEntries = output.Position;
89+
WriteIndex(writer, entries);
90+
91+
var posAfterEntries = output.Position;
92+
var indexSize = (int) (posAfterEntries - posBeforeEntries);
93+
output.Position = 0L;
94+
writer.Write(indexSize);
95+
output.Position = posAfterEntries;
96+
97+
foreach (var resource in resources)
98+
{
99+
using var resourceStream = resource.Open();
100+
resourceStream.CopyTo(output);
101+
}
102+
}
88103
}
89104

90-
[DataContract]
91105
#if !BUILDTASK
92106
public
93107
#endif
94108
class AvaloniaResourcesIndexEntry
95109
{
96-
[DataMember]
97110
public string? Path { get; set; }
98-
99-
[DataMember]
111+
100112
public int Offset { get; set; }
101-
102-
[DataMember]
113+
103114
public int Size { get; set; }
104115
}
105116
}

src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
<Compile Include="../Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs">
1919
<Link>Shared/AvaloniaResourcesIndex.cs</Link>
2020
</Compile>
21+
<Compile Include="../Avalonia.Base/Platform/Internal/Constants.cs">
22+
<Link>Shared/Constants.cs</Link>
23+
</Compile>
2124
<Compile Include="../Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaResourceXamlInfo.cs">
2225
<Link>Shared/AvaloniaResourceXamlInfo.cs</Link>
2326
</Compile>

src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -77,29 +77,9 @@ List<Source> BuildResourceSources()
7777

7878
private void Pack(Stream output, List<Source> sources)
7979
{
80-
var offsets = new Dictionary<Source, int>();
81-
var coffset = 0;
82-
foreach (var s in sources)
83-
{
84-
offsets[s] = coffset;
85-
coffset += s.Size;
86-
}
87-
var index = sources.Select(s => new AvaloniaResourcesIndexEntry
88-
{
89-
Path = s.Path,
90-
Size = s.Size,
91-
Offset = offsets[s]
92-
}).ToList();
93-
var ms = new MemoryStream();
94-
AvaloniaResourcesIndexReaderWriter.Write(ms, index);
95-
new BinaryWriter(output).Write((int)ms.Length);
96-
ms.Position = 0;
97-
ms.CopyTo(output);
98-
foreach (var s in sources)
99-
{
100-
using (var input = s.Open())
101-
input.CopyTo(output);
102-
}
80+
AvaloniaResourcesIndexReaderWriter.WriteResources(
81+
output,
82+
sources.Select(source => (source.Path, source.Size, (Func<Stream>) source.Open)).ToList());
10383
}
10484

10585
private bool PreProcessXamlFiles(List<Source> sources)

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.IO;
34
using System.Linq;
5+
using Avalonia.Platform.Internal;
46
using Avalonia.Utilities;
57
using Mono.Cecil;
68
using Mono.Cecil.Cil;
@@ -34,13 +36,13 @@ public AvaloniaResources(AssemblyDefinition asm, string projectDir)
3436
{
3537
_asm = asm;
3638
_embedded = ((EmbeddedResource)asm.MainModule.Resources.FirstOrDefault(r =>
37-
r.ResourceType == ResourceType.Embedded && r.Name == "!AvaloniaResources"));
39+
r.ResourceType == ResourceType.Embedded && r.Name == Constants.AvaloniaResourceName));
3840
if (_embedded == null)
3941
return;
4042
using (var stream = _embedded.GetResourceStream())
4143
{
4244
var br = new BinaryReader(stream);
43-
var index = AvaloniaResourcesIndexReaderWriter.Read(new MemoryStream(br.ReadBytes(br.ReadInt32())));
45+
var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new MemoryStream(br.ReadBytes(br.ReadInt32())));
4446
var baseOffset = stream.Position;
4547
foreach (var e in index)
4648
{
@@ -61,9 +63,18 @@ public void Save()
6163
if (_resources.Count == 0)
6264
return;
6365

64-
_embedded = new EmbeddedResource("!AvaloniaResources", ManifestResourceAttributes.Public,
65-
AvaloniaResourcesIndexReaderWriter.Create(_resources.ToDictionary(x => x.Key,
66-
x => x.Value.FileContents)));
66+
var output = new MemoryStream();
67+
68+
AvaloniaResourcesIndexReaderWriter.WriteResources(
69+
output,
70+
_resources.Select(x => (
71+
Path: x.Key,
72+
Size: x.Value.FileContents.Length,
73+
Open: (Func<Stream>) (() => new MemoryStream(x.Value.FileContents))
74+
)).ToList());
75+
76+
output.Position = 0L;
77+
_embedded = new EmbeddedResource(Constants.AvaloniaResourceName, ManifestResourceAttributes.Public, output);
6778
_asm.MainModule.Resources.Add(_embedded);
6879
}
6980

0 commit comments

Comments
 (0)