using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
namespace FluidExplorer.Services.Search.Usn;
///
/// 索引的紧凑存储:结构体数组(Struct-of-Arrays)而不是对象数组。
///
/// 每条记录只占 45 字节(8+8+8+8+8+4+1),100 万条约 45MB,
/// 再加上名字池(约 2 字节/字符)就构成整个索引;绝无 per-entry 的对象头与 string。
///
/// 并发模型(读多写极少):
/// * 用 volatile 发布:写入方先写满数据,最后自增 Count;读方只需读一次 Count 再顺序访问。
/// * 结构只增不减(删除只打墓碑标志位),因此已经发布的 [0, Count) 区间永远有效。
/// * 容量不足时 采用 copy-on-write,整体替换数组;老快照对正在查询的线程依然合法。
///
internal sealed class IndexStore
{
internal const byte FlagDeleted = 0x01;
internal const byte FlagDirectory = 0x02;
internal const byte FlagSizeKnown = 0x04;
internal readonly ulong[] Frn; // 归一化 FRN(低 48 位记录号)
internal readonly ulong[] ParentFrn; // 归一化父目录 FRN
internal readonly long[] NameRef; // NamePool.Pack(偏移, 长度)
internal readonly long[] Size; // -1 = 未知(USN 降级模式)
internal readonly long[] ModifiedTicks; // UTC ticks;0 = 未知
internal readonly uint[] Attributes; // FILE_ATTRIBUTE_*
internal readonly byte[] Flags;
///
/// 名字池与数组快照绑定在一起:查询只需抓取一次 store 引用就能得到一致的 (名字池, 数组, 条数)。
/// 关键:扩容()必须复用同一个名字池 —— NameRef 里存的是池内偏移,
/// 换池等于让所有老记录的名字全部失效。
///
internal readonly NamePool Names;
internal readonly int Capacity;
/// 已发布条数。写入方必须在写完所有字段之后再自增它。
internal volatile int Count;
internal IndexStore(int capacity) : this(capacity, new NamePool())
{
}
private IndexStore(int capacity, NamePool names)
{
if (capacity < 16) capacity = 16;
Capacity = capacity;
Names = names;
Frn = new ulong[capacity];
ParentFrn = new ulong[capacity];
NameRef = new long[capacity];
Size = new long[capacity];
ModifiedTicks = new long[capacity];
Attributes = new uint[capacity];
Flags = new byte[capacity];
}
private IndexStore(IndexStore old, int capacity) : this(capacity, old.Names)
{
int n = old.Count;
Array.Copy(old.Frn, Frn, n);
Array.Copy(old.ParentFrn, ParentFrn, n);
Array.Copy(old.NameRef, NameRef, n);
Array.Copy(old.Size, Size, n);
Array.Copy(old.ModifiedTicks, ModifiedTicks, n);
Array.Copy(old.Attributes, Attributes, n);
Array.Copy(old.Flags, Flags, n);
Count = n;
}
/// 扩容(copy-on-write)。返回新快照,旧快照仍然可被并发查询安全使用。
internal static IndexStore Grow(IndexStore old, int minCapacity)
{
int capacity = old.Capacity;
while (capacity < minCapacity) capacity = capacity < 1024 ? capacity * 2 : capacity + (capacity >> 1);
return new IndexStore(old, capacity);
}
internal bool IsDeleted(int i) => (Flags[i] & FlagDeleted) != 0;
internal bool IsDirectory(int i) => (Flags[i] & FlagDirectory) != 0;
/// 读名字(零分配)。
internal ReadOnlySpan GetName(int i)
{
long nameRef = Volatile.Read(ref NameRef[i]);
return Names.Get(NamePool.UnpackOffset(nameRef), NamePool.UnpackLength(nameRef));
}
}
///
/// FRN(低 48 位记录号)→ 索引下标的映射。
///
/// 策略:构建期用普通 Dictionary 最快;构建结束后调用 ,
/// 如果记录号足够密集(maxRecord <= 8 * count),就换成“记录号直接寻址”的 int[],
/// 100 万文件只占几 MB(而 Dictionary 要 30~40MB)。
/// 稀疏卷或监听期新增的越界记录号退回到 侧表。
///
internal sealed class FrnMap
{
private const int NoIndex = 0;
private const int DenseSlack = 8;
private Dictionary? _buildMap;
private int[]? _dense; // 记录号 → 下标+1;0 表示不存在
private ConcurrentDictionary? _sparse;
internal FrnMap(int estimatedCount)
{
_buildMap = new Dictionary(Math.Max(16, estimatedCount));
}
internal int Count => _buildMap?.Count ?? _sparse?.Count ?? _dense?.Length ?? 0;
/// 构建期写入(单线程,无锁,最快)。
internal void AddBuild(ulong frn, int index)
{
_buildMap![UsnNative.NormalizeFrn(frn)] = index;
}
/// 构建完成后调用:把构建期的 Dictionary 压缩成密集数组或稀疏侧表。
internal void Optimize()
{
var map = _buildMap ?? throw new InvalidOperationException("FrnMap 已经压缩过。");
_buildMap = null;
ulong maxRecord = 0;
foreach (var key in map.Keys)
{
if (key > maxRecord) maxRecord = key;
}
// 密集数组代价 = (maxRecord+1)*4 字节;只要不超过 8 个记录号/条目的开销就值得。
if (map.Count > 0 && maxRecord <= (ulong)map.Count * DenseSlack)
{
var dense = new int[maxRecord + 1];
foreach (var (key, value) in map)
{
dense[key] = value + 1;
}
Volatile.Write(ref _dense, dense);
return;
}
var sparse = new ConcurrentDictionary();
foreach (var (key, value) in map)
{
sparse[key] = value;
}
Volatile.Write(ref _sparse, sparse);
}
/// 监听期写入(可能并发于查询,必须线程安全)。
internal void Set(ulong frn, int index)
{
ulong record = UsnNative.NormalizeFrn(frn);
var dense = Volatile.Read(ref _dense);
if (dense is not null && record < (ulong)dense.Length)
{
Volatile.Write(ref dense[record], index + 1);
return;
}
var sparse = _sparse;
if (sparse is null)
{
var created = new ConcurrentDictionary();
sparse = Interlocked.CompareExchange(ref _sparse, created, null) ?? created;
}
sparse[record] = index;
}
/// 批量覆盖(重建索引时用,单线程)。
internal void SetUnsafe(ulong frn, int index)
{
ulong record = UsnNative.NormalizeFrn(frn);
var dense = _dense;
if (dense is not null && record < (ulong)dense.Length)
{
dense[record] = index + 1;
return;
}
_sparse![record] = index;
}
internal bool TryGet(ulong frn, out int index)
{
ulong record = UsnNative.NormalizeFrn(frn);
var dense = Volatile.Read(ref _dense);
if (dense is not null && record < (ulong)dense.Length)
{
int slot = Volatile.Read(ref dense[record]);
if (slot == NoIndex)
{
index = -1;
return false;
}
index = slot - 1;
return true;
}
var sparse = Volatile.Read(ref _sparse);
if (sparse is not null && sparse.TryGetValue(record, out index)) return true;
index = -1;
return false;
}
/// NormizeFrn 的别名,便于调用点表达意图。
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ulong Normalize(ulong frn) => UsnNative.NormalizeFrn(frn);
}