diff --git a/runtime/rtss_decide.h b/runtime/rtss_decide.h index 682a61fa..997244a7 100644 --- a/runtime/rtss_decide.h +++ b/runtime/rtss_decide.h @@ -80,6 +80,7 @@ static inline double rtss_fps(const RtssApp *a) { #define RTSS_OVER_SLACK 1.05 // above limit * this, RTSS is taken not to hold the game to it #define RTSS_FRESH_NS 2500000000ull // a period is stale after this, or 2.5 times its own length if longer #define RTSS_HOLD_IDLE_NS 200000000ull // no host present for this long: RTSS holds nothing (vk_lowlat.c HOLD_IDLE_NS) +#define RTSS_SETTLE_POLLS 2 // polls in a row a want must stay before it acts on the profile enum { RTSS_FAIL_NONE, RTSS_FAIL_LIBRARY, RTSS_FAIL_READONLY, RTSS_FAIL_NOT_LIMITING }; // out of it for the run enum { RTSS_DO_NOTHING, RTSS_DO_APPLY, RTSS_DO_PUT_BACK }; @@ -97,6 +98,8 @@ enum { RTSS_WHY_UPDATE, // RTSS has not read the profile the game wrote yet RTSS_WHY_NOT_COUNTING, // RTSS has measured no period of the game's frames lately RTSS_WHY_IDLE, // no picture went out lately (a minimised window, a frame hold): RTSS has none to hold + RTSS_WHY_SETTLING, // a new want: it reaches the profile once it stayed RTSS_SETTLE_POLLS polls + RTSS_WHY_NO_PRESENT, // no picture went out yet: nothing is written before the first }; // What the worker keeps between polls. @@ -109,8 +112,10 @@ typedef struct { int held; // the poll before stood aside: a fresh period now shows how RTSS holds the game uint32_t t1, span; // the end and length (ms) of the newest period seen uint64_t fresh_ns; // when a new period was last seen; 0 never + int64_t seen; // the want of the poll before + int polls; // polls in a row with that want } RtssState; -#define RTSS_STATE_INIT {-1, -1, 0, 0, RTSS_FAIL_NONE, 0, 0, 0, 0} +#define RTSS_STATE_INIT {-1, -1, 0, 0, RTSS_FAIL_NONE, 0, 0, 0, 0, RTSS_UNSET, 0} // One poll's view. typedef struct { @@ -144,10 +149,13 @@ static inline int rtss_wants_hooks(const RtssState *s, const RtssIn *in) { return in->want >= 0 && !s->fail && in->app.running; } -// The decision of one poll, in this order: no want does nothing; a kept-out want puts back what this run wrote (once -// the hooks are loaded); a failure lasts the run; RTSS not running leaves the profile for its return; a library that -// did not load fails the run; a refused limit stays with the game; a new limit (rtss_target) goes into the profile at -// once. The game's limiter stands aside only once RTSS is seen to hold the game: the profile held the limit at the poll +// The decision of one poll, in this order: no want does nothing; a want acts on the profile only once it stayed +// RTSS_SETTLE_POLLS polls in a row, so a want that flips (a driver's low latency on and off at each swapchain rebuild, +// vk_lowlat.c) does not write and remove the profile at each flip, while the game's limiter takes a new want at once +// (rtss_limit_active). A settled kept-out want puts back what this run wrote (once the hooks are loaded); a failure +// lasts the run; RTSS not running leaves the profile for its return; a library that did not load fails the run; +// nothing is written before the first picture went out; a refused limit stays with the game; a settled new limit +// (rtss_target) goes into the profile. The game's limiter stands aside only once RTSS is seen to hold the game: the profile held the limit at the poll // before (RTSS reads it after the write), RTSS lists the game with its Vulkan presents, has read the profile // (RTSS_UPDATE_ASKED clear) and measured a period of the game's frames lately (a fresh one, rtss_fresh), no movie plays // (rtss_target) and a picture went out within RTSS_HOLD_IDLE_NS. Each fresh period after a poll that stood aside is @@ -163,16 +171,21 @@ static inline RtssOut rtss_decide(RtssState *s, const RtssIn *in) { if (fresh) s->t1 = a->t1, s->span = a->t1 - a->t0, s->fresh_ns = in->now_ns; int held = s->held; s->held = 0; // until this poll stands aside again + s->polls = w == s->seen ? s->polls + 1 : 1; + s->seen = w; + int settled = s->polls >= RTSS_SETTLE_POLLS; if (w < 0) { - if (in->hooks == 1 && s->changed) o.act = RTSS_DO_PUT_BACK; + if (settled && in->hooks == 1 && s->changed) o.act = RTSS_DO_PUT_BACK; s->over = 0; return o.why = RTSS_WHY_OUT, o; } if (s->fail) return o.why = RTSS_WHY_FAILED, o; if (!a->running) return o.why = RTSS_WHY_NOT_RUNNING, o; if (in->hooks == 0) return s->fail = RTSS_FAIL_LIBRARY, o.why = RTSS_WHY_FAILED, o; + if (!in->hosted_ns) return o.why = RTSS_WHY_NO_PRESENT, o; int64_t t = o.mhz = rtss_target(w, in->movie_mhz); if (t == s->refused) return o.why = RTSS_WHY_REFUSED, o; + if (t != s->applied && !settled) return o.why = RTSS_WHY_SETTLING, o; if (t != s->applied) { // RTSS reads it after the write: a later poll sees whether it holds o.act = RTSS_DO_APPLY; o.why = in->movie_mhz ? RTSS_WHY_MOVIE : a->listed ? RTSS_WHY_UPDATE : RTSS_WHY_NOT_LISTED; @@ -266,6 +279,8 @@ static inline void rtss_why_text(char *out, size_t n, const RtssOut *o, int fail else if (o->why == RTSS_WHY_NOT_COUNTING) snprintf(out, n, "RivaTuner Statistics Server has measured no frames of the game lately"); else if (o->why == RTSS_WHY_IDLE) snprintf(out, n, "no picture went out lately: RivaTuner has none to hold"); + else if (o->why == RTSS_WHY_SETTLING) snprintf(out, n, "a new limit: RivaTuner Statistics Server gets it once it stays"); + else if (o->why == RTSS_WHY_NO_PRESENT) snprintf(out, n, "no picture has gone out yet"); else snprintf(out, n, "RivaTuner Statistics Server has not hooked the game yet"); } diff --git a/tests/rtss_limit_check.c b/tests/rtss_limit_check.c index 038f9467..3e75019d 100644 --- a/tests/rtss_limit_check.c +++ b/tests/rtss_limit_check.c @@ -190,14 +190,17 @@ static void why_is(const RtssOut *o, const RtssState *s, const char *want, int l #define WHY(o, r, text) why_is(&(o), &(r).s, text, __LINE__) // RTSS's injection delay: it runs but has not hooked the game for 30 polls. The limit goes into the profile at the -// first poll, with no second write. Once RTSS lists the game the game's limiter stands aside only after RTSS has seen -// its Vulkan presents, read the profile and measured a period of its frames lately. +// second poll (the first sees the want new), with no second write. Once RTSS lists the game the game's limiter stands +// aside only after RTSS has seen its Vulkan presents, read the profile and measured a period of its frames lately. static void injection_delay(void) { Run r; run_init(&r); r.listed = 0; RtssOut o = step(&r, 60000); - CHECK(o.act == RTSS_DO_APPLY && !o.holds && o.why == RTSS_WHY_NOT_LISTED, "written at once, not held yet"); + CHECK(o.act == RTSS_DO_NOTHING && !o.holds && o.why == RTSS_WHY_SETTLING, "a new want: not written yet"); + WHY(o, r, "a new limit: RivaTuner Statistics Server gets it once it stays"); + o = step(&r, 60000); + CHECK(o.act == RTSS_DO_APPLY && !o.holds && o.why == RTSS_WHY_NOT_LISTED, "written, not held yet"); CHECK(r.st.firsts == 1 && r.s.applied == 60000 && r.s.changed, "the first write of the run"); for (int i = 0; i < 30; i++) o = step(&r, 60000); CHECK(r.st.applies == 1 && !o.holds && o.why == RTSS_WHY_NOT_LISTED, "no write while not listed"); @@ -227,6 +230,7 @@ static void quit(void) { Run r; run_init(&r); step(&r, 60000); + step(&r, 60000); RtssOut o = step(&r, 60000); CHECK(o.holds, "held"); r.running = 0; @@ -243,6 +247,7 @@ static void readonly(void) { Run r; run_init(&r); r.st.readonly = 1; + step(&r, 60000); RtssOut o = step(&r, 60000); CHECK(o.act == RTSS_DO_APPLY && !o.holds && o.why == RTSS_WHY_FAILED && r.s.fail == RTSS_FAIL_READONLY, "failed"); CHECK(!r.s.changed, "nothing of the run in the profile"); @@ -252,27 +257,32 @@ static void readonly(void) { } // A limit out of RTSS's range: refused and kept by the game; the earlier limit of this run is cleared to none; a limit -// in range goes to RTSS at once. +// in range goes to RTSS once it stays. static void refused(void) { Run r; run_init(&r); r.st.refuse_above = 200000; step(&r, 60000); + step(&r, 60000); + step(&r, 240000); RtssOut o = step(&r, 240000); CHECK(o.act == RTSS_DO_APPLY && !o.holds && o.why == RTSS_WHY_REFUSED, "refused"); CHECK(r.s.refused == 240000 && r.s.applied == 0 && r.st.clears == 1 && r.st.holds == 0, "the 60 cleared to none"); for (int i = 0; i < 5; i++) o = step(&r, 240000); CHECK(r.st.applies == 2 && o.why == RTSS_WHY_REFUSED && !o.holds, "not written again"); o = step(&r, 144000); - CHECK(o.act == RTSS_DO_APPLY && !o.holds && r.st.holds == 144000, "144 at once"); + CHECK(o.act == RTSS_DO_NOTHING && o.why == RTSS_WHY_SETTLING, "144 settles first"); + o = step(&r, 144000); + CHECK(o.act == RTSS_DO_APPLY && !o.holds && r.st.holds == 144000, "144 written"); o = step(&r, 144000); CHECK(o.holds, "held from the next poll"); CHECK(r.st.firsts == 1, "one first write in the run (%d)", r.st.firsts); o = step(&r, 240000); - CHECK(o.act == RTSS_DO_NOTHING && o.why == RTSS_WHY_REFUSED, "the refused limit stays with the game"); + CHECK(o.act == RTSS_DO_NOTHING && o.why == RTSS_WHY_REFUSED, "the refused limit stays with the game at once"); // refused as the run's first write: the profile is the run's from then on run_init(&r); r.st.refuse_above = 200000; + step(&r, 240000); o = step(&r, 240000); CHECK(o.why == RTSS_WHY_REFUSED && r.s.changed && r.st.clears == 1, "a first refusal clears too, as it stands"); } @@ -319,7 +329,7 @@ static void frame_count(void) { // two periods over, then one under: the count starts again run_init(&r); r.frames = 144; - for (int i = 0; i < 6; i++) step(&r, 60000); + for (int i = 0; i < 7; i++) step(&r, 60000); CHECK(r.s.over == 2, "two periods over (%d)", r.s.over); r.frames = 60; for (int i = 0; i < 2; i++) step(&r, 60000); @@ -327,22 +337,25 @@ static void frame_count(void) { // a want that changed while the poll ran: nothing decided, the count waits run_init(&r); r.frames = 144; - for (int i = 0; i < 4; i++) step(&r, 60000); + for (int i = 0; i < 5; i++) step(&r, 60000); o = step_ex(&r, 60000, 0); CHECK(o.why == RTSS_WHY_NEWER && !o.holds && o.act == RTSS_DO_NOTHING && r.s.over == 1, "a newer want: no count"); } // No picture went out for 200 ms (a minimised window, a frame hold) or none yet: RTSS has nothing to hold, and the -// game's limiter takes the limit back until pictures go out again. +// game's limiter takes the limit back until pictures go out again. Before the first picture nothing is written. static void idle(void) { Run r; run_init(&r); r.presenting = 0; - step(&r, 60000); - RtssOut o = step(&r, 60000); - CHECK(!o.holds && o.why == RTSS_WHY_IDLE, "no host present yet"); + RtssOut o; + for (int i = 0; i < 4; i++) o = step(&r, 60000); + CHECK(!o.holds && o.why == RTSS_WHY_NO_PRESENT && !r.st.applies, "no host present yet: nothing written"); + WHY(o, r, "no picture has gone out yet"); r.presenting = 1; o = step(&r, 60000); + CHECK(o.act == RTSS_DO_APPLY && !o.holds, "written at the first picture, the want having stayed"); + o = step(&r, 60000); CHECK(o.holds, "held once pictures go out"); r.presenting = 0; r.hosted_ns = r.now_ns + 500000000ull - 199000000ull; // the next poll comes 199 ms after the last present @@ -362,16 +375,22 @@ static void kept_out(void) { Run r; run_init(&r); step(&r, 60000); + step(&r, 60000); RtssOut o = step(&r, RTSS_OUT_FRAMEGEN); + CHECK(o.act == RTSS_DO_NOTHING && o.why == RTSS_WHY_OUT && !o.holds && r.st.holds == 60000, + "the game's limiter at once, the profile once the want stays"); + o = step(&r, RTSS_OUT_FRAMEGEN); CHECK(o.act == RTSS_DO_PUT_BACK && o.why == RTSS_WHY_OUT && !o.holds && r.st.holds == -1, "put back"); CHECK(r.st.last_put_back_why == RTSS_WHY_OUT, "the put back says why"); o = step(&r, RTSS_OUT_FRAMEGEN); CHECK(o.act == RTSS_DO_NOTHING && r.st.put_backs == 1, "once"); - o = step(&r, RTSS_OUT_LOWLAT); - CHECK(o.act == RTSS_DO_NOTHING && o.mhz == RTSS_OUT_LOWLAT, "a second reason: nothing more"); + for (int i = 0; i < 3; i++) o = step(&r, RTSS_OUT_LOWLAT); + CHECK(o.act == RTSS_DO_NOTHING && o.mhz == RTSS_OUT_LOWLAT && r.st.put_backs == 1, "a second reason: nothing more"); WHY(o, r, "RivaTuner Statistics Server kept out: the driver's low latency"); o = step(&r, 60000); - CHECK(o.act == RTSS_DO_APPLY && !o.holds && r.st.firsts == 2, "written at once, the run's first write again"); + CHECK(o.act == RTSS_DO_NOTHING && o.why == RTSS_WHY_SETTLING, "a limit again: settles first"); + o = step(&r, 60000); + CHECK(o.act == RTSS_DO_APPLY && !o.holds && r.st.firsts == 2, "written, the run's first write again"); o = step(&r, 60000); CHECK(o.holds, "held from the next poll"); // kept out from the start: the hooks library is never wanted, and nothing is written or put back @@ -380,13 +399,31 @@ static void kept_out(void) { CHECK(!r.st.loaded && !r.st.applies && !r.st.put_backs && o.why == RTSS_WHY_OUT, "read only"); } -// Low latency switching on and off at every poll: each switch writes or puts back, as it stands. +// Low latency switching on and off at every poll (a swapchain rebuild, the time before the first swapchain): the want +// never stays two polls, so the profile keeps the limit written before, and the game's limiter holds the frames while +// the want is new. From the start, the same flips write nothing. static void churn(void) { Run r; run_init(&r); step(&r, 60000); + step(&r, 60000); + RtssOut o; + for (int i = 0; i < 20; i++) { + o = step(&r, i & 1 ? 60000 : RTSS_OUT_LOWLAT); + CHECK(!(i & 1) ? !o.holds : 1, "kept out at poll %d: the game's limiter", i); + } + CHECK(r.st.applies == 1 && !r.st.put_backs && r.st.holds == 60000, "%d writes, %d put backs", r.st.applies, + r.st.put_backs); + o = step(&r, 60000); + CHECK(o.holds && r.st.applies == 1, "held once the limit stays"); + run_init(&r); for (int i = 0; i < 20; i++) step(&r, i & 1 ? 60000 : RTSS_OUT_LOWLAT); - CHECK(r.st.applies == 11 && r.st.put_backs == 10, "%d writes, %d put backs", r.st.applies, r.st.put_backs); + CHECK(!r.st.applies && !r.st.put_backs, "from the start: nothing written"); + // a flip every two polls is a want that stays: each one acts + run_init(&r); + for (int i = 0; i < 8; i++) step(&r, i & 2 ? RTSS_OUT_LOWLAT : 60000); + CHECK(r.st.applies == 2 && r.st.put_backs == 2, "two polls each: %d writes, %d put backs", r.st.applies, + r.st.put_backs); } // A movie under RTSS: the want stays the play limit and the game's pacer holds the movie. At a limit of 144 the profile @@ -396,6 +433,7 @@ static void movie(void) { Run r; run_init(&r); step(&r, 144000); + step(&r, 144000); r.movie_mhz = 60000; RtssOut o = step(&r, 144000); CHECK(o.act == RTSS_DO_NOTHING && !o.holds && o.why == RTSS_WHY_MOVIE, "a movie at 144: no write, the pacer's"); @@ -407,6 +445,7 @@ static void movie(void) { CHECK(o.act == RTSS_DO_NOTHING && o.holds && o.why == RTSS_WHY_HOLDS && r.st.applies == 1, "held again after it"); run_init(&r); step(&r, 40000); + step(&r, 40000); r.movie_mhz = 60000; o = step(&r, 40000); CHECK(o.act == RTSS_DO_APPLY && o.mhz == 0 && !o.holds && o.why == RTSS_WHY_MOVIE && r.st.holds == 0, diff --git a/tests/rtss_worker_check.c b/tests/rtss_worker_check.c index e6b8b347..79efcdeb 100644 --- a/tests/rtss_worker_check.c +++ b/tests/rtss_worker_check.c @@ -379,6 +379,11 @@ static void ask(int64_t mhz) { // a want at a present; the first one that star CHECK(WaitForMultipleObjects(2, h, FALSE, 5000) != WAIT_TIMEOUT, "the worker did not finish its first poll"); } } +static void settle(int64_t mhz) { // a new want, then the polls it takes to reach the profile (RTSS_SETTLE_POLLS) + ask(mhz); + poll(); + poll(); +} static void show(const char *what) { printf(" %s: profile %s %lu/%lu; active %d; state %d; saves %ld deletes %ld updates %ld loads %ld\n", what, own.exists ? "exists," : "absent,", own.limit, own.denom, rtss_limit_active(), rtss_limit_state(), saves, @@ -498,8 +503,7 @@ static void kept_lowlat_then_limit(void) { "the driver's low latency"), "the two-limiter line names the game's profile and low latency"); CHECK(!loads, "kept out, the hooks library is never loaded"); - ask(60000); - poll(); + settle(60000); show("low latency off, limit 60"); CHECK(loads == 1 && own.exists && own.limit == 60, "the limit goes into the profile once RTSS is let in"); poll(); @@ -525,6 +529,7 @@ static void active_race(void) { app_set(1, 0xA, 1000, 2000, 60); ask(60000); poll(); + poll(); CHECK(rtss_limit_active(), "RTSS limits at 60"); quiet = 1, free_run = 1; SetEvent(tick); @@ -555,6 +560,7 @@ static void active_logged(void) { app_set(1, 0xA, 1000, 2000, 60); during_save = framegen_on; ask(60000); + poll(); show("frame generation switched on during the first write"); CHECK(own.exists && own.limit == 60, "the limit went into the profile"); CHECK(!rtss_limit_active(), "the game's limiter paces the kept-out want"); @@ -589,11 +595,9 @@ static void round_logged_whole(void) { show("display at 59.94 Hz, RTSS without a denominator"); CHECK(own.limit == 60, "rounded to whole frames"); CHECK(logged("profile ocarina.exe limits to 60/1 (60.000 fps; the game asked 59.940)"), "the log names 60/1"); - ask(143856); - poll(); + settle(143856); CHECK(logged("profile ocarina.exe limits to 144/1 (144.000 fps; the game asked 143.856)"), "the log names 144/1"); - ask(60000); - poll(); + settle(60000); CHECK(logged("profile ocarina.exe limits to 60.000 fps\n"), "a whole limit as it is"); } @@ -712,9 +716,9 @@ static void refused_run(void) { app_set(1, 0xA, 1000, 2000, 60); ask(60000); poll(); - CHECK(own.limit == 60 && rtss_limit_active(), "60 taken"); - ask(240000); poll(); + CHECK(own.limit == 60 && rtss_limit_active(), "60 taken"); + settle(240000); show("240 refused"); CHECK(own.exists && own.limit == 0, "the earlier limit of this run is cleared to none"); CHECK(!rtss_limit_active() && rtss_limit_state() == 0, "the game's limiter keeps 240"); @@ -722,15 +726,15 @@ static void refused_run(void) { poll(); poll(); CHECK(saves == s0, "the refused limit is not written again"); - ask(144000); - poll(); + settle(144000); show("144 after the refusal"); - CHECK(own.limit == 144, "144 written at once"); + CHECK(own.limit == 144, "144 written once it stayed"); poll(); CHECK(rtss_limit_active(), "and held from the next poll"); ask(240000); + CHECK(!rtss_limit_active(), "240 with the game at once"); poll(); - CHECK(!rtss_limit_active(), "240 stays with the game"); + CHECK(!rtss_limit_active() && own.limit == 144, "240 stays with the game"); } // RTSS lists the game but lets it run well over the limit: three fresh periods in a row over 1.05 times the limit, each @@ -738,15 +742,14 @@ static void refused_run(void) { static void over_run(void) { app_set(1, 0xA, 1000, 2000, 144); ask(60000); - for (int i = 0; i < 3; i++) poll(); + for (int i = 0; i < 4; i++) poll(); CHECK(rtss_limit_active() && own.exists, "two periods over: RTSS still limits"); poll(); show("three periods at 144 fps with the limit at 60"); CHECK(rtss_limit_state() == 3 && !rtss_limit_active(), "the menu says RTSS does not limit the game"); CHECK(!own.exists, "the profile is put back"); CHECK(logged("RivaTuner Statistics Server measures 144.0 fps with its limit at 60.000 fps"), "logged"); - ask(30000); - poll(); + settle(30000); CHECK(saves == 1 && rtss_limit_state() == 3, "out for the run"); } // 1.05 times the limit is not caught; a period RTSS no longer moves (the limit lowered, then the window minimised) @@ -796,6 +799,7 @@ static void idle_run(void) { app_set(1, 0xA, 1000, 2000, 60); ask(60000); poll(); + poll(); CHECK(rtss_limit_active(), "RTSS limits"); presenting = 0; poll(); @@ -843,26 +847,36 @@ static void library_run(void) { CHECK(logged("RivaTuner Statistics Server's hooks library did not load"), "logged"); } -// Low latency switching on and off at every poll (the want flips between a limit and kept out): each switch writes or -// puts back the profile, as it stands, and the log numbers each creation and removal so a play-test can count them. +// Low latency switching on and off at every poll (the want flips between a limit and kept out, as at each swapchain +// rebuild): a want acts on the profile only once it stayed two polls, so the flips write and remove nothing, and the +// game's limiter holds each kept-out want at once. A want that stays two polls each time does act, and the log numbers +// each creation and removal so a play-test can count them. static void churn_run(void) { app_set(1, 0xA, 1000, 2000, 60); ask(60000); poll(); + poll(); for (int i = 0; i < 10; i++) { ask(i & 1 ? 60000 : RTSS_OUT_LOWLAT); + if (!(i & 1)) CHECK(!rtss_limit_active(), "kept out at flip %d: the game's limiter at once", i); poll(); } show("ten polls with the want flipping"); + CHECK(created == 1 && removed == 0, "%ld creations, %ld removals", created, removed); poll(); - CHECK(created == 6 && removed == 5, "%ld creations, %ld removals", created, removed); + CHECK(rtss_limit_active(), "RTSS limits once the limit stays"); + for (int i = 0; i < 4; i++) { + settle(RTSS_OUT_LOWLAT); + settle(60000); + } + show("four flips of two polls each"); + CHECK(created == 5 && removed == 4, "%ld creations, %ld removals", created, removed); CHECK(logged("created; it goes away when the game closes (creation 1 this run)\n") && - logged("(creation 6 this run)\n") && !logged("(creation 7"), + logged("(creation 5 this run)\n") && !logged("(creation 6"), "the created lines are numbered"); - CHECK(logged("removed, as before the game started (removal 1 this run)\n") && logged("(removal 5 this run)\n") && - !logged("(removal 6"), + CHECK(logged("removed, as before the game started (removal 1 this run)\n") && logged("(removal 4 this run)\n") && + !logged("(removal 5"), "the removed lines are numbered"); - CHECK(rtss_limit_active(), "RTSS limits after the last want of a limit"); } // The state file: the run's first write records the profile as it was, each later one the value written, and the exit's @@ -874,8 +888,7 @@ static void state_record(void) { show("playing at 60"); CHECK(own.exists && own.limit == 60 && state_has("\nrtss1 0 0 1 60 1 ocarina.exe\n"), "recorded:\n%s", state()); CHECK(!strncmp(state(), "# RivaTuner Statistics Server's profiles", 40), "the header first"); - ask(144000); - poll(); + settle(144000); CHECK(own.limit == 144 && state_has("\nrtss1 0 0 1 144 1 ocarina.exe\n"), "the line follows:\n%s", state()); rtss_limit_restore(); show("after the restore"); @@ -887,11 +900,9 @@ static void state_player(void) { ask(60000); poll(); CHECK(own.limit == 60 && state_has("\nrtss1 1 30 1 60 1 ocarina.exe\n"), "recorded:\n%s", state()); - ask(RTSS_OUT_LOWLAT); // kept out: put back during the run, and the line goes with it - poll(); + settle(RTSS_OUT_LOWLAT); // kept out: put back during the run, and the line goes with it CHECK(own.limit == 30 && !*state(), "put back, no line:\n%s", state()); - ask(60000); - poll(); + settle(60000); CHECK(own.limit == 60 && state_has("\nrtss1 1 30 1 60 1 ocarina.exe\n"), "recorded again:\n%s", state()); rtss_limit_restore(); CHECK(own.exists && own.limit == 30 && !*state(), "the player's 30 back, no line"); @@ -904,6 +915,7 @@ static void state_unwritable(void) { app_set(1, 0xA, 1000, 2000, 60); ask(60000); poll(); + poll(); show("no state file"); CHECK(own.exists && own.limit == 60 && rtss_limit_active(), "RTSS limits as before"); CHECK(logged("rtss-profile.txt could not be written; a crash would leave the game's limit in RivaTuner Statistics "