mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-01 07:29:43 +08:00
Ampr: cooked-id index preload and host FD LRU cache (#725)
Preload the app0 APR index during bind and bound the open host-file cache so streaming reads stay responsive under large title archives.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.Libs.Ampr;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Libs.Tests.Ampr;
|
||||
|
||||
public class AmprFileRegistryTests
|
||||
{
|
||||
[Fact]
|
||||
public void ComputeFileId_matches_utf8_fnv1a()
|
||||
{
|
||||
const string relative = "CoreData/foo/bar.bin";
|
||||
Assert.Equal(FnvUtf8("$/" + relative), AmprFileRegistry.ComputeFileId("$/" + relative));
|
||||
Assert.Equal(FnvUtf8("/app0/" + relative), AmprFileRegistry.ComputeFileId("/app0/" + relative));
|
||||
Assert.Equal(FnvUtf8("app0/" + relative), AmprFileRegistry.ComputeFileId("app0/" + relative));
|
||||
Assert.Equal(FnvUtf8(relative), AmprFileRegistry.ComputeFileId(relative));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegisterApp0Relative_publishes_same_ids_as_string_hashes()
|
||||
{
|
||||
AmprFileRegistry.ClearForTests();
|
||||
const string relative = "misc/loadouts/test.txt";
|
||||
var host = Path.Combine(Path.GetTempPath(), "sharpemu-ampr-test", relative);
|
||||
AmprFileRegistry.RegisterApp0RelativeForTests(relative, host);
|
||||
|
||||
Assert.True(AmprFileRegistry.TryGetHostPath(
|
||||
AmprFileRegistry.ComputeFileId("$/" + relative), out var a));
|
||||
Assert.True(AmprFileRegistry.TryGetHostPath(
|
||||
AmprFileRegistry.ComputeFileId("/app0/" + relative), out var b));
|
||||
Assert.True(AmprFileRegistry.TryGetHostPath(
|
||||
AmprFileRegistry.ComputeFileId("app0/" + relative), out var c));
|
||||
Assert.True(AmprFileRegistry.TryGetHostPath(
|
||||
AmprFileRegistry.ComputeFileId(relative), out var d));
|
||||
Assert.Equal(host, a);
|
||||
Assert.Equal(host, b);
|
||||
Assert.Equal(host, c);
|
||||
Assert.Equal(host, d);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Register_publishes_all_app0_path_aliases()
|
||||
{
|
||||
AmprFileRegistry.ClearForTests();
|
||||
const string relative = "scripts/cp11/cp11main.script";
|
||||
var host = Path.Combine(Path.GetTempPath(), "sharpemu-ampr-test2", relative);
|
||||
AmprFileRegistry.Register("$/" + relative, host);
|
||||
|
||||
Assert.True(AmprFileRegistry.TryGetHostPath(
|
||||
AmprFileRegistry.ComputeFileId("$/" + relative), out var a));
|
||||
Assert.True(AmprFileRegistry.TryGetHostPath(
|
||||
AmprFileRegistry.ComputeFileId("/app0/" + relative), out var b));
|
||||
Assert.True(AmprFileRegistry.TryGetHostPath(
|
||||
AmprFileRegistry.ComputeFileId("app0/" + relative), out var c));
|
||||
Assert.True(AmprFileRegistry.TryGetHostPath(
|
||||
AmprFileRegistry.ComputeFileId(relative), out var d));
|
||||
Assert.Equal(host, a);
|
||||
Assert.Equal(host, b);
|
||||
Assert.Equal(host, c);
|
||||
Assert.Equal(host, d);
|
||||
}
|
||||
|
||||
private static uint FnvUtf8(string text)
|
||||
{
|
||||
const uint offset = 2166136261;
|
||||
const uint prime = 16777619;
|
||||
var hash = offset;
|
||||
foreach (var b in System.Text.Encoding.UTF8.GetBytes(text))
|
||||
{
|
||||
hash ^= b;
|
||||
hash *= prime;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
@@ -318,6 +318,53 @@ public sealed class AprStreamingContractTests
|
||||
return BinaryPrimitives.ReadUInt32LittleEndian(bytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Register_AlsoPublishesApp0AndDollarPathAliases()
|
||||
{
|
||||
// Resolve may register "$/asset.bin" while cooked tables look up
|
||||
// FNV("/app0/asset.bin"). Both ids must map to the same host file.
|
||||
var hostPath = Path.Combine(Path.GetTempPath(), $"sharpemu-apr-alias-{Guid.NewGuid():N}.bin");
|
||||
File.WriteAllBytes(hostPath, [1, 2, 3]);
|
||||
try
|
||||
{
|
||||
var dollarId = AmprFileRegistry.Register("$/weapons/demo.cani", hostPath);
|
||||
Assert.True(AmprFileRegistry.TryGetHostPath(dollarId, out var viaDollar));
|
||||
Assert.Equal(hostPath, viaDollar);
|
||||
|
||||
var app0Id = AmprFileRegistry.ComputeFileId("/app0/weapons/demo.cani");
|
||||
Assert.NotEqual(dollarId, app0Id);
|
||||
Assert.True(AmprFileRegistry.TryGetHostPath(app0Id, out var viaApp0));
|
||||
Assert.Equal(hostPath, viaApp0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(hostPath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureApp0Indexed_PublishesCookedApp0FileIds()
|
||||
{
|
||||
var mountRoot = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
$"sharpemu-apr-index-{Guid.NewGuid():N}");
|
||||
var relativeDir = Path.Combine(mountRoot, "weapons");
|
||||
Directory.CreateDirectory(relativeDir);
|
||||
var hostPath = Path.Combine(relativeDir, "demo.cani");
|
||||
File.WriteAllBytes(hostPath, [9, 8, 7]);
|
||||
try
|
||||
{
|
||||
AmprFileRegistry.EnsureApp0Indexed(mountRoot);
|
||||
var cookedId = AmprFileRegistry.ComputeFileId("/app0/weapons/demo.cani");
|
||||
Assert.True(AmprFileRegistry.TryGetHostPath(cookedId, out var resolved));
|
||||
Assert.Equal(Path.GetFullPath(hostPath), Path.GetFullPath(resolved));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(mountRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong ReadUInt64(FakeCpuMemory memory, ulong address)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[sizeof(ulong)];
|
||||
|
||||
Reference in New Issue
Block a user