Fist commit
This commit is contained in:
123
lib/timbre_core/Scheduler.cpp
Executable file
123
lib/timbre_core/Scheduler.cpp
Executable file
@@ -0,0 +1,123 @@
|
||||
#include "Scheduler.h"
|
||||
|
||||
#include "ScheduleValidator.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace timbre_core {
|
||||
|
||||
void Scheduler::reset() noexcept {
|
||||
hasLastKey_ = false;
|
||||
lastKey_ = LocalMinuteKey{};
|
||||
}
|
||||
|
||||
bool Scheduler::isHoliday(const AppState& state, std::uint8_t month,
|
||||
std::uint8_t day) noexcept {
|
||||
for (std::size_t i = 0; i < state.holidays.size(); ++i) {
|
||||
const Holiday& holiday = state.holidays[i];
|
||||
if (holiday.enabled && holiday.month == month && holiday.day == day) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Scheduler::scheduleMatches(const Schedule& schedule, const AppState& state,
|
||||
const CivilDateTime& localTime) noexcept {
|
||||
if (!schedule.enabled || schedule.minuteOfDay != localTime.minuteOfDay()) {
|
||||
return false;
|
||||
}
|
||||
const Profile* activeProfile = state.profile(state.activeProfileId);
|
||||
if (activeProfile == nullptr || !activeProfile->enabled ||
|
||||
schedule.profileId != state.activeProfileId) {
|
||||
return false;
|
||||
}
|
||||
const std::uint8_t weekday = CivilTime::weekdayForDate(localTime.year, localTime.month,
|
||||
localTime.day);
|
||||
if ((schedule.weekdayMask & static_cast<std::uint8_t>(1u << weekday)) == 0) {
|
||||
return false;
|
||||
}
|
||||
if (isHoliday(state, localTime.month, localTime.day)) {
|
||||
return false;
|
||||
}
|
||||
const Pattern* pattern = state.pattern(schedule.patternId);
|
||||
return pattern != nullptr && isPatternId(schedule.patternId) &&
|
||||
ScheduleValidator::validatePattern(*pattern).valid;
|
||||
}
|
||||
|
||||
ScheduleDecision Scheduler::poll(const AppState& state,
|
||||
const CivilDateTime& localTime,
|
||||
bool firstObservation) {
|
||||
ScheduleDecision decision;
|
||||
decision.firstObservation = firstObservation;
|
||||
|
||||
if (!localTime.valid()) {
|
||||
decision.invalidTime = true;
|
||||
return decision;
|
||||
}
|
||||
|
||||
const LocalMinuteKey currentKey = LocalMinuteKey::from(localTime);
|
||||
const std::int64_t currentSerial = currentKey.serial();
|
||||
if (currentSerial == std::numeric_limits<std::int64_t>::min()) {
|
||||
decision.invalidTime = true;
|
||||
return decision;
|
||||
}
|
||||
|
||||
if (hasLastKey_) {
|
||||
const std::int64_t previousSerial = lastKey_.serial();
|
||||
if (currentSerial <= previousSerial) {
|
||||
decision.alreadyProcessed = true;
|
||||
return decision;
|
||||
}
|
||||
const std::int64_t distance = currentSerial - previousSerial;
|
||||
decision.missedMinutes = distance > 1 ? static_cast<std::uint64_t>(distance - 1) : 0;
|
||||
}
|
||||
|
||||
// Consume the current key before evaluating matches. Therefore a failed
|
||||
// lookup (holiday, disabled profile, etc.) cannot be replayed later.
|
||||
lastKey_ = currentKey;
|
||||
hasLastKey_ = true;
|
||||
|
||||
if (firstObservation) {
|
||||
return decision;
|
||||
}
|
||||
|
||||
if (isHoliday(state, localTime.month, localTime.day)) {
|
||||
decision.blockedByHoliday = true;
|
||||
return decision;
|
||||
}
|
||||
|
||||
const Schedule* selected = nullptr;
|
||||
for (std::size_t i = 0; i < state.schedules.size(); ++i) {
|
||||
const Schedule& candidate = state.schedules[i];
|
||||
if (!scheduleMatches(candidate, state, localTime)) {
|
||||
continue;
|
||||
}
|
||||
// Stable IDs, rather than array positions, define deterministic
|
||||
// precedence when two schedules intentionally share a minute.
|
||||
if (selected == nullptr || candidate.id < selected->id) {
|
||||
selected = &candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (selected != nullptr) {
|
||||
decision.due = true;
|
||||
decision.scheduleId = selected->id;
|
||||
decision.patternId = selected->patternId;
|
||||
}
|
||||
return decision;
|
||||
}
|
||||
|
||||
ScheduleDecision Scheduler::poll(const AppState& state,
|
||||
SessionClock& clock,
|
||||
std::int64_t utcEpochSeconds) {
|
||||
const SessionClock::Observation observation = clock.observe(utcEpochSeconds);
|
||||
if (!observation.valid) {
|
||||
ScheduleDecision decision;
|
||||
decision.invalidTime = true;
|
||||
return decision;
|
||||
}
|
||||
return poll(state, observation.local, observation.firstObservation);
|
||||
}
|
||||
|
||||
} // namespace timbre_core
|
||||
Reference in New Issue
Block a user