mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 16:43:05 -05:00
The 30 ms limit gated all controller output, so a rumble pulse shorter than the interval could be lost and a stop could wait up to 30 ms. Rumble goes out on every output change again, as on main; SDL already skips repeated values and merges consecutive rumble reports. Only the light bar is paced: a changed colour is sent at most every 30 ms, always the latest one, and a colour SDL refuses is retried after the interval. The trigger effect is cached only when SDL_GameControllerSendEffect succeeds, so a refused effect is sent again on the next update as it was before. LightBarPacer and TriggerEffectCache hold these decisions; pad_output_pacing covers changes, retries, a new controller and a light bar animated every frame at 120 Hz. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
90 lines
3.5 KiB
C++
90 lines
3.5 KiB
C++
#include "prx/libSceVideoOut/include/PadOutputPacing.hpp"
|
|
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
|
|
namespace {
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
void Require(bool condition, const char* message) {
|
|
if (condition) return;
|
|
std::fprintf(stderr, "pad_output_pacing: %s\n", message);
|
|
std::exit(1);
|
|
}
|
|
|
|
void LightBarSendsChangesOnly() {
|
|
LightBarPacer pacer;
|
|
const auto start = LightBarPacer::Clock::time_point{} + 1s;
|
|
const LightBarPacer::Color blue{0, 64, 255};
|
|
Require(pacer.Due(blue, start), "the first colour is sent at once");
|
|
pacer.Result(blue, start, true);
|
|
Require(!pacer.Pending(blue), "a colour that was sent is not sent again");
|
|
Require(!pacer.Due(blue, start + 1s), "an unchanged colour stays quiet");
|
|
const LightBarPacer::Color red{255, 0, 0};
|
|
Require(pacer.Pending(red) && !pacer.Due(red, start + 10ms), "a new colour waits for the interval");
|
|
Require(pacer.Due(red, start + LightBarPacer::Interval), "a new colour goes out once the interval has passed");
|
|
}
|
|
|
|
void LightBarRetriesFailures() {
|
|
LightBarPacer pacer;
|
|
const auto start = LightBarPacer::Clock::time_point{} + 1s;
|
|
const LightBarPacer::Color green{0, 255, 0};
|
|
pacer.Result(green, start, false);
|
|
Require(pacer.Pending(green), "a colour SDL refused is still pending");
|
|
Require(!pacer.Due(green, start + 1ms), "a refused colour is not retried on the next frame");
|
|
Require(pacer.Due(green, start + LightBarPacer::Interval), "a refused colour is retried after the interval");
|
|
pacer.Result(green, start + LightBarPacer::Interval, true);
|
|
Require(!pacer.Pending(green), "a retried colour that was sent is done");
|
|
pacer.Reset();
|
|
Require(pacer.Due(green, start + LightBarPacer::Interval), "a new controller gets the current colour");
|
|
}
|
|
|
|
void LightBarAnimationIsPaced() {
|
|
LightBarPacer pacer;
|
|
const auto start = LightBarPacer::Clock::time_point{} + 1s;
|
|
constexpr int frames = 120;
|
|
int sent = 0;
|
|
LightBarPacer::Color color{};
|
|
for (int frame = 0; frame < frames; ++frame) {
|
|
const auto now = start + std::chrono::microseconds(frame * 8333);
|
|
color = {static_cast<std::uint8_t>(frame), 32, 32};
|
|
if (!pacer.Due(color, now)) continue;
|
|
pacer.Result(color, now, true);
|
|
++sent;
|
|
}
|
|
Require(sent >= 30 && sent <= 34, "a colour that changes every frame at 120 Hz goes out about 33 times a second");
|
|
const auto end = start + 1s;
|
|
Require(pacer.Pending(color), "the last colour of the animation is still owed");
|
|
Require(pacer.Due(color, end + LightBarPacer::Interval), "the last colour of the animation is sent when the interval allows");
|
|
}
|
|
|
|
void TriggerEffectRetriesFailures() {
|
|
TriggerEffectCache cache;
|
|
TriggerEffectCache::Effect effect{};
|
|
effect[0] = 0x0C;
|
|
effect[10] = 0x21;
|
|
Require(cache.NeedsSend(effect), "the first effect is sent");
|
|
cache.Result(effect, false);
|
|
Require(cache.NeedsSend(effect), "an effect SDL refused is sent again");
|
|
cache.Result(effect, true);
|
|
Require(!cache.NeedsSend(effect), "an effect that was sent is not repeated");
|
|
auto changed = effect;
|
|
changed[21] = 0x26;
|
|
Require(cache.NeedsSend(changed), "a changed effect is sent");
|
|
cache.Reset();
|
|
Require(cache.NeedsSend(effect), "a new controller gets the current effect");
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
LightBarSendsChangesOnly();
|
|
LightBarRetriesFailures();
|
|
LightBarAnimationIsPaced();
|
|
TriggerEffectRetriesFailures();
|
|
std::puts("pad_output_pacing: ok");
|
|
return 0;
|
|
}
|