Enforce that Promise body resolves OR rejects exactly once

We now enforce that the body of a promise returns a "Result" struct. Such a struct can only be obtained by calling "resolve" or "reject".
Additionally, if you call "resolve" or "reject" without using its result, `Q_REQUIRED_RESULT` will kick in and give you a compiler warning. So you either have to return it (which you should do), or you use it in another way, which is impossible to do unconsciously.

The only disadvantage is that you now have to write `return resolve` etc. instead of just `resolve`, but the advantages are bigger.
This commit is contained in:
Casper Jeukendrup 2022-03-26 17:32:20 +01:00 committed by Igor Korsukov
parent f57078bca1
commit 112d29d931
5 changed files with 57 additions and 55 deletions

View file

@ -52,16 +52,16 @@ Promise<AudioOutputParams> AudioOutputHandler::outputParams(const TrackSequenceI
ITrackSequencePtr s = sequence(sequenceId);
if (!s) {
reject(static_cast<int>(Err::InvalidSequenceId), "invalid sequence id");
return reject(static_cast<int>(Err::InvalidSequenceId), "invalid sequence id");
}
RetVal<AudioOutputParams> result = s->audioIO()->outputParams(trackId);
if (!result.ret) {
reject(result.ret.code(), result.ret.text());
return reject(result.ret.code(), result.ret.text());
}
resolve(result.val);
return resolve(result.val);
}, AudioThread::ID);
}
@ -92,10 +92,10 @@ Promise<AudioOutputParams> AudioOutputHandler::masterOutputParams() const
ONLY_AUDIO_WORKER_THREAD;
IF_ASSERT_FAILED(mixer()) {
reject(static_cast<int>(Err::Undefined), "undefined reference to a mixer");
return reject(static_cast<int>(Err::Undefined), "undefined reference to a mixer");
}
resolve(mixer()->masterOutputParams());
return resolve(mixer()->masterOutputParams());
}, AudioThread::ID);
}
@ -125,7 +125,7 @@ Promise<AudioResourceMetaList> AudioOutputHandler::availableOutputResources() co
Promise<AudioResourceMetaList>::Reject /*reject*/) {
ONLY_AUDIO_WORKER_THREAD;
resolve(fxResolver()->resolveAvailableResources());
return resolve(fxResolver()->resolveAvailableResources());
}, AudioThread::ID);
}
@ -138,16 +138,14 @@ Promise<AudioSignalChanges> AudioOutputHandler::signalChanges(const TrackSequenc
ITrackSequencePtr s = sequence(sequenceId);
if (!s) {
reject(static_cast<int>(Err::InvalidSequenceId), "invalid sequence id");
return;
return reject(static_cast<int>(Err::InvalidSequenceId), "invalid sequence id");
}
if (!s->audioIO()->isHasTrack(trackId)) {
reject(static_cast<int>(Err::InvalidTrackId), "no track");
return;
return reject(static_cast<int>(Err::InvalidTrackId), "no track");
}
resolve(s->audioIO()->audioSignalChanges(trackId));
return resolve(s->audioIO()->audioSignalChanges(trackId));
}, AudioThread::ID);
}
@ -158,10 +156,10 @@ Promise<AudioSignalChanges> AudioOutputHandler::masterSignalChanges() const
ONLY_AUDIO_WORKER_THREAD;
IF_ASSERT_FAILED(mixer()) {
reject(static_cast<int>(Err::Undefined), "undefined reference to a mixer");
return reject(static_cast<int>(Err::Undefined), "undefined reference to a mixer");
}
resolve(mixer()->masterAudioSignalChanges());
return resolve(mixer()->masterAudioSignalChanges());
}, AudioThread::ID);
}

View file

@ -57,7 +57,7 @@ Promise<TrackSequenceId> Playback::addSequence()
m_sequences.emplace(newId, std::make_shared<TrackSequence>(newId));
m_sequenceAdded.send(newId);
resolve(std::move(newId));
return resolve(std::move(newId));
}, AudioThread::ID);
}
@ -73,7 +73,7 @@ Promise<TrackSequenceIdList> Playback::sequenceIdList() const
result.push_back(pair.first);
}
resolve(std::move(result));
return resolve(std::move(result));
}, AudioThread::ID);
}

View file

@ -122,16 +122,16 @@ Promise<bool> PlayerHandler::setLoop(const TrackSequenceId sequenceId, const mse
ITrackSequencePtr s = sequence(sequenceId);
if (!s) {
reject(static_cast<int>(Err::InvalidSequenceId), "invalid sequence id");
return reject(static_cast<int>(Err::InvalidSequenceId), "invalid sequence id");
}
Ret result = s->player()->setLoop(fromMsec, toMsec);
if (!result) {
reject(result.code(), result.text());
return reject(result.code(), result.text());
}
resolve(result);
return resolve(result);
}, AudioThread::ID);
}

View file

@ -51,9 +51,9 @@ Promise<TrackIdList> TracksHandler::trackIdList(const TrackSequenceId sequenceId
ITrackSequencePtr s = sequence(sequenceId);
if (s) {
resolve(s->trackIdList());
return resolve(s->trackIdList());
} else {
reject(static_cast<int>(Err::InvalidSequenceId), "invalid sequence id");
return reject(static_cast<int>(Err::InvalidSequenceId), "invalid sequence id");
}
}, AudioThread::ID);
}
@ -66,9 +66,9 @@ Promise<TrackName> TracksHandler::trackName(const TrackSequenceId sequenceId, co
ITrackSequencePtr s = sequence(sequenceId);
if (s) {
resolve(s->trackName(trackId));
return resolve(s->trackName(trackId));
} else {
reject(static_cast<int>(Err::InvalidSequenceId), "invalid sequence id");
return reject(static_cast<int>(Err::InvalidSequenceId), "invalid sequence id");
}
}, AudioThread::ID);
}
@ -84,18 +84,16 @@ Promise<TrackId, AudioParams> TracksHandler::addTrack(const TrackSequenceId sequ
ITrackSequencePtr s = sequence(sequenceId);
if (!s) {
reject(static_cast<int>(Err::InvalidSequenceId), "invalid sequence id");
return;
return reject(static_cast<int>(Err::InvalidSequenceId), "invalid sequence id");
}
RetVal2<TrackId, AudioParams> result = s->addTrack(trackName, playbackData, params);
if (!result.ret) {
reject(result.ret.code(), result.ret.text());
return;
return reject(result.ret.code(), result.ret.text());
}
resolve(result.val1, result.val2);
return resolve(result.val1, result.val2);
}, AudioThread::ID);
}
@ -109,17 +107,16 @@ Promise<TrackId, AudioParams> TracksHandler::addTrack(const TrackSequenceId sequ
ITrackSequencePtr s = sequence(sequenceId);
if (!s) {
reject(static_cast<int>(Err::InvalidSequenceId), "invalid sequence id");
return;
return reject(static_cast<int>(Err::InvalidSequenceId), "invalid sequence id");
}
RetVal2<TrackId, AudioParams> result = s->addTrack(trackName, playbackData, params);
if (!result.ret) {
reject(result.ret.code(), result.ret.text());
return reject(result.ret.code(), result.ret.text());
}
resolve(result.val1, result.val2);
return resolve(result.val1, result.val2);
}, AudioThread::ID);
}
@ -175,7 +172,7 @@ Promise<AudioResourceMetaList> TracksHandler::availableInputResources() const
Promise<AudioResourceMetaList>::Reject /*reject*/) {
ONLY_AUDIO_WORKER_THREAD;
resolve(resolver()->resolveAvailableResources());
return resolve(resolver()->resolveAvailableResources());
}, AudioThread::ID);
}
@ -188,18 +185,16 @@ Promise<AudioInputParams> TracksHandler::inputParams(const TrackSequenceId seque
ITrackSequencePtr s = sequence(sequenceId);
if (!s) {
reject(static_cast<int>(Err::InvalidSequenceId), "invalid sequence id");
return;
return reject(static_cast<int>(Err::InvalidSequenceId), "invalid sequence id");
}
RetVal<AudioInputParams> result = s->audioIO()->inputParams(trackId);
if (!result.ret) {
reject(result.ret.code(), result.ret.text());
return;
return reject(result.ret.code(), result.ret.text());
}
resolve(result.val);
return resolve(result.val);
}, AudioThread::ID);
}

View file

@ -14,13 +14,27 @@ template<typename ... T>
class Promise
{
public:
// Dummy struct, with the purpose to enforce that the body
// of a Promise resolves OR rejects exactly once
struct Result {
private:
Result() = default;
friend struct Resolve;
friend struct Reject;
};
struct Resolve
{
Resolve(Promise<T...> _p)
: p(_p) {}
void operator ()(const T& ... val) const { p.resolve(val ...); }
Q_REQUIRED_RESULT
Result operator ()(const T& ... val) const
{
p.resolve(val ...);
return {};
}
private:
mutable Promise<T...> p;
@ -31,32 +45,27 @@ public:
Reject(Promise<T...> _p)
: p(_p) {}
void operator ()(int code, const std::string& msg) const { p.reject(code, msg); }
Q_REQUIRED_RESULT
Result operator ()(int code, const std::string& msg) const
{
p.reject(code, msg);
return {};
}
private:
mutable Promise<T...> p;
};
template<typename Exec>
Promise(Exec exec, const std::thread::id& th = std::this_thread::get_id())
using Body = std::function<Result(Resolve, Reject)>;
Promise(Body body, const std::thread::id& th = std::this_thread::get_id())
{
Resolve res(*this);
Reject rej(*this);
Async::call(nullptr, [res, rej](Exec exec) mutable {
exec(res, rej);
}, exec, th);
}
template<typename Exec>
Promise(Exec exec)
{
Resolve res(*this);
Reject rej(*this);
Async::call(nullptr, [res, rej](Exec exec) mutable {
exec(res, rej);
}, exec);
Async::call(nullptr, [res, rej](Body body) mutable {
body(res, rej);
}, body, th);
}
Promise(const Promise& p)