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

igor-krechetov / hsmcpp / 4957453907

pending completion
4957453907

push

github

igor-krechetov
[0.37.0][r] fix early delete issue

74 of 74 new or added lines in 5 files covered. (100.0%)

2291 of 2482 relevant lines covered (92.3%)

1739.84 hits per line

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

90.26
/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 <chrono>
20
  #include <cstdlib>
21
  #include <cstring>
22
  #include <array>
23

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

35
#ifndef HSM_DISABLE_DEBUG_TRACES
36
  #define DEBUG_DUMP_ACTIVE_STATES() dumpActiveStates()
37
#else
38
  #define DEBUG_DUMP_ACTIVE_STATES()
39
#endif
40

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

47
constexpr const char* HSM_TRACE_CLASS = "HierarchicalStateMachine";
48

49
// These macroses can't be converted to 'constexpr' template functions
50
// NOLINTBEGIN(cppcoreguidelines-macro-usage)
51

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

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

69
// NOLINTEND(cppcoreguidelines-macro-usage)
70

71
namespace hsmcpp {
72

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

83
HierarchicalStateMachine::Impl::~Impl() {
2,360✔
84
    release();
1,180✔
85
}
4,930✔
86

87
void HierarchicalStateMachine::Impl::resetParent() {
1,180✔
88
#ifndef HSM_DISABLE_THREADSAFETY
89
    LockGuard lk(mParentSync);
1,180✔
90
#endif
91

92
    mParent = nullptr;
1,180✔
93
}
1,180✔
94

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

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

105
    if (true == mDispatcher.expired()) {
1,190✔
106
        auto dispatcherPtr = dispatcher.lock();
1,190✔
107

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

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

120
                if (false == ptrInstance.expired()) {
2,380✔
121
                    mDispatcher = dispatcher;
1,190✔
122

123
                    createEventHandler(dispatcherPtr, ptrInstance);
1,190✔
124
                    createTimerHandler(dispatcherPtr, ptrInstance);
1,190✔
125
                    createEnqueuedEventHandler(dispatcherPtr, ptrInstance);
1,190✔
126

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

154
    return result;
1,190✔
155
}
156

157
std::weak_ptr<IHsmEventDispatcher> HierarchicalStateMachine::Impl::dispatcher() const {
×
158
    return mDispatcher;
×
159
}
160

161
bool HierarchicalStateMachine::Impl::isInitialized() const {
×
162
    return (false == mDispatcher.expired());
×
163
}
164

165
void HierarchicalStateMachine::Impl::release() {
2,370✔
166
    mStopDispatching = true;
2,370✔
167
    HSM_TRACE_CALL_DEBUG();
2,370✔
168

169
    disableHsmDebugging();
2,370✔
170

171
    auto dispatcherPtr = mDispatcher.lock();
2,370✔
172

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

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

186
void HierarchicalStateMachine::Impl::registerFailedTransitionCallback(HsmTransitionFailedCallback_t onFailedTransition) {
210✔
187
    mFailedTransitionCallback = std::move(onFailedTransition);
210✔
188
}
210✔
189

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

200
    if (onStateChanged || onEntering || onExiting) {
4,000✔
201
        StateCallbacks cbState;
3,030✔
202

203
        cbState.onStateChanged = std::move(onStateChanged);
3,030✔
204
        cbState.onEntering = std::move(onEntering);
3,030✔
205
        cbState.onExiting = std::move(onExiting);
3,030✔
206
        mRegisteredStates[state] = cbState;
3,030✔
207

208
        HSM_TRACE_CALL_DEBUG_ARGS("mRegisteredStates.size=%ld", mRegisteredStates.size());
3,030✔
209
    }
3,030✔
210
}
4,000✔
211

212
void HierarchicalStateMachine::Impl::registerFinalState(const StateID_t state,
270✔
213
                                                        const EventID_t event,
214
                                                        HsmStateChangedCallback_t onStateChanged,
215
                                                        HsmStateEnterCallback_t onEntering,
216
                                                        HsmStateExitCallback_t onExiting) {
217
    mFinalStates.emplace(state, event);
270✔
218
    registerState(state, std::move(onStateChanged), std::move(onEntering), std::move(onExiting));
620✔
219
}
270✔
220

221
void HierarchicalStateMachine::Impl::registerHistory(const StateID_t parent,
100✔
222
                                                     const StateID_t historyState,
223
                                                     const HistoryType type,
224
                                                     const StateID_t defaultTarget,
225
                                                     HsmTransitionCallback_t transitionCallback) {
226
    (void)mHistoryStates.emplace(parent, historyState);
100✔
227
    mHistoryData.emplace(historyState, HistoryInfo(type, defaultTarget, std::move(transitionCallback)));
210✔
228
}
100✔
229

230
bool HierarchicalStateMachine::Impl::registerSubstate(const StateID_t parent, const StateID_t substate) {
740✔
231
    return registerSubstate(parent, substate, false);
740✔
232
}
233

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

242
void HierarchicalStateMachine::Impl::registerTimer(const TimerID_t timerID, const EventID_t event) {
150✔
243
    mTimers.emplace(timerID, event);
150✔
244
}
150✔
245

246
bool HierarchicalStateMachine::Impl::registerSubstate(const StateID_t parent,
1,710✔
247
                                                      const StateID_t substate,
248
                                                      const bool isEntryPoint,
249
                                                      const EventID_t eventCondition,
250
                                                      HsmTransitionConditionCallback_t conditionCallback,
251
                                                      const bool expectedConditionValue) {
252
    bool registrationAllowed = false;
1,710✔
253

254
#ifdef HSM_ENABLE_SAFE_STRUCTURE
255
    // do a simple sanity check
256
    if (parent != substate) {
1,710✔
257
        StateID_t curState = parent;
1,710✔
258
        StateID_t prevState = INVALID_HSM_STATE_ID;
1,710✔
259

260
        if (false == hasParentState(substate, prevState)) {
1,710✔
261
            registrationAllowed = true;
2,140✔
262

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

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

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

291
            entryInfo.state = substate;
930✔
292
            entryInfo.onEvent = eventCondition;
930✔
293
            entryInfo.checkCondition = std::move(conditionCallback);
930✔
294
            entryInfo.expectedConditionValue = expectedConditionValue;
930✔
295

296
            (void)mSubstateEntryPoints.emplace(parent, entryInfo);
930✔
297
        }
930✔
298

299
        (void)mSubstates.emplace(parent, substate);
1,670✔
300

301
#ifdef HSM_ENABLE_SAFE_STRUCTURE
302
        if (true == isTopState(substate)) {
1,670✔
303
            mTopLevelStates.remove(substate);
1,670✔
304
        }
305
#endif  // HSM_ENABLE_SAFE_STRUCTURE
306
    }
307

308
    return registrationAllowed;
1,710✔
309
}
310

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

323
    newAction.actionArgs = args;
220✔
324

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

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

351
    return result;
220✔
352
}
220✔
353

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

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

384
StateID_t HierarchicalStateMachine::Impl::getLastActiveState() const {
40✔
385
    StateID_t currentState = INVALID_HSM_STATE_ID;
40✔
386

387
    if (false == mActiveStates.empty()) {
40✔
388
        currentState = mActiveStates.back();
40✔
389
    }
390

391
    return currentState;
40✔
392
}
393

394
const std::list<StateID_t>& HierarchicalStateMachine::Impl::getActiveStates() const {
2,310✔
395
    return mActiveStates;
2,310✔
396
}
397

398
bool HierarchicalStateMachine::Impl::isStateActive(const StateID_t state) const {
12,083✔
399
    return (std::find(mActiveStates.begin(), mActiveStates.end(), state) != mActiveStates.end());
12,083✔
400
}
401

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

405
    (void)transitionExWithArgsArray(event, false, false, 0, args);
100✔
406
}
100✔
407

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

419
    bool status = false;
2,490✔
420
    auto dispatcherPtr = mDispatcher.lock();
2,490✔
421

422
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
423
    if (dispatcherPtr) {
2,490✔
424
        PendingEventInfo eventInfo;
2,480✔
425

426
        eventInfo.type = event;
2,480✔
427
        eventInfo.args = args;
2,480✔
428

429
        if (true == sync) {
2,480✔
430
            eventInfo.initLock();
1,170✔
431
        }
432

433
        {
2,480✔
434
            HSM_SYNC_EVENTS_QUEUE();
2,480✔
435

436
            if (true == clearQueue) {
2,480✔
437
                clearPendingEvents();
10✔
438
            }
439

440
            mPendingEvents.push_back(eventInfo);
2,480✔
441
        }
2,480✔
442

443
        HSM_TRACE_DEBUG("transitionEx: emit");
2,480✔
444
        dispatcherPtr->emitEvent(mEventsHandlerId);
2,480✔
445

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

458
    HSM_TRACE_CALL_RESULT("%d", SC2INT(status));
2,490✔
459
    return status;
2,490✔
460
}
2,490✔
461

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

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

472
    return res;
10✔
473
}
10✔
474

475
bool HierarchicalStateMachine::Impl::isTransitionPossible(const EventID_t event, const VariantVector_t& args) {
60✔
476
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>", getEventName(event).c_str());
60✔
477
    bool possible = false;
60✔
478

479
    for (const StateID_t& state : mActiveStates) {
100✔
480
        possible = checkTransitionPossibility(state, event, args);
60✔
481

482
        if (true == possible) {
60✔
483
            break;
484
        }
485
    }
486

487
    HSM_TRACE_CALL_RESULT("%d", BOOL2INT(possible));
60✔
488
    return possible;
60✔
489
}
490

491
void HierarchicalStateMachine::Impl::startTimer(const TimerID_t timerID,
50✔
492
                                                const unsigned int intervalMs,
493
                                                const bool isSingleShot) {
494
    auto dispatcherPtr = mDispatcher.lock();
50✔
495

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

502
void HierarchicalStateMachine::Impl::restartTimer(const TimerID_t timerID) {
10✔
503
    auto dispatcherPtr = mDispatcher.lock();
10✔
504

505
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
506
    if (dispatcherPtr) {
10✔
507
        dispatcherPtr->restartTimer(timerID);
10✔
508
    }
509
}
10✔
510

511
void HierarchicalStateMachine::Impl::stopTimer(const TimerID_t timerID) {
20✔
512
    auto dispatcherPtr = mDispatcher.lock();
20✔
513

514
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
515
    if (dispatcherPtr) {
20✔
516
        dispatcherPtr->stopTimer(timerID);
20✔
517
    }
518
}
20✔
519

520
bool HierarchicalStateMachine::Impl::isTimerRunning(const TimerID_t timerID) {
60✔
521
    bool running = false;
60✔
522
    auto dispatcherPtr = mDispatcher.lock();
60✔
523

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

529
    return running;
60✔
530
}
60✔
531

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

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

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

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

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

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

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

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

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

593
void HierarchicalStateMachine::Impl::handleStartup() {
1,190✔
594
    HSM_TRACE_CALL_DEBUG_ARGS("mActiveStates.size=%ld", mActiveStates.size());
1,190✔
595
    auto dispatcherPtr = mDispatcher.lock();
1,190✔
596

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

602
        (void)onStateEntering(mInitialState, VariantVector_t());
1,190✔
603
        mActiveStates.push_back(mInitialState);
1,190✔
604
        onStateChanged(mInitialState, VariantVector_t());
1,190✔
605

606
        if (true == getEntryPoints(mInitialState, INVALID_HSM_EVENT_ID, VariantVector_t(), entryPoints)) {
1,190✔
607
            PendingEventInfo entryPointTransitionEvent;
240✔
608

609
            entryPointTransitionEvent.transitionType = TransitionBehavior::ENTRYPOINT;
240✔
610
            entryPointTransitionEvent.type = INVALID_HSM_EVENT_ID;
240✔
611

612
            {
240✔
613
                HSM_SYNC_EVENTS_QUEUE();
240✔
614
                mPendingEvents.push_front(entryPointTransitionEvent);
240✔
615
            }
240✔
616
        }
240✔
617

618
        if (false == mPendingEvents.empty()) {
1,190✔
619
            dispatcherPtr->emitEvent(mEventsHandlerId);
240✔
620
        }
621
    }
1,190✔
622
}
1,190✔
623

624
void HierarchicalStateMachine::Impl::transitionSimple(const EventID_t event) {
150✔
625
    (void)transitionExWithArgsArray(event, false, false, 0, VariantVector_t());
150✔
626
}
150✔
627

628
void HierarchicalStateMachine::Impl::dispatchEvents() {
3,065✔
629
    HSM_TRACE_CALL_DEBUG_ARGS("mPendingEvents.size=%ld", mPendingEvents.size());
3,065✔
630
    auto dispatcherPtr = mDispatcher.lock();
3,065✔
631

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

636
        if (false == mStopDispatching) {
3,065✔
637
            if (false == mPendingEvents.empty()) {
3,065✔
638
                PendingEventInfo pendingEvent;
3,010✔
639

640
                {
3,010✔
641
                    HSM_SYNC_EVENTS_QUEUE();
3,010✔
642
                    pendingEvent = mPendingEvents.front();
3,010✔
643
                    mPendingEvents.pop_front();
3,010✔
644
                }
3,010✔
645

646
                HsmEventStatus transitiontStatus = doTransition(pendingEvent);
3,010✔
647

648
                HSM_TRACE_DEBUG("unlock with status %d", SC2INT(transitiontStatus));
3,010✔
649
                pendingEvent.unlock(transitiontStatus);
3,010✔
650
            }
3,010✔
651

652
            if ((false == mStopDispatching) && (false == mPendingEvents.empty())) {
3,065✔
653
                dispatcherPtr->emitEvent(mEventsHandlerId);
1,424✔
654
            }
655
        }
656

657
        mIsDispatching.clear();
3,065✔
658
        mIsDispatching.notify();
3,065✔
659
    }
3,065✔
660
}
3,065✔
661

662
void HierarchicalStateMachine::Impl::dispatchTimerEvent(const TimerID_t id) {
140✔
663
    HSM_TRACE_CALL_DEBUG_ARGS("id=%d", SC2INT(id));
140✔
664
    auto it = mTimers.find(id);
140✔
665

666
    if (mTimers.end() != it) {
140✔
667
        transitionSimple(it->second);
140✔
668
    }
669
}
140✔
670

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

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

686
    // execute state action only if transition was accepted by client
687
    if (true == res) {
330✔
688
        executeStateAction(state, StateActionTrigger::ON_STATE_EXIT);
2,470✔
689
    }
690

691
    return res;
2,490✔
692
}
693

694
bool HierarchicalStateMachine::Impl::onStateEntering(const StateID_t state, const VariantVector_t& args) {
4,370✔
695
    HSM_TRACE_CALL_DEBUG_ARGS("state=<%s>", getStateName(state).c_str());
4,370✔
696
    bool res = true;
4,370✔
697

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

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

708
        // execute state action only if transition was accepted by client
709
        if (true == res) {
560✔
710
            executeStateAction(state, StateActionTrigger::ON_STATE_ENTRY);
4,300✔
711
        }
712
    }
713

714
    return res;
4,370✔
715
}
716

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

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

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

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

748
        for (auto it = itRange.first; it != itRange.second; ++it) {
410✔
749
            const StateActionInfo& actionInfo = it->second;
230✔
750

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

763
                if (actionInfo.actionArgs.size() > 1U) {
100✔
764
                    transitionArgs.reserve(actionInfo.actionArgs.size() - 1U);
10✔
765

766
                    for (size_t i = 1; i < actionInfo.actionArgs.size(); ++i) {
30✔
767
                        transitionArgs.push_back(actionInfo.actionArgs[i]);
20✔
768
                    }
769
                }
770

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

779
bool HierarchicalStateMachine::Impl::getParentState(const StateID_t child, StateID_t& outParent) {
10,973✔
780
    bool wasFound = false;
10,973✔
781
    auto it = std::find_if(mSubstates.begin(), mSubstates.end(), [child](const std::pair<StateID_t, StateID_t>& item) {
10,973✔
782
        // cppcheck-suppress misra-c2012-15.5 ; false-positive. "return" statement belongs to lambda function
783
        return (child == item.second);
22,923✔
784
    });
785

786
    if (mSubstates.end() != it) {
10,973✔
787
        outParent = it->first;  // cppcheck-suppress misra-c2012-17.8 ; outParent is used to return result
5,563✔
788
        wasFound = true;
5,563✔
789
    }
790

791
    return wasFound;
10,973✔
792
}
793
bool HierarchicalStateMachine::Impl::isSubstateOf(const StateID_t parent, const StateID_t child) {
5,743✔
794
    HSM_TRACE_CALL_DEBUG_ARGS("parent=<%s>, child=<%s>", getStateName(parent).c_str(), getStateName(child).c_str());
5,743✔
795
    StateID_t curState = child;
5,743✔
796

797
    if (parent != child) {
5,743✔
798
        // TODO: can be optimized by checking siblings on each level
799

800
        do {
6,913✔
801
            if (false == getParentState(curState, curState)) {
6,913✔
802
                break;
803
            }
804
        } while (parent != curState);
3,873✔
805
    }
806

807
    return (parent != child) && (parent == curState);
5,743✔
808
}
809

810
bool HierarchicalStateMachine::Impl::isFinalState(const StateID_t state) const {
510✔
811
    return (mFinalStates.end() != mFinalStates.find(state));
510✔
812
}
813

814
bool HierarchicalStateMachine::Impl::hasActiveChildren(const StateID_t parent, const bool includeFinal) {
240✔
815
    HSM_TRACE_CALL_DEBUG_ARGS("parent=<%s>", getStateName(parent).c_str());
240✔
816
    bool res = false;
240✔
817

818
    for (const StateID_t& activeStateId : mActiveStates) {
700✔
819
        if ((true == includeFinal) || (false == isFinalState(activeStateId))) {
510✔
820
            if (isSubstateOf(parent, activeStateId)) {
290✔
821
                HSM_TRACE_DEBUG("parent=<%s> has <%s> active",
822
                                getStateName(parent).c_str(),
823
                                getStateName(activeStateId).c_str());
824
                res = true;
825
                break;
826
            }
827
        }
828
    }
829

830
    HSM_TRACE_CALL_RESULT("res=%d", BOOL2INT(res));
240✔
831
    return res;
240✔
832
}
833

834
bool HierarchicalStateMachine::Impl::getHistoryParent(const StateID_t historyState, StateID_t& outParent) {
100✔
835
    bool wasFound = false;
100✔
836
    auto it =
100✔
837
        std::find_if(mHistoryStates.begin(), mHistoryStates.end(), [historyState](const std::pair<StateID_t, StateID_t>& item) {
100✔
838
            // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
839
            // cppcheck-suppress misra-c2012-15.5
840
            return (historyState == item.second);
100✔
841
        });
842

843
    if (mHistoryStates.end() != it) {
100✔
844
        outParent = it->first;  // cppcheck-suppress misra-c2012-17.8 ; outParent is used to return result
100✔
845
        wasFound = true;
100✔
846
    }
847

848
    return wasFound;
100✔
849
}
850

851
void HierarchicalStateMachine::Impl::updateHistory(const StateID_t topLevelState, const std::list<StateID_t>& exitedStates) {
2,080✔
852
    HSM_TRACE_CALL_DEBUG_ARGS("topLevelState=<%s>, exitedStates.size=%ld",
853
                              getStateName(topLevelState).c_str(),
854
                              exitedStates.size());
2,080✔
855
    std::list<std::list<StateID_t>*> upatedHistory;
2,080✔
856

857
    for (const auto& activeState : exitedStates) {
4,710✔
858
        StateID_t curState = activeState;
2,630✔
859
        StateID_t parentState = INVALID_HSM_STATE_ID;
2,630✔
860

861
        // check if we have parent state
862
        while (true == getParentState(curState, parentState)) {
3,450✔
863
            HSM_TRACE_DEBUG("curState=<%s>, parentState=<%s>",
864
                            getStateName(curState).c_str(),
865
                            getStateName(parentState).c_str());
1,370✔
866
            auto itRange = mHistoryStates.equal_range(parentState);
1,370✔
867

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

872
                for (auto it = itRange.first; it != itRange.second; ++it) {
680✔
873
                    auto itCurHistory = mHistoryData.find(it->second);
340✔
874

875
                    if (itCurHistory != mHistoryData.end()) {
340✔
876
                        const auto itUpdatedHistory =
340✔
877
                            std::find(upatedHistory.begin(), upatedHistory.end(), &itCurHistory->second.previousActiveStates);
340✔
878

879
                        // check if this is the first time we are updating this history state
880
                        if (itUpdatedHistory == upatedHistory.end()) {
340✔
881
                            // clear previos history items
882
                            itCurHistory->second.previousActiveStates.clear();
200✔
883
                            // we store pointer to be able to identify if this history state was already cleared or not
884
                            upatedHistory.push_back(&(itCurHistory->second.previousActiveStates));
200✔
885
                        }
886

887
                        if (HistoryType::SHALLOW == itCurHistory->second.type) {
340✔
888
                            if (std::find(itCurHistory->second.previousActiveStates.begin(),
160✔
889
                                          itCurHistory->second.previousActiveStates.end(),
160✔
890
                                          curState) == itCurHistory->second.previousActiveStates.end()) {
320✔
891
                                HSM_TRACE_DEBUG("SHALLOW -> store state <%s> in history of parent <%s>",
892
                                                getStateName(curState).c_str(),
893
                                                getStateName(it->second).c_str());
120✔
894
                                itCurHistory->second.previousActiveStates.push_back(curState);
120✔
895
                            }
896
                        } else if (HistoryType::DEEP == itCurHistory->second.type) {
180✔
897
                            if (std::find(itCurHistory->second.previousActiveStates.begin(),
180✔
898
                                          itCurHistory->second.previousActiveStates.end(),
180✔
899
                                          activeState) == itCurHistory->second.previousActiveStates.end()) {
360✔
900
                                HSM_TRACE_DEBUG("DEEP -> store state <%s> in history of parent <%s>",
901
                                                getStateName(activeState).c_str(),
902
                                                getStateName(it->second).c_str());
180✔
903
                                itCurHistory->second.previousActiveStates.push_back(activeState);
340✔
904
                            }
905
                        } else {
906
                            // NOTE: do nothing
907
                        }
908
                    }
909
                }
910
            }
911

912
            if (topLevelState != parentState) {
1,370✔
913
                curState = parentState;
820✔
914
            } else {
915
                break;
916
            }
917
        }
918
    }
919
}
2,080✔
920

921
bool HierarchicalStateMachine::Impl::checkTransitionPossibility(const StateID_t fromState,
60✔
922
                                                                const EventID_t event,
923
                                                                const VariantVector_t& args) {
924
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>", getEventName(event).c_str());
60✔
925

926
    StateID_t currentState = fromState;
60✔
927
    std::list<TransitionInfo> possibleTransitions;
60✔
928
    EventID_t nextEvent = INVALID_HSM_EVENT_ID;
60✔
929
    bool possible = true;
60✔
930

931
    {
60✔
932
        // NOTE: findTransitionTarget can be a bit heavy. possible optimization to reduce lock time is
933
        //       to make a copy of mPendingEvents and work with it
934
        HSM_SYNC_EVENTS_QUEUE();
60✔
935

936
        for (auto it = mPendingEvents.begin(); (it != mPendingEvents.end()) && (true == possible); ++it) {
60✔
937
            nextEvent = it->type;
×
938
            possible = findTransitionTarget(currentState, nextEvent, args, true, possibleTransitions);
×
939

940
            if (true == possible) {
×
941
                if (false == possibleTransitions.empty()) {
×
942
                    currentState = possibleTransitions.front().destinationState;
×
943
                } else {
944
                    possible = false;
945
                    break;
946
                }
947
            }
948
        }
949
    }
60✔
950

951
    if (true == possible) {
60✔
952
        nextEvent = event;
60✔
953
        possible = findTransitionTarget(currentState, nextEvent, args, true, possibleTransitions);
60✔
954
    }
955

956
    HSM_TRACE_CALL_RESULT("%d", BOOL2INT(possible));
60✔
957
    return possible;
60✔
958
}
60✔
959

960
bool HierarchicalStateMachine::Impl::findTransitionTarget(const StateID_t fromState,
2,860✔
961
                                                          const EventID_t event,
962
                                                          const VariantVector_t& transitionArgs,
963
                                                          const bool searchParents,
964
                                                          std::list<TransitionInfo>& outTransitions) {
965
    HSM_TRACE_CALL_DEBUG_ARGS("fromState=<%s>, event=<%s>", getStateName(fromState).c_str(), getEventName(event).c_str());
2,860✔
966
    bool continueSearch = false;
2,860✔
967
    StateID_t curState = fromState;
2,860✔
968

969
    do {
2,860✔
970
        const auto itRange = mTransitionsByEvent.equal_range(std::make_pair(curState, event));
2,860✔
971

972
        continueSearch = false;
2,860✔
973

974
        // if there are no matching transitions try to go one level up
975
        if (itRange.first == itRange.second) {
2,860✔
976
            if (true == searchParents) {
800✔
977
                continueSearch = getParentState(curState, curState);
40✔
978
            }
979

980
            continue;
800✔
981
        }
982

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

987
            if ((nullptr == it->second.checkCondition) ||
2,320✔
988
                (it->second.expectedConditionValue == it->second.checkCondition(transitionArgs))) {
140✔
989
                bool wasFound = false;
2,230✔
990
                std::list<StateID_t> parentStates = {it->second.destinationState};
2,230✔
991

992
                // cppcheck-suppress misra-c2012-15.4
993
                do {
2,650✔
994
                    StateID_t currentParent = parentStates.front();
2,650✔
995

996
                    parentStates.pop_front();
2,650✔
997

998
                    // if state has substates we must check if transition into them is possible (after cond)
999
                    if (true == hasSubstates(currentParent)) {
2,650✔
1000
                        if (true == hasEntryPoint(currentParent)) {
470✔
1001
                            HSM_TRACE_DEBUG("state <%s> has entrypoints", getStateName(currentParent).c_str());
440✔
1002
                            std::list<StateID_t> entryPoints;
440✔
1003

1004
                            if (true == getEntryPoints(currentParent, event, transitionArgs, entryPoints)) {
440✔
1005
                                parentStates.splice(parentStates.end(), entryPoints);
840✔
1006
                            } else {
1007
                                HSM_TRACE_WARNING("no matching entrypoints found");
1008
                                break;
1009
                            }
1010
                        } else {
20✔
1011
                            HSM_TRACE_WARNING("state <%s> doesn't have an entrypoint defined",
1012
                                              getStateName(currentParent).c_str());
1013
                            break;
1014
                        }
1015
                    } else {
1016
                        outTransitions.push_back(it->second);
2,230✔
1017
                        wasFound = true;
1018
                    }
1019
                } while ((false == wasFound) && (parentStates.empty() == false));
420✔
1020
            }
2,230✔
1021
        }
1022
    } while (true == continueSearch);
800✔
1023

1024
    HSM_TRACE_CALL_RESULT("%s", BOOL2STR(outTransitions.empty() == false));
2,860✔
1025
    return (outTransitions.empty() == false);
2,860✔
1026
}
1027

1028
HsmEventStatus HierarchicalStateMachine::Impl::doTransition(const PendingEventInfo& event) {
3,010✔
1029
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>, transitionType=%d", getEventName(event.type).c_str(), SC2INT(event.transitionType));
3,010✔
1030
    HsmEventStatus res = HsmEventStatus::DONE_FAILED;
3,010✔
1031
    auto activeStatesSnapshot = mActiveStates;
3,010✔
1032
    std::list<StateID_t> acceptedStates;  // list of states that accepted transitions
3,010✔
1033

1034
    for (auto it = activeStatesSnapshot.rbegin(); it != activeStatesSnapshot.rend(); ++it) {
7,297✔
1035
        // in case of parallel transitions some states might become inactive after handleSingleTransition()
1036
        // example: [*B, *C] -> D
1037
        if (true == isStateActive(*it)) {
4,563✔
1038
            // we don't need to process transitions for active states if their child already processed it
1039
            bool childStateProcessed = false;
4,563✔
1040

1041
            for (const auto& state : acceptedStates) {
4,633✔
1042
                if (true == isSubstateOf(*it, state)) {
863✔
1043
                    childStateProcessed = true;
1044
                    break;
1045
                }
1046
            }
1047

1048
            if (false == childStateProcessed) {
4,563✔
1049
                const HsmEventStatus singleTransitionResult = handleSingleTransition(*it, event);
3,770✔
1050

1051
                switch (singleTransitionResult) {
3,770✔
1052
                    case HsmEventStatus::PENDING:
700✔
1053
                        res = singleTransitionResult;
700✔
1054
                        acceptedStates.push_back(*it);
700✔
1055
                        break;
1056
                    case HsmEventStatus::DONE_OK:
2,170✔
1057
                        logHsmAction(HsmLogAction::IDLE,
2,170✔
1058
                                     INVALID_HSM_STATE_ID,
1059
                                     INVALID_HSM_STATE_ID,
1060
                                     INVALID_HSM_EVENT_ID,
1061
                                     false,
1062
                                     VariantVector_t());
2,170✔
1063
                        if (HsmEventStatus::PENDING != res) {
2,170✔
1064
                            res = singleTransitionResult;
2,170✔
1065
                        }
1066
                        acceptedStates.push_back(*it);
2,170✔
1067
                        break;
1068
                    case HsmEventStatus::CANCELED:
1069
                    case HsmEventStatus::DONE_FAILED:
1070
                    default:
1071
                        // do nothing
1072
                        break;
1073
                }
1074
            }
1075
        }
1076

1077
        // stop processing other events if HSM was released
1078
        if (true == mDispatcher.expired()) {
4,563✔
1079
            res = HsmEventStatus::DONE_FAILED;
1080
            break;
1081
        }
1082
    }
1083

1084
    if (mFailedTransitionCallback && ((HsmEventStatus::DONE_FAILED == res) || (HsmEventStatus::CANCELED == res))) {
3,010✔
1085
        mFailedTransitionCallback(event.type, event.args);
120✔
1086
    }
1087

1088
    HSM_TRACE_CALL_RESULT("%d", SC2INT(res));
3,010✔
1089
    return res;
3,010✔
1090
}
6,020✔
1091

1092
HsmEventStatus HierarchicalStateMachine::Impl::processExternalTransition(const PendingEventInfo& event,
3,150✔
1093
                                                                         const StateID_t fromState,
1094
                                                                         const TransitionInfo& curTransition,
1095
                                                                         const std::list<StateID_t>& exitedStates) {
1096
    HSM_TRACE_CALL_DEBUG();
3,150✔
1097
    HsmEventStatus res = HsmEventStatus::DONE_FAILED;
3,150✔
1098

1099
    // NOTE: Decide if we need functionality to cancel ongoing transition
1100
    logHsmAction(((TransitionBehavior::ENTRYPOINT != event.transitionType) ? HsmLogAction::TRANSITION
3,150✔
1101
                                                                           : HsmLogAction::TRANSITION_ENTRYPOINT),
1102
                 curTransition.fromState,
3,150✔
1103
                 curTransition.destinationState,
3,150✔
1104
                 event.type,
3,150✔
1105
                 false,
1106
                 event.args);
3,150✔
1107

1108
    // cppcheck-suppress misra-c2012-14.4 : false-positive. std::shared_ptr has a bool() operator
1109
    if (curTransition.onTransition) {
3,150✔
1110
        curTransition.onTransition(event.args);
260✔
1111
    }
1112

1113
    if (true == onStateEntering(curTransition.destinationState, event.args)) {
3,150✔
1114
        if (true == replaceActiveState(fromState, curTransition.destinationState)) {
3,130✔
1115
            onStateChanged(curTransition.destinationState, event.args);
3,080✔
1116
        }
1117

1118
        // check if current state is a final state
1119
        if (true == processFinalStateTransition(event, curTransition.destinationState)) {
3,130✔
1120
            res = HsmEventStatus::DONE_OK;
1121
        }
1122
        // check if we transitioned into history state
1123
        else if (true == processHistoryTransition(event, curTransition.destinationState)) {
2,880✔
1124
            res = HsmEventStatus::PENDING;
1125
        } else {
1126
            // check if new state has substates and initiate entry transition
1127
            if (false == event.ignoreEntryPoints) {
2,780✔
1128
                std::list<StateID_t> entryPoints;
2,630✔
1129

1130
                if (true == getEntryPoints(curTransition.destinationState, event.type, event.args, entryPoints)) {
2,630✔
1131
                    HSM_TRACE_DEBUG("state <%s> has substates with %d entry points (first: <%s>)",
1132
                                    getStateName(curTransition.destinationState).c_str(),
1133
                                    SC2INT(entryPoints.size()),
1134
                                    getStateName(entryPoints.front()).c_str());
450✔
1135
                    PendingEventInfo entryPointTransitionEvent = event;
450✔
1136

1137
                    entryPointTransitionEvent.transitionType = TransitionBehavior::ENTRYPOINT;
450✔
1138

1139
                    {
450✔
1140
                        HSM_SYNC_EVENTS_QUEUE();
450✔
1141
                        mPendingEvents.push_front(entryPointTransitionEvent);
450✔
1142
                    }
450✔
1143
                    res = HsmEventStatus::PENDING;
450✔
1144
                } else {
450✔
1145
                    res = HsmEventStatus::DONE_OK;
1146
                }
1147
            } else {
2,630✔
1148
                HSM_TRACE_DEBUG("entry points were forcefully ignored (probably due to history transition)");
1149
                res = HsmEventStatus::PENDING;
1150
            }
1151
        }
1152
    } else {
1153
        for (const StateID_t& curState : exitedStates) {
40✔
1154
            // to prevent infinite loops we don't allow state to cancel transition
1155
            (void)onStateEntering(curState, VariantVector_t());
20✔
1156
            (void)addActiveState(curState);
20✔
1157
            onStateChanged(curState, VariantVector_t());
20✔
1158
        }
1159
    }
1160

1161
    return res;
3,150✔
1162
}
1163

1164
bool HierarchicalStateMachine::Impl::determineTargetState(const PendingEventInfo& event,
3,770✔
1165
                                                          const StateID_t fromState,
1166
                                                          std::list<TransitionInfo>& outMatchingTransitions) {
1167
    HSM_TRACE_CALL_DEBUG();
3,770✔
1168
    bool isCorrectTransition = false;
3,770✔
1169

1170
    if (TransitionBehavior::REGULAR == event.transitionType) {
3,770✔
1171
        isCorrectTransition = findTransitionTarget(fromState, event.type, event.args, false, outMatchingTransitions);
2,800✔
1172

1173
        if (false == isCorrectTransition) {
2,800✔
1174
            HSM_TRACE_WARNING("no suitable transition from state <%s> with event <%s>",
1175
                              getStateName(fromState).c_str(),
1176
                              getEventName(event.type).c_str());
1177
        }
1178
    } else if (TransitionBehavior::ENTRYPOINT == event.transitionType) {
970✔
1179
        isCorrectTransition = true;
720✔
1180

1181
        // if fromState doesnt have active children
1182
        for (auto it = mActiveStates.rbegin(); it != mActiveStates.rend(); ++it) {
1,760✔
1183
            if (fromState != *it) {
1,040✔
1184
                StateID_t activeParent = INVALID_HSM_STATE_ID;
320✔
1185

1186
                if (true == getParentState(*it, activeParent)) {
320✔
1187
                    if (activeParent == fromState) {
80✔
1188
                        // no need to handle entry transition for already active state
1189
                        isCorrectTransition = false;
×
1190
                        break;
×
1191
                    }
1192
                }
1193
            }
1194
        }
1195

1196
        if (true == isCorrectTransition) {
×
1197
            std::list<StateID_t> entryStates;
720✔
1198

1199
            isCorrectTransition = getEntryPoints(fromState, event.type, event.args, entryStates);
720✔
1200

1201
            if (true == isCorrectTransition) {
720✔
1202
                for (const auto& curEntryState : entryStates) {
1,470✔
1203
                    (void)outMatchingTransitions.emplace_back(fromState,
1,560✔
1204
                                                              curEntryState,
1205
                                                              TransitionType::EXTERNAL_TRANSITION,
1,560✔
1206
                                                              nullptr,
1,560✔
1207
                                                              nullptr);
1,560✔
1208
                }
1209
            } else {
1210
                HSM_TRACE_WARNING("state <%s> doesn't have a suitable entry point (event <%s>)",
1211
                                  getStateName(fromState).c_str(),
1212
                                  getEventName(event.type).c_str());
720✔
1213
            }
1214
        }
720✔
1215
    } else if (TransitionBehavior::FORCED == event.transitionType) {
250✔
1216
        HSM_TRACE_DEBUG("forced history transitions: %d", SC2INT(event.forcedTransitionsInfo->size()));
250✔
1217
        // cppcheck-suppress misra-c2012-17.8 ; outMatchingTransitions is used to return result
1218
        outMatchingTransitions = *event.forcedTransitionsInfo;
250✔
1219
        isCorrectTransition = true;
1220
    } else {
1221
        // NOTE: do nothing
1222
    }
1223

1224
    return isCorrectTransition;
3,770✔
1225
}
1226

1227
bool HierarchicalStateMachine::Impl::executeSelfTransitions(const PendingEventInfo& event,
2,910✔
1228
                                                            const std::list<TransitionInfo>& matchingTransitions) {
1229
    bool hadSelfTransitions = false;
2,910✔
1230

1231
    // execute self transitions first
1232
    for (const auto& curTransition : matchingTransitions) {
6,140✔
1233
        if ((curTransition.fromState == curTransition.destinationState) &&
3,230✔
1234
            (TransitionType::INTERNAL_TRANSITION == curTransition.transitionType)) {
90✔
1235
            // TODO: separate type for self transition?
1236
            logHsmAction(HsmLogAction::TRANSITION,
60✔
1237
                         curTransition.fromState,
1238
                         curTransition.destinationState,
1239
                         event.type,
60✔
1240
                         false,
1241
                         event.args);
60✔
1242

1243
            // NOTE: false-positive. std::function has a bool() operator
1244
            // cppcheck-suppress misra-c2012-14.4
1245
            if (curTransition.onTransition) {
60✔
1246
                curTransition.onTransition(event.args);
3,230✔
1247
            }
1248

1249
            hadSelfTransitions = true;
1250
        }
1251
    }
1252

1253
    return hadSelfTransitions;
2,910✔
1254
}
1255

1256
bool HierarchicalStateMachine::Impl::executeExitTransition(const PendingEventInfo& event,
2,910✔
1257
                                                           const std::list<TransitionInfo>& matchingTransitions,
1258
                                                           std::list<StateID_t>& outExitedStates) {
1259
    HSM_TRACE_CALL_DEBUG();
2,910✔
1260
    bool isExitAllowed = true;
2,910✔
1261

1262
    for (const auto& curTransition : matchingTransitions) {
6,140✔
1263
        if (  // process everything except internal self-transitions
3,230✔
1264
            ((curTransition.fromState != curTransition.destinationState) ||
3,230✔
1265
             (TransitionType::EXTERNAL_TRANSITION == curTransition.transitionType)) &&
90✔
1266
            // exit active states only during regular transitions
1267
            (TransitionBehavior::REGULAR == event.transitionType)) {
3,170✔
1268
            // it's an outer transition from parent state. we need to find and exit all active substates
1269
            for (auto itActiveState = mActiveStates.rbegin(); itActiveState != mActiveStates.rend(); ++itActiveState) {
5,370✔
1270
                HSM_TRACE_DEBUG("OUTER EXIT: FROM=%s, ACTIVE=%s",
1271
                                getStateName(curTransition.fromState).c_str(),
1272
                                getStateName(*itActiveState).c_str());
3,290✔
1273
                if ((curTransition.fromState == *itActiveState) ||
4,650✔
1274
                    (true == isSubstateOf(curTransition.fromState, *itActiveState))) {
1,360✔
1275
                    isExitAllowed = onStateExiting(*itActiveState);
2,490✔
1276

1277
                    if (true == isExitAllowed) {
2,490✔
1278
                        outExitedStates.push_back(*itActiveState);
3,270✔
1279
                    } else {
1280
                        break;
1281
                    }
1282
                }
1283
            }
1284

1285
            // if no one blocked ongoing transition - remove child states from active list
1286
            if (true == isExitAllowed) {
2,100✔
1287
                // store history for states between "fromState" ----> "it->fromState"
1288
                updateHistory(curTransition.fromState, outExitedStates);
2,080✔
1289

1290
                for (const auto& curState : outExitedStates) {
4,710✔
1291
                    mActiveStates.remove(curState);
2,630✔
1292
                }
1293
            }
1294
            // if one of the states blocked ongoing transition we need to rollback
1295
            else {
1296
                for (const auto& curState : outExitedStates) {
30✔
1297
                    mActiveStates.remove(curState);
10✔
1298
                    // to prevent infinite loops we don't allow state to cancel transition
1299
                    (void)onStateEntering(curState, VariantVector_t());
10✔
1300
                    mActiveStates.push_back(curState);
10✔
1301
                    onStateChanged(curState, VariantVector_t());
10✔
1302
                }
1303
            }
1304
        }
1305
    }
1306

1307
    return isExitAllowed;
2,910✔
1308
}
1309

1310
bool HierarchicalStateMachine::Impl::processHistoryTransition(const PendingEventInfo& event, const StateID_t destinationState) {
2,880✔
1311
    HSM_TRACE_CALL_DEBUG();
2,880✔
1312
    auto itHistoryData = mHistoryData.find(destinationState);
2,880✔
1313

1314
    // check if we transitioned into a history state
1315
    if (itHistoryData != mHistoryData.end()) {
2,880✔
1316
        HSM_TRACE_DEBUG("state=<%s> is a history state with %ld stored states",
1317
                        getStateName(destinationState).c_str(),
1318
                        itHistoryData->second.previousActiveStates.size());
100✔
1319

1320
        if (itHistoryData->second.previousActiveStates.empty() == false) {
100✔
1321
            transitionToPreviousActiveStates(itHistoryData->second.previousActiveStates, event, destinationState);
60✔
1322
        } else {
1323
            transitionToDefaultHistoryState(itHistoryData->second.defaultTarget,
40✔
1324
                                            itHistoryData->second.defaultTargetTransitionCallback,
40✔
1325
                                            event,
1326
                                            destinationState);
1327
        }
1328
    }
1329

1330
    return (itHistoryData != mHistoryData.end());
2,880✔
1331
}
1332

1333
void HierarchicalStateMachine::Impl::transitionToPreviousActiveStates(std::list<StateID_t>& previousActiveStates,
60✔
1334
                                                                      const PendingEventInfo& event,
1335
                                                                      const StateID_t destinationState) {
1336
    StateID_t prevChildState = INVALID_HSM_STATE_ID;
60✔
1337
    PendingEventInfo historyTransitionEvent = event;
60✔
1338

1339
    historyTransitionEvent.transitionType = TransitionBehavior::FORCED;
60✔
1340
    historyTransitionEvent.forcedTransitionsInfo = std::make_shared<std::list<TransitionInfo>>();
120✔
1341

1342
    {
60✔
1343
        HSM_SYNC_EVENTS_QUEUE();
60✔
1344

1345
        for (const StateID_t prevState : previousActiveStates) {
220✔
1346
            if ((INVALID_HSM_STATE_ID != prevChildState) && (true == isSubstateOf(prevState, prevChildState))) {
160✔
1347
                if (false == historyTransitionEvent.forcedTransitionsInfo->empty()) {
60✔
1348
                    mPendingEvents.push_front(historyTransitionEvent);
60✔
1349
                }
1350

1351
                historyTransitionEvent.forcedTransitionsInfo = std::make_shared<std::list<TransitionInfo>>();
120✔
1352
                historyTransitionEvent.ignoreEntryPoints = true;
60✔
1353
            } else {
1354
                historyTransitionEvent.ignoreEntryPoints = false;
100✔
1355
            }
1356

1357
            prevChildState = prevState;
160✔
1358
            historyTransitionEvent.forcedTransitionsInfo->emplace_back(destinationState,
160✔
1359
                                                                       prevState,
1360
                                                                       TransitionType::EXTERNAL_TRANSITION,
320✔
1361
                                                                       nullptr,
320✔
1362
                                                                       nullptr);
320✔
1363
        }
1364

1365
        mPendingEvents.push_front(historyTransitionEvent);
60✔
1366
    }
60✔
1367

1368
    previousActiveStates.clear();
60✔
1369

1370
    StateID_t historyParent = INVALID_HSM_STATE_ID;
60✔
1371

1372
    if (true == getHistoryParent(destinationState, historyParent)) {
60✔
1373
        historyTransitionEvent.forcedTransitionsInfo = std::make_shared<std::list<TransitionInfo>>();
120✔
1374
        historyTransitionEvent.forcedTransitionsInfo->emplace_back(destinationState,
60✔
1375
                                                                   historyParent,
1376
                                                                   TransitionType::EXTERNAL_TRANSITION,
120✔
1377
                                                                   nullptr,
120✔
1378
                                                                   nullptr);
60✔
1379
        historyTransitionEvent.ignoreEntryPoints = true;
60✔
1380

1381
        HSM_SYNC_EVENTS_QUEUE();
60✔
1382
        mPendingEvents.push_front(historyTransitionEvent);
60✔
1383
    }
60✔
1384
}
60✔
1385

1386
void HierarchicalStateMachine::Impl::transitionToDefaultHistoryState(
40✔
1387
    const StateID_t defaultTarget,
1388
    const HsmTransitionCallback_t& defaultTargetTransitionCallback,
1389
    const PendingEventInfo& event,
1390
    const StateID_t destinationState) {
1391
    HSM_TRACE_CALL_DEBUG();
40✔
1392
    std::list<StateID_t> historyTargets;
40✔
1393
    StateID_t historyParent = INVALID_HSM_STATE_ID;
40✔
1394

1395
    if (true == getHistoryParent(destinationState, historyParent)) {
40✔
1396
        HSM_TRACE_DEBUG("found parent=<%s> for history state=<%s>",
1397
                        getStateName(historyParent).c_str(),
1398
                        getStateName(destinationState).c_str());
40✔
1399

1400
        if (INVALID_HSM_STATE_ID == defaultTarget) {
40✔
1401
            // transition to parent's entry point if there is no default history target
1402
            historyTargets.push_back(historyParent);
10✔
1403
        } else {
1404
            historyTargets.push_back(defaultTarget);
30✔
1405
            historyTargets.push_back(historyParent);
30✔
1406
        }
1407
    } else {
1408
        HSM_TRACE_ERROR("parent for history state=<%s> wasnt found", getStateName(destinationState).c_str());
40✔
1409
    }
1410

1411
    PendingEventInfo defHistoryTransitionEvent = event;
40✔
1412

1413
    defHistoryTransitionEvent.transitionType = TransitionBehavior::FORCED;
40✔
1414

1415
    for (const StateID_t historyTargetState : historyTargets) {
110✔
1416
        HsmTransitionCallback_t cbTransition;
70✔
1417

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

1420
        if ((INVALID_HSM_STATE_ID != defaultTarget) && (historyTargetState == historyParent)) {
70✔
1421
            defHistoryTransitionEvent.ignoreEntryPoints = true;
30✔
1422
        } else {
1423
            cbTransition = defaultTargetTransitionCallback;
40✔
1424
        }
1425

1426
        defHistoryTransitionEvent.forcedTransitionsInfo->emplace_back(destinationState,
70✔
1427
                                                                      historyTargetState,
1428
                                                                      TransitionType::EXTERNAL_TRANSITION,
140✔
1429
                                                                      cbTransition,
1430
                                                                      nullptr);
70✔
1431

1432
        mPendingEvents.push_front(defHistoryTransitionEvent);
70✔
1433
    }
70✔
1434
}
80✔
1435

1436
bool HierarchicalStateMachine::Impl::processFinalStateTransition(const PendingEventInfo& event,
3,130✔
1437
                                                                 const StateID_t destinationState) {
1438
    const auto itFinalStateEvent = mFinalStates.find(destinationState);
3,130✔
1439

1440
    if (itFinalStateEvent != mFinalStates.end()) {
3,130✔
1441
        StateID_t parentState = INVALID_HSM_STATE_ID;
250✔
1442

1443
        // don't generate events for top level final states since no one can process them
1444
        if (true == getParentState(destinationState, parentState)) {
250✔
1445
            // check if there are any other active siblings in this parent state
1446
            // only generate final state event when all siblings got deactivated
1447
            if (false == hasActiveChildren(parentState, false)) {
240✔
1448
                PendingEventInfo finalStateEvent;
190✔
1449

1450
                finalStateEvent.transitionType = TransitionBehavior::REGULAR;
190✔
1451
                finalStateEvent.args = event.args;
190✔
1452

1453
                if (INVALID_HSM_EVENT_ID != itFinalStateEvent->second) {
190✔
1454
                    finalStateEvent.type = itFinalStateEvent->second;
110✔
1455
                } else {
1456
                    finalStateEvent.type = event.type;
80✔
1457
                }
1458

1459
                {
190✔
1460
                    HSM_SYNC_EVENTS_QUEUE();
190✔
1461
                    mPendingEvents.push_front(finalStateEvent);
190✔
1462
                }
190✔
1463
            }
190✔
1464
        }
1465
    }
1466

1467
    return (itFinalStateEvent != mFinalStates.end());
3,130✔
1468
}
1469

1470
HsmEventStatus HierarchicalStateMachine::Impl::handleSingleTransition(const StateID_t fromState,
3,770✔
1471
                                                                      const PendingEventInfo& event) {
1472
    HSM_TRACE_CALL_DEBUG_ARGS("fromState=<%s>, event=<%s>, transitionType=%d",
1473
                              getStateName(fromState).c_str(),
1474
                              getEventName(event.type).c_str(),
1475
                              SC2INT(event.transitionType));
3,770✔
1476
    HsmEventStatus res = HsmEventStatus::DONE_FAILED;
3,770✔
1477
    bool isCorrectTransition = false;
3,770✔
1478
    std::list<TransitionInfo> matchingTransitions;
3,770✔
1479

1480
    DEBUG_DUMP_ACTIVE_STATES();
3,770✔
1481

1482
    // determine target state based on current transition
1483
    isCorrectTransition = determineTargetState(event, fromState, matchingTransitions);
3,770✔
1484

1485
    // handle transition if it passed validation and has a target state
1486
    if (true == isCorrectTransition) {
3,770✔
1487
        bool isExitAllowed = true;
2,910✔
1488
        std::list<StateID_t> exitedStates;
2,910✔
1489

1490
        // execute self transitions first
1491
        if (true == executeSelfTransitions(event, matchingTransitions)) {
2,910✔
1492
            res = HsmEventStatus::DONE_OK;
50✔
1493
        }
1494

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

1498
        // proceed if transition was not blocked during state exit
1499
        if (true == isExitAllowed) {
2,910✔
1500
            for (const TransitionInfo& curTransition : matchingTransitions) {
6,100✔
1501
                // everything except internal self-transitions
1502
                if ((curTransition.fromState != curTransition.destinationState) ||
3,210✔
1503
                    (TransitionType::EXTERNAL_TRANSITION == curTransition.transitionType)) {
90✔
1504
                    res = processExternalTransition(event, fromState, curTransition, exitedStates);
3,150✔
1505
                }
1506
            }
1507
        } else {
1508
            res = HsmEventStatus::CANCELED;
1509
        }
1510
    }
2,910✔
1511

1512
    if (HsmEventStatus::DONE_FAILED == res) {
3,770✔
1513
        HSM_TRACE_DEBUG("event <%s> in state <%s> was ignored.",
1514
                        getEventName(event.type).c_str(),
1515
                        getStateName(fromState).c_str());
1516
    }
1517

1518
    DEBUG_DUMP_ACTIVE_STATES();
3,770✔
1519
    HSM_TRACE_CALL_RESULT("%d", SC2INT(res));
3,770✔
1520
    return res;
3,770✔
1521
}
3,770✔
1522

1523
void HierarchicalStateMachine::Impl::clearPendingEvents() {
10✔
1524
    HSM_TRACE_CALL_DEBUG_ARGS("clearPendingEvents: mPendingEvents.size()=%ld", mPendingEvents.size());
10✔
1525

1526
    for (auto it = mPendingEvents.begin(); (it != mPendingEvents.end()); ++it) {
10✔
1527
        // since ongoing transitions can't be canceled we need to treat entry point transitions as atomic
1528
        if (TransitionBehavior::REGULAR == it->transitionType) {
×
1529
            it->releaseLock();
×
1530
        }
1531
    }
1532

1533
    mPendingEvents.clear();
10✔
1534
}
10✔
1535

1536
bool HierarchicalStateMachine::Impl::hasSubstates(const StateID_t parent) const {
2,650✔
1537
    return (mSubstates.find(parent) != mSubstates.end());
2,650✔
1538
}
1539

1540
bool HierarchicalStateMachine::Impl::hasEntryPoint(const StateID_t state) const {
470✔
1541
    return (mSubstateEntryPoints.find(state) != mSubstateEntryPoints.end());
470✔
1542
}
1543

1544
bool HierarchicalStateMachine::Impl::getEntryPoints(const StateID_t state,
4,980✔
1545
                                                    const EventID_t onEvent,
1546
                                                    const VariantVector_t& transitionArgs,
1547
                                                    std::list<StateID_t>& outEntryPoints) const {
1548
    auto itRange = mSubstateEntryPoints.equal_range(state);
4,980✔
1549

1550
    outEntryPoints.clear();
4,980✔
1551

1552
    for (auto it = itRange.first; it != itRange.second; ++it) {
7,240✔
1553
        if (((INVALID_HSM_EVENT_ID == it->second.onEvent) || (onEvent == it->second.onEvent)) &&
2,260✔
1554
            // check transition condition if it was defined
1555
            ((nullptr == it->second.checkCondition) ||
2,200✔
1556
             (it->second.checkCondition(transitionArgs) == it->second.expectedConditionValue))) {
120✔
1557
            outEntryPoints.push_back(it->second.state);
2,260✔
1558
        }
1559
    }
1560

1561
    return (false == outEntryPoints.empty());
4,980✔
1562
}
1563

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

1567
    if (false == isSubstateOf(oldState, newState)) {
3,130✔
1568
        mActiveStates.remove(oldState);
2,160✔
1569
    }
1570

1571
    return addActiveState(newState);
3,130✔
1572
}
1573

1574
bool HierarchicalStateMachine::Impl::addActiveState(const StateID_t newState) {
3,150✔
1575
    HSM_TRACE_CALL_DEBUG_ARGS("newState=<%s>", getStateName(newState).c_str());
3,150✔
1576
    bool wasAdded = false;
3,150✔
1577

1578
    if (false == isStateActive(newState)) {
3,150✔
1579
        mActiveStates.push_back(newState);
3,100✔
1580
        wasAdded = true;
3,100✔
1581
    }
1582

1583
    HSM_TRACE_DEBUG("mActiveStates.size=%d", SC2INT(mActiveStates.size()));
3,150✔
1584
    return wasAdded;
3,150✔
1585
}
1586

1587
#ifdef HSM_ENABLE_SAFE_STRUCTURE
1588

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

1592
    return (it == mTopLevelStates.end());
5,670✔
1593
}
1594

1595
bool HierarchicalStateMachine::Impl::isSubstate(const StateID_t state) const {
4,000✔
1596
    bool result = false;
4,000✔
1597

1598
    for (const auto& curSubstate : mSubstates) {
4,000✔
1599
        if (curSubstate.second == state) {
×
1600
            result = true;
1601
            break;
1602
        }
1603
    }
1604

1605
    return result;
4,000✔
1606
}
1607

1608
bool HierarchicalStateMachine::Impl::hasParentState(const StateID_t state, StateID_t& outParent) const {
3,850✔
1609
    bool hasParent = false;
3,850✔
1610

1611
    for (const auto& curSubstate : mSubstates) {
8,110✔
1612
        if (state == curSubstate.second) {
4,740✔
1613
            hasParent = true;
480✔
1614
            outParent = curSubstate.first;  // cppcheck-suppress misra-c2012-17.8 ; outParent is used to return result
480✔
1615
            break;
480✔
1616
        }
1617
    }
1618

1619
    return hasParent;
3,850✔
1620
}
1621

1622
#endif  // HSM_ENABLE_SAFE_STRUCTURE
1623

1624
bool HierarchicalStateMachine::Impl::enableHsmDebugging() {
×
1625
#ifdef HSMBUILD_DEBUGGING
1626
    constexpr const char* DEFAULT_DUMP_PATH = "./dump.hsmlog";
×
1627
    constexpr const char* ENV_DUMPPATH = "HSMCPP_DUMP_PATH";
×
1628
    // NOLINTNEXTLINE(concurrency-mt-unsafe): enableHsmDebugging() doesn't need to be thread-safe
1629
    char* envPath = std::getenv(ENV_DUMPPATH);
×
1630

1631
    return enableHsmDebugging((nullptr == envPath) ? DEFAULT_DUMP_PATH : std::string(envPath));
×
1632
#else
1633
    return true;
1634
#endif
1635
}
1636

1637
bool HierarchicalStateMachine::Impl::enableHsmDebugging(const std::string& dumpPath) {
×
1638
#ifdef HSMBUILD_DEBUGGING
1639
    bool res = false;
×
1640
    bool isNewLog = (access(dumpPath.c_str(), F_OK) != 0);
×
1641

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

1645
        if (true == isNewLog) {
×
1646
            *mHsmLog << "---\n";
×
1647
            mHsmLog->flush();
×
1648
        }
1649

1650
        res = true;
1651
    }
1652

1653
    return res;
×
1654
#else
1655
    return true;
1656
#endif
1657
}
1658

1659
void HierarchicalStateMachine::Impl::disableHsmDebugging() {
2,370✔
1660
#ifdef HSMBUILD_DEBUGGING
1661
    mHsmLogFile.close();
2,370✔
1662
#endif
1663
}
2,370✔
1664

1665
void HierarchicalStateMachine::Impl::logHsmAction(const HsmLogAction action,
10,180✔
1666
                                                  const StateID_t fromState,
1667
                                                  const StateID_t targetState,
1668
                                                  const EventID_t event,
1669
                                                  const bool hasFailed,
1670
                                                  const VariantVector_t& args) {
1671
#ifdef HSMBUILD_DEBUGGING
1672
    if (true == mHsmLogFile.is_open()) {
10,180✔
1673
        static const std::map<HsmLogAction, std::string> actionsMap = {
×
1674
            std::make_pair(HsmLogAction::IDLE, "idle"),
×
1675
            std::make_pair(HsmLogAction::TRANSITION, "transition"),
×
1676
            std::make_pair(HsmLogAction::TRANSITION_ENTRYPOINT, "transition_entrypoint"),
×
1677
            std::make_pair(HsmLogAction::CALLBACK_EXIT, "callback_exit"),
×
1678
            std::make_pair(HsmLogAction::CALLBACK_ENTER, "callback_enter"),
×
1679
            std::make_pair(HsmLogAction::CALLBACK_STATE, "callback_state"),
×
1680
            std::make_pair(HsmLogAction::ON_ENTER_ACTIONS, "onenter_actions"),
×
1681
            std::make_pair(HsmLogAction::ON_EXIT_ACTIONS, "onexit_actions")};
×
1682
        constexpr size_t bufTimeSize = 80;
×
1683
        constexpr size_t bufTimeMsSize = 6;
×
1684
        std::array<char, bufTimeSize> bufTime = {0};
×
1685
        std::array<char, bufTimeMsSize> bufTimeMs = {0};
×
1686
        auto currentTimePoint = std::chrono::system_clock::now();
×
1687
        const std::time_t tt = std::chrono::system_clock::to_time_t(currentTimePoint);
×
1688
        std::tm timeinfo = {0};
×
1689
        const std::tm* tmResult = nullptr;  // this is just to check that localtime was executed correctly
×
1690

1691
  #ifdef WIN32
1692
        if (0 == ::localtime_s(&timeinfo, &tt)) {
1693
            tmResult = &timeinfo;
1694
        }
1695
  #else
1696
        // TODO: function is not thread safe [concurrency-mt-unsafe]
1697
        tmResult = localtime(&tt);
×
1698
        if (nullptr != tmResult) {
×
1699
            timeinfo = *tmResult;
×
1700
        }
1701
  #endif  // WIN32
1702

1703
        if (nullptr != tmResult) {
×
1704
            constexpr int millisecondsPerSecond = 1000;
×
1705

1706
            (void)std::strftime(bufTime.data(), bufTime.size(), "%Y-%m-%d %H:%M:%S", &timeinfo);
×
1707
            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg): using snprintf instead stringstream for performance reasons
1708
            (void)snprintf(
×
1709
                bufTimeMs.data(),
1710
                bufTimeMs.size(),
1711
                ".%03d",
1712
                static_cast<int>(
1713
                    std::chrono::duration_cast<std::chrono::milliseconds>(currentTimePoint.time_since_epoch()).count() %
×
1714
                    millisecondsPerSecond));
1715
        } else {
1716
            (void)std::strncpy(bufTime.data(), "0000-00-00 00:00:00", bufTime.size() - 1U);
×
1717
            (void)std::strncpy(bufTimeMs.data(), ".000", bufTimeMs.size() - 1U);
×
1718
        }
1719

1720
        *mHsmLog << "\n-\n"
×
1721
                    "  timestamp: \""
1722
                 << bufTime.data() << bufTimeMs.data()
×
1723
                 << "\"\n"
1724
                    "  active_states:";
×
1725

1726
        for (const auto& curState : mActiveStates) {
×
1727
            *mHsmLog << "\n    - \"" << getStateName(curState) << "\"";
×
1728
        }
1729

1730
        *mHsmLog << "\n  action: " << actionsMap.at(action)
×
1731
                 << "\n"
1732
                    "  from_state: \""
1733
                 << getStateName(fromState)
×
1734
                 << "\"\n"
1735
                    "  target_state: \""
1736
                 << getStateName(targetState)
×
1737
                 << "\"\n"
1738
                    "  event: \""
1739
                 << getEventName(event)
×
1740
                 << "\"\n"
1741
                    "  status: "
1742
                 << (hasFailed ? "failed" : "")
1743
                 << "\n"
1744
                    "  args:";
×
1745

1746
        for (const auto& curArg : args) {
×
1747
            *mHsmLog << "\n    - " << curArg.toString();
×
1748
        }
1749

1750
        mHsmLog->flush();
×
1751
    }
1752
#endif  // HSMBUILD_DEBUGGING
1753
}
10,180✔
1754

1755
#ifndef HSM_DISABLE_DEBUG_TRACES
1756
void HierarchicalStateMachine::Impl::dumpActiveStates() {
7,540✔
1757
    HSM_TRACE_CALL();
7,540✔
1758

1759
    std::string temp;
7,540✔
1760

1761
    for (const auto& curState : mActiveStates) {
21,880✔
1762
        temp += getStateName(curState) + std::string(", ");
28,680✔
1763
    }
1764

1765
    HSM_TRACE_DEBUG("active states: <%s>", temp.c_str());
7,540✔
1766
}
7,540✔
1767

1768
std::string HierarchicalStateMachine::Impl::getStateName(const StateID_t state) {
14,340✔
1769
    std::string res;
14,340✔
1770
  #ifndef HSM_DISABLE_THREADSAFETY
1771
    LockGuard lk(mParentSync);
14,340✔
1772
  #endif
1773

1774
    if (nullptr != mParent) {
14,340✔
1775
        res = mParent->getStateName(state);
14,340✔
1776
    }
1777

1778
    return res;
14,340✔
1779
}
14,340✔
1780

1781
std::string HierarchicalStateMachine::Impl::getEventName(const EventID_t event) {
×
1782
    std::string res;
×
1783
  #ifndef HSM_DISABLE_THREADSAFETY
1784
    LockGuard lk(mParentSync);
×
1785
  #endif
1786

1787
    if (nullptr != mParent) {
×
1788
        res = mParent->getEventName(event);
×
1789
    }
1790

1791
    return res;
×
1792
}
×
1793
#else   // HSM_DISABLE_DEBUG_TRACES
1794

1795
std::string HierarchicalStateMachine::Impl::getStateName(const StateID_t state) {
1796
    return std::string();
1797
}
1798

1799
std::string HierarchicalStateMachine::Impl::getEventName(const EventID_t event) {
1800
    return std::string();
1801
}
1802
#endif  // HSM_DISABLE_DEBUG_TRACES
1803

1804
}  // 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