162 lines
4.9 KiB
C++
162 lines
4.9 KiB
C++
#include "pch.h"
|
|
|
|
#include "event_queue.h"
|
|
|
|
bool TriggerScheduler::assertNotInList(AngleBasedEvent *head, AngleBasedEvent *element) {
|
|
assertNotInListMethodBody(head, element, nextToothEvent)
|
|
}
|
|
|
|
void TriggerScheduler::schedule(const char *msg, AngleBasedEvent* event, angle_t angle, action_s action) {
|
|
event->setAngle(angle);
|
|
|
|
schedule(msg, event, action);
|
|
}
|
|
|
|
/**
|
|
* Schedules 'action' to occur at engine cycle angle 'angle'.
|
|
*
|
|
* @return true if event corresponds to current tooth and was time-based scheduler
|
|
* false if event was put into queue for scheduling at a later tooth
|
|
*/
|
|
bool TriggerScheduler::scheduleOrQueue(const char *msg, AngleBasedEvent *event,
|
|
efitick_t edgeTimestamp,
|
|
angle_t angle,
|
|
action_s action,
|
|
float currentPhase, float nextPhase) {
|
|
event->setAngle(angle);
|
|
|
|
// *kludge* naming mess: if (shouldSchedule) { scheduleByAngle } else { schedule } see header for more details
|
|
if (event->shouldSchedule(currentPhase, nextPhase)) {
|
|
// if we're due now, just schedule the event
|
|
scheduleByAngle(
|
|
&event->eventScheduling,
|
|
edgeTimestamp,
|
|
event->getAngleFromNow(currentPhase),
|
|
action
|
|
);
|
|
|
|
return true;
|
|
} else {
|
|
// If not due now, add it to the queue to be scheduled later
|
|
schedule(msg, event, action);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void TriggerScheduler::schedule(const char *msg, AngleBasedEvent* event, action_s action) {
|
|
if (event->getAngle() < 0) {
|
|
// at the moment we expect API consumer to wrap angle. shall we do the wrapping in the enginePhase setter?
|
|
// i.e. what is the best level to take care of the range constraint?
|
|
criticalError("Negative angle %s %f", msg, event->getAngle());
|
|
}
|
|
|
|
event->action = action;
|
|
|
|
{
|
|
chibios_rt::CriticalSectionLocker csl;
|
|
|
|
// TODO: This is O(n), consider some other way of detecting if in a list,
|
|
// and consider doubly linked or other list tricks.
|
|
|
|
if (!assertNotInList(m_angleBasedEventsHead, event)) {
|
|
// Use Append to retain some semblance of event ordering in case of
|
|
// time skew. Thus on events are always followed by off events.
|
|
LL_APPEND2(m_angleBasedEventsHead, event, nextToothEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TriggerScheduler::scheduleEventsUntilNextTriggerTooth(int rpm,
|
|
efitick_t edgeTimestamp, float currentPhase, float nextPhase) {
|
|
|
|
if (!isValidRpm(rpm)) {
|
|
// this might happen for instance in case of a single trigger event after a pause
|
|
return;
|
|
}
|
|
|
|
AngleBasedEvent *current, *tmp, *keephead;
|
|
AngleBasedEvent *keeptail = nullptr;
|
|
|
|
{
|
|
chibios_rt::CriticalSectionLocker csl;
|
|
|
|
keephead = m_angleBasedEventsHead;
|
|
m_angleBasedEventsHead = nullptr;
|
|
}
|
|
|
|
LL_FOREACH_SAFE2(keephead, current, tmp, nextToothEvent)
|
|
{
|
|
if (current->shouldSchedule(currentPhase, nextPhase)) {
|
|
// time to fire a spark which was scheduled previously
|
|
|
|
// Yes this looks like O(n^2), but that's only over the entire engine
|
|
// cycle. It's really O(mn + nn) where m = # of teeth and n = # events
|
|
// fired per cycle. The number of teeth outweigh the number of events, at
|
|
// least for 60-2.... So odds are we're only firing an event or two per
|
|
// tooth, which means the outer loop is really only O(n). And if we are
|
|
// firing many events per teeth, then it's likely the events before this
|
|
// one also fired and thus the call to LL_DELETE2 is closer to O(1).
|
|
LL_DELETE2(keephead, current, nextToothEvent);
|
|
|
|
scheduling_s * sDown = ¤t->eventScheduling;
|
|
|
|
#if SPARK_EXTREME_LOGGING
|
|
efiPrintf("time to invoke [%.1f, %.1f) %d %d",
|
|
currentPhase, nextPhase, getRevolutionCounter(), time2print(getTimeNowUs()));
|
|
#endif /* SPARK_EXTREME_LOGGING */
|
|
|
|
// In case this event was scheduled by overdwell protection, cancel it so
|
|
// we can re-schedule at the correct time
|
|
// [tag:overdwell]
|
|
engine->executor.cancel(sDown);
|
|
|
|
scheduleByAngle(
|
|
sDown,
|
|
edgeTimestamp,
|
|
current->getAngleFromNow(currentPhase),
|
|
current->action
|
|
);
|
|
} else {
|
|
keeptail = current; // Used for fast list concatenation
|
|
}
|
|
}
|
|
|
|
if (keephead) {
|
|
chibios_rt::CriticalSectionLocker csl;
|
|
|
|
// Put any new entries onto the end of the keep list
|
|
keeptail->nextToothEvent = m_angleBasedEventsHead;
|
|
m_angleBasedEventsHead = keephead;
|
|
}
|
|
}
|
|
|
|
bool AngleBasedEvent::shouldSchedule(float currentPhase, float nextPhase) const {
|
|
return isPhaseInRange(this->enginePhase, currentPhase, nextPhase);
|
|
}
|
|
|
|
float AngleBasedEvent::getAngleFromNow(float currentPhase) const {
|
|
float angleOffset = this->enginePhase - currentPhase;
|
|
if (angleOffset < 0) {
|
|
angleOffset += engine->engineState.engineCycle;
|
|
}
|
|
|
|
return angleOffset;
|
|
}
|
|
|
|
#if EFI_UNIT_TEST
|
|
// todo: reduce code duplication with another 'getElementAtIndexForUnitText'
|
|
AngleBasedEvent * TriggerScheduler::getElementAtIndexForUnitTest(int index) {
|
|
AngleBasedEvent * current;
|
|
|
|
LL_FOREACH2(m_angleBasedEventsHead, current, nextToothEvent)
|
|
{
|
|
if (index == 0)
|
|
return current;
|
|
index--;
|
|
}
|
|
criticalError("getElementAtIndexForUnitText: null");
|
|
return nullptr;
|
|
}
|
|
#endif /* EFI_UNIT_TEST */
|