Initial commit
This commit is contained in:
commit
b7d90b9afe
16245 files changed
+3396666
No files matched your search
+423
@@ -0,0 +1,423 @@
|
||||
/**
|
||||
* AES fire automaton - scheduling fire patterns for multiple effectors
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*/
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QTimer>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
|
||||
#include <AreaEffectSimulation/logger.h>
|
||||
#include <AreaEffectSimulation/settings.h>
|
||||
|
||||
#include "fire_automaton.h"
|
||||
|
||||
namespace AreaEffectSimulation
|
||||
{
|
||||
|
||||
FireAutomaton::FireAutomaton(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_nextIdx(0)
|
||||
, m_tick(0)
|
||||
, m_durationMSecs(0)
|
||||
, m_intervalMSecs(0)
|
||||
, m_remainingTickMSecs(0)
|
||||
, m_state(State::Uninitialized)
|
||||
, m_logger(getLogger("Utils.FireAutomaton"))
|
||||
{
|
||||
m_timer.setSingleShot(true);
|
||||
connect(&m_timer, &QTimer::timeout, this, &FireAutomaton::nextTick, Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes the fire automaton and derives a fire sequence from given parameters
|
||||
*
|
||||
* @param time Time used for the fire sequence calculation in milliseconds
|
||||
* @param timeType Type of the given time
|
||||
* @param rounds Number of rounds to be fired
|
||||
* @param effectorIds Ids of the effector that shall be used to fire rounds
|
||||
* @param pattern Fire pattern to be used
|
||||
* @return true when the automaton is succesfully initialized
|
||||
*/
|
||||
bool FireAutomaton::init(int32_t time, TimeType type, int32_t rounds, const QVector<int32_t>& effectorIds, const QString& pattern)
|
||||
{
|
||||
if(m_state != State::Stopped && m_state != State::Uninitialized)
|
||||
{
|
||||
ERROR_LOG(m_logger) << " init failed - fire automaton must be in state unitialized or stopped.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(time < 0)
|
||||
{
|
||||
ERROR_LOG(m_logger) << " init failed - time invalid.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(effectorIds.isEmpty())
|
||||
{
|
||||
ERROR_LOG(m_logger) << " init failed - effector Ids invalid.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(rounds <= 0)
|
||||
{
|
||||
ERROR_LOG(m_logger) << " init failed - number of rounds invalid.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(pattern.isEmpty())
|
||||
{
|
||||
ERROR_LOG(m_logger) << " init failed - invalid fire pattern.";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_effectorIds = effectorIds;
|
||||
m_tick = 0;
|
||||
m_remainingTickMSecs = 0;
|
||||
m_nextIdx = 0;
|
||||
|
||||
if(!readPattern(pattern))
|
||||
{
|
||||
ERROR_LOG(m_logger) << " init failed - firing pattern invalid.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!calculateSchedule(rounds))
|
||||
{
|
||||
ERROR_LOG(m_logger) << " init failed - firing schedule invalid.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32_t maxTick = m_schedule.size();
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case TimeType::Cadence:
|
||||
m_intervalMSecs = time;
|
||||
m_durationMSecs = (maxTick - 1) * time;
|
||||
|
||||
break;
|
||||
|
||||
case TimeType::Duration:
|
||||
m_durationMSecs = time;
|
||||
|
||||
if(maxTick == 1)
|
||||
{
|
||||
m_intervalMSecs = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_intervalMSecs = m_durationMSecs / (maxTick - 1);
|
||||
|
||||
// ensure atleast 1 ms spacing between ticks
|
||||
if(m_intervalMSecs <= 0)
|
||||
{
|
||||
m_intervalMSecs = 1;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
setState(State::Stopped);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FireAutomaton::setState(State state)
|
||||
{
|
||||
m_state = state;
|
||||
}
|
||||
|
||||
void FireAutomaton::nextTick()
|
||||
{
|
||||
QVector<int32_t> fireIds = getEffectorIds(m_tick);
|
||||
|
||||
for(auto id : fireIds)
|
||||
{
|
||||
emit fire(id);
|
||||
}
|
||||
|
||||
++m_tick;
|
||||
|
||||
if(m_tick >= m_schedule.size())
|
||||
{
|
||||
stop();
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_state != State::Stepping)
|
||||
{
|
||||
m_timer.start(m_intervalMSecs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Starts the fire automaton's sequence derived from the fire pattern and parameters
|
||||
*
|
||||
* The sequence can only be started in state stopped and sets the state to running. After the sequence is finished, the state will be set to stopped.
|
||||
*
|
||||
* @return True when the automaton is succesfully resumed
|
||||
*/
|
||||
bool FireAutomaton::start()
|
||||
{
|
||||
if(m_state != State::Stopped)
|
||||
{
|
||||
ERROR_LOG(m_logger) << " start failed - fire automaton must be in state stopped.";
|
||||
return false;
|
||||
}
|
||||
|
||||
setState(State::Running);
|
||||
m_timer.start(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resumes a previously stopped fire automaton
|
||||
*
|
||||
* In the state paused the fire automaton sequence execution will be resumed and the state set to running.
|
||||
*
|
||||
* @return True when the automaton is succesfully resumed
|
||||
*/
|
||||
bool FireAutomaton::resume()
|
||||
{
|
||||
if(m_state != State::Paused)
|
||||
{
|
||||
ERROR_LOG(m_logger) << " resume failed - fire automaton must be in state paused.";
|
||||
return false;
|
||||
}
|
||||
|
||||
setState(State::Running);
|
||||
m_timer.start(m_remainingTickMSecs);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pauses a running fire automaton sequence
|
||||
*
|
||||
* In the state running the fire automaton sequence execution will be halted and the state set to paused.
|
||||
*
|
||||
* @return True when the automaton is succesfully paused
|
||||
*/
|
||||
bool FireAutomaton::pause()
|
||||
{
|
||||
if(m_state != State::Running)
|
||||
{
|
||||
ERROR_LOG(m_logger) << " pause failed - fire automaton must be in state running.";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_remainingTickMSecs = m_timer.remainingTime();
|
||||
m_timer.stop();
|
||||
setState(State::Paused);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stops and resets the fire automaton
|
||||
*
|
||||
* Forces the state stopped and resets the automaton sequence execution.
|
||||
*/
|
||||
void FireAutomaton::stop()
|
||||
{
|
||||
m_tick = 0;
|
||||
m_remainingTickMSecs = 0;
|
||||
m_timer.stop();
|
||||
setState(State::Stopped);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Steps through the derived fire automaton sequence
|
||||
*
|
||||
* Stepping can only be started from the state stopped and sets the state to stepping. After stepping through the whole sequence, the state will be set to stopped.
|
||||
*
|
||||
* @return True when step is succesfully performed
|
||||
*/
|
||||
bool FireAutomaton::performStep()
|
||||
{
|
||||
if(m_state != State::Stopped && m_state != State::Stepping)
|
||||
{
|
||||
ERROR_LOG(m_logger) << " performStep failed - fire automaton must be in state stepping or stopped.";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_state = State::Stepping;
|
||||
nextTick();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the current state of the fire automaton
|
||||
*
|
||||
* @return Fire automaton state
|
||||
*/
|
||||
FireAutomaton::State FireAutomaton::getState() const
|
||||
{
|
||||
return m_state;
|
||||
}
|
||||
|
||||
bool FireAutomaton::readPattern(const QString& pattern)
|
||||
{
|
||||
bool fill = false;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
QStringList elements = pattern.split(QStringLiteral(","), Qt::SkipEmptyParts);
|
||||
#else
|
||||
QStringList elements = pattern.split(QStringLiteral(","), QString::SkipEmptyParts);
|
||||
#endif
|
||||
|
||||
for(const QString& element : elements)
|
||||
{
|
||||
FireAutomaton::PatternElement patternElement;
|
||||
|
||||
if(!patternElementFromString(element, patternElement))
|
||||
{
|
||||
WARNING_LOG(m_logger) << " skipped invalid firing pattern elements.";
|
||||
continue;
|
||||
}
|
||||
|
||||
if(patternElement == FireAutomaton::PatternElement::NextFill || patternElement == FireAutomaton::PatternElement::AllFill)
|
||||
{
|
||||
if(!fill)
|
||||
{
|
||||
fill = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG(m_logger) << " a pattern may contain at most one fill element.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_pattern.append(patternElement);
|
||||
}
|
||||
|
||||
if(m_pattern.empty())
|
||||
{
|
||||
ERROR_LOG(m_logger) << " error occured reading firing pattern.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FireAutomaton::patternElementFromString(const QString& str, FireAutomaton::PatternElement& out)
|
||||
{
|
||||
const QString key = str.trimmed().toLower();
|
||||
|
||||
if(key == QStringLiteral("all"))
|
||||
out = FireAutomaton::PatternElement::All;
|
||||
else if(key == QStringLiteral("all*"))
|
||||
out = FireAutomaton::PatternElement::AllFill;
|
||||
else if(key == QStringLiteral("next"))
|
||||
out = FireAutomaton::PatternElement::Next;
|
||||
else if(key == QStringLiteral("next*"))
|
||||
out = FireAutomaton::PatternElement::NextFill;
|
||||
else if(key == QStringLiteral("idle"))
|
||||
out = FireAutomaton::PatternElement::Idle;
|
||||
else
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVector<int32_t> FireAutomaton::getEffectorIds(int32_t tick) const
|
||||
{
|
||||
return m_schedule[tick];
|
||||
}
|
||||
|
||||
int32_t FireAutomaton::getMaxTick() const
|
||||
{
|
||||
return m_schedule.size();
|
||||
}
|
||||
|
||||
bool FireAutomaton::calculateSchedule(int32_t rounds)
|
||||
{
|
||||
|
||||
int32_t minCounter = 0;
|
||||
|
||||
// count needed rounds
|
||||
for(const FireAutomaton::PatternElement& element : m_pattern)
|
||||
{
|
||||
if(element == FireAutomaton::PatternElement::All)
|
||||
minCounter += m_effectorIds.size();
|
||||
else if(element == FireAutomaton::PatternElement::Next)
|
||||
++minCounter;
|
||||
}
|
||||
|
||||
int32_t fillCounter = rounds - minCounter;
|
||||
m_schedule.clear();
|
||||
|
||||
for(const FireAutomaton::PatternElement& element : m_pattern)
|
||||
{
|
||||
// stop when all rounds used
|
||||
if(rounds <= 0)
|
||||
break;
|
||||
|
||||
if(element == FireAutomaton::PatternElement::All)
|
||||
{
|
||||
if(rounds >= m_effectorIds.size())
|
||||
{
|
||||
m_schedule.append(m_effectorIds);
|
||||
rounds -= m_effectorIds.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_schedule.append(m_effectorIds.mid(0,rounds));
|
||||
rounds = 0;
|
||||
}
|
||||
}
|
||||
else if(element == FireAutomaton::PatternElement::AllFill && fillCounter > 0)
|
||||
{
|
||||
const int32_t times = fillCounter / m_effectorIds.size();
|
||||
const int32_t remainder = fillCounter % m_effectorIds.size();
|
||||
|
||||
for(int32_t i=0; i < times; i++)
|
||||
{
|
||||
m_schedule.append(m_effectorIds);
|
||||
rounds -= m_effectorIds.size();
|
||||
}
|
||||
|
||||
if(remainder > 0)
|
||||
{
|
||||
m_schedule.append(m_effectorIds.mid(0, remainder));
|
||||
rounds -= remainder;
|
||||
}
|
||||
}
|
||||
else if(element == FireAutomaton::PatternElement::Idle)
|
||||
{
|
||||
m_schedule.append(QVector<int32_t>{});
|
||||
}
|
||||
else if(element == FireAutomaton::PatternElement::Next)
|
||||
{
|
||||
m_schedule.append(QVector<int32_t>{m_effectorIds[m_nextIdx]});
|
||||
m_nextIdx = (m_nextIdx + 1) % m_effectorIds.size();
|
||||
rounds--;
|
||||
}
|
||||
else if(element == FireAutomaton::PatternElement::NextFill && fillCounter > 0)
|
||||
{
|
||||
for(int32_t i = 0; i < fillCounter; i++)
|
||||
{
|
||||
m_schedule.append(QVector<int32_t>{m_effectorIds[i % m_effectorIds.size()]});
|
||||
rounds--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(rounds > 0)
|
||||
{
|
||||
ERROR_LOG(m_logger) << " scheduling error - fire pattern does not provide sufficient slots to schedule all rounds. " << rounds;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AreaEffectSimulation
|
||||
Reference in new issue
Block a user