Skip to content

Commit 39c98d7

Browse files
Welp, now it's officially done.
Well that commit took forever to even push onto my repository, but that's simply because of how my sharded friday night funkin' binary chart system works, which is that every quarter of a second is a separate file, allowing you effortless addition and deletion. Oh, and for c++14 being the default version of hxcpp, I couldn't get it to build the chartFile extern on hashlink, but then I got claude.ai to fix that by replacing the c++17 stuff with native things. So now, I have a basic foundation for my chart editor.
1 parent 8845833 commit 39c98d7

6 files changed

Lines changed: 108 additions & 106 deletions

File tree

hdlls/windows/chart_file.hdll

175 KB
Binary file not shown.

src/data/chart/chart_file.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@ int64_t getLength() {
2929

3030
int64_t getTimeCorrectionForIndex(int64_t index) {
3131
if (!gReader) return 0;
32-
uint64_t shardId = gReader->findShardForGlobalIndex(index); // thumbsup
32+
uint64_t shardId = gReader->findShardForGlobalIndex(index);
3333
return shardId * 1000000000LL;
3434
}

src/data/chart/chart_file_core.cpp

Lines changed: 101 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
#include <algorithm>
66
#include <map>
77
#include <string>
8-
#include <filesystem>
98
#include <vector>
109
#include <fstream>
1110
#include <iomanip>
@@ -17,13 +16,64 @@
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) {

src/data/chart/chart_file_hl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ HL_PRIM void HL_NAME(destroyChart)(_NO_ARG) {
3232

3333
HL_PRIM int64_t HL_NAME(getTimeCorrectionForIndex)(int64_t index) {
3434
if (!gReader) return 0;
35-
uint64_t shardId = gReader->findShardForGlobalIndex(index); // make this public or add a method
35+
uint64_t shardId = gReader->findShardForGlobalIndex(index);
3636
return shardId * 1000000000LL;
3737
}
3838

src/structures/gameplay/NoteSystem.hx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ class NoteSystem {
109109
if (noteSpawner != null)
110110
noteSpawner.update(pos);
111111

112-
trace('Note pool length: ${notePool.inactiveVirtualNotes.length}, Sustain pool length: ${notePool.inactiveVirtualSusses.length}');
112+
//trace('Note pool length: ${notePool.inactiveVirtualNotes.length}, Sustain pool length: ${notePool.inactiveVirtualSusses.length}');
113113
}
114114

115115
private var _lastPos(default, null):Int64; // for adaptive bot timer
@@ -316,7 +316,7 @@ class NoteSystem {
316316
}
317317

318318
// --- Sustain handling ---
319-
var sustainLength = duration - 10;
319+
var sustainLength = duration - 20;
320320
if (sustainExists) {
321321
sustainSpr.ref = noteSpr;
322322
sustainSpr.speed = parent.scrollSpeed;
@@ -337,7 +337,7 @@ class NoteSystem {
337337
if (sustainSpr.w < 0) sustainSpr.w = 0;
338338
}
339339

340-
if (pos > position + (MetaNote.floatToMetaNotePosition(sustainLength - 12)) && !isHeld) {
340+
if (pos > position + (MetaNote.floatToMetaNotePosition(sustainLength - 20)) && !isHeld) {
341341
var n:Int64 = note.toNumber();
342342
(n:MetaNote).held = true;
343343
isHeld = true;
@@ -358,7 +358,7 @@ class NoteSystem {
358358
}
359359

360360
// Fixes the rare receptor pause issue, finally
361-
if (diff + sustainLength - 12 < 0)
361+
if (diff + sustainLength - 20 < 0)
362362
strumline.sustainsActive[index] = !isHeld;
363363

364364
if (noteSpr != null)

0 commit comments

Comments
 (0)