/
DemienMedich
/
BodyweightBase
Обзор
Документация
Войти
/
DemienMedich
/
BodyweightBase
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
core/src/workout_session_runner.cpp
410 строк
12 KB
DemienMedich
Replace C# app with C++ Qt version
20 июн 2026, 00:04
20 июн 2026, 00:04
e516ad2
Код
Авторство
О чём код?
#include "bodyweight/workout_session_runner.h" #include <algorithm> namespace bodyweight { WorkoutSessionRunner::WorkoutSessionRunner(QVector<ExerciseDefinition> exercises) { for (ExerciseDefinition &exercise : exercises) { exercises_.insert(exercise.id, std::move(exercise)); } } bool WorkoutSessionRunner::start(const WorkoutPlan &plan, QString *error) { if (!validatePlan(plan, error)) { return false; } abort(); plan_ = plan; moveToStep(0); return true; } void WorkoutSessionRunner::abort() { plan_.reset(); stepIndex_ = 0; phase_ = SessionPhase::idle; phaseBeforePause_ = SessionPhase::idle; phaseDurationSeconds_ = 0; phaseElapsedSeconds_ = 0; currentValue_ = 0; totalRestSeconds_ = 0; currentLoadNote_.clear(); completedSteps_.clear(); finishedSession_.reset(); } void WorkoutSessionRunner::advance(int seconds) { int remainingAdvance = std::max(0, seconds); if (phase_ == SessionPhase::repetitionExercise) { phaseElapsedSeconds_ += remainingAdvance; return; } while (remainingAdvance > 0 && isTimerPhase()) { const int remainingPhase = std::max(0, phaseDurationSeconds_ - phaseElapsedSeconds_); const int consumed = std::min(remainingAdvance, remainingPhase); phaseElapsedSeconds_ += consumed; remainingAdvance -= consumed; if (phaseElapsedSeconds_ < phaseDurationSeconds_) { break; } switch (phase_) { case SessionPhase::preparation: beginExercise(); break; case SessionPhase::timedExercise: finishTimedExercise(); break; case SessionPhase::rest: totalRestSeconds_ += phaseDurationSeconds_; advanceAfterRest(); break; default: return; } } } void WorkoutSessionRunner::pause() { if (phase_ != SessionPhase::preparation && phase_ != SessionPhase::repetitionExercise && phase_ != SessionPhase::timedExercise && phase_ != SessionPhase::rest) { return; } phaseBeforePause_ = phase_; phase_ = SessionPhase::paused; } void WorkoutSessionRunner::resume() { if (phase_ != SessionPhase::paused) { return; } phase_ = phaseBeforePause_; phaseBeforePause_ = SessionPhase::idle; } void WorkoutSessionRunner::addRep() { if (phase_ == SessionPhase::repetitionExercise) { ++currentValue_; } } void WorkoutSessionRunner::removeRep() { if (phase_ == SessionPhase::repetitionExercise) { currentValue_ = std::max(0, currentValue_ - 1); } } void WorkoutSessionRunner::setRepValue(int value) { if (phase_ == SessionPhase::repetitionExercise) { currentValue_ = std::max(0, value); } } void WorkoutSessionRunner::setCurrentLoadNote(const QString &loadNote) { currentLoadNote_ = loadNote.trimmed(); } void WorkoutSessionRunner::completeRepExercise() { if (!plan_ || phase_ != SessionPhase::repetitionExercise) { return; } const ExerciseDefinition &exercise = exerciseAt(stepIndex_); completedSteps_.append({ exercise.id, exercise.name, exercise.metric, targetAt(stepIndex_), currentValue_, std::max(1, phaseElapsedSeconds_), restAt(stepIndex_), currentValue_ >= targetAt(stepIndex_), currentLoadNote_ }); beginRestOrFinish(); } void WorkoutSessionRunner::skipCurrentExercise() { if (!plan_ || phase_ == SessionPhase::idle || phase_ == SessionPhase::completed || phase_ == SessionPhase::rest || phase_ == SessionPhase::paused) { return; } const ExerciseDefinition &exercise = exerciseAt(stepIndex_); const bool activeExercise = phase_ == SessionPhase::repetitionExercise || phase_ == SessionPhase::timedExercise; completedSteps_.append({ exercise.id, exercise.name, exercise.metric, targetAt(stepIndex_), phase_ == SessionPhase::repetitionExercise ? currentValue_ : (phase_ == SessionPhase::timedExercise ? phaseElapsedSeconds_ : 0), activeExercise ? std::max(1, phaseElapsedSeconds_) : 0, restAt(stepIndex_), false, currentLoadNote_ }); beginRestOrFinish(); } void WorkoutSessionRunner::skipCountdown() { if (phase_ == SessionPhase::preparation) { beginExercise(); } else if (phase_ == SessionPhase::rest) { totalRestSeconds_ += phaseElapsedSeconds_; advanceAfterRest(); } } SessionSnapshot WorkoutSessionRunner::snapshot() const { return { phase_, phaseBeforePause_, stepIndex_, phaseDurationSeconds_, phaseElapsedSeconds_, isTimerPhase() ? std::max(0, phaseDurationSeconds_ - phaseElapsedSeconds_) : 0, currentValue_, static_cast<int>(completedSteps_.size()) }; } std::optional<SessionDraft> WorkoutSessionRunner::createDraft() const { if (!plan_ || phase_ == SessionPhase::idle || phase_ == SessionPhase::completed) { return std::nullopt; } return SessionDraft{ *plan_, stepIndex_, phase_, phaseBeforePause_, phaseDurationSeconds_, phaseElapsedSeconds_, currentValue_, totalRestSeconds_, currentLoadNote_, completedSteps_ }; } bool WorkoutSessionRunner::restore(const SessionDraft &draft, QString *error) { if (!validatePlan(draft.plan, error)) { return false; } const bool activePhase = draft.phase == SessionPhase::preparation || draft.phase == SessionPhase::repetitionExercise || draft.phase == SessionPhase::timedExercise || draft.phase == SessionPhase::rest; const bool validPausedPhase = draft.phase == SessionPhase::paused && (draft.phaseBeforePause == SessionPhase::preparation || draft.phaseBeforePause == SessionPhase::repetitionExercise || draft.phaseBeforePause == SessionPhase::timedExercise || draft.phaseBeforePause == SessionPhase::rest); if (!activePhase && !validPausedPhase) { if (error) { *error = QStringLiteral("Draft has no restorable active phase."); } return false; } if (draft.stepIndex < 0 || draft.stepIndex >= draft.plan.steps.size()) { if (error) { *error = QStringLiteral("Draft step index is outside the plan."); } return false; } const bool repetitionPhase = draft.phase == SessionPhase::repetitionExercise || (draft.phase == SessionPhase::paused && draft.phaseBeforePause == SessionPhase::repetitionExercise); if (draft.phaseDurationSeconds < 0 || draft.phaseElapsedSeconds < 0 || (!repetitionPhase && draft.phaseElapsedSeconds > draft.phaseDurationSeconds) || draft.currentValue < 0 || draft.totalRestSeconds < 0 || draft.completedSteps.size() > draft.stepIndex) { if (error) { *error = QStringLiteral("Draft contains invalid counters."); } return false; } abort(); plan_ = draft.plan; stepIndex_ = draft.stepIndex; phase_ = draft.phase; phaseBeforePause_ = draft.phaseBeforePause; phaseDurationSeconds_ = draft.phaseDurationSeconds; phaseElapsedSeconds_ = draft.phaseElapsedSeconds; currentValue_ = draft.currentValue; totalRestSeconds_ = draft.totalRestSeconds; currentLoadNote_ = draft.currentLoadNote.trimmed(); completedSteps_ = draft.completedSteps; return true; } std::optional<FinishedSession> WorkoutSessionRunner::takeFinishedSession() { std::optional<FinishedSession> result = std::move(finishedSession_); finishedSession_.reset(); return result; } bool WorkoutSessionRunner::isTimerPhase() const { return phase_ == SessionPhase::preparation || phase_ == SessionPhase::timedExercise || phase_ == SessionPhase::rest; } const ExerciseDefinition &WorkoutSessionRunner::exerciseAt(int stepIndex) const { return exercises_.constFind(plan_->steps.at(stepIndex).exerciseId).value(); } int WorkoutSessionRunner::targetAt(int stepIndex) const { const WorkoutStep &step = plan_->steps.at(stepIndex); return step.targetOverride.value_or(exerciseAt(stepIndex).defaultTarget); } int WorkoutSessionRunner::restAt(int stepIndex) const { const WorkoutStep &step = plan_->steps.at(stepIndex); return step.restSecondsOverride.value_or(exerciseAt(stepIndex).defaultRestSeconds); } void WorkoutSessionRunner::moveToStep(int stepIndex) { stepIndex_ = stepIndex; if (!plan_) { phase_ = SessionPhase::idle; } else if (stepIndex_ >= plan_->steps.size()) { finish(); } else { beginPreparation(stepIndex_); } } void WorkoutSessionRunner::beginPreparation(int stepIndex) { stepIndex_ = stepIndex; phase_ = SessionPhase::preparation; phaseDurationSeconds_ = defaultPreparationSeconds; phaseElapsedSeconds_ = 0; currentValue_ = exerciseAt(stepIndex_).metric == ExerciseMetric::repetitions ? targetAt(stepIndex_) : 0; currentLoadNote_.clear(); } void WorkoutSessionRunner::beginExercise() { const ExerciseDefinition &exercise = exerciseAt(stepIndex_); phaseElapsedSeconds_ = 0; if (exercise.metric == ExerciseMetric::seconds) { phase_ = SessionPhase::timedExercise; phaseDurationSeconds_ = targetAt(stepIndex_); currentValue_ = 0; } else { phase_ = SessionPhase::repetitionExercise; phaseDurationSeconds_ = 0; currentValue_ = std::max(0, currentValue_ > 0 ? currentValue_ : targetAt(stepIndex_)); } } void WorkoutSessionRunner::finishTimedExercise() { const ExerciseDefinition &exercise = exerciseAt(stepIndex_); const int target = targetAt(stepIndex_); completedSteps_.append({ exercise.id, exercise.name, exercise.metric, target, target, target, restAt(stepIndex_), true, currentLoadNote_ }); beginRestOrFinish(); } void WorkoutSessionRunner::beginRestOrFinish() { const int rest = restAt(stepIndex_); ++stepIndex_; if (stepIndex_ >= plan_->steps.size() || rest <= 0) { moveToStep(stepIndex_); return; } phase_ = SessionPhase::rest; phaseDurationSeconds_ = rest; phaseElapsedSeconds_ = 0; } void WorkoutSessionRunner::advanceAfterRest() { moveToStep(stepIndex_); } void WorkoutSessionRunner::finish() { phase_ = SessionPhase::completed; FinishedSession session; session.planId = plan_->id; session.planName = plan_->name; session.totalRestSeconds = totalRestSeconds_; session.steps = completedSteps_; session.completedAll = completedSteps_.size() == plan_->steps.size(); for (const CompletedExercise &step : completedSteps_) { session.totalWorkSeconds += step.workSeconds; } finishedSession_ = std::move(session); } bool WorkoutSessionRunner::validatePlan(const WorkoutPlan &plan, QString *error) const { if (plan.steps.isEmpty()) { if (error) { *error = QStringLiteral("Workout plan has no steps."); } return false; } for (const WorkoutStep &step : plan.steps) { if (!exercises_.contains(step.exerciseId)) { if (error) { *error = QStringLiteral("Unknown exercise: %1").arg(step.exerciseId); } return false; } if ((step.targetOverride.has_value() && *step.targetOverride <= 0) || (step.restSecondsOverride.has_value() && *step.restSecondsOverride < 0)) { if (error) { *error = QStringLiteral("Exercise '%1' has invalid target or rest.").arg(step.exerciseId); } return false; } } return true; } } // namespace bodyweight