This repository has been archived on 2024-06-03. You can view files and clone it, but cannot push or open issues or pull requests.
shared-libraries/ScStwLibraries/sources/scstwrace.cpp

713 lines
22 KiB
C++
Raw Normal View History

/****************************************************************************
2020-06-07 14:43:47 +02:00
** ScStw Libraries
** Copyright (C) 2020 Itsblue development
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/
#include "../headers/scstwrace.h"
ScStwRace::ScStwRace(ScStwSettings *settings, QObject *parent) : QObject(parent)
{
this->state = IDLE;
2020-10-03 17:45:54 +02:00
this->competitionMode = false;
// configure the loop that waits for the sound effect to finish
this->soundPlayer = new ScStwSoundPlayer();
// configure timer that handles the delay between the start commands
this->startDelayTimer = new QTimer(this);
startDelayTimer->setSingleShot(true);
this->startWaitLoop = new QEventLoop(this);
connect(this->startDelayTimer, &QTimer::timeout, this->startWaitLoop, &QEventLoop::quit);
2020-10-02 19:53:07 +02:00
connect(this, &ScStwRace::currentStartDelayChanged, this, &ScStwRace::detailsChanged);
connect(this, &ScStwRace::timersChanged, this, &ScStwRace::detailsChanged);
2020-10-03 11:10:15 +02:00
connect(this, &ScStwRace::stateChanged, this, &ScStwRace::detailsChanged);
2020-10-02 19:53:07 +02:00
// init settings
this->settings = settings;
2020-10-03 17:45:54 +02:00
this->refreshCompetitionMode();
}
// --------------------------
// --- Main Functionality ---
// --------------------------
2020-10-02 15:23:30 +02:00
ScStw::StatusCode ScStwRace::start(bool asyncronous) {
if(this->state == WAITING) {
2020-10-03 11:10:15 +02:00
if(this->getIsReadyForNextState()) {
this->startWaitLoop->exit(LoopManualExit);
return ScStw::Success;
}
else {
return ScStw::TimersNotReadyError;
}
}
else if(this->state != IDLE) {
return ScStw::CurrentStateNotVaildForOperationError;
}
2020-10-03 17:45:54 +02:00
this->refreshCompetitionMode();
2020-10-03 11:10:15 +02:00
if(!this->getIsReadyForNextState())
return ScStw::TimersNotReadyError;
this->setState(PREPAIRING);
2020-04-19 13:09:24 +02:00
if(asyncronous) {
QTimer::singleShot(1, [=](){this->playSoundsAndStartTimers();});
2020-04-19 13:09:24 +02:00
}
else
this->playSoundsAndStartTimers();
2020-04-19 22:39:33 +02:00
return ScStw::Success;
}
2020-10-02 15:23:30 +02:00
ScStw::StatusCode ScStwRace::stop() {
if(this->state != RUNNING && this->state != STARTING) {
return ScStw::CurrentStateNotVaildForOperationError;
}
if(this->competitionMode)
return this->cancel();
qDebug() << "[INFO][RACE] stopping race";
double timeOfStop = QDateTime::currentMSecsSinceEpoch();
2020-10-02 15:23:30 +02:00
ScStw::StatusCode returnCode = ScStw::Success;
foreach(ScStwTimer *speedTimer, this->timers){
if(!speedTimer->stop(timeOfStop) && speedTimer->getState() != ScStwTimer::DISABLED){
2020-04-19 22:39:33 +02:00
returnCode = ScStw::InternalErrorTimerOperationFailed;
}
}
2020-04-19 22:39:33 +02:00
if(returnCode == ScStw::Success) {
this->setState(STOPPED);
}
return returnCode;
}
void ScStwRace::handleTimerStop() {
if(this->state == RUNNING) {
// find out which timer has won
double lowestStoppedTime = -1;
QList<ScStwTimer *> timersWhichHaveWonIds;
// iterate through all timers and find the lowest time taht was stopped
foreach(ScStwTimer * timer, this->timers) {
if(timer->getCurrentTime() > 0 && (timer->getCurrentTime() <= lowestStoppedTime || lowestStoppedTime < 0)) {
// this is the timer with the lowest stopped time
lowestStoppedTime = timer->getCurrentTime();
}
}
// append the timer(s) with the lowest stopped time to the winner list
foreach(ScStwTimer * timer, this->timers) {
if(timer->getCurrentTime() > 0
&& (timer->getCurrentTime() <= lowestStoppedTime || lowestStoppedTime < 0)
&& timer->getState() != ScStwTimer::RUNNING
) {
// this is the timer with the lowest stopped time
timersWhichHaveWonIds.append(timer);
}
}
// update the states of all timers
foreach(ScStwTimer * timer, this->timers) {
if(timer->getState() == ScStwTimer::RUNNING)
continue;
if(timersWhichHaveWonIds.contains(timer)) {
timer->setResult(ScStwTimer::WON);
}
else {
timer->setResult(ScStwTimer::LOST);
}
}
}
}
2020-10-02 15:23:30 +02:00
ScStw::StatusCode ScStwRace::reset() {
if(this->state != STOPPED && this->state != INCIDENT) {
return ScStw::CurrentStateNotVaildForOperationError;
}
qDebug() << "[INFO][RACE] resetting race";
2020-10-02 15:23:30 +02:00
ScStw::StatusCode returnCode = ScStw::Success;
foreach(ScStwTimer *speedTimer, this->timers){
if(!speedTimer->reset() && speedTimer->getState() != ScStwTimer::DISABLED && speedTimer->getState() != ScStwTimer::IDLE) {
2020-04-19 22:39:33 +02:00
returnCode = ScStw::InternalErrorTimerOperationFailed;
}
}
if(returnCode == ScStw::Success)
this->setState(IDLE);
2020-10-02 17:05:52 +02:00
this->soundPlayer->cancel();
return returnCode;
}
2020-10-02 15:23:30 +02:00
ScStw::StatusCode ScStwRace::cancel() {
if(this->state != PREPAIRING && this->state != WAITING && this->state != STARTING && this->state != RUNNING)
return ScStw::CurrentStateNotVaildForOperationError;
2020-04-19 22:39:33 +02:00
qDebug() << "[INFO][RACE] cancelling race";
2020-10-02 15:23:30 +02:00
ScStw::StatusCode returnCode = ScStw::Success;
2020-04-19 22:39:33 +02:00
foreach(ScStwTimer *timer, this->timers){
if(!timer->cancel() && timer->getState() != ScStwTimer::DISABLED)
returnCode = ScStw::InternalErrorTimerOperationFailed;
}
if(returnCode != ScStw::Success)
return returnCode;
this->setState(STOPPED);
this->startWaitLoop->exit(LoopCancelExit);
2020-10-03 11:10:15 +02:00
this->soundPlayer->cancel();
this->startDelayTimer->stop();
2020-10-02 17:05:52 +02:00
emit this->currentStartDelayChanged();
2020-04-19 22:39:33 +02:00
return returnCode;
}
void ScStwRace::technicalIncident() {
foreach(ScStwTimer *timer, this->timers){
timer->cancel();
}
this->setState(INCIDENT);
}
2020-04-19 22:39:33 +02:00
int ScStwRace::handleFalseStart() {
if(this->getState() != STARTING && this->getState() != RUNNING)
return ScStw::CurrentStateNotVaildForOperationError;
2020-04-19 22:39:33 +02:00
int returnCode = ScStw::Success;
// cancel all running timers
foreach(ScStwTimer *timer, this->timers) {
if(!timer->cancel() && timer->getState() != ScStwTimer::DISABLED && timer->getState() != ScStwTimer::FAILED)
returnCode = ScStw::InternalErrorTimerOperationFailed;
}
2020-04-19 22:39:33 +02:00
this->setState(STOPPED);
2020-10-02 19:53:07 +02:00
this->soundPlayer->cancel();
2020-10-03 17:45:54 +02:00
this->soundPlayer->play(ScStwSoundPlayer::FalseStart, this->getSoundVolume());
2020-04-19 22:39:33 +02:00
return returnCode;
}
bool ScStwRace::playSoundsAndStartTimers() {
if(this->state != PREPAIRING)
return true;
// The check if all timers are ready has already happened at this point
if(!this->doDelayAndSoundOfCurrentStartState())
return false;
// check if the start was cancelled
if(!this->isStarting())
return false;
this->setState(WAITING);
// do climber readiness tests
// wait until both climbers are ready
// if the automatic ready tone is enabled, wait for the climbers to become ready
2020-10-03 17:45:54 +02:00
if(this->competitionMode && this->getSoundEnabledSetting(ScStwSoundPlayer::Ready)) {
qDebug() << "[RACE][INFO] Now waiting for climbers";
// get delay
int minimumReadyDelay = 1000;
2020-10-03 17:45:54 +02:00
if(this->getSoundDelaySetting(ScStwSoundPlayer::Ready) > 1000)
minimumReadyDelay = this->getSoundDelaySetting(ScStwSoundPlayer::Ready);
this->startDelayTimer->setInterval(minimumReadyDelay);
2020-10-02 17:05:52 +02:00
// wait for all climbers to be ready for the ReadyActionDelay, but at least one second continuosly
// the climber ready wait loop will also quit, if the climber steps of the pad
// -> wait for both climbers to stand on the pad for at least one second
bool timerTriggered = true;
do {
2020-10-03 11:10:15 +02:00
if(!this->getIsReadyForNextState()) {
this->startDelayTimer->stop();
timerTriggered = false;
}
else if(this->startDelayTimer->isActive()) {
timerTriggered = true;
}
else {
this->startDelayTimer->stop();
this->startDelayTimer->start();
timerTriggered = true;
}
emit this->currentStartDelayChanged();
int loopExitCode = this->startWaitLoop->exec();
switch (loopExitCode) {
case LoopAutomaticExit:
break;
case LoopManualExit:
// prevent manual stop
timerTriggered = false;
break;
case LoopCancelExit:
return false;
}
2020-10-02 17:05:52 +02:00
//qDebug() << "At end of loop: remaining time: " << this->startDelayTimer->remainingTime() << " timer triggered: " << timerTriggered << " ready for next state: " << this->isReadyForNextState();
2020-10-03 11:10:15 +02:00
} while(this->startDelayTimer->remainingTime() > 0 || !timerTriggered || !this->getIsReadyForNextState());
qDebug() << "[DEBUG][RACE] Wait finished, starting now!";
// play ready tone
2020-10-03 17:45:54 +02:00
if(!this->soundPlayer->play(ScStwSoundPlayer::Ready, this->getSoundVolume())) {
qDebug() << "[ERROR][RACE] Ready sound returned false!";
this->technicalIncident();
return false;
2020-10-02 17:05:52 +02:00
}
}
else if(this->competitionMode) {
// wait for climbers and manual start
int loopExitCode;
do {
loopExitCode = this->startWaitLoop->exec();
if(loopExitCode == LoopCancelExit)
return false;
2020-10-03 11:10:15 +02:00
} while(loopExitCode != LoopManualExit || !this->getIsReadyForNextState());
}
else {
2020-10-02 17:05:52 +02:00
if(!this->doDelayAndSoundOfCurrentStartState()) {
this->cancel();
return false;
2020-10-02 17:05:52 +02:00
}
}
// enter starting state
this->setState(STARTING);
// play start tone
double timeOfSoundPlaybackStart;
2020-10-02 17:05:52 +02:00
if(!this->doDelayAndSoundOfCurrentStartState(&timeOfSoundPlaybackStart)) {
this->technicalIncident();
2020-10-02 17:05:52 +02:00
return false;
}
// perform start
// start all timers
bool startOk = true;
foreach(ScStwTimer *timer, this->timers){
if(!timer->start(timeOfSoundPlaybackStart + 3100) && timer->getState() != ScStwTimer::DISABLED){
startOk = false;
}
}
if(!startOk) {
qDebug() << "[ERROR][RACE] error staring all timers";
this->technicalIncident();
return false;
}
if(!this->soundPlayer->waitForSoundFinish()) {
qDebug() << "[ERROR][RACE] start sound wait error";
this->technicalIncident();
return false;
}
// check if a false start occured
if(!this->isStarting())
return true;
this->setState(RUNNING);
return true;
}
bool ScStwRace::doDelayAndSoundOfCurrentStartState(double *timeOfSoundPlaybackStart) {
2020-10-03 17:45:54 +02:00
ScStwSoundPlayer::StartSound sound = this->getSoundForState(this->state);
2020-10-03 17:45:54 +02:00
if(this->getSoundEnabledSetting(sound)) {
if(sound != ScStwSoundPlayer::Start && this->getSoundDelaySetting(sound) > 0) {
// perform the delay before the start
// get delay
2020-10-03 17:45:54 +02:00
int thisSoundDelay = this->getSoundDelaySetting(sound);
// perform next action
if(thisSoundDelay > 0) {
this->startDelayTimer->setInterval(thisSoundDelay);
this->startDelayTimer->start();
emit this->currentStartDelayChanged();
if(this->startWaitLoop->exec() == LoopCancelExit)
return false;
}
}
if(!this->isStarting())
return false;
2020-10-03 17:45:54 +02:00
if(!this->soundPlayer->play(sound, this->getSoundVolume(), timeOfSoundPlaybackStart))
return false;
}
return true;
}
void ScStwRace::setState(RaceState newState) {
if(newState != this->state) {
2020-04-19 13:09:24 +02:00
qDebug() << "[INFO][RACE] state changed: " << newState;
this->state = newState;
emit this->stateChanged(newState);
if(this->state == IDLE) {
2020-10-03 17:45:54 +02:00
this->refreshCompetitionMode();
}
}
}
void ScStwRace::refreshTimerStates() {
qDebug() << "[INFO][MAIN] refreshing timer states";
// check if the race is over
bool raceIsOver = true;
foreach(ScStwTimer * timer, this->timers){
if(timer->getState() < ScStwTimer::WON && timer->getState() != ScStwTimer::WAITING){
// if the timer is not in stoped state
raceIsOver = false;
break;
}
else if(timer->getState() == ScStwTimer::WAITING) {
this->handleTimerStop();
}
2020-04-19 22:39:33 +02:00
else if (timer->getState() == ScStwTimer::FAILED) {
this->handleFalseStart();
}
}
2020-04-19 22:39:33 +02:00
if(raceIsOver)
this->setState(STOPPED);
}
// ------------------------
// --- helper functions ---
// ------------------------
2020-10-03 11:10:15 +02:00
bool ScStwRace::getIsReadyForNextState() {
if(!this->competitionMode) {
return true;
}
switch (this->state) {
case IDLE: {
foreach (ScStwTimer *timer, this->timers) {
if(timer->getState() == ScStwTimer::DISABLED)
continue;
if(timer->getReadyState() == ScStwTimer::ExtensionIsNotConnected || timer->getReadyState() == ScStwTimer::ExtensionBatteryNotFine) {
timer->technicalIncident();
foreach (ScStwTimer *subTimer, this->timers) {
if(timer != subTimer && (timer->getReadyState() == ScStwTimer::ExtensionIsNotConnected || timer->getReadyState() == ScStwTimer::ExtensionBatteryNotFine))
subTimer->technicalIncident();
else if(timer != subTimer)
subTimer->setState(ScStwTimer::CANCELLED);
}
this->technicalIncident();
qDebug() << "[ERROR][RACE] Could not start due to not-ready timers";
return false;
}
}
break;
case WAITING: {
foreach (ScStwTimer *timer, this->timers) {
if(timer->getReadyState() != ScStwTimer::IsReady)
return false;
}
break;
}
default:
break;;
}
}
return true;
}
/**
* @brief ScStwRace::handleTimerEnable function to enable timers at the right moment to prevent them from bricking the state machine
* @param {ScStwExtensionControlledTimer*} timer timer to be enabled
*/
void ScStwRace::handleTimerWantsToBeDisabledChange(ScStwTimer* timer, bool wantsToBeDisabled) {
if(this->competitionMode)
return;
if(this->state == IDLE) {
timer->setDisabled(wantsToBeDisabled);
}
}
2020-10-03 17:45:54 +02:00
void ScStwRace::refreshCompetitionMode() {
if(this->state != IDLE)
return;
2020-10-03 17:45:54 +02:00
bool currentCompetitionMode = this->settings->readSetting(ScStwSettings::CompetitionModeSetting).toBool();
if(this->competitionMode != currentCompetitionMode) {
qDebug() << "[INFO][RACE] Setting competition mode to " << currentCompetitionMode;
this->competitionMode = currentCompetitionMode;
if(this->competitionMode) {
foreach (ScStwTimer *timer, this->timers) {
timer->setDisabled(false);
}
}
else {
foreach(ScStwTimer* timer, this->timers) {
if(timer->getWantsToBeDisabled() && timer->getState() != ScStwTimer::DISABLED)
this->handleTimerWantsToBeDisabledChange(timer, timer->getWantsToBeDisabled());
}
}
}
}
QVariantMap ScStwRace::getCurrentStartDelay() {
QVariantMap currentStartDelay = {
{"total", -1.0},
{"progress", -1.0}
};
switch (this->state) {
case WAITING:
2020-10-03 17:45:54 +02:00
if(!this->getSoundEnabledSetting(ScStwSoundPlayer::Ready))
return currentStartDelay;
2020-10-03 12:45:44 +02:00
if(!this->getIsReadyForNextState()) {
// indicate that we are waiting for climbers and the progress shall be zero
2020-10-03 12:45:44 +02:00
currentStartDelay["progress"] = 0;
return currentStartDelay;
}
break;
case PREPAIRING: {
2020-10-03 17:45:54 +02:00
if(!this->getSoundEnabledSetting(ScStwSoundPlayer::AtYourMarks))
return currentStartDelay;
break;
2020-10-02 17:05:52 +02:00
}
default:
return currentStartDelay;
break;
}
// get the total delay and the delay progress of the next action timer
double remaining = this->startDelayTimer->remainingTime();
if(remaining < 0)
return currentStartDelay;
currentStartDelay["total"] = this->startDelayTimer->interval();
currentStartDelay["progress"] = 1 - (remaining / currentStartDelay["total"].toDouble());
if(currentStartDelay["progress"].toDouble() < 0)
currentStartDelay["progress"] = 0;
return currentStartDelay;
}
bool ScStwRace::addTimer(ScStwTimer *timer) {
if(this->state != IDLE)
return false;
foreach(ScStwTimer *existingTimer, this->timers) {
if(existingTimer == timer)
return true;
}
this->timers.append(timer);
connect(timer, &ScStwTimer::stateChanged, this, &ScStwRace::refreshTimerStates);
connect(timer, &ScStwTimer::stateChanged, this, &ScStwRace::timersChanged);
2020-10-02 19:53:07 +02:00
connect(timer, &ScStwTimer::wantsToBeDisabledChanged, this, &ScStwRace::handleTimerWantsToBeDisabledChange);
connect(timer, &ScStwTimer::reactionTimeChanged, this, &ScStwRace::timersChanged);
connect(timer, &ScStwTimer::readyStateChanged, this->startWaitLoop, &QEventLoop::quit);
2020-10-02 19:53:07 +02:00
connect(timer, &ScStwTimer::readyStateChanged, this, &ScStwRace::handleTimerReadyStateChange);
2020-10-02 19:56:08 +02:00
connect(timer, &ScStwTimer::readyStateChanged, this, &ScStwRace::isReadyForNextStateChanged);
if(this->competitionMode && timer->getState() == ScStwTimer::DISABLED)
timer->setDisabled(false);
return true;
}
2020-10-03 17:45:54 +02:00
ScStwRace::RaceState ScStwRace::getState() {
return this->state;
}
2020-10-03 17:45:54 +02:00
QList<ScStwTimer*> ScStwRace::getTimers() {
return this->timers;
}
2020-10-03 17:45:54 +02:00
double ScStwRace::getSoundVolume() {
return this->settings->readSetting(ScStwSettings::SoundVolumeSetting).toDouble();
}
2020-10-03 17:45:54 +02:00
ScStwSoundPlayer::StartSound ScStwRace::getSoundForState(ScStwRace::RaceState state) {
switch (state) {
case PREPAIRING:
return ScStwSoundPlayer::AtYourMarks;
break;
case WAITING:
return ScStwSoundPlayer::Ready;
break;
case STARTING:
return ScStwSoundPlayer::Start;
break;
default:
return ScStwSoundPlayer::StartSound(-1);
}
2020-10-03 17:45:54 +02:00
}
2020-10-03 17:45:54 +02:00
bool ScStwRace::getSoundEnabledSetting(ScStwSoundPlayer::StartSound sound) {
ScStwSettings::BaseStationSetting soundEnabledSetting;
switch (sound) {
case ScStwSoundPlayer::AtYourMarks:
soundEnabledSetting = ScStwSettings::AtYourMarksSoundEnableSetting;
break;
2020-10-03 17:45:54 +02:00
case ScStwSoundPlayer::Ready:
soundEnabledSetting = ScStwSettings::ReadySoundEnableSetting;
break;
case ScStwSoundPlayer::Start:
return true;
break;
default:
return false;
}
2020-10-03 17:45:54 +02:00
return this->settings->readSetting(soundEnabledSetting).toBool();
}
2020-10-03 17:45:54 +02:00
int ScStwRace::getSoundDelaySetting(ScStwSoundPlayer::StartSound sound) {
ScStwSettings::BaseStationSetting soundDelaySetting;
switch (sound) {
case ScStwSoundPlayer::AtYourMarks:
soundDelaySetting = ScStwSettings::AtYourMarksSoundDelaySetting;
break;
case ScStwSoundPlayer::Ready:
soundDelaySetting = ScStwSettings::ReadySoundDelaySetting;
break;
case ScStwSoundPlayer::Start:
return 0;
break;
default:
return -1;
}
return this->settings->readSetting(soundDelaySetting).toInt();
}
QVariantList ScStwRace::getTimerDetailList() {
QVariantList tmpTimers;
foreach(ScStwTimer * timer, this->timers){
QVariantMap tmpTimer;
tmpTimer.insert("id", this->timers.indexOf(timer));
tmpTimer.insert("state", timer->getState());
tmpTimer.insert("currentTime", timer->getCurrentTime());
tmpTimer.insert("reactionTime", timer->getReactionTime());
2020-06-13 12:38:24 +02:00
tmpTimer.insert("letter", timer->getLetter());
2020-10-02 19:53:07 +02:00
tmpTimer.insert("readyState", timer->getReadyState());
2020-10-03 18:32:39 +02:00
tmpTimer.insert("text", timer->getText());
tmpTimers.append(tmpTimer);
}
return tmpTimers;
}
2020-10-02 19:53:07 +02:00
QVariantMap ScStwRace::getDetails() {
QVariantMap tmpDetails;
2020-10-03 11:10:15 +02:00
tmpDetails.insert("state", this->getState());
2020-10-02 19:53:07 +02:00
tmpDetails.insert("competitionMode", this->competitionMode);
2020-10-03 17:45:54 +02:00
tmpDetails.insert("readySoundEnabled", this->getSoundEnabledSetting(ScStwSoundPlayer::Ready));
2020-10-03 11:10:15 +02:00
tmpDetails.insert("currentStartDelay", this->getCurrentStartDelay());
tmpDetails.insert("timers", this->getTimerDetailList());
2020-10-02 19:53:07 +02:00
if(this->state == WAITING)
2020-10-03 11:10:15 +02:00
tmpDetails.insert("isReadyForNextState", this->getIsReadyForNextState());
else
tmpDetails.insert("isReadyForNextState", true);
2020-10-02 19:53:07 +02:00
return tmpDetails;
}
bool ScStwRace::isStarting() {
return this->state == PREPAIRING || this->state == WAITING || this->state == STARTING;
}
2020-10-02 19:53:07 +02:00
void ScStwRace::handleTimerReadyStateChange(ScStwTimer::ReadyState readyState) {
Q_UNUSED(readyState)
// only continue if the current state is waiting
if(this->state == WAITING)
emit this->timersChanged();
}
bool ScStwRace::getCompetitionMode() {
return this->competitionMode;
}
bool ScStwRace::getReadySoundEnabled() {
2020-10-03 17:45:54 +02:00
return this->getSoundEnabledSetting(ScStwSoundPlayer::Ready);
}