Files
RobustToolbox/Robust.Packaging/AssetProcessing/Passes/AssetPassPackRsis.cs
PJB3005 c4dff678a9 Make .rsic packing in asset packaging work
Finishing what I started a couple years ago, the packaging system now packages .rsi files into single .rsic files. This means a single .rsi "file" (1 + N files) becomes a single file when packaged.

This should improve performance on game startup, downloading, etc etc. The total file count for SS14 goes down from 30,000 to 6,000 (with the previous change for merging text files too).

Mostly just involved shuffling a bunch of the RSI loading code around so that it can be re-used for this purpose nicely. The original prototype in the code was copy-pasted, which obviously couldn't be relied upon.

This does mean that if you're loading an RSI's interior PNG directly via a texture path, that PNG will now be unavailable on packaged builds. To avoid this, you can set "rsic": false in the meta.json, so that it gets left alone by the pass.
2025-07-26 01:51:17 +02:00

142 lines
4.1 KiB
C#

using System.Text.RegularExpressions;
using Robust.Shared.Resources;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png.Chunks;
namespace Robust.Packaging.AssetProcessing.Passes;
// This is a proof of concept/example. The client is not currently able to load these.
/// <summary>
/// Packs .rsi bundles into .rsic files,
/// that are single pre-atlassed PNG files with JSON metadata embedded in the PNG header.
/// </summary>
public sealed class AssetPassPackRsis : AssetPass
{
private readonly Dictionary<string, RsiDat> _foundRsis = new();
private static readonly Regex RegexMetaJson = new(@"^(.+)\.rsi/meta\.json$");
private static readonly Regex RegexPng = new(@"^(.+)\.rsi/(.+)\.png$");
private readonly Configuration _imageConfiguration;
public AssetPassPackRsis()
{
_imageConfiguration = Configuration.Default.Clone();
_imageConfiguration.PreferContiguousImageBuffers = true;
}
protected override AssetFileAcceptResult AcceptFile(AssetFile file)
{
if (!file.Path.Contains(".rsi/"))
return AssetFileAcceptResult.Pass;
// .rsi/meta.json
var matchMetaJson = RegexMetaJson.Match(file.Path);
if (matchMetaJson.Success)
{
lock (_foundRsis)
{
var dat = _foundRsis.GetOrNew(matchMetaJson.Groups[1].Value);
dat.MetaJson = file;
}
return AssetFileAcceptResult.Consumed;
}
// .rsi/*.png
var matchPng = RegexPng.Match(file.Path);
if (matchPng.Success)
{
lock (_foundRsis)
{
var dat = _foundRsis.GetOrNew(matchPng.Groups[1].Value);
dat.StatesFound.Add(matchPng.Groups[2].Value, file);
return AssetFileAcceptResult.Consumed;
}
}
return AssetFileAcceptResult.Pass;
}
protected override void AcceptFinished()
{
// ReSharper disable once InconsistentlySynchronizedField
foreach (var (key, dat) in _foundRsis)
{
if (dat.MetaJson == null)
continue;
RunJob(() =>
{
// Console.WriteLine($"Packing RSI: {key}");
var result = PackRsi($"{key}.rsi", dat);
if (result == null)
{
// Don't rsic pack this one.
SkipRsiPack(dat);
return;
}
SendFile(result);
});
}
}
private void SkipRsiPack(RsiDat dat)
{
SendFile(dat.MetaJson!);
foreach (var file in dat.StatesFound.Values)
{
SendFile(file);
}
}
private AssetFile? PackRsi(string rsiPath, RsiDat dat)
{
RsiLoading.RsiMetadata metadata;
string metaJson;
using (var manifestFile = dat.MetaJson!.Open())
{
metadata = RsiLoading.LoadRsiMetadata(manifestFile);
manifestFile.Position = 0;
using var sr = new StreamReader(manifestFile);
metaJson = sr.ReadToEnd();
}
if (!metadata.Rsic)
return null;
var frameCounts = RsiLoading.CalculateFrameCounts(metadata);
var images = RsiLoading.LoadImages(metadata, _imageConfiguration, name => dat.StatesFound[name].Open());
try
{
using var sheet = RsiLoading.GenerateAtlas(metadata, frameCounts, images, _imageConfiguration, out _);
var ms = new MemoryStream();
sheet.Metadata.GetPngMetadata().TextData.Add(new PngTextData(RsiLoading.RsicPngField, metaJson, "", ""));
sheet.SaveAsPng(ms);
Logger?.Verbose($"Done packing {rsiPath}");
return new AssetFileMemory($"{rsiPath}c", ms.ToArray());
}
finally
{
foreach (var image in images)
{
image.Dispose();
}
}
}
private sealed class RsiDat
{
public AssetFile? MetaJson;
public readonly Dictionary<string, AssetFile> StatesFound = new();
}
}