diff --git a/beatmap.cc b/beatmap.cc index acbdc84..eefdd17 100644 --- a/beatmap.cc +++ b/beatmap.cc @@ -90,9 +90,6 @@ size_t get_exe_path(char* buf, size_t bufsize) // disclaimer: this beatmap parser is meant purely for difficulty calculation // and I don't support any malicious use of it -const size_t bufsize = 2000000; // 2 mb -globvar u8 buf[bufsize]; - const f32 od0_ms = 79.5, od10_ms = 19.5, ar0_ms = 1800, @@ -222,6 +219,8 @@ bool whitespace(const char* s) // note: values not required for diff calc will be omitted from this parser struct beatmap { + oppai_ctx* ctx; + u32 format_version; // general @@ -264,6 +263,10 @@ struct beatmap return false; \ } + char versionbuf[64]; + strcpy(versionbuf, version_string); + w(versionbuf); + w(format_version) w(stack_leniency) w(mode) @@ -312,6 +315,13 @@ struct beatmap return false; \ } + char versionbuf[64]; + w(versionbuf); + + if (strcmp(versionbuf, version_string)) { + return false; + } + w(format_version) w(stack_leniency) w(mode) @@ -352,7 +362,8 @@ struct beatmap return true; } - beatmap() : + beatmap(oppai_ctx* ctx) : + ctx(ctx), format_version(0), stack_leniency(0), mode(0), @@ -372,13 +383,18 @@ struct beatmap } static - size_t get_cache_file(char* cache_path, size_t bufsize, bool mk = true) + size_t get_cache_file( + char* beatmap, + size_t beatmap_size, + char* cache_path, + size_t bufsize, + bool mk = true) { u8 digest_bytes[MD5_DIGEST_LENGTH]; char* p = cache_path; char const* folder_name = "oppai_cache"; - MD5((u8*)buf, strlen((char const*)buf), digest_bytes); + MD5((u8*)beatmap, beatmap_size, digest_bytes); p += get_exe_path( p, @@ -409,28 +425,32 @@ struct beatmap static void parse( const char* osu_file, beatmap& b, + char* buf, size_t bufsize, bool disable_cache = false) { #if OPPAI_PROFILING const int prid = 1; #endif + oppai_ctx* ctx = b.ctx; + profile(prid, "I/O"); // if osu_file is "-" read from stdin instead of file FILE* f = (strcmp(osu_file, "-") == 0) ? stdin : fopen(osu_file, "rb"); if (!f) { - die("Failed to open beatmap"); + die(ctx, "Failed to open beatmap"); return; } + dbgputs("reading .osu file"); size_t cb = fread(buf, 1, bufsize, f); if (cb == bufsize) { - die("Beatmap is too big for the internal buffer"); + die(ctx, "Beatmap is too big for the internal buffer"); return; } - dbgprintf("%zd bytes\n", cb); + dbgprintf("%" fu32 " bytes\n", (u32)cb); fclose(f); // just to be safe @@ -442,7 +462,7 @@ struct beatmap FILE* cachefd = 0; if (!disable_cache) { - get_cache_file(cachefile, sizeof(cachefile)); + get_cache_file(buf, cb, cachefile, sizeof(cachefile)); cachefd = fopen(cachefile, "rb"); } @@ -461,7 +481,8 @@ struct beatmap profile(prid, "format version"); - while (not_section()) { + while (not_section()) + { if (sscanf(tok, "osu file format v%" fu32 "", cachefd ? &tmp_format_version :&b.format_version) == 1) { break; @@ -478,22 +499,22 @@ struct beatmap profile(prid, "general"); if (!find_fwd(tok, "[General]")) { - die("Could not find General info"); + die(ctx, "Could not find General info"); return; } // --- - // NOTE: I could just store all properties and map them by section and name - // but I'd rather parse only the ones I need since I'd still need to parse - // them one by one and check for format errors. + // NOTE: I could just store all properties and map them by section and + // name but I'd rather parse only the ones I need since I'd still need + // to parse them one by one and check for format errors. f32 tmp_stack_leniency = 0; u16 tmp_mode = 0; // StackLeniency and Mode are not present in older formats - for (; not_section(); fwd()) { - + for (; not_section(); fwd()) + { if (sscanf(tok, "StackLeniency: %f", cachefd ? &tmp_stack_leniency : &b.stack_leniency) == 1) { continue; @@ -513,7 +534,7 @@ struct beatmap profile(prid, "metadata"); if (!find_fwd(tok, "[Metadata]")) { - die("Could not find Metadata"); + die(ctx, "Could not find Metadata"); return; } @@ -527,9 +548,9 @@ struct beatmap if (cachefd) { memset(tmp_title, 0, sizeof(tmp_title)); - memset(tmp_artist, 0, sizeof(tmp_title)); - memset(tmp_creator, 0, sizeof(tmp_title)); - memset(tmp_version, 0, sizeof(tmp_title)); + memset(tmp_artist, 0, sizeof(tmp_artist)); + memset(tmp_creator, 0, sizeof(tmp_creator)); + memset(tmp_version, 0, sizeof(tmp_version)); } for (; not_section(); fwd()) { @@ -575,13 +596,9 @@ struct beatmap return; } - dbgprintf( - "hash collision for %s: " - "%s - %s (%s) [%s] != %s - %s (%s) [%s]\n", - osu_file, - b.artist, b.title, b.creator, b.version, - tmp_artist, tmp_title, tmp_creator, tmp_version - ); + // force recache + cachefd = 0; + fprintf(stderr, "invalid cache file for %s, recaching\n", osu_file); b.format_version = tmp_format_version; b.mode = (u8)tmp_mode; @@ -598,7 +615,7 @@ struct beatmap profile(prid, "difficulty"); if (!find_fwd(tok, "[Difficulty]")) { - die("Could not find Difficulty"); + die(ctx, "Could not find Difficulty"); return; } @@ -632,17 +649,17 @@ struct beatmap } if (b.hp > 10) { - die("Invalid or missing HP"); + die(ctx, "Invalid or missing HP"); return; } if (b.cs > 10) { - die("Invalid or missing CS"); + die(ctx, "Invalid or missing CS"); return; } if (b.od > 10) { - die("Invalid or missing OD"); + die(ctx, "Invalid or missing OD"); return; } @@ -652,7 +669,7 @@ struct beatmap } if (b.sv > 10) { // not sure what max sv is - die("Invalid or missing SV"); + die(ctx, "Invalid or missing SV"); return; } @@ -661,7 +678,7 @@ struct beatmap profile(prid, "timing points"); if (!find_fwd(tok, "[TimingPoints]")) { - die("Could not find TimingPoints"); + die(ctx, "Could not find TimingPoints"); return; } @@ -677,7 +694,7 @@ struct beatmap } if (b.num_timing_points >= beatmap::max_timing_points) { - die("Too many timing points for the internal buffer"); + die(ctx, "Too many timing points for the internal buffer"); return; } @@ -702,7 +719,7 @@ struct beatmap if (sscanf(tok, "%lf,%lf", &time_tmp, &tp.ms_per_beat) != 2) { tp.time = (i32)time_tmp; - die("Invalid format for timing point"); + die(ctx, "Invalid format for timing point"); return; } @@ -715,7 +732,7 @@ struct beatmap profile(prid, "hit objects"); if (!find_fwd(tok, "[HitObjects]")) { - die("Could not find HitObjects"); + die(ctx, "Could not find HitObjects"); return; } @@ -735,7 +752,7 @@ struct beatmap } if (b.num_objects >= beatmap::max_objects) { - die("Too many hit objects for the internal buffer"); + die(ctx, "Too many hit objects for the internal buffer"); return; } @@ -805,12 +822,12 @@ struct beatmap goto object_type_done; } - die("Invalid hit object found"); + die(ctx, "Invalid hit object found"); return; object_type_done: b.num_objects++; - dbgprintf("\n\nobject %zd\n", b.num_objects); + dbgprintf("\n\nobject %" fu32 "\n", (u32)b.num_objects); // increase max combo and circle/slider count b.max_combo++; // slider ticks are calculated later @@ -833,7 +850,7 @@ struct beatmap break; case obj::invalid: - die("How did you get here????????"); + die(ctx, "How did you get here????????"); return; } @@ -845,7 +862,7 @@ struct beatmap // not a slider yet slider points were found if (ho.type != obj::slider) { - die("Invalid slider found"); + die(ctx, "Invalid slider found"); return; } @@ -892,7 +909,7 @@ struct beatmap timing_point* tp = b.timing(ho.time); timing_point* parent = b.parent_timing(tp); - if (err()) { + if (oppai_err(ctx)) { return; } @@ -988,14 +1005,14 @@ struct beatmap if (cur.time <= t->time && !cur.inherit) { - if (!res || res->time > res->time) { + if (!res || cur.time > res->time) { res = &cur; } } } if (!res) { - die("Orphan timing section"); + die(ctx, "Orphan timing section"); } return res; diff --git a/diff_calc.cc b/diff_calc.cc index 508db30..b874613 100644 --- a/diff_calc.cc +++ b/diff_calc.cc @@ -34,6 +34,7 @@ namespace diff // diffcalc hit object struct d_obj { + oppai_ctx* ctx; hit_object* ho; f64 strains[2]; @@ -52,8 +53,9 @@ struct d_obj strains[1] = 1; } - void init(hit_object* base_object, f32 radius) + void init(oppai_ctx* ctx, hit_object* base_object, f32 radius) { + this->ctx = ctx; this->ho = base_object; // positions are normalized on circle radius so that we can calc as @@ -78,7 +80,7 @@ struct d_obj void calculate_strains(d_obj& prev) { calculate_strain(prev, diff::speed); - if (err()) { + if (oppai_err(ctx)) { return; } @@ -103,7 +105,7 @@ struct d_obj break; case obj::invalid: - die("Found invalid hit object"); + die(ctx, "Found invalid hit object"); return; } @@ -162,11 +164,17 @@ const i32 strain_step = 400; // weight decays. const f64 decay_weight = 0.9; -globvar d_obj objects[beatmap::max_objects]; -globvar size_t num_objects; +struct d_calc_ctx +{ + oppai_ctx* octx; + d_obj objects[beatmap::max_objects]; + size_t num_objects; + + d_calc_ctx(oppai_ctx* octx) : octx(octx) {} +}; internalfn -f64 calculate_difficulty(u8 type) +f64 calculate_difficulty(d_calc_ctx* ctx, u8 type) { std::vector highest_strains; i32 interval_end = strain_step; @@ -174,9 +182,9 @@ f64 calculate_difficulty(u8 type) d_obj* prev = 0; - for (size_t i = 0; i < num_objects; i++) + for (size_t i = 0; i < ctx->num_objects; i++) { - d_obj& o = objects[i]; + d_obj& o = ctx->objects[i]; // make previous peak strain decay until the current object while (o.ho->time > interval_end) @@ -222,17 +230,21 @@ f64 calculate_difficulty(u8 type) // aim, speed: pointers to the variables where // aim and speed stars will be stored. // returns overall stars -f64 d_calc(beatmap& b, f64* aim, f64* speed, - f64* rhythm_awkwardness, - u16* nsingles, - u16* nsingles_timing, - u16* nsingles_threshold, - i32 singletap_threshold) + +OPPAIAPI +f64 d_calc( + d_calc_ctx* ctx, + beatmap& b, f64* aim, f64* speed, + f64* rhythm_awkwardness = 0, + u16* nsingles = 0, + u16* nsingles_timing = 0, + u16* nsingles_threshold = 0, + i32 singletap_threshold = 240) { dbgputs("\ndiff calc"); if (b.mode != 0) { - die("This gamemode is not supported"); + die(ctx->octx, "This gamemode is not supported"); return 0; } @@ -241,13 +253,13 @@ f64 d_calc(beatmap& b, f64* aim, f64* speed, dbgprintf("circle radius: %g\n", circle_radius); - num_objects = b.num_objects; + ctx->num_objects = b.num_objects; dbgputs("initializing objects"); for (size_t i = 0; i < b.num_objects; i++) { - objects[i].init(&b.objects[i], circle_radius); - if (err()) { + ctx->objects[i].init(ctx->octx, &b.objects[i], circle_radius); + if (oppai_err(ctx->octx)) { return 0; } } @@ -255,13 +267,13 @@ f64 d_calc(beatmap& b, f64* aim, f64* speed, // TODO: don't use vector std::vector intervals; - d_obj* prev = &objects[0]; + d_obj* prev = &ctx->objects[0]; for (size_t i = 1; i < b.num_objects; i++) { - d_obj& o = objects[i]; + d_obj& o = ctx->objects[i]; o.calculate_strains(*prev); - if (err()) { + if (oppai_err(ctx->octx)) { return 0; } @@ -341,8 +353,8 @@ f64 d_calc(beatmap& b, f64* aim, f64* speed, *rhythm_awkwardness *= 82; skip_awkwardness: - *aim = calculate_difficulty(diff::aim); - *speed = calculate_difficulty(diff::speed); + *aim = calculate_difficulty(ctx, diff::aim); + *speed = calculate_difficulty(ctx, diff::speed); *aim = sqrt(*aim) * star_scaling_factor; *speed = sqrt(*speed) * star_scaling_factor; diff --git a/lib_example/amd64-msvc2010.bat b/lib_example/amd64-msvc2010.bat new file mode 100644 index 0000000..670c7dc --- /dev/null +++ b/lib_example/amd64-msvc2010.bat @@ -0,0 +1,2 @@ +call C:\\"Program Files"\\"Microsoft Visual Studio 10.0"\\VC\\vcvarsall.bat x86_amd64 +call build.bat diff --git a/lib_example/build.bat b/lib_example/build.bat new file mode 100644 index 0000000..fc59291 --- /dev/null +++ b/lib_example/build.bat @@ -0,0 +1,15 @@ +@echo off + +del test.exe +del test.obj +cl -D_CRT_SECURE_NO_WARNINGS=1 ^ + -DNOMINMAX=1 ^ + -DOPPAI_LIB=1 ^ + -O2 ^ + -nologo -MT -Gm- -GR- -EHsc -W4 -WX ^ + -wd4201 ^ + -wd4100 ^ + -F8000000 ^ + main.cc ^ + -Fetest.exe ^ + Advapi32.lib diff --git a/lib_example/i386-msvc2010.bat b/lib_example/i386-msvc2010.bat new file mode 100644 index 0000000..a2ca2dc --- /dev/null +++ b/lib_example/i386-msvc2010.bat @@ -0,0 +1,2 @@ +call C:\\"Program Files"\\"Microsoft Visual Studio 10.0"\\VC\\vcvarsall.bat x86 +call build.bat diff --git a/lib_example/main.cc b/lib_example/main.cc new file mode 100644 index 0000000..a7caedb --- /dev/null +++ b/lib_example/main.cc @@ -0,0 +1,48 @@ +//#include "/path/to/oppai/code/main.cc" +#include "../main.cc" + +// don't forget to define OPPAI_LIB=1 in your build script! + +#define BUFSIZE 2000000 +static char buf[BUFSIZE]; + +int main(int argc, char* argv[]) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s file.osu\n", argv[0]); + return 1; + } + + // if you need to multithread, create one ctx and buffer for each thread + oppai_ctx ctx; + + beatmap b(&ctx); + beatmap::parse(argv[1], b, buf, BUFSIZE, true); + + if (oppai_err(&ctx)) { + fprintf(stderr, "parse: %s\n", oppai_err(&ctx)); + return 1; + } + + // b.apply_mods(mods::...) + + f64 stars, aim = 0, speed = 0; + + d_calc_ctx dctx(&ctx); + stars = d_calc(&dctx, b, &aim, &speed); + + if (oppai_err(&ctx)) { + fprintf(stderr, "d_calc: %s\n", oppai_err(&ctx)); + return 1; + } + + printf( + "%.17g stars, %.17g aim stars, %.17g speed stars\n", + stars, aim, speed + ); + + pp_calc_result result = pp_calc(&ctx, aim, speed, b /* mods ... */); + printf("%.17g pp\n", result.pp); + + return 0; +} diff --git a/main.cc b/main.cc index 4f0f9d6..e996d4d 100644 --- a/main.cc +++ b/main.cc @@ -13,41 +13,69 @@ #include // tolower/toupper -const char* version_string = "0.9.1"; +#ifndef OPPAI_LIB +#define OPPAIAPI internalfn +#define VERSION_SUFFIX +#else +// these are not thread safe so we must disable them in lib mode +#undef OPPAI_PROFILER +#undef SHOW_BEATMAP +#undef _DEBUG + +#define OPPAIAPI +#define VERSION_SUFFIX "-lib" +#endif + +const char* version_string = "0.9.2" VERSION_SUFFIX; // ----------------------------------------------------------------------------- -// sets the last error to msg if it's not already set -#define die(msg) dbgputs(msg); die_impl(msg) +struct oppai_ctx +{ + const char* last_err; + + oppai_ctx() : last_err(0) {} +}; + +// returns the last error, or 0 if no error has occurred +OPPAIAPI +const char* oppai_err(oppai_ctx* ctx) { + return ctx->last_err; +} -globvar const char* last_err = 0; +// ----------------------------------------------------------------------------- + +// sets the last error to msg if it's not already set +#define die(ctx, msg) dbgputs(msg); die_impl(ctx, msg) internalfn -void die_impl(const char* msg) +void die_impl(oppai_ctx* ctx, const char* msg) { - if (last_err) { + if (ctx->last_err) { return; } - last_err = msg; -} - -// returns the last error, or 0 if no error has occurred -internalfn -const char* err() { - return last_err; + ctx->last_err = msg; } +#ifdef OPPAI_LIB +#define chk(ctx) \ + if (oppai_err(ctx)) { \ + return 1; \ + } +#else internalfn -void chk() { - if (!err()) { +void chk(oppai_ctx* ctx) +{ + if (!oppai_err(ctx)) { return; } - fputs(err(), stderr); + fputs(oppai_err(ctx), stderr); fputs("\n", stderr); exit(1); } +#endif // ----------------------------------------------------------------------------- @@ -83,8 +111,8 @@ const size_t num_mods = sizeof(mod_masks) / sizeof(mod_masks[0]); // ----------------------------------------------------------------------------- -// TODO: move this -bool encode_str(FILE* fd, const char* str); +// TODO: move this? +internalfn bool encode_str(FILE* fd, const char* str); // this a monolithic build. stuff is separated into files purely for readability #include "profiler.cc" @@ -95,43 +123,50 @@ bool encode_str(FILE* fd, const char* str); // ----------------------------------------------------------------------------- -// globals for main -beatmap b; - #ifdef SHOW_BEATMAP -internalfn void print_beatmap(); +internalfn void print_beatmap(beatmap& b, oppai_ctx* ctx); #endif // ----------------------------------------------------------------------------- +// !!!!!!!!! OVERRIDE PRINTF AND PUTS FOR OUTPUT MODULES !!!!!!!!!! +#define printf(fmt, ...) fprintf(fd, fmt, __VA_ARGS__) +#define puts(s) fputs(s, fd) +#define putchar(c) fputc(c, fd) + // output modules -#define print_sig(name) \ - void name( \ - char* mods_str, \ - u16 combo, \ - u16 misses, \ - u32 scoring, \ - f64 stars, \ - f64 aim, \ - f64 speed, \ - f64 rhythm_awkwardness, \ - u16 nsingles, \ - u16 nsingles_timing, \ - u16 nsingles_threshold, \ - pp_calc_result& res) +#define print_sig(name) \ + void name( \ + FILE* fd, \ + char* mods_str, \ + u16 combo, \ + u16 misses, \ + u32 scoring, \ + f64 stars, \ + f64 aim, \ + f64 speed, \ + f64 rhythm_awkwardness, \ + u16 nsingles, \ + u16 nsingles_timing, \ + u16 nsingles_threshold, \ + pp_calc_result& res, \ + beatmap& b) + +#define twodecimals(x) (macro_round((x) * 100.0) / 100.0) // text output internalfn print_sig(text_print) { // round to 2 decimal places - aim = macro_round(aim * 100.0) / 100.0; - speed = macro_round(speed * 100.0) / 100.0; - stars = macro_round(stars * 100.0) / 100.0; - res.pp = macro_round(res.pp * 100.0) / 100.0; - res.aim_pp = macro_round(res.aim_pp * 100.0) / 100.0; - res.speed_pp = macro_round(res.speed_pp * 100.0) / 100.0; - res.acc_pp = macro_round(res.acc_pp * 100.0) / 100.0; + aim = twodecimals(aim); + speed = twodecimals(speed); + stars = twodecimals(stars); + f64 pp = twodecimals(res.pp); + f64 aim_pp = twodecimals(res.aim_pp); + f64 speed_pp = twodecimals(res.speed_pp); + f64 acc_pp = twodecimals(res.acc_pp); + f64 acc_percent = twodecimals(res.acc_percent); printf("o p p a i | v%s\n", version_string); puts("s d n | "); @@ -151,14 +186,14 @@ print_sig(text_print) printf("%" fu16 " circles, %" fu16 " sliders %" fu16 " spinners\n", b.num_circles, b.num_sliders, b.num_spinners); printf("%" fu16 "xmiss\n", misses); - printf("%g%%\n", res.acc_percent); + printf("%g%%\n", acc_percent); printf("scorev%" fu32"\n\n", scoring); printf("%g stars\naim stars: %g, speed stars: %g\n\n", stars, aim, speed); - printf("aim: %g\n", res.aim_pp); - printf("speed: %g\n", res.speed_pp); - printf("accuracy: %g\n", res.acc_pp); + printf("aim: %g\n", aim_pp); + printf("speed: %g\n", speed_pp); + printf("accuracy: %g\n", acc_pp); printf("\nrhythm awkwardness (beta): %g\n", rhythm_awkwardness); @@ -177,17 +212,19 @@ print_sig(text_print) nsingles_threshold, (f32)nsingles_threshold / (b.num_circles + b.num_sliders) * 100.0f); - printf("\n%gpp\n", res.pp); + printf("\n%gpp\n", pp); } // json output internalfn -void print_escaped_json_string(const char* str) +void print_escaped_json_string(FILE* fd, const char* str) { putchar('"'); const char* chars_to_escape = "\\\""; - for (; *str; ++str) { + + for (; *str; ++str) + { // escape all characters in chars_to_escape for (const char* p = chars_to_escape; *p; ++p) { if (*p == *str) { @@ -204,19 +241,19 @@ void print_escaped_json_string(const char* str) internalfn print_sig(json_print) { - printf("{\"oppai_version\":"); - print_escaped_json_string(version_string); + printf("%s", "{\"oppai_version\":"); + print_escaped_json_string(fd, version_string); // first print the artist, title, version and creator like this // since json-string so " and \ needs to be escaped - printf(",\"artist\":"); - print_escaped_json_string(b.artist); - printf(",\"title\":"); - print_escaped_json_string(b.title); - printf(",\"version\":"); - print_escaped_json_string(b.version); - printf(",\"creator\":"); - print_escaped_json_string(b.creator); + printf("%s", ",\"artist\":"); + print_escaped_json_string(fd, b.artist); + printf("%s", ",\"title\":"); + print_escaped_json_string(fd, b.title); + printf("%s", ",\"version\":"); + print_escaped_json_string(fd, b.version); + printf("%s", ",\"creator\":"); + print_escaped_json_string(fd, b.creator); // now print the rest printf( @@ -249,14 +286,15 @@ print_sig(json_print) } // binary output +internalfn bool encode_str(FILE* fd, const char* str) { - if (strlen(str) > 0xFFFF) { - die("encode_str can only handle strings up to FFFF characters"); - return false; - } + u16 len = 0xFFFF; + size_t slen = strlen(str); - u16 len = (u16)strlen(str); + if (slen < 0xFFFF) { + len = (u16)slen; + } if (fwrite(&len, 2, 1, fd) != 1) { perror("fwrite"); @@ -276,46 +314,50 @@ bool encode_str(FILE* fd, const char* str) print_sig(binary_print) { - if (!freopen(0, "wb", stdout)) { - perror(0); + if (!freopen(0, "wb", fd)) { + perror("freopen"); exit(1); } u32 binary_output_version = 2; // TODO: shorten this with macros - putc('\0', stdout); - putc('\0', stdout); // is_struct - fwrite(&binary_output_version, 4, 1, stdout); - encode_str(stdout, version_string); chk(); - encode_str(stdout, b.artist); chk(); - encode_str(stdout, b.title); chk(); - encode_str(stdout, b.version); chk(); - encode_str(stdout, b.creator); chk(); - encode_str(stdout, mods_str ? mods_str : ""); chk(); - fwrite(&b.od, sizeof(f32), 1, stdout); - fwrite(&b.ar, sizeof(f32), 1, stdout); - fwrite(&b.cs, sizeof(f32), 1, stdout); - fwrite(&b.hp, sizeof(f32), 1, stdout); - fwrite(&combo, 2, 1, stdout); - fwrite(&b.max_combo, 2, 1, stdout); - fwrite(&b.num_circles, 2, 1, stdout); - fwrite(&b.num_sliders, 2, 1, stdout); - fwrite(&b.num_spinners, 2, 1, stdout); - fwrite(&misses, 2, 1, stdout); - fwrite(&scoring, 4, 1, stdout); + fputc('\0', fd); + fputc('\0', fd); // is_struct + fwrite(&binary_output_version, 4, 1, fd); + if (!encode_str(fd, version_string) || + !encode_str(fd, b.artist) || + !encode_str(fd, b.title) || + !encode_str(fd, b.version) || + !encode_str(fd, b.creator) || + !encode_str(fd, mods_str ? mods_str : "")) + { + exit(1); + } + + fwrite(&b.od, sizeof(f32), 1, fd); + fwrite(&b.ar, sizeof(f32), 1, fd); + fwrite(&b.cs, sizeof(f32), 1, fd); + fwrite(&b.hp, sizeof(f32), 1, fd); + fwrite(&combo, 2, 1, fd); + fwrite(&b.max_combo, 2, 1, fd); + fwrite(&b.num_circles, 2, 1, fd); + fwrite(&b.num_sliders, 2, 1, fd); + fwrite(&b.num_spinners, 2, 1, fd); + fwrite(&misses, 2, 1, fd); + fwrite(&scoring, 4, 1, fd); f32 tmp = (f32)stars; - fwrite(&tmp, sizeof(f32), 1, stdout); + fwrite(&tmp, sizeof(f32), 1, fd); tmp = (f32)speed; - fwrite(&tmp, sizeof(f32), 1, stdout); + fwrite(&tmp, sizeof(f32), 1, fd); tmp = (f32)aim; - fwrite(&tmp, sizeof(f32), 1, stdout); + fwrite(&tmp, sizeof(f32), 1, fd); tmp = (f32)res.pp; - fwrite(&tmp, sizeof(f32), 1, stdout); + fwrite(&tmp, sizeof(f32), 1, fd); } #ifndef __GNUC__ @@ -351,7 +393,7 @@ __attribute__ ((aligned (1), packed)); print_sig(binary_struct_print) { - if (!freopen(0, "wb", stdout)) { + if (!freopen(0, "wb", fd)) { perror(0); exit(1); } @@ -382,13 +424,16 @@ print_sig(binary_struct_print) d.aim = (f32)aim; d.pp = (f32)res.pp; - fwrite(&d, sizeof(binary_output_data), 1, stdout); + fwrite(&d, sizeof(binary_output_data), 1, fd); } // --- typedef print_sig(print_callback); #undef print_sig +#undef printf +#undef puts +#undef putchar struct output_module { @@ -396,6 +441,7 @@ struct output_module print_callback* print; }; +globvar output_module modules[] = { { "text", text_print }, @@ -405,6 +451,12 @@ output_module modules[] = { 0, 0 } }; +OPPAIAPI +output_module* get_output_modules() { + return modules; +} + +OPPAIAPI output_module* get_output_module(const char* name) { for (output_module* m = modules; m->name; ++m) @@ -419,6 +471,10 @@ output_module* get_output_module(const char* name) // ----------------------------------------------------------------------------- +#ifndef OPPAI_LIB +const size_t bufsize = 2000000; +globvar char buf[bufsize]; + int main(int argc, char* argv[]) { // TODO: abstract error outputting into the output modules @@ -449,7 +505,7 @@ int main(int argc, char* argv[]) printf("output_module: the module that will be used to output the " "results (defaults to text). currently available modules: "); - for (output_module* m = modules; m->name; ++m) { + for (output_module* m = get_output_modules(); m->name; ++m) { printf("%s ", m->name); } @@ -469,6 +525,8 @@ int main(int argc, char* argv[]) return 1; } + // --- + #if OPPAI_PROFILING const int prid = 0; #endif @@ -488,11 +546,19 @@ int main(int argc, char* argv[]) profile_init(); + // --- + profile(prid, "beatmap parse"); - beatmap::parse(argv[1], b, no_cache); - chk(); + + oppai_ctx ctx; + beatmap b(&ctx); + beatmap::parse(argv[1], b, buf, bufsize, no_cache); + chk(&ctx); + + // --- profile(prid, "arguments parse"); + char* output_module_name = (char*)"text"; char* mods_str = 0; f64 acc = 0; @@ -509,7 +575,9 @@ int main(int argc, char* argv[]) bool no_awkwardness = false; dbgputs("\nparsing arguments"); - for (int i = 2; i < argc; i++) { + + for (int i = 2; i < argc; i++) + { char suff[64] = {0}; char* a = argv[i]; @@ -632,27 +700,31 @@ int main(int argc, char* argv[]) } printf(">%s\n", a); - die("Invalid parameter"); + die(&ctx, "Invalid parameter"); break; } - chk(); + chk(&ctx); // --- #ifdef SHOW_BEATMAP - print_beatmap(); - chk(); + print_beatmap(b, &ctx); + chk(&ctx); #endif profile(prid, "diff calc"); b.apply_mods(mods); - chk(); + chk(&ctx); + + d_calc_ctx dctx(&ctx); u16 nsingles = 0, nsingles_timing = 0, nsingles_threshold = 0; - f64 aim, speed, rhythm_complexity = 0; + f64 aim = 0, speed = 0, rhythm_complexity = 0; + f64 stars = d_calc( + &dctx, b, &aim, &speed, @@ -662,26 +734,32 @@ int main(int argc, char* argv[]) &nsingles_threshold, (i32)((60000.0f / single_max_bpm) / 2) ); - chk(); + chk(&ctx); pp_calc_result res = no_percent ? - pp_calc(aim, speed, b, mods, combo, misses, 0xFFFF, c100, c50, scoring) - : pp_calc_acc(aim, speed, b, acc, mods, combo, misses, scoring); + pp_calc( + &ctx, + aim, speed, b, mods, + combo, misses, + 0xFFFF, c100, c50, + scoring + ) + : pp_calc_acc(&ctx, aim, speed, b, acc, mods, combo, misses, scoring); - chk(); + chk(&ctx); // --- profile(prid, "output"); output_module* m = get_output_module(output_module_name); if (!m) { - die("The specified output module does not exist"); + die(&ctx, "The specified output module does not exist"); } - chk(); + chk(&ctx); - m->print(mods_str, combo, misses, scoring, + m->print(stdout, mods_str, combo, misses, scoring, stars, aim, speed, rhythm_complexity, - nsingles, nsingles_timing, nsingles_threshold, res); + nsingles, nsingles_timing, nsingles_threshold, res, b); // --- @@ -691,10 +769,12 @@ int main(int argc, char* argv[]) return 0; } +#endif #ifdef SHOW_BEATMAP internalfn -void print_beatmap() { +void print_beatmap(beatmap& b, oppai_ctx* ctx) +{ printf( "Format version: %" fi32 "\n" "Stack Leniency: %g\n" @@ -729,8 +809,8 @@ void print_beatmap() { printf("\n> %" fu32 " hit objects\n", (u32)b.num_objects); - for (size_t i = 0; i < b.num_objects; i++) { - + for (size_t i = 0; i < b.num_objects; i++) + { hit_object& ho = b.objects[i]; switch (ho.type) { case obj::circle: @@ -759,7 +839,7 @@ void print_beatmap() { } default: - die("Invalid object type"); + die(ctx, "Invalid object type"); return; } } diff --git a/math.cc b/math.cc index 2e7ff4f..7de7e1a 100644 --- a/math.cc +++ b/math.cc @@ -3,17 +3,24 @@ class v2f public: f32 x, y; +#if defined(_DEBUG) || defined(SHOW_BEATMAP) #define i() memset(buf, 0, sizeof(buf)) +#else +#define i() +#endif v2f(f32 x, f32 y) : x(x), y(y) { i(); } v2f() : x(0), y(0) { i(); } v2f(f32 v) : x(v), y(v) { i(); } +#undef i +#if defined(_DEBUG) || defined(SHOW_BEATMAP) const char* str() { sprintf(buf, "(%g %g)", x, y); return buf; } +#endif f32 len() const { return sqrt(x * x + y * y); @@ -32,10 +39,16 @@ class v2f #undef do_op +#if defined(_DEBUG) || defined(SHOW_BEATMAP) protected: // this is used for formatting with str() // without having to pass copies of the string around // obviously not thread safe - char buf[42]; + static char buf[42]; }; +char v2f::buf[42]; +#else +}; +#endif + diff --git a/pp_calc.cc b/pp_calc.cc index 05ee74e..6d35aa8 100644 --- a/pp_calc.cc +++ b/pp_calc.cc @@ -44,9 +44,10 @@ struct pp_calc_result // c100, c50: number of 100s and 50s. // score_version: 1 or 2, affects accuracy pp. -internalfn +OPPAIAPI pp_calc_result pp_calc( + oppai_ctx* ctx, f64 aim, f64 speed, beatmap& b, u32 used_mods=mods::nomod, u16 combo = 0xFFFF, u16 misses = 0, u16 c300 = 0xFFFF, @@ -75,13 +76,17 @@ pp_calc( u16 total_hits = c300 + c100 + c50 + misses; - if (total_hits != b.num_objects) { - dbgprintf("warning: total hits(%" fu16 ") don't " - "match hit-object count (%zd)\n", total_hits, b.num_objects); + if (total_hits != b.num_objects) + { + dbgprintf( + "warning: total hits(%" fu16 ") don't match hit-object count " + "(%" fu32 ")\n", + total_hits, (u32)b.num_objects + ); } if (score_version != 1 && score_version != 2) { - die("This score version does not exist or isn't supported"); + die(ctx, "This score version does not exist or isn't supported"); return res; } @@ -239,9 +244,11 @@ pp_calc( // misses: amount of misses // score_version: 1 or 2, affects accuracy pp. +OPPAIAPI pp_calc_result pp_calc_acc( - f64 aim, f64 speed, beatmap& b, f64 acc_percent, + oppai_ctx* ctx, + f64 aim, f64 speed, beatmap& b, f64 acc_percent = 100.0, u32 used_mods=mods::nomod, u16 combo = 0xFFFF, u16 misses = 0, u32 score_version = 1) { @@ -274,7 +281,13 @@ pp_calc_acc( u16 c300 = (u16)b.num_objects - c100 - c50 - misses; - return pp_calc(aim, speed, b, used_mods, combo, misses, c300, c100, c50, - score_version); + return + pp_calc( + ctx, + aim, speed, b, used_mods, + combo, misses, + c300, c100, c50, + score_version + ); } diff --git a/profiler.cc b/profiler.cc index bd7ef8c..50a976f 100644 --- a/profiler.cc +++ b/profiler.cc @@ -4,6 +4,7 @@ #include // profiling is linux-only for now since it's mainly for myself +internalfn f64 time_now() { struct timespec t; @@ -20,8 +21,8 @@ f64 time_now() #define MAX_PROFILERS 3 -char const* profile_last_name[MAX_PROFILERS]; -f64 profile_last[MAX_PROFILERS]; +globvar char const* profile_last_name[MAX_PROFILERS]; +globvar f64 profile_last[MAX_PROFILERS]; struct iterations { @@ -32,13 +33,15 @@ struct iterations }; // TODO: don't use map -std::map profile_iterations[MAX_PROFILERS]; +globvar std::map profile_iterations[MAX_PROFILERS]; +internalfn void profile_init() { memset(profile_last_name, 0, sizeof(profile_last_name)); memset(profile_last, 0, sizeof(profile_last)); } +internalfn void profile(int i, char const* name) { if (i > MAX_PROFILERS - 1) { @@ -59,6 +62,7 @@ void profile(int i, char const* name) profile_last_name[i] = name; } +internalfn void profile_end() { for (int i = 0; i < MAX_PROFILERS; ++i) diff --git a/win/build.bat b/win/build.bat index 27c5233..db20f50 100644 --- a/win/build.bat +++ b/win/build.bat @@ -10,6 +10,7 @@ cl -D_CRT_SECURE_NO_WARNINGS=1 ^ -nologo -MT -Gm- -GR- -EHsc -W4 -WX ^ -wd4201 ^ -wd4100 ^ + -F8000000 ^ ..\main.cc ^ -Feoppai.exe ^ Advapi32.lib