anshdadhich commited on
Commit
8899a5e
·
verified ·
1 Parent(s): 8c7599c

Upload FastSeekWpf/Core/IndexStore.cs

Browse files
Files changed (1) hide show
  1. FastSeekWpf/Core/IndexStore.cs +100 -68
FastSeekWpf/Core/IndexStore.cs CHANGED
@@ -1,12 +1,7 @@
1
  using System;
2
- using System.Buffers;
3
  using System.Collections.Generic;
4
- using System.IO;
5
  using System.Linq;
6
- using System.Runtime.InteropServices;
7
  using System.Text;
8
- using System.Text.Json;
9
- using System.Text.Json.Serialization;
10
 
11
  namespace FastSeekWpf.Core;
12
 
@@ -41,26 +36,33 @@ public struct IndexEntry
41
  public readonly FileKind Kind => IsDir ? FileKind.Directory : FileKind.File;
42
  }
43
 
 
 
 
 
 
 
44
  public class IndexStore
45
  {
46
  public List<IndexEntry> Entries = new();
47
- public byte[] NameArena = Array.Empty<byte>();
48
- public byte[] NameLowerArena = Array.Empty<byte>();
49
- public List<(ulong fileRef, uint idx)> RefLookup = new();
 
 
 
 
 
50
  public string DriveRoot = string.Empty;
51
  public List<JournalCheckpoint> Checkpoints = new();
52
 
53
  public int Count => Entries.Count;
54
 
55
- public string Name(IndexEntry e)
56
- {
57
- return Encoding.UTF8.GetString(NameArena, (int)e.NameOff, e.NameLen);
58
- }
59
 
60
- public string NameLower(IndexEntry e)
61
- {
62
- return Encoding.UTF8.GetString(NameLowerArena, (int)e.NameLowerOff, e.NameLowerLen);
63
- }
64
 
65
  public uint? LookupIdx(ulong fileRef)
66
  {
@@ -68,8 +70,8 @@ public class IndexStore
68
  while (lo <= hi)
69
  {
70
  int mid = (lo + hi) >>> 1;
71
- var (r, _) = RefLookup[mid];
72
- if (r == fileRef) return RefLookup[mid].idx;
73
  if (r < fileRef) lo = mid + 1;
74
  else hi = mid - 1;
75
  }
@@ -80,8 +82,8 @@ public class IndexStore
80
  {
81
  RefLookup.Clear();
82
  RefLookup.Capacity = Entries.Count;
83
- for (uint i = 0; i < Entries.Count; i++)
84
- RefLookup.Add((Entries[(int)i].FileRef, i));
85
  RefLookup.Sort((a, b) => a.fileRef.CompareTo(b.fileRef));
86
  }
87
 
@@ -89,9 +91,16 @@ public class IndexStore
89
  {
90
  DriveRoot = driveRoot;
91
  int count = scan.Records.Count;
 
92
  Entries.Capacity = count;
93
- var nameArenaList = new List<byte>(count * 30);
94
- var nameLowerList = new List<byte>(count * 30);
 
 
 
 
 
 
95
 
96
  foreach (var r in scan.Records)
97
  {
@@ -102,13 +111,13 @@ public class IndexStore
102
  byte[] nameBytes = Encoding.UTF8.GetBytes(name);
103
  byte[] lowerBytes = Encoding.UTF8.GetBytes(nameLower);
104
 
105
- uint nOff = (uint)nameArenaList.Count;
106
  ushort nLen = (ushort)nameBytes.Length;
107
- nameArenaList.AddRange(nameBytes);
108
 
109
- uint nlOff = (uint)nameLowerList.Count;
110
  ushort nlLen = (ushort)lowerBytes.Length;
111
- nameLowerList.AddRange(lowerBytes);
112
 
113
  Entries.Add(new IndexEntry
114
  {
@@ -120,20 +129,32 @@ public class IndexStore
120
  NameLowerLen = nlLen,
121
  Flags = r.IsDir ? (byte)1 : (byte)0
122
  });
123
- }
124
 
125
- NameArena = nameArenaList.ToArray();
126
- NameLowerArena = nameLowerList.ToArray();
 
127
  }
128
 
129
  public void Finalize()
130
  {
131
- Entries.Sort((a, b) =>
 
 
 
 
 
 
 
 
132
  {
133
- var sa = NameLower(a);
134
- var sb = NameLower(b);
135
- return string.CompareOrdinal(sa, sb);
136
- });
 
 
 
 
137
  RebuildRefLookup();
138
  }
139
 
@@ -141,11 +162,11 @@ public class IndexStore
141
  {
142
  return new CacheData
143
  {
144
- Entries = Entries.Select(e => new CachedEntry
145
  {
146
  FileRef = e.FileRef,
147
  ParentRef = e.ParentRef,
148
- Name = Name(e),
149
  Kind = e.Kind
150
  }).ToList(),
151
  DriveRoot = DriveRoot,
@@ -162,9 +183,8 @@ public class IndexStore
162
  Checkpoints = new List<JournalCheckpoint>(cache.Checkpoints)
163
  };
164
  store.Entries.Capacity = count;
165
-
166
- var nameArenaList = new List<byte>(count * 30);
167
- var nameLowerList = new List<byte>(count * 30);
168
 
169
  foreach (var c in cache.Entries)
170
  {
@@ -173,13 +193,13 @@ public class IndexStore
173
  byte[] nameBytes = Encoding.UTF8.GetBytes(c.Name);
174
  byte[] lowerBytes = Encoding.UTF8.GetBytes(nameLower);
175
 
176
- uint nOff = (uint)nameArenaList.Count;
177
  ushort nLen = (ushort)nameBytes.Length;
178
- nameArenaList.AddRange(nameBytes);
179
 
180
- uint nlOff = (uint)nameLowerList.Count;
181
  ushort nlLen = (ushort)lowerBytes.Length;
182
- nameLowerList.AddRange(lowerBytes);
183
 
184
  store.Entries.Add(new IndexEntry
185
  {
@@ -191,10 +211,11 @@ public class IndexStore
191
  NameLowerLen = nlLen,
192
  Flags = c.Kind == FileKind.Directory ? (byte)1 : (byte)0
193
  });
 
 
 
194
  }
195
 
196
- store.NameArena = nameArenaList.ToArray();
197
- store.NameLowerArena = nameLowerList.ToArray();
198
  store.RebuildRefLookup();
199
  return store;
200
  }
@@ -206,18 +227,13 @@ public class IndexStore
206
  var nameBytes = Encoding.UTF8.GetBytes(record.Name);
207
  var lowerBytes = Encoding.UTF8.GetBytes(nameLower);
208
 
209
- var nameArenaList = new List<byte>(NameArena.Length + nameBytes.Length + 64);
210
- nameArenaList.AddRange(NameArena);
211
- var nameLowerList = new List<byte>(NameLowerArena.Length + lowerBytes.Length + 64);
212
- nameLowerList.AddRange(NameLowerArena);
213
-
214
- uint nOff = (uint)nameArenaList.Count;
215
  ushort nLen = (ushort)nameBytes.Length;
216
- nameArenaList.AddRange(nameBytes);
217
 
218
- uint nlOff = (uint)nameLowerList.Count;
219
  ushort nlLen = (ushort)lowerBytes.Length;
220
- nameLowerList.AddRange(lowerBytes);
221
 
222
  var entry = new IndexEntry
223
  {
@@ -230,22 +246,37 @@ public class IndexStore
230
  Flags = record.Kind == FileKind.Directory ? (byte)1 : (byte)0
231
  };
232
 
 
233
  int pos = 0;
234
  for (; pos < Entries.Count; pos++)
235
  {
236
- if (string.CompareOrdinal(nameLower, NameLower(Entries[pos])) < 0)
237
  break;
238
  }
239
  Entries.Insert(pos, entry);
 
 
240
 
241
- NameArena = nameArenaList.ToArray();
242
- NameLowerArena = nameLowerList.ToArray();
243
  RebuildRefLookup();
244
  }
245
 
246
  public void Remove(ulong fileRef)
247
  {
248
- Entries.RemoveAll(e => e.FileRef == fileRef);
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  RebuildRefLookup();
250
  }
251
 
@@ -269,7 +300,9 @@ public class IndexStore
269
 
270
  public string BuildPath(ulong fileRef)
271
  {
272
- var components = new List<string>(16);
 
 
273
  ulong current = fileRef;
274
 
275
  for (int i = 0; i < 64; i++)
@@ -278,20 +311,19 @@ public class IndexStore
278
  if (idx == null) break;
279
 
280
  var entry = Entries[(int)idx];
281
- components.Add(Name(entry));
282
  if (entry.ParentRef == current) break;
283
  current = entry.ParentRef;
284
  }
285
 
286
- components.Reverse();
287
- var path = new StringBuilder(DriveRoot.Length + components.Sum(c => c.Length + 1));
288
- path.Append(DriveRoot);
289
- foreach (var comp in components)
290
  {
291
- if (path.Length > 0 && path[path.Length - 1] != '\\' && path[path.Length - 1] != '/')
292
- path.Append('\\');
293
- path.Append(comp);
294
  }
295
- return path.ToString();
296
  }
297
  }
 
1
  using System;
 
2
  using System.Collections.Generic;
 
3
  using System.Linq;
 
4
  using System.Text;
 
 
5
 
6
  namespace FastSeekWpf.Core;
7
 
 
36
  public readonly FileKind Kind => IsDir ? FileKind.Directory : FileKind.File;
37
  }
38
 
39
+ /// <summary>
40
+ /// High-performance index store matching the Rust design:
41
+ /// - Arena-backed name storage (List&lt;byte&gt; for O(1) append)
42
+ /// - Parallel cached lowercase strings for zero-allocation search
43
+ /// - Sorted ref_lookup for O(log n) parent lookups
44
+ /// </summary>
45
  public class IndexStore
46
  {
47
  public List<IndexEntry> Entries = new();
48
+ // Arena stores UTF-8 bytes
49
+ public List<byte> NameArena = new();
50
+ public List<byte> NameLowerArena = new();
51
+ // Parallel string cache for hot-path search (no UTF8 decoding during search)
52
+ public List<string> NameCache = new();
53
+ public List<string> NameLowerCache = new();
54
+ // Sorted by file_ref for binary search
55
+ public List<(ulong fileRef, int idx)> RefLookup = new();
56
  public string DriveRoot = string.Empty;
57
  public List<JournalCheckpoint> Checkpoints = new();
58
 
59
  public int Count => Entries.Count;
60
 
61
+ // ---- Hot path: cached name access (zero-allocation) ----
 
 
 
62
 
63
+ public string Name(IndexEntry e) => NameCache[Entries.IndexOf(e)];
64
+ public string NameAt(int i) => NameCache[i];
65
+ public string NameLowerAt(int i) => NameLowerCache[i];
 
66
 
67
  public uint? LookupIdx(ulong fileRef)
68
  {
 
70
  while (lo <= hi)
71
  {
72
  int mid = (lo + hi) >>> 1;
73
+ var r = RefLookup[mid].fileRef;
74
+ if (r == fileRef) return (uint)RefLookup[mid].idx;
75
  if (r < fileRef) lo = mid + 1;
76
  else hi = mid - 1;
77
  }
 
82
  {
83
  RefLookup.Clear();
84
  RefLookup.Capacity = Entries.Count;
85
+ for (int i = 0; i < Entries.Count; i++)
86
+ RefLookup.Add((Entries[i].FileRef, i));
87
  RefLookup.Sort((a, b) => a.fileRef.CompareTo(b.fileRef));
88
  }
89
 
 
91
  {
92
  DriveRoot = driveRoot;
93
  int count = scan.Records.Count;
94
+ Entries.Clear();
95
  Entries.Capacity = count;
96
+ NameArena.Clear();
97
+ NameArena.Capacity = count * 30;
98
+ NameLowerArena.Clear();
99
+ NameLowerArena.Capacity = count * 30;
100
+ NameCache.Clear();
101
+ NameCache.Capacity = count;
102
+ NameLowerCache.Clear();
103
+ NameLowerCache.Capacity = count;
104
 
105
  foreach (var r in scan.Records)
106
  {
 
111
  byte[] nameBytes = Encoding.UTF8.GetBytes(name);
112
  byte[] lowerBytes = Encoding.UTF8.GetBytes(nameLower);
113
 
114
+ uint nOff = (uint)NameArena.Count;
115
  ushort nLen = (ushort)nameBytes.Length;
116
+ NameArena.AddRange(nameBytes);
117
 
118
+ uint nlOff = (uint)NameLowerArena.Count;
119
  ushort nlLen = (ushort)lowerBytes.Length;
120
+ NameLowerArena.AddRange(lowerBytes);
121
 
122
  Entries.Add(new IndexEntry
123
  {
 
129
  NameLowerLen = nlLen,
130
  Flags = r.IsDir ? (byte)1 : (byte)0
131
  });
 
132
 
133
+ NameCache.Add(name);
134
+ NameLowerCache.Add(nameLower);
135
+ }
136
  }
137
 
138
  public void Finalize()
139
  {
140
+ // Sort by lowercase name for search
141
+ var indices = Enumerable.Range(0, Entries.Count).ToArray();
142
+ Array.Sort(indices, (a, b) => string.CompareOrdinal(NameLowerCache[a], NameLowerCache[b]));
143
+
144
+ var sortedEntries = new List<IndexEntry>(Entries.Count);
145
+ var sortedNames = new List<string>(Entries.Count);
146
+ var sortedLower = new List<string>(Entries.Count);
147
+
148
+ foreach (var i in indices)
149
  {
150
+ sortedEntries.Add(Entries[i]);
151
+ sortedNames.Add(NameCache[i]);
152
+ sortedLower.Add(NameLowerCache[i]);
153
+ }
154
+
155
+ Entries = sortedEntries;
156
+ NameCache = sortedNames;
157
+ NameLowerCache = sortedLower;
158
  RebuildRefLookup();
159
  }
160
 
 
162
  {
163
  return new CacheData
164
  {
165
+ Entries = Entries.Select((e, i) => new CachedEntry
166
  {
167
  FileRef = e.FileRef,
168
  ParentRef = e.ParentRef,
169
+ Name = NameAt(i),
170
  Kind = e.Kind
171
  }).ToList(),
172
  DriveRoot = DriveRoot,
 
183
  Checkpoints = new List<JournalCheckpoint>(cache.Checkpoints)
184
  };
185
  store.Entries.Capacity = count;
186
+ store.NameCache.Capacity = count;
187
+ store.NameLowerCache.Capacity = count;
 
188
 
189
  foreach (var c in cache.Entries)
190
  {
 
193
  byte[] nameBytes = Encoding.UTF8.GetBytes(c.Name);
194
  byte[] lowerBytes = Encoding.UTF8.GetBytes(nameLower);
195
 
196
+ uint nOff = (uint)store.NameArena.Count;
197
  ushort nLen = (ushort)nameBytes.Length;
198
+ store.NameArena.AddRange(nameBytes);
199
 
200
+ uint nlOff = (uint)store.NameLowerArena.Count;
201
  ushort nlLen = (ushort)lowerBytes.Length;
202
+ store.NameLowerArena.AddRange(lowerBytes);
203
 
204
  store.Entries.Add(new IndexEntry
205
  {
 
211
  NameLowerLen = nlLen,
212
  Flags = c.Kind == FileKind.Directory ? (byte)1 : (byte)0
213
  });
214
+
215
+ store.NameCache.Add(c.Name);
216
+ store.NameLowerCache.Add(nameLower);
217
  }
218
 
 
 
219
  store.RebuildRefLookup();
220
  return store;
221
  }
 
227
  var nameBytes = Encoding.UTF8.GetBytes(record.Name);
228
  var lowerBytes = Encoding.UTF8.GetBytes(nameLower);
229
 
230
+ uint nOff = (uint)NameArena.Count;
 
 
 
 
 
231
  ushort nLen = (ushort)nameBytes.Length;
232
+ NameArena.AddRange(nameBytes);
233
 
234
+ uint nlOff = (uint)NameLowerArena.Count;
235
  ushort nlLen = (ushort)lowerBytes.Length;
236
+ NameLowerArena.AddRange(lowerBytes);
237
 
238
  var entry = new IndexEntry
239
  {
 
246
  Flags = record.Kind == FileKind.Directory ? (byte)1 : (byte)0
247
  };
248
 
249
+ // Insert in sorted position by lowercase name
250
  int pos = 0;
251
  for (; pos < Entries.Count; pos++)
252
  {
253
+ if (string.CompareOrdinal(nameLower, NameLowerAt(pos)) < 0)
254
  break;
255
  }
256
  Entries.Insert(pos, entry);
257
+ NameCache.Insert(pos, record.Name);
258
+ NameLowerCache.Insert(pos, nameLower);
259
 
260
+ // Rebuild lookup (rare operation, acceptable)
 
261
  RebuildRefLookup();
262
  }
263
 
264
  public void Remove(ulong fileRef)
265
  {
266
+ int idx = -1;
267
+ for (int i = 0; i < Entries.Count; i++)
268
+ {
269
+ if (Entries[i].FileRef == fileRef)
270
+ {
271
+ idx = i;
272
+ break;
273
+ }
274
+ }
275
+ if (idx < 0) return;
276
+
277
+ Entries.RemoveAt(idx);
278
+ NameCache.RemoveAt(idx);
279
+ NameLowerCache.RemoveAt(idx);
280
  RebuildRefLookup();
281
  }
282
 
 
300
 
301
  public string BuildPath(ulong fileRef)
302
  {
303
+ // Stackalloc-friendly: use array instead of List
304
+ Span<string> components = stackalloc string[64];
305
+ int compCount = 0;
306
  ulong current = fileRef;
307
 
308
  for (int i = 0; i < 64; i++)
 
311
  if (idx == null) break;
312
 
313
  var entry = Entries[(int)idx];
314
+ components[compCount++] = NameAt((int)idx);
315
  if (entry.ParentRef == current) break;
316
  current = entry.ParentRef;
317
  }
318
 
319
+ var sb = new StringBuilder(DriveRoot.Length + compCount * 32);
320
+ sb.Append(DriveRoot);
321
+ for (int i = compCount - 1; i >= 0; i--)
 
322
  {
323
+ if (sb.Length > 0 && sb[sb.Length - 1] != '\\' && sb[sb.Length - 1] != '/')
324
+ sb.Append('\\');
325
+ sb.Append(components[i]);
326
  }
327
+ return sb.ToString();
328
  }
329
  }