55#include < algorithm>
66#include < map>
77#include < string>
8- #include < filesystem>
98#include < vector>
109#include < fstream>
1110#include < iomanip>
1716#include < windows.h>
1817#undef NOMINMAX
1918#else
20- #include < sys/mman .h>
19+ #include < sys/types .h>
2120#include < sys/stat.h>
22- #include < fcntl .h>
21+ #include < dirent .h>
2322#include < unistd.h>
2423#endif
2524
26- namespace fs = std::filesystem;
25+ // ============================================================================
26+ // Portable filesystem helpers (no std::filesystem / C++17 required)
27+ // ============================================================================
28+
29+ // Returns file size in bytes, or -1 on failure
30+ static int64_t getFileSize (const std::string& path) {
31+ #ifdef _WIN32
32+ WIN32_FILE_ATTRIBUTE_DATA info;
33+ if (!GetFileAttributesExA (path.c_str (), GetFileExInfoStandard, &info))
34+ return -1 ;
35+ return ((int64_t )info.nFileSizeHigh << 32 ) | info.nFileSizeLow ;
36+ #else
37+ struct stat st;
38+ if (stat (path.c_str (), &st) != 0 ) return -1 ;
39+ return (int64_t )st.st_size ;
40+ #endif
41+ }
42+
43+ // Strips directory and extension, returning just the stem (e.g. "42" from "42.bin")
44+ static std::string getStem (const std::string& filename) {
45+ size_t dot = filename.rfind (' .' );
46+ return (dot == std::string::npos) ? filename : filename.substr (0 , dot);
47+ }
48+
49+ static std::string getExtension (const std::string& filename) {
50+ size_t dot = filename.rfind (' .' );
51+ return (dot == std::string::npos) ? " " : filename.substr (dot);
52+ }
53+
54+ // Fills `out` with filenames (not full paths) of regular files in `dir`
55+ static void listFiles (const std::string& dir, std::vector<std::string>& out) {
56+ #ifdef _WIN32
57+ WIN32_FIND_DATAA ffd;
58+ std::string pattern = dir + " \\ *" ;
59+ HANDLE hFind = FindFirstFileA (pattern.c_str (), &ffd);
60+ if (hFind == INVALID_HANDLE_VALUE ) return ;
61+ do {
62+ if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ))
63+ out.push_back (ffd.cFileName );
64+ } while (FindNextFileA (hFind, &ffd));
65+ FindClose (hFind);
66+ #else
67+ DIR * d = opendir (dir.c_str ());
68+ if (!d) return ;
69+ struct dirent * entry;
70+ while ((entry = readdir (d)) != nullptr ) {
71+ if (entry->d_type == DT_REG )
72+ out.push_back (entry->d_name );
73+ }
74+ closedir (d);
75+ #endif
76+ }
2777
2878// ============================================================================
2979// Time formatting function (matching Haxe behavior)
@@ -71,16 +121,16 @@ std::string formatTime(double ms, bool showMS = false) {
71121// ============================================================================
72122// Note unpacking helpers - POSITION IS ABSOLUTE IN TICKS
73123// ============================================================================
74- inline uint32_t getPosition (uint64_t note) { return (note >> 0 ) & 0x3FFFFFFF ; } // Absolute position in ticks
75- inline uint16_t getDuration (uint64_t note) { return (note >> 30 ) & 0xFFFF ; } // Duration in ms
76- inline uint8_t getIndex (uint64_t note) { return (note >> 46 ) & 0xFF ; }
77- inline uint8_t getType (uint64_t note) { return (note >> 54 ) & 0x7F ; }
78- inline bool getFlag (uint64_t note) { return (note >> 61 ) & 1 ; }
79- inline bool getMissed (uint64_t note) { return (note >> 62 ) & 1 ; }
80- inline bool getHeld (uint64_t note) { return (note >> 63 ) & 1 ; }
124+ uint32_t getPosition (uint64_t note) { return (note >> 0 ) & 0x3FFFFFFF ; } // Absolute position in ticks
125+ uint16_t getDuration (uint64_t note) { return (note >> 30 ) & 0xFFFF ; } // Duration in ms
126+ uint8_t getIndex (uint64_t note) { return (note >> 46 ) & 0xFF ; }
127+ uint8_t getType (uint64_t note) { return (note >> 54 ) & 0x7F ; }
128+ bool getFlag (uint64_t note) { return (note >> 61 ) & 1 ; }
129+ bool getMissed (uint64_t note) { return (note >> 62 ) & 1 ; }
130+ bool getHeld (uint64_t note) { return (note >> 63 ) & 1 ; }
81131
82132// Convert ticks to milliseconds (1 tick = 0.25 nanoseconds = 0.00000025 ms)
83- inline double ticksToMs (uint32_t ticks) {
133+ double ticksToMs (uint32_t ticks) {
84134 return ticks / 4000000.0 ; // 4,000,000 ticks per ms
85135}
86136
@@ -122,16 +172,22 @@ class DirectFileReader {
122172 }
123173 }
124174
125- inline uint64_t get (int64_t index) const {
175+ uint64_t get (int64_t index) const {
176+ if (index < 0 || index >= (int64_t )data.size ()) {
177+ throw std::out_of_range (" Index out of range" );
178+ }
126179 return data[index];
127180 }
128181
129- inline void set (int64_t index, uint64_t value) {
182+ void set (int64_t index, uint64_t value) {
183+ if (index < 0 || index >= (int64_t )data.size ()) {
184+ throw std::out_of_range (" Index out of range" );
185+ }
130186 data[index] = value;
131187 modified = true ;
132188 }
133189
134- inline int64_t size () const { return data.size (); }
190+ int64_t size () const { return data.size (); }
135191};
136192
137193// ============================================================================
@@ -152,26 +208,25 @@ class ShardedChartReader {
152208 std::vector<uint64_t > availableShards;
153209 std::vector<int64_t > shardStartIndices; // For binary search
154210 uint64_t currentShardId = 0 ;
155- ShardInfo* cachedShard = nullptr ; // Current shard cache
156- ShardInfo* prevCachedShard = nullptr ; // Previous shard cache (for backwards iteration)
157211 int64_t totalNotes = 0 ;
158212
159213 static constexpr int POOL_SIZE = 100 ;
160- static constexpr int POOL_THRESHOLD = POOL_SIZE / 4 ; // Quarter of pool size = 25
161214 static constexpr int PRELOAD_THRESHOLD = 50 ;
162215
163216 int64_t correctionTime = 0 ;
164217
165218 void scanShards () {
166- for (const auto & entry : fs::directory_iterator (chartDir)) {
167- if (entry.is_regular_file () && entry.path ().extension () == " .bin" ) {
168- std::string filename = entry.path ().stem ().string ();
169- try {
170- uint64_t shardId = std::stoull (filename);
171- availableShards.push_back (shardId);
172- } catch (...) {
173- std::cerr << " Warning: Invalid shard filename: " << filename << std::endl;
174- }
219+ std::vector<std::string> files;
220+ listFiles (chartDir, files);
221+
222+ for (const auto & name : files) {
223+ if (getExtension (name) != " .bin" ) continue ;
224+ std::string stem = getStem (name);
225+ try {
226+ uint64_t shardId = std::stoull (stem);
227+ availableShards.push_back (shardId);
228+ } catch (...) {
229+ std::cerr << " Warning: Invalid shard filename: " << name << std::endl;
175230 }
176231 }
177232
@@ -182,7 +237,8 @@ class ShardedChartReader {
182237 shardStartIndices.push_back (cumulative);
183238
184239 std::string path = chartDir + " /" + std::to_string (shardId) + " .bin" ;
185- uint64_t fileSize = fs::file_size (path);
240+ int64_t fileSize = getFileSize (path);
241+ if (fileSize < 0 ) throw std::runtime_error (" Cannot stat shard: " + path);
186242 int64_t noteCount = fileSize / sizeof (uint64_t );
187243 cumulative += noteCount;
188244 }
@@ -221,32 +277,25 @@ class ShardedChartReader {
221277 info.noteCount = info.reader .size ();
222278 info.endIndex = info.startIndex + info.noteCount ;
223279 activeShards[shardId] = std::move (info);
280+
281+ // std::cout << "[POOL] Loaded shard " << shardId << " (" << info.noteCount << " notes)" << std::endl;
224282 }
225283
226284 void unloadShard (uint64_t shardId) {
227285 auto it = activeShards.find (shardId);
228286 if (it != activeShards.end ()) {
229287 it->second .reader .flush ();
230-
231- // Invalidate caches if we unload them
232- if (cachedShard && cachedShard->shardId == shardId) {
233- cachedShard = nullptr ;
234- }
235- if (prevCachedShard && prevCachedShard->shardId == shardId) {
236- prevCachedShard = nullptr ;
237- }
238-
239288 activeShards.erase (it);
289+ // std::cout << "[POOL] Unloaded shard " << shardId << std::endl;
240290 }
241291 }
242292
243293 void managePool (uint64_t currentShard) {
244- // Remove shards that are beyond the quarter threshold
294+ // Remove shards that are far behind
245295 std::vector<uint64_t > toRemove;
246296 for (auto & pair : activeShards) {
247297 uint64_t shardId = pair.first ;
248- // Use POOL_THRESHOLD (25) instead of full POOL_SIZE
249- if (shardId + POOL_THRESHOLD < currentShard && currentShard > shardId + POOL_THRESHOLD ) {
298+ if (shardId + POOL_SIZE < currentShard) {
250299 toRemove.push_back (shardId);
251300 }
252301 }
@@ -292,8 +341,6 @@ class ShardedChartReader {
292341 pair.second .reader .flush ();
293342 }
294343 activeShards.clear ();
295- cachedShard = nullptr ;
296- prevCachedShard = nullptr ;
297344 }
298345
299346 // Binary search to find which shard contains the global index
@@ -312,81 +359,43 @@ class ShardedChartReader {
312359 return availableShards[shardPos];
313360 }
314361
315- inline uint64_t getNote (int64_t globalIndex) {
316- // Fast path 1: check current cached shard
317- if (cachedShard && globalIndex >= cachedShard->startIndex && globalIndex < cachedShard->endIndex ) {
318- return cachedShard->reader .get (globalIndex - cachedShard->startIndex );
319- }
320-
321- // Fast path 2: check previous cached shard (for backwards iteration)
322- if (prevCachedShard && globalIndex >= prevCachedShard->startIndex && globalIndex < prevCachedShard->endIndex ) {
323- // Swap caches - the previous becomes current, current becomes previous
324- std::swap (cachedShard, prevCachedShard);
325- return cachedShard->reader .get (globalIndex - cachedShard->startIndex );
326- }
327-
328- // Slow path: find the correct shard
362+ uint64_t getNote (int64_t globalIndex) {
329363 uint64_t shardId = findShardForGlobalIndex (globalIndex);
330364 correctionTime = shardId * 1000000000 ;
331365
332366 // Cache the current shard for sequential access
333367 if (shardId != currentShardId) {
334- // Before changing current, save it as previous
335- if (cachedShard && cachedShard->shardId == currentShardId) {
336- prevCachedShard = cachedShard;
337- }
338368 currentShardId = shardId;
339369 managePool (currentShardId);
340370 }
341371
342372 // Ensure shard is loaded
343- auto it = activeShards.find (shardId);
344- if (it == activeShards.end ()) {
373+ if (activeShards.find (shardId) == activeShards.end ()) {
345374 loadShard (shardId);
346- it = activeShards.find (shardId);
347375 }
348376
349- cachedShard = &(it-> second ) ;
350- int64_t localIndex = globalIndex - cachedShard-> startIndex ;
377+ ShardInfo& info = activeShards[shardId] ;
378+ int64_t localIndex = globalIndex - info. startIndex ;
351379
352- return cachedShard-> reader .get (localIndex);
380+ return info. reader .get (localIndex);
353381 }
354382
355- inline void setNote (int64_t globalIndex, uint64_t value) {
356- // Fast path 1: check current cached shard
357- if (cachedShard && globalIndex >= cachedShard->startIndex && globalIndex < cachedShard->endIndex ) {
358- cachedShard->reader .set (globalIndex - cachedShard->startIndex , value);
359- return ;
360- }
361-
362- // Fast path 2: check previous cached shard
363- if (prevCachedShard && globalIndex >= prevCachedShard->startIndex && globalIndex < prevCachedShard->endIndex ) {
364- std::swap (cachedShard, prevCachedShard);
365- cachedShard->reader .set (globalIndex - cachedShard->startIndex , value);
366- return ;
367- }
368-
369- // Slow path
383+ void setNote (int64_t globalIndex, uint64_t value) {
370384 uint64_t shardId = findShardForGlobalIndex (globalIndex);
371385
372386 if (shardId != currentShardId) {
373- if (cachedShard && cachedShard->shardId == currentShardId) {
374- prevCachedShard = cachedShard;
375- }
376387 currentShardId = shardId;
377388 managePool (currentShardId);
378389 }
379390
380- auto it = activeShards.find (shardId);
381- if (it == activeShards.end ()) {
391+ if (activeShards.find (shardId) == activeShards.end ()) {
382392 loadShard (shardId);
383- it = activeShards.find (shardId);
384393 }
385394
386- cachedShard = &(it-> second ) ;
387- int64_t localIndex = globalIndex - cachedShard-> startIndex ;
395+ ShardInfo& info = activeShards[shardId] ;
396+ int64_t localIndex = globalIndex - info. startIndex ;
388397
389- cachedShard-> reader .set (localIndex, value);
398+ info. reader .set (localIndex, value);
390399 }
391400
392401 void printNoteInfo (int64_t globalIndex) {
@@ -413,23 +422,16 @@ class ShardedChartReader {
413422 << " raw=0x" << std::hex << note << std::dec << std::endl;
414423 }
415424
416- inline int64_t getLength () const {
425+ int64_t getLength () const {
417426 return totalNotes;
418427 }
419428
420- inline size_t getShardCount () const {
429+ size_t getShardCount () const {
421430 return availableShards.size ();
422431 }
423432
424433 void printPoolStats () const {
425434 std::cout << " [POOL] Active shards: " << activeShards.size () << " / " << availableShards.size () << std::endl;
426- std::cout << " [POOL] Threshold: " << POOL_THRESHOLD << " shards" << std::endl;
427- if (cachedShard) {
428- std::cout << " [CACHE] Current shard: " << cachedShard->shardId << std::endl;
429- }
430- if (prevCachedShard) {
431- std::cout << " [CACHE] Previous shard: " << prevCachedShard->shardId << std::endl;
432- }
433435 int count = 0 ;
434436 for (const auto & pair : activeShards) {
435437 if (count++ >= 10 ) {
0 commit comments