C
Qt Quick Ultralite Motorcycle Cluster Demo
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
#ifndef STATEMACHINE_H
#define STATEMACHINE_H
#include <map>
#include <vector>
#include <qul/timer.h>
#include <platforminterface/allocator.h>
template<typename TStateId,
const size_t MaxStates = 10,
typename TLayerId = int,
const size_t NumLayers = 1,
const size_t ChangeStateBufferSize = NumLayers * 10,
const size_t UpdateStateBufferSize = NumLayers * 10>
class StateMachine
{
public:
struct AbstractState
{
typedef TStateId Id;
typedef StateMachine SM;
typedef TLayerId LayerId;
virtual ~AbstractState() {}
virtual void onEnter(const TStateId &prevState, const TLayerId &layerId, StateMachine &sm) = 0;
virtual void onUpdate(uint32_t tick, const TLayerId &layerId, StateMachine &sm) = 0;
virtual void onLeave(const TStateId &nextState, const TLayerId &layerId, StateMachine &sm) = 0;
};
typedef AbstractState BaseStateType;
typedef TStateId StateId;
StateMachine(const TStateId &idleStateId);
bool registerState(const TStateId &id, BaseStateType *state);
void changeState(const TStateId &nextStateId, const TLayerId &layerId = TLayerId(), uint32_t delay = 0);
void update(uint32_t tick, const TLayerId &layerId = TLayerId());
bool requestUpdate(uint32_t interval, bool repeat, const TLayerId &layerId = TLayerId());
bool dismissUpdate(const TLayerId &layerId = TLayerId());
bool dismissScheduledStateChanges(const TLayerId &layerId = TLayerId());
TStateId getCurrentStateId(const TLayerId &layerId = TLayerId()) const;
private:
struct DelayedTask
{
DelayedTask(const TLayerId &layerId_, StateMachine &sm_)
: layerId(layerId_)
, sm(&sm_)
{}
virtual ~DelayedTask() {}
virtual void doTask() = 0;
protected:
TLayerId layerId;
StateMachine *sm;
};
struct TimerElapsed
{
DelayedTask *self;
void operator()() { self->doTask(); }
};
struct UpdateStateTask : DelayedTask
{
UpdateStateTask(const TLayerId &layerId_, StateMachine &sm_)
: DelayedTask(layerId_, sm_)
, interval(0)
, repeat(false)
{}
void doTask()
{
// TODO: Can try to calculate real time delta
this->sm->update(this->interval, this->layerId);
if (!this->repeat) {
this->sm->removeUpdateStateTask(this->layerId, this);
}
};
uint32_t interval;
bool repeat;
};
struct ChangeStateTask : DelayedTask
{
ChangeStateTask(const TStateId &nextStateId_, const TLayerId &layerId_, StateMachine &sm_)
: DelayedTask(layerId_, sm_)
, nextStateId(nextStateId_)
{}
void doTask()
{
this->sm->doStateChange(this->nextStateId, this->layerId);
this->sm->removeStateChangeTask(this->layerId, this);
};
private:
TStateId nextStateId;
};
struct Tasks
{
typedef std::multimap<TLayerId,
ChangeStateTask,
std::less<TLayerId>,
Qul::PlatformInterface::Allocator<std::pair<const TLayerId, ChangeStateTask> > >
ChangeStateTasks;
typedef std::multimap<TLayerId,
UpdateStateTask,
std::less<TLayerId>,
Qul::PlatformInterface::Allocator<std::pair<const TLayerId, UpdateStateTask> > >
UpdateStateTasks;
typedef std::map<DelayedTask *,
Qul::Timer *,
std::less<DelayedTask *>,
Qul::PlatformInterface::Allocator<std::pair<DelayedTask *const, Qul::Timer *> > >
Timers;
typedef std::vector<DelayedTask *, Qul::PlatformInterface::Allocator<DelayedTask *> > DetachedTimers;
ChangeStateTasks changeState;
UpdateStateTasks updateState;
Timers timers;
DetachedTimers detachedTimers;
};
typedef std::map<TStateId,
AbstractState *,
std::less<TStateId>,
Qul::PlatformInterface::Allocator<std::pair<const TStateId, AbstractState *> > >
StatesMap;
typedef std::pair<TStateId, AbstractState *> StateEntry;
typedef std::map<TLayerId,
StateEntry,
std::less<TLayerId>,
Qul::PlatformInterface::Allocator<std::pair<const TLayerId, StateEntry> > >
Layers;
void detachTimer(DelayedTask *task);
void removeDetachedTimers();
bool doStateChange(const TStateId &newStateId, const TLayerId &layerId);
bool scheduleStateChange(const TStateId &newStateId, const TLayerId &layerId, uint32_t timeout);
bool removeStateChangeTask(const TLayerId &layerId, ChangeStateTask *task);
bool removeUpdateStateTask(const TLayerId &layerId, UpdateStateTask *task);
StatesMap _stateRegistry;
Layers _layers;
Tasks _tasks;
const TStateId _idleStateId;
};
template<typename TStateId,
const size_t MaxStates,
typename TLayerId,
const size_t NumLayers,
const size_t ChangeStateBufferSize,
const size_t UpdateStateBufferSize>
StateMachine<TStateId, MaxStates, TLayerId, NumLayers, ChangeStateBufferSize, UpdateStateBufferSize>::StateMachine(
const TStateId &idleStateId)
: _idleStateId(idleStateId)
{}
template<typename TStateId,
const size_t MaxStates,
typename TLayerId,
const size_t NumLayers,
const size_t ChangeStateBufferSize,
const size_t UpdateStateBufferSize>
bool StateMachine<TStateId, MaxStates, TLayerId, NumLayers, ChangeStateBufferSize, UpdateStateBufferSize>::registerState(
const TStateId &id, BaseStateType *state)
{
if (_stateRegistry.size() >= MaxStates) {
return false;
}
if (_stateRegistry.count(id) > 0) {
return false;
}
_stateRegistry[id] = state;
return true;
}
template<typename TStateId,
const size_t MaxStates,
typename TLayerId,
const size_t NumLayers,
const size_t ChangeStateBufferSize,
const size_t UpdateStateBufferSize>
void StateMachine<TStateId, MaxStates, TLayerId, NumLayers, ChangeStateBufferSize, UpdateStateBufferSize>::changeState(
const TStateId &nextStateId, const TLayerId &layer, uint32_t delay)
{
removeDetachedTimers();
if (delay == 0) {
doStateChange(nextStateId, layer);
} else {
scheduleStateChange(nextStateId, layer, delay);
}
}
template<typename TStateId,
const size_t MaxStates,
typename TLayerId,
const size_t NumLayers,
const size_t ChangeStateBufferSize,
const size_t UpdateStateBufferSize>
void StateMachine<TStateId, MaxStates, TLayerId, NumLayers, ChangeStateBufferSize, UpdateStateBufferSize>::update(
uint32_t tick, const TLayerId &layerId)
{
typename Layers::iterator layerIt = _layers.find(layerId);
if (layerIt != _layers.end()) {
layerIt->second.second->onUpdate(tick, layerId, *this);
}
}
template<typename TStateId,
const size_t MaxStates,
typename TLayerId,
const size_t NumLayers,
const size_t ChangeStateBufferSize,
const size_t UpdateStateBufferSize>
TStateId
StateMachine<TStateId, MaxStates, TLayerId, NumLayers, ChangeStateBufferSize, UpdateStateBufferSize>::getCurrentStateId(
const TLayerId &layerId) const
{
typename Layers::const_iterator layerIt = _layers.find(layerId);
if (layerIt == _layers.end()) {
// No layer found (layer was idle)
return _idleStateId;
}
return layerIt->second.first;
}
template<typename TStateId,
const size_t MaxStates,
typename TLayerId,
const size_t NumLayers,
const size_t ChangeStateBufferSize,
const size_t UpdateStateBufferSize>
bool StateMachine<TStateId, MaxStates, TLayerId, NumLayers, ChangeStateBufferSize, UpdateStateBufferSize>::doStateChange(
const TStateId &nextStateId, const TLayerId &layer)
{
// Check if state is present on any layer
typename Layers::iterator layerIt;
for (layerIt = _layers.begin(); layerIt != _layers.end(); ++layerIt) {
if (layerIt->second.first == nextStateId) {
// next state is already present on other layer
break;
}
}
if (layerIt != _layers.end()) {
// nextState already exist on different layer
if (layerIt->first == layer) {
// Change for the current state requested
// Nothing to do here
return false;
} else {
// Move state between layers
// Get current state from target layer
typename Layers::iterator targetLayerIt = _layers.find(layer);
TStateId prevStateId = _idleStateId;
if (targetLayerIt != _layers.end()) {
// Notify old state from target layer if any
AbstractState *currentStateFromTargetLayer = targetLayerIt->second.second;
prevStateId = targetLayerIt->second.first;
currentStateFromTargetLayer->onLeave(nextStateId, layer, *this);
}
assert(nextStateId == layerIt->second.first);
// Leave old layer
AbstractState *currentState = layerIt->second.second;
currentState->onLeave(_idleStateId, layerIt->first, *this);
// Enter new layer
currentState->onEnter(prevStateId, layer, *this);
_layers[layer] = layerIt->second;
// Clear old layer
_layers.erase(layerIt);
return true;
}
}
// Find next state
typename StatesMap::iterator nextStateIt = _stateRegistry.end();
if (nextStateId != _idleStateId) {
nextStateIt = _stateRegistry.find(nextStateId);
if (nextStateIt == _stateRegistry.end()) {
// Target state not found! This should not happen.
assert(false);
return false;
}
}
// Find target layer
typename Layers::iterator targetLayerIt = _layers.find(layer);
if (targetLayerIt == _layers.end()) {
// No state on this layer yet
if (nextStateIt == _stateRegistry.end()) {
// No new state is idle - nothing to do
return false;
}
nextStateIt->second->onEnter(_idleStateId, layer, *this);
_layers[layer] = *nextStateIt;
} else {
// State already exists on this layer
if (nextStateIt == _stateRegistry.end()) {
// Next state is idle
targetLayerIt->second.second->onLeave(_idleStateId, layer, *this);
_layers.erase(targetLayerIt);
} else {
// Switch states
targetLayerIt->second.second->onLeave(nextStateIt->first, layer, *this);
nextStateIt->second->onEnter(targetLayerIt->second.first, layer, *this);
_layers[layer] = *nextStateIt;
}
}
return true;
}
template<typename TStateId,
const size_t MaxStates,
typename TLayerId,
const size_t NumLayers,
const size_t ChangeStateBufferSize,
const size_t UpdateStateBufferSize>
bool StateMachine<TStateId, MaxStates, TLayerId, NumLayers, ChangeStateBufferSize, UpdateStateBufferSize>::
scheduleStateChange(const TStateId &newStateId, const TLayerId &layerId, uint32_t timeout)
{
if (_tasks.changeState.size() >= _tasks.changeState.max_size()) {
assert(!"Tasks capacity exceeded");
return false;
}
typename Tasks::ChangeStateTasks::iterator result = _tasks.changeState.insert(
typename Tasks::ChangeStateTasks::value_type(layerId, ChangeStateTask(newStateId, layerId, *this)));
ChangeStateTask *task = &result->second;
Qul::Timer *timer = Qul::PlatformInterface::qul_new<Qul::Timer>();
_tasks.timers[task] = timer;
TimerElapsed callback = {task};
timer->onTimeout(callback);
timer->setSingleShot(true);
timer->start(timeout);
return true;
}
template<typename TStateId,
const size_t MaxStates,
typename TLayerId,
const size_t NumLayers,
const size_t ChangeStateBufferSize,
const size_t UpdateStateBufferSize>
bool StateMachine<TStateId, MaxStates, TLayerId, NumLayers, ChangeStateBufferSize, UpdateStateBufferSize>::
removeStateChangeTask(const TLayerId &layerId, ChangeStateTask *task)
{
std::pair<typename Tasks::ChangeStateTasks::iterator, typename Tasks::ChangeStateTasks::iterator> scope
= _tasks.changeState.equal_range(layerId);
for (; scope.first != scope.second; ++scope.first) {
if (&scope.first->second == task) {
detachTimer(task);
_tasks.changeState.erase(scope.first);
return true;
}
}
assert(!"Task not found");
return false;
}
template<typename TStateId,
const size_t MaxStates,
typename TLayerId,
const size_t NumLayers,
const size_t ChangeStateBufferSize,
const size_t UpdateStateBufferSize>
bool StateMachine<TStateId, MaxStates, TLayerId, NumLayers, ChangeStateBufferSize, UpdateStateBufferSize>::requestUpdate(
uint32_t interval, bool repeat, const TLayerId &layerId)
{
removeDetachedTimers();
if (_tasks.updateState.size() >= _tasks.updateState.max_size()) {
assert(!"Tasks capacity exceeded");
return false;
}
typename Tasks::UpdateStateTasks::iterator result = _tasks.updateState.insert(
typename Tasks::UpdateStateTasks::value_type(layerId, UpdateStateTask(layerId, *this)));
UpdateStateTask *task = &result->second;
task->interval = interval;
task->repeat = repeat;
Qul::Timer *timer = Qul::PlatformInterface::qul_new<Qul::Timer>();
_tasks.timers[task] = timer;
TimerElapsed callback = {task};
timer->onTimeout(callback);
timer->setSingleShot(!repeat);
timer->start(interval);
return true;
}
template<typename TStateId,
const size_t MaxStates,
typename TLayerId,
const size_t NumLayers,
const size_t ChangeStateBufferSize,
const size_t UpdateStateBufferSize>
bool StateMachine<TStateId, MaxStates, TLayerId, NumLayers, ChangeStateBufferSize, UpdateStateBufferSize>::
removeUpdateStateTask(const TLayerId &layerId, UpdateStateTask *task)
{
std::pair<typename Tasks::UpdateStateTasks::iterator, typename Tasks::UpdateStateTasks::iterator> scope
= _tasks.updateState.equal_range(layerId);
for (; scope.first != scope.second; ++scope.first) {
if (&scope.first->second == task) {
detachTimer(task);
_tasks.updateState.erase(scope.first);
return true;
}
}
assert(!"Task not found");
return false;
}
template<typename TStateId,
const size_t MaxStates,
typename TLayerId,
const size_t NumLayers,
const size_t ChangeStateBufferSize,
const size_t UpdateStateBufferSize>
void StateMachine<TStateId, MaxStates, TLayerId, NumLayers, ChangeStateBufferSize, UpdateStateBufferSize>::detachTimer(
DelayedTask *task)
{
typename Tasks::Timers::iterator timerIt = _tasks.timers.find(task);
if (timerIt != _tasks.timers.end()) {
timerIt->second->stop();
}
_tasks.detachedTimers.push_back(task);
}
template<typename TStateId,
const size_t MaxStates,
typename TLayerId,
const size_t NumLayers,
const size_t ChangeStateBufferSize,
const size_t UpdateStateBufferSize>
void StateMachine<TStateId, MaxStates, TLayerId, NumLayers, ChangeStateBufferSize, UpdateStateBufferSize>::
removeDetachedTimers()
{
for (size_t i = 0; i < _tasks.detachedTimers.size(); ++i) {
typename Tasks::Timers::iterator it = _tasks.timers.find(_tasks.detachedTimers[i]);
if (it != _tasks.timers.end()) {
Qul::PlatformInterface::qul_delete(it->second);
_tasks.timers.erase(it);
}
}
_tasks.detachedTimers.clear();
}
template<typename TStateId,
const size_t MaxStates,
typename TLayerId,
const size_t NumLayers,
const size_t ChangeStateBufferSize,
const size_t UpdateStateBufferSize>
bool StateMachine<TStateId, MaxStates, TLayerId, NumLayers, ChangeStateBufferSize, UpdateStateBufferSize>::dismissUpdate(
const TLayerId &layerId)
{
removeDetachedTimers();
std::pair<typename Tasks::UpdateStateTasks::iterator, typename Tasks::UpdateStateTasks::iterator> scope
= _tasks.updateState.equal_range(layerId);
if (scope.first != scope.second) {
UpdateStateTask *task = &scope.first->second;
detachTimer(task);
_tasks.updateState.erase(scope.first);
return true;
}
return false;
}
template<typename TStateId,
const size_t MaxStates,
typename TLayerId,
const size_t NumLayers,
const size_t ChangeStateBufferSize,
const size_t UpdateStateBufferSize>
bool StateMachine<TStateId, MaxStates, TLayerId, NumLayers, ChangeStateBufferSize, UpdateStateBufferSize>::
dismissScheduledStateChanges(const TLayerId &layerId)
{
removeDetachedTimers();
std::pair<typename Tasks::ChangeStateTasks::iterator, typename Tasks::ChangeStateTasks::iterator> scope
= _tasks.changeState.equal_range(layerId);
if (scope.first != scope.second) {
ChangeStateTask *task = &scope.first->second;
detachTimer(task);
_tasks.changeState.erase(scope.first);
return true;
}
return false;
}
#endif // STATEMACHINE_H