• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

igor-krechetov / hsmcpp / 5076885588

pending completion
5076885588

push

github

igor-krechetov
[1.0.0][r] first release

63 of 63 new or added lines in 9 files covered. (100.0%)

2318 of 2522 relevant lines covered (91.91%)

1610.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.13
/src/HsmImpl.cpp
1
// Copyright (C) 2021 Igor Krechetov
2
// Distributed under MIT license. See file LICENSE for details
3

4
#include "HsmImpl.hpp"
5

6
#include <algorithm>
7

8
#include "hsmcpp/IHsmEventDispatcher.hpp"
9
#include "hsmcpp/logging.hpp"
10
#include "hsmcpp/os/ConditionVariable.hpp"
11
#include "hsmcpp/os/LockGuard.hpp"
12
#include "hsmcpp/os/os.hpp"
13

14
#if !defined(HSM_DISABLE_THREADSAFETY) && defined(FREERTOS_AVAILABLE)
15
  #include "hsmcpp/os/InterruptsFreeSection.hpp"
16
#endif
17

18
#ifdef HSMBUILD_DEBUGGING
19
  #include <array>
20
  #include <chrono>
21
  #include <cstdlib>
22
  #include <cstring>
23

24
  #include "hsmcpp/os/Mutex.hpp"
25
  #include "hsmcpp/os/CriticalSection.hpp"
26

27
// WIN, access
28
  #ifdef WIN32
29
    #include <io.h>
30
    #define F_OK 0
31
  #else
32
    #include <unistd.h>
33
    // cppcheck-suppress misra-c2012-21.10
34
    #include <ctime>
35
  #endif
36
#endif
37

38
#ifndef HSM_DISABLE_DEBUG_TRACES
39
  #define DEBUG_DUMP_ACTIVE_STATES() dumpActiveStates()
40
#else
41
  #define DEBUG_DUMP_ACTIVE_STATES()
42
#endif
43

44
// NOTE: used only for logging during development. in release mode macros is empty
45
// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
46
// cppcheck-suppress misra-c2012-8.4
47
HSM_TRACE_PREINIT()
48
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
49

50
constexpr const char* HSM_TRACE_CLASS = "HierarchicalStateMachine";
51

52
// These macroses can't be converted to 'constexpr' template functions
53
// NOLINTBEGIN(cppcoreguidelines-macro-usage)
54

55
// If defined, HSM will performe safety checks during states and substates registration.
56
// Normally HSM structure should be static, so this feature is usefull only
57
// during development since it reduces performance a bit
58
// #define HSM_ENABLE_SAFE_STRUCTURE                    1
59

60
// Thread safety is enabled by default, but it adds some overhead related with mutex usage.
61
// If performance is critical and it's ensured that HSM is used only from a single thread,
62
// then synchronization could be disabled during compilation.
63
// #define HSM_DISABLE_THREADSAFETY                     1
64
#ifdef HSM_DISABLE_THREADSAFETY
65
  #define HSM_SYNC_EVENTS_QUEUE()
66
#elif defined(FREERTOS_AVAILABLE)
67
  #define HSM_SYNC_EVENTS_QUEUE() InterruptsFreeSection lck
68
#else
69
  #define HSM_SYNC_EVENTS_QUEUE() LockGuard lck(mEventsSync)
70
#endif  // HSM_DISABLE_THREADSAFETY
71

72
// NOLINTEND(cppcoreguidelines-macro-usage)
73

74
namespace hsmcpp {
75

76
// ============================================================================
77
// PUBLIC
78
// ============================================================================
79
HierarchicalStateMachine::Impl::Impl(HierarchicalStateMachine* parent, const StateID_t initialState)
1,190✔
80
    // cppcheck-suppress misra-c2012-10.4 ; false-positive. thinks that ':' is arithmetic operation
81
    : mParent(parent)
1,190✔
82
    , mInitialState(initialState) {
1,190✔
83
    HSM_TRACE_INIT();
1,190✔
84
}
1,190✔
85

86
HierarchicalStateMachine::Impl::~Impl() {
2,360✔
87
    release();
1,180✔
88
}
4,930✔
89

90
void HierarchicalStateMachine::Impl::resetParent() {
1,180✔
91
#ifndef HSM_DISABLE_THREADSAFETY
92
    LockGuard lk(mParentSync);
1,180✔
93
#endif
94

95
    mParent = nullptr;
1,180✔
96
}
1,180✔
97

98
void HierarchicalStateMachine::Impl::setInitialState(const StateID_t initialState) {
410✔
99
    if (true == mDispatcher.expired()) {
410✔
100
        mInitialState = initialState;
350✔
101
    }
102
}
410✔
103

104
bool HierarchicalStateMachine::Impl::initialize(const std::weak_ptr<IHsmEventDispatcher>& dispatcher) {
1,190✔
105
    HSM_TRACE_CALL_DEBUG();
1,190✔
106
    bool result = false;
1,190✔
107

108
    if (true == mDispatcher.expired()) {
1,190✔
109
        auto dispatcherPtr = dispatcher.lock();
1,190✔
110

111
        // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
112
        if (dispatcherPtr) {
1,190✔
113
            if (true == dispatcherPtr->start()) {
1,190✔
114
                std::weak_ptr<Impl> ptrInstance;
1,190✔
115

116
                HSM_TRY {
1,190✔
117
                    ptrInstance = shared_from_this();
2,380✔
118
                }
119
                HSM_CATCH(const std::bad_weak_ptr& e) {
×
120
                    HSM_TRACE_ERROR("Impl instance must be created as shared_ptr");
×
121
                }
×
122

123
                if (false == ptrInstance.expired()) {
2,380✔
124
                    mDispatcher = dispatcher;
1,190✔
125

126
                    createEventHandler(dispatcherPtr, ptrInstance);
1,190✔
127
                    createTimerHandler(dispatcherPtr, ptrInstance);
1,190✔
128
                    createEnqueuedEventHandler(dispatcherPtr, ptrInstance);
1,190✔
129

130
                    if ((INVALID_HSM_DISPATCHER_HANDLER_ID != mEventsHandlerId) &&
1,190✔
131
                        (INVALID_HSM_DISPATCHER_HANDLER_ID != mTimerHandlerId)) {
1,190✔
132
                        logHsmAction(HsmLogAction::IDLE,
1,190✔
133
                                     INVALID_HSM_STATE_ID,
134
                                     INVALID_HSM_STATE_ID,
135
                                     INVALID_HSM_EVENT_ID,
136
                                     false,
137
                                     VariantVector_t());
1,190✔
138
                        handleStartup();
1,190✔
139
                        result = true;
140
                    } else {
141
                        HSM_TRACE_ERROR("failed to register event handlers");
×
142
                        dispatcherPtr->unregisterEventHandler(mEventsHandlerId);
×
143
                        dispatcherPtr->unregisterEnqueuedEventHandler(mEnqueuedEventsHandlerId);
×
144
                        dispatcherPtr->unregisterTimerHandler(mTimerHandlerId);
×
145
                    }
146
                }
147
            } else {
1,190✔
148
                HSM_TRACE_ERROR("failed to start dispatcher");
149
            }
150
        } else {
151
            HSM_TRACE_ERROR("dispatcher is NULL");
1,190✔
152
        }
153
    } else {
1,190✔
154
        HSM_TRACE_ERROR("already initialized");
1,190✔
155
    }
156

157
    return result;
1,190✔
158
}
159

160
std::weak_ptr<IHsmEventDispatcher> HierarchicalStateMachine::Impl::dispatcher() const {
×
161
    return mDispatcher;
×
162
}
163

164
bool HierarchicalStateMachine::Impl::isInitialized() const {
×
165
    return (false == mDispatcher.expired());
×
166
}
167

168
void HierarchicalStateMachine::Impl::release() {
2,370✔
169
    mStopDispatching = true;
2,370✔
170
    HSM_TRACE_CALL_DEBUG();
2,370✔
171

172
    disableHsmDebugging();
2,370✔
173

174
    auto dispatcherPtr = mDispatcher.lock();
2,370✔
175

176
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
177
    if (dispatcherPtr) {
2,370✔
178
        dispatcherPtr->unregisterEventHandler(mEventsHandlerId);
1,180✔
179
        dispatcherPtr->unregisterEnqueuedEventHandler(mEnqueuedEventsHandlerId);
1,180✔
180
        dispatcherPtr->unregisterTimerHandler(mTimerHandlerId);
1,180✔
181
        mDispatcher.reset();
1,180✔
182
        mEventsHandlerId = INVALID_HSM_DISPATCHER_HANDLER_ID;
1,180✔
183

184
        // wait for current dispatching to finish if it's ongoing
185
        mIsDispatching.wait(true);
1,180✔
186
    }
187
}
2,370✔
188

189
void HierarchicalStateMachine::Impl::registerFailedTransitionCallback(HsmTransitionFailedCallback_t onFailedTransition) {
210✔
190
    mFailedTransitionCallback = std::move(onFailedTransition);
210✔
191
}
210✔
192

193
void HierarchicalStateMachine::Impl::registerState(const StateID_t state,
4,000✔
194
                                                   HsmStateChangedCallback_t onStateChanged,
195
                                                   HsmStateEnterCallback_t onEntering,
196
                                                   HsmStateExitCallback_t onExiting) {
197
#ifdef HSM_ENABLE_SAFE_STRUCTURE
198
    if ((false == isSubstate(state)) && (false == isTopState(state))) {
4,000✔
199
        mTopLevelStates.emplace_back(state);
×
200
    }
201
#endif  // HSM_ENABLE_SAFE_STRUCTURE
202

203
    mRegisteredStates[state] =
4,000✔
204
        std::move(StateCallbacks(std::move(onStateChanged), std::move(onEntering), std::move(onExiting)));
8,000✔
205
    HSM_TRACE_CALL_DEBUG_ARGS("mRegisteredStates.size=%ld", mRegisteredStates.size());
4,000✔
206
}
4,000✔
207

208
void HierarchicalStateMachine::Impl::registerFinalState(const StateID_t state,
270✔
209
                                                        const EventID_t event,
210
                                                        HsmStateChangedCallback_t onStateChanged,
211
                                                        HsmStateEnterCallback_t onEntering,
212
                                                        HsmStateExitCallback_t onExiting) {
213
    mFinalStates[state] = event;
270✔
214
    registerState(state, std::move(onStateChanged), std::move(onEntering), std::move(onExiting));
620✔
215
}
270✔
216

217
void HierarchicalStateMachine::Impl::registerHistory(const StateID_t parent,
100✔
218
                                                     const StateID_t historyState,
219
                                                     const HistoryType type,
220
                                                     const StateID_t defaultTarget,
221
                                                     HsmTransitionCallback_t transitionCallback) {
222
    (void)mHistoryStates.emplace(parent, historyState);
100✔
223
    mHistoryData[historyState] = std::move(HistoryInfo(type, defaultTarget, std::move(transitionCallback)));
110✔
224
}
100✔
225

226
bool HierarchicalStateMachine::Impl::registerSubstate(const StateID_t parent, const StateID_t substate) {
740✔
227
    return registerSubstate(parent, substate, false);
740✔
228
}
229

230
bool HierarchicalStateMachine::Impl::registerSubstateEntryPoint(const StateID_t parent,
970✔
231
                                                                const StateID_t substate,
232
                                                                const EventID_t onEvent,
233
                                                                HsmTransitionConditionCallback_t conditionCallback,
234
                                                                const bool expectedConditionValue) {
235
    return registerSubstate(parent, substate, true, onEvent, std::move(conditionCallback), expectedConditionValue);
1,010✔
236
}
237

238
void HierarchicalStateMachine::Impl::registerTimer(const TimerID_t timerID, const EventID_t event) {
150✔
239
    mTimers[timerID] = event;
150✔
240
}
150✔
241

242
bool HierarchicalStateMachine::Impl::registerSubstate(const StateID_t parent,
1,710✔
243
                                                      const StateID_t substate,
244
                                                      const bool isEntryPoint,
245
                                                      const EventID_t eventCondition,
246
                                                      HsmTransitionConditionCallback_t conditionCallback,
247
                                                      const bool expectedConditionValue) {
248
    bool registrationAllowed = false;
1,710✔
249

250
#ifdef HSM_ENABLE_SAFE_STRUCTURE
251
    // do a simple sanity check
252
    if (parent != substate) {
1,710✔
253
        StateID_t curState = parent;
1,710✔
254
        StateID_t prevState = INVALID_HSM_STATE_ID;
1,710✔
255

256
        if (false == hasParentState(substate, prevState)) {
1,710✔
257
            registrationAllowed = true;
2,140✔
258

259
            while (true == hasParentState(curState, prevState)) {
2,140✔
260
                if (substate == prevState) {
470✔
261
                    HSM_TRACE_CALL_DEBUG_ARGS(
262
                        "requested operation will result in substates recursion (parent=<%s>, substate=<%s>)",
263
                        getStateName(parent).c_str(),
264
                        getStateName(substate).c_str());
265
                    registrationAllowed = false;
266
                    break;
267
                }
268

269
                curState = prevState;
270
            }
271
        } else {
272
            HSM_TRACE_CALL_DEBUG_ARGS("substate <%s> already has a parent <%s>",
273
                                      getStateName(substate).c_str(),
274
                                      getStateName(prevState).c_str());
1,710✔
275
        }
276
    }
277
#else
278
    registrationAllowed = (parent != substate);
279
#endif  // HSM_ENABLE_SAFE_STRUCTURE
280

281
    if (registrationAllowed) {
1,710✔
282
        // NOTE: false-positive. isEntryPoint is of type bool
283
        // cppcheck-suppress misra-c2012-14.4
284
        if (isEntryPoint) {
1,670✔
285
            StateEntryPoint entryInfo;
930✔
286

287
            entryInfo.state = substate;
930✔
288
            entryInfo.onEvent = eventCondition;
930✔
289
            entryInfo.checkCondition = std::move(conditionCallback);
930✔
290
            entryInfo.expectedConditionValue = expectedConditionValue;
930✔
291

292
            (void)mSubstateEntryPoints.emplace(parent, entryInfo);
930✔
293
        }
930✔
294

295
        (void)mSubstates.emplace(parent, substate);
1,670✔
296

297
#ifdef HSM_ENABLE_SAFE_STRUCTURE
298
        if (true == isTopState(substate)) {
1,670✔
299
            mTopLevelStates.remove(substate);
1,670✔
300
        }
301
#endif  // HSM_ENABLE_SAFE_STRUCTURE
302
    }
303

304
    return registrationAllowed;
1,710✔
305
}
306

307
bool HierarchicalStateMachine::Impl::registerStateAction(const StateID_t state,
220✔
308
                                                         const StateActionTrigger actionTrigger,
309
                                                         const StateAction action,
310
                                                         const VariantVector_t& args) {
311
    HSM_TRACE_CALL_DEBUG_ARGS("state=<%s>, actionTrigger=%d, action=%d",
312
                              getStateName(state).c_str(),
313
                              SC2INT(actionTrigger),
314
                              SC2INT(action));
220✔
315
    bool result = false;
220✔
316
    bool argsValid = false;
220✔
317
    StateActionInfo newAction;
220✔
318

319
    newAction.actionArgs = args;
220✔
320

321
    // validate arguments
322
    switch (action) {
220✔
323
        case StateAction::START_TIMER:
100✔
324
            argsValid = (newAction.actionArgs.size() == 3U) && newAction.actionArgs[0].isNumeric() &&
200✔
325
                        newAction.actionArgs[1].isNumeric() && newAction.actionArgs[2].isBool();
300✔
326
            break;
327
        case StateAction::RESTART_TIMER:
20✔
328
        case StateAction::STOP_TIMER:
20✔
329
            argsValid = (newAction.actionArgs.size() == 1U) && newAction.actionArgs[0].isNumeric();
20✔
330
            break;
331
        case StateAction::TRANSITION:
100✔
332
            argsValid = (false == newAction.actionArgs.empty()) && newAction.actionArgs[0].isNumeric();
100✔
333
            break;
334
        default:
335
            // do nothing
336
            break;
337
    }
338

339
    if (true == argsValid) {
340
        newAction.action = action;
220✔
341
        (void)mRegisteredActions.emplace(std::make_pair(state, actionTrigger), newAction);
220✔
342
        result = true;
220✔
343
    } else {
344
        HSM_TRACE_ERROR("invalid arguments");
220✔
345
    }
346

347
    return result;
220✔
348
}
220✔
349

350
void HierarchicalStateMachine::Impl::registerTransition(const StateID_t fromState,
2,980✔
351
                                                        const StateID_t toState,
352
                                                        const EventID_t onEvent,
353
                                                        HsmTransitionCallback_t transitionCallback,
354
                                                        HsmTransitionConditionCallback_t conditionCallback,
355
                                                        const bool expectedConditionValue) {
356
    (void)mTransitionsByEvent.emplace(std::make_pair(fromState, onEvent),
2,980✔
357
                                      TransitionInfo(fromState,
5,960✔
358
                                                     toState,
359
                                                     TransitionType::EXTERNAL_TRANSITION,
360
                                                     std::move(transitionCallback),
2,980✔
361
                                                     std::move(conditionCallback),
2,980✔
362
                                                     expectedConditionValue));
363
}
2,980✔
364

365
void HierarchicalStateMachine::Impl::registerSelfTransition(const StateID_t state,
90✔
366
                                                            const EventID_t onEvent,
367
                                                            const TransitionType type,
368
                                                            HsmTransitionCallback_t transitionCallback,
369
                                                            HsmTransitionConditionCallback_t conditionCallback,
370
                                                            const bool expectedConditionValue) {
371
    (void)mTransitionsByEvent.emplace(std::make_pair(state, onEvent),
90✔
372
                                      TransitionInfo(state,
180✔
373
                                                     state,
374
                                                     type,
375
                                                     std::move(transitionCallback),
90✔
376
                                                     std::move(conditionCallback),
90✔
377
                                                     expectedConditionValue));
378
}
90✔
379

380
StateID_t HierarchicalStateMachine::Impl::getLastActiveState() const {
40✔
381
    StateID_t currentState = INVALID_HSM_STATE_ID;
40✔
382

383
    if (false == mActiveStates.empty()) {
40✔
384
        currentState = mActiveStates.back();
40✔
385
    }
386

387
    return currentState;
40✔
388
}
389

390
const std::list<StateID_t>& HierarchicalStateMachine::Impl::getActiveStates() const {
2,310✔
391
    return mActiveStates;
2,310✔
392
}
393

394
bool HierarchicalStateMachine::Impl::isStateActive(const StateID_t state) const {
12,086✔
395
    return (std::find(mActiveStates.begin(), mActiveStates.end(), state) != mActiveStates.end());
12,086✔
396
}
397

398
void HierarchicalStateMachine::Impl::transitionWithArgsArray(const EventID_t event, VariantVector_t&& args) {
100✔
399
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>, args.size=%lu", getEventName(event).c_str(), args.size());
100✔
400

401
    (void)transitionExWithArgsArray(event, false, false, 0, std::move(args));
100✔
402
}
100✔
403

404
bool HierarchicalStateMachine::Impl::transitionExWithArgsArray(const EventID_t event,
2,490✔
405
                                                               const bool clearQueue,
406
                                                               const bool sync,
407
                                                               const int timeoutMs,
408
                                                               VariantVector_t&& args) {
409
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>, clearQueue=%s, sync=%s, args.size=%lu",
410
                              getEventName(event).c_str(),
411
                              BOOL2STR(clearQueue),
412
                              BOOL2STR(sync),
413
                              args.size());
2,490✔
414

415
    bool status = false;
2,490✔
416
    auto dispatcherPtr = mDispatcher.lock();
2,490✔
417

418
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
419
    if (dispatcherPtr) {
2,490✔
420
        PendingEventInfo eventInfo;
2,480✔
421

422
        eventInfo.id = event;
2,480✔
423
        eventInfo.args = std::make_shared<VariantVector_t>(std::move(args));
2,480✔
424

425
        if (true == sync) {
2,480✔
426
            eventInfo.initLock();
1,170✔
427
        }
428

429
        {
2,480✔
430
            HSM_SYNC_EVENTS_QUEUE();
2,480✔
431

432
            if (true == clearQueue) {
2,480✔
433
                clearPendingEvents();
10✔
434
            }
435

436
            mPendingEvents.emplace_back(eventInfo);
2,480✔
437
        }
2,480✔
438

439
        HSM_TRACE_DEBUG("transitionEx: emit");
2,480✔
440
        dispatcherPtr->emitEvent(mEventsHandlerId);
2,480✔
441

442
        if (true == sync) {
2,480✔
443
            HSM_TRACE_DEBUG("transitionEx: wait...");
1,170✔
444
            eventInfo.wait(timeoutMs);
1,170✔
445
            status = (HsmEventStatus::DONE_OK == *eventInfo.transitionStatus);
1,170✔
446
        } else {
447
            // always return true for async transitions
448
            status = true;
449
        }
450
    } else {
2,480✔
451
        HSM_TRACE_ERROR("HSM is not initialized");
2,490✔
452
    }
453

454
    HSM_TRACE_CALL_RESULT("%d", SC2INT(status));
2,490✔
455
    return status;
2,490✔
456
}
2,490✔
457

458
bool HierarchicalStateMachine::Impl::transitionInterruptSafe(const EventID_t event) {
10✔
459
    bool res = false;
10✔
460
    // TODO: this part needs testing with real interrupts. Not sure if it's safe to use weak_ptr.lock()
461
    auto dispatcherPtr = mDispatcher.lock();
10✔
462

463
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
464
    if (dispatcherPtr) {
10✔
465
        res = dispatcherPtr->enqueueEvent(mEnqueuedEventsHandlerId, event);
10✔
466
    }
467

468
    return res;
10✔
469
}
10✔
470

471
bool HierarchicalStateMachine::Impl::isTransitionPossible(const EventID_t event, const VariantVector_t& args) {
60✔
472
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>", getEventName(event).c_str());
60✔
473
    bool possible = false;
60✔
474

475
    for (const StateID_t& state : mActiveStates) {
100✔
476
        possible = checkTransitionPossibility(state, event, args);
60✔
477

478
        if (true == possible) {
60✔
479
            break;
480
        }
481
    }
482

483
    HSM_TRACE_CALL_RESULT("%d", BOOL2INT(possible));
60✔
484
    return possible;
60✔
485
}
486

487
void HierarchicalStateMachine::Impl::startTimer(const TimerID_t timerID,
50✔
488
                                                const unsigned int intervalMs,
489
                                                const bool isSingleShot) {
490
    auto dispatcherPtr = mDispatcher.lock();
50✔
491

492
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
493
    if (dispatcherPtr) {
50✔
494
        dispatcherPtr->startTimer(mTimerHandlerId, timerID, intervalMs, isSingleShot);
50✔
495
    }
496
}
50✔
497

498
void HierarchicalStateMachine::Impl::restartTimer(const TimerID_t timerID) {
10✔
499
    auto dispatcherPtr = mDispatcher.lock();
10✔
500

501
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
502
    if (dispatcherPtr) {
10✔
503
        dispatcherPtr->restartTimer(timerID);
10✔
504
    }
505
}
10✔
506

507
void HierarchicalStateMachine::Impl::stopTimer(const TimerID_t timerID) {
20✔
508
    auto dispatcherPtr = mDispatcher.lock();
20✔
509

510
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
511
    if (dispatcherPtr) {
20✔
512
        dispatcherPtr->stopTimer(timerID);
20✔
513
    }
514
}
20✔
515

516
bool HierarchicalStateMachine::Impl::isTimerRunning(const TimerID_t timerID) {
60✔
517
    bool running = false;
60✔
518
    auto dispatcherPtr = mDispatcher.lock();
60✔
519

520
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
521
    if (dispatcherPtr) {
60✔
522
        running = dispatcherPtr->isTimerRunning(timerID);
60✔
523
    }
524

525
    return running;
60✔
526
}
60✔
527

528
// ============================================================================
529
// PRIVATE
530
// ============================================================================
531
void HierarchicalStateMachine::Impl::createEventHandler(const std::shared_ptr<IHsmEventDispatcher>& dispatcherPtr,
1,190✔
532
                                                        const std::weak_ptr<HierarchicalStateMachine::Impl>& ptrInstance) {
533
    // cppcheck-suppress misra-c2012-13.1 ; false-positive. this is a functor, not initializer list
534
    mEventsHandlerId = dispatcherPtr->registerEventHandler([ptrInstance]() {
17,893✔
535
        bool handlerIsValid = false;
3,205✔
536
        auto pThis = ptrInstance.lock();
3,205✔
537

538
        // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
539
        if (pThis && (false == pThis->mStopDispatching)) {
3,205✔
540
            pThis->dispatchEvents();
3,065✔
541
            handlerIsValid = true;
542
        }
543

544
        // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
545
        // cppcheck-suppress misra-c2012-15.5
546
        return handlerIsValid;
3,205✔
547
    });
3,205✔
548
}
1,190✔
549

550
void HierarchicalStateMachine::Impl::createTimerHandler(const std::shared_ptr<IHsmEventDispatcher>& dispatcherPtr,
1,190✔
551
                                                        const std::weak_ptr<HierarchicalStateMachine::Impl>& ptrInstance) {
552
    // cppcheck-suppress misra-c2012-13.1 ; false-positive. this is a functor, not initializer list
553
    mTimerHandlerId = dispatcherPtr->registerTimerHandler([ptrInstance](const TimerID_t timerId) {
9,940✔
554
        bool handlerIsValid = false;
140✔
555
        auto pThis = ptrInstance.lock();
140✔
556

557
        // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
558
        if (pThis && (false == pThis->mStopDispatching)) {
140✔
559
            pThis->dispatchTimerEvent(timerId);
140✔
560
            handlerIsValid = true;
561
        }
562

563
        // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
564
        // cppcheck-suppress misra-c2012-15.5
565
        return handlerIsValid;
140✔
566
    });
140✔
567
}
1,190✔
568

569
void HierarchicalStateMachine::Impl::createEnqueuedEventHandler(
1,190✔
570
    const std::shared_ptr<IHsmEventDispatcher>& dispatcherPtr,
571
    const std::weak_ptr<HierarchicalStateMachine::Impl>& ptrInstance) {
572
    // cppcheck-suppress misra-c2012-13.1 ; false-positive. this is a functor, not initializer list
573
    mEnqueuedEventsHandlerId = dispatcherPtr->registerEnqueuedEventHandler([ptrInstance](const EventID_t event) {
9,550✔
574
        bool handlerIsValid = false;
10✔
575
        auto pThis = ptrInstance.lock();
10✔
576

577
        // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
578
        if (pThis && (false == pThis->mStopDispatching)) {
10✔
579
            pThis->transitionSimple(event);
10✔
580
            handlerIsValid = true;
581
        }
582

583
        // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
584
        // cppcheck-suppress misra-c2012-15.5
585
        return handlerIsValid;
10✔
586
    });
10✔
587
}
1,190✔
588

589
void HierarchicalStateMachine::Impl::handleStartup() {
1,190✔
590
    HSM_TRACE_CALL_DEBUG_ARGS("mActiveStates.size=%ld", mActiveStates.size());
1,190✔
591
    auto dispatcherPtr = mDispatcher.lock();
1,190✔
592

593
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
594
    if (dispatcherPtr) {
1,190✔
595
        HSM_TRACE_DEBUG("state=<%s>", getStateName(mInitialState).c_str());
1,190✔
596
        std::list<StateID_t> entryPoints;
1,190✔
597

598
        (void)onStateEntering(mInitialState, VariantVector_t());
1,190✔
599
        mActiveStates.emplace_back(mInitialState);
1,190✔
600
        onStateChanged(mInitialState, VariantVector_t());
1,190✔
601

602
        if (true == getEntryPoints(mInitialState, INVALID_HSM_EVENT_ID, VariantVector_t(), entryPoints)) {
1,190✔
603
            PendingEventInfo entryPointTransitionEvent;
240✔
604

605
            entryPointTransitionEvent.transitionType = TransitionBehavior::ENTRYPOINT;
240✔
606
            entryPointTransitionEvent.id = INVALID_HSM_EVENT_ID;
240✔
607

608
            {
240✔
609
                HSM_SYNC_EVENTS_QUEUE();
240✔
610
                mPendingEvents.emplace_front(std::move(entryPointTransitionEvent));
240✔
611
            }
240✔
612
        }
240✔
613

614
        if (false == mPendingEvents.empty()) {
1,190✔
615
            dispatcherPtr->emitEvent(mEventsHandlerId);
240✔
616
        }
617
    }
1,190✔
618
}
1,190✔
619

620
void HierarchicalStateMachine::Impl::transitionSimple(const EventID_t event) {
150✔
621
    (void)transitionExWithArgsArray(event, false, false, 0, VariantVector_t());
150✔
622
}
150✔
623

624
void HierarchicalStateMachine::Impl::dispatchEvents() {
3,065✔
625
    HSM_TRACE_CALL_DEBUG_ARGS("mPendingEvents.size=%ld", mPendingEvents.size());
3,065✔
626
    auto dispatcherPtr = mDispatcher.lock();
3,065✔
627

628
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
629
    if (dispatcherPtr && (false == mIsDispatching.test_and_set())) {
3,065✔
630
        UniqueLock lk = mIsDispatching.lock();
3,065✔
631

632
        if (false == mStopDispatching) {
3,065✔
633
            if (false == mPendingEvents.empty()) {
3,065✔
634
                PendingEventInfo pendingEvent;
3,010✔
635

636
                {
3,010✔
637
                    HSM_SYNC_EVENTS_QUEUE();
3,010✔
638
                    pendingEvent = std::move(mPendingEvents.front());
3,010✔
639
                    mPendingEvents.pop_front();
3,010✔
640
                }
3,010✔
641

642
                HsmEventStatus transitiontStatus = doTransition(pendingEvent);
3,010✔
643

644
                HSM_TRACE_DEBUG("unlock with status %d", SC2INT(transitiontStatus));
3,010✔
645
                pendingEvent.unlock(transitiontStatus);
3,010✔
646
            }
3,010✔
647

648
            if ((false == mStopDispatching) && (false == mPendingEvents.empty())) {
3,065✔
649
                dispatcherPtr->emitEvent(mEventsHandlerId);
1,424✔
650
            }
651
        }
652

653
        mIsDispatching.clear();
3,065✔
654
        mIsDispatching.notify();
3,065✔
655
    }
3,065✔
656
}
3,065✔
657

658
void HierarchicalStateMachine::Impl::dispatchTimerEvent(const TimerID_t id) {
140✔
659
    HSM_TRACE_CALL_DEBUG_ARGS("id=%d", SC2INT(id));
140✔
660
    auto it = mTimers.find(id);
140✔
661

662
    if (mTimers.end() != it) {
140✔
663
        transitionSimple(it->second);
140✔
664
    }
665
}
140✔
666

667
bool HierarchicalStateMachine::Impl::onStateExiting(const StateID_t state) {
2,490✔
668
    HSM_TRACE_CALL_DEBUG_ARGS("state=<%s>", getStateName(state).c_str());
2,490✔
669
    bool res = true;
2,490✔
670
    auto it = mRegisteredStates.find(state);
2,490✔
671

672
    if ((mRegisteredStates.end() != it) && it->second.onExiting) {
2,490✔
673
        res = it->second.onExiting();
330✔
674
        logHsmAction(HsmLogAction::CALLBACK_EXIT,
330✔
675
                     state,
676
                     INVALID_HSM_STATE_ID,
677
                     INVALID_HSM_EVENT_ID,
678
                     (false == res),
679
                     VariantVector_t());
330✔
680
    }
681

682
    // execute state action only if transition was accepted by client
683
    if (true == res) {
330✔
684
        executeStateAction(state, StateActionTrigger::ON_STATE_EXIT);
2,470✔
685
    }
686

687
    return res;
2,490✔
688
}
689

690
bool HierarchicalStateMachine::Impl::onStateEntering(const StateID_t state, const VariantVector_t& args) {
4,370✔
691
    HSM_TRACE_CALL_DEBUG_ARGS("state=<%s>", getStateName(state).c_str());
4,370✔
692
    bool res = true;
4,370✔
693

694
    // since we can have a situation when same state is entered twice (parallel transitions) there
695
    // is no need to call callbacks multiple times
696
    if (false == isStateActive(state)) {
4,370✔
697
        auto it = mRegisteredStates.find(state);
4,320✔
698

699
        if ((mRegisteredStates.end() != it) && it->second.onEntering) {
4,320✔
700
            res = it->second.onEntering(args);
560✔
701
            logHsmAction(HsmLogAction::CALLBACK_ENTER, INVALID_HSM_STATE_ID, state, INVALID_HSM_EVENT_ID, (false == res), args);
560✔
702
        }
703

704
        // execute state action only if transition was accepted by client
705
        if (true == res) {
560✔
706
            executeStateAction(state, StateActionTrigger::ON_STATE_ENTRY);
4,300✔
707
        }
708
    }
709

710
    return res;
4,370✔
711
}
712

713
void HierarchicalStateMachine::Impl::onStateChanged(const StateID_t state, const VariantVector_t& args) {
4,300✔
714
    HSM_TRACE_CALL_DEBUG_ARGS("state=<%s>", getStateName(state).c_str());
4,300✔
715
    auto it = mRegisteredStates.find(state);
4,300✔
716

717
    if ((mRegisteredStates.end() != it) && it->second.onStateChanged) {
4,300✔
718
        it->second.onStateChanged(args);
2,540✔
719
        logHsmAction(HsmLogAction::CALLBACK_STATE, INVALID_HSM_STATE_ID, state, INVALID_HSM_EVENT_ID, false, args);
2,540✔
720
    } else {
721
        HSM_TRACE_WARNING("no callback registered for state <%s>", getStateName(state).c_str());
4,300✔
722
    }
723
}
4,300✔
724

725
void HierarchicalStateMachine::Impl::executeStateAction(const StateID_t state, const StateActionTrigger actionTrigger) {
6,770✔
726
    HSM_TRACE_CALL_DEBUG_ARGS("state=<%s>, actionTrigger=%d", getStateName(state).c_str(), SC2INT(actionTrigger));
6,770✔
727
    auto dispatcherPtr = mDispatcher.lock();
6,770✔
728
    auto itRange = mRegisteredActions.equal_range(std::make_pair(state, actionTrigger));
6,770✔
729

730
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
731
    if (dispatcherPtr && (itRange.first != itRange.second)) {
6,770✔
732
        switch (actionTrigger) {
180✔
733
            case StateActionTrigger::ON_STATE_ENTRY:
130✔
734
                logHsmAction(HsmLogAction::ON_ENTER_ACTIONS, INVALID_HSM_STATE_ID, state);
130✔
735
                break;
130✔
736
            case StateActionTrigger::ON_STATE_EXIT:
50✔
737
                logHsmAction(HsmLogAction::ON_EXIT_ACTIONS, INVALID_HSM_STATE_ID, state);
50✔
738
                break;
50✔
739
            default:
740
                // NOTE: do nothing
741
                break;
742
        }
743

744
        for (auto it = itRange.first; it != itRange.second; ++it) {
410✔
745
            const StateActionInfo& actionInfo = it->second;
230✔
746

747
            if (StateAction::START_TIMER == actionInfo.action) {
230✔
748
                dispatcherPtr->startTimer(mTimerHandlerId,
220✔
749
                                          static_cast<TimerID_t>(actionInfo.actionArgs[0].toInt64()),
110✔
750
                                          actionInfo.actionArgs[1].toInt64(),
110✔
751
                                          actionInfo.actionArgs[2].toBool());
110✔
752
            } else if (StateAction::STOP_TIMER == actionInfo.action) {
120✔
753
                dispatcherPtr->stopTimer(static_cast<TimerID_t>(actionInfo.actionArgs[0].toInt64()));
10✔
754
            } else if (StateAction::RESTART_TIMER == actionInfo.action) {
110✔
755
                dispatcherPtr->restartTimer(static_cast<TimerID_t>(actionInfo.actionArgs[0].toInt64()));
10✔
756
            } else if (StateAction::TRANSITION == actionInfo.action) {
100✔
757
                VariantVector_t transitionArgs;
100✔
758

759
                if (actionInfo.actionArgs.size() > 1U) {
100✔
760
                    transitionArgs.reserve(actionInfo.actionArgs.size() - 1U);
10✔
761

762
                    // copy arguments except for the first one
763
                    for (size_t i = 1; i < actionInfo.actionArgs.size(); ++i) {
30✔
764
                        transitionArgs.emplace_back(actionInfo.actionArgs[i]);
20✔
765
                    }
766
                }
767

768
                transitionWithArgsArray(static_cast<EventID_t>(actionInfo.actionArgs[0].toInt64()), std::move(transitionArgs));
100✔
769
            } else {
100✔
770
                HSM_TRACE_WARNING("unsupported action <%d>", SC2INT(actionInfo.action));
230✔
771
            }
772
        }
773
    }
774
}
6,770✔
775

776
bool HierarchicalStateMachine::Impl::getParentState(const StateID_t child, StateID_t& outParent) {
4,060✔
777
    bool wasFound = false;
4,060✔
778
    auto it = std::find_if(mSubstates.begin(), mSubstates.end(), [child](const std::pair<StateID_t, StateID_t>& item) {
4,060✔
779
        // cppcheck-suppress misra-c2012-15.5 ; false-positive. "return" statement belongs to lambda function
780
        return (child == item.second);
8,130✔
781
    });
782

783
    if (mSubstates.end() != it) {
4,060✔
784
        outParent = it->first;  // cppcheck-suppress misra-c2012-17.8 ; outParent is used to return result
1,690✔
785
        wasFound = true;
1,690✔
786
    }
787

788
    return wasFound;
4,060✔
789
}
790
bool HierarchicalStateMachine::Impl::isSubstateOf(const StateID_t parent, const StateID_t child) {
5,746✔
791
    HSM_TRACE_CALL_DEBUG_ARGS("parent=<%s>, child=<%s>", getStateName(parent).c_str(), getStateName(child).c_str());
5,746✔
792
    StateID_t curState = child;
5,746✔
793
    bool stopSearch = false;
5,746✔
794

795
    while ((false == stopSearch) && (parent != curState)) {
17,908✔
796
        stopSearch = true;
6,416✔
797

798
        for (auto itParent = mSubstates.begin(); itParent != mSubstates.end(); ++itParent) {
16,096✔
799
            if (curState == itParent->second) {
13,336✔
800
                // found next parent
801
                curState = itParent->first;
3,656✔
802
                stopSearch = false;
3,656✔
803

804
                if (parent != curState) {
3,656✔
805
                    // check left siblings
806
                    for (auto itSibling = itParent; (itSibling != mSubstates.begin()) && (itSibling->first == itParent->first); --itSibling) {
2,650✔
807
                        if (itSibling->second == parent) {
1,710✔
808
                            stopSearch = true;
809
                            break;
810
                        }
811
                    }
812

813
                    // check right siblings
814
                    for (auto itSibling = itParent; (itSibling != mSubstates.end()) && (itSibling->first == itParent->first); ++itSibling) {
2,920✔
815
                        if (itSibling->second == parent) {
1,700✔
816
                            stopSearch = true;
817
                            break;
818
                        }
819
                    }
820
                }
821

822
                break;
823
            }
824
        }
825
    }
826

827
    return (parent != child) && (parent == curState);
5,746✔
828
}
829

830
bool HierarchicalStateMachine::Impl::isFinalState(const StateID_t state) const {
510✔
831
    return (mFinalStates.end() != mFinalStates.find(state));
510✔
832
}
833

834
bool HierarchicalStateMachine::Impl::hasActiveChildren(const StateID_t parent, const bool includeFinal) {
240✔
835
    HSM_TRACE_CALL_DEBUG_ARGS("parent=<%s>", getStateName(parent).c_str());
240✔
836
    bool res = false;
240✔
837

838
    for (const StateID_t& activeStateId : mActiveStates) {
700✔
839
        if ((parent != activeStateId) && (true == includeFinal) || (false == isFinalState(activeStateId))) {
510✔
840
            if (isSubstateOf(parent, activeStateId)) {
290✔
841
                HSM_TRACE_DEBUG("parent=<%s> has <%s> active",
842
                                getStateName(parent).c_str(),
843
                                getStateName(activeStateId).c_str());
844
                res = true;
845
                break;
846
            }
847
        }
848
    }
849

850
    HSM_TRACE_CALL_RESULT("res=%d", BOOL2INT(res));
240✔
851
    return res;
240✔
852
}
853

854
bool HierarchicalStateMachine::Impl::getHistoryParent(const StateID_t historyState, StateID_t& outParent) {
100✔
855
    bool wasFound = false;
100✔
856
    auto it =
100✔
857
        std::find_if(mHistoryStates.begin(), mHistoryStates.end(), [historyState](const std::pair<StateID_t, StateID_t>& item) {
100✔
858
            // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
859
            // cppcheck-suppress misra-c2012-15.5
860
            return (historyState == item.second);
100✔
861
        });
862

863
    if (mHistoryStates.end() != it) {
100✔
864
        outParent = it->first;  // cppcheck-suppress misra-c2012-17.8 ; outParent is used to return result
100✔
865
        wasFound = true;
100✔
866
    }
867

868
    return wasFound;
100✔
869
}
870

871
void HierarchicalStateMachine::Impl::updateHistory(const StateID_t topLevelState, const std::list<StateID_t>& exitedStates) {
2,080✔
872
    HSM_TRACE_CALL_DEBUG_ARGS("topLevelState=<%s>, exitedStates.size=%ld",
873
                              getStateName(topLevelState).c_str(),
874
                              exitedStates.size());
2,080✔
875
    std::list<std::list<StateID_t>*> upatedHistory;
2,080✔
876

877
    for (const auto& activeState : exitedStates) {
4,710✔
878
        StateID_t curState = activeState;
2,630✔
879
        StateID_t parentState = INVALID_HSM_STATE_ID;
2,630✔
880

881
        // check if we have parent state
882
        while (true == getParentState(curState, parentState)) {
3,450✔
883
            HSM_TRACE_DEBUG("curState=<%s>, parentState=<%s>",
884
                            getStateName(curState).c_str(),
885
                            getStateName(parentState).c_str());
1,370✔
886
            auto itRange = mHistoryStates.equal_range(parentState);
1,370✔
887

888
            // check if parent state has any history states
889
            if (itRange.first != itRange.second) {
1,370✔
890
                HSM_TRACE_DEBUG("parent=<%s> has history items", getStateName(parentState).c_str());
891

892
                for (auto it = itRange.first; it != itRange.second; ++it) {
680✔
893
                    auto itCurHistory = mHistoryData.find(it->second);
340✔
894

895
                    if (itCurHistory != mHistoryData.end()) {
340✔
896
                        const auto itUpdatedHistory =
340✔
897
                            std::find(upatedHistory.begin(), upatedHistory.end(), &itCurHistory->second.previousActiveStates);
340✔
898

899
                        // check if this is the first time we are updating this history state
900
                        if (itUpdatedHistory == upatedHistory.end()) {
340✔
901
                            // clear previos history items
902
                            itCurHistory->second.previousActiveStates.clear();
200✔
903
                            // we store pointer to be able to identify if this history state was already cleared or not
904
                            upatedHistory.emplace_back(&(itCurHistory->second.previousActiveStates));
400✔
905
                        }
906

907
                        if (HistoryType::SHALLOW == itCurHistory->second.type) {
340✔
908
                            if (std::find(itCurHistory->second.previousActiveStates.begin(),
160✔
909
                                          itCurHistory->second.previousActiveStates.end(),
160✔
910
                                          curState) == itCurHistory->second.previousActiveStates.end()) {
320✔
911
                                HSM_TRACE_DEBUG("SHALLOW -> store state <%s> in history of parent <%s>",
912
                                                getStateName(curState).c_str(),
913
                                                getStateName(it->second).c_str());
120✔
914
                                itCurHistory->second.previousActiveStates.emplace_back(curState);
120✔
915
                            }
916
                        } else if (HistoryType::DEEP == itCurHistory->second.type) {
180✔
917
                            if (std::find(itCurHistory->second.previousActiveStates.begin(),
180✔
918
                                          itCurHistory->second.previousActiveStates.end(),
180✔
919
                                          activeState) == itCurHistory->second.previousActiveStates.end()) {
360✔
920
                                HSM_TRACE_DEBUG("DEEP -> store state <%s> in history of parent <%s>",
921
                                                getStateName(activeState).c_str(),
922
                                                getStateName(it->second).c_str());
180✔
923
                                itCurHistory->second.previousActiveStates.emplace_back(activeState);
520✔
924
                            }
925
                        } else {
926
                            // NOTE: do nothing
927
                        }
928
                    }
929
                }
930
            }
931

932
            if (topLevelState != parentState) {
1,370✔
933
                curState = parentState;
820✔
934
            } else {
935
                break;
936
            }
937
        }
938
    }
939
}
2,080✔
940

941
bool HierarchicalStateMachine::Impl::checkTransitionPossibility(const StateID_t fromState,
60✔
942
                                                                const EventID_t event,
943
                                                                const VariantVector_t& args) {
944
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>", getEventName(event).c_str());
60✔
945

946
    StateID_t currentState = fromState;
60✔
947
    std::list<TransitionInfo> possibleTransitions;
60✔
948
    EventID_t nextEvent = INVALID_HSM_EVENT_ID;
60✔
949
    bool possible = true;
60✔
950

951
    {
60✔
952
        // NOTE: findTransitionTarget can be a bit heavy. possible optimization to reduce lock time is
953
        //       to make a copy of mPendingEvents and work with it
954
        HSM_SYNC_EVENTS_QUEUE();
60✔
955

956
        for (auto it = mPendingEvents.begin(); (it != mPendingEvents.end()) && (true == possible); ++it) {
60✔
957
            nextEvent = it->id;
×
958
            possible = findTransitionTarget(currentState, nextEvent, args, true, possibleTransitions);
×
959

960
            if (true == possible) {
×
961
                if (false == possibleTransitions.empty()) {
×
962
                    currentState = possibleTransitions.front().destinationState;
×
963
                } else {
964
                    possible = false;
965
                    break;
966
                }
967
            }
968
        }
969
    }
60✔
970

971
    if (true == possible) {
60✔
972
        nextEvent = event;
60✔
973
        possible = findTransitionTarget(currentState, nextEvent, args, true, possibleTransitions);
60✔
974
    }
975

976
    HSM_TRACE_CALL_RESULT("%d", BOOL2INT(possible));
60✔
977
    return possible;
60✔
978
}
60✔
979

980
bool HierarchicalStateMachine::Impl::findTransitionTarget(const StateID_t fromState,
2,860✔
981
                                                          const EventID_t event,
982
                                                          const VariantVector_t& transitionArgs,
983
                                                          const bool searchParents,
984
                                                          std::list<TransitionInfo>& outTransitions) {
985
    HSM_TRACE_CALL_DEBUG_ARGS("fromState=<%s>, event=<%s>", getStateName(fromState).c_str(), getEventName(event).c_str());
2,860✔
986
    bool continueSearch = false;
2,860✔
987
    StateID_t curState = fromState;
2,860✔
988

989
    do {
2,860✔
990
        const auto itRange = mTransitionsByEvent.equal_range(std::make_pair(curState, event));
2,860✔
991

992
        continueSearch = false;
2,860✔
993

994
        // if there are no matching transitions try to go one level up
995
        if (itRange.first == itRange.second) {
2,860✔
996
            if (true == searchParents) {
800✔
997
                continueSearch = getParentState(curState, curState);
40✔
998
            }
999

1000
            continue;
800✔
1001
        }
1002

1003
        // check available transitions
1004
        for (auto it = itRange.first; it != itRange.second; ++it) {
4,380✔
1005
            HSM_TRACE_DEBUG("check transition to <%s>...", getStateName(it->second.destinationState).c_str());
2,320✔
1006

1007
            if ((nullptr == it->second.checkCondition) ||
2,320✔
1008
                (it->second.expectedConditionValue == it->second.checkCondition(transitionArgs))) {
140✔
1009
                bool wasFound = false;
2,230✔
1010
                std::list<StateID_t> parentStates = {it->second.destinationState};
2,230✔
1011

1012
                // cppcheck-suppress misra-c2012-15.4
1013
                do {
2,650✔
1014
                    StateID_t currentParent = parentStates.front();
2,650✔
1015

1016
                    parentStates.pop_front();
2,650✔
1017

1018
                    // if state has substates we must check if transition into them is possible (after cond)
1019
                    if (true == hasSubstates(currentParent)) {
2,650✔
1020
                        if (true == hasEntryPoint(currentParent)) {
470✔
1021
                            HSM_TRACE_DEBUG("state <%s> has entrypoints", getStateName(currentParent).c_str());
440✔
1022
                            std::list<StateID_t> entryPoints;
440✔
1023

1024
                            if (true == getEntryPoints(currentParent, event, transitionArgs, entryPoints)) {
440✔
1025
                                parentStates.splice(parentStates.end(), entryPoints);
840✔
1026
                            } else {
1027
                                HSM_TRACE_WARNING("no matching entrypoints found");
1028
                                break;
1029
                            }
1030
                        } else {
20✔
1031
                            HSM_TRACE_WARNING("state <%s> doesn't have an entrypoint defined",
1032
                                              getStateName(currentParent).c_str());
1033
                            break;
1034
                        }
1035
                    } else {
1036
                        outTransitions.emplace_back(it->second);
4,410✔
1037
                        wasFound = true;
1038
                    }
1039
                } while ((false == wasFound) && (parentStates.empty() == false));
420✔
1040
            }
2,230✔
1041
        }
1042
    } while (true == continueSearch);
800✔
1043

1044
    HSM_TRACE_CALL_RESULT("%s", BOOL2STR(outTransitions.empty() == false));
2,860✔
1045
    return (outTransitions.empty() == false);
2,860✔
1046
}
1047

1048
HsmEventStatus HierarchicalStateMachine::Impl::doTransition(const PendingEventInfo& event) {
3,010✔
1049
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>, transitionType=%d", getEventName(event.id).c_str(), SC2INT(event.transitionType));
3,010✔
1050
    HsmEventStatus res = HsmEventStatus::DONE_FAILED;
3,010✔
1051
    auto activeStatesSnapshot = mActiveStates;
3,010✔
1052
    std::list<StateID_t> acceptedStates;  // list of states that accepted transitions
3,010✔
1053

1054
    for (auto it = activeStatesSnapshot.rbegin(); it != activeStatesSnapshot.rend(); ++it) {
7,309✔
1055
        // in case of parallel transitions some states might become inactive after handleSingleTransition()
1056
        // example: [*B, *C] -> D
1057
        if (true == isStateActive(*it)) {
4,566✔
1058
            // we don't need to process transitions for active states if their child already processed it
1059
            bool childStateProcessed = false;
4,566✔
1060

1061
            for (const auto& state : acceptedStates) {
4,636✔
1062
                if (true == isSubstateOf(*it, state)) {
866✔
1063
                    childStateProcessed = true;
1064
                    break;
1065
                }
1066
            }
1067

1068
            if (false == childStateProcessed) {
4,566✔
1069
                const HsmEventStatus singleTransitionResult = handleSingleTransition(*it, event);
3,770✔
1070

1071
                switch (singleTransitionResult) {
3,770✔
1072
                    case HsmEventStatus::PENDING:
700✔
1073
                        res = singleTransitionResult;
700✔
1074
                        acceptedStates.emplace_back(*it);
700✔
1075
                        break;
1076
                    case HsmEventStatus::DONE_OK:
2,170✔
1077
                        logHsmAction(HsmLogAction::IDLE,
2,170✔
1078
                                     INVALID_HSM_STATE_ID,
1079
                                     INVALID_HSM_STATE_ID,
1080
                                     INVALID_HSM_EVENT_ID,
1081
                                     false,
1082
                                     VariantVector_t());
2,170✔
1083
                        if (HsmEventStatus::PENDING != res) {
2,170✔
1084
                            res = singleTransitionResult;
2,170✔
1085
                        }
1086
                        acceptedStates.emplace_back(*it);
2,170✔
1087
                        break;
1088
                    case HsmEventStatus::CANCELED:
1089
                    case HsmEventStatus::DONE_FAILED:
1090
                    default:
1091
                        // do nothing
1092
                        break;
1093
                }
1094
            }
1095
        }
1096

1097
        // stop processing other events if HSM was released
1098
        if (true == mDispatcher.expired()) {
4,566✔
1099
            res = HsmEventStatus::DONE_FAILED;
1100
            break;
1101
        }
1102
    }
1103

1104
    if (mFailedTransitionCallback && ((HsmEventStatus::DONE_FAILED == res) || (HsmEventStatus::CANCELED == res))) {
3,010✔
1105
        mFailedTransitionCallback(activeStatesSnapshot, event.id, event.getArgs());
240✔
1106
    }
1107

1108
    HSM_TRACE_CALL_RESULT("%d", SC2INT(res));
3,010✔
1109
    return res;
3,010✔
1110
}
6,020✔
1111

1112
HsmEventStatus HierarchicalStateMachine::Impl::processExternalTransition(const PendingEventInfo& event,
3,150✔
1113
                                                                         const StateID_t fromState,
1114
                                                                         const TransitionInfo& curTransition,
1115
                                                                         const std::list<StateID_t>& exitedStates) {
1116
    HSM_TRACE_CALL_DEBUG();
3,150✔
1117
    HsmEventStatus res = HsmEventStatus::DONE_FAILED;
3,150✔
1118

1119
    // NOTE: Decide if we need functionality to cancel ongoing transition
1120
    logHsmAction(((TransitionBehavior::ENTRYPOINT != event.transitionType) ? HsmLogAction::TRANSITION
6,300✔
1121
                                                                           : HsmLogAction::TRANSITION_ENTRYPOINT),
1122
                 curTransition.fromState,
3,150✔
1123
                 curTransition.destinationState,
3,150✔
1124
                 event.id,
3,150✔
1125
                 false,
1126
                 event.getArgs());
1127

1128
    // cppcheck-suppress misra-c2012-14.4 : false-positive. std::shared_ptr has a bool() operator
1129
    if (curTransition.onTransition) {
3,150✔
1130
        curTransition.onTransition(event.getArgs());
260✔
1131
    }
1132

1133
    if (true == onStateEntering(curTransition.destinationState, event.getArgs())) {
3,150✔
1134
        if (true == replaceActiveState(fromState, curTransition.destinationState)) {
3,130✔
1135
            onStateChanged(curTransition.destinationState, event.getArgs());
3,080✔
1136
        }
1137

1138
        // check if current state is a final state
1139
        if (true == processFinalStateTransition(event, curTransition.destinationState)) {
3,130✔
1140
            res = HsmEventStatus::DONE_OK;
1141
        }
1142
        // check if we transitioned into history state
1143
        else if (true == processHistoryTransition(event, curTransition.destinationState)) {
2,880✔
1144
            res = HsmEventStatus::PENDING;
1145
        } else {
1146
            // check if new state has substates and initiate entry transition
1147
            if (false == event.ignoreEntryPoints) {
2,780✔
1148
                std::list<StateID_t> entryPoints;
2,630✔
1149

1150
                if (true == getEntryPoints(curTransition.destinationState, event.id, event.getArgs(), entryPoints)) {
2,630✔
1151
                    HSM_TRACE_DEBUG("state <%s> has substates with %d entry points (first: <%s>)",
1152
                                    getStateName(curTransition.destinationState).c_str(),
1153
                                    SC2INT(entryPoints.size()),
1154
                                    getStateName(entryPoints.front()).c_str());
450✔
1155
                    PendingEventInfo entryPointTransitionEvent = event;
450✔
1156

1157
                    entryPointTransitionEvent.transitionType = TransitionBehavior::ENTRYPOINT;
450✔
1158

1159
                    {
450✔
1160
                        HSM_SYNC_EVENTS_QUEUE();
450✔
1161
                        mPendingEvents.push_front(entryPointTransitionEvent);
450✔
1162
                    }
450✔
1163
                    res = HsmEventStatus::PENDING;
450✔
1164
                } else {
450✔
1165
                    res = HsmEventStatus::DONE_OK;
1166
                }
1167
            } else {
2,630✔
1168
                HSM_TRACE_DEBUG("entry points were forcefully ignored (probably due to history transition)");
1169
                res = HsmEventStatus::PENDING;
1170
            }
1171
        }
1172
    } else {
1173
        for (const StateID_t& curState : exitedStates) {
40✔
1174
            // to prevent infinite loops we don't allow state to cancel transition
1175
            (void)onStateEntering(curState, VariantVector_t());
20✔
1176
            (void)addActiveState(curState);
20✔
1177
            onStateChanged(curState, VariantVector_t());
20✔
1178
        }
1179
    }
1180

1181
    return res;
3,150✔
1182
}
1183

1184
bool HierarchicalStateMachine::Impl::determineTargetState(const PendingEventInfo& event,
3,770✔
1185
                                                          const StateID_t fromState,
1186
                                                          std::list<TransitionInfo>& outMatchingTransitions) {
1187
    HSM_TRACE_CALL_DEBUG();
3,770✔
1188
    bool isCorrectTransition = false;
3,770✔
1189

1190
    if (TransitionBehavior::REGULAR == event.transitionType) {
3,770✔
1191
        isCorrectTransition = findTransitionTarget(fromState, event.id, event.getArgs(), false, outMatchingTransitions);
2,800✔
1192

1193
        if (false == isCorrectTransition) {
2,800✔
1194
            HSM_TRACE_WARNING("no suitable transition from state <%s> with event <%s>",
1195
                              getStateName(fromState).c_str(),
1196
                              getEventName(event.id).c_str());
1197
        }
1198
    } else if (TransitionBehavior::ENTRYPOINT == event.transitionType) {
970✔
1199
        isCorrectTransition = true;
720✔
1200

1201
        // if fromState doesnt have active children
1202
        for (auto it = mActiveStates.rbegin(); it != mActiveStates.rend(); ++it) {
1,760✔
1203
            if (fromState != *it) {
1,040✔
1204
                StateID_t activeParent = INVALID_HSM_STATE_ID;
320✔
1205

1206
                if (true == getParentState(*it, activeParent)) {
320✔
1207
                    if (activeParent == fromState) {
80✔
1208
                        // no need to handle entry transition for already active state
1209
                        isCorrectTransition = false;
×
1210
                        break;
×
1211
                    }
1212
                }
1213
            }
1214
        }
1215

1216
        if (true == isCorrectTransition) {
×
1217
            std::list<StateID_t> entryStates;
720✔
1218

1219
            isCorrectTransition = getEntryPoints(fromState, event.id, event.getArgs(), entryStates);
720✔
1220

1221
            if (true == isCorrectTransition) {
720✔
1222
                for (const auto& curEntryState : entryStates) {
1,470✔
1223
                    (void)outMatchingTransitions.emplace_back(fromState,
1,560✔
1224
                                                              curEntryState,
1225
                                                              TransitionType::EXTERNAL_TRANSITION,
1,560✔
1226
                                                              nullptr,
1,560✔
1227
                                                              nullptr);
1,560✔
1228
                }
1229
            } else {
1230
                HSM_TRACE_WARNING("state <%s> doesn't have a suitable entry point (event <%s>)",
1231
                                  getStateName(fromState).c_str(),
1232
                                  getEventName(event.id).c_str());
720✔
1233
            }
1234
        }
720✔
1235
    } else if (TransitionBehavior::FORCED == event.transitionType) {
250✔
1236
        HSM_TRACE_DEBUG("forced history transitions: %d", SC2INT(event.forcedTransitionsInfo->size()));
250✔
1237
        // cppcheck-suppress misra-c2012-17.8 ; outMatchingTransitions is used to return result
1238
        outMatchingTransitions = *event.forcedTransitionsInfo;
250✔
1239
        isCorrectTransition = true;
1240
    } else {
1241
        // NOTE: do nothing
1242
    }
1243

1244
    return isCorrectTransition;
3,770✔
1245
}
1246

1247
bool HierarchicalStateMachine::Impl::executeSelfTransitions(const PendingEventInfo& event,
2,910✔
1248
                                                            const std::list<TransitionInfo>& matchingTransitions) {
1249
    bool hadSelfTransitions = false;
2,910✔
1250

1251
    // execute self transitions first
1252
    for (const auto& curTransition : matchingTransitions) {
6,140✔
1253
        if ((curTransition.fromState == curTransition.destinationState) &&
3,230✔
1254
            (TransitionType::INTERNAL_TRANSITION == curTransition.transitionType)) {
90✔
1255
            // TODO: separate type for self transition?
1256
            logHsmAction(HsmLogAction::TRANSITION,
60✔
1257
                         curTransition.fromState,
60✔
1258
                         curTransition.destinationState,
60✔
1259
                         event.id,
60✔
1260
                         false,
1261
                         event.getArgs());
1262

1263
            // NOTE: false-positive. std::function has a bool() operator
1264
            // cppcheck-suppress misra-c2012-14.4
1265
            if (curTransition.onTransition) {
60✔
1266
                curTransition.onTransition(event.getArgs());
3,290✔
1267
            }
1268

1269
            hadSelfTransitions = true;
1270
        }
1271
    }
1272

1273
    return hadSelfTransitions;
2,910✔
1274
}
1275

1276
bool HierarchicalStateMachine::Impl::executeExitTransition(const PendingEventInfo& event,
2,910✔
1277
                                                           const std::list<TransitionInfo>& matchingTransitions,
1278
                                                           std::list<StateID_t>& outExitedStates) {
1279
    HSM_TRACE_CALL_DEBUG();
2,910✔
1280
    bool isExitAllowed = true;
2,910✔
1281

1282
    for (const auto& curTransition : matchingTransitions) {
6,140✔
1283
        if (  // process everything except internal self-transitions
3,230✔
1284
            ((curTransition.fromState != curTransition.destinationState) ||
3,230✔
1285
             (TransitionType::EXTERNAL_TRANSITION == curTransition.transitionType)) &&
90✔
1286
            // exit active states only during regular transitions
1287
            (TransitionBehavior::REGULAR == event.transitionType)) {
3,170✔
1288
            // it's an outer transition from parent state. we need to find and exit all active substates
1289
            for (auto itActiveState = mActiveStates.rbegin(); itActiveState != mActiveStates.rend(); ++itActiveState) {
5,370✔
1290
                HSM_TRACE_DEBUG("OUTER EXIT: FROM=%s, ACTIVE=%s",
1291
                                getStateName(curTransition.fromState).c_str(),
1292
                                getStateName(*itActiveState).c_str());
3,290✔
1293
                if ((curTransition.fromState == *itActiveState) ||
4,650✔
1294
                    (true == isSubstateOf(curTransition.fromState, *itActiveState))) {
1,360✔
1295
                    isExitAllowed = onStateExiting(*itActiveState);
2,490✔
1296

1297
                    if (true == isExitAllowed) {
2,490✔
1298
                        outExitedStates.emplace_back(*itActiveState);
5,740✔
1299
                    } else {
1300
                        break;
1301
                    }
1302
                }
1303
            }
1304

1305
            // if no one blocked ongoing transition - remove child states from active list
1306
            if (true == isExitAllowed) {
2,100✔
1307
                // store history for states between "fromState" ----> "it->fromState"
1308
                updateHistory(curTransition.fromState, outExitedStates);
2,080✔
1309

1310
                for (const auto& curState : outExitedStates) {
4,710✔
1311
                    mActiveStates.remove(curState);
2,630✔
1312
                }
1313
            }
1314
            // if one of the states blocked ongoing transition we need to rollback
1315
            else {
1316
                for (const auto& curState : outExitedStates) {
30✔
1317
                    mActiveStates.remove(curState);
10✔
1318
                    // to prevent infinite loops we don't allow state to cancel transition
1319
                    (void)onStateEntering(curState, VariantVector_t());
10✔
1320
                    mActiveStates.emplace_back(curState);
10✔
1321
                    onStateChanged(curState, VariantVector_t());
10✔
1322
                }
1323
            }
1324
        }
1325
    }
1326

1327
    return isExitAllowed;
2,910✔
1328
}
1329

1330
bool HierarchicalStateMachine::Impl::processHistoryTransition(const PendingEventInfo& event, const StateID_t destinationState) {
2,880✔
1331
    HSM_TRACE_CALL_DEBUG();
2,880✔
1332
    auto itHistoryData = mHistoryData.find(destinationState);
2,880✔
1333

1334
    // check if we transitioned into a history state
1335
    if (itHistoryData != mHistoryData.end()) {
2,880✔
1336
        HSM_TRACE_DEBUG("state=<%s> is a history state with %ld stored states",
1337
                        getStateName(destinationState).c_str(),
1338
                        itHistoryData->second.previousActiveStates.size());
100✔
1339

1340
        if (itHistoryData->second.previousActiveStates.empty() == false) {
100✔
1341
            transitionToPreviousActiveStates(itHistoryData->second.previousActiveStates, event, destinationState);
60✔
1342
        } else {
1343
            transitionToDefaultHistoryState(itHistoryData->second.defaultTarget,
40✔
1344
                                            itHistoryData->second.defaultTargetTransitionCallback,
40✔
1345
                                            event,
1346
                                            destinationState);
1347
        }
1348
    }
1349

1350
    return (itHistoryData != mHistoryData.end());
2,880✔
1351
}
1352

1353
void HierarchicalStateMachine::Impl::transitionToPreviousActiveStates(std::list<StateID_t>& previousActiveStates,
60✔
1354
                                                                      const PendingEventInfo& event,
1355
                                                                      const StateID_t destinationState) {
1356
    StateID_t prevChildState = INVALID_HSM_STATE_ID;
60✔
1357
    PendingEventInfo historyTransitionEvent = event;
60✔
1358

1359
    historyTransitionEvent.transitionType = TransitionBehavior::FORCED;
60✔
1360
    historyTransitionEvent.forcedTransitionsInfo = std::make_shared<std::list<TransitionInfo>>();
120✔
1361

1362
    {
60✔
1363
        HSM_SYNC_EVENTS_QUEUE();
60✔
1364

1365
        for (const StateID_t prevState : previousActiveStates) {
220✔
1366
            if ((INVALID_HSM_STATE_ID != prevChildState) && (true == isSubstateOf(prevState, prevChildState))) {
160✔
1367
                if (false == historyTransitionEvent.forcedTransitionsInfo->empty()) {
60✔
1368
                    mPendingEvents.push_front(historyTransitionEvent);
60✔
1369
                }
1370

1371
                historyTransitionEvent.forcedTransitionsInfo = std::make_shared<std::list<TransitionInfo>>();
120✔
1372
                historyTransitionEvent.ignoreEntryPoints = true;
60✔
1373
            } else {
1374
                historyTransitionEvent.ignoreEntryPoints = false;
100✔
1375
            }
1376

1377
            prevChildState = prevState;
160✔
1378
            historyTransitionEvent.forcedTransitionsInfo->emplace_back(destinationState,
160✔
1379
                                                                       prevState,
1380
                                                                       TransitionType::EXTERNAL_TRANSITION,
320✔
1381
                                                                       nullptr,
320✔
1382
                                                                       nullptr);
320✔
1383
        }
1384

1385
        mPendingEvents.push_front(historyTransitionEvent);
60✔
1386
    }
60✔
1387

1388
    previousActiveStates.clear();
60✔
1389

1390
    StateID_t historyParent = INVALID_HSM_STATE_ID;
60✔
1391

1392
    if (true == getHistoryParent(destinationState, historyParent)) {
60✔
1393
        historyTransitionEvent.forcedTransitionsInfo = std::make_shared<std::list<TransitionInfo>>();
120✔
1394
        historyTransitionEvent.forcedTransitionsInfo->emplace_back(destinationState,
60✔
1395
                                                                   historyParent,
1396
                                                                   TransitionType::EXTERNAL_TRANSITION,
120✔
1397
                                                                   nullptr,
120✔
1398
                                                                   nullptr);
60✔
1399
        historyTransitionEvent.ignoreEntryPoints = true;
60✔
1400

1401
        HSM_SYNC_EVENTS_QUEUE();
60✔
1402
        mPendingEvents.push_front(historyTransitionEvent);
60✔
1403
    }
60✔
1404
}
60✔
1405

1406
void HierarchicalStateMachine::Impl::transitionToDefaultHistoryState(
40✔
1407
    const StateID_t defaultTarget,
1408
    const HsmTransitionCallback_t& defaultTargetTransitionCallback,
1409
    const PendingEventInfo& event,
1410
    const StateID_t destinationState) {
1411
    HSM_TRACE_CALL_DEBUG();
40✔
1412
    std::list<StateID_t> historyTargets;
40✔
1413
    StateID_t historyParent = INVALID_HSM_STATE_ID;
40✔
1414

1415
    if (true == getHistoryParent(destinationState, historyParent)) {
40✔
1416
        HSM_TRACE_DEBUG("found parent=<%s> for history state=<%s>",
1417
                        getStateName(historyParent).c_str(),
1418
                        getStateName(destinationState).c_str());
40✔
1419

1420
        if (INVALID_HSM_STATE_ID == defaultTarget) {
40✔
1421
            // transition to parent's entry point if there is no default history target
1422
            historyTargets.emplace_back(historyParent);
10✔
1423
        } else {
1424
            historyTargets.emplace_back(defaultTarget);
30✔
1425
            historyTargets.emplace_back(historyParent);
30✔
1426
        }
1427
    } else {
1428
        HSM_TRACE_ERROR("parent for history state=<%s> wasnt found", getStateName(destinationState).c_str());
40✔
1429
    }
1430

1431
    PendingEventInfo defHistoryTransitionEvent = event;
40✔
1432

1433
    defHistoryTransitionEvent.transitionType = TransitionBehavior::FORCED;
40✔
1434

1435
    for (const StateID_t historyTargetState : historyTargets) {
110✔
1436
        HsmTransitionCallback_t cbTransition;
70✔
1437

1438
        defHistoryTransitionEvent.forcedTransitionsInfo = std::make_shared<std::list<TransitionInfo>>();
140✔
1439

1440
        if ((INVALID_HSM_STATE_ID != defaultTarget) && (historyTargetState == historyParent)) {
70✔
1441
            defHistoryTransitionEvent.ignoreEntryPoints = true;
30✔
1442
        } else {
1443
            cbTransition = defaultTargetTransitionCallback;
40✔
1444
        }
1445

1446
        defHistoryTransitionEvent.forcedTransitionsInfo->emplace_back(destinationState,
70✔
1447
                                                                      historyTargetState,
1448
                                                                      TransitionType::EXTERNAL_TRANSITION,
140✔
1449
                                                                      cbTransition,
1450
                                                                      nullptr);
70✔
1451

1452
        mPendingEvents.push_front(defHistoryTransitionEvent);
70✔
1453
    }
70✔
1454
}
80✔
1455

1456
bool HierarchicalStateMachine::Impl::processFinalStateTransition(const PendingEventInfo& event,
3,130✔
1457
                                                                 const StateID_t destinationState) {
1458
    const auto itFinalStateEvent = mFinalStates.find(destinationState);
3,130✔
1459

1460
    if (itFinalStateEvent != mFinalStates.end()) {
3,130✔
1461
        StateID_t parentState = INVALID_HSM_STATE_ID;
250✔
1462

1463
        // don't generate events for top level final states since no one can process them
1464
        if (true == getParentState(destinationState, parentState)) {
250✔
1465
            // check if there are any other active siblings in this parent state
1466
            // only generate final state event when all siblings got deactivated
1467
            if (false == hasActiveChildren(parentState, false)) {
240✔
1468
                PendingEventInfo finalStateEvent;
190✔
1469

1470
                finalStateEvent.transitionType = TransitionBehavior::REGULAR;
190✔
1471
                finalStateEvent.args = event.args;
190✔
1472

1473
                if (INVALID_HSM_EVENT_ID != itFinalStateEvent->second) {
190✔
1474
                    finalStateEvent.id = itFinalStateEvent->second;
110✔
1475
                } else {
1476
                    finalStateEvent.id = event.id;
80✔
1477
                }
1478

1479
                {
190✔
1480
                    HSM_SYNC_EVENTS_QUEUE();
190✔
1481
                    mPendingEvents.push_front(finalStateEvent);
190✔
1482
                }
190✔
1483
            }
190✔
1484
        }
1485
    }
1486

1487
    return (itFinalStateEvent != mFinalStates.end());
3,130✔
1488
}
1489

1490
HsmEventStatus HierarchicalStateMachine::Impl::handleSingleTransition(const StateID_t fromState,
3,770✔
1491
                                                                      const PendingEventInfo& event) {
1492
    HSM_TRACE_CALL_DEBUG_ARGS("fromState=<%s>, event=<%s>, transitionType=%d",
1493
                              getStateName(fromState).c_str(),
1494
                              getEventName(event.id).c_str(),
1495
                              SC2INT(event.transitionType));
3,770✔
1496
    HsmEventStatus res = HsmEventStatus::DONE_FAILED;
3,770✔
1497
    bool isCorrectTransition = false;
3,770✔
1498
    std::list<TransitionInfo> matchingTransitions;
3,770✔
1499

1500
    DEBUG_DUMP_ACTIVE_STATES();
3,770✔
1501

1502
    // determine target state based on current transition
1503
    isCorrectTransition = determineTargetState(event, fromState, matchingTransitions);
3,770✔
1504

1505
    // handle transition if it passed validation and has a target state
1506
    if (true == isCorrectTransition) {
3,770✔
1507
        bool isExitAllowed = true;
2,910✔
1508
        std::list<StateID_t> exitedStates;
2,910✔
1509

1510
        // execute self transitions first
1511
        if (true == executeSelfTransitions(event, matchingTransitions)) {
2,910✔
1512
            res = HsmEventStatus::DONE_OK;
50✔
1513
        }
1514

1515
        // execute exit transition (only once in case of parallel transitions)
1516
        isExitAllowed = executeExitTransition(event, matchingTransitions, exitedStates);
2,910✔
1517

1518
        // proceed if transition was not blocked during state exit
1519
        if (true == isExitAllowed) {
2,910✔
1520
            for (const TransitionInfo& curTransition : matchingTransitions) {
6,100✔
1521
                // everything except internal self-transitions
1522
                if ((curTransition.fromState != curTransition.destinationState) ||
3,210✔
1523
                    (TransitionType::EXTERNAL_TRANSITION == curTransition.transitionType)) {
90✔
1524
                    res = processExternalTransition(event, fromState, curTransition, exitedStates);
3,150✔
1525
                }
1526
            }
1527
        } else {
1528
            res = HsmEventStatus::CANCELED;
1529
        }
1530
    }
2,910✔
1531

1532
    if (HsmEventStatus::DONE_FAILED == res) {
3,770✔
1533
        HSM_TRACE_DEBUG("event <%s> in state <%s> was ignored.",
1534
                        getEventName(event.id).c_str(),
1535
                        getStateName(fromState).c_str());
1536
    }
1537

1538
    DEBUG_DUMP_ACTIVE_STATES();
3,770✔
1539
    HSM_TRACE_CALL_RESULT("%d", SC2INT(res));
3,770✔
1540
    return res;
3,770✔
1541
}
3,770✔
1542

1543
void HierarchicalStateMachine::Impl::clearPendingEvents() {
10✔
1544
    HSM_TRACE_CALL_DEBUG_ARGS("clearPendingEvents: mPendingEvents.size()=%ld", mPendingEvents.size());
10✔
1545

1546
    for (auto it = mPendingEvents.begin(); (it != mPendingEvents.end()); ++it) {
10✔
1547
        // since ongoing transitions can't be canceled we need to treat entry point transitions as atomic
1548
        if (TransitionBehavior::REGULAR == it->transitionType) {
×
1549
            it->releaseLock();
×
1550
        }
1551
    }
1552

1553
    mPendingEvents.clear();
10✔
1554
}
10✔
1555

1556
bool HierarchicalStateMachine::Impl::hasSubstates(const StateID_t parent) const {
2,650✔
1557
    return (mSubstates.find(parent) != mSubstates.end());
2,650✔
1558
}
1559

1560
bool HierarchicalStateMachine::Impl::hasEntryPoint(const StateID_t state) const {
470✔
1561
    return (mSubstateEntryPoints.find(state) != mSubstateEntryPoints.end());
470✔
1562
}
1563

1564
bool HierarchicalStateMachine::Impl::getEntryPoints(const StateID_t state,
4,980✔
1565
                                                    const EventID_t onEvent,
1566
                                                    const VariantVector_t& transitionArgs,
1567
                                                    std::list<StateID_t>& outEntryPoints) const {
1568
    auto itRange = mSubstateEntryPoints.equal_range(state);
4,980✔
1569

1570
    outEntryPoints.clear();
4,980✔
1571

1572
    for (auto it = itRange.first; it != itRange.second; ++it) {
7,240✔
1573
        if (((INVALID_HSM_EVENT_ID == it->second.onEvent) || (onEvent == it->second.onEvent)) &&
2,260✔
1574
            // check transition condition if it was defined
1575
            ((nullptr == it->second.checkCondition) ||
2,200✔
1576
             (it->second.checkCondition(transitionArgs) == it->second.expectedConditionValue))) {
120✔
1577
            outEntryPoints.emplace_back(it->second.state);
4,280✔
1578
        }
1579
    }
1580

1581
    return (false == outEntryPoints.empty());
4,980✔
1582
}
1583

1584
bool HierarchicalStateMachine::Impl::replaceActiveState(const StateID_t oldState, const StateID_t newState) {
3,130✔
1585
    HSM_TRACE_CALL_DEBUG_ARGS("oldState=<%s>, newState=<%s>", getStateName(oldState).c_str(), getStateName(newState).c_str());
3,130✔
1586

1587
    if (false == isSubstateOf(oldState, newState)) {
3,130✔
1588
        mActiveStates.remove(oldState);
2,160✔
1589
    }
1590

1591
    return addActiveState(newState);
3,130✔
1592
}
1593

1594
bool HierarchicalStateMachine::Impl::addActiveState(const StateID_t newState) {
3,150✔
1595
    HSM_TRACE_CALL_DEBUG_ARGS("newState=<%s>", getStateName(newState).c_str());
3,150✔
1596
    bool wasAdded = false;
3,150✔
1597

1598
    if (false == isStateActive(newState)) {
3,150✔
1599
        mActiveStates.emplace_back(newState);
3,100✔
1600
        wasAdded = true;
3,100✔
1601
    }
1602

1603
    HSM_TRACE_DEBUG("mActiveStates.size=%d", SC2INT(mActiveStates.size()));
3,150✔
1604
    return wasAdded;
3,150✔
1605
}
1606

1607
#ifdef HSM_ENABLE_SAFE_STRUCTURE
1608

1609
bool HierarchicalStateMachine::Impl::isTopState(const StateID_t state) const {
5,670✔
1610
    auto it = std::find(mTopLevelStates.begin(), mTopLevelStates.end(), state);
5,670✔
1611

1612
    return (it == mTopLevelStates.end());
5,670✔
1613
}
1614

1615
bool HierarchicalStateMachine::Impl::isSubstate(const StateID_t state) const {
4,000✔
1616
    bool result = false;
4,000✔
1617

1618
    for (const auto& curSubstate : mSubstates) {
4,000✔
1619
        if (curSubstate.second == state) {
×
1620
            result = true;
1621
            break;
1622
        }
1623
    }
1624

1625
    return result;
4,000✔
1626
}
1627

1628
bool HierarchicalStateMachine::Impl::hasParentState(const StateID_t state, StateID_t& outParent) const {
3,850✔
1629
    bool hasParent = false;
3,850✔
1630

1631
    for (const auto& curSubstate : mSubstates) {
8,110✔
1632
        if (state == curSubstate.second) {
4,740✔
1633
            hasParent = true;
480✔
1634
            outParent = curSubstate.first;  // cppcheck-suppress misra-c2012-17.8 ; outParent is used to return result
480✔
1635
            break;
480✔
1636
        }
1637
    }
1638

1639
    return hasParent;
3,850✔
1640
}
1641

1642
#endif  // HSM_ENABLE_SAFE_STRUCTURE
1643

1644
bool HierarchicalStateMachine::Impl::enableHsmDebugging() {
×
1645
#ifdef HSMBUILD_DEBUGGING
1646
    constexpr const char* DEFAULT_DUMP_PATH = "./dump.hsmlog";
×
1647
    constexpr const char* ENV_DUMPPATH = "HSMCPP_DUMP_PATH";
×
1648
    // NOLINTNEXTLINE(concurrency-mt-unsafe): enableHsmDebugging() doesn't need to be thread-safe
1649
    char* envPath = std::getenv(ENV_DUMPPATH);
×
1650

1651
    return enableHsmDebugging((nullptr == envPath) ? DEFAULT_DUMP_PATH : std::string(envPath));
×
1652
#else
1653
    return true;
1654
#endif
1655
}
1656

1657
bool HierarchicalStateMachine::Impl::enableHsmDebugging(const std::string& dumpPath) {
×
1658
#ifdef HSMBUILD_DEBUGGING
1659
    bool res = false;
×
1660
    bool isNewLog = (access(dumpPath.c_str(), F_OK) != 0);
×
1661

1662
    if (nullptr != mHsmLogFile.open(dumpPath.c_str(), std::ios::out | std::ios::app)) {
×
1663
        mHsmLog = std::make_shared<std::ostream>(&mHsmLogFile);
×
1664

1665
        if (true == isNewLog) {
×
1666
            *mHsmLog << "---\n";
×
1667
            mHsmLog->flush();
×
1668
        }
1669

1670
        res = true;
1671
    }
1672

1673
    return res;
×
1674
#else
1675
    return true;
1676
#endif
1677
}
1678

1679
void HierarchicalStateMachine::Impl::disableHsmDebugging() {
2,370✔
1680
#ifdef HSMBUILD_DEBUGGING
1681
    mHsmLogFile.close();
2,370✔
1682
#endif
1683
}
2,370✔
1684

1685
void HierarchicalStateMachine::Impl::logHsmAction(const HsmLogAction action,
10,180✔
1686
                                                  const StateID_t fromState,
1687
                                                  const StateID_t targetState,
1688
                                                  const EventID_t event,
1689
                                                  const bool hasFailed,
1690
                                                  const VariantVector_t& args) {
1691
#ifdef HSMBUILD_DEBUGGING
1692
    if (true == mHsmLogFile.is_open()) {
10,180✔
1693
        static Mutex logMutex;
×
1694
        CriticalSection logSync(logMutex);
×
1695

1696
        static const std::map<HsmLogAction, std::string> actionsMap = {
×
1697
            std::make_pair(HsmLogAction::IDLE, "idle"),
×
1698
            std::make_pair(HsmLogAction::TRANSITION, "transition"),
×
1699
            std::make_pair(HsmLogAction::TRANSITION_ENTRYPOINT, "transition_entrypoint"),
×
1700
            std::make_pair(HsmLogAction::CALLBACK_EXIT, "callback_exit"),
×
1701
            std::make_pair(HsmLogAction::CALLBACK_ENTER, "callback_enter"),
×
1702
            std::make_pair(HsmLogAction::CALLBACK_STATE, "callback_state"),
×
1703
            std::make_pair(HsmLogAction::ON_ENTER_ACTIONS, "onenter_actions"),
×
1704
            std::make_pair(HsmLogAction::ON_EXIT_ACTIONS, "onexit_actions")};
×
1705
        constexpr size_t bufTimeSize = 80;
×
1706
        constexpr size_t bufTimeMsSize = 6;
×
1707
        std::array<char, bufTimeSize> bufTime = {0};
×
1708
        std::array<char, bufTimeMsSize> bufTimeMs = {0};
×
1709
        auto currentTimePoint = std::chrono::system_clock::now();
×
1710
        const std::time_t tt = std::chrono::system_clock::to_time_t(currentTimePoint);
×
1711
        std::tm timeinfo = {0};
×
1712
        const std::tm* tmResult = nullptr;  // this is just to check that localtime was executed correctly
×
1713

1714
  #ifdef WIN32
1715
        if (0 == ::localtime_s(&timeinfo, &tt)) {
1716
            tmResult = &timeinfo;
1717
        }
1718
  #else
1719
        // NOTE: function is not thread safe [concurrency-mt-unsafe]
1720
        tmResult = localtime(&tt);
×
1721
        if (nullptr != tmResult) {
×
1722
            timeinfo = *tmResult;
×
1723
        }
1724
  #endif  // WIN32
1725

1726
        if (nullptr != tmResult) {
×
1727
            constexpr int millisecondsPerSecond = 1000;
×
1728

1729
            (void)std::strftime(bufTime.data(), bufTime.size(), "%Y-%m-%d %H:%M:%S", &timeinfo);
×
1730
            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg): using snprintf instead stringstream for performance reasons
1731
            (void)snprintf(
×
1732
                bufTimeMs.data(),
1733
                bufTimeMs.size(),
1734
                ".%03d",
1735
                static_cast<int>(
1736
                    std::chrono::duration_cast<std::chrono::milliseconds>(currentTimePoint.time_since_epoch()).count() %
×
1737
                    millisecondsPerSecond));
1738
        } else {
1739
            (void)std::strncpy(bufTime.data(), "0000-00-00 00:00:00", bufTime.size() - 1U);
×
1740
            (void)std::strncpy(bufTimeMs.data(), ".000", bufTimeMs.size() - 1U);
×
1741
        }
1742

1743
        *mHsmLog << "\n-\n"
×
1744
                    "  timestamp: \""
1745
                 << bufTime.data() << bufTimeMs.data()
×
1746
                 << "\"\n"
1747
                    "  active_states:";
×
1748

1749
        for (const auto& curState : mActiveStates) {
×
1750
            *mHsmLog << "\n    - \"" << getStateName(curState) << "\"";
×
1751
        }
1752

1753
        *mHsmLog << "\n  action: " << actionsMap.at(action)
×
1754
                 << "\n"
1755
                    "  from_state: \""
1756
                 << getStateName(fromState)
×
1757
                 << "\"\n"
1758
                    "  target_state: \""
1759
                 << getStateName(targetState)
×
1760
                 << "\"\n"
1761
                    "  event: \""
1762
                 << getEventName(event)
×
1763
                 << "\"\n"
1764
                    "  status: "
1765
                 << (hasFailed ? "failed" : "")
1766
                 << "\n"
1767
                    "  args:";
×
1768

1769
        for (const auto& curArg : args) {
×
1770
            *mHsmLog << "\n    - " << curArg.toString();
×
1771
        }
1772

1773
        mHsmLog->flush();
×
1774
    }
×
1775
#endif  // HSMBUILD_DEBUGGING
1776
}
10,180✔
1777

1778
#ifndef HSM_DISABLE_DEBUG_TRACES
1779
void HierarchicalStateMachine::Impl::dumpActiveStates() {
1780
    HSM_TRACE_CALL();
1781

1782
    std::string temp;
1783

1784
    for (const auto& curState : mActiveStates) {
1785
        temp += getStateName(curState) + std::string(", ");
1786
    }
1787

1788
    HSM_TRACE_DEBUG("active states: <%s>", temp.c_str());
1789
}
1790

1791
std::string HierarchicalStateMachine::Impl::getStateName(const StateID_t state) {
1792
    std::string res;
1793
  #ifndef HSM_DISABLE_THREADSAFETY
1794
    LockGuard lk(mParentSync);
1795
  #endif
1796

1797
    if (nullptr != mParent) {
1798
        res = mParent->getStateName(state);
1799
    }
1800

1801
    return res;
1802
}
1803

1804
std::string HierarchicalStateMachine::Impl::getEventName(const EventID_t event) {
1805
    std::string res;
1806
  #ifndef HSM_DISABLE_THREADSAFETY
1807
    LockGuard lk(mParentSync);
1808
  #endif
1809

1810
    if (nullptr != mParent) {
1811
        res = mParent->getEventName(event);
1812
    }
1813

1814
    return res;
1815
}
1816
#else   // HSM_DISABLE_DEBUG_TRACES
1817

1818
std::string HierarchicalStateMachine::Impl::getStateName(const StateID_t state) {
×
1819
    return std::string();
×
1820
}
1821

1822
std::string HierarchicalStateMachine::Impl::getEventName(const EventID_t event) {
×
1823
    return std::string();
×
1824
}
1825
#endif  // HSM_DISABLE_DEBUG_TRACES
1826

1827
}  // namespace hsmcpp
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc