Initial commit: FluidExplorer:从零实现的 WinUI 3 文件资源管理器替代品(Mica、命令栏、面包屑)

This commit is contained in:
WpyQwq
2026-09-19 11:54:03 +08:00
commit 5780fde61a
60 changed files with 15035 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
namespace FluidExplorer.Services.Search.Usn;
/// <summary>
/// 紧凑文件名池 —— 索引“快且省内存”的关键之一。
///
/// 所有文件名以 UTF-16 连续存放在若干 <b>定长块</b>(每块 1&lt;&lt;20 个字符 = 2MB)里,
/// 整个索引里不会为文件名产生任何 string 对象;查询时直接在这块内存上取 ReadOnlySpan&lt;char&gt;。
///
/// 偏移编码:<c>(chunkIndex &lt;&lt; 20) | offsetInChunk</c>,单个 int 即可寻址 2G 字符。
/// 因为块大小固定为 1&lt;&lt;20,而 NTFS 单个文件名最长 255 字符,
/// 所以一个名字永远不会跨越两个块 —— 取值时无需拼接。
///
/// 线程安全:块数组一次性预分配(只写指针不扩容),块本身写入后用 <see cref="Volatile.Write{T}(ref T, T)"/> 发布。
/// 查询线程只会读到“已经完整写入”的块;配合索引条数的发布顺序(先写数据、后写 Count),读侧无需加锁。
/// </summary>
internal sealed class NamePool
{
internal const int ChunkShift = 20;
internal const int ChunkSize = 1 << ChunkShift;
internal const int ChunkMask = ChunkSize - 1;
/// <summary>NameRef 只用 8 位存长度,NTFS 文件名最长 255 字符,正好用满。</summary>
internal const int MaxNameLength = 255;
private const int MaxChunks = 2048; // 2048 * 1M = 2G 字符上限
private readonly char[][] _chunks = new char[MaxChunks][];
private int _chunkCount;
private int _fill; // 当前块已使用字符数
private int _publishedChars;
internal NamePool()
{
AddChunk();
}
/// <summary>池中已发布的字符总数(约等于所有名字长度之和)。</summary>
internal int TotalChars => _publishedChars;
/// <summary>写入一个名字,返回它的池内偏移。空名字返回 0(配合 NameRef 的长度字段仍然无歧义)。</summary>
internal int Add(ReadOnlySpan<char> name)
{
if (name.Length == 0) return 0;
if (name.Length > ChunkSize) name = name[..ChunkSize];
if (_fill + name.Length > ChunkSize) AddChunk();
int chunkIndex = _chunkCount - 1;
int offset = (chunkIndex << ChunkShift) | _fill;
name.CopyTo(_chunks[chunkIndex].AsSpan(_fill));
_fill += name.Length;
_publishedChars = (chunkIndex << ChunkShift) + _fill;
return offset;
}
/// <summary>取回名字。参数非法时返回空 span(绝不抛异常,读侧可能并发读到正在更新的旧值)。</summary>
internal ReadOnlySpan<char> Get(int offset, int length)
{
if (length <= 0 || offset < 0) return default;
int chunkIndex = offset >> ChunkShift;
int inChunk = offset & ChunkMask;
if (chunkIndex >= _chunkCount) return default;
var chunk = Volatile.Read(ref _chunks[chunkIndex]);
if (chunk is null || inChunk + length > ChunkSize) return default;
return chunk.AsSpan(inChunk, length);
}
private void AddChunk()
{
if (_chunkCount >= MaxChunks) throw new InvalidOperationException("文件名池已达到容量上限(2G 字符)。");
var chunk = new char[ChunkSize];
Volatile.Write(ref _chunks[_chunkCount], chunk);
_chunkCount++;
_fill = 0;
}
// ---------------------------------------------------------------- NameRef 打包
/// <summary>把 (偏移, 长度) 打包进一个 long:低 8 位是长度,高 56 位是偏移。</summary>
internal static long Pack(int offset, int length) => ((long)offset << 8) | (uint)(length & 0xFF);
internal static int UnpackOffset(long nameRef) => (int)(nameRef >> 8);
internal static int UnpackLength(long nameRef) => (int)(nameRef & 0xFF);
}