Fist commit
This commit is contained in:
603
lib/timbre_core/Model.h
Executable file
603
lib/timbre_core/Model.h
Executable file
@@ -0,0 +1,603 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <utility>
|
||||
|
||||
namespace timbre_core {
|
||||
|
||||
// The data model deliberately uses bounded storage: it is safe for the ESP32
|
||||
// heap and never needs PSRAM or a dynamic container.
|
||||
constexpr std::size_t kMaxProfiles = 8;
|
||||
constexpr std::size_t kMaxPatterns = 3;
|
||||
constexpr std::size_t kMaxProfileOrPatternSlots = 8;
|
||||
constexpr std::size_t kMaxSchedules = 100;
|
||||
constexpr std::size_t kMaxHolidays = 64;
|
||||
constexpr std::size_t kPatternQueueCapacity = 8;
|
||||
constexpr std::size_t kPatternCount = kMaxPatterns;
|
||||
constexpr std::size_t kCanonicalPatternCount = kMaxPatterns;
|
||||
constexpr std::size_t kMaxConfiguredPatterns = 8;
|
||||
constexpr std::size_t kProfileLimit = kMaxProfiles;
|
||||
constexpr std::size_t kMaxPatternPhases = 6;
|
||||
constexpr std::uint8_t kMaxPhaseDurationSeconds = 99;
|
||||
constexpr std::uint16_t kMaxPatternTotalSeconds = 600;
|
||||
|
||||
// Upper-case aliases make the limits convenient for small configuration code.
|
||||
constexpr std::size_t MAX_PROFILES = kMaxProfiles;
|
||||
constexpr std::size_t MAX_PATTERNS = kMaxPatterns;
|
||||
constexpr std::size_t MAX_PROFILE_PATTERN_SLOTS = kMaxProfileOrPatternSlots;
|
||||
constexpr std::size_t MAX_SCHEDULES = kMaxSchedules;
|
||||
constexpr std::size_t MAX_HOLIDAYS = kMaxHolidays;
|
||||
constexpr std::size_t PATTERN_QUEUE_CAPACITY = kPatternQueueCapacity;
|
||||
constexpr std::uint8_t MAX_PHASE_DURATION = kMaxPhaseDurationSeconds;
|
||||
constexpr std::uint16_t MAX_PATTERN_TOTAL = kMaxPatternTotalSeconds;
|
||||
|
||||
using Id = std::uint16_t;
|
||||
using EntityId = Id;
|
||||
using ProfileId = Id;
|
||||
using PatternIdValue = Id;
|
||||
using ScheduleId = Id;
|
||||
using HolidayId = Id;
|
||||
using StableId = Id;
|
||||
constexpr Id kInvalidId = 0xFFFFu;
|
||||
|
||||
enum class PatternId : std::uint8_t {
|
||||
A = 0,
|
||||
B = 1,
|
||||
C = 2
|
||||
};
|
||||
|
||||
// RelayId is intentionally separate from PatternId. A/B/C are sound
|
||||
// patterns; they are never relay identifiers.
|
||||
enum class RelayId : std::uint8_t {
|
||||
Main = 0,
|
||||
Primary = 0,
|
||||
MainRelay = 0,
|
||||
Relay1 = 0,
|
||||
MainGPIO22 = 0,
|
||||
Reserved = 1,
|
||||
ReservedRelay = 1,
|
||||
Relay2 = 1,
|
||||
ReservedGPIO23 = 1,
|
||||
Invalid = 0xFF
|
||||
};
|
||||
|
||||
constexpr std::uint8_t kMainRelayGpio = 22;
|
||||
constexpr std::uint8_t kReservedRelayGpio = 23;
|
||||
constexpr std::uint8_t kMainRelayGPIO = kMainRelayGpio;
|
||||
constexpr std::uint8_t kReservedRelayGPIO = kReservedRelayGpio;
|
||||
constexpr std::uint8_t RELAY_MAIN_GPIO = kMainRelayGpio;
|
||||
constexpr std::uint8_t RELAY_RESERVED_GPIO = kReservedRelayGpio;
|
||||
constexpr std::uint8_t kRelayCount = 2;
|
||||
constexpr RelayId kPrimaryRelay = RelayId::Main;
|
||||
constexpr RelayId kReservedRelay = RelayId::Reserved;
|
||||
constexpr std::uint8_t kAllWeekdaysMask = 0x7F;
|
||||
constexpr std::uint8_t kSundayMask = 1u << 0;
|
||||
constexpr std::uint8_t kMondayMask = 1u << 1;
|
||||
constexpr std::uint8_t kTuesdayMask = 1u << 2;
|
||||
constexpr std::uint8_t kWednesdayMask = 1u << 3;
|
||||
constexpr std::uint8_t kThursdayMask = 1u << 4;
|
||||
constexpr std::uint8_t kFridayMask = 1u << 5;
|
||||
constexpr std::uint8_t kSaturdayMask = 1u << 6;
|
||||
|
||||
constexpr bool isPatternId(PatternId id) noexcept {
|
||||
return static_cast<std::uint8_t>(id) <= static_cast<std::uint8_t>(PatternId::C);
|
||||
}
|
||||
|
||||
constexpr std::size_t patternIndex(PatternId id) noexcept {
|
||||
return isPatternId(id) ? static_cast<std::size_t>(id) : kMaxPatterns;
|
||||
}
|
||||
|
||||
constexpr bool isRelayId(RelayId id) noexcept {
|
||||
return id == RelayId::Main || id == RelayId::Reserved;
|
||||
}
|
||||
|
||||
constexpr std::uint8_t gpioForRelay(RelayId id) noexcept {
|
||||
return id == RelayId::Main ? kMainRelayGpio
|
||||
: (id == RelayId::Reserved ? kReservedRelayGpio : 0xFFu);
|
||||
}
|
||||
|
||||
constexpr bool isReservedRelay(RelayId id) noexcept {
|
||||
return id == RelayId::Reserved;
|
||||
}
|
||||
|
||||
constexpr std::size_t relayIndex(RelayId id) noexcept {
|
||||
return id == RelayId::Main ? 0u
|
||||
: (id == RelayId::Reserved ? 1u : kRelayCount);
|
||||
}
|
||||
|
||||
constexpr RelayId relayIdFromIndex(std::size_t index) noexcept {
|
||||
return index == 0 ? RelayId::Main
|
||||
: (index == 1 ? RelayId::Reserved : RelayId::Invalid);
|
||||
}
|
||||
|
||||
constexpr RelayId relayIdFromGpio(std::uint8_t gpio) noexcept {
|
||||
return gpio == kMainRelayGpio ? RelayId::Main
|
||||
: (gpio == kReservedRelayGpio ? RelayId::Reserved : RelayId::Invalid);
|
||||
}
|
||||
|
||||
constexpr bool isKnownRelayGpio(std::uint8_t gpio) noexcept {
|
||||
return gpio == kMainRelayGpio || gpio == kReservedRelayGpio;
|
||||
}
|
||||
|
||||
constexpr std::uint8_t relayGpio(RelayId id) noexcept { return gpioForRelay(id); }
|
||||
|
||||
constexpr PatternId patternIdFromIndex(std::size_t index) noexcept {
|
||||
return index < kMaxPatterns ? static_cast<PatternId>(index) : PatternId::A;
|
||||
}
|
||||
|
||||
constexpr PatternId patternIdFromChar(char value) noexcept {
|
||||
return value == 'A' || value == 'a' ? PatternId::A
|
||||
: (value == 'B' || value == 'b' ? PatternId::B
|
||||
: (value == 'C' || value == 'c' ? PatternId::C : PatternId::A));
|
||||
}
|
||||
|
||||
constexpr char patternChar(PatternId id) noexcept {
|
||||
return id == PatternId::A ? 'A'
|
||||
: (id == PatternId::B ? 'B'
|
||||
: (id == PatternId::C ? 'C' : '?'));
|
||||
}
|
||||
|
||||
enum class Weekday : std::uint8_t {
|
||||
Sunday = 0,
|
||||
Monday = 1,
|
||||
Tuesday = 2,
|
||||
Wednesday = 3,
|
||||
Thursday = 4,
|
||||
Friday = 5,
|
||||
Saturday = 6
|
||||
};
|
||||
|
||||
template <std::size_t Capacity>
|
||||
class FixedText {
|
||||
public:
|
||||
FixedText() = default;
|
||||
|
||||
explicit FixedText(const char* value) { assign(value); }
|
||||
|
||||
FixedText& operator=(const char* value) {
|
||||
assign(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void assign(const char* value) noexcept {
|
||||
std::size_t index = 0;
|
||||
if (value != nullptr) {
|
||||
while (value[index] != '\0' && index + 1 < Capacity) {
|
||||
data_[index] = value[index];
|
||||
++index;
|
||||
}
|
||||
}
|
||||
while (index < Capacity) {
|
||||
data_[index] = '\0';
|
||||
++index;
|
||||
}
|
||||
length_ = 0;
|
||||
while (length_ < Capacity && data_[length_] != '\0') {
|
||||
++length_;
|
||||
}
|
||||
}
|
||||
|
||||
const char* c_str() const noexcept { return data_.data(); }
|
||||
std::size_t size() const noexcept { return length_; }
|
||||
std::size_t length() const noexcept { return length_; }
|
||||
bool empty() const noexcept { return length_ == 0; }
|
||||
|
||||
char operator[](std::size_t index) const noexcept {
|
||||
return index < length_ ? data_[index] : '\0';
|
||||
}
|
||||
|
||||
bool operator==(const FixedText& other) const noexcept {
|
||||
if (length_ != other.length_) {
|
||||
return false;
|
||||
}
|
||||
for (std::size_t i = 0; i < length_; ++i) {
|
||||
if (data_[i] != other.data_[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator!=(const FixedText& other) const noexcept { return !(*this == other); }
|
||||
bool operator==(const char* other) const noexcept {
|
||||
if (other == nullptr) {
|
||||
return length_ == 0;
|
||||
}
|
||||
for (std::size_t i = 0; i < length_; ++i) {
|
||||
if (other[i] == '\0' || other[i] != data_[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return other[length_] == '\0';
|
||||
}
|
||||
bool operator!=(const char* other) const noexcept { return !(*this == other); }
|
||||
|
||||
private:
|
||||
std::array<char, Capacity> data_{};
|
||||
std::size_t length_ = 0;
|
||||
};
|
||||
|
||||
template <typename T, std::size_t Capacity>
|
||||
class FixedVector {
|
||||
public:
|
||||
using value_type = T;
|
||||
using iterator = T*;
|
||||
using const_iterator = const T*;
|
||||
|
||||
std::size_t size() const noexcept { return size_; }
|
||||
std::size_t capacity() const noexcept { return Capacity; }
|
||||
bool empty() const noexcept { return size_ == 0; }
|
||||
bool full() const noexcept { return size_ == Capacity; }
|
||||
void clear() noexcept { size_ = 0; }
|
||||
|
||||
bool push_back(const T& value) noexcept {
|
||||
if (full()) {
|
||||
return false;
|
||||
}
|
||||
data_[size_++] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
bool emplace_back(Args&&... args) noexcept {
|
||||
if (full()) {
|
||||
return false;
|
||||
}
|
||||
data_[size_++] = T(std::forward<Args>(args)...);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pop_back() noexcept {
|
||||
if (empty()) {
|
||||
return false;
|
||||
}
|
||||
--size_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool erase(std::size_t index) noexcept {
|
||||
if (index >= size_) {
|
||||
return false;
|
||||
}
|
||||
for (std::size_t i = index + 1; i < size_; ++i) {
|
||||
data_[i - 1] = data_[i];
|
||||
}
|
||||
--size_;
|
||||
return true;
|
||||
}
|
||||
|
||||
T& operator[](std::size_t index) noexcept { return data_[index]; }
|
||||
const T& operator[](std::size_t index) const noexcept { return data_[index]; }
|
||||
T& back() noexcept { return data_[size_ - 1]; }
|
||||
const T& back() const noexcept { return data_[size_ - 1]; }
|
||||
T& front() noexcept { return data_[0]; }
|
||||
const T& front() const noexcept { return data_[0]; }
|
||||
|
||||
// No throwing allocator or unchecked access is needed by the core. The
|
||||
// caller can use size() before this helper when a reference is required.
|
||||
T* find_if_index(std::size_t index) noexcept {
|
||||
return index < size_ ? &data_[index] : nullptr;
|
||||
}
|
||||
const T* find_if_index(std::size_t index) const noexcept {
|
||||
return index < size_ ? &data_[index] : nullptr;
|
||||
}
|
||||
|
||||
T* data() noexcept { return data_.data(); }
|
||||
const T* data() const noexcept { return data_.data(); }
|
||||
iterator begin() noexcept { return data_.data(); }
|
||||
const_iterator begin() const noexcept { return data_.data(); }
|
||||
iterator end() noexcept { return data_.data() + size_; }
|
||||
const_iterator end() const noexcept { return data_.data() + size_; }
|
||||
|
||||
private:
|
||||
std::array<T, Capacity> data_{};
|
||||
std::size_t size_ = 0;
|
||||
};
|
||||
|
||||
struct Profile {
|
||||
union {
|
||||
ProfileId id;
|
||||
ProfileId profileId;
|
||||
};
|
||||
FixedText<32> name{};
|
||||
bool enabled = true;
|
||||
|
||||
Profile() : id(0) {}
|
||||
explicit Profile(ProfileId identifier) : id(identifier) {}
|
||||
Profile(ProfileId identifier, const char* profileName) : id(identifier), name(profileName) {}
|
||||
};
|
||||
|
||||
struct Pattern {
|
||||
// Six alternating durations, in seconds: ON, OFF, ON, OFF, ...
|
||||
// phaseCount permits a short final pattern while retaining a fixed buffer.
|
||||
PatternId id = PatternId::A;
|
||||
std::uint8_t phaseCount = static_cast<std::uint8_t>(kMaxPatternPhases);
|
||||
union {
|
||||
std::array<std::uint8_t, kMaxPatternPhases> phases;
|
||||
std::array<std::uint8_t, kMaxPatternPhases> durations;
|
||||
std::array<std::uint8_t, kMaxPatternPhases> phaseSeconds;
|
||||
};
|
||||
|
||||
Pattern() : phases{} {}
|
||||
explicit Pattern(PatternId patternIdentifier) : id(patternIdentifier), phases{} {}
|
||||
|
||||
Pattern(PatternId patternIdentifier, std::initializer_list<std::uint8_t> values)
|
||||
: id(patternIdentifier), phaseCount(0), phases{} {
|
||||
for (std::uint8_t value : values) {
|
||||
if (phaseCount < kMaxPatternPhases) {
|
||||
phases[phaseCount++] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Pattern(PatternId patternIdentifier, const std::array<std::uint8_t, kMaxPatternPhases>& values)
|
||||
: id(patternIdentifier), phases(values) {}
|
||||
|
||||
explicit Pattern(const std::array<std::uint8_t, kMaxPatternPhases>& values)
|
||||
: phases(values) {}
|
||||
|
||||
Pattern(std::uint8_t onSeconds, std::uint8_t offSeconds, std::uint8_t onSeconds2,
|
||||
std::uint8_t offSeconds2, std::uint8_t onSeconds3, std::uint8_t offSeconds3)
|
||||
: phases{onSeconds, offSeconds, onSeconds2, offSeconds2, onSeconds3, offSeconds3} {}
|
||||
|
||||
std::uint16_t totalSeconds() const noexcept {
|
||||
std::uint16_t total = 0;
|
||||
const std::size_t count = phaseCount <= kMaxPatternPhases ? phaseCount : 0;
|
||||
for (std::size_t i = 0; i < count; ++i) {
|
||||
total = static_cast<std::uint16_t>(total + phases[i]);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
bool valid() const noexcept {
|
||||
if (!isPatternId(id) || phaseCount > kMaxPatternPhases) {
|
||||
return false;
|
||||
}
|
||||
std::uint16_t total = 0;
|
||||
for (std::size_t i = 0; i < phaseCount; ++i) {
|
||||
if (phases[i] > kMaxPhaseDurationSeconds) {
|
||||
return false;
|
||||
}
|
||||
total = static_cast<std::uint16_t>(total + phases[i]);
|
||||
}
|
||||
return total <= kMaxPatternTotalSeconds;
|
||||
}
|
||||
|
||||
bool phaseIsOn(std::size_t index) const noexcept {
|
||||
return index < phaseCount && (index & 1u) == 0u;
|
||||
}
|
||||
};
|
||||
|
||||
struct Schedule {
|
||||
union {
|
||||
ScheduleId id;
|
||||
ScheduleId scheduleId;
|
||||
};
|
||||
union {
|
||||
ProfileId profileId;
|
||||
ProfileId profile;
|
||||
};
|
||||
union {
|
||||
PatternId patternId;
|
||||
PatternId pattern;
|
||||
};
|
||||
union {
|
||||
std::uint16_t minuteOfDay;
|
||||
std::uint16_t minute;
|
||||
};
|
||||
|
||||
union {
|
||||
std::uint8_t weekdayMask;
|
||||
std::uint8_t days;
|
||||
std::uint8_t dayMask;
|
||||
};
|
||||
|
||||
bool enabled = true;
|
||||
|
||||
Schedule()
|
||||
: id(kInvalidId),
|
||||
profileId(0),
|
||||
patternId(PatternId::A),
|
||||
minuteOfDay(0),
|
||||
weekdayMask(kAllWeekdaysMask) {}
|
||||
|
||||
Schedule(ScheduleId scheduleIdentifier,
|
||||
ProfileId profileIdentifier,
|
||||
PatternId patternIdentifier,
|
||||
std::uint16_t minute,
|
||||
std::uint8_t daysMask,
|
||||
bool isEnabled = true)
|
||||
: id(scheduleIdentifier),
|
||||
profileId(profileIdentifier),
|
||||
patternId(patternIdentifier),
|
||||
minuteOfDay(minute),
|
||||
weekdayMask(daysMask),
|
||||
enabled(isEnabled) {}
|
||||
|
||||
// Convenience overload for callers that naturally specify time before the
|
||||
// pattern identifier.
|
||||
Schedule(ScheduleId scheduleIdentifier,
|
||||
ProfileId profileIdentifier,
|
||||
std::uint16_t minute,
|
||||
PatternId patternIdentifier,
|
||||
std::uint8_t daysMask,
|
||||
bool isEnabled = true)
|
||||
: id(scheduleIdentifier),
|
||||
profileId(profileIdentifier),
|
||||
patternId(patternIdentifier),
|
||||
minuteOfDay(minute),
|
||||
weekdayMask(daysMask),
|
||||
enabled(isEnabled) {}
|
||||
|
||||
static Schedule at(ScheduleId scheduleIdentifier,
|
||||
ProfileId profileIdentifier,
|
||||
PatternId patternIdentifier,
|
||||
std::uint8_t hour,
|
||||
std::uint8_t minute,
|
||||
std::uint8_t daysMask,
|
||||
bool isEnabled = true) {
|
||||
return Schedule(scheduleIdentifier, profileIdentifier, patternIdentifier,
|
||||
static_cast<std::uint16_t>(hour) * 60u + minute,
|
||||
daysMask, isEnabled);
|
||||
}
|
||||
|
||||
std::uint8_t hourPart() const noexcept {
|
||||
return static_cast<std::uint8_t>(minuteOfDay / 60u);
|
||||
}
|
||||
std::uint8_t minutePart() const noexcept {
|
||||
return static_cast<std::uint8_t>(minuteOfDay % 60u);
|
||||
}
|
||||
};
|
||||
|
||||
struct Holiday {
|
||||
union {
|
||||
HolidayId id;
|
||||
HolidayId holidayId;
|
||||
};
|
||||
std::uint8_t month = 1;
|
||||
std::uint8_t day = 1;
|
||||
bool enabled = true;
|
||||
|
||||
Holiday() : id(kInvalidId) {}
|
||||
Holiday(HolidayId holidayIdentifier, std::uint8_t holidayMonth,
|
||||
std::uint8_t holidayDay, bool isEnabled = true)
|
||||
: id(holidayIdentifier), month(holidayMonth), day(holidayDay), enabled(isEnabled) {}
|
||||
|
||||
Holiday(std::uint8_t holidayMonth, std::uint8_t holidayDay)
|
||||
: month(holidayMonth), day(holidayDay) {}
|
||||
|
||||
static Holiday recurring(std::uint8_t holidayMonth, std::uint8_t holidayDay,
|
||||
HolidayId holidayIdentifier = kInvalidId,
|
||||
bool isEnabled = true) {
|
||||
return Holiday(holidayIdentifier, holidayMonth, holidayDay, isEnabled);
|
||||
}
|
||||
};
|
||||
|
||||
struct AppState {
|
||||
FixedVector<Profile, kMaxProfiles> profiles;
|
||||
std::array<Pattern, kMaxPatterns> patterns;
|
||||
FixedVector<Schedule, kMaxSchedules> schedules;
|
||||
FixedVector<Holiday, kMaxHolidays> holidays;
|
||||
union {
|
||||
ProfileId activeProfileId;
|
||||
ProfileId activeProfile;
|
||||
};
|
||||
std::uint32_t revision = 0;
|
||||
|
||||
AppState() : activeProfileId(0) {
|
||||
for (std::size_t i = 0; i < kMaxPatterns; ++i) {
|
||||
patterns[i].id = patternIdFromIndex(i);
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t profileCount() const noexcept { return profiles.size(); }
|
||||
std::size_t scheduleCount() const noexcept { return schedules.size(); }
|
||||
std::size_t holidayCount() const noexcept { return holidays.size(); }
|
||||
|
||||
Pattern* pattern(PatternId id) noexcept {
|
||||
const std::size_t index = patternIndex(id);
|
||||
return index < kMaxPatterns ? &patterns[index] : nullptr;
|
||||
}
|
||||
|
||||
const Pattern* pattern(PatternId id) const noexcept {
|
||||
const std::size_t index = patternIndex(id);
|
||||
return index < kMaxPatterns ? &patterns[index] : nullptr;
|
||||
}
|
||||
|
||||
Profile* profile(ProfileId id) noexcept {
|
||||
for (std::size_t i = 0; i < profiles.size(); ++i) {
|
||||
if (profiles[i].id == id) {
|
||||
return &profiles[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Profile* profile(ProfileId id) const noexcept {
|
||||
for (std::size_t i = 0; i < profiles.size(); ++i) {
|
||||
if (profiles[i].id == id) {
|
||||
return &profiles[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Schedule* schedule(ScheduleId id) noexcept {
|
||||
for (std::size_t i = 0; i < schedules.size(); ++i) {
|
||||
if (schedules[i].id == id) {
|
||||
return &schedules[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Schedule* schedule(ScheduleId id) const noexcept {
|
||||
for (std::size_t i = 0; i < schedules.size(); ++i) {
|
||||
if (schedules[i].id == id) {
|
||||
return &schedules[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Holiday* holiday(HolidayId id) noexcept {
|
||||
for (std::size_t i = 0; i < holidays.size(); ++i) {
|
||||
if (holidays[i].id == id) {
|
||||
return &holidays[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Holiday* holiday(HolidayId id) const noexcept {
|
||||
for (std::size_t i = 0; i < holidays.size(); ++i) {
|
||||
if (holidays[i].id == id) {
|
||||
return &holidays[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
// Monotonic IDs are never inferred from a vector position. Deleting an item
|
||||
// therefore cannot silently change the identity of another item.
|
||||
class StableIdAllocator {
|
||||
public:
|
||||
StableIdAllocator() = default;
|
||||
explicit StableIdAllocator(Id first) noexcept : next_(first == 0 ? 1 : first) {}
|
||||
|
||||
Id nextValue() const noexcept { return next_; }
|
||||
void setNextValue(Id value) noexcept { next_ = value == 0 ? 1 : value; }
|
||||
|
||||
Id next() noexcept {
|
||||
for (;;) {
|
||||
if (next_ == 0 || next_ == kInvalidId) {
|
||||
next_ = 1;
|
||||
}
|
||||
const Id result = next_++;
|
||||
if (result != 0 && result != kInvalidId) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void observe(Id id) noexcept {
|
||||
if (id != kInvalidId && id >= next_) {
|
||||
next_ = static_cast<Id>(id + 1u);
|
||||
}
|
||||
}
|
||||
|
||||
void reset(Id first = 1) noexcept { next_ = first == 0 ? 1 : first; }
|
||||
|
||||
private:
|
||||
Id next_ = 1;
|
||||
};
|
||||
|
||||
} // namespace timbre_core
|
||||
|
||||
// A short namespace alias keeps the library pleasant to use from both the
|
||||
// application and small native tests.
|
||||
namespace timbre = timbre_core;
|
||||
Reference in New Issue
Block a user